NASM

xiaoxiao2021-03-06  90

Linux Assembly Language Development Guide

content:

I. Introduction II, Linux compilation language format three, Hello World! IV, Linux assembly tool 5. System call six, command line parameters seven, GCC Inline Continent eight, small knot nine, reference information about the author

related information:

Introduction

In the Linux area:

Tutorial Tools & Product Codes and Component Project Articles

Xiao Wenpeng (xiaowp@263.net) Master's degree in Computer Department, Beijing University of Science and Technology July 2003

The advantages of assembly language are fast, and hardware can be done directly, which is very important to key applications such as graphic processing. Linux is a operating system developed with C language, which makes many programmers to have forgotten to optimize the performance of the program directly in Linux. This article provides a guide to programmers who write assembly code on the Linux platform, introduce the syntax format and development tools of Linux assembly language, and supplemented to specific examples to develop practical Linux assemblers.

I. Introduction as one of the most basic programming languages, although the application is not very wide, the importance is not doubtful because it can complete the functions that many other languages ​​cannot be completed. Take Linux kernels, although most of the code is written in C language, but still inevitably use assembly code in some critical places, which is mainly in the LINUX start section. Since this part of the code is very close to hardware, even C language will have some strength, and assembly language can be very good to avoid short, maximize the performance of hardware. In most cases, Linux programmers do not need to use assembly language, because even if the hardware driver can be implemented in the Linux operating system, they can be implemented using the C language in the Linux operating system, plus GCC this excellent compiler is currently Good optimization for the final generated code, indeed enough reasons to allow us to temporarily throw the assembly language. But the implementation is that Linux programmers sometimes need to use compilation, or have to use compilation, reason is simple: streamline, efficient and libc independence. Assume that it is necessary to transplant Linux to a particular embedded hardware environment, first, it will necessarily face problems such as system size, improve execution efficiency, and maybe only assembly languages ​​can be helpless. The assembly language is directly interacting with the underlying software and even hardware of the computer. It has some advantages:

Ability to access the memory or I / O port associated with the hardware; can be completely controlled by the generated binary code without being limited by the compiler; can be more accurately controlled to the critical code or hardware devices The deadlock caused by sharing; the best optimization can be performed according to a specific application, improve the running speed; to maximize the function of hardware. At the same time, it should also be recognized that assembly language is a very low language, which is only higher than the binary machine instruction code directly, so it is inevitably there are some shortcomings:

The code written is very difficult to understand, it is not good to maintain; it is easy to generate bugs, it is difficult to debug; it can only be optimized for a specific architecture and processor; the development efficiency is very low and monotonous. The code written in the assembly language is two different forms under the LINUX. The first is a complete assembly code refers to all the procedures all written in assembly language. Although it is a complete assembly code, the assembly tool under the Linux platform also absorbs the strengths of the C language, so that the programmer can use the # include, # ifdef, etc., and can simplify the code through the macro definition. The second is the embedded assembly code refers to the assembly code segment that can be embedded into the C language program. Although there is no corresponding provision regarding embedded assembly code in ANSI's C language standard, all actually used C compilers have done this extension, which of course include GCC under the Linux platform. Second, Linux compilation synthetic format Most Linux programmers have previously contacted assembly languages ​​under DOS / Windows, which are Intel style. However, in UNIX and Linux systems, more use or AT & T formats, both have great differences in grammatical format: In the AT & T assembly format, the register name is to add '%' as a prefix; and in the Intel assembly format In the case, the register name does not require a prefix. E.g:

At & T Format Intel Format Pushl% EaxPush Eax In the AT & T assembly format, use the '$' prefix represent an immediate operand; and in the Intel assembly format, the immediate representation does not have to bring any prefix. E.g:

At & T Format Intel Format Pushl $ 1PUSH 1 AT & T and Intel format The source operand in the Intel format The position of the target operand is exactly the opposite. In the Intel assembly format, the target operands are on the left of the source operation; in the AT & T assembly format, the target operand is on the right side of the source operand. E.g:

