A customer was getting crashes with the following stack:
ucrtbase!abort+0x4e ucrtbase!terminate+0x29 ucrtbase!FindHandler<__FrameHandler4>+0x4f4 ucrtbase!__InternalCxxFrameHandler<__FrameHandler4>+0x276 ucrtbase!__InternalCxxFrameHandlerWrapper<__FrameHandler4>+0x3e ucrtbase!__CxxFrameHandler4+0xa9 contoso!__GSHandlerCheck_EH4+0x64 ntdll!RtlpExecuteHandlerForException+0xf ntdll!RtlDispatchException+0x26c ntdll!RtlRaiseException+0x195 KERNELBASE!RaiseException+0x6c ucrtbase!_CxxThrowException+0x9a contoso!winrt::throw_hresult+0xd3 contoso!winrt::check_hresult+0xcf contoso!winrt::impl::consume_Microsoft_UI_Xaml_Controls_ Primitives_IFlyoutBase<winrt::Microsoft::UI::Xaml:: Controls::Primitives::IFlyoutBase>::Hide+0xe9 contoso!MyPanel::CancelCurrentFlyout+0x25e contoso!MyPanel::IsReadyForNewContextMenu+0x4b contoso!MyPanel::DoContextMenu+0x1ac contoso!MyPanel::WndProc+0x7f2 user32!UserCallWinProcCheckWow+0x2ba user32!DispatchMessageWorker+0x2b0 contoso!CMainWindow::Run+0xde contoso!wWinMain+0xd69 contoso!invoke_main+0x21 contoso!__scrt_common_main_seh+0x110 kernel32!BaseThreadInitThunk+0x1d ntdll!RtlUserThreadStart+0x28
The block of stack frames at the top is the C++ runtime infrastructure throwing an exception and looking for a handler. Eventually, no handler is found, and we end up terminating due to an unhandled exception.
The next block of stack frames is the code that threw the unhandled exception. From the winrt::
, we know that this code is written in C++/WinRT, and the check_
tells us that we are converting a failure HRESULT
into an exception. The next frame tells us that this failure HRESULT
came from a call to IFlyoutBase::
.
The name of that frame breaks down like this:
winrt::impl::
C++/WinRT internal methodconsume_
Wrapper for client code to call into Windows Runtime component (consuming an interface)Microsoft_
The interface we called isUI_ Xaml_ Controls_ Primitives_ IFlyoutBase Microsoft::
.UI:: Xaml:: Controls:: Primitives:: IFlyoutBase <winrt::
The interface is implemented by an object whose type isMicrosoft:: UI:: Xaml:: Controls:: Primitives:: IFlyoutBase> winrt::
. In this case, we are invoking it directly on the interface, so the interface simply implements itself, and this part looks silly.Microsoft:: UI:: Xaml:: Controls:: Primitives:: IFlyoutBase Hide
is the method being invoked.
Okay, so our call on the IFlyoutBase::
method failed with an “invalid parameter” exception. How can a method with no parameters have an invalid parameter?
Here’s the call in the Contoso app:
void MyPanel::CancelCurrentFlyout() { // Cancel any pending action. if (!m_actionPending) { InvokeCancel(); } // Hide any open flyout. if (m_menu && ((m_flyoutState == FlyoutState::Opened) || (m_flyoutState == FlyoutState::Opening))) { m_menu.Hide(); /* we failed here */ } TransitionFlyoutState(FlyoutState::Closed); ResetDataModelAndTasks(); }
Let’s look more closely at the exception that was thrown. We know that it was an hresult_error
, so we can just dump it straight away.
0:000> .frame b 0b 00000000`0107c670 00007ffa`84e2d24b ucrtbase!_CxxThrowException+0x9a 0:000> dv pExceptionObject = 0x00000000`0107c700 ... 0:000> dps 0x00000000`0107c700 L3 00000000`0107c700 00000000`00000000 00000000`0107c708 80070057`aabbccdd // There's our ERROR_INVALID_PARAMETER 00000000`0107c710 00000000`2c81b7a8 // Here's the IErrorInfo
Although we don’t know the internal layout of the IErrorInfo
, we can dump it to see if anything jumps out at us.
0:000> dc 0x00000000`2c81b7a8 00000000`2c81b7a8 6b70cca8 00007ffa 6b70cb30 00007ffa ..pk....0.pk.... 00000000`2c81b7b8 6b70cad0 00007ffa 6b70ccd0 00007ffa ..pk......pk.... 00000000`2c81b7c8 6b70cb78 00007ffa 6b70cc50 00007ffa x.pk....P.pk.... 00000000`2c81b7d8 6b70cc08 00007ffa 6b70caf8 00007ffa ..pk......pk.... 00000000`2c81b7e8 00000000 00000001 28a6a1a0 00000000 ...........(.... 00000000`2c81b7f8 290bc760 00000000 00000000 00000000 `..)............ 00000000`2c81b808 80070057 00000000 00000000 00000000 W............... 00000000`2c81b818 00000000 00000000 00010002 00000039 ............9... 00000000`2c81b828 283dbe90 00000000 00000008 00000000 ..=(............ 00000000`2c81b838 00000038 53453032 80070057 000056dd 8...20ESW....V.. 00000000`2c81b848 6b4d1bf8 00007ffa 00000008 00000039 ..Mk........9... 00000000`2c81b858 283dbe90 00000000 00000000 00000000 ..=(............ 00000000`2c81b868 00000000 00000000 00000000 00000000 ................ 00000000`2c81b878 43832534 35316d47 108a28cb 09f94d9d 4%.CGm15.(...M.. 00000000`2c81b888 00000000 00000000 2c81ab08 00000000 ...........,.... 00000000`2c81b898 00000000 00000000 00000002 00000000 ................
After a lot of introductory stuff, we recognize the failure HRESULT
of 0x80070057
, but more interestingly, the signature value 53453032
which decodes to the ASCII characters SE02
, the signature for STOWED_
. There’s a good chance this is a STOWED_
structure. Let’s ask the !pde.dse
extension to dump it.
The !pde.dse
extension optionally takes a pointer to an array of stowed exception pointers. To dump a single stowed exception, we’ll have to create one of these arrays and ask !pde.dse
to dump it.
0:000> eq @rsp-8 0x00000000`2c81b7a8 0:000> !pde.dse @rsp-8 Stowed Exception Array @ 0x00000000035477e8 Stowed Exception #1 @ 0x000000002c81b838 0x80070057: E_INVALIDARG - One or more arguments are not valid Stack : 0x283dbe90 combase!RoOriginateErrorW+0x131 Microsoft_UI_Xaml!DirectUI::ErrorHelper::OriginateError+0x172 Microsoft_UI_Xaml!DirectUI::ErrorHelper::OriginateError+0x28 Microsoft_UI_Xaml!DirectUI::VisualTreeHelper::GetOpenPopupsForXamlRoot+0x63 contoso!<lambda_⟦…⟧>::operator()+0x3e contoso!winrt::⟦…⟧::VisualTreeHelper::GetOpenPopupsForXamlRoot+0x43 contoso!<lambda_⟦…⟧>::operator()+0x7f contoso!winrt::impl::delegate<winrt::⟦…⟧::TypedEventHandler< winrt::⟦…⟧::FlyoutBase, winrt::⟦…⟧::FlyoutBaseClosingEventArgs>, <lambda_⟦…⟧> >::Invoke+0x22 Microsoft_UI_Xaml!DirectUI::CEventSourceBase<⟦…⟧>::Raise+0xa0 Microsoft_UI_Xaml!DirectUI::FlyoutBase::OnClosing+0x7c Microsoft_UI_Xaml!DirectUI::FlyoutBase::HideImpl+0x3b Microsoft_UI_Xaml!DirectUI::FlyoutBaseGenerated::Hide+0x52 contoso!MyPanel::CancelCurrentFlyout+0x25e contoso!MyPanel::IsReadyForNewContextMenu+0x4b contoso!MyPanel::DoContextMenu+0x1ac contoso!MyPanel::WndProc+0x7f2 user32!UserCallWinProcCheckWow+0x2ba user32!DispatchMessageWorker+0x2b0 contoso!CMainWindow::Run+0xde contoso!wWinMain+0xd69 contoso!invoke_main+0x21 contoso!__scrt_common_main_seh+0x110 kernel32!BaseThreadInitThunk+0x1d ntdll!RtlUserThreadStart+0x28
Notice that the deeper part of the stack matches our current stack. This is expected because the stowed exception captures the error origination slightly deeper in the stack, so the stuff outside MyPanel::
will still be the same.
Note also that the stack trace from the stowed exception doesn’t include inlined functions, because the stack capturing code doesn’t have access to symbols. It just walks the stack frames.
So let’s look at the new information we gained from the stowed exception:
combase!RoOriginateErrorW+0x131 Microsoft_UI_Xaml!DirectUI::ErrorHelper::OriginateError+0x172 Microsoft_UI_Xaml!DirectUI::ErrorHelper::OriginateError+0x28 Microsoft_UI_Xaml!DirectUI::VisualTreeHelper::GetOpenPopupsForXamlRoot+0x63 contoso!`winrt::⟦…⟧::VisualTreeHelper::GetOpenPopupsForXamlRoot`:: <lambda_1>::operator()+0x3e contoso!winrt::⟦…⟧::VisualTreeHelper::GetOpenPopupsForXamlRoot+0x43 contoso!`winrt::⟦…⟧::MenuFlyoutControl::CreateMenu`::<lambda_1>::operator()+0x7f contoso!winrt::impl::delegate<winrt::⟦…⟧::TypedEventHandler< winrt::⟦…⟧::FlyoutBase, winrt::⟦…⟧::FlyoutBaseClosingEventArgs>, `winrt::⟦…⟧::MenuFlyoutControl::CreateMenu`::<lambda_1> >::Invoke+0x22 Microsoft_UI_Xaml!DirectUI::CEventSourceBase<⟦…⟧>::Raise+0xa0 Microsoft_UI_Xaml!DirectUI::FlyoutBase::OnClosing+0x7c Microsoft_UI_Xaml!DirectUI::FlyoutBase::HideImpl+0x3b Microsoft_UI_Xaml!DirectUI::FlyoutBaseGenerated::Hide+0x52
Reading from the bottom: Our code called the Hide
method, which went into HideImpl
, which called OnClosing
. Based on the name of the method and the fact that it calls CEventSourceBase::
, it’s apparent that this code is raising the Closing
event. The delegate registered for this event is a lambda in MenuÂFlyoutÂControl::
, and it called GetÂOpenÂPopupsÂForÂXamlÂRoot
, which decided to return an “invalid argument” error.
Let’s see why.
0:000> u 7ffa266ad663-63 7ffa266ad663 DirectUI::VisualTreeHelper::GetOpenPopupsForXamlRoot: 00007ffa`266ad600 mov qword ptr [rsp+20h],rbx 00007ffa`266ad605 push rbp 00007ffa`266ad606 push rsi 00007ffa`266ad607 push rdi 00007ffa`266ad608 sub rsp,30h 00007ffa`266ad60c mov rax,qword ptr [_security_cookie (00007ffa`26bef010)] 00007ffa`266ad613 xor rax,rsp 00007ffa`266ad616 mov qword ptr [rsp+28h],rax 00007ffa`266ad61b and qword ptr [rsp+20h],0 00007ffa`266ad621 mov rsi,r8 00007ffa`266ad624 mov rdi,rdx 00007ffa`266ad627 mov rbp,rcx 00007ffa`266ad62a test r8,r8 00007ffa`266ad62d jne 00007ffa`266ad64a 00007ffa`266ad62f lea r8,[`string' (00007ffa`26904470)] 00007ffa`266ad636 mov ecx,80070057h 00007ffa`266ad63b lea edx,[rsi+0Bh] 00007ffa`266ad63e call DirectUI::ErrorHelper::OriginateError 00007ffa`266ad643 mov ebx,eax 00007ffa`266ad645 jmp 00007ffa`266ad6f3 00007ffa`266ad64a test rdi,rdi 00007ffa`266ad64d jne 00007ffa`266ad675 00007ffa`266ad64f lea r8,[`string' (00007ffa`26b663b0)] 00007ffa`266ad656 mov ecx,80070057h 00007ffa`266ad65b lea edx,[rdi+8] 00007ffa`266ad65e call DirectUI::ErrorHelper::OriginateError 00007ffa`266ad663 mov ebx,eax
The code that leads up to the error breaks up into three parts:
00007ffa`266ad600 mov qword ptr [rsp+20h],rbx 00007ffa`266ad605 push rbp 00007ffa`266ad606 push rsi 00007ffa`266ad607 push rdi 00007ffa`266ad608 sub rsp,30h 00007ffa`266ad60c mov rax,qword ptr [_security_cookie (00007ffa`26bef010)] 00007ffa`266ad613 xor rax,rsp 00007ffa`266ad616 mov qword ptr [rsp+28h],rax 00007ffa`266ad61b and qword ptr [rsp+20h],0 00007ffa`266ad621 mov rsi,r8 00007ffa`266ad624 mov rdi,rdx 00007ffa`266ad627 mov rbp,rcx
This first section is function prologue stuff and initializing local variables. The inbound parameters are stored in rbp
, rdi
, and rsi
. Therefore we have this so far:
rbp = rcx |
this |
rdi = rdx |
xamlRoot |
rsi = r8 |
result (output) |
Next up is this section:
00007ffa`266ad62a test r8,r8 00007ffa`266ad62d jne 00007ffa`266ad64a 00007ffa`266ad62f lea r8,[`string' (00007ffa`26904470)] 00007ffa`266ad636 mov ecx,80070057h 00007ffa`266ad63b lea edx,[rsi+0Bh] 00007ffa`266ad63e call DirectUI::ErrorHelper::OriginateError 00007ffa`266ad643 mov ebx,eax 00007ffa`266ad645 jmp 00007ffa`266ad6f3
If r8
is zero, then we return 0x80070057
= E_INVALIDARG
with a custom message:
0:000> du 00007ffa`26904470 00007ffa`26904470 "returnValue"
Okay, so first we verify that the result parameter (which I guess this function calls returnValue
) is not null; if it is, we fail with E_INVALIDARG
.
And then we have this:
00007ffa`266ad64a test rdi,rdi 00007ffa`266ad64d jne 00007ffa`266ad675 00007ffa`266ad64f lea r8,[`string' (00007ffa`26b663b0)] 00007ffa`266ad656 mov ecx,80070057h 00007ffa`266ad65b lea edx,[rdi+8] 00007ffa`266ad65e call DirectUI::ErrorHelper::OriginateError 00007ffa`266ad663 mov ebx,eax
This returns E_INVALIDARG
with a custom message if the rdi
register is zero, which our table above tells us corresponds to the xamlRoot
parameter. The error message confirms this:
0:000> du 00007ffa`26b663b0 00007ffa`26b663b0 "xamlRoot"
So we got an “invalid argument” error because xamlRoot
was null. Working backward, we can look at the Closing
event handler:
void MenuFlyoutControl::CreateMenu(const winrt::IIterable& menuItems) { ⟦…⟧ m_closingRevoker = flyout.Closing(winrt::auto_revoke, [](auto&& sender, auto&&) { if (auto flyout = sender.try_as<xaml::CommandBarFlyout>()) { auto popups = xaml::VisualTreeHelper::GetOpenPopupsForXamlRoot(flyout.XamlRoot()); ⟦…⟧ } }); ⟦…⟧ }
We infer therefore that flyout.XamlRoot()
was null. And that’s what generates the “invalid parameter” exception: Null is not a valid parameter for GetÂOpenÂPopupsÂForÂXamlÂRoot
.
This invalid parameter error projects as an exception, which causes the Closing
event handler to fail with an exception, which is then transformed back into E_INVALIDARG
at the ABI. This error presumably propagates out of the Raise
method back into OnClosing
and then to HideImpl
, and then back to the caller, which causes m_menu.Hide()
to fail with an invalid argument exception, which is where our crash dump was generated.
The customer fixed the problem by revising the Closing
event handler to skip the call to GetÂOpenÂPopupsÂForÂXamlÂRoot
if flyout.XamlRoot()
is null, and just assume that there are no popups.
Bonus viewing: Stowed Exception C000027B: A video with the creator of the !pde.dse
debugger extension. The array of pointers shows up at time code 5:24.
Wow, debugging such a ‘simple something is null’ exception looks fairly hard and not straight forward.
So no chance to debug such issues if you don’t have a good understanding of WinDbg and the memory layout of some WinRT objects? :s
When would you have an array of stowed exception pointers?