Skip to main content

ASM x86 – Functions and Calls

· 5 min read
Strider

Hi, since I haven't written anything for a while (new project), I wanted to continue the x86 assembler series today.

In this part of the series, we look at how functions and function calls work in assembler. In the last part, we took a closer look at the stack and its operations. The stack will be used again here.

A function in assembler is similar to functions in high level languages like C/C++. A general structure of a function in assembler looks like this.

section .text
global _start:
_start:
...
call _func
...

_func:
;do staff
ret

But before we can create a function, we have to understand how the sequence looks like and how the single registers of the CPU have to be set. When we make a function call, the instruction pointer (EIP) is incremented or decremented by the relative address of the label. Because the EIP points to the label, the instructions that are at the label are executed one after the other. As soon as a ret-command comes, the value from the EBP is loaded into the EIP and we end up back where we were the last time. Here it is to be noted however that the registers should be saved before all, so that it does not come after a Return, to problems. I have created a small animation here, where you can see how a function call works in the core.

animation.gif

We now know how to make function calls, but how can we pass parameters to a function? The answer here is simple, we use the stack. On the stack we can put as much as we want. But here we have to make sure that we put the parameters on the stack in reverse order.

  1. parameter as the last on the stack,
  2. parameter as the last but one and so on.

dia.png

Here we see how a function looks like on the stack. To get the parameters from the stack, we have to increase our secured base pointer (EBP) by 8 and from there, always in 4 byte steps, we have to go up the stack. The first parameter is always at the position EBP+8EBP + 8, the 2nd at the position EBP+cEBP + c and so on... On the positions EBP+4EBP + 4 and EBP+0EBP + 0 are once the return address as well as the secured base pointer itself. If we now want to create and use a local variable, we simply go in the negative direction, i.e. EBP4EBP-4. What we see here is a stack frame. A stack frame is always created when a function is called. Let's look at a sample code for this.

section .text
global _start

_addition:
push ebp ; Save EBP
mov ebp, esp ; EBP now points to the stack pointer. Here we can now work as shown in the stack frame above.

mov eax, DWORD [ebp+0x8] ; Get parameter 1 from stack
mov ebx, DWORD [ebp+0xc] ; Get parameter 2 from stack

add eax, ebx
mov esp, ebp ; Restore old stack pointer
pop ebp ; Basepointer also restore
ret

_start:
push 0x2
push 0x2
call _addition
mov edx, eax
nop

We see here that we put 2 numbers on the stack. After that we make a function call for the _addition. Now from here it gets interesting, because we need to make sure that we build our stack frame. First we need to save our base pointer (EBP), on the stack. After that we just set our base pointer equal to our stack pointer and that's it. Now we can work normally according to the stack frame from above.

The parameters are now read from the stack and added. The result ends up in the EAX register, because the add command automatically stores the result in this register. If we want to exit the function, we have to make sure that we restore the base pointer and the stack pointer before we make the return.

I have translated the assembly code into C for better understanding.

int addition(int a, int b)
{
return a + b;
}

int main()
{
int y = addition(2, 2);
}

If we just run the whole thing now, it looks the result as in the recording:

asciicast

The creation as well as the destruction of a stack frame, can also be achieved with the commands enter and leave. I have modified here again the function _addition.

_addition:
enter 0, 0
mov eax, DWORD [ebp+0x8]
mov ebx, DWORD [ebp+0xc]
add eax, ebx
leave
ret

Here we see the command enter, which wants to have as first operand the stack size, and as 2nd operand the lexical scoping depth from 0...31. I have set both to 0. After that, we execute our addition, with both parameters again, and dismantle our stack frame, with the leave command.

I must admit, it may be hard at the beginning to understand how to create functions in assembler, but after some time it gets easier 😄.

I hope I could make you understand functions in x86 assembler a little bit. I hope you enjoyed it, and bye 😃