AT & T format Intel format ADDL $ 1,% EaxAdd Eax, 1 In the AT & T assembly format, the word length of the operand is determined by the last letter of the operator, the suffix 'b', 'w', 'L' represents the operand, respectively. (Byte, 8 Bits), Word (Word, 16 Bits) and Longlings (Long, 32 Bits); In Intel assembly format, the word length of the operand is prefixed with "Byte PTR" and "Word PTR". It is expressed. E.g:

AT & T Format Intel Format MOVB VAL,% Almov Al, Byte Ptr Val In the AT & T assembly format, the absolute transfer and call command (JUMP / CALL) should be added to '*' as a prefix, and in the Intel format need. The operation code of the remote transfer instruction and the remote subcarery command is "ljump" and "LCALL" in the AT & T assembly format, and "JMP FAR" and "Call Far" in the Intel assembly format, namely:

AT & T format Intel format ljump $ section, $ offsetjmp far section: offsetlcall $ section, $ offsetcall far section: offset corresponding remote therewith return instruction was: AT & T format Intel format lret $ stack_adjustret far stack_adjust in AT & T assembly format, the memory operation The number of addressing is

Section: DISP (Base, INDEX, Scale)

In the Intel assembly format, the addressing method of memory operation is:

Section: [Base INDEX * Scale DISP]

Since Linux works in the protection mode, use a 32-bit linear address, so that the segment base address and the offset are not considered when the address is calculated, but is used as the following address calculation method:

DISP BASE INDEX * SCALE

Here are some examples of memory operations:

AT & T format Intel format MOVL-4 (% EBP),% EAXMOV Eax, [EBP - 4] MOVL Array (,% EAX, 4),% Eaxmov Eax, [EAX * 4 Array] MOVW Array (% EBX,% EAX , 4),% CXMOV CX, [EBX 4 * EAX Array] MOVB $ 4,% FS: (% EAX) MOV FS: Eax, 43, Hello World! I really don't know what to break this tradition. The consequences, but since all programming languages ​​have a string "Hello World!" On the screen, then we also introduce the assembly language programming under Linux in this way. In the Linux operating system, you have many ways to display a string on the screen, but the most concise way is to use the system calls provided by the Linux kernel. The biggest benefit of using this method is to communicate directly with the kernel of the operating system, no need to link a library such as libc, and do not need to use an ELF interpreter, so the code size is small and the speed is quickly. Linux is a 32-bit operating system running in protection mode, using Flat Memory mode, currently used by the binary code in ELF format. An executable program in an ELF format is usually divided into the following sections: .text, .data, and .bss, where .text is a read-only code area, .data is a readable data area, and .bsss are Recognitive data area that can be written and there is no initialization. The code area and data area are known as section in ELF, and you can use other standard sections according to actual needs, but you can add custom sections, but an ELF executable should have at least one .Text section. Let's give our first assembler, use AT & T assembly language format: Example 1. AT & T format

# Hello.s

.DATA # Data segment declaration

Msg: .string "Hello, World! // N" # To output the string

Len =. - MSG # string length

.Text # code segment declaration

.global _Start # Specifies the entry function

_Start: # Display a string on the screen

MOVL $ LEN,% EDX # parameter 3: String length

MOVL $ MSG,% ECX # parameter 2: String MOVL $ 1,% EBX # parameter 1,% EBX # parameter 1: File Descriptor (STDOUT)

MOVL $ 4,% EAX # system call number (sys_write)

INT $ 0x80 # Call kernel function

# exit the program

MOVL $ 0,% EBX # parameter 1: Exit code

MOVL $ 1,% EAX # system call number (SYS_EXIT)

INT $ 0x80 # Call kernel function

When you first come into contact with the assembly code in the AT & T format, many programmers believe that it is too embarrassing, there is no relationship, you can also use Intel format on the Linux platform to write assembler: Example 2. Intel format

Hello.asm

Section .data; Data segment declaration

MSG DB "Hello, World!", 0xA; string to be output

LEN EQU $ - MSG; string length

Section .text; code segment declaration

Global_Start; Specifies the entry function

_Start:; Display a string on the screen

Mov Edx, Len; Parameter 3: String Length

