11

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?

user541686
  • 205,094
  • 128
  • 528
  • 886

3 Answers3

10

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

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
  • I'm not sure that `volatile` would make a difference here. `volatile` pertains to a memory location, but EAX is a register; you can't mark a register as being volatile. So `volatile` would explain [ebp-4] being reloaded *onto* eax immediately before every operation, but not eax being saved. – Crashworks Jan 25 '12 at 03:13
  • For certain arithmetics the compiler _has_ to load a memory location marked volatile into a register, because the operation is not possible as read-modify-write instruction. After this code segment a store might follow, and before this a load may preceed, so from the information at hand it may absoluteley be possible. But posly it't only a missed optimization by VC. – Gunther Piez Jan 25 '12 at 10:15
  • @drhirsch Ah, because volatile also means that *stores* have to be committed immediately, not just that *reads* deferred to the last moment. I'd not considered that. It would be a bit peculiar for a `volatile` pointer to aim at a stack address (`ebp` offset) but I suppose it's possible. – Crashworks Jan 25 '12 at 10:42
  • @Crashworks: I may be wrong, but `ebp-4` seems to be a function parameter (a pointer or a reference), so it feels even more strange. – Matteo Italia Jan 25 '12 at 14:18
6

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.

Crashworks
  • 40,496
  • 12
  • 101
  • 170
  • I believe it's *supposed* to be release-mode code (there is no 'debug' version of anything related I can see anywhere...), but I'm not sure... I don't have the source code either. But +1 that's a reasonable guess, thanks. – user541686 Jan 25 '12 at 00:15
2

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.

FrankH.
  • 17,675
  • 3
  • 44
  • 63