分类目录归档:The Old New Thing

伟大的产品-windows进化启示录

std::move操作导致的一个神秘崩溃

原文:The mystery of the crash that seems to be on a std::move operation – The Old New Thing (microsoft.com) January 20th, 2022

客户遇到了只在ARM上出现的程序崩溃。下面是一个简化的版本

-
void polarity_test(std::shared_ptr<Test> test)
{
    test->harness->callAndReport([test2 = std::move(test)]() mutable
    {
        test2->reverse_polarity();
        ::resume_on_main_thread([test3 = std::move(test2)]()
        {
            test3->reverse_polarity();
        });
    });
}
-

他们说,只在第一行就崩溃了:

test->harness->callAndReport([test2 = std::move(test)]() mutable

现在,std::move实际上并不生成任何代码。它只是把引用从左值改为右值,这是一个完全在计算机大脑中(编译阶段)进行的操作。它不需要生成代码。

问题在别的地方。

由于该问题只发生在一个CPU架构(ARM)上,因此后端代码生成器(编译器)可能存在bug。但是为了安全起见,他们联系了编译器前端团队、后端团队(用于代码生成)和库团队(用于shared_ptr)。

我介入并指出,有一个求值顺序(order-of-evaluation)的依赖关系。

test->harness->callAndReport([test2 = std::move(test)]() mutable

语句的左边从test中读取。lambda捕获修改了test(通过std::move将其移动到捕获的变量test2)。

历史上,大多数子表达式的求值顺序是不确定的,虽然有一些操作定义了一个顺序,最明显的是短路表达式(short-circuiting)求值在第二个操作数之前计算第一个操作数(如果有的话)¹。

传统的表达式排序规则不要求在计算参数之前必须决定调用哪个函数。

传统的依赖关系图是这样的²:

test 
 
operator-> 
 
harness 
 
operator->test2 = std::move(test)
callAndReportlambda constructed
function call

由于在左边读取test和在右边修改test之间没有依赖关系,操作可以以任意一种顺序发生。

然后c++ 17出现了。

c++ 17增加了在传统规则之外的额外的求值顺序规则:在下面的表达式中,a在b之前求值:

OperationDescription
a(b)Function call
a[b]Subscript operator
a.*b
a->*b
Pointer to member
a << b
a >> b
Shifting
b = a
b op= a
Assignment
(note: right to left)

就我个人而言,我发现“标准”选择在参数之前对函数求值是很有趣的。在实践中,如果函数是通过指针追踪来识别的,那么首先计算参数会更方便,因为这样做不会干扰很多寄存器。

无论如何,由于函数调用现在在参数之前求值,从c++ 17开始的求值顺序现在要求在lambda中test2 = std::move(test)之前求值左边的test。

因此,问题归结为客户正在使用的语言版本。
客户回来后说,他们正在使用Visual Studio 2019,但使用的是c++ 14模式。
这就解释了。


下次,我们将看看潜在的修复(除了“升级到c++ 17”)。

¹编译器以任何顺序求值的自由情况导致底层体系结构会影响操作顺序。基于堆栈的参数更有可能在基于寄存器参数之前计算:一旦计算了基于寄存器参数的值,就必须在计算其他参数时找到保存它的位置。你可以尝试将它保存在用来传递参数的寄存器中(好),或者你可以尝试将它暂时保存在另一个寄存器中(好),或者你可以spill它并在调用之前重新加载它(坏)。如果其他参数计算起来很复杂,您可能会被迫spill。另一方面,基于堆栈的参数无论如何都会spill到堆栈中,所以您可以计算它并spill它,这样就完成了。在调用之前,您不必复刻(burn)一个寄存器来保存参数。

这意味着即使您只考虑调用约定,最优的求值顺序也可能在x86-32(没有寄存器参数,除了这个)、x86-64/arm(4个寄存器参数)和arm64(8个寄存器参数)之间变化。

²尽管在传统的排序中,我在前面的操作符->之后显示了harness,这并不是语言的规则,是精神内联的产物。真正发生的是

 is

test 
 
operator->&Test::harness
operator->*
(produces harness)

但是 &Test::harness不依赖任何东西。

这是什么意思?由于有一个主机终止处于队列中,因此在操作完成前,调用方指定的等待超时。

原文:What does this mean? The caller specified wait timed out before the operation completed because a host termination is in queued – The Old New Thing (microsoft.com)September 20th, 2021

因此,您的程序正在运行,处理自己的业务,然后它因为异常0x80070bfe崩溃了:“由于有一个主机终止处于队列中,因此在操作完成前,调用方指定的等待超时”。或可能是0x80070bfd :“在操作完成之前,调用方指定的等待超时”。这完全是胡说八道。这是什么意思?我不记得指定了任何超时,我也不知道主机终止是什么。

这意味着当你的应用程序暂停或恢复时,你调用了Core­Application.Create­New­View。

这个特定的错误是在窗口基础结构代码的深处产生的,问题表现为内部超时。内部low-level组件报告操作超时,这就是错误消息提示为超时的原因。

然后,这个错误会一路向上传播到应用程序,而没有人意识到,“嘿,这个错误可能对我将要报告它的人没有意义”。错误消息在最初的时候是有意义的,但是当错误到达应用程序时,错误的原始上下文已经消失了,除非您戴着可以看穿基础结构的有色眼镜,否则消息就没有意义了。

抱歉!

附加聊天:有时会报告错误码0X87B20C08:这个错误码甚至没有相关的文本信息。

=========================

我们使用SDK时,一定要对SDK错误进行处理。因为SDK错误信息相对专业,最终最终用户是看不懂的。

CertUtil程序将可解码Windows错误代码,并可使用多种形式

原文地址:The CertUtil program will decode Windows error codes, and in a variety of formats – The Old New Thing (microsoft.com)September 21st, 2021

前段时间,我注意到The NET HELPMSG command will decode Windows error codes, at least the simple ones.

Stefan Kanthak指出,还有另一个内置程序可以将数字转换为错误消息,它处理的错误数字和格式比NET HELPMSG多得多。

certutil /error 2
certutil /error 0x80070002
certutil /error -2147024894
certutil /error 2147942402
certutil /error -0x7ff8fffe

第一个显示:

0x2 (WIN32: 2 ERROR_FILE_NOT_FOUND) -- 2 (2)
Error message text: The system cannot find the file specified.
CertUtil: -error command completed successfully.

另外几条说的是同样的事情,但错误号不同:

0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND) -- 2147942402 (-2147024894)
Error message text: The system cannot find the file specified.
CertUtil: -error command completed successfully.

在c++ /WinRT中转换UTF-8字符串和UTF-16字符串

原文:Converting between UTF-8 strings and UTF-16 strings in C++/WinRT – The Old New Thing (microsoft.com)September 22nd, 2021

c++ /WinRT提供了一组函数,用于在UTF-8字符串(底层代码使用char表达)和UTF-16字符串(底层代码使用wchar_t)之间进行转换。

to_string函数接受UTF-16编码的std::wstring_view,并将它们转换为UTF-8字符串,表示为std::string。

相反,to_hstring函数接受UTF-8编码的std::string_view,并将它们转换为UTF-16字符串,表示为winrt::hstring。

to_string和to_hstring的参数可以是任何可转换为相应的字符串视图类型的参数。属于这个类别的类型包括:

TypeConverts to
std::stringstd::string_view
std::wstringstd::wstring_view
winrt::hstringstd::wstring_view

以后,我们可以好好利用这些转换~

一个UWP应用在Windows 10X上启动时崩溃的案例

原文:The case of the UWP application that crashes at launch on Windows 10X – The Old New Thing (microsoft.com)September 24th, 2021

Windows 10X应用程序兼容性测试发现一个程序在启动时崩溃。

我们能够获得应用程序运行崩溃时的栈跟踪。让我们进行一些应用程序兼容性debugging。

尽管Windows10x项目已经被搁置,但对于其他非桌面平台(如Xbox)来说,这个case仍然是一个教训。所以,让我们深入研究。

应用程序在此终止点:

 # Call Site
00 SharedLibrary!$8_NativePrimitiveDecoder.DecodeUnsigned+0xe3
01 SharedLibrary!$11_NativeReader.DecodeUnsigned+0x3b
02 SharedLibrary!$14_NativeParser.GetUnsigned+0x23
03 SharedLibrary!$11_ExecutionEnvironmentImplementation.TryGetMethodForOriginalLdFtnResult_Inner+0x30a
04 SharedLibrary!$11_ExecutionEnvironmentImplementation.TryGetMethodForOriginalLdFtnResult+0xa9
05 SharedLibrary!$11_ReflectionExecutionDomainCallbacksImplementation.GetMethodNameFromStartAddressIfAvailable+0x45
06 SharedLibrary!DeveloperExperience.CreateStackTraceString+0x65
07 SharedLibrary!StackTraceHelper.FormatStackTrace+0x8b
08 SharedLibrary!Exception.get_StackTrace+0x47
09 Contoso+0x1f6eaa
0a Contoso+0x5a2dc3
0b Windows_UI_Xaml!GetStringRawBuffer+0x1028
0c Windows_UI_Xaml!GetStringRawBuffer+0x1225
0d Windows_UI_Xaml!GetStringRawBuffer+0x46bc1
0e Windows_UI_Xaml!GetStringRawBuffer+0x46a68
0f Windows_UI_Xaml!GetStringRawBuffer+0x46443
10 Windows_UI_Xaml!GetStringRawBuffer+0x46269
11 twinapi_appcore!GitInvokeHelper<IEventHandler<Windows::ApplicationModel::Core::UnhandledErrorDetectedEventArgs *>>::Invoke+0x5a
12 twinapi_appcore!UnhandledErrorInvokeHelper::Invoke+0x1a
13 twinapi_appcore!EventSource<IEventHandler<UnhandledErrorDetectedEventArgs *>>::InvokeAll::lambda::operator()+0x1d
14 twinapi_appcore!InvokeTraits<2>::InvokeDelegates+0x4f
15 twinapi_appcore!EventSource<IEventHandler<UnhandledErrorDetectedEventArgs *>>::DoInvoke+0x7b
16 twinapi_appcore!EventSource<IEventHandler<UnhandledErrorDetectedEventArgs *>>::InvokeAll+0x29
17 twinapi_appcore!CoreApplication::ForwardLocalError+0x73
18 twinapi_appcore!CoreApplicationFactory::ForwardLocalError+0xf0
19 combase!CallErrorForwarder+0x138
1a SharedLibrary!$8_ExceptionHelpers.ReportUnhandledError+0xcd
1b SharedLibrary!$8_InteropCallbacks.ReportUnhandledError+0x9
1c Contoso+0x35d8d
1d SharedLibrary!RuntimeExceptionHelpers.ReportUnhandledException+0x63
1e SharedLibrary!RuntimeAugments.ReportUnhandledException+0x9
1f SharedLibrary!$13_Invoker.InvokeCore$catch$0+0xa
20 mrt100_app!RhpCallCatchFunclet2
21 mrt100_app!RhRethrow+0x4c9
22 mrt100_app!RhThrowEx+0x4f
23 mrt100_app!RhpThrowEx2
24 SharedLibrary!ExceptionServices::ExceptionDispatchInfo.Throw+0x22
25 SharedLibrary!$22_ExceptionDispatchHelper::<>c__DisplayClass0.<ThrowAsync>b__3+0x1f
26 SharedLibrary!$13_WinRTSynchronizationContext::Invoker.InvokeCore+0x4d
27 SharedLibrary!$13_WinRTSynchronizationContext::Invoker.Invoke+0x1c
28 SharedLibrary!Func$2<__Canon,TimeSpan>.InvokeOpenStaticThunk+0x1a
29 SharedLibrary!$25_AsyncOperationWithProgressCompletedHandler$2<__Canon,UInt32>.Invoke+0x14
2a Contoso+0x35d78
2b Contoso+0x5a320b
2c Windows_UI!CDispatcher::ProcessInvokeItem+0x2cc
2d Windows_UI!CDispatcher::ProcessMessage+0x347
2e Windows_UI!CDispatcher::WaitAndProcessMessagesInternal+0xc9
2f Windows_UI!CDispatcher::ProcessEvents+0x132
30 Windows_UI_Xaml!DllGetActivationFactory+0xbaee0
31 Windows_UI_Xaml!DllGetActivationFactory+0xbae7f
32 twinapi_appcore!CoreApplicationView::Run+0x3a
33 twinapi_appcore!lambda::operator()+0xf2
34 shcore!_WrapperThreadProc+0xfb
35 ntdll!RtlUserThreadStart+0x2f

从调用栈底部看,我们看到UI线程正在调度一项工作。此工作正在报告应用程序中其他位置的未处理异常,即:应用程序在终止时尝试构建堆栈跟踪,可能是因为它触发了某个系统超时异常。

让我们找出崩溃栈中的异常,看看错误是什么。这个未处理的异常在事件参数中。

⟦ .frame命令用来更改为特定的堆栈帧⟧
0:008> .frame 12
12 0000004b`0a1fe9d0 00007fff`be4fa76b twinapi_appcore!UnhandledErrorInvokeHelper::Invoke+0x1a

⟦dv命令用来dump当前帧的局部变量⟧
0:008> dv
           this = <value unavailable>
         source = <value unavailable>
           args = 0x00000219`7fa93160
⟦??命令打印c++表达式⟧
0:008> ?? args
struct Windows::ApplicationModel::Core::IUnhandledErrorDetectedEventArgs * 0x00000219`7fa93160
   +0x000 __VFN_table : 0x00007fff`be5c1290

这个参数是IUnhandled­Error­Detected­Event­Args,它是Unhandled-Error-Detected-Event-Args运行时类的专用接口。

所以我们可以采取一个简易方式,并假设底层对象是一个Unhandled-Error-Detected-Event-Args对象(如果我们想要花费长时间去验证,可以转储虚表,看看它来自哪个具体类)。

⟦dt命令用来dump类型⟧
0:008> dt twinapi_appcore!Windows::ApplicationModel::Core::UnhandledErrorDetectedEventArgs 0x00000219`7fa93160
   +0x000 __VFN_table : 0x00007fff`be5c1290
   +0x008 __VFN_table : 0x00007fff`be5c1270
   +0x010 __VFN_table : 0x00007fff`be5c1228
   +0x028 marshaller_      : Microsoft::WRL::ComPtr<IMarshal>
   +0x038 refCount_        : Microsoft::WRL::Details::ReferenceCountOrWeakReferencePointer
   +0x040 _error           : Microsoft::WRL::ComPtr<Windows::ApplicationModel::Core::UnhandledError>

我猜_error包含要报告的异常(最好是这样,因为其他成员似乎没有任何有用的东西!)。

⟦? ?命令对于更复杂的dumping非常方便⟧
0:008> ?? ((twinapi_appcore!Windows::ApplicationModel::Core::UnhandledErrorDetectedEventArgs*) 0x00000219`7fa93160)->_error
class Microsoft::WRL::ComPtr<Windows::ApplicationModel::Core::UnhandledError>
   +0x000 ptr_             : 0x00000219`7fa93250 Windows::ApplicationModel::Core::UnhandledError

ComPtr是一个智能指针类,因此我们必须深入到ptr_内部,以获取它所管理的原始指针。不同的智能指针类对内部原始指针有不同的名称。试图记住这些名字是没有意义的,否则会被转储类型签着你的鼻子走。

0:008> ?? ((twinapi_appcore!Windows::ApplicationModel::Core::UnhandledErrorDetectedEventArgs*) 0x00000219`7fa93160)->_error.ptr_
class Windows::ApplicationModel::Core::UnhandledError * 0x00000219`7fa93250
   +0x000 __VFN_table : 0x00007fff`be5c1bf0
   +0x008 __VFN_table : 0x00007fff`be5c1c38
   +0x010 __VFN_table : 0x00007fff`be5c1c58
   +0x028 marshaller_      : Microsoft::WRL::ComPtr<IMarshal>
   +0x038 refCount_        : Microsoft::WRL::Details::ReferenceCountOrWeakReferencePointer
   +0x040 _handled         : 0x1 ''
   +0x048 _restrictedError : Microsoft::WRL::ComPtr<IRestrictedErrorInfo>