MOV ECX, MSG; Parameter 2: String to display

MOV EBX, 1; Parameter 1: File Descriptor (stdout)

MOV EAX, 4; system call number (Sys_WRITE)

INT 0x80; call kernel function

; exit the program

MOV EBX, 0; parameter 1: Exit code

MOV EAX, 1; system call number (SYS_EXIT)

INT 0x80; call kernel function

Although the syntax used above the above assembler is completely different, the function is all Sys_WRITE that calls the Linux kernel to display a string, and then call the SYS_EXIT exit program. In Linux kernel source file include / ASM-I386 / Unistd.h, you can find definitions of all system calls. Fourth, Linux Assembly Tools Linux platform is a lot, but like DOS / Windows, the most basic still is still assembler, connector, and debugger. 1. The role of the assembler assembler is to convert the source program written in assembly language into binary forms of target code. The standard assembler of the Linux platform is GAS, which is a background assembly tool that GCC depends on, which is usually included in a binutils package. GAS uses standard AT & T assemble syntax, which can be used to compile programs written in AT & T format:

[xiaowp @ Gary Code] $ as -o hello.o hello.s

Another commonly used assembler on the Linux platform is NASM, which provides a good macro function and supports considerable target code format, including BIN, A.out, Coff, ELF, RDF, etc. NASM uses artificial written syntax analyzer, thus performing a lot of speed than GAS, but it is more important to use Intel compilation syntax, which can be used to compile assembler written in Intel syntax format:

[xiaowp @ Gary Code] $ NASM -F Elf Hello.asm

2. The target code generated by the assembler cannot be run directly on the computer, which must be handled by the linker to generate an executable code. The linker is usually used to connect multiple target code into an executable code, so you can divide the entire program into several modules to be developed separately, and then combine them (link) into an application. Linux uses LD as a standard linker, which is also included in the binutils package. After successful compiling and generates the target code through the GAS or NASM, the assembler can use LD to link it into an executable program: [xiaowp @ Gary Code] $ ld -s -o hello hello.o

3. The debugger said that the program is not compiled but the important role in which the debugging is in software development, especially when writing a program with assembly language. Linux Upset Mission code can use GDB, DDD's commissioning, or use ASSEMBLY LANGUAGE DBUGGER that is specifically used to invoke assembly code. From the perspective of debugging, the advantage of using GAS is that the symbol table can be included in the generated target code so that GDB and DDD can be used to debug the source level. To include the symbol table in the generated executable, you can compile and link below:

[xiaowp @ Gary Code] $ as --gstabs -o hello.o hello.s

[xiaowp @ Gary Code] $ ld -o hello hello.o

Bring the parameter when executing the as command - Gstabs can tell the compliance with the symbol table in the generated target code, but also note that do not add -s parameters when using the LD command to link, otherwise the target code The symbol table will be deleted when the link is linked. In the GDB and DDD, the debugging code and debug C language code are the same, you can interrupt the program's operation, view the current value of the program, and can check the code by setting breakpoints. Figure 1 is a scenario at the time of debugging code in DDD: Figure 1 During the DDD, the test assembly assembly programming program is usually facing some of the harsh hardware and software environments, and short-term ALD may be more in line with the actual needs. The following mainly introduces how to call the assembler with ALD. First execute the ALD command in the command line mode to start the debugger, the parameter of this command is the executable that will be debugged:

[xiaowp @ Gary Doc] $ ALD Hello

AskEMBLY LANGUAGE DEBUGER 0.1.3

CopyRight (C) 2000-2002 Patrick Alkeen

Hello: ELF Intel 80386 (32 BIT), LSB, Executable, Version 1 (Current)

Loading Debugging Symbols ... (15 Symbols loaded)

ALD>

When the ALD prompt appears, use the disassemble command to disassemble the code segment:

ALD> Disassemble -s .text

Disassembling section .text (0x08048074 - 0x08048096)

08048074 bang000000 MOV EDX, 0xF

08048079 B998900408 MOV ECX, 0X8049098

0804807E BB01000000 MOV EBX, 0x1

08048083 B804000000 MOV EAX, 0x4

