之前的一篇筆記<Windows Dump文件分析>介紹了應用dump的生成方式和調試手法,有稍微提一句藍屏dump。但是實際在分析藍屏的dump文件的時候和普通應用的dump文件分析還是有一些差異的。這篇筆記就記錄下藍屏dump的一些相關知識。
藍屏dump文件的配置
首先我們可以在"右鍵選中我的電腦->屬性->高級系統設置->高級->啟動和故障恢復設置->系統失敗"里面設置藍屏時候生成dump文件的一些邏輯:
- 將事件寫入系統日志 : 顧名思義
- 自動重新啟動 : 藍屏dump保存完成之后釋放需要自動重啟,有時候可能需要保持藍屏的狀態usb線連接外部筆記本電腦,在外部筆記本使用WinDbg的Attach to kernal功能調試系統可以去掉這個勾選
- 寫入調試信息 : 用于選擇生成藍屏dump的內容,例如截圖選擇的“完成內存轉儲”我們也叫full dump,即將整個物理內存dump下來保存成dump文件(可以參考官方文檔的詳細介紹)
- 轉存文件 : 生成dump文件的路徑,例如截圖的“%SystemRoot%\MEMORY.DMP”指的就是“C:\Windows\MEMORY.DMP”
- 覆蓋任何現有文件 : 如果有新的dump生成會覆蓋現有的dump文件
- 禁止在磁盤空間不足時自動刪除內存轉儲 : 當磁盤空間不足的時候默認會刪除dump文件,可以勾選禁止自動刪除防止dump文件丟失
藍屏一般是驅動之類的底層代碼出現異常導致的,在學習調試的時候可以使用NotMyFault去主動觸發藍屏獲取dump文件。
內核模式
拿到之后可以同樣用WinDbg打開它,打開full dump可以看到命令行提示為kd
,意味著進入的是kernel debug內核模式:
在這個模式下有些命令是不能使用的例如我們在調試應用crash的時候常用的~就只能在用戶模式下使用:
如果在kd下輸入就會報錯:
5: kd> ~
^ Syntax error in '~'
不過依然可以直接!analyze -v
分析:
DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
...
myfault+0x1560:
fffff804`a0a41560 8b03 mov eax,dword ptr [rbx] ds:00000000`00000000=????????
Resetting default scope
STACK_TEXT:
ffff860c`933aec48 fffff802`3bc11aa9 : 00000000`0000000a ffff8904`92e54560 00000000`00000002 00000000`00000000 : nt!KeBugCheckEx
ffff860c`933aec50 fffff802`3bc0d563 : 00000000`00000000 00000000`00000000 00000000`00000f4d 00000000`00000000 : nt!KiBugCheckDispatch+0x69
ffff860c`933aed90 fffff804`a0a41560 : ffff9b0e`6e6470c0 fffff802`3ba4085f ffff9b0e`6e6470c0 ffff9b0e`61c0ee78 : nt!KiPageFault+0x463
ffff860c`933aef20 fffff804`a0a4191e : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : myfault+0x1560
ffff860c`933aef50 fffff804`a0a41a81 : 00000000`00000002 00000000`00000000 ffff9b0e`00000001 fffff802`3be462c1 : myfault+0x191e
ffff860c`933af090 fffff802`3ba35cf5 : 00000000`00000002 00000000`00000000 ffff860c`933af480 00000000`00000000 : myfault+0x1a81
ffff860c`933af0f0 fffff802`3be452ac : 00000000`00000001 00000000`83360018 ffff9b0e`728045d0 fffff802`00000000 : nt!IofCallDriver+0x55
ffff860c`933af130 fffff802`3be44f03 : ffff9b0e`00000000 ffff860c`933af480 00000000`00010000 00000000`83360018 : nt!IopSynchronousServiceTail+0x34c
ffff860c`933af1d0 fffff802`3be441d6 : ffff9b0e`70743ab0 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0xd13
ffff860c`933af320 fffff802`3bc11235 : ffff9b0e`6e6470c0 ffff860c`933af480 000000a3`a65eead8 ffff860c`933af3a8 : nt!NtDeviceIoControlFile+0x56
ffff860c`933af390 00007ffd`3b06d0c4 : 00007ffd`386e591b 00000000`00010000 00007ff7`83a24e88 000000a3`a65ef77c : nt!KiSystemServiceCopyEnd+0x25
000000a3`a65ef618 00007ffd`386e591b : 00000000`00010000 00007ff7`83a24e88 000000a3`a65ef77c 000000a3`a65ef6a0 : ntdll!NtDeviceIoControlFile+0x14
000000a3`a65ef620 00007ffd`3a6d5921 : 00000000`83360018 00007ff7`83a96500 00000000`00000001 00007ff7`839dae99 : KERNELBASE!DeviceIoControl+0x6b
000000a3`a65ef690 00007ff7`839db437 : 0000028a`91751d30 0000028a`91751d85 000000a3`a65ef830 00000000`00000003 : KERNEL32!DeviceIoControlImplementation+0x81
000000a3`a65ef6e0 00007ff7`839dd162 : 0000028a`91751d30 000000a3`a65ef830 00000000`00000001 0000028a`91730000 : notmyfaultc64+0xb437
000000a3`a65ef730 00007ff7`839dd8e8 : 00000000`00000000 00007ff7`839dda71 0000028a`91751d30 00000000`00000000 : notmyfaultc64+0xd162
000000a3`a65ef900 00007ffd`3a6d7344 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : notmyfaultc64+0xd8e8
000000a3`a65ef940 00007ffd`3b0226b1 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
000000a3`a65ef970 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
可以看到藍屏的代碼是DRIVER_IRQL_NOT_LESS_OR_EQUAL
,然后從后面的匯編可以看出來是讀取0地址的內存到eax寄存器:
fffff804
a0a41560 8b03 mov eax,dword ptr [rbx] ds:00000000
00000000=????????
然后就是調用堆棧,可以看到的確是從notmyfault觸發的。
分析其他進程
有時候驅動的異常是應用層調用接口的流程異常導致的,默認情況下內存上下文是觸發藍屏的進程,我們也可以轉存full dump之后選擇其他的進程進行分析。
在內核模式下我們可以用!process 0 0這個命令列出所有的進程,如果你知道程序的exe名字也可以用在后面加上直接列舉,例如!process 0 0 Demo.exe
:
4: kd> !process 0 0 Demo.exe
PROCESS ffff9b0e6ef94080
SessionId: 1 Cid: 0b48 Peb: 74b5500000 ParentCid: 247c
DirBase: 23b8b1000 ObjectTable: ffff890492285600 HandleCount: 45.
Image: Demo.exe
這個命令里面的第二個0是Flags用于指定需要展示進程的什么信息,0的話就是摘要。如果是!process 0 0xf Demo.exe
就會列舉出所有的Demo.exe的所有信息,例如全部的線程堆棧等,但是由于默認不會加載其他進程的pdb所以很多的符號看不到。
但可以從上面的信息看到進程的id是ffffe78e91d032c0,所以我們可以用.process /p /r ffff9b0e6ef94080指定用于進程上下文的進程并加載pdb。
然后用lm命令就可以看到Demo.exe已經加載了,然后括號里的deferred代表后面有需要的時候就會去加載這個模塊的pdb:
4: kd> lm
start end module name
00007ff7`000b0000 00007ff7`000c0000 Demo (deferred)
...
之后再用!process 0 0xf Demo.exe
就能看到具體的線程堆棧了:
4: kd> !process 0 0xf Demo.exe
PROCESS ffff9b0e6ef94080
SessionId: 1 Cid: 0b48 Peb: 74b5500000 ParentCid: 247c
DirBase: 23b8b1000 ObjectTable: ffff890492285600 HandleCount: 45.
Image: Demo.exe
VadRoot ffff9b0e720c7870 Vads 31 Clone 0 Private 143. Modified 0. Locked 0.
DeviceMap ffff89047ff7a660
Token ffff890492286060
ElapsedTime 00:00:19.086
UserTime 00:00:00.000
KernelTime 00:00:00.000
QuotaPoolUsage[PagedPool] 25456
QuotaPoolUsage[NonPagedPool] 4480
Working Set Sizes (now,min,max) (953, 50, 345) (3812KB, 200KB, 1380KB)
PeakWorkingSetSize 916
VirtualSize 4145 Mb
PeakVirtualSize 4145 Mb
PageFaultCount 986
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 164
Job ffff9b0e6fb0e060
THREAD ffff9b0e70a9f2c0 Cid 0b48.2bac Teb: 00000074b5501000 Win32Thread: 0000000000000000 WAIT: (DelayExecution) UserMode Non-Alertable
ffffffffffffffff NotificationEvent
Not impersonating
DeviceMap ffff89047ff7a660
Owning Process ffff9b0e6ef94080 Image: Demo.exe
Attached Process N/A Image: N/A
Wait Start TickCount 194267 Ticks: 56 (0:00:00:00.875)
Context Switch Count 107 IdealProcessor: 2
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address Demo!ILT+340(mainCRTStartup) (0x00007ff7000b1159)
Stack Init ffff860c91fb7590 Current ffff860c91fb7070
Base ffff860c91fb8000 Limit ffff860c91fb1000 Call 0000000000000000
Priority 8 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
Child-SP RetAddr Call Site
ffff860c`91fb70b0 fffff802`3ba41330 nt!KiSwapContext+0x76
ffff860c`91fb71f0 fffff802`3ba4085f nt!KiSwapThread+0x500
ffff860c`91fb72a0 fffff802`3bacc132 nt!KiCommitThreadWait+0x14f
ffff860c`91fb7340 fffff802`3be8be2f nt!KeDelayExecutionThread+0x122
ffff860c`91fb73d0 fffff802`3bc11235 nt!NtDelayExecution+0x5f
ffff860c`91fb7400 00007ffd`3b06d664 nt!KiSystemServiceCopyEnd+0x25 (TrapFrame @ ffff860c`91fb7400)
00000074`b56ff858 00007ffd`386fb62e ntdll!NtDelayExecution+0x14
00000074`b56ff860 00007ffd`08d1285c KERNELBASE!SleepEx+0x9e
00000074`b56ff900 00007ff7`000b16b3 MSVCP140!_Thrd_sleep+0x3c [d:\agent\_work\3\s\src\vctools\crt\crtw32\stdcpp\thr\cthread.cpp @ 70]
00000074`b56ff950 00007ff7`000b1619 Demo!std::this_thread::sleep_until<std::chrono::steady_clock,std::chrono::duration<__int64,std::ratio<1,1000000000> > >+0x83 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.36.32532\include\thread @ 199]
00000074`b56ff9a0 00007ff7`000b1736 Demo!std::this_thread::sleep_for<__int64,std::ratio<1,1> >+0x19 [C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.36.32532\include\thread @ 205]
00000074`b56ff9d0 00007ff7`000b175f Demo!foo+0x36 [C:\Users\user\workspace\CppAutoRegisterDemo\main.cpp @ 8]
00000074`b56ffa00 00007ff7`000b1ab8 Demo!main+0xf [C:\Users\user\workspace\CppAutoRegisterDemo\main.cpp @ 12]
(Inline Function) --------`-------- Demo!invoke_main+0x22 (Inline Function @ 00007ff7`000b1ab8) [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
00000074`b56ffa30 00007ffd`3a6d7344 Demo!__scrt_common_main_seh+0x10c [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
00000074`b56ffa70 00007ffd`3b0226b1 KERNEL32!BaseThreadInitThunk+0x14
00000074`b56ffaa0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
...
此時再用lm
命令也可以看到pdb的確被加載了:
4: kd> lm
start end module name
00007ff7`000b0000 00007ff7`000c0000 Demo C (private pdb symbols) d:\symbols\Demo.pdb
...
然后可以用.thread ffff9b0e70a9f2c0
設置線程上下文就能在堆棧窗口看到對應的線程堆棧了。接著就能雙擊對應的棧幀打開代碼窗口:
有時候還能在Locals窗口看到傳入函數的參數的值,雖然這個dump沒有抓到param的值。
dump對虛擬內存大小的要求
在具體分析dump的時候發現有時候就算加載了正確的pdb文件也會有看到一些符號無法解析,我猜測是因為抓出來的dump只包含物理內存的信息,而這部分符號的地址在虛擬內存上。
本來想關掉虛擬內存再抓取確認的。但是發現藍屏dump的抓取對虛擬內存大小是有要求的,可以在官方文檔上看到。例如full dump的要求如下:
物理內存 | 虛擬內存要求 |
---|---|
256 MB–1,373 MB | 物理內存大小的 1.5 倍 |
1,374 MB 或更大 | 32 位系統:2 GB 加 16 MB 64 位系統:物理內存的大小加上 128 MB |
所以有時候藍屏dump抓不出來也可以看看是不是虛擬內存設置的小了或者直接被關掉了。