次の方法で共有


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!

Comments

  • Anonymous
    January 30, 2005
    How did you find the 000af954 value? Is it because it's the next slot on the stack after what looks like the address that the catch block will return to?

    000af954 000af980
    000af958 7c3929b5 MSVCR80!_CallCatchBlock2+0x4a

    By the way, there's more information about .cxr and the magical 0x1003f and 0x1001f numbers in the Mike Stall's blog:

    http://blogs.msdn.com/jmstall/archive/2005/01/18/355697.aspx

  • Anonymous
    January 30, 2005
    I am also interested for the value 000af954. I wrote the sample code and debugged locally also, the pattern is different. Could you explain a little more?

  • Anonymous
    January 31, 2005
    Yes. It appeared to be the first valid frame. So you can do two things here one is to set ebp to it and see if it works or the other way is run kb=000af954

    >>By the way, there's more information about .cxr and the magical 0x1003f and 0x1001f numbers in the Mike Stall's blog:

    >>http://blogs.msdn.com/jmstall/archive/2005/01/18/355697.aspx Remove Comment 363443

    That is a good point. I saw the post couple of days ago and that is exact reason why I didn't go into details of the magic :-). I forgot to put a pointer. Thank you very much for reminder!

  • Anonymous
    January 31, 2005
    >> I am also interested for the value 000af954. I wrote the sample code and debugged locally also, the pattern is different. Could you explain a little more?

    Yes in you case it will be different. You need to be looking at your thread stack. If you run dps @esp as I did above the first column is thread's stack. The second column is what is stored on the stack. Usually a stack frame contains both address to previous frame and return address. All I did is looked for the pattern that seems to be a valid frame and then tried it. Let me know if you want me to dig more information out for you about how frames getting set and how to traverse them by hand.

  • Anonymous
    January 31, 2005
    Interesting finds today

  • Anonymous
    February 28, 2005
    A call of a function pushes the return value on the stack first, followed by ebp.

    When looking at dps/dds output you can identify a likely frame start by looking for a pointer that points to an address just a bit down the stack that could be a valid pushed ebp, followed by what looks like a valid return address. This matches that pattern:

    000af954 000af980
    000af958 7c3929b5 MSVCR80!_CallCatchBlock2+0x4a

    You can pass the address of the likely ebp to the k command (kv=000af954) to start a stack walk at that point. This is also a useful trick to recover the second half of a stack that is truncated due to an FPO frame in a module that you don't have symbols for.

  • Anonymous
    February 21, 2006
    Very useful blog. Thank you.

  • Anonymous
    May 10, 2006
    Anyone knows the AMD64 counterpart of the magical 0001003f?

  • Anonymous
    October 11, 2007
    Good post on how to see the stack when an exception is thrown on Windows at http://blogs.msdn.com/slavao/archive/2005/01/30/363428.asp

  • Anonymous
    January 05, 2008
    PingBack from http://boxing.247blogging.info/?p=3202

  • Anonymous
    April 21, 2009
    PingBack from http://jmdesp.free.fr/billeterie/?p=168

  • Anonymous
    June 02, 2009
    PingBack from http://woodtvstand.info/story.php?id=89954

  • Anonymous
    October 20, 2009
    Very useful article. It would be better if could get the stack trace within the catch block.