08048088 CD80 INT 0X800804808A BB00000000 MOV EBX, 0x0

0804808F B801000000 MOV EAX, 0x1

08048094 CD80 INT 0x80

The first column of the above output information is an address code corresponding to the instruction, and it can be set at the breakpoint of the program execution:

Ald> Break 0x08048088

Breakpoint 1 set for 0x08048088

After the breakpoint is set, use the run command to start executing the program. ALD will automatically suspend the run when you encounter a breakpoint, and the current value of all registers is displayed:

ALD> RUN

Starting Program: Hello

Breakpoint 1 Encountered AT 0x08048088

EAX = 0x00000004 EBX = 0x00000001 ECX = 0x08049098 EDX = 0x0000000F

ESP = 0xBfffff6c0 EBP = 0x00000000 ESI = 0x00000000 EDI = 0x00000000

DS = 0x0000002B ES = 0x0000002B fs = 0x00000000 GS = 0x00000000

SS = 0x0000002B CS = 0x00000023 EIP = 0x08048088 EFLAGS = 0x00000246

Flags: PF ZF IF

08048088 CD80 INT 0x80

If you need a single-step debugging of the assembly code, you can use the next command:

ALD> NEXT

Hello, World!

EAX = 0x0000000f EBX = 0x00000000 ECX = 0x08049098 EDX = 0x0000000F

ESP = 0xBfffff6c0 EBP = 0x00000000 ESI = 0x00000000 EDI = 0x00000000

DS = 0x0000002B ES = 0x0000002B fs = 0x00000000 GS = 0x00000000

SS = 0x0000002B CS = 0x00000023 EIP = 0x0804808f EFLAGS = 0x00000346

Flags: PF Zf TF IF

0804808F B801000000 MOV EAX, 0x1

If you want to get a detailed list of all debug commands supported by ALD, you can use the help command:

ALD> HELP

Commands may be abbreviated.

IF a Blank Command Is Entered, The Last Command is Repeated.

