Recognizing different types of exception objects that Windows platform libraries can throw

Raymond Chen

Raymond

Last time, we saw how to dig the exception object out of a std::exception_ptr. But what kind of exception objects might there be?

For C++/CX code, you are probably going to get a Platform::Exception^.

0:007> ?? p
class std::exception_ptr
   +0x000 _Data1           : 0x08a5885c Void
   +0x004 _Data2           : 0x08a58850 Void

We learned that the _Data1 points to an EXCEPTION_RECORD¹

0:007> .exr 0x08a5885c
ExceptionAddress: 00000000
   ExceptionCode: e06d7363 (C++ EH exception)
  ExceptionFlags: 00000001
NumberParameters: 3
   Parameter[0]: 19930520
   Parameter[1]: 08a61330
   Parameter[2]: b371fc08
  pExceptionObject: 08a61330
  _s_ThrowInfo    : b371fc08

And we noted that Parameter[1] is the object that was thrown.

0:007> dps 08a61330
08a61330  08a5b088
08a61334  fdfdfdfd
08a61338  ...

A hat pointer ^ is just a COM pointer in disguise, so let’s dump the pointer to see what’s there.

0:007> dps 08a5b088 l5
08a5b088  793d0640 vccorlib140d_app!Platform::Exception::`vftable'
08a5b08c  793d065c vccorlib140d_app!Platform::Exception::`vftable'
08a5b090  793d0680 vccorlib140d_app!Platform::Exception::`vftable'
08a5b094  793d06ac vccorlib140d_app!Platform::Exception::`vftable'
08a5b098  00e5c694

The vtable tells us that we have a Platform::Exception, which, to be fair, isn’t particularly surprising.

0:007> dt  vccorlib140d_app!Platform::Exception 08a5b088
   +0x000 __VFN_table : 0x793d0640
   +0x004 __VFN_table : 0x793d065c
   +0x008 __VFN_table : 0x793d0680
   +0x00c __VFN_table : 0x793d06ac
   +0x010 __description    : 0x00e5c694 Void
   +0x014 __restrictedErrorString : 0x00e5da54 Void
   +0x018 __restrictedErrorReference : (null)
   +0x01c __capabilitySid  : (null)
   +0x020 __hresult        : 0n-2147483635
   +0x024 __restrictedInfo : 0x08a3843c Void
   +0x028 __throwInfo      : 0x793c6c24 Void
   +0x02c __size           : 0x20
   +0x030 __prepare        : Platform::IntPtr
   +0x034 __abi_reference_count : __abi_FTMWeakRefData
   +0x03c __abi_disposed   : 0

Okay, now we have something. The __hresult is 0n-2147483635, which is the debugger’s strange way of saying 0x8000000D which is E_ILLEGAL_STATE_CHANGE.

If you’re writing in C++/WinRT, then the thing that is thrown will probably be a winrt::hresult_error.

Go through the same exercise as before, but this time when we dump the thrown object we get

0:007> dps 0942b8e0
0942b8e0  00000000
0942b8e4  aabbccdd
0942b8e8  80004002
0942b8ec  09439b5c
0942b8f0  fdfdfdfd

The structure of an hresult_error is currently

    bstr_handle m_debug_reference;
    uint32_t m_debug_magic{ 0xAABBCCDD };
    hresult m_code;
    com_ptr<IRestrictedErrorInfo> m_info;

We can match those up to the dump above. The m_debug_reference is nullptr, the m_debug_magic is the expected value of 0xxAABBCCDD (which is good, because it acts as confirmation that what we have really is an hresult_error), the m_code is 0x80004002, which is E_NOINTERFACE, and the m_info is 0x09439b5c, whatever that is.

If you use WIL, then you may have a wil::ResultException, which looks like this in memory:

