Disassembly Training

In an attempt to understand what is happening under this, I make small C programs and then change it and try to understand its objdump output.

Program C:

#include <stdio.h> int function(int a, int b, int c) { printf("%d, %d, %d\n", a,b,c); } int main() { int a; int *ptr; asm("nop"); function(1,2,3); } 

The objdump result for the function gives me the following.

 080483a4 <function>: 80483a4: 55 push ebp 80483a5: 89 e5 mov ebp,esp 80483a7: 83 ec 08 sub esp,0x8 80483aa: ff 75 10 push DWORD PTR [ebp+16] 80483ad: ff 75 0c push DWORD PTR [ebp+12] 80483b0: ff 75 08 push DWORD PTR [ebp+8] 80483b3: 68 04 85 04 08 push 0x8048504 80483b8: e8 fb fe ff ff call 80482b8 < printf@plt > 80483bd: 83 c4 10 add esp,0x10 80483c0: c9 leave 

Note that before calling printf, three DWORD stacks with offsets of 8,16,12 (they must be the function argument in the reverse order) are pushed onto the stack. Later, the hex address is pressed, which should be the address of the format string.

My doubt is

  • Instead of directly pushing 3 DWORDS and the format specifier onto the stack, I expected esp to be manually reduced, and after that the values ​​would be pushed onto the stack. How can this behavior be explained?
+4
source share
6 answers

Well, on some machines there is a stack pointer, which seems to be like any other register, so the way you press something is yes, with a decree followed by a store.

But some machines, such as x86 32/64 , have a push command that executes a macro: pointer decrement and storage execution.

Macro opera, by the way, has a funny story. Sometimes some examples on some machines were slower than performing basic operations with simple instructions.

I doubt this happens often today. Modern x86 is surprisingly sophisticated. The CPU will disassemble your opcodes in microoperations, which are then stored in the cache. Micro-operations have specific requirements for the pipeline and the time interval, and the end result is that there is currently a RISC processor inside x86 in x86, and all this is very fast and has good code density at the architecture level.

+4
source

The stack pointer is configured using the push command. Thus, it is copied to ebp , and the parameters are inserted into the stack so that they exist in 2 places: the function stack and the printf stack. push es affects esp , so ebp copied.

+1
source

There is no command mov [esp + x], [ebp + y], too many operands. This will require two instructions and the use of a register. Push does this in one instruction.

+1
source

This is the standard cdecl calling convention for an x86 machine. There are several different types of calls. You can read the following Wikipedia article:

http://en.wikipedia.org/wiki/X86_calling_conventions

It explains the basic principle.

+1
source

You are raising an interesting point, which, I think, has not yet been addressed directly. I suppose you saw assembly code that looks something like this:

 sub esp, X ... mov [ebp+Y], eax call Z 

This kind of disassembly is generated by some compilers. What he does is expand the stack and then assign the value of the new eax space (which I hope was filled with something meaningful for this point). This is actually equivalent to what push mnemonics do. I cannot answer why some compilers generate this code instead, but I assume that at some point its execution was rated as more efficient.

0
source

In your efforts to learn assembly language and disassemble binary files, you may find ODA useful. This is a web disassembler that is convenient for disassembling many different architectures without the need to create binutil objdump for each of them.

http://onlinedisassembler.com/

0
source

Source: https://habr.com/ru/post/1334979/


All Articles