This is my first post in this category so I am really looking forward to your comments :-) Today I would like to cover a specific feature related to our implementation of C++ exception handling. Every day I realize that not many people are familiar with this feature. When they learn about it they really get surprised :-). You need to see their reaction, it is just awesome! So now let’s see yours :-).
Have you ever had a day when you are starring at catch block and willing you knew the stack of original exception? Do you remember how frustrated you were :-)? Well it turns out that the original stack is there under your fingertips. The debugger just hides it . Did I get you going J?
MS implementation of C++ exception handling doesn't actually pop the stack until the flow control leaves catch block. From C++ standard "A throw-expression initializes a temporary object, the type of which is determined by removing any top-level cv-qualifiers from the static type..." It means that CRT needs to a make copy of an object somewhere when exception is thrown. As you can guess, it makes a copy of it on the stack! It means that by default run time can't pop the stack until exception is dismissed. This feels good J
Here is what actually happens on x86 platform when exception gets thrown. This is oversimplified view but you will get an idea:
- Copy of the object is created on the stack
- Appropriate handler, catch block, is found
- Runtime calls object destructors located on the stack
- EBP is adjusted to a frame containing the catch block
- ESP is NOT TOUCHED
- Flow of control transferred to catch block
- When done with a catch block the flow of control returns back to the place where it came before entering catch block
- ESP is adjusted
- Flow of control is transferred outside of catch block
Amazing right! Now since EBP gets adjusted before getting to the catch block debugger doesn’t show the real stack. It shows you stack as you would expect: without exception on the stack!
Lets take a look at the example:
int __cdecl main (int argc, char* rgArg )
BYTE byte = 10;
catch (int exception)
mov pStack, esp;
printf ("Stack difference %d\n",&byte - pStack);
// To see real flow control we need to put some code
printf (“I am almost done\n”);
Stack difference 1595
Wow! Now let’s take a look at the stack. I put a break point in the debugger on the first printf statement:
000aff84 01001469 00000001 002d5cc0 002d3b30 test!main+0x44
000affc0 77e4f38c 00000000 00000000 7ffdf000 test!mainCRTStartup+0xb0
000afff0 00000000 010013b9 00000000 78746341 kernel32!BaseProcessStart+0x23
This is what you would expect right? Now let’s take a look at registers
eax=01001331 ebx=01001331 ecx=00000100 edx=00000002 esi=000afbc8 edi=000aff78
eip=01001334 esp=000af938 ebp=000aff84 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
01001334 8d4def lea ecx,[ebp-0x11] ss:0023:000aff73=0aff580a
Please notice a big difference between ebp and esp! The real question now is how to get the real stack. The simple way is to make a search for 0001003f pattern. As many of you know this is usually how the first four bytes of CONTEXT look like on x86 platform
0:000> s -d @esp @ebp 0001003f
000afbe8 0001003f 00000000 00000000 00000000 ?...............
All we have to do know is to switch context:
0:000> .cxr 000afbe8
eax=000afeb8 ebx=7ffdf000 ecx=00000000 edx=002d3b30 esi=000aff48 edi=000aff48
eip=77e649d3 esp=000afeb4 ebp=000aff08 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
77e649d3 5e pop esi
000aff08 7c3929f8 e06d7363 00000001 00000003 kernel32!RaiseException+0x51
000aff48 01001331 000aff68 010018d4 00000000 MSVCR80!_CxxThrowException+0x34
000aff84 01001469 00000001 002d5cc0 002d3b30 test!main+0x41
And here you go J. Also we could have used dps command to find a previous frame.
0:000> dps @esp
000af938 7c392a47 MSVCR80!_NLG_Return
000af94c 01001331 test!main+0x41
000af958 7c3929b5 MSVCR80!_CallCatchBlock2+0x4a
Now all we need to do is reset ebp to 000af954
0:000> r ebp=000af954
000af954 7c3929b5 01001331 000aff78 00000100 test!main+0x44
000af980 7c391bc4 000aff78 01001918 01001331 MSVCR80!_CallCatchBlock2+0x4a
000af9e4 7c392081 000afbc8 000aff78 00000001 MSVCR80!CallCatchBlock+0x84
000afa14 7c3923dd 000afbc8 000afbe8 000afba0 MSVCR80!CatchIt+0x5c
000afa70 7c3925c6 000afbc8 000aff78 000afbe8 MSVCR80!FindHandler+0x26e
000afaa4 7c392680 000afbc8 000aff78 MSVCR80!__InternalCxxFrameHandler+0xc5
000afae0 77f68cf6 000afbc8 000aff78 000afbe8 MSVCR80!__CxxFrameHandler3+0x26
000afae0 77f68cf6 000afbc8 000aff78 000afbe8 ntdll!ExecuteHandler2+0x26
000afb04 77f68cc5 000afbc8 000aff78 000afbe8 ntdll!ExecuteHandler2+0x26
000afbb0 77f45275 00000000 000afbe8 000afbc8 ntdll!ExecuteHandler+0x24
000afbb0 77e649d3 00000000 000afbe8 ntdll!KiUserExceptionDispatcher+0xe
This stack dump is even better. It gives you full stack right before we executed jump to catch block!
The consequence of this behavior is that you need to be very careful when re-throwing exceptions from the catch block in recursive or deep functions - you might hit stack overflow! We actually did and not only once J.
As many people say you live you learn :-)
Enjoy the rest of the day!