The biggest difference between Assembly in Linux and Windows is that the first operand is the original operand, and the second is the destination operand, but the opposite is true in Windows.
1. Basic operation commands
There are three types of operations,
(1) Number of immediate operations, that is, constant values. The Writing Method of the immediate number is "$" followed by an integer, such as $ 0x1F. This will be seen in the detailed analysis below.
(2) Register operand, which indicates the content of a register. The symbol Ea is used to represent any register a and R [Ea] is used to represent its value, this is to regard the register set as an array R and use the register identifier as an index.
(3) the operand is a memory reference. It accesses a memory location based on the calculated address (usually called a valid address. The Mb [Addr] symbol represents a reference to the B-byte value stored in the memory starting from the address Addr. Generally, subscript B can be omitted.
Skipped. For more information, see understanding computer systems.
2. Simplified C code analysis
To simplify the problem, analyze the compilation code generated by the simplest c code:
# Vi test1.c
Int main ()
{
Return 0;
}
Compile the program to generate a binary file:
# Gcc test1.c-o test1
# File test1
Test1: ELF 32-bit LSB executable 80386 Version 1, dynamically linked, not stripped
Test1 is an executable file in the ELF format of 32-bit Little Endian. It is dynamically linked and the symbol table is not removed.
This is a typical executable file format on the Unix/Linux platform.
With mdb disassembly, you can observe the generated assembly code:
# Mdb test1
Loading modules: [libc. so.1]
> Main: dis; disassemble the main function. The command format of mdb is <address>: dis
Main: pushl % ebp; ebp register content pressure stack, that is, save the stack base address of the upper-level function called by the main function
Main + 1: movl % esp, % ebp; the esp value is assigned to ebp and the stack base address of the main function is set.
Main + 3: subl $8, % esp
Main + 6: andl $0xf0, % esp
Main + 9: movl $0, % eax
Main + 0xe: subl % eax, % esp
Main + 0x10: movl $0, % eax; Set function return value 0
Main + 0x15: leave; assign the ebp value to esp, pop the base address of the upper-level function stack in the previous stack to ebp, and restore the base address of the original Stack
Main + 0x16: ret; the main function returns to the upper-level call
>
Note: The syntax format of the assembly language obtained here is very different from that of Intel's manual. Unix/Linux uses the AT&T Assembly format as the syntax format of the assembly language.
For details about AT&T assembly, refer to: Linux AT&T Assembly Language Development Guide.
Q: Who calls the main function?
At the C language level, the main function is the initial entry point of a program. In fact, the entry point of the ELF executable file is not main but _ start.
Mdb can also disassemble _ start:
> _ Start: dis; disassembly starts from the address of _ start
_ Start: pushl $0
_ Start + 2: pushl $0
_ Start + 4: movl % esp, % ebp
_ Start + 6: pushl % edx
_ Start + 7: movl $0x80504b0, % eax
_ Start + 0xc: testl % eax, % eax
_ Start + 0xe: je + 0xf <_ start + 0x1d>
_ Start + 0x10: pushl $0x80504b0
_ Start + 0x15: call-0x75 <atexit>
_ Start + 0x1a: addl $4, % esp
_ Start + 0x1d: movl $0x8060710, % eax
_ Start + 0x22: testl % eax, % eax
_ Start + 0x24: je + 7 <_ start + 0x2b>
_ Start + 0x26: call-0x86 <atexit>
_ Start + 0x2b: pushl $ 0x80506cd
_ Start + 0x30: call-0x90 <atexit>
_ Start + 0x35: movl + 8 (% ebp), % eax
_ Start + 0x38: leal + 0x10 (% ebp, % eax, 4), % edx
_ Start + 0 x 3C: movl % edx, 0x8060804
_ Start + 0x42: andl $0xf0, % esp
_ Start + 0x45: subl $4, % esp
_ Start + 0x48: pushl % edx
_ Start + 0x49: leal + 0xc (% ebp), % edx
_ Start + 0x4c: pushl % edx
_ Start + 0x4d: pushl % eax
_ Start + 0x4e: call + 0x152 <_ init>
_ Start + 0x53: call-0xa3 <__ fpstart>
_ Start + 0x58: call + 0xfb <main>; the main function is called here.
_ Start + 0x5d: addl $ 0xc, % esp
_ Start + 0x60: pushl % eax
_ Start + 0x61: call-0xa1 <exit>
_ Start + 0x66: pushl $0
_ Start + 0x68: movl $1, % eax
_ Start + 0x6d: lcall $7, $0
_ Start + 0x74: hlt
>
Q: Why do I use the EAX register to save the function return value?
In fact, IA32 does not specify which register to use to save the returned value. However, if you disassemble the Solaris/Linux binary files, you will find that the function return values are saved using EAX.
This is not accidental. It is determined by the operating system's ABI (Application Binary Interface.
The ABI of the Solaris/Linux operating system is Sytem v abi.
Concept: SFP (Stack Frame Pointer) Stack framework Pointer
To understand SFP correctly, you must understand:
Concept of IA32 Stack
Functions of 32-bit register ESP/EBP in CPU
How the PUSH/POP command affects the stack
How CALL/RET/LEAVE and other commands affect the stack
As we know:
1) The IA32 stack is used to store temporary data, and it is LIFO, that is, the later, first, first, and foremost. Stack growth direction is from high address to low address, by byte.
2) EBP is the pointer of the stack base address, always pointing to the bottom of the stack (high address), ESP is the stack pointer, always pointing to the top of the stack (low address ).
3) PUSH a long data, in bytes as the unit of data into the stack, from high to low by byte data into the ESP-1, ESP-2, ESP-3, ESP-4 address unit.
4) POP a long type of data, the process and PUSH the opposite, in turn the ESP-4, ESP-3, ESP-2, ESP-1 from the stack popped up, put a 32-bit register.
5) The CALL command is used to CALL a function or process. At this time, the next instruction address is pushed into the stack for resuming execution of the next instruction when the return result is returned.
6) The RET command is used to return from a function or process. The next instruction address saved in the previous CALL will pop up from the stack to the EIP register, and the program will execute the next instruction before the CALL.
7) ENTER is the stack framework of the current function, which is equivalent to the following two commands:
Pushl % ebp
Movl % esp, % ebp
8) LEAVE is the stack framework for releasing the current function or process, which is equivalent to the following two commands:
Movl ebp esp
Popl ebp
If you disassemble a function, it is often found that there are Assembly statements similar to the following forms in the function entry and return:
Pushl % ebp; ebp register content pressure stack, that is, save the stack base address of the upper-level function called by the main function
Movl % esp, % ebp; the esp value is assigned to ebp and the stack base address of the main function is set.
..........; The preceding two commands are equivalent to enter 0, 0.
...........
Leave; assign the ebp value to esp. the base address of the upper-level function stack in the pop stack is given to ebp to restore the base address of the original stack.
Ret; the main function returns to the upper-level call
These statements are used to create and release a function or process stack framework.
The compiler automatically inserts statements for creating and releasing stack frameworks at the function entry and exit.
When a function is called:
1) EIP/EBP becomes the boundary of the new function Stack
When a function is called, The EIP returned is first pushed into the stack. When a stack framework is created, the EBP of the upper-level function stack is pushed into the stack, and the EIP works together to form the boundary of the new function stack framework.
2) EBP becomes the stack framework pointer SFP, which is used to indicate the boundary of the new function stack.
After the stack framework is established, the content of the stack that EBP points to is the EBP of the upper-level function stack. As you can imagine, through EBP, You can traverse the stacks that call function layers, the debugger uses this feature to implement the backtrace function.
3) ESP always points to the top of the stack as a stack pointer to allocate stack space
Stack allocation space to the function of local variables is usually the statement to ESP minus a constant value, for example, to assign an integer data is ESP-4
4) function parameter transfer and local variable access can be achieved through SFP or EBP.
Because the stack framework pointer always points to the stack base address of the current function, access to parameters and local variables is usually in the following form:
+ 8 + xx (% ebp); function entry parameter access
-Xx (% ebp); Function Local variable access
If function A calls function B and function B calls Function C, the function stack framework and call relationship are shown in:
+ ------------------------- + ----> High address
| EIP (the address returned by the upper-level function) |
+ ------------------------- +
+ --> | EBP (EBP of the upper-level function) | -- + <------ EBP of the current function A (that is, SFP framework pointer)
| + ------------------------- ++ --> Offset
| Local Variables |
| ...... | -- + <------ ESP points to the new local variable assigned by function A. local variables can be accessed through ebp-offset A of function.
| F + ------------------------- +
| R | Arg n (nth parameter of function B) |
| A + ------------------------- +
| M | Arg. (The. Parameter of function B) |
| E + ------------------------- +
| Arg 1 (the 1st parameters of function B) |
| O + ------------------------- +
| F | Arg 0 (0th parameters of function B) | -- + <------ parameters of function B can be accessed by ebp + offset B of function B.
| + ------------------------- ++ --> Offset B
| A | EIP (return address of function A) |
| + ------------------------- + -- +
+ --- | EBP (EBP of function A) | <-- + <------ EBP of function B (SFP framework pointer)
+ ------------------------- + |
| Local Variables |
| ...... ||||<------ ESP points to the newly allocated local variable of function B
+ ------------------------- + |
| Arg n (n parameter of Function C) |
+ ------------------------- + |
| Arg. (The. Parameter of Function C) |
+ ------------------------- + --> Frame of B
| Arg 1 (the 1st parameters of Function C) |
+ ------------------------- + |
| Arg 0 (0th parameters of Function C) |
+ ------------------------- + |
| EIP (return address of function B) |
+ ------------------------- + |
+ --> | EBP (EBP of function B) | -- + <------ EBP of Function C (SFP framework pointer)
| + ------------------------- +
| Local Variables |
| ...... | <------ ESP points to the newly allocated local variable of Function C.
| + ------------------------- + ----> Low address
Frame of C
Analyze the meaning of the remaining statement in the test1 disassembly result:
# Mdb test1
Loading modules: [libc. so.1]
> Main: dis; disassemble the main function
Main: pushl % ebp
Main + 1: movl % esp, % ebp; create a Stack Frame)
Main + 3: subl $8, % esp; allocate 8-byte stack space via ESP-8
Main + 6: andl $0xf0, % esp; alignment the stack address in 16 bytes
Main + 9: movl $0, % eax; meaningless
Main + 0xe: subl % eax, % esp; meaningless
Main + 0x10: movl $0, % eax; set the return value of the main Function
Main + 0x15: leave; Undo Stack Frame (Stack Frame)
Main + 0x16: ret; the main function returns
>
The following two statements seem meaningless. Are they true?
Movl $0, % eax
Subl % eax, % esp
Recompile test1.c with gcc's O2-level optimization:
# Gcc-O2 test1.c-o test1
# Mdb test1
> Main: dis
Main: pushl % ebp
Main + 1: movl % esp, % ebp
Main + 3: subl $8, % esp
Main + 6: andl $0xf0, % esp
Main + 9: xorl % eax, % eax; set the main return value. Use xorl exception or command to set eax to 0.
Main + 0xb: leave
Main + 0xc: ret
>
The new disassembly results are more concise than the original results. The previously considered useless statements were optimized and further verified.
Tip: some statements generated by the compiler may be useless in the actual semantics of the program. You can use optimization options to remove these statements.
Q: Why is xorl used to set the eax value?
Note that in the optimized code, the eax return value is changed from movl $0, % eax to xorl % eax, % eax, because the IA32 command is in, xorl is faster than movl.
Concept: aligned Stack aligned
So what are the roles of the following statements?
Subl $8, % esp
Andl $0xf0, % esp; Use andl to set the minimum 4 bits to 0 to ensure 16-byte alignment of stack addresses
On the surface, the most direct consequence of this statement is to make the last 4 bits of the ESP address 0, that is, 16-byte alignment. Why?
It turns out that some commands of the IA32 series CPU run faster when they are 4, 8, and 16 bytes aligned. Therefore, the gcc compiler improves the speed of code generation on IA32, the generated code is 16 bytes aligned by default.
The meaning of andl $0xf0 and % esp is obvious. So what about subl $8 and % esp?
Assume that the stack is 16-byte aligned before entering the main function. after entering the main function, the EIP and EBP are pushed to the stack, the last 4 binary digits of the stack address must be 1000, and esp-8 exactly makes the last 4 binary digits of the stack address 0000. It seems that this is to ensure 16-byte alignment of the stack.
If you check the gcc manual, you will find the stack alignment parameter settings:
-Mpreferred-stack-boundary = n; the stack is expected to be aligned according to the byte boundary of 2 n times. The value range of n is 2-12.
By default, n is equal to 4. That is to say, by default, gcc is 16-byte alignment to meet the requirements of most IA32 commands.
Let's use-mpreferred-stack-boundary = 2 to remove the stack alignment command:
# Gcc-mpreferred-stack-boundary = 2 test1.c-o test1
> Main: dis
Main: pushl % ebp
Main + 1: movl % esp, % ebp
Main + 3: movl $0, % eax
Main + 8: leave
Main + 9: ret
>
As you can see, the stack alignment command is gone, because the IA32 stack itself is 4-byte alignment and no additional commands are needed for alignment.
So is SFP a stack framework pointer necessary?
# Gcc-mpreferred-stack-boundary = 2-fomit-frame-pointer test1.c-o test
> Main: dis
Main: movl $0, % eax
Main + 5: ret
>
We can see that-fomit-frame-pointer can remove SFP.
Q: What are the disadvantages of SFP removal?
1) increase the difficulty of Adjustment
SFP is used in the command of the debugger backtrace, so it cannot be used without SFP.
2) reduce compilation code readability
Function parameters and local variables can only be accessed in the + xx (esp) mode without ebp, but it is difficult to distinguish between the two methods, reduced the readability of the program.
Q: What are the advantages of SFP removal?
1) Save stack space
2) the code is simplified after the instructions for setting up and revoking the stack framework are reduced.
3) make the ebp idle and use it as a general register to increase the number of General registers.
4) The above three points make the program run faster
Concept: Calling Convention call Convention and ABI (Application Binary Interface) Application Binary Interface
How does a function find its parameters?
How does a function return results?
Where does a function store local variables?
Which hardware register is the starting space?
Which hardware register must be reserved in advance?
The Calling Convention call Convention specifies the above issues. Calling Convention is also part of ABI.
Therefore, operating systems that comply with the same ABI specifications make it possible to implement binary code interoperability between them.
For example, because both Solaris and Linux comply with the ABI of System V, Solaris 10 provides the function of directly running the Linux binary program.
For details, see article: follow: 10 major new changes of Solaris 10
3. Summary
This article introduces the following concepts through the simplest C program:
SFP stack framework pointer
Aligned Stack aligned
Calling Convention call Convention and ABI (Application Binary Interface) Application Binary Interface
In the future, we will explore these concepts through further experiments. By mastering these concepts, the core dump generated by the Assembly-level debugging program and the advanced C language debugging skills become possible.