Linux assembly language development guide is reproduced from: IBM DeveloperWorks China website 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 Intel assembly format, the addressing method of memory operation is:
Section: [Base INDEX * Scale DISP] Since Linux works in the protection mode, the 32-bit linear address is used, so 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 The following is an example of some 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 # specify the entry Function_start: # Display a string MOVL $ LEN,% edx # parameter 3: String length MOVL $ msg,% ECX # parameter 2: String MOVL $ 1,% EBX # parameters 1: Document Descriptor (stdout) MOVL $ 4,% EAX # system call number (SYS_WRITE) INT $ 0x80 # Call kernel function # 退出 内 $ 0,% EBX # parameter 1: Exit code MOVL $ 1,% EAX # system call Number # calling the kernel function for the first time to come into the AT & T format assembly code, many programmers believe that it is difficult to understand, 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 Section Declaration MSG DB "Hello, World!", 0xA; String Len EQU $ - MSG; String length section .text; code segment declaration global _start; specify the entry function _ Start:; Display a string MOV EDX, LEN; parameter 3: String length MOV ECX, MSG; Parameter 2: String MOV EBX, 1; Parameter 1: File Descriptor (stdout ) MOV EAX, 4; system call number (SYS_WRITE) INT 0x80; calling kernel function; exit program MOV EBX, 0; parameter 1: Exit code MOV EAX, 1; system call number (SYS_EXIT) INT 0x80; call kernel function above Although the syntax used by the two assemblers is completely different, the functions are the 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 assembly syntax, which can be used to compile programs written in AT & T format: [xiaowp @ Gary Code] $ as -o hello.o hello.s Linux platform The other common assembler is NASM, it Provides a good macro function and support 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 linker is not running directly on the computer by the assembler, which must be handled by the linker to generate 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. The assembler can use LD to link it to an executable program after successful compilation of GAS or NASM and generate the target code.
[xiaowp @ Gary Code] $ ld -s -o hello hello.o 3. The debugger is said that the program is not compiled but the important role in which the debugging is in software development, especially when writing programs in assembly language. in this way. 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 a 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 parameters when performing the as command - Gstabs can tell the compliance to add a symbol table in the generated target code, but also pay attention to the link when using the LD command, do not add -s parameters, Otherwise, the symbol table in the target code 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 Assembly Language Debugger 0.1.3 Copyright (C) 2000-2002 Patrick Alken 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 BA0F000000 mov edx, 0xf 08048079 B998900408 mov ecx, 0x8049098 0804807E BB01000000 mov ebx, 0x1 08048083 B804000000 mov eax, 0x4 08048088 CD80 int 0x80 0804808A 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, which can be set in the breakpoint of the program execution:
ALD> BREAK 0X08048088 BREAKPOINT 1 SET for 0x08048088 After the breakpoint is set, start executing the program using the Run command. ALD at a breakpoint will automatically pause running the program, and it will display the current values of all registers: ald> run Starting program: hello Breakpoint 1 encountered at 0x08048088 eax = 0x00000004 ebx = 0x00000001 ecx = 0x08049098 edx = 0x0000000F esp = 0xBFFFF6C0 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 desired compilation step through the code Debug, you can use the next command:
ald> next Hello, world! eax = 0x0000000F ebx = 0x00000000 ecx = 0x08049098 edx = 0x0000000F esp = 0xBFFFF6C0 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
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, the system call function number will still be called when executing the INT 0x80 command. Save in the register EAX, only all parameters should be placed in a continuous memory area, while saving the pointer to the memory area 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 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 It 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"); for example:
__ASM __ ("NOP"); if you need to perform multiple assembled statements simultaneously, 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 statement usually embedded in C code is difficult to do with other There is no relationship, so there is more time you need to use a complete intra-container format: __ASM__ ("ASM Statements": Outputs: Inputs: Registers-Modified; the assembled statement inserted into the C code is ":" separate Four parts, wherein the first part is the assembly code itself, commonly referred to as an instruction unit, format, and the format used in the assembly language. 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. Below 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"); / * Untably register * / printf ("Result:% D,% D // n ", a, b);} The upper program completes the value of the value of the variable A, there is a need to explain that the variable B is the output operand, referenced by% 0, and the variable A is an input operation. Number, 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:
Limits "M", "V", "O" memory unit "R" any register "Q" register EAX, EBX, ECX, EDX "I", "H" direct operands "E" and "F "Floating point" g "arbitrarily" A "," B "," C "," D "represents register EAX, EBX, ECX, ESI, EDI" I "register ESI, EDI" I "constant (0 to 31) Eight, the small knot Linux operating system is written in C language, and the compilation will only be thought of when necessary, but it is a very important means of reducing code size and optimizing code performance, especially in hardware. When interacting, the 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
On the website http://linuxassembly.org/ you can find a large number of Linux compilation resources. 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 whose information 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.