问题
I am currently learning x86 assembly. Something is not clear to me still however when using the stack for function calls. I understand that the call instruction will involve pushing the return address on the stack and then load the program counter with the address of the function to call. The ret instruction will load this address back to the program counter.
My confusion is, does it matter when the ret instruction is called within the procedure/function? Will it always find the correct return address stored on the stack, or must the stack pointer be currently pointing to where the return address was stored? If that's the case, can't we just use push and pop instead of call and ret?
For example, the code below could be the first on entering the function , if we push different registers on the stack, must the ret instruction only be called after the registers are popped in the reverse order so that after the pop %ebp instruction , the stack pointer will point to the correct place on the stack where the return address is, or will it still find it regardless where it is called? Thanks in advance
push %ebp
mov %ebp, %esp
//push other registers
...
//pop other registers
mov %esp, %ebp
(could ret instruction go here for example and still pop the correct return address?)
pop %ebp
ret
回答1:
You must leave the stack and non-volatile registers as you found them. The calling function has no clue what you might have done with them otherwise - the calling function will simply continue to its next instruction after ret
. Only ret
after you're done cleaning up.
ret
will always look to the top of the stack for its return address and will pop
it into EIP
. If the ret
is a "far" return then it will also pop
the code segment into the CS
register (which would also have been pushed by call
for a "far" call). Since these are the first things pushed by call
, they must be the last things popped by ret
. Otherwise you'll end up ret
ing somewhere undefined.
回答2:
The CPU has no idea what is function/etc... The ret
instruction will fetch value from memory pointed to by esp
a jump there. For example you can do things like (to illustrate the CPU is not interested into how you structurally organize your source code):
; slow alternative to "jmp continue_there_address"
push continue_there_address
ret
continue_there_address:
...
Also you don't need to restore the registers from stack, (not even restore them to the original registers), as long as esp
points to the return address when ret
is executed, it will be used:
call SomeFunction
...
SomeFunction:
push eax
push ebx
push ecx
add esp,8 ; forget about last 2 push
pop ecx ; ecx = original eax
ret ; returns back after call
If your function should be interoperable from other parts of code, you may still want to store/restore the registers as required by the calling convention of the platform you are programming for, so from the caller point of view you will not modify some register value which should be preserved, etc... but none of that bothers CPU and executing instruction ret
, the CPU just loads value from stack ([esp]
), and jumps there.
Also when the return address is stored to stack, it does not differ from other values pushed to stack in any way, all of them are just values written in memory, so the ret
has no chance to somehow find "return address" in stack and skip "values", for CPU the values in memory look the same, each 32 bit value is that, 32 bit value. Whether it was stored by call
, push
, mov
, or something else, doesn't matter, that information (origin of value) is not stored, only value.
If that's the case, can't we just use push and pop instead of call and ret?
You can certainly push
preferred return address into stack (my first example). But you can't do pop eip
, there's no such instruction. Actually that's what ret
does, so pop eip
is effectively the same thing, but no x86 assembly programmer use such mnemonics, and the opcode differs from other pop
instructions. You can of course pop
the return address into different register, like eax
, and then do jmp eax
, to have slow ret
alternative (modifying also eax
).
That said, the complex modern x86 CPUs do keep some track of call/ret
pairings (to predict where the next ret
will return, so it can prefetch the code ahead quickly), so if you will use one of those alternative non-standard ways, at some point the CPU will realize it's prediction system for return address is off the real state, and it will have to drop all those caches/preloads and re-fetch everything from real eip
value, so you may pay performance penalty for confusing it.
回答3:
In the example code, if the return was done before pop %ebp
, it would attempt to return to the "address" that was in ebp at the start of the function, which would be the wrong address to return to.
来源:https://stackoverflow.com/questions/46714626/does-it-matter-where-the-ret-instruction-is-called-in-a-procedure-in-x86-assembl