struct ResultException : std::exception
{
    FailureType type; // 0 means FailureType::Exception
    HRESULT hr;
    long failureId;
    PCWSTR pszMessage;
    DWORD threadId;
    PCSTR pszCode;
    PCSTR pszFunction;
    PCSTR pszFile;
    uint32_t uLineNumber;
    ... more stuff ...
};
0:000> dps 000001e8a083edb0
000001e8`a083edb0  00007ff7`e2200298 Win32!wil::ResultException::`vftable'
000001e8`a083edb8  00000000`00000000 // std::exception
000001e8`a083edc0  00000000`00000000 // std::exception
000001e8`a083edc8  8007139f`00000000 // hr / type (0 = Exception)
000001e8`a083edd0  cccccccc`00000042 // padding / failureId
000001e8`a083edd8  00000000`00000000 // pszMessage
000001e8`a083ede0  cccccccc`00001de4 // padding / threadId
000001e8`a083ede8  000001e8`a0833a14 // pszCode
000001e8`a083edf0  000001e8`a0833a26 // pszFunction
000001e8`a083edf8  000001e8`a0833a2f // pszFile
000001e8`a083ee00  00000001`0000001e // cFailureCount / uLineNumber

From this, we can extract that the HRESULT was 0x8007139f which is E_NOT_VALID_STATE, there is no custom message (pszMessage is null), and we have pointers to various strings plus a line number.

0:000> $ pszCode is the expression that failed
0:000> da 000001e8`a0833a14
000001e8`a0833a14  "TrySomething(a, b, c)"

0:000> $ pszFunction is the function that threw
0:000> da 000001e8`a0833a26
000001e8`a0833a26  "Foo::Bar"

0:000> $ pszFile is the file name
0:000> da 000001e8`a0833a2f
000001e8`a0833a2f  "C:\\Test\\Bar.cpp"

So we see that this exception was the result of a

THROW_IF_FAILED(TrySomething(a, b, c));

that can be found in the function Foo::Bar at line 30 (0x001e) of the file C:\Test\Bar.cpp. It was the 0x42‘th error encountered by the program.

If you have symbols, you can ask the debugger to print this all nice and pretty for you.

0:000> ?? ((Contoso!wil::ResultException*)0x000001e8a083edb0)
class wil::ResultException * 0x000001e8`a083edb0
   +0x000 __VFN_table : 0x00007ff7`c9de0308
   +0x008 _Data            : __std_exception_data
   +0x018 m_failure        : wil::StoredFailureInfo
   +0x0b8 m_what           : wil::details::shared_buffer

0:000> ?? ((Contoso!wil::ResultException*)0x000001e8a083edb0)->m_failure
class wil::StoredFailureInfo
   +0x000 m_failureInfo    : wil::FailureInfo
   +0x090 m_spStrings      : wil::details::shared_buffer

0:000> ?? ((Contoso!wil::ResultException*)0x000001e8a083edb0)->m_failure.m_failureInfo
truct wil::FailureInfo
   +0x000 type             : 0 ( Exception )
   +0x004 hr               : 8007139f
   +0x008 failureId        : 0n66
   +0x010 pszMessage       : (null)
   +0x018 threadId         : 0x1de4
   +0x020 pszCode          : 0x000001e8`a0833a14  "TrySomething(a, b, c)"
   +0x028 pszFunction      : 0x000001e8`a0833a26  "Foo::Bar"
   +0x030 pszFile          : 0x000001e8`a0833a2f  "C:\Test\Bar.cpp"
   +0x038 uLineNumber      : 0x1e
   +0x03c cFailureCount    : 0n1
   +0x040 pszCallContext   : (null)
   +0x048 callContextOriginating : wil::CallContextInfo
   +0x060 callContextCurrent : wil::CallContextInfo
   +0x078 pszModule        : 0x000001e8`a0833a38  "Test.exe"
   +0x080 returnAddress    : 0x00007ff7`c9dcf993 Void
   +0x088 callerReturnAddress : 0x00007ff7`c9dd7b65 Void

You can see the other stuff in the FailureInfo structure, like the module name, and a few return addresses.

Of course, the exception could have come from something else, in which case you’ll have to do your own decoding.

0:000> dps 0x00000090`5172f8a0 l4
00000090`5172f8a0  00007ffb`22408b98 std::time_put<...>::`vftable'+0x7b8
00000090`5172f8a8  0000026b`841b4b10
00000090`5172f8b0  00000000`00000001

This looks like somebody threw a std::time_put, but you’re just being faked out by COMDAT folding. You can dig into the exception type data to see what it is, or you can just guess at pointers, in the hope that one of them will give you a clue.

Fortunately, many exceptions derive from std::exception, and in MSVC, the std::exception has a string pointer.

0:000> da 0000026b`841b4b10
0000026b`841b4b10  "invalid vector<T> subscript"

Aha, so this perticular guy was an invalid subscript exception from std::vector, also known as std::out_of_range.

Those are the four commonly-thrown exceptions I was planning to cover today. They are the ones that come from the libraries commonly used in Windows client code.²

¹ Note that this is an implementation detail that may change at any time. This information is provided for debugging purposes. The fields have opaque names to discourage people from relying on said implementation details. But we’re debugging here, so we can deal with the possibility that the fields no longer mean what we think they mean. Debugging is an exercise in optimism.

² I left out MFC’s CException. Sorry, MFC developers.

0 comments

Comments are closed. Login to edit/delete your existing comments