0

I'm learning the assembly and the topic that puzzles me a bit is the function call conventions. There is an example of a simple function definition and call on this site, here is the code:

section .text
   global _start        ;must be declared for using gcc
    
_start:                 ;tell linker entry point
   mov  ecx,'4'
   sub     ecx, '0'
    
   mov  edx, '5'
   sub     edx, '0'
    
   call    sum          ;call sum procedure
   mov  [res], eax
   mov  ecx, msg    
   mov  edx, len
   mov  ebx,1           ;file descriptor (stdout)
   mov  eax,4           ;system call number (sys_write)
   int  0x80            ;call kernel
    
   mov  ecx, res
   mov  edx, 1
   mov  ebx, 1          ;file descriptor (stdout)
   mov  eax, 4          ;system call number (sys_write)
   int  0x80            ;call kernel
    
   mov  eax,1           ;system call number (sys_exit)
   int  0x80            ;call kernel
sum:
   mov     eax, ecx
   add     eax, edx
   add     eax, '0'
   ret
    
section .data
msg db "The sum is:", 0xA,0xD 
len equ $- msg   

segment .bss
res resb 1

I'm wondering WHY there is no standard function prolouge:

push ebp        ; save previous stackbase-pointer register
mov  ebp, esp   ; ebp = esp

and function epilogue?

How does it work without that? What assembly flavor the above code is? NASM, MASM or sth else?

Sorry if the question is a bit lame, but i'm rather a newbie. Thx for explanation.

Darox99
  • 19
  • 5
  • 2
    Prologue and epilogue are not required. The code does not use `ebp` so no need to set it up or save it. Even if it did, the convention only says what you must preserve, not how. Also for your own code you do not have to follow the convention anyway. Note this is linux code not windows as you tagged. It's nasm syntax. – Jester Jul 22 '23 at 10:30
  • @Jester Ok I've corrected the tag. Thx for explanation anyway. – Darox99 Jul 22 '23 at 10:32
  • @Jester By the way - how to recognize the flavor of the compiler looking by the code itself? – Darox99 Jul 22 '23 at 10:37
  • 1
    Once you know various assemblers you can match up the syntax. In this case the `resb` directive, the `$` symbol and the fact that bare labels mean addresses hint at being nasm. – Jester Jul 22 '23 at 12:42
  • Your code is NASM syntax, using Linux system calls. See [How to know if an assembly code has particular syntax (emu8086, NASM, TASM, ...)?](https://stackoverflow.com/q/44853636) for that part. This is defining `_start`, the ELF entry point, so it's not even a function. [Nasm segmentation fault on RET in \_start](https://stackoverflow.com/q/19760002) / [What happens if there is no exit system call in an assembly program?](https://stackoverflow.com/q/49674026). – Peter Cordes Jul 22 '23 at 16:43
  • Setting up EBP as a frame pointer is totally optional even in functions like `sum`. And not helpful at all with register args and no local variables. – Peter Cordes Jul 22 '23 at 16:44

1 Answers1

0

Let's suppose you have this stack (image borrowed from wikipedia):

Stack Image

To call the DrawSquare function, you first have to push its parameters on the stack. Then, you

call DrawSquare

and what this does is essentially

push eip ; push the current instruction pointer on the stack
jmp DrawSquare

so you now have the return address on the stack, too.

Then, the function you just called sets up its own stack frame.

push ebp

saves the previous function's base pointer (it will become clearer why, later); then

mov ebp, esp

moves the base pointer to where the stack pointer currently is (the pushed base pointer, in this case), making the base pointer point at the previous base pointer.

This way, we can freely move our stack pointer and restore it when we're done. Why am I saying that? Well, because one very important thing in functions is local variables, and you will store them on the stack.

So you make room for your local variables (you could also push them but conventionally you do it this way), and since the stack grows downwards, you move the stack pointer downwards, by subtracting from it.

sub esp, 4 ; make room for 4 bytes
           ; or one double word variable (32 bit)
           ; or two one-word variables (2 x 16 bit)
           ; or 4 byte variables (4 x 8 bit)

Then you can access these local variables by subtracting from the base pointer (that's why we need it to stay still at the beginning of the function frame)

mov [ebp - 4], 0xAABBCCDD ; move some random value (32 bit) into the new variable
                          ; subtract 4 from ebp because it's a 4-byte variable

You can also access the parameters by using the base pointer

mov eax, [ebp + 8] ; first 32 bit parameter
                   ; add 8 to ebp because
                   ; at [ebp] is the previous base pointer (32 bits)
                   ; and at [ebp + 4] is the return address (32 bits)
                   ; so 32 + 32 is 64, 8 bytes of offset to access the first parameter

Then we decide to call another function, DrawLine.

So we push its parameters on the stack, just like we did before; and call it.

push ebx
push eax
call DrawLine

The function initialises its stack frame just like before

push ebp ; DrawSquare's base pointer
mov ebp, esp

and when it's done, it can close its call frame just by resetting the stack pointer and the base pointer

mov esp, ebp ; moves the stack pointer to where the base pointer is.
             ; ebp still points to the old ebp
pop ebp ; pops the previous ebp value into ebp
        ; effectively restoring the previous functions' base pointer

Then the function will also use (if in stdcall) (with cdecl (which is another calling convention) the caller clears the parameters instead)

ret 8

to "clear" its parameters and restore the instruction pointer to where the call was made. Actually, the eip is restored before the parameters are cleared, since the parameters are below the eip on the stack; however we can't do that ourselves since after restoring the eip (returning) we can't do anything more in the funciton, and so with ret 8 we tell the processor to do

pop eip ; restore esp from the stack, where the return address lies

and

add esp, 8 ; move the stack pointer before the parameters, effectively "clearing" them

at the same time.

Then the DrawSquare function does the same thing when it's done.

^ This is how a conventional (stdcall) call stack frame works ^

In your case, _start is not even a function, it's the entry point of the program. Indeed it has no return address pushed onto the stack, and so the esp is actually pointing to argc.

The sum function has no local variables whatsoever, so the function frame would only slow down the program and use more space.

And since the stack frame is only useful when accessing parameters and/or local variables on the stack (both things can also be done by just using esp without the frame, but when adding local variables it's common to use the ebp for it doesn't move like esp does), it isn't needed here.

What that sum function does, is passing the parameters via registers, which is fine if your parameters are very few.

SerialSniper
  • 46
  • 1
  • 4
  • The OP's code is for Linux, where the standard 32-bit calling convention is the one defined in the i386 System V ABI document. It's the same as cdecl in many cases. But this code isn't using that convention, it's passing args in registers to `sum`. So you've written a long answer about how things work in some cases other than the OPs, and then just said it doesn't work like that. You didn't mention anything about `_start` not being a function (it's the ELF entry point, ESP points to `argc` not a return address) or anything like that. – Peter Cordes Jul 22 '23 at 16:47
  • `pop esp` should be `pop eip`. Also, you can address the stack relative to `esp` so you do not need a stack frame for arguments or local variables. – Jester Jul 22 '23 at 18:09
  • Sorry @PeterCordes I didn't notice it was a linux program, I just wanted to tell the op how a function frame works the way I know it, and then comparing it to what he has there. I also have now adapted my answer to fix `pop eip` and include what you both suggested. – SerialSniper Jul 22 '23 at 20:00