I'm currently learning x86 Assembly language (I'm at the start of the course) and I'm having some problems understanding how the stack works in one particular case.
Let's say I have this code:
double(entier n) { return n + n; }
I've tried to convert it into x86 code and I ended up with this :
push ebp #save old pointer stack
mov ebp, esp #put new pointer stack
mov ebx, dword[ebp + 8] #get argument n and put it in ebx
add ebx, dword[ebp + 8] #add n to ebx
But then I was totally blocked and couldn't find how to return the value of ebx
. I found a solution on internet that was the following:
mov [ebp + 12], ebx
pop ebp
ret
pop ebp
ret
I don't understand how it works. Isn't ebp+12
the value of the 2nd argument? (In my case there's none). The pop is used to move the esp
pointer but why do we need 2 pop and 2 return in that case ? Is it only to remove the value that have been used during the function declaration?
Since it appears that you are throughly confused by this, let me just show you how to do it:
double: push ebp ; establish...
mov ebp, esp ; ...stack frame
mov eax, [ebp + 8] ; load argument from stack into eax
add eax, eax ; add it to itself
leave ; tear down the stack frame
ret ; return to the caller
Note that I chose eax
instead of ebx
for the register. This is for two reasons:
eax
is a caller-saved register (meaning that the caller must take care to preserve its value if desired) whereas ebx
is a callee-saved register (meaning that the callee, i.e. double
must preserve its value). If we wanted to use ebx
, we had to save and restore its old value. If we use a caller-saved register like eax
instead, we can avoid this effort.eax
is the register where by convention the return value is found. The caller will take the value of eax
as the return value.
In almost all calling conventions for x86, the return value is whatever value is found in eax
on return. Thus, by placing the result of the addition in eax
, we don't have to do any extra work to set up the return value.
For future questions along these lines, I advise you to consult the output of a C compiler with optimisations turned on. The C compiler is very good at generating assembly and rarely makes any mistakes. On UNIX-like systems such as Linux, you can use the -S
option to generate assembly code. For gcc
I advice you to type
gcc -m32 -O3 -masm=intel -fno-omit-frame-pointer -S -o- source.c
to have assembly code for source.c
printed to the terminal in Intel syntax, which is the style of assembly you seem to be using.
See also How to remove "noise" from GCC/clang assembly output? for more details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With