0:008> ?? ((twinapi_appcore!Windows::ApplicationModel::Core::UnhandledErrorDetectedEventArgs*) 0x00000219`7fa93160)->_error.ptr_->_restrictedError
class Microsoft::WRL::ComPtr<IRestrictedErrorInfo>
   +0x000 ptr_             : 0x00000219`777a6888 IRestrictedErrorInfo

我们定位到底层的指向COM接口的原始指针。现在还不清楚哪个类实现了这个接口,所以我们必须使用vtable帮忙我们识别它。

⟦the dps command dumps pointer-sized data in the style of a stack⟧
0:008> dps 0x00000219`777a6888 L1
00000219`777a6888  00007fff`c2b89208 combase!CRestrictedError::`vftable'
Okay, 这是一个CRestricted­Error对象. 我们 dump 虚表里的第一个入口看看需要调整多少这个指针才能到达对象的起点

⟦the dpp command dumps pointer-sized data, and then dereferences it as a pointer⟧
0:008> dpp 0x00000219`777a6888 L1
00000219`777a6888  00007fff`c2b89208 00007fff`c2a85090 combase![thunk]:...::QueryInterface`adjustor{8}'

调试告诉我们该对象比起身位置少了8个字节,所以我们需要在将指针显示为CRestricted-Error之前从指针偏移值中减去8。用一个以8结尾的值减去8很容易,只要把8换成0就行了。

0:008> dt combase!CRestrictedError 00000219`777a6880
   +0x000 __VFN_table : 0x00007fff`c2b89230
...
   +0x050 _pszDescription  : 0x00000219`7faaa650  "Class not registered.."
   +0x058 _pszRestrictedDescription : 0x00000219`7fade760  "Class not registered (Excep_FromHResult 0x80040154)"
   +0x068 _hrError         : 80040154
   +0x084 _cStackBackTrace : 0x1e
   +0x088 _ppvStackBackTrace : 0x00000219`7fafc040  -> 0x00007fff`c2a3d884 Void

到目前为止,我们已经了解到原始异常是一个“Class not registered”错误。我不确定,但根据名称,很可能_ppvStackBackTrace指向一个堆栈回溯,而_cStackBackTrace是回溯中的元素数。

0:008> dps 0x00000219`7fafc040 l1e
00000219`7fafc040  combase!RoOriginateLanguageException+0x54
00000219`7fafc048  SharedLibrary!$8_::ExceptionHelpers.OriginateLanguageException+0xf8
00000219`7fafc050  SharedLibrary!$8_ExceptionHelpers.ReportUnhandledError+0x7b
00000219`7fafc058  SharedLibrary!$8_InteropCallbacks.ReportUnhandledError+0x9
00000219`7fafc060  Contoso+0x35d8d
00000219`7fafc068  SharedLibrary!SystemRuntimeExceptionHelpers.ReportUnhandledException+0x63
00000219`7fafc070  SharedLibrary!RuntimeAugments.ReportUnhandledException+0x9
00000219`7fafc078  SharedLibrary!$13_WinRTSynchronizationContext::Invoker.InvokeCore$catch$0+0xa
00000219`7fafc080  mrt100_app!RhpCallCatchFunclet2
00000219`7fafc088  mrt100_app!RhRethrow+0x4c9
00000219`7fafc090  mrt100_app!RhThrowEx+0x4f
00000219`7fafc098  mrt100_app!RhpThrowEx2
00000219`7fafc0a0  SharedLibrary!ExceptionDispatchInfo.Throw+0x22
00000219`7fafc0a8  SharedLibrary!$22_ExceptionDispatchHelper::<>c__DisplayClass0.<ThrowAsync>b__3+0x1f
00000219`7fafc0b0  SharedLibrary!$13_Invoker.InvokeCore+0x4d
00000219`7fafc0b8  SharedLibrary!$13_Invoker.Invoke+0x1c
00000219`7fafc0c0  SharedLibrary!Func$2<__Canon,TimeSpan>.InvokeOpenStaticThunk+0x1a
00000219`7fafc0c8  SharedLibrary!$25_AsyncOperationWithProgressCompletedHandler$2<__Canon,UInt32>.Invoke+0x14
00000219`7fafc0d0  Contoso+0x35d78
00000219`7fafc0d8  Contoso+0x5a320b
00000219`7fafc0e0  Windows_UI!CDispatcher::ProcessInvokeItem+0x2cc
00000219`7fafc0e8  Windows_UI!CDispatcher::ProcessMessage+0x347
00000219`7fafc0f0  Windows_UI!CDispatcher::WaitAndProcessMessagesInternal+0xc9
00000219`7fafc0f8  Windows_UI!CDispatcher::ProcessEvents+0x132
00000219`7fafc100  Windows_UI_Xaml!DllGetActivationFactory+0xbaee0
00000219`7fafc108  Windows_UI_Xaml!DllGetActivationFactory+0xbae7f
00000219`7fafc110  twinapi_appcore!CoreApplicationView::Run+0x3a
00000219`7fafc118  twinapi_appcore!lambda::operator()+0xf2
00000219`7fafc120  shcore!_WrapperThreadProc+0xfb
00000219`7fafc128  ntdll!RtlUserThreadStart+0x2f

OKay,堆栈跟踪不是很有趣,因为它和我们现在所处的堆栈是一样的。我们想要的是生成原始异常的堆栈。

因此,让我们找到排队到这个线程上的工作项,看看是谁在队列中。

此时,我们利用一个时序跟踪的实际情况:向后执行到当前工作项所在队列中的地址。

我们需要回到堆栈跟踪的这一个地址:

2b 0000004b`0a1ff4c0 00007fff`ad9f9140 Contoso+0x5a320b
2c 0000004b`0a1ff510 00007fff`ad9f790b Windows_UI!Windows::UI::Core::CDispatcher::ProcessInvokeItem+0x2cc

我们想要的地址是在堆栈深处的下一个函数返回地址之前的指令。

⟦u命令用来反汇编 ("unassembles")⟧
0:008> u 00007fff`ad9f9140-20 00007fff`ad9f9140
00007fff`ad9f9122 488b01          mov     rax,qword ptr [rcx]
00007fff`ad9f9125 488b4068        mov     rax,qword ptr [rax+68h]
00007fff`ad9f9129 ff15e1a70900    call    qword ptr [Windows_UI!__guard_dispatch_icall_fptr (00007fff`ada93910)]
00007fff`ad9f912f 488b4b10        mov     rcx,qword ptr [rbx+10h]
00007fff`ad9f9133 488b01          mov     rax,qword ptr [rcx]
00007fff`ad9f9136 488b4018        mov     rax,qword ptr [rax+18h]
00007fff`ad9f913a ff15d0a70900    call    qword ptr [Windows_UI!__guard_dispatch_icall_fptr (00007fff`ada93910)]
00007fff`ad9f9140 8be8            mov     ebp,eax

返回地址是00007fff ‘ad9f9140,所以我们想回到它之前的指令,它是00007fff ‘ad9f913a。

⟦g-命令向后执行,直到命中一个断点⟧
0:008> g- 00007fff`ad9f913a
Time Travel Position: 1D6637:68
Windows_UI!Windows::UI::Core::CDispatcher::ProcessInvokeItem+0x2c6:
00007fff`ad9f913a ff15d0a70900    call    qword ptr [Windows_UI!__guard_dispatch_icall_fptr (00007fff`ada93910)]

0:008> dv
                 this = 0x00000219`74134a30
pbInvokeItemProcessed = 0x0000004b`0a1ff691
                   hr = 0x00000000
          pInvokeItem = 0x00000219`7f41a170
             dwStatus = <value unavailable>
     requiredPriority = <value unavailable>
                  msg = {msg=0x270 wp=0x0 lp=0x1}
         bucketAssist = 0x00007fff`7d731330
 spIdleDispatchedArgs = {...}
   WPP_GLOBAL_Control = <value unavailable>
0:008> ?? pInvokeItem
struct Windows::UI::Core::_InvokeEntry * 0x00000219`7f41a170
   +0x000 pNext            : 0x00000219`7f41ab30 Windows::UI::Core::_InvokeEntry
   +0x008 dwTickCount      : 0x115a777
   +0x00c Priority         : 0 ( CoreDispatcherPriority_Normal )
   +0x010 spHandler        : Microsoft::WRL::ComPtr<Windows::UI::Core::IDispatchedHandler>
   +0x018 spIdleHandler    : Microsoft::WRL::ComPtr<Windows::UI::Core::IIdleDispatchedHandler>
   +0x020 spCoreAsyncInfo  : Microsoft::WRL::ComPtr<Windows::UI::Core::ICoreAsyncInfo>

现在,我们已经执行到了将要分派工作项的地方,我们可以查看局部变量来获取项目。(如果这不起作用,我们可以将它从rcx寄存器中取出,因为代码设置为调用。)

pInvokeItem看起来像是已进入队列并即将被分派的工作项。我想回到创建工作项的时候,所以我选择了一个可能只在构建时设置的字段:The priority(优先级)。

0:008> ?? pInvokeItem->Priority
Windows::UI::Core::CoreDispatcherPriority CoreDispatcherPriority_Normal (0n0)
0:008> ?? &pInvokeItem->Priority
Windows::UI::Core::CoreDispatcherPriority * 0x00000219`7f41a17c
0:008> ba w4 0x00000219`7f41a17c

我们向调试器请求Priority成员的地址,并在其上设置一个4字节的写断点。然后再向后执行一些,看看是谁完成的。

0:008> g-
Breakpoint 6 hit
Time Travel Position: 1CE288:87
Windows_UI!Windows::UI::Core::CDispatcher::EnqueueAsyncWork+0x390:
00007fff`ad9f88bc 48ff15eda90900  call    qword ptr [Windows_UI!_imp_GetTickCount (00007fff`ada932b0)]

从函数名Enqueue-Async-Work看来,我们找到了创建工作项的代码。让我们看看为什么要创建它。

⟦k命令用来堆栈跟踪⟧
0:008> k
 # Call Site
00 Windows_UI!Windows::UI::Core::CDispatcher::EnqueueAsyncWork+0x390
01 Windows_UI!Windows::UI::Core::CDispatcher::RunAsyncWorker+0xb0
02 Windows_UI!Windows::UI::Core::CDispatcher::RunAsync+0x58
03 Contoso+0x150d0d
04 Contoso+0x150c07
05 Contoso+0x150b98
06 Contoso+0x150b79
07 Contoso+0x35d46
08 SharedLibrary!$13_System::Threading::WinRTSynchronizationContext.Post+0x68
09 SharedLibrary!$13_System::Runtime::CompilerServices::AsyncMethodBuilderCore.ThrowAsync+0x7f
0a SharedLibrary!$13_System::Runtime::CompilerServices::AsyncVoidMethodBuilder.SetException+0x86
0b Contoso+0x45a79c
0c mrt100_app!RhpCallCatchFunclet2
0d mrt100_app!RhRethrow+0x4c9
0e mrt100_app!RhThrowEx+0x4f
0f mrt100_app!RhpThrowEx2
10 SharedLibrary!$8_Interop::WinRT.RoGetActivationFactory+0x19c
11 SharedLibrary!$8_System::Runtime::InteropServices::FactoryCache.GetActivationFactoryInternal+0x5e
12 SharedLibrary!$8_System::Runtime::InteropServices::FactoryCache.GetActivationFactory+0x126
13 SharedLibrary!$8_System::Runtime::InteropServices::McgMarshal.GetActivationFactory+0x4a
14 SharedLibrary!$8_System::Runtime::InteropServices::McgModuleManager.GetActivationFactory+0x50
15 Contoso+0x3fe578
16 Contoso+0x3fdb71
17 Contoso+0x454fab
18 Contoso+0x454f10
19 Contoso+0x45a446
1a Contoso+0x45a3a7
1b Contoso+0x45a36c
1c Contoso+0x45a351
1d Contoso+0x5a2593
1e Windows_UI_Xaml!GetErrorContextIndex+0x40f58
1f Windows_UI_Xaml!GetErrorContextIndex+0x40cc4
20 Windows_UI_Xaml!DllCanUnloadNow+0x26484
21 Windows_UI_Xaml!DllCanUnloadNow+0x27e91
22 Windows_UI_Xaml!DllCanUnloadNow+0x27a9d
23 Windows_UI_Xaml!DllGetActivationFactory+0x243ea
24 Windows_UI_Xaml!GetErrorContextIndex+0x51cf2
25 Windows_UI_Xaml!GetErrorContextIndex+0x51b1e
26 Windows_UI_Xaml!GetErrorContextIndex+0x519fa
27 minuser!Core::Yield::WndProc+0x6b
28 minuser!Core::Window::DeliverMessage+0x30f
29 minuser!Core::Window::SendCommon+0x82
2a minuser!Core::Window::Send+0x3a
2b minuser!Core::Api::SendMessageAW+0x48
2c minuser!minSendMessageAW+0x5d
2d Windows_UI_Xaml!GetErrorContextIndex+0x24de7
2e Windows_UI_Xaml!GetErrorContextIndex+0x4cc7c
2f Windows_UI_Xaml!GetErrorContextIndex+0x47b05
30 Windows_UI_Xaml!GetErrorContextIndex+0x47a0f
31 Windows_UI_Xaml!GetErrorContextIndex+0x2c29f
32 Windows_UI_Xaml!GetErrorContextIndex+0x9e01d
33 Windows_UI_Xaml!GetErrorContextIndex+0x9deaf
34 Windows_UI_Xaml!GetErrorContextIndex+0x9d7e6
35 Windows_UI_Xaml!DllGetActivationFactory+0x3d26d
36 Windows_UI_Xaml!DllGetActivationFactory+0x3d183
37 Windows_UI_Xaml!GetErrorContextIndex+0x51a89
38 Windows_UI_Xaml!GetErrorContextIndex+0x9dc1b
39 Windows_UI_Xaml!GetErrorContextIndex+0x9db0c
3a CoreMessaging!Microsoft__CoreUI__Dispatch__TimeoutHandler$CallbackThunk+0x11b
3b CoreMessaging!Microsoft::CoreUI::Dispatch::TimeoutHandler::Invoke+0x1a
3c CoreMessaging!Microsoft::CoreUI::Dispatch::TimeoutManager::Callback_OnDispatch+0x18b
3d CoreMessaging!Microsoft::CoreUI::Dispatch::Dispatcher::DispatchNextItem+0x885
3e CoreMessaging!Microsoft::CoreUI::Dispatch::Dispatcher::Callback_DispatchLoop+0x9e3
3f CoreMessaging!Microsoft::CoreUI::Dispatch::EventLoop::Callback_RunCoreLoop+0xc45
40 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapterBase::DrainCoreMessagingQueue+0x14d
41 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter::OnUserDispatch+0x1d7
42 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter::OnUserDispatchRaw+0x9c
43 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter_DoWork+0xe9
44 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter_WindowProc+0xa3
45 minuser!Core::Yield::WndProc+0x6b
46 minuser!Core::Window::DeliverMessage+0x30f
47 minuser!Input::EventMessageDeliveryManager::OnDispatchNotify+0x27
48 minuser!Input::EventMessageDeliveryManager$R::Delegate0+0x2b
49 minuser!Core::OnWindowEventMessage::Invoke+0x65
4a minuser!Input::EventMessageDeliveryManager::CheckAndConsumeMinQEventMessage+0x193
4b minuser!Input::InputQueue::PeekForInput+0x2f0
4c minuser!Core::ThreadInfo::ReadMessageEntryWorker+0x155
4d minuser!Core::ThreadInfo::ReadMessageEntry+0x50
4e minuser!Core::Api::PeekMessageAW+0x88
4f minuser!minPeekMessageAW+0x66
50 Windows_UI!Windows::UI::Core::CDispatcher::ProcessMessage+0xdf
51 Windows_UI!Windows::UI::Core::CDispatcher::WaitAndProcessMessagesInternal+0xc9
52 Windows_UI!Windows::UI::Core::CDispatcher::ProcessEvents+0x132
53 Windows_UI_Xaml!DllGetActivationFactory+0xbaee0
54 Windows_UI_Xaml!DllGetActivationFactory+0xbae7f
55 twinapi_appcore!Windows::ApplicationModel::Core::CoreApplicationView::Run+0x3a
56 twinapi_appcore!<lambda_643db08282a766b00cec20194396f531>::operator()+0xf2
57 shcore!_WrapperThreadProc+0xfb
58 ntdll!RtlUserThreadStart+0x2f

这是一个巨大的堆栈,但它可以整洁地分成三部分。

靠近堆栈顶部的部分是记录Ro-Get-Activation-Factory中失败的运行时。

靠近底部的部分是导致Ro-Get-Activation-Factory失败的代码。看起来是由计时器触发的。

看看我们想激活什么。

0:008> .frame 11
11 0000004b`0a1fd8d0 00007fff`7c209536 SharedLibrary!$8_FactoryCache.GetActivationFactoryInternal+0x5e
0:008> dv
       typeName = 0x00007fff`7ce64060
       typeInfo = 0x0000004b`0a1fdb58
 currentContext = 0x00000219`000109b0
       pFactory = struct System::IntPtr
        itfGuid = struct System::Guid
0:008> ?? typeName
class System::String * 0x00007fff`7ce64060
   +0x000 __VFN_table : 0x00007fff`7c554030
   +0x000 m_pEEType        : System::IntPtr
   +0x008 m_stringLength   : 0n38
   +0x00c m_firstChar      : 0x57 'W'
0:008> du 0x00007fff`7ce64060+c
00007fff`7ce6406c  "Windows.Devices.Portable.Storage"
00007fff`7ce640ac  "Device"

这个程序试图创建一个Windows.Devices.Portable.StorageDevice。我打赌它失败了。

⟦g-命令反向执行前设置参数指定的临时断点⟧
0:008> g- combase!RoGetActivationFactory
Time Travel Position: 1CD829:AE
combase!RoGetActivationFactory:
00007fff`c29ca2f0 4d8bc8          mov     r9,r8
0:008> dv
activatableClassId = 0x0000004b`0a1fd860 "Windows.Devices.Portable.StorageDevice"
               iid = 0x0000004b`0a1fd920 {5ECE44EE-1B23-4DD2-8652-BC164F003128}
           factory = 0x0000004b`0a1fd900

⟦gu命令运行直到函数返回 ("go up")⟧
0:008> gu
Time Travel Position: 1CE201:20
SharedLibrary!$8_Interop::WinRT.RoGetActivationFactory+0x136:
00007fff`7c209836 488b4db0        mov     rcx,qword ptr [rbp-50h] ss:0000004b`0a1fd7f0=000002197408c8e0
0:008> r
rax=0000000080040154 rbx=00007fff7ce64060 rcx=fff83836ff080000
rdx=0000000000000006 rsi=0000004b0a1fd920 rdi=0000004b0a1fd900
rip=00007fff7c209836 rsp=0000004b0a1fd7c0 rbp=0000004b0a1fd840
 r8=0000000000000001  r9=0000000000000001 r10=0000000000005dc0
r11=0000004b0a1fcee0 r12=00000000e409abda r13=000002197fabec70
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
SharedLibrary!$8_Interop::WinRT.RoGetActivationFactory+0x136:
00007fff`7c209836 488b4db0        mov     rcx,qword ptr [rbp-50h] ss:0000004b`0a1fd7f0=000002197408c8e0

函数的返回值被放在rax寄存器中,我们看到它确实是0x80040154,也就是“Class not registered”。

Windows.Devices.Portable。Storage-Device类不是Universal协议的一部分。它位于一个单独的PortableDevice协议中。应用程序在其manifest中将自己标记为Universal,但它使用Universal协议之外的类,而没有首先验证协议是否可用。PortableDevice协议在桌面Windows上是可用的,这就是为什么它们在桌面Windows上运行时没有受到影响。但在Windows 10X上并没有PortableDevice协议,这就是为什么该应用程序会崩溃。

这个程序在Xbox上可能也会崩溃,因为Xbox也不支持PortableDevice合约。

所以这个应用程序在商店里有一个特殊的兼容性标志,上面写着,“是的,当这个应用程序说它是通用的,他们在撒谎。不要在非桌面系统上安装它。”

额外的闲聊:既然我们知道了答案是什么,我们回过头来就会意识到我们本可以更快地找到答案。错误消息“Class not registered”表示对Co-Create-Instance、Co-Get-Class-Factory、Ro-Activate-Instance或Co-Get-Activation-Factory的调用失败。我们可以让时序堆栈跟踪对象模型找到所有返回错误代码的调用:

0:008> dx -r2 @$currsession.TTD.Calls("combase!CoCreateInstance", "combase!CoGetClassFactory", "combase!RoActivateInstance", "combase!RoGetActivationFactory").Where(c => c.ReturnValue == (HRESULT)0x80040154)

    [0x97]
        EventType        : 0x0
        ThreadId         : 0x2fc0
        UniqueThreadId   : 0xd
        TimeStart        : 1CD829:AE
        TimeEnd          : 1CE201:20
        Function         : combase!RoGetActivationFactory
        FunctionAddress  : 0x7fffc29ca2f0
        ReturnAddress    : 0x7fff7c209836
        ReturnValue      : 0x80040154 (Class not registered) [Type: HRESULT]
        Parameters
Boom, there’s the failure, at call number 0x97.
Boom,出现了故障,调用地址是0x97。

现在,我们可以使用!tt命令跳转到该时间码,以查看对象是什么。

0:008> !tt 1CD829:AE
Setting position: 1CD829:AE
(4420.3130): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: 1CD829:AE
combase!RoGetActivationFactory:
00007fff`c29ca2f0 4d8bc8          mov     r9,r8