OpenRCE_RolfRolles
January 1st, 2008, 18:21
A friend of a friend wanted to know whether I had a better name in mind for the following function:
I can't argue: this sure does look like a WTF on its face. If you don't often debug debug-build code compiled with MSVC, and know the trick behind this, then it's bound to be a mystery. I can imagine all sorts of strange thoughts crossing one's mind when encountering this.
What's actually happening here is this. As an aid to debugging, MSVC has a compiler switch (/GZ) that will force it to write 0xCC bytes atop uninitialized variables of any scope. This is documented, amongst other places, here ("http://www.samblackburn.com/wfc/technotes/WTN006.htm"). Notice that var_8 is also overwritten due to the length of the memset. So /GZ explains the CC bytes on the stack.
Of course, we can clearly see that neither stack variable is used at all after it's declared/initialized, so that adds to the puzzling effect. An optimizing compiler would have sensed (as the result of a global reaching assignments data flow analysis) that the variables had no uses, and would have eliminated both (and the initializing/saving of ecx code) altogether in its first dead code elimination phase. Therefore I surmise that the compiler has all optimizations disabled (which makes sense in combination with the previous paragraph -- debug builds are typically unoptimized).
Further evidence for this "no optimizations" theory comes in noticing that ebx and esi are saved in the function's prologue and restored in the epilogue, despite neither being used throughout the course of the function. This is called "register saving", and is subject to optimization when optimizations are enabled. In fact, none of the registers need to be saved if the two dead variables and related initialization are eliminated.
With the stack variables eliminated and no alloca() present, the compiler can (and should) decide to use an esp-based frame instead. The result is that the function needs no prologue/epilogue, and ebp no longer needs to be saved as above.
So really, this whole function should have been optimized down into one instruction: "retn". If whole-program optimizations had been applied (and/or devirtualization, if this is a virtual function -- I didn't see the references) this function would be a good candidate for inlining, in which case all call-sites to this function would simply disappear, and the function itself would not be present in the final binary.
https://www.openrce.org/blog/view/1009/Weird_Code:__CCs_On_The_Stack
Code:
Write_CCs_To_Stack_WTF proc near
var_CC = byte ptr -0CCh
var_8 = dword ptr -8
push ebp
mov ebp, esp
sub esp, 0CCh
push ebx
push esi
push edi
push ecx
lea edi, [ebp+var_CC]
mov ecx, 33h
mov eax, 0CCCCCCCCh
rep stosd
pop ecx
mov [ebp+var_8], ecx
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
retn
Write_CCs_To_Stack_WTF endp
I can't argue: this sure does look like a WTF on its face. If you don't often debug debug-build code compiled with MSVC, and know the trick behind this, then it's bound to be a mystery. I can imagine all sorts of strange thoughts crossing one's mind when encountering this.
What's actually happening here is this. As an aid to debugging, MSVC has a compiler switch (/GZ) that will force it to write 0xCC bytes atop uninitialized variables of any scope. This is documented, amongst other places, here ("http://www.samblackburn.com/wfc/technotes/WTN006.htm"). Notice that var_8 is also overwritten due to the length of the memset. So /GZ explains the CC bytes on the stack.
Of course, we can clearly see that neither stack variable is used at all after it's declared/initialized, so that adds to the puzzling effect. An optimizing compiler would have sensed (as the result of a global reaching assignments data flow analysis) that the variables had no uses, and would have eliminated both (and the initializing/saving of ecx code) altogether in its first dead code elimination phase. Therefore I surmise that the compiler has all optimizations disabled (which makes sense in combination with the previous paragraph -- debug builds are typically unoptimized).
Further evidence for this "no optimizations" theory comes in noticing that ebx and esi are saved in the function's prologue and restored in the epilogue, despite neither being used throughout the course of the function. This is called "register saving", and is subject to optimization when optimizations are enabled. In fact, none of the registers need to be saved if the two dead variables and related initialization are eliminated.
With the stack variables eliminated and no alloca() present, the compiler can (and should) decide to use an esp-based frame instead. The result is that the function needs no prologue/epilogue, and ebp no longer needs to be saved as above.
So really, this whole function should have been optimized down into one instruction: "retn". If whole-program optimizations had been applied (and/or devirtualization, if this is a virtual function -- I didn't see the references) this function would be a good candidate for inlining, in which case all call-sites to this function would simply disappear, and the function itself would not be present in the final binary.
https://www.openrce.org/blog/view/1009/Weird_Code:__CCs_On_The_Stack