Record of experiments, readings, links, videos and other things that I find on the long road.
Registro de experimentos, lecturas, links, vídeos y otras cosas que voy encontrando en el largo camino.

Friday, May 18, 2012

Using the stack to pass parameters between assembly functions

For a new assignment in AETC I have to pass parameters between assembly functions using the stack instead of registers.

I've made a simple test using again the power example (like in previuos posts: [1] and [2]).
In this case, an assembly function called pow_caller, which in turn calls the p_pow function, is being called from C.

pow_caller:
push rbp ; Store stack initial state
mov rbp, rsp
sub rsp, 8 ; We make space for p_pow result by
; subtracting 8 bytes to the stack
; pointer (RSP)
push rdi ; We push the base (b) to the stack
push rsi ; We push the exponent (e) to the stack
call p_pow ; Now we call p_pow
add rsp, 16 ; We make RSP point to the
; position were the result is.
pop rax ; And move the result to rax so
; that it's returned to the C program
mov rsp, rbp ; Restore stack initial state
pop rbp
ret

As you can see, pow_caller is passing the parameters (base and exponent) to p_pow using the stack.
Before passing the parameters it's also reserving some space (8 bytes) for the result of p_pow to be returned.
That's what sub rsp, 8 is doing, (remember that the stack grows towards decreasing addresses).

Once p_pow finishes, we have to get the result from the stack. To do it we go to the place where the result is by moving the stack pointer RSP (add rsp, 16) and, once there, get the result from the stack moving it to the RAX register (pop rax), so that it's returned to the C program when pow_caller ends.
Before executing add rsp, 16, the result of p_pow was in memory at address rbp+16, but after executing add rsp, 16 it was at address rbp, so it can be reached just using pop.

It's very important to remark that the state of the stack (RSP and RBP) must be the same before and after the function to avoid funny bugs when you go back to the caller.

In pow_caller we've pushed the content of 3 registers (RBP, RDI and RSI) to the stack (24 bytes) and subtracted 8 bytes to store the result of p_pow, that's 32 bytes, so that, $rsp' = $rsp - 32, where $rsp is the content of RSP when entering p_pow_caller and $rsp' is its current value.
After p_pow finished, we added 16 bytes to RSP and then made two pops (another 16 bytes), so at the end $rsp' = $rsp again.
That last pop made also that $rbp' = $rbp again.

It only remains to see how p_pow accesses the stack to get the parameters and store the result.

Ok, so again the first thing we do is pushing the initial value of RBP (the value in p_pow_caller), so that we can get it back once the function has finished.

Then we copy in RBP the initial content of RSP+8 (because we did a pop). We do this because we want to be able to go on using the stack doing push and pop (which changes RSP), but we also want to be able to reach the parameters that are at certain distance in memory of the original RSP.
This explains why to get a parameter from the stack we use [rbp+distance].
In p_pow_caller we pushed the base first and then the exponent. Since the stack is a LIFO data structure, the exponent is closer to RBP than the base.
How far is it? Well, before pushing p_pow initial RBP content, the exponent was at a distance of 8 bytes from the caller's RSP and the base at a distance of 16. After pushing RBP, the distance is 8 bytes more: base at 28 and exponent at 16 bytes distance.
In that moment we stored the content of RSP inside RBP, so that later changes to RSP cannot change the distance to the parameters. That's the reason why we are able to grab the base and exponent by doing mov ebx, dword[rbp+24] and mov ecx, dword[rbp+16], respectively.

When we've computed the power, we store it in the place that we reserved for it before pushing the parameters (which is 8 bytes farther than the base) doing mov [rbp+32], rax

At the end of p_pow we restore the initial state of the stack (RBP and RSP) again before returning to p_pow_caller.