I have a peculiar snippet of code which I cannot wrap my head around:

push 0xC           ; arg1 for call
mov ecx,edi        ; set the this pointer for call
call sdk.100039F0  ; make the call (internally calls DeviceIOControl)
push ecx           ; ECX now points to a function within kernelbase.dll
mov ecx,edi        ; set the this pointer for call
call sdk.10003BD0  ; make the call

A call to DeviceIoControl within the first function call modifies ECX. Why is it pushed as an argument to the second call? The second call itself does NOT accept arguments, it does not reference [ebp + n] at all, yet it still concludes with a ret 4.

Directly after this is a third call, which also internally uses DeviceIOControl then the function returns. This third call does not have the mysterious push before it. All these functions, including the container, make use of thiscall.

Just in case I missed something, this is the body of the second call:

push ebp
mov ebp,esp
sub esp,0x30
push ebx
push esi
mov eax,ecx
xor esi,esi
push edi
mov edi,dword ptr ds:[<&DeviceIoControl>]
mov ecx,sdk.10002690
mov dword ptr ss:[ebp-0x8],eax
mov edx,0x8000
test ecx,ecx
je aura_sdk.10003C35
mov ax,word ptr ds:[eax+0x4]
push 0x0
mov word ptr ss:[ebp-0x20],ax
lea eax,dword ptr ss:[ebp-0x14]
push eax
push 0x4
lea eax,dword ptr ss:[ebp-0x4]
mov dword ptr ss:[ebp-0x4],0x0
push eax
push 0x7
lea eax,dword ptr ss:[ebp-0x20]
mov byte ptr ss:[ebp-0x1A],0x1
push eax
push 0x80102050
push dword ptr ds:[0x100375C0]
call edi
mov bl,byte ptr ss:[ebp-0x4]
mov ecx,sdk.10002690
mov edx,0x8000
jmp sdk.10003C37
xor bl,bl
test bl,0x9E
jne sdk.10003C8A
test ecx,ecx
je sdk.10003C7D
push 0x0
mov eax,0xED
mov dword ptr ss:[ebp-0xC],0x0
mov word ptr ss:[ebp-0x28],ax
lea eax,dword ptr ss:[ebp-0x18]
push eax
push 0x4
lea eax,dword ptr ss:[ebp-0xC]
mov byte ptr ss:[ebp-0x22],0x1
push eax
push 0x7
lea eax,dword ptr ss:[ebp-0x28]
push eax
push 0x80102050
push dword ptr ds:[0x100375C0]
call edi
mov ecx,sdk.10002690
mov edx,0x8000
mov eax,dword ptr ss:[ebp-0x8]
inc esi
cmp si,dx
jb sdk.10003BF0
test bl,0x82
je sdk.10003CA3
pop edi
test bl,0x1C
mov eax,0x0
pop esi
sete al
pop ebx
mov esp,ebp
pop ebp
ret 0x4

Is this a case of the compiler correcting itself or is there a purpose?

  • I would imagine ecx is saved and restored in the first call, and parhaps being used as an argument to a third function? We'll need to see more of the code I guess...
    – NirIzr
    Aug 30, 2018 at 17:25
  • @NirIzr The first function call is almost exactly the same except it make more calls to DeviceIOControl. ECX is used within to calculate a few variables based on arg1, but ultimately the final DeviceIOControl call is setting it to a kernelbase.dll function pointer. Please don't let my low score give the impression that I'm a total newbie ;) If you want the fist function code I will post it, but trust me, ECX is the this pointer for the call, which is copied to EDI before allowing ECX to be used as a normal register.
    – Twifty
    Aug 30, 2018 at 17:37
  • Sorry if my comment came of as disrespecting, that was definitely not my intention! The point I was trying to make is that it feels like code around ecx is somewhat lacking, and that I'm guessing that we'll need to see more code to reach an answer.
    – NirIzr
    Aug 30, 2018 at 17:53
  • As you know, ASM can take up quite a lot of lines. So I try to keep it to the necessities. I just find it strange that a register, which shouldn't contain any meaningful value after a call, is being pushed only for the next call to clean up the stack without even looking at the value. I guess the compiler was having a blonde moment.
    – Twifty
    Aug 30, 2018 at 18:03
  • @Twifty welcome to RE.SE! Well the compiler may decide calling conventions, if parts were written in assembly, all bets are off. What would be interesting is what's in edi and what comes after the second call. What we can tell, though, is that the second call makes use of the ecx value, which is similar to what we'd see in a thiscall (except it doesn't look like a vtable access). But what comes after the second call? Was the program perhaps really just trying to save the register value and pops it later on?
    – 0xC0000022L
    Aug 31, 2018 at 7:20

1 Answer 1


This looks like the result of compiler optimization. The second callee (a fastcall function) takes an argument it obviously doesn't use. The compiler is in a situation where it is unable to modify the calling convention of the second call, so it still has to take the one argument and remove it from the stack. Most likely, the compiler can not prove that the function is not called by external code it can not adjust. But the compiler can use the knownledge that the function does not use its argument when compiling callers to it, by removing the code that calculates the argument and pushing a dummy argument instead. push ecx is a one-byte instruction and thus a very efficient way to provide the required dummy argument.

In the case of Visual C++ in release mode, you should be aware that by default link-time code generation is enabled. If both the application and the SDK are compiled with link-time code generation, the "object files" in fact contain some abstract intermediate representation of the code and the actual transformation to binary code happens at link time when all libraries with link-time code generation enabled and the application are combined. This enables argument elision even if the object files or static libraries were generated independent of each other.

  • 2
    I find it quite odd that a compiler will use the information a variable is unused inside a function it did not compile.
    – NirIzr
    Aug 31, 2018 at 18:26
  • @NirIzr Good point. Perhaps it can be explained by my addition to the answer. Also, I don't think we know whether the caller is also part of the SDK, in which case the compiler might actually have compiled all three functions at the same time Aug 31, 2018 at 18:30
  • Given that all four function use thiscall, and relatively small, I would say that they were all compiled from the same source file. The SDK is public code if anybody want to look at it themselves.
    – Twifty
    Aug 31, 2018 at 19:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.