Reputation: 210445
I disassembled an object file (most likely generated using the Visual C++ compiler) using DumpBin
and saw the following piece of code:
... ...
mov dword ptr [ebp-4],eax // Why save EAX?
push dword ptr [ebp+14h]
push dword ptr [ebp+10h]
push dword ptr [ebp+0Ch]
push dword ptr [ebp+8]
mov eax,dword ptr [ebp-4] // Why restore EAX? Did it change at all?
call <function>
... ...
Could someone please explain why the EAX register is being saved and restored across these 4 push
instructions?
Upvotes: 11
Views: 358
Reputation: 18217
volatile
or not, the only technical reason why EAX
would have to be initialized directly before making a function call on Windows were if that function
is declared __syscall
, i.e. using the Windows CS_SYSCALL calling convention. Conceptually, this is a bit similar to the UN*X x86_64 convention where %al
contains the number of floating point type args passed in %xmm
registers.
The syscall calling convention on Windows is identical to __cdecl
, i.e. function args on stack in reverse order, but with the addition that AL
contains a count of the number of arguments; this is done so that the kernel code which is usually at the final end of this knows how much data to read from the user stack onto the kernel stack to retrieve the args.
EAX
is a scratch register for all calling conventions on 32bit Windows, its value is never preserved over function calls, initializing it directly before making a call is redundant. Even if the variable it holds were volatile
- because a simple re-load isn't a memory barrier and doesn't "commit" a previous store. In addition, the location [EBP - 4]
is within the stack, so the variable is local (and a volatile
qualifier makes little sense).
If it's not a missed optimization then it could be an invocation of a __syscall function(...)
with different numbers of arguments, like, hypothetically,
__syscall printf_syscall_conv(char *fmt, ...);
void possibly_print_three_vals(char *fmt, int val1, int val2, int val3)
{
if (*strchr('%', fmt) == '\0') // if no "%" in fmt, pass no args
printf_syscall_conv(fmt);
else
printf_syscall_conv(fmt, val1, val2, val3);
}
This could conceivably create assembly output like yours.
Upvotes: 2
Reputation: 126787
Also, maybe it's compiled in release mode, but that variable has been marked as volatile
, which tells the compiler that such variable may change without it knowing, so it is forced to continuously write/restore it on/from the stack
Upvotes: 10
Reputation: 41384
Was this built in debug mode? If so, the compiler stores every local variable on the stack so that the debugger can find them in a consistent way.
The elision of such unnecessary stores and reloads is one of the optimizations that constitutes "release" mode.
Upvotes: 6