Sometimes it happens we have to analyze dumps of our application: it’s a mixed .NET/C/C++ software, so the legacy libraries can trigger unhandled exceptions that cause the process to crash.
In most of these cases either through the internal minidumper or thanks to the WER, the user is able to send us the core dump, that we analyze with WinDbg.
In a couple of cases the exception code resulted to be 0xE06D7363
, which is a generic C++ error code that doesn’t provide any details.
Meaning of the error code
Looking at VS header files, we can find in ehdata.h
that EH_EXCEPTION_NUMBER
is defined as follows:
#define EH_EXCEPTION_NUMBER ('msc' | 0xE0000000) // The NT Exception # that we use
Which might seem a bit weird definition but once we realize that 'm' ==> 6D
, 's' ==> 73
and 'c' ==> 63
(see the ASCII table), we get that 'msc' ==> 6D7363
and the OR with 0xE0000000
(the E
stands for “exception“) will produce the final value 0xE06D7363
.
First look at the Exception Context
Let’s start from the dump and see the last exception record:
0:000> .exr -1
ExceptionAddress: 00007ff876693b19 (KERNELBASE!RaiseException+0x0000000000000069)
ExceptionCode: e06d7363 (C++ EH exception)
ExceptionFlags: 00000001
NumberParameters: 4
Parameter[0]: 0000000019930520 ==> magic number
Parameter[1]: 00000045355fce90 ==> pointer to exception object
Parameter[2]: 00007ff802eef9f0 ==> pointer to throw info
Parameter[3]: 00007ff802d60000 ==> HINSTANCE of the DLL
this is where we get to know the exception number. As usual the next step is to check the stack:
0:000> k
*** Stack trace for last set context - .thread/.cxr resets it
# Child-SP RetAddr Call Site
00 00000045`355fcd10 00007ff8`737eeed3 KERNELBASE!RaiseException+0x69
01 00000045`355fcdf0 00007ff8`02e1646c msvcr120!_CxxThrowException+0xb3 [f:\dd\vctools\crt\crtw32\eh\throw.cpp @ 154]
02 ...
03 ...
04 ...
In this case the whole stack was available and we could actually get a lot of information right from it: the analysis could proceed and we found the culprit of the problem. Anyway, being able to get a more detailed information from the C++ runtime can help in trickier situations.
We start from the NT Exception Record definition (ehdata.h
):
/////////////////////////////////////////////////////////////////////////////
//
// The NT Exception record that we use to pass information from the throw to
// the possible catches.
//
// The constants in the comments are the values we expect.
// This is based on the definition of EXCEPTION_RECORD in winnt.h.
//
[...]
typedef struct EHExceptionRecord {
DWORD ExceptionCode; // The code of this exception. (= EH_EXCEPTION_NUMBER)
DWORD ExceptionFlags; // Flags determined by NT
struct _EXCEPTION_RECORD *ExceptionRecord; // An extra exception record (not used)
void * ExceptionAddress; // Address at which exception occurred
DWORD NumberParameters; // Number of extended parameters. (= EH_EXCEPTION_PARAMETERS)
struct EHParameters {
DWORD magicNumber; // = EH_MAGIC_NUMBER1
void * pExceptionObject; // Pointer to the actual object thrown
ThrowInfo * pThrowInfo; // Description of thrown object
#if _EH_RELATIVE_OFFSETS
void * pThrowImageBase; // Image base of thrown object
#endif
} params;
} EHExceptionRecord;
Now it’s a bit clearer from whence the .exr -1
info are retrieved; let’s take a look at the #1 frame:
0:000> .frame 0n1;dv /t /v
01 00000045`355fcdf0 00007ff8`02e1646c msvcr120!_CxxThrowException+0xb3 [f:\dd\vctools\crt\crtw32\eh\throw.cpp @ 154]
@rdi void * pExceptionObject = 0x00000045`355fce90
<unavailable> struct _s__ThrowInfo * pThrowInfo = <value unavailable>
00000045`355fce10 struct EHExceptionRecord ThisException = struct EHExceptionRecord
@rbx struct _s_ThrowInfo * pTI = 0x00007ff8`02eef9f0
00000045`355fce60 void * ThrowImageBase = 0x00007ff8`02d60000
<unavailable> struct WinRTExceptionInfo ** ppWei = <value unavailable>
<unavailable> unsigned int64 * exceptionInfoPointer = <value unavailable>
00007ff8`73839840 struct EHExceptionRecord ExceptionTemplate = struct EHExceptionRecord
We can identify here the two parameters passed in to the call to _CxxThrowException
, that is:
pExceptionObject → 0x00000045`355fce90
pThrowInfo → <unavailable>
Starting from pExceptionObject
we can navigate further and get the same values shown in the .exr -1
call:
0:000> dx -r1 (*((msvcr120!EHExceptionRecord *)0x45355fce10))
(*((msvcr120!EHExceptionRecord *)0x45355fce10)) [Type: EHExceptionRecord]
[+0x000] ExceptionCode : 0xe06d7363 [Type: unsigned long]
[+0x004] ExceptionFlags : 0x1 [Type: unsigned long]
[+0x008] ExceptionRecord : 0x0 [Type: _EXCEPTION_RECORD *]
[+0x010] ExceptionAddress : 0x0 [Type: void *]
[+0x018] NumberParameters : 0x4 [Type: unsigned long]
[+0x020] params [Type: EHExceptionRecord::EHParameters]
0:000> dx -r1 (*((msvcr120!EHExceptionRecord::EHParameters *)0x45355fce30))
(*((msvcr120!EHExceptionRecord::EHParameters *)0x45355fce30)) [Type: EHExceptionRecord::EHParameters]
[+0x000] magicNumber : 0x19930520 [Type: unsigned long]
[+0x008] pExceptionObject : 0x45355fce90 [Type: void *]
[+0x010] pThrowInfo : 0x7ff802eef9f0 [Type: _s_ThrowInfo *]
[+0x018] pThrowImageBase : 0x7ff802d60000 [Type: void *]
- Exception Code: 0xe06d7363
- Flags: 1
- Parameters: 4
- Magic number: 0x19930520
- Pointer to Exception Object: 0x45355fce90
- Pointer to ThrowInfo: 0x7ff802eef9f0
HINSTANCE
of the DDL that threw the exception: 0x7ff802d60000
The magic number is an internal value, defined in ehdata.h
as follows
It’s useful to see how the ThrowInfo
is defined:
Navigating the Exception Record
Given all the above, to get the object involved in the exception, we have to do some jumps starting from the pointer of the ThrowInfo
; note that 0x00007ff802eef9f0
is the value of Parameter[2]
we got right from the very first .exr -1
command we did:
0:000> dd 7ff802eef9f0
00007ff8`02eef9f0 00000000 000919c0 00000000 0018fa10 <== we'll use "0018fa10"
00007ff8`02eefa00 00000000 00000000 00000000 00000000
00007ff8`02eefa10 00000001 0018f940 00000000 00000000
00007ff8`02eefa20 00000002 0018fa38 0018f9a0 00000000
00007ff8`02eefa30 00000000 00000000 00000010 001ed9f8
00007ff8`02eefa40 00000000 ffffffff 00000000 00000018
00007ff8`02eefa50 000251e0 00000000 00000000 00000000
00007ff8`02eefa60 00000000 00000000 00000000 0018fac8
we can find here the fields of the ThrowInfo struct
definition above, that is:
0:000> dx -r1 ((msvcr120!_s_ThrowInfo *)0x7ff802eef9f0)
((msvcr120!_s_ThrowInfo *)0x7ff802eef9f0) : 0x7ff802eef9f0 [Type: _s_ThrowInfo *]
[+0x000] attributes : 0x0 [Type: unsigned int]
[+0x004] pmfnUnwind : 596416 [Type: int] =====> 0x000919c0
[+0x008] pForwardCompat : 0 [Type: int]
[+0x00c] pCatchableTypeArray : 1636880 [Type: int] ==> 0x0018fa10
We use the HINSTANCE
to displace the pointer to pCatchableTypeArray
(of type CatchableType
):
// Step (1) 0x00007ff802d60000 + 0x0018fa10 = 0x00007FF802EEFA10
0:000> dd 0x00007FF802EEFA10
00007ff8`02eefa10 00000001 0018f940 00000000 00000000 // <== we'll use 0018f940
// Step (2) 0x00007ff802d60000 + 0x0018f940 = 0x00007FF802EEF940
0:000> dd 0x00007FF802EEF940
00007ff8`02eef940 00000000 001ef8e8 00000000 ffffffff // <== we'll use 001ef8e8
Step (1): in the CatchableTypeArray
the first integer is the number of items (0x00000001
in this case); 0x0018f940
is the address of the array of CatchableType
.
Let’s take a look at the definition of a CatchableType
:
Using these details we can better understand the:
Step (2): we use the address of the array we’ve found (0x0018f940
) and we displace by the usual HINSTANCE
, getting:
00000000 →
properties
0x00000000’001ef8e8 →
pointer to the TypeDescriptor
, which definition is:
what we’ll do next -Step (3)- is to use the TypeDescriptor
pointer and jump forward by two pointers (0x10 – we skip pVFTable
and spare
) to go to the decorated name of the exception type; we’ll display it by using the WinDbg “da
” command:
// Step(3): 0x00007ff802d60000 + 0x001ef8e8 + 0x10 = 0x00007FF802F4F8F8
0:000> da 0x00007FF802F4F8F8
00007ff8`02f4f8f8 ".?AVbad_alloc@std@@" ===> here's the decorated name of the actual exception!
In this dump the exception was not a bad allocation (I replaced the name here to anonymize the actual type…) but at this point we should have found the final information that will give us a better understanding of the problem, instead of the generic error 0xE06D7363.