-1

I am thinking about how a function call works in assembler. Currently I think it works like:

push arguments on stack
push eip register on stack and setting new eip value over jump  # call instruction

# callee's code
push ebp register on stack
working in the function
returning from function
pop ebp
pop eip       # ret instruction

so but now I am thinking about it, how does assembler save the current stack pointer?

For example if I have some local variables the esp(stack pointer) goes down and if I come back to the main function assembler has to set the esp pointer to the right place but how does this work ?

assemblerMan
  • 37
  • 1
  • 1
  • 3
  • 1
    The architectures I use don't have such registers. And the C standard does not require a stack. Your question is too broad. How about reading the assembly code generated by your compiler and reading a book about compiler construction? – too honest for this site Dec 19 '16 at 21:55
  • 3
    Even within a compiler, there can be [multiple calling conventions](https://blogs.msdn.microsoft.com/oldnewthing/20040108-00/?p=41163). – ikegami Dec 19 '16 at 21:56
  • 1
    ESP is call-preserved in all calling conventions, so code can assume it wasn't modified by the call. EBP is also call-preserved in all calling conventions I'm aware of. See http://stackoverflow.com/tags/x86/info – Peter Cordes Dec 19 '16 at 22:02
  • [This page](http://unixwiz.net/techtips/win32-callconv-asm.html) may help. – John Bode Dec 19 '16 at 22:07
  • Re: your last edit that removed my `and jump` from the pseudo-code for `call`: pushing a register only reads it. e.g. `push eax` doesn't modify EAX, so IDK why you think `push eip` would also set EIP to the address of the function you want to call. Even in an ISA like ARM that exposes the program-counter as a normal register, it takes two instructions to emulate a call: one to put the return address somewhere, and a separate one to jump. (See [my answer on another question](http://stackoverflow.com/questions/8333413/why-cant-you-set-the-instruction-pointer-directly/41150027#41150027).) – Peter Cordes Dec 19 '16 at 22:55
  • Yeah I changed it because I learned at school it's enough to change the address in the eip register because the cpu is always executing the next instruction from the eip register so I thought the jmp instruction is more pseudo or am I false. – assemblerMan Dec 19 '16 at 23:00
  • sorry my fault you are right the jump instructions sets the value to the eip register :D I changed it ^^ But for me it's not clear why in my assembler code if I disassemble a programm there is no line which popes the arguments from the stack like add esp 0x8 – assemblerMan Dec 19 '16 at 23:05
  • man ...? may you help me :S – assemblerMan Dec 20 '16 at 05:52

2 Answers2

2

It was hard to figure out what you were missing, but I think what you're missing is that the caller has to fix the stack after the called function returns. The caller knows how much it pushed before the call, so it can add esp, some_constant after the call instruction to clear the args from the stack, putting ESP back to where it was before the first push.


ESP is call-preserved in all calling conventions. Called functions aren't allowed to return with ESP different from what it was before the call. If they return with ret, this could only happen if they copied the return address somewhere else on the stack before running ret! So it's a pretty obvious restriction that some calling-convention descriptions fail to mention.

Anyway, this means that the caller can assume ESP wasn't modified, so it can save/restore anything else with PUSH/POP.

EBP is also call-preserved in all calling conventions I'm aware of. See https://stackoverflow.com/tags/x86/info (the tag wiki) for calling convention/ABI docs.

Also calling conventions on Wikipedia for short summaries.


Also, your pseudo-code for a function call was really weird and confusing (before I edited the question). It didn't clearly show the boundary between the caller's code and the callee's code. In a previous version of this answer, I thought you were saying the caller's code was pushing EBP, because that came before the working in the function line.

EIP isn't directly accessible, and can only be modified by jump instructions. CALL pushes a return address and then jumps (note that it pushes the address of the next instruction, so it doesn't run again on return. EIP during the execution of an instruction could be said to point at the next instruction, since relative jumps are encoded with a displacement from the end of the instruction. Same for x86-64 RIP-relative addresses.)

RET pops into EIP. For it to return to the right place, the code has to restore ESP to pointing at the return address pushed by the caller.

Assuming a 32-bit stack-args calling convention like System V i386, I'd write your pseudocode as:

(optional) push ecx or whatever call-clobbered registers you want to save
push arguments on stack
CALL function (pushes a return address, i.e. the addr of the insn after the call)

  # code of the called function
  (optional) push ebp   (and any other call-preserved regs the function wants to use)
  working in the function
  (optional) pop  ebp   (and any other regs, in reverse order of pushing)
  RET (pops the return address into EIP)

add esp, 8 (for example) to clear args from the stack
(optional) pop  ecx   or whatever other volatile regs you want to restore

Look at the compiler-generated asm for a real function sometime, like this:

Try with different compiler options or change the source on the Godbolt compiler explorer:

int extern_func(int a);

int foo() {
  int a = extern_func(2);
  int b = extern_func(5);
  return a+b;
}

Compiled with gcc6.2 -m32 -O3 -fno-omit-frame-pointer to make 32-bit code which uses EBP the way you're assuming, instead of the default omit-frame-pointer mode. I could have used -O0, but un-optimized asm is so bloated that it sucks to read, and there's nothing confusing that gcc can do here. Also used -fverbose-asm to get it to mark variable names on operands.

foo:
    push    ebp
    mov     ebp, esp              # standard prologue
    push    ebx                   # save ebx so we have a call-preserved register
    sub     esp, 16               # reserve space for locals
    push    2                     # the arg for the first function call
    call    extern_func
    mov     ebx, eax  # a,        # stash the return value where it won't be clobbered by the next call
    mov     DWORD PTR [esp], 5        # just write the new arg to the stack, instead of add esp, 4  and push 5
    call    extern_func     #
    add     eax, ebx  # tmp90, a     # this is a+b as the return value
    mov     ebx, DWORD PTR [ebp-4]    #, ESP isn't pointing to where we pushed EBX, so restore it with a normal MOV load.
    leave                             # and set esp=ebp and pop ebp
    # at this point, ESP is back to its value on entry to the function
    ret

clang makes some different choices about how to do things (including using esi instead of ebx), and does the epilogue with

    add     eax, esi
    add     esp, 4
    pop     esi
    pop     ebp
    ret

So it's a more "normal" sequence: restore ESP to pointing at the registers pushed in the prologue and pop them, again leaving ESP pointing at the return address ready for RET.

CL.
  • 173,858
  • 17
  • 217
  • 259
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • yeah i knew this but the problem is the esp gets resetet at the end of the function with esp = ebp – assemblerMan Dec 19 '16 at 22:11
  • then pop ebp and pop eip gets executet but the function arguments of the functions are never getting poped! – assemblerMan Dec 19 '16 at 22:11
  • so they would be on the stack but they shouldn't because the esp should be back at the old state and thats after my local variables – assemblerMan Dec 19 '16 at 22:16
  • @assemblerMan: updated my answer with a fix for your totally broken sequence of steps. That's probably why you're confused. – Peter Cordes Dec 19 '16 at 22:17
  • so thanks for your answer and there is the concret problem ^^ from where gets assembler the ,8 <-- 8 the offset for the arguments – assemblerMan Dec 19 '16 at 22:24
  • @assemblerMan: I just made that up as an example. See my update with some real code, where there's only one integer arg so it uses 4 bytes. – Peter Cordes Dec 19 '16 at 22:34
  • thanks, but this is all clear my problem is: I think now the poping of the arguments from the function is managed by the ret instruction but from where does assembler get the offset of the arguments and where does it save the offset? – assemblerMan Dec 19 '16 at 22:46
  • @assemblerMan: I just finally figured out that this was what you were missing, after decoding your call-sequence pseudo-code and noticing that you omitted the stack-cleanup. See my last update to the answer. – Peter Cordes Dec 19 '16 at 22:49
  • yeah that's it but why if I disassemble my programm there is no line which is doing this ? – assemblerMan Dec 19 '16 at 22:54
  • @assemblerMan There's also `ret immediate` variant of `ret`, which does `pop eip, add esp,immediate`, so it cleans up argument from stack. Some people programming in ASM use this calling convention, and I absolutely hate it. When I do `push 5` ahead of `call`, I expect to be that place in stack still occupied after `ret` (and I'm responsible to do `pop` or `add esp,4`), so when reading source with this `ret 4` convention, it's confusing me a lot. – Ped7g Dec 20 '16 at 11:35
  • @assemblerMan: look at the gcc output + hand-written-comments in my answer. LEAVE sets `esp=ebp` before popping ebp, so it takes care of getting ESP pointing where it should be pointing before the RET. All that matters is that the right thing happens; as usual in asm there are multiple ways to achieve that with different choices of instructions. Or if you're on Windows, one of the common calling conventions uses callee-cleans-the-stack like \@Ped7g describes. If the function name ends with `@4`, that means the function pops 4 bytes after RET. – Peter Cordes Dec 20 '16 at 16:00
  • │0x80483a0
    push ebp │ │0x80483a1
    mov ebp,esp │ │0x80483a3
    sub esp,0x4 │ │0x80483a6
    mov DWORD PTR [esp],0x5 │ │0x80483ad
    call 0x8048394 │ │0x80483b2
    mov eax,0x0 │ │0x80483b7
    leave │ │0x80483b8
    ret
    – assemblerMan Dec 20 '16 at 17:23
  • that's the assembler code of my test programm – assemblerMan Dec 20 '16 at 17:23
  • and there is neither a @4 at the or a ret 4 so how does it work :S – assemblerMan Dec 20 '16 at 17:24
  • @assemblerMan: http://www.felixcloutier.com/x86/LEAVE.html, exactly like I explained in the first half of that comment. Single-step it in a debugger and watch the register values change. (e.g. gdb with `layout reg`, see the bottom of http://stackoverflow.com/tags/x86/info) – Peter Cordes Dec 20 '16 at 17:37
  • yeah I tried this I don't get anything and if I check the register it wouldn't make any sense because then recursive functions won't work! – assemblerMan Dec 20 '16 at 17:47
  • @assemblerMan: Recursive functions can use LEAVE. No idea what you're talking about. If your debugger isn't showing you register values, you're using it wrong. That's what debuggers are for. – Peter Cordes Dec 20 '16 at 17:54
  • no I mean it wouldn't make any sense if the current stack would be saved in a register because if a recursiv functions call itself again it would override the old stack state! – assemblerMan Dec 21 '16 at 21:24
  • @assemblerMan: A recursive function doesn't return until all the recursive calls *it* makes have already returned. At that point, it *is* time to clean up any extra stack space it reserved for its own use inside this invocation. You seem to have some huge gaps in your understanding, so I'd suggest you use a debugger and single-step through a recursive function (written in C and compile with `gcc -Og` or something) so you can see what it does. – Peter Cordes Dec 21 '16 at 21:31
  • I know what it does but lets say i have a function – assemblerMan Dec 21 '16 at 21:33
  • test() {int i = 3; test(); i = 5;} – assemblerMan Dec 21 '16 at 21:34
  • so after the first call test0 will save the stack pointer in lets say ebx – assemblerMan Dec 21 '16 at 21:34
  • then test1 calls test2 and save stackpointer(I mean the current stackpointer location) to ebx and overrides the old. so that's what I mean – assemblerMan Dec 21 '16 at 21:35
  • That's because you're doing it wrong. Let's use EBP for saving the ESP value, like an un-optimized function would. You have to save the caller's value of EBP on the stack so you can restore it later. Since every recursive call does that, you end up with saved values on the stack for each level of recursion. If you had even bothered to look at compiler output for what you typed, instead of making wrong guesses, you would have seen what it did. – Peter Cordes Dec 21 '16 at 21:38
  • yeah you are right but if I anlyze the stack with gdb there is no address which would contain the offset or the real address of the current stack location and ebp saves the base address! – assemblerMan Dec 21 '16 at 21:51
  • @assemblerMan: The base address is where you saved the caller's EBP value. `push ebp` / `mov ebp, esp` on entry means that you can use the value in EBP to restore ESP later, and then restore the caller's EBP before returning. – Peter Cordes Dec 21 '16 at 22:00
  • so now we are at the point and from where does the assembler know how much to add to the esp to pop the function arguments if there are some! (add because the stack grows down) – assemblerMan Dec 21 '16 at 22:15
  • @assemblerMan: The assembler doesn't. The human or compiler that generated the asm knows, because it balances whatever you did to the stack earlier in the function. e.g. for every `push`, add another 4 bytes in 32-bit code. – Peter Cordes Dec 21 '16 at 22:25
  • yeah but there is nothing in assembler no command nothing which is doing this thats the sense behind my question :D – assemblerMan Dec 21 '16 at 22:34
  • @assemblerMan: What, you wanted there to be an instruction that "automatically" does the right thing based on some kind of complicated state-tracking? That makes no sense when you can just write `add esp, 4`, or `leave`. Those instructions *do* implement what you were asking. Asm is all about having to implement the calling convention and stack manipulation yourself. – Peter Cordes Dec 21 '16 at 22:43
  • yeah and I am interested how it's handling it :S – assemblerMan Dec 21 '16 at 22:54
  • do you don't know it or why are you not answering :C I don't find anything on google :O – assemblerMan Dec 23 '16 at 10:08
  • 1
    @assemblerMan: I can't figure out what part you don't understand. When you or the compiler are generating code to call a function, you always know how that affects ESP, therefore you know what code to put after the `call` to clean up. If that doesn't work, you're doing it wrong. – Peter Cordes Dec 23 '16 at 10:29
  • yeah but if I look at the assembly source generated from a c programm there is no reset of the stack but it gets resettet but there is no code line – assemblerMan Dec 23 '16 at 11:28
  • @assemblerMan. Wrong. Look at what [`leave`](http://felixcloutier.com/x86/LEAVE.html) does. There's no magic, just instructions modifying ESP as documented. – Peter Cordes Dec 23 '16 at 11:46
1

Have a look at the Calling conventions page on wikipedia.

Stack before call:

0x8100 - +------------+ <- ESP
...... - |            |
...... - |            |
0x8000 - +------------+ <- EBP
...... - |            |
...... - | Cur. Frame |
...... - |            |
...... - +------------+

push arguments
push eip register on stack
push ebp register on stack


0x8100 - +------------+ <- ESP
...... - |            |
...... - |            |
0x8000 - +------------+ 
...... - |            |
...... - | Old Frame  |
...... - |            |
...... - +------------+ <- EBP
...... - | Arguments  |
...... - | EIP        |
...... - | 0x8000     | <- Old EBP
...... - +------------+ 

pop ebp
pop eip

0x8100 - +------------+ <- ESP
...... - |            |
...... - |            |
0x8000 - +------------+ <- EBP
...... - |            |
...... - |  Frame     | <- Current again frame!
...... - |            |
...... - +------------+ 
...... - |            |
...... - | Popped     |
...... - |            |
...... - +------------+ 
599644
  • 561
  • 2
  • 15
  • thanks but there is nothing about where the esp gets saved, – assemblerMan Dec 19 '16 at 22:04
  • because I can't imagine that it gets saved in an register, because then recursiv functions won't work. – assemblerMan Dec 19 '16 at 22:05
  • @assemblerMan: At function entry, the current value of `esp` is usually saved to `ebp` (the base pointer). `ebp` does not change over the lifetime of the function. – John Bode Dec 19 '16 at 22:08
  • 1
    This link-only answer doesn't explain how that answers the question. – Peter Cordes Dec 19 '16 at 22:09
  • yeah i know but and it gets resetet at the end of the function but then there are also the arguments on the stack from the function and in my assembly source they never get poped from the stack – assemblerMan Dec 19 '16 at 22:10
  • @assemblerMan, they don't need to get popped explicitly. When the function return, the whole `stack` shrinks, which in essence pops the entire `stack frame` – 599644 Dec 19 '16 at 22:16
  • yeah but from where gets assembler the offset to pop all the arguments? thats where I am struggling because the offset doesnt get pushed on the stack or anything like this ... – assemblerMan Dec 19 '16 at 22:17
  • @assemblerMan: The `pop ebp` does exactly that. – 599644 Dec 19 '16 at 22:19
  • @assemblerMan: Does the drawing above help? – 599644 Dec 19 '16 at 22:34
  • I am so sorry but I think you are not understanding me the problem is the popped part from where does the assembler or the compiler know the offset of the function arguments which have to be popped? Because if i objdump mi c file there is nothing like sub esp 0x4 – assemblerMan Dec 19 '16 at 22:44