对于上架的windows store应用程序,微软提供了反馈机制让用户在程序崩溃的时候可以自动上传迷你转储(mini dump)文件。这些dump文件对于分析用户遇到的问题是相当有帮助的,这里我就如何使用windbg分析转储文件来做一个示范,我的目标是让即使是没用过windbg的人也能够使用它来分析转储文件,所以如果你发现文章中哪里有让你迷惑的地方,请告诉我。:)
首先,你需要有windows debugger tools,也就是windbg。这是一个强大的系统级调试工具,它附属于Windows WDK和Windows SDK安装包中,WDK这玩意儿太小众了,我们这边暂且不提,所以安装windbg的最佳办法就是安装Windows SDK了,Windows SDK很大,你在安装的时候你可以选择只安装Debugging Tools for Windows。Windows SDK for Windows 8.1的下载地址如下:
http://msdn.microsoft.com/en-US/windows/desktop/bg162891
��安装完windbg以后,我们就可以来分析转储文件了。但是且慢,转储文件呢,转储文件到哪里去找? 如果你找不到的话,那么请随我来。当你的windows store应用上架以后,你就可以看到一个“报告”按钮。
点击这个“报告”按钮,你就可以看到应用程序的统计数据以及左边菜单栏中的“质量”链接:
点击“质量”链接你会看到程序关于质量方面统计数据,这里直接忽略前面的三项看第四项“最常见的崩溃情况”,这里就提供了客户上传的转储文件。
选取你想要看的转储文件,然后下载,下载的文件是一个cab格式的压缩包,将它解压缩就得到了dmp后缀的转储文件了。我们会在这里演示两个转储文件的分析。
用windbg打开第一个转储文件,windbg会在命令窗口中显示报错的代码,这通常是在系统的组件中,对我们查抄问题并没有帮助。比如以下就是载入转储文件后显示的内容:
Windows 8.1 Version 9600 MP (4 procs) Free x86 compatible
Product: WinNt, suite: SingleUserTS Personal
Built by: 6.3.9600.16384 (winblue_rtm.130821-1623)
Machine Name:
Debug session time: Tue Jan 28 21:19:13.000 2014 (UTC + 8:00)
System Uptime: 0 days 13:25:15.561
Process Uptime: 0 days 0:06:15.000
................................................................
................................................................
.............................................
Loading unloaded module list
........
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(2054.2efc): Unknown exception - code c000027b (first/second chance not available)
eax=aaaaaaaa ebx=00000000 ecx=00000000 edx=00000000 esi=050eeea0 edi=aaaaaaaa
eip=75789bcf esp=050eee5c ebp=050eeef8 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
combase!RoFailFastWithErrorContextInternal+0x10a:
75789bcf 6a03 push 3
为了帮助我们查看转储文件中的内容,首先我们需要告诉windbg到哪里去找符号(symbol)文件,你可以在windbg的命令栏中依次键入以下命令:
0:004> .sympath+ srv*http://msdl.microsoft.com/download/symbols
Symbol search path is: srv*http://msdl.microsoft.com/download/symbols
Expanded Symbol search path is: srv*http://msdl.microsoft.com/download/symbols
************* Symbol Path validation summary **************
Response Time (ms) Location
Deferred srv*http://msdl.microsoft.com/download/symbols
0:004> .reload
................................................................
................................................................
.............................................
Loading unloaded module list
........
通常你也需要用以上命令加入你的应用程序的符号文件路径,符号文件的后缀名为pdb,通常由编译器在生成最后的二进制文件时一起生成,所以这里你可以将路径设为Visual Studio输出可执行文件的目录。注意这里的pdb文件必须和你要查看的转储文件使用的可执行文件版本完全一致,所以为了方便起见,你最好妥善保留每一个需要维护的版本的pdb文件。
首先我们用k命令来看一看出问题时的调用堆栈(call stack):
0:004> k
ChildEBP RetAddr
050eeef8 5fa44fec combase!RoFailFastWithErrorContextInternal+0x10a
050eef20 5fa44c62 Windows_UI_Xaml!DirectUI::ErrorHelper::ProcessUnhandledError+0xb8
050eef84 5fa44af6 Windows_UI_Xaml!DirectUI::FinalUnhandledErrorDetectedRegistration::OnFinalUnhandledErrorDetected+0xad
050eef98 6ab37e13 Windows_UI_Xaml!Microsoft::WRL::Details::InvokeHelper<Microsoft::WRL::Implements<Microsoft::WRL::RuntimeClassFlags<2>,Windows::Foundation::IEventHandler<Windows::ApplicationModel::Core::UnhandledErrorDetectedEventArgs *>,Microsoft::WRL::FtmBase,Microsoft::WRL::Details::Nil,Microsoft::WRL::Details::Nil,Microsoft::WRL::Details::Nil,Microsoft::WRL::Details::Nil,Microsoft::WRL::Details::Nil,Microsoft::WRL::Details::Nil,Microsoft::WRL::Details::Nil>,long (__stdcall*)(IInspectable *,Windows::ApplicationModel::Core::IUnhandledErrorDetectedEventArgs *),2>::Invoke+0x12
050eefb4 6ab37e44 twinapi_appcore!Windows::Internal::Details::GitInvokeHelper<Windows::Foundation::IEventHandler<Windows::ApplicationModel::Core::UnhandledErrorDetectedEventArgs *>,Windows::Internal::GitPtr,2>::Invoke+0x3c
050eefcc 6ab30d3e twinapi_appcore!Windows::ApplicationModel::Core::UnhandledErrorInvokeHelper::Invoke+0x19
…
从调用栈中可以看出,这时候系统已经在处理产生的异常(exception),这完全不是我们所想要的,我们想要看到的是这个异常是什么以及是谁报出来的。请不要着急,我们可以先看一下最后一个异常的详细信息,这通常就是我们想要找的异常:
0:004> .exr -1
ExceptionAddress: 5fa44fec (Windows_UI_Xaml!DirectUI::ErrorHelper::ProcessUnhandledError+0x000000b8)
ExceptionCode: c000027b
ExceptionFlags: 00000001
NumberParameters: 2
Parameter[0]: 1089daa0
Parameter[1]: 00000001
这里给出了异常的错误代码,快看一下这是什么错误:
0:004> !error c000027b
Error code: (NTSTATUS) 0xc000027b (3221226107) - An application-internal exception has occurred.
天阿,应用程序内部异常,这等于还是什么都不知道么。别急,看到第一个参数了没有,这个参数里面包含了进一步的信息,让我们一步一步来把其中的秘密给找出来。
第一个参数0x1089daa0实质上是一个内部结构的指针,这个内部结构通常有两个版本,首先我们需要确定这里用的是哪一个版本,我们用以下命令得到结构头部分的输出然后进行格式化:
0:004> dt 1089daa0 combase!_STOWED_EXCEPTION_INFORMATION_HEADER*
0x171e0f74
+0x000 Size : 0x20
+0x004 Signature : 0x53453031
0:004> .formats 0x53453031
Evaluate expression:
Hex: 53453031
Decimal: 1397043249
Octal: 12321230061
Binary: 01010011 01000101 00110000 00110001
Chars: SE01
Time: Wed Apr 09 19:34:09 2014
Float: low 8.46917e+011 high 0
Double: 6.90231e-315
这里可以看出这是一个SE01版本的结构,所以我们可以重新解析地址0x1089daa0:
0:004> dt 1089daa0 combase!_STOWED_EXCEPTION_INFORMATION_V1*
0x171e0f74
+0x000 Header : _STOWED_EXCEPTION_INFORMATION_HEADER
+0x008 ResultCode : 80070057
+0x00c ExceptionForm : 0y01
+0x00c ThreadId : 0y000000000000000000101110111111 (0xbbf)
+0x010 ExceptionAddress : (null)
+0x014 StackTraceWordSize : 4
+0x018 StackTraceWords : 0x30
+0x01c StackTrace : 0x171dff6c Void
+0x010 ErrorText : (null)
当然如果输出的结果是SE02的话,那么就需要使用_STOWED_EXCEPTION_INFORMATION_V2这个数据结构了。这里我们终于看到了真正的错误代码80070057,这是一个参数错误:
0:004> !error 80070057
Error code: (HRESULT) 0x80070057 (2147942487) - The parameter is incorrect.
那么它是在什么地方发生的呢,_STOWED_EXCEPTION_INFORMATION_V1结构也给出了调用栈的指针0x171dff6c,让我们看看调用栈是什么样的:
0:004> dps 0x171dff6c L30
171dff6c 5f8a8b50 Windows_UI_Xaml!DirectUI::Selector::OnSelectedIndexChanged+0x3f8b1c
171dff70 5f4b0020 Windows_UI_Xaml!DirectUI::Selector::OnPropertyChanged+0x7d
171dff74 5fa80fc7 Windows_UI_Xaml!DirectUI::FlipView::OnPropertyChanged+0x59
171dff78 5f73641f Windows_UI_Xaml!DirectUI::DependencyObject::UpdateEffectiveValue+0x122
171dff7c 5f7361c7 Windows_UI_Xaml!DirectUI::DependencyObject::SetValueInternal+0x4f5
171dff80 5f4af548 Windows_UI_Xaml!DirectUI::SelectorGenerated::put_SelectedIndex+0x6d
171dff84 5fa81fa8 Windows_UI_Xaml!DirectUI::FlipView::UpdateSelectedIndex+0x13
171dff88 5fa7f9c5 Windows_UI_Xaml!DirectUI::FlipView::MovePrevious+0x53
171dff8c 5fa80e92 Windows_UI_Xaml!DirectUI::FlipView::OnPointerWheelChanged+0x211
171dff90 5fbda3ea Windows_UI_Xaml!DirectUI::ControlGenerated::OnPointerWheelChangedProtected+0x59
171dff94 5f860630 Windows_UI_Xaml!DirectUI::Control::FireEvent+0x4c1b36
171dff98 5f39ea11 Windows_UI_Xaml!DirectUI::DXamlCore::FireEvent+0x200
171dff9c 5f39db3d Windows_UI_Xaml!AgCoreCallbacks::FireEvent+0x40
171dffa0 5f39da7b Windows_UI_Xaml!CCoreServices::CLR_FireEvent+0x9b
171dffa4 5f39d9c1 Windows_UI_Xaml!CommonBrowserHost::CLR_FireEvent+0x20
171dffa8 5f39d2f3 Windows_UI_Xaml!CControlBase::ScriptCallback+0x9a
…
通过观察这个调用栈关系,结果就很明显了,这个问题发生在FlipView的SelectedIndex属性发生变化的时候,错误原因是参数不对,也就是说赋给SelectedIndex的值有问题,我们需要找到给SelectedIndex赋值的地方,看看是不是有不合适的数据赋给了它。
接下来让我们看一看另一个转储文件,同样,在windbg中加载转储文件,设置symbol路径以后,先用k命令看一下当前的调用栈:
0:004> k
ChildEBP RetAddr
051af5fc 7696a5bd combase!RoFailFastWithErrorContextInternal+0x10a
051af610 60ee5863 combase!RoFailFastWithErrorContext+0x11
051af630 60ee58b3 twinapi_appcore!Windows::ApplicationModel::Core::CoreApplication::ForwardLocalError+0x77
051af648 7696a1e4 twinapi_appcore!Windows::ApplicationModel::Core::CoreApplicationFactory::ForwardLocalError+0x30
051af690 7696a6ac combase!CallErrorForwarder+0x9d
051af69c 723c2d42 combase!RoReportUnhandledError+0x18
051af6fc 7245ffc1 mscorlib_ni+0x9b2d42
051af71c 7245fd35 mscorlib_ni+0xa4ffc1
051af74c 5b09f9df mscorlib_ni+0xa4fd35
051af83c 5b09f965 System_Runtime_WindowsRuntime_ni+0x1f9df
051af8a4 71d33156 System_Runtime_WindowsRuntime_ni+0x1f965
051af8b8 5b09f934 mscorlib_ni+0x323156
051af8d4 5b1aff16 System_Runtime_WindowsRuntime_ni+0x1f934
051af8f8 72b92a36 Windows_UI_ni+0x9ff16
051af978 5d783fb5 clr!COMToCLRDispatchHelper+0x28
051af9f8 5d7837e7 Windows_UI!Windows::UI::Core::CDispatcher::ProcessInvokeItem+0x4d0
(Inline) -------- Windows_UI!Windows::UI::Core::CDispatcher::ProcessMessage+0xffffff8a
051afa94 5d7816dc Windows_UI!Windows::UI::Core::CDispatcher::WaitAndProcessMessages+0x271
051afaf4 5c949931 Windows_UI!Windows::UI::Core::CDispatcher::ProcessEvents+0x60
051afb3c 60ebf45e Windows_UI_Xaml!DirectUI::FrameworkView::Run+0x5e
051afb48 60ebf322
…
显然,这也不是我们想要看的调用栈,那么像第一个例子一样,看看最近的那个异常:
0:004> .exr -1
ExceptionAddress: 7696a5bd (combase!RoFailFastWithErrorContext+0x00000011)
ExceptionCode: c000027b
ExceptionFlags: 00000001
NumberParameters: 2
Parameter[0]: 051af574
Parameter[1]: 00000001
错误码仍然是c000027b。这次我们不怕了,因为我们从前面的例子中学了一招,按照前面的办法重新做一遍:
0:004> dt 051af574 combase!_STOWED_EXCEPTION_INFORMATION_HEADER*
0x0d331aac
+0x000 Size : 0x20
+0x004 Signature : 0x53453031
0:004> .formats 0x53453031
Evaluate expression:
Hex: 53453031
Decimal: 1397043249
Octal: 12321230061
Binary: 01010011 01000101 00110000 00110001
Chars: SE01
Time: Wed Apr 09 19:34:09 2014
Float: low 8.46917e+011 high 0
Double: 6.90231e-315
0:004> dt 051af574 combase!_STOWED_EXCEPTION_INFORMATION_V1*
0x0d331aac
+0x000 Header : _STOWED_EXCEPTION_INFORMATION_HEADER
+0x008 ResultCode : 80070102
+0x00c ExceptionForm : 0y01
+0x00c ThreadId : 0y000000000000000000001101000110 (0x346)
+0x010 ExceptionAddress : 0x768ea9d7 Void
+0x014 StackTraceWordSize : 4
+0x018 StackTraceWords : 0xa
+0x01c StackTrace : 0x076dc300 Void
+0x010 ErrorText : 0x768ea9d7 "趍ﯰ???"
0:004> !error 80070102
Error code: (HRESULT) 0x80070102 (2147942658) - The wait operation timed out.
0:004> dps 0x076dc300 La
076dc300 768ff132 combase!RoOriginateLanguageException+0x3b
076dc304 723c2c6a mscorlib_ni+0x9b2c6a
076dc308 7245ff62 mscorlib_ni+0xa4ff62
076dc30c 7245fd21 mscorlib_ni+0xa4fd21
076dc310 5b09f9df System_Runtime_WindowsRuntime_ni+0x1f9df
076dc314 5b09f965 System_Runtime_WindowsRuntime_ni+0x1f965
076dc318 71d33156 mscorlib_ni+0x323156
076dc31c 5b09f934 System_Runtime_WindowsRuntime_ni+0x1f934
076dc320 5b1aff16 Windows_UI_ni+0x9ff16
076dc324 72b92a36 clr!COMToCLRDispatchHelper+0x28
这回错误代码找到了,是0x80070102,但是调用栈中显示的怎么仍然是一个无意义的RoOriginateLanguageException呢?别急,这说明这个异常是在.net代码中产生的,我们用调试.net代码的插件sos来看一看,首先,在windbg中加载sos插件:
0:004> .loadby sos clr
接下来就可以使用sos的命令PrintException (pe)来查看.net代码报出来的异常了:
0:004> !sos.pe
Exception object: 02807538
Exception type: System.Exception
Message: <Invalid Object>
InnerException: <none>
StackTrace (generated):
SP IP Function
051AF710 71D9D17A mscorlib_ni!System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)+0x5e
051AF720 71D9D115 mscorlib_ni!System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)+0x35
051AF72C 04E9DA2D OurCalendar!OurCalendar.ViewModel.LoginViewModel+<Login>d__7.MoveNext()+0x29d
051AF814 7251458F mscorlib_ni!System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__4(System.Object)+0x33
051AF81C 5B09F994 System_Runtime_WindowsRuntime_ni!System.Threading.WinRTSynchronizationContext+Invoker.InvokeCore()+0x24
StackTraceString: <none>
HResult: 80070102
接下来的情况就和明显了,这是在应用程序的代码中报的错,当然,我们的代码实际上是没有MoveNext这个函数的,这个函数是编译器在编译异函数Login的异步处理的时候自动生成的,所以下一步我们就应该去查看OurCalendar.ViewModel.LoginViewModel.Login函数中为什么会报这个异常了。
以上就是我们在分析用户报告的Windows Store应用崩溃时常用的办法。这里我用两个实际的例子做了一个演示,分别针对Winrt的本地代码中报出的异常和.NET代码中报出的异常,希望对大家提高Window Store应用的稳定性有所帮助。