Getting an exception call stack from the catch block (C++)

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 thatCRT 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;

     try

     {

     throw (10);

     }

     catch (int exception)

     {

     BYTE* pStack;

     __asm

     {

     mov pStack, esp;

       }

     printf ("Stack difference %d\n",&byte - pStack);

     }

     // To see real flow control we need to put some code

     // here

     //

     printf (“I am almost done\n”);

     return 0;

}

Output:

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

 

0:000> r

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

test!main+0x44:

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

kernel32!RaiseException+0x51:

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

000affc0 77e4f38c 00000000 00000000 7ffdf000 test!mainCRTStartup+0xb0

000afff0 00000000 010013b9 00000000 78746341 kernel32!BaseProcessStart+0x23

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

000af93c 000aff78

000af940 000afbc8

000af944 000af954

000af948 000aff78

000af94c 01001331 test!main+0x41

000af950 000aff84

000af954 000af980

000af958 7c3929b5 MSVCR80!_CallCatchBlock2+0x4a

000af95c 01001331

000af960 000aff78

000af964 00000100

000af968 000af9d4

 

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

000aff08 7c3929f8 e06d7363 00000001 00000003 kernel32!RaiseException+0x51

000aff48 01001331 000aff68 010018d4 00000000 MSVCR80!_CxxThrowException+0x34

000aff84 01001469 00000001 002d5cc0 002d3b30 test!main+0x41

000affc0 77e4f38c 00000000 00000000 7ffdf000 test!mainCRTStartup+0xb0

000afff0 00000000 010013b9 00000000 78746341 kernel32!BaseProcessStart+0x23

 

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!