调用函数时状态寄存器(EFLAGS)保存问题及GCC调用约定相关疑问
Great question—let’s break down your two key points clearly, since these are common stumbling blocks when learning x86 calling conventions with GCC:
1. Who is responsible for saving the EFLAGS register?
In standard 32-bit GCC calling conventions (like the default cdecl), the EFLAGS register is not required to be preserved by the callee. Here’s what that means in practice:
- A called function can freely modify any bits in EFLAGS (such as the carry flag
CF, zero flagZF, or overflow flagOF) without restoring their original state before returning. - If the caller needs to retain the state of EFLAGS across a function call, it’s entirely the caller’s responsibility to save and restore it manually. This is done with instructions like
pushf(push EFLAGS to the stack) before invoking the function, andpopf(restore EFLAGS from the stack) once the function returns.
There are rare edge cases where a function explicitly documents that it preserves specific flags, but this is the standard, default behavior.
2. Is the return address pushed to the stack equal to the value of the instruction register (EIP)?
Not exactly—let’s walk through what happens when the call instruction executes:
- First, the CPU calculates the address of the next instruction immediately following the
callitself (sincecallis a multi-byte instruction, this isEIP + length_of_call_instruction). - This calculated address is pushed onto the stack as the return address.
- Finally, the CPU updates EIP to point to the entry address of the called function, so execution jumps there.
So the return address stored on the stack is the address the caller should resume executing at after the callee finishes (via the ret instruction). At the moment the return address is pushed, EIP has already been updated to this next instruction’s address—but to put it simply: the return address isn’t the EIP value before the call ran, it’s the EIP value that would have been used if the call hadn’t triggered a jump.
For a concrete example:
- Suppose
call my_funcis at memory address0x1000and takes up 5 bytes. - Before
callruns, EIP is0x1000. - The CPU first computes
0x1000 + 5 = 0x1005(the next instruction’s address), pushes0x1005to the stack. - EIP is then set to
my_func’s address (say0x2000), so execution starts there. - When
my_funcrunsret, it pops0x1005back into EIP, and execution resumes at that instruction.
内容的提问来源于stack exchange,提问作者Dusan