TYPE `HELP 'for more specifications on .

General Commands

Attach Clear Continue Detach Disassemble

Enter Examine File Help loadingNext Quit Register Run Set

Step unloaded window write

BreakPoint Related Commands

Break delete disable enable ignore

LBreak TBREAK

5. System calls Even the simplest assembler, it is inevitable to use operations such as input, output, and exit, and do these operations require calling the service provided by the operating system, which is the system call. Unless your program completes mathematical operation such as the addition and subtraction, the system call is used, it will be difficult to avoid using system calls. In fact, the assembly programming programming of various operating systems is often similar in addition to the system calls. There are two ways to use system calls under the Linux platform: use the packaged C library (libc) or directly call it through the assembly. The system calls are directly called by assembly language, which is the most efficient use of the Linux kernel service, because the final generated program does not need to be linked to any library, but directly communicate with kernel. Like DOS, the system call under Linux is also implemented by the interrupt (int 0x80). When the INT 80 command is executed, the register EAX is stored in the function number of the system call, and the parameters that pass to the system call must be placed in the register EBX, ECX, EDX, ESI, EDI, after the system call is completed, The return value can be obtained in the register EAX. All system call function numbers can be found in file /usr/include/bits/syscall.h, for easy use, they are defined with SYS_WRITE, SYS_EXIT, etc. in sys_write, sys_exit, etc. for easy use. For example, the WRITE function that is often used is defined below:

SSIZE_T WRITE (INT FD, Const Void * BUF, SIZE_T COUNT);

The function of this function is ultimately implemented by system calls for SYS_WRITE. According to the above convention, the parameters FB, BUF, and COUNT exist in registers EBX, ECX, and EDX, and the system call number SYS_WRITE is placed in the register EAX. When the INT 0x80 instruction is executed, the return value can be obtained from the register EAX. Perhaps you have found that only 5 registers can be used to save parameters when performing system calls, is there no more than 5 parameters? Of course, there are 6 parameters, such as the MMAP function, which last needs to be passed to the system call SYS_MMAP:

Void * mmap (void * start, size_t length, int prot, int flags, int fd, off_t offset);

When the number of parameters required for a system call is greater than 5, it is still necessary to save the system call function number in the register EAX when executing the INT 0x80 command, and the different parameters should be placed in a continuous memory area. The pointer to the memory area is saved in the register EBX. After the system call is complete, the return value will remain in the register EAX. Since only a continuous memory area is required to save the parameters of the system call, it can be used to use the stack to transfer the parameters required for the system call like a normal function call. But note that Linux uses the C language call mode, which means that all parameters must be in the opposite order, that is, the last parameter is first in the stack, and the first parameter is finally set. If you use the stack to transfer the parameters required to call the system call, you should copy the current value of the stack pointer to the register EBX when executing the INT 0x80 command. 6. Command line parameters In the Linux operating system, when a executable starts through the command line, the parameters they need will be saved to the stack: first is Argc, then point to the pointer argument argument argument argument of each command line parameter, Finally, the pointer data ENVP is directed to the environment variable. When writing assembly language programs, many times you need to process these parameters. The following code demonstrates how to process the command line parameters in assembly code: Example 3. Processing command line parameters # args.s

.Text

.globl _Start

_Start:

POPL% ECX # argc

VNEXT:

POPL% ECX # argv

TEST% ECX,% ECX # empty pointer indicates the end

JZ EXIT

MOVL% ECX,% EBX

XORL% EDX,% EDX

Strlen:

MOVB (% EBX),% Al

INC% EDX

INC% EBX

TEST% Al,% Al

JNZ Strlen

MOVB $ 10, -1 (% EBX)

MOVL $ 4,% EAX # system call number (sys_write)

MOVL $ 1,% EBX # file descriptor (stdout)

INT $ 0x80

JMP VNEXT

EXIT:

MOVL $ 1,% EAX # system call number (SYS_EXIT)

XORL% EBX,% EBX # Exit code

INT $ 0x80

RET

Seven, the program written by the online container assembly of the GCC is fast, but the development speed is very slow, the efficiency is also very low. If you just want to optimize key code segments, better ways to embed assembly instructions into C language programs to make full use of their own characteristics of advanced languages ​​and assembly languages. However, in general, embedded complications in C code is much more complicated than "pure" assembly language code, because it is necessary to resolve how to assign registers, and how to combine with variables in C code. GCC provides a good inline assembly support, the most basic format:

__ASM __ ("ASM Statements");

E.g:

__ASM __ ("NOP");

If you need to perform multiple assembled statements at the same time, you should use "// n // t" to separate each statement, for example:

__ASM __ ("Pushl %% EAX // N // T"

"MOVL $ 0, %% EAX // N // T"

"POPL% EAX");

The assembled statements typically embedded in the C code are difficult to do with other parts without any relationship, so there is more time you need to use a complete intra Lianghui format:

__ASM __ ("ASM Statements": Outputs: Inputs: registers-modified;

The assembly statement inserted into the C code is four parts separated by ":", where the first part is the assembly code itself, commonly referred to as an instruction portion, format, and the format used in the assembly language is basically the same. The instruction portion is a must, while the other ports can be omitted according to the actual situation. When the assembly statement is embedded in the C code, the operand is a big problem with the combination of variables in the C code. The GCC uses the following method to solve this problem: the programmer provides specific instructions, and the use of the register only gives "model" and constraints, how to combine the registers and variables completely from GCC and GAS Be responsible for. In the instruction portion of the GCC, the number of prefix '%' (as% 0,% 1) is represented by the prefix '%', is a "sample" operand that needs to use the register. Several model operands are used in the instruction unit, which indicates that several variables need to be combined with registers so that GCC and GAS are properly processed according to the following constraints in compilation and assembly. Since the model operand also uses '%' as a prefix, there should be two '%' in front of the register name when it involves a specific register, so as to avoid confusion. The output unit is followed by the output unit, which is the condition that the output variable is combined with the sample operand, and each condition is called a "constraint". If necessary, multiple constraints can be included in each other. OK can be opened. Each output constraint starts with the '=' number, and then follows the word that will be described with the operand type, it is finally constrained with the variable. Any register or operand that is combined with the operand described in the output unit itself does not retain the execution before executing the embedded assembly code, which is the basis used when the GCC is used in the scheduler. The output portion is similar to the input unit, the input constraints, and the output constraints, but without the '=' number. If an input constraint requires the register, the GCC assigns a register for it when preprocessing and inserts the necessary instructions to load the operands into the register. The registers or operands combined with the operand described in the input unit itself, and the previous content before performing the embedded assembly code is not retained. Sometimes when some operations are performed, in addition to using registers for data input and output, multiple registers are used to save the intermediate calculation results, which is inevitably destroying the contents of the original registers. In the last portion of the GCC inline contrast format, a register that generates side effects can be described so that the GCC can adopt corresponding measures. The following is a simple example of an inline assembly: Example 4. Inline assembly / * inline.c * /

int main ()

{

INT A = 10, b = 0;

__asm__ __volatile __ ("MOVL% 1, %% EAX; // n // r"

"MOVL %% EAX,% 0;"

: "= r" (b) / * Output * /

: "R" (a) / * input * /

: "% EAX"); / * Unaffected register * /

Printf ("Result:% D,% D // N", A, B);

}

The above program completes the value of the value of the variable A to the variable B, there is a need to explain:

Variable B is the output operand, referenced by% 0, and the variable A is the input operand, referenced by% 1. The input operands and the output operand are constrained using R, indicating that the variables A and variable B are stored in the register. The difference between the input constraints and output constraints is that the output constraints are more than one constraint modifier '='. When the register EAX is used in the inner joint statement, two '%' should be added before the register name, namely %% EAX. Inline assembly uses% 0,% 1, etc. to identify variables, any identifier with only one '%' is regarded as an operand, not a register. The last part of the inner linked statement tells the GCC that it will change the value in the register EAX, and the GCC should not use the register when processed to store any other value. Since the variable B is specified as an output operand, the value it saved will be updated when the internal exchange representation is executed. The operand used in the inner assembly is numbered from the first constraint from the output unit, and the serial number starts from 0, each constraint count, when the instruction unit is to reference these operands, only need to be added before the serial number '%' Is a prefix. It should be noted that the instruction portion of the inner contractual statement is always used as a 32-bit long word when referenced by reference, but the actual situation may need a word or byte, so it should be correct in the constraint. Limits: Limizes "M", "V", "O" memory unit "R" Any register "Q" register EAX, EBX, ECX, EDX "I", "H" direct operand "E" And "F" Floating point number "G" arbitrary "A", "B", "C", "D" represents register EAX, EBX, ECX, ESI, ESI, EDI "I" constant, respectively, EDI "i" constant (0 to 31) Eight, the small knot Linux operating system is written in C language, and the compilation is only considered when necessary, but it is a very important means of reducing code size and optimizing code performance, especially When interacting with hardware, assembly can be said to be the best choice. Linux provides a very good tool to support the development of assembler, using GCC's inline assembly to fully play their own advantages in C language and assembly language. Nine, reference materials

A large number of Linux compilation resources can be found on the website http://linuxassembly.org. Package Binutils provides practical tools such as AS and LDs, which can be found on the website http://sources.redhat.com/binutils/. NASM is an assembler in Intel format, which can be found on the website http://nasm.sourceforge.net. Ald is a short-fierce assembly adjustment, which can be found on the website http://dunx1.irt.drexel.edu/ ~ psa22/ald.html. Intel2gas is a gadget that converts Intel assembly formats into AT & T assembly format, which can be found on the website http://www.niksula.cs.hut.fi/~mtiihone/intel2gas/. There is an article on the IBM DeveloperWorks introducing the IBC inline assembly (http://www-900.ibm.com/developerworks/cn/linux/sdk/ssemble/inline/index_eng.shtml). This article code download: code.

About author

This article author Xiao Wenpeng is a master's degree from the computer department of Beijing Institute of Technology, mainly engaged in the research of operating systems and distributed computing environments, love Linux and Python. You can contact him through xiaowp@263.net.

Page

转载请注明原文地址:https://www.9cbs.com/read-106136.html

New Post(0)