难译 | windbg 乐趣之道(下)
前言
Yarden Shafir 分享了兩篇非常通俗易懂的,關于 windbg 新引入的調試數據模型的文章。原文鏈接如下:
part1:https://medium.com/@yardenshafir2/windbg-the-fun-way-part-1-2e4978791f9b
part2:https://medium.com/@yardenshafir2/windbg-the-fun-way-part-2-7a904cba5435
本文是第二部分的譯文。同樣在有道詞典、必應詞典、谷歌翻譯的大力幫助下完成,感謝以上翻譯工具,我只是一個搬運工。強烈建議英文好的朋友閱讀原文,因為在翻譯的過程中不可避免的按我的理解做了調整。
第一部分譯文在這里。
以下是譯文!
歡迎來到我試圖讓您享受在 Windows 上調試的第 2 部分(哇,我真是個書呆子)!
在第一部分中,我們已經了解了新調試數據模型的基本知識——使用新對象、使用自定義寄存器、搜索和過濾輸出、聲明匿名類型以及解析列表和數組。在這一部分中,我們將學習如何在 dx 中使用傳統命令(譯注: 原文是 legacy commands,我理解是在引入 dx 之前就存在的一些命令,比如 dt、u 等),了解令人驚嘆的新反匯編器,創建合成方法和類型,查看斷點的奇妙變化,并在調試器中使用文件系統。
聽起來有很多內容。因為確實有很多內容。我們開始吧!
傳統命令(Legacy Commands)
新數據模型完全改變了調試體驗。但有時確實需要使用我們已經習慣的舊命令或擴展(這些功能在 dx 中沒有對應的實現)。
我們仍然可以通過 Debugger.Utility.Control.ExecuteCommand 在 dx 中使用這些舊命令,它讓我們可以將傳統命令作為 dx 查詢的一部分運行。例如,我們可以使用傳統的 u 命令查看第二個棧幀中 RIP 所指向的地址對應的反匯編。
由于 dx 輸出默認是十進制的,而傳統命令只接受十六進制輸入,我們首先需要使用 ToDisplayString("x") 將其轉換為十六進制:
dx?Debugger.Utility.Control.ExecuteCommand("u?"?+?@$curstack.Frames[1].Attributes.InstructionOffset.ToDisplayString("x"))Debugger.Utility.Control.ExecuteCommand("u?"?+?@$curstack.Frames[1].Attributes.InstructionOffset.ToDisplayString("x"))????????????????[0x0]?????:?kdnic!TXTransmitQueuedSends+0x125:[0x1]?????:?fffff807`52ad2b61?4883c430????????add?????rsp,30h[0x2]?????:?fffff807`52ad2b65?5b??????????????pop?????rbx[0x3]?????:?fffff807`52ad2b66?c3??????????????ret[0x4]?????:?fffff807`52ad2b67?4c8d4370????????lea?????r8,[rbx+70h][0x5]?????:?fffff807`52ad2b6b?488bd7??????????mov?????rdx,rdi[0x6]?????:?fffff807`52ad2b6e?488d4b60????????lea?????rcx,[rbx+60h][0x7]?????:?fffff807`52ad2b72?4c8b15d7350000??mov?????r10,qword?ptr?[kdnic!_imp_ExInterlockedInsertTailList?(fffff807`52ad6150)][0x8]?????:?fffff807`52ad2b79?e8123af8fb??????call????nt!ExInterlockedInsertTailList?(fffff807`4ea56590)另一個有用的傳統命令是 !irp。這個命令為我們提供了大量關于 IRP 的信息,所以沒必要大費周章的使用 dx 重新實現它。
我們將嘗試對 lsass.exe 進程中所有的 IRP 執行 !irp 命令。讓我們看看整個過程:
首先,我們需要找到 lsass.exe 進程的容器。我們已經知道如何使用 Where() 來做到這一點。我們選擇返回的第一個進程。通常應該只有一個 lsass,除非機器上有服務隔離。
譯注: 服務隔離對應的原文是 server silos,在網上找到一個解釋 :A silo in IT is an isolated point in a system where data is kept and segregated from other parts of the architecture.
dx?@$lsass?=?@$cursession.Processes.Where(p?=>?p.Name?==?"lsass.exe").First()然后,我們需要遍歷進程中每個線程的 IrpList 并獲得 IRP 本身。我們可以很容易地通過前面提到的 FromListEntry() 做到這一點。我們只選取包含 IRP 的線程:
dx?-r4?@$irpThreads?=?@$lsass.Threads.Select(t?=>?new?{irp?=?Debugger.Utility.Collections.FromListEntry(t.KernelObject.IrpList,?"nt!_IRP",?"ThreadListEntry")}).Where(t?=>?t.irp.Count()?!=?0)@$irpThreads?=?@$lsass.Threads.Select(t?=>?new?{irp?=? Debugger.Utility.Collections.FromListEntry(t.KernelObject.IrpList,?"nt!_IRP",?"ThreadListEntry")}).Where(t?=>?t.irp.Count()?!=?0)????????????????[0x384]?????????irp?????????????[0x0]??[Type:?_IRP][<Raw?View>]??[Type:?_IRP]IoStack???????:?Size?=?12,?Current?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\Ntfs"CurrentThread?:?0xffffb90a59477080?[Type:?_ETHREAD?*][0x1]??[Type:?_IRP][<Raw?View>]??[Type:?_IRP]IoStack???????:?Size?=?12,?Current?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\Ntfs"CurrentThread?:?0xffffb90a59477080?[Type:?_ETHREAD?*]我們可以在這里停一下,點擊其中一個 IRP 的 IoStack (或者運行 -r5 來查看全部內容)可以得到調用棧信息:
dx?@$irpThreads.First().irp[0].IoStack@$irpThreads.First().irp[0].IoStack??:?Size?=?12,?Current?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\Ntfs"[0]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][1]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][2]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][3]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][4]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][5]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][6]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][7]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][8]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][9]??????????????:?IRP_MJ_CREATE?/?0x0?for?{...}?[Type:?_IO_STACK_LOCATION][10]?????????????:?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\Ntfs"?[Type:?_IO_STACK_LOCATION][11]?????????????:?IRP_MJ_DIRECTORY_CONTROL?/?0x2?for?Device?for?"\FileSystem\FltMgr"?[Type:?_IO_STACK_LOCATION]最后,我們將遍歷每個線程,并且遍歷線程中的每個 IRP,并對其執行 Executeccommand !IRP <irp address>。這里我們也需要類型轉換和 ToDisplayString("x") 來匹配傳統命令所期望的格式。( !irp 的輸出很長,所以我們將其截斷,只關注感興趣的數據):
dx?-r3?@$irpThreads.Select(t?=>?t.irp.Select(i?=>?Debugger.Utility.Control.ExecuteCommand("!irp?"?+?((__int64)&i).ToDisplayString("x"))))@$irpThreads.Select(t?=>?t.irp.Select(i?=>?Debugger.Utility.Control.ExecuteCommand("!irp?"?+?((__int64)&i).ToDisplayString("x"))))????????????????[0x384]?????????[0x0]???????????[0x0]????????????:?Irp?is?active?with?12?stacks?11?is?current?(=?0xffffb90a5b8f4d40)[0x1]????????????:??No?Mdl:?No?System?Buffer:?Thread?ffffb90a59477080:??Irp?stack?trace.??[0x2]????????????:??????cmd??flg?cl?Device???File?????Completion-Context[0x3]????????????:??[N/A(0),?N/A(0)]...[0x34]???????????:?Irp?Extension?present?at?0xffffb90a5b8f4dd0:[0x1]???????????[0x0]????????????:?Irp?is?active?with?12?stacks?11?is?current?(=?0xffffb90a5bd24840)[0x1]????????????:??No?Mdl:?No?System?Buffer:?Thread?ffffb90a59477080:??Irp?stack?trace.??[0x2]????????????:??????cmd??flg?cl?Device???File?????Completion-Context[0x3]????????????:??[N/A(0),?N/A(0)]...[0x34]???????????:?Irp?Extension?present?at?0xffffb90a5bd248d0:!irp 提供給我們的大部分信息都可以通過使用 dx 解析 IRP 并轉儲其 IoStack 獲得。但是有一些信息如果不使用傳統命令則很難獲得。比如, IrpExtension 是否存在及其地址,以及可能與 IRP 關聯的 Mdl 的信息。
反匯編器(Disassembler)
我們以 u 命令為例,下面的例子展示的是:在 dx 中通過 Debugger.Utility.Code.CreateDisassember 和 DisassembleBlock 創建可以遍歷和搜索的反匯編代碼:
dx?-r3?Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(@$curstack.Frames[1].Attributes.InstructionOffset)Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(@$curstack.Frames[1].Attributes.InstructionOffset)????????????????[0xfffff80752ad2b61]?:?Basic?Block?[0xfffff80752ad2b61?-?0xfffff80752ad2b67)StartAddress?????:?0xfffff80752ad2b61EndAddress???????:?0xfffff80752ad2b67Instructions????[0xfffff80752ad2b61]?:?add?????????rsp,30h[0xfffff80752ad2b65]?:?pop?????????rbx[0xfffff80752ad2b66]?:?ret[0xfffff80752ad2b67]?:?Basic?Block?[0xfffff80752ad2b67?-?0xfffff80752ad2b7e)StartAddress?????:?0xfffff80752ad2b67EndAddress???????:?0xfffff80752ad2b7eInstructions????[0xfffff80752ad2b67]?:?lea?????????r8,[rbx+70h][0xfffff80752ad2b6b]?:?mov?????????rdx,rdi[0xfffff80752ad2b6e]?:?lea?????????rcx,[rbx+60h][0xfffff80752ad2b72]?:?mov?????????r10,qword?ptr?[kdnic!__imp_ExInterlockedInsertTailList?(fffff80752ad6150)][0xfffff80752ad2b79]?:?call????????ntkrnlmp!ExInterlockedInsertTailList?(fffff8074ea56590)[0xfffff80752ad2b7e]?:?Basic?Block?[0xfffff80752ad2b7e?-?0xfffff80752ad2b80)StartAddress?????:?0xfffff80752ad2b7eEndAddress???????:?0xfffff80752ad2b80Instructions????[0xfffff80752ad2b7e]?:?jmp?????????kdnic!TXTransmitQueuedSends+0xd0?(fffff80752ad2b0c)[0xfffff80752ad2b80]?:?Basic?Block?[0xfffff80752ad2b80?-?0xfffff80752ad2b81)StartAddress?????:?0xfffff80752ad2b80EndAddress???????:?0xfffff80752ad2b81Instructions...只選擇 Instructions 并將其扁平化顯示,整理后的版本如下:
dx?-r2?Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(@$curstack.Frames[1].Attributes.InstructionOffset).Select(b?=>?b.Instructions).Flatten()Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(@$curstack.Frames[1].Attributes.InstructionOffset).Select(b?=>?b.Instructions).Flatten()????????????????[0x0]???????????[0xfffff80752ad2b61]?:?add?????????rsp,30h[0xfffff80752ad2b65]?:?pop?????????rbx[0xfffff80752ad2b66]?:?ret[0x1]???????????[0xfffff80752ad2b67]?:?lea?????????r8,[rbx+70h][0xfffff80752ad2b6b]?:?mov?????????rdx,rdi[0xfffff80752ad2b6e]?:?lea?????????rcx,[rbx+60h][0xfffff80752ad2b72]?:?mov?????????r10,qword?ptr?[kdnic!__imp_ExInterlockedInsertTailList?(fffff80752ad6150)][0xfffff80752ad2b79]?:?call????????ntkrnlmp!ExInterlockedInsertTailList?(fffff8074ea56590)[0x2]???????????[0xfffff80752ad2b7e]?:?jmp?????????kdnic!TXTransmitQueuedSends+0xd0?(fffff80752ad2b0c)[0x3]???????????[0xfffff80752ad2b80]?:?int?????????3[0x4]???????????[0xfffff80752ad2b81]?:?int?????????3...合成方法(Synthetic Methods)
新調試數據模型提供的另一個功能是允許我們創建并使用自定義函數。語法如下:
0:?kd>?dx?@$multiplyByThree?=?(x?=>?x?*?3)@$multiplyByThree?=?(x?=>?x?*?3)0:?kd>?dx?@$multiplyByThree(5)@$multiplyByThree(5)?:?15我們也可以創建包含多個參數的函數:
0:?kd>?dx?@$add?=?((x,?y)?=>?x?+?y)@$add?=?((x,?y)?=>?x?+?y)0:?kd>?dx?@$add(5,?7)@$add(5,?7)??????:?12或者,如果我們真的想要玩的更高端一些,我們可以將這些函數應用到前面的反匯編輸出中,以便找出 ZwSetInformationProcess 函數中所有的內存寫入指令。為此,我們需要對每條指令進行一些檢查,以便知道它是否是內存寫入指令:
指令至少有兩個操作數嗎?
例如,ret 指令的操作數是 0,而 jmp <address> 指令的操作數是 1。我們只關心將一個值寫入某個位置的情況,這總是需要兩個操作數。為了驗證這一點,我們需要檢查每條指令的 Operands.Count() > 1。
是否是內存引用操作? 我們只對內存寫入操作感興趣,并希望忽略像 mon r10, rcx 這樣的指令。為此,我們將檢查每條指令的 Operands[0].Attributes.IsMemoryReference == true。我們檢查 Operands[0] ,因為它是目的操作數。如果我們想查找內存讀取指令,我們應該檢查源操作數,它在 Operands[1] 中。
目的操作數是輸出嗎?
我們想過濾掉引用內存但沒有寫入內存的指令。我們將使用 Operands[0].IsOutput == true 進行檢查。
作為最后一個過濾器,我們希望忽略棧內存寫入操作,它看起來像 mov [rsp+0x18],1 或 mov [rbp-0x10],rdx。
我們將檢查第一個操作數的寄存器,并確保其索引不是 rsp 索引(0x14)或 rbp 索引(0x15)。
我們將編寫一個函數 @$isMemWrite,它接收一個代碼塊并且只返回內存寫入指令(基于上面的檢查)。然后我們可以創建一個反匯編器,反匯編我們的目標函數并且只輸出其中的內存寫入指令:
dx?-r0?@$rspId?=?0x14dx?-r0?@$rbpId?=?0x15dx?-r0?@$isMemWrite?=?(b?=>?b.Instructions.Where(i?=>?i.Operands.Count()?>?1?&&?i.Operands[0].Attributes.IsOutput?&&?i.Operands[0].Registers[0].Id?!=?@$rspId?&&?i.Operands[0].Registers[0].Id?!=?@$rbpId?&&?i.Operands[0].Attributes.IsMemoryReference))dx?-r0?@$findMemWrite?=?(a?=>?Debugger.Utility.Code.CreateDisassembler().DisassembleBlocks(a).Select(b?=>?@$isMemWrite(b)))dx?-r2?@$findMemWrite(&nt!ZwSetInformationProcess).Where(b?=>?b.Count()?!=?0)@$findMemWrite(&nt!ZwSetInformationProcess).Where(b?=>?b.Count()?!=?0)?????????[0xfffff8074ebd23d4][0xfffff8074ebd23e9]?:?mov?????????qword?ptr?[r10+80h],rax[0xfffff8074ebd23f5]?:?mov?????????qword?ptr?[r10+44h],rax[0xfffff8074ebd2415][0xfffff8074ebd2421]?:?mov?????????qword?ptr?[r10+98h],r8[0xfffff8074ebd2428]?:?mov?????????qword?ptr?[r10+0F8h],r9[0xfffff8074ebd2433]?:?mov?????????byte?ptr?gs:[5D18h],al[0xfffff8074ebd25b2][0xfffff8074ebd25c3]?:?mov?????????qword?ptr?[rcx],rax[0xfffff8074ebd25c9]?:?mov?????????qword?ptr?[rcx+8],rax[0xfffff8074ebd25d0]?:?mov?????????qword?ptr?[rcx+10h],rax[0xfffff8074ebd25d7]?:?mov?????????qword?ptr?[rcx+18h],rax[0xfffff8074ebd25df]?:?mov?????????qword?ptr?[rcx+0A0h],rax[0xfffff8074ebd263f][0xfffff8074ebd264f]?:?and?????????byte?ptr?[rax+5],0FDh[0xfffff8074ebd26da][0xfffff8074ebd26e3]?:?mov?????????qword?ptr?[rcx],rax[0xfffff8074ebd26e9]?:?mov?????????qword?ptr?[rcx+8],rax[0xfffff8074ebd26f0]?:?mov?????????qword?ptr?[rcx+10h],rax[0xfffff8074ebd26f7]?:?mov?????????qword?ptr?[rcx+18h],rax[0xfffff8074ebd26ff]?:?mov?????????qword?ptr?[rcx+0A0h],rax[0xfffff8074ebd2708]?:?mov?????????word?ptr?[rcx+72h],ax...作為另外一個項目,它幾乎結合了上面提到的所有內容 —— 實現 dx 版本的 !apc 。為了簡化,我們只尋找內核 APC。要做到這一點,需要如下幾個步驟:
使用 @$cursession.Processes 遍歷所有進程,以找到這些進程:包含 ?KTHREAD.ApcState.KernelApcPending 值為 1 的線程。
在對應進程中創建一個容器,只包含那些包含掛起內核 APC 的線程,忽略其它線程。
對于每個線程,遍歷 KTHREAD.ApcState.ApcListHead[0] (包含內核 APC)并收集感興趣的信息。可以通過前面介紹的 FromListHead() 方法實現。為了使我們的容器盡可能地與 !apc 相似,我們將只獲取 KernelRoutine 和 RundownRoutine,盡管在你的實現中,你可能會發現其他感興趣的字段。
為了讓容器更容易遍歷,我們將收集進程名、進程 ID、 EPROCESS 地址以及線程 ID 和 ETHREAD 地址。
在我們的實現中,我們創建了幾個輔助函數:
@$printLn —— 運行傳統命令 ln,以獲取指定地址對應的符號信息。
**@$extractBetween** —— 提取兩個字符串之間的字符串,用于從 `@$printLn` 的輸出中獲取子字符串。
**@$printSymbol** —— 傳遞一個地址給 `@$printLn,并且使用 @$extractSymbol` 提取符號名。
@$apcsForThread —— 找到一個線程中的所有內核 APC,并且創建一個包含 KernelRoutine 和 RundownRoutine 的容器。
在得到所有進程(包含掛起的內核 APC 的線程)后,將其保存到 @$procWithKernelApcs 寄存器中,然后在一個單獨的命令中使用 @$apcsForThread 獲取 APC 信息。我們將 EPPROCESS 和 ETHREAD 指針強制轉換為 void*,這樣當我們顯示最終結果時 dx 就不會顯示整個結構。
這是我解決問題的方法,但也有其他方法,你的方法不一定和我的完全相同!
我想出的腳本是:
dx?-r0?@$printLn?=?(a?=>?Debugger.Utility.Control.ExecuteCommand(“ln?“+((__int64)a).ToDisplayString(“x”)))dx?-r0?@$extractBetween?=?((x,y,z)?=>?x.Substring(x.IndexOf(y)?+?y.Length,?x.IndexOf(z)?—?x.IndexOf(y)?—?y.Length))dx?-r0?@$printSymbol?=?(a?=>?@$extractBetween(@$printLn(a)[3],?“?“,?“|”))dx?-r0?@$apcsForThread?=?(t?=>?new?{TID?=?t.Id,?Object?=?(void*)&t.KernelObject,?Apcs?=?Debugger.Utility.Collections.FromListEntry(*(nt!_LIST_ENTRY*)&t.KernelObject.Tcb.ApcState.ApcListHead[0],?“nt!_KAPC”,?“ApcListEntry”).Select(a?=>?new?{?Kernel?=?@$printSymbol(a.KernelRoutine),?Rundown?=?@$printSymbol(a.RundownRoutine)})})dx?-r0?@$procWithKernelApc?=?@$cursession.Processes.Select(p?=>?new?{Name?=?p.Name,?PID?=?p.Id,?Object?=?(void*)&p.KernelObject,?ApcThreads?=?p.Threads.Where(t?=>?t.KernelObject.Tcb.ApcState.KernelApcPending?!=?0)}).Where(p?=>?p.ApcThreads.Count()?!=?0)dx?-r6?@$procWithKernelApc.Select(p?=>?new?{?Name?=?p.Name,?PID?=?p.PID,?Object?=?p.Object,?ApcThreads?=?p.ApcThreads.Select(t?=>?@$apcsForThread(t))})它會產生以下輸出:
dx?-r6?@$procWithKernelApc.Select(p?=>?new?{?Name?=?p.Name,?PID?=?p.PID,?Object?=?p.Object,?ApcThreads?=?p.ApcThreads.Select(t?=>?@$apcsForThread(t))})@$procWithKernelApc.Select(p?=>?new?{?Name?=?p.Name,?PID?=?p.PID,?Object?=?p.Object,?ApcThreads?=?p.ApcThreads.Select(t?=>?@$apcsForThread(t))})[0x15b8]????????Name?????????????:?SearchUI.exeLength???????????:?0xcPID??????????????:?0x15b8Object???????????:?0xffffb90a5b1300c0?[Type:?void?*]ApcThreads??????[0x159c]????????TID??????????????:?0x159cObject???????????:?0xffffb90a5b14f080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1528]????????TID??????????????:?0x1528Object???????????:?0xffffb90a5aa6b080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x16b4]????????TID??????????????:?0x16b4Object???????????:?0xffffb90a59f1e080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x16a0]????????TID??????????????:?0x16a0Object???????????:?0xffffb90a5b141080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x16b8]????????TID??????????????:?0x16b8Object???????????:?0xffffb90a5aab20c0?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1740]????????TID??????????????:?0x1740Object???????????:?0xffffb90a5ab362c0?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1780]????????TID??????????????:?0x1780Object???????????:?0xffffb90a5b468080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1778]????????TID??????????????:?0x1778Object???????????:?0xffffb90a5b6f7080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x17d0]????????TID??????????????:?0x17d0Object???????????:?0xffffb90a5b1e8080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x17d4]????????TID??????????????:?0x17d4Object???????????:?0xffffb90a5b32f080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x17f8]????????TID??????????????:?0x17f8Object???????????:?0xffffb90a5b32e080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0xb28]?????????TID??????????????:?0xb28Object???????????:?0xffffb90a5b065600?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList???[0x1850]????????TID??????????????:?0x1850Object???????????:?0xffffb90a5b6a5080?[Type:?void?*]Apcs????????????[0x0]???????????Kernel???????????:???nt!EmpCheckErrataList???Rundown??????????:???nt!EmpCheckErrataList雖然輸出結果沒有 !apc 美觀,但已經很美觀了。
我們還可以將其輸出成表格,顯示相關進程的信息并且可以分別瀏覽每個進程中的 APC:
dx?-g?@$procWithKernelApc.Select(p?=>?new?{?Name?=?p.Name,?PID?=?p.PID,?Object?=?p.Object,?ApcThreads?=?p.ApcThreads.Select(t?=>?@$apcsForThread(t))}) dx-g-procWithKernelApc.Select但是請等一下,這些包含 nt!EmpCheckErrataList 的 APC 是怎么回事?為什么 SearchUI.exe 里到處都是?這個進程和 erratas 有什么關系?(譯注: 函數 nt!EmpCheckErrataList 中包含 Errata。)
秘密在于實際上并沒有 APC 會調用 nt!EmpCheckErrataList。不,符號也沒有錯。(譯注: 哈哈,感覺作者在跟讀者討論問題)
我們之所以看到這樣的現象,是因為編譯器很智能 —— 當編譯器看到不同的函數具有相同的代碼的時候,編譯器會讓它們全部指向同一段代碼,而不是多次復制這段代碼。你可能會認為這不是一個會經常發生的事情,但讓我們看看 nt!EmpCheckErrataList 的反匯編代碼(這次使用舊辦法):
u?EmpCheckErrataListnt!EmpCheckErrataList: fffff807`4eb86010?c20000??????????ret?????0 fffff807`4eb86013?cc??????????????int?????3 fffff807`4eb86014?cc??????????????int?????3 fffff807`4eb86015?cc??????????????int?????3 fffff807`4eb86016?cc??????????????int?????3這實際上只是一個存根。它可能是一個尚未實現的函數(EmpCheckErrataList 可能是這種情況),也可能是出于某些原因被故意定義為存根函數。這些 APC 的 KernelRoutine/RundownRoutine 真正對應的函數是 nt!KiSchedulerApcNop,它被故意設計成存根函數,并且已經多年了。我們可以看到它具有相同的代碼并且指向同一地址:
u?nt!KiSchedulerApcNopnt!EmpCheckErrataList: fffff807`4eb86010?c20000??????????ret?????0 fffff807`4eb86013?cc??????????????int?????3 fffff807`4eb86014?cc??????????????int?????3 fffff807`4eb86015?cc??????????????int?????3 fffff807`4eb86016?cc??????????????int?????3那么為什么我們會看到這么多這樣的 APC 呢?
當一個線程被掛起時,系統會創建一個信號量,并將一個等待該信號量的 APC 發送到該線程。線程將一直等待,直到有人喚醒它,然后系統將釋放信號量,線程將停止等待并恢復運行。APC 本身不需要做很多事情,但它必須有一個 KernelRoutine 和一個 RundownRoutine,所以系統使用了存根。這個存根使用了這些函數(包含存根代碼)中的一個,這次是 nt!EmpCheckErrataList,但在下一個版本中可能是一個不同的函數名。
任何對掛起機制感興趣的人都可以查看 ReactOS。這些函數的代碼發生了一點變化,并且存根函數名從 KiSuspendNop 變成了 KiSchedulerApcNop,但總體設計保持相似。
我跑題了,這不是本篇文章應該討論的內容。讓我們回到 WinDbg,接著講合成類型:
合成類型(Synthetic Types)
在介紹完合成方法后,我們還可以添加自定義的命名類型(named types),并使用它們來解析之前無法解析的數據。
例如,讓我們嘗試打印 PspCreateProcessNotifyRoutine 數組,該數組包含所有已注冊的進程通知例程 —— 這些函數由驅動程序注冊,在進程啟動時會收到一個通知。但是這個數組不包含指向注冊例程的指針。相反,它包含指向未文檔化的結構體 EX_CALLBACK_ROUTINE_BLOCK 的指針。
因此,為了解析這個數組,我們需要確保 WinDbg 認識這個類型 —— 我們需要使用合成類型。我們首先要創建一個頭文件,里面包含我們想要定義的所有類型(我使用 c:\temp\header.h)。在本例中,我們只定義了 EX_CALLBACK_ROUTINE_BLOCK,可以在 ReactOS 中找到它的定義:
typedef?struct?_EX_CALLBACK_ROUTINE_BLOCK {_EX_RUNDOWN_REF?RundownProtect;void*?Function;void*?Context; }?EX_CALLBACK_ROUTINE_BLOCK,?*PEX_CALLBACK_ROUTINE_BLOCK;現在我們可以讓 WinDbg 加載這個頭文件并將里面的類型添加到 nt 模塊中:
dx?Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("c:\\temp\\header.h",?"nt")Debugger.Utility.Analysis.SyntheticTypes.ReadHeader("c:\\temp\\header.h",?"nt")?????????????????:?ntkrnlmp.exe(header.h)ReturnEnumsAsObjects?:?falseRegisterSyntheticTypeModels?:?falseModule???????????:?ntkrnlmp.exeHeader???????????:?header.hTypes這會為我們返回一個對象,通過這個對象,我們可以查看添加到此模塊的所有類型。現在,類型已經定義好了,我們可以通過 ?CreateInstance 使用它:
dx?Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?*(__int64*)&nt!PspCreateProcessNotifyRoutine)Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?*(__int64*)&nt!PspCreateProcessNotifyRoutine)????????????????RundownProtect???[Type:?_EX_RUNDOWN_REF]Function?????????:?0xfffff8074cbdff50?[Type:?void?*]Context??????????:?0x6e496c4102031400?[Type:?void?*]需要注意的是 CreateInstance 只接受 __int64 作為輸入,所以任何其他類型都必須強制轉換。提前知道這一點很好,因為返回的錯誤消息并不總是那么容易理解的。
現在如果查看輸出結果,特別是 Context,有些東西似乎很奇怪。實際上,如果我們試圖轉儲 Function,我們會看到它并不指向任何代碼:
dq?0xfffff8074cbdff50 fffff807`4cbdff50??????????`?????????????????`???????? fffff807`4cbdff60??????????`?????????????????`???????? fffff807`4cbdff70??????????`?????????????????`???????? fffff807`4cbdff80??????????`?????????????????`???????? fffff807`4cbdff90??????????`?????????????????`????????發生了什么?
不是我們強制轉換到 EX_CALLBACK_ROUTINE_BLOCK 出了問題,而是我們轉換的地址有問題。如果我們轉儲 PspCreateProcessNotifyRoutine 中的值,我們可能會看到它的值:
dx?((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0)((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0)[0]??????????????:?0xffffb90a530504ef?[Type:?void?*?*][1]??????????????:?0xffffb90a532a512f?[Type:?void?*?*][2]??????????????:?0xffffb90a53da9d5f?[Type:?void?*?*][3]??????????????:?0xffffb90a53da9ccf?[Type:?void?*?*][4]??????????????:?0xffffb90a53e5d15f?[Type:?void?*?*][5]??????????????:?0xffffb90a571469ef?[Type:?void?*?*][6]??????????????:?0xffffb90a5714722f?[Type:?void?*?*][7]??????????????:?0xffffb90a571473df?[Type:?void?*?*][8]??????????????:?0xffffb90a597d989f?[Type:?void?*?*]所有這些地址的低半字節都是 0xF,而我們知道 x64 機器中的指針總會對齊到 8 字節,通常是 0x10。這是因為我之前過度簡化了 —— 這些不是指向 EX_CALLBACK_ROUTINE_BLOCK 的指針,它們實際上是包含 EX_RUNDOWN_REF 的 EX_CALLBACK 結構體(另一種不在公共符號中的類型)。但是為了使這個示例更簡單,我將把它們當作與 ~0xF 執行與操作后的簡單指針,因為這對于我們的目的來說已經足夠好了。如果你選擇寫一個驅動程序來處理 PspCreateProcessNotifyRoutine,請不要使用這個 hack,查看 ReactOS 并正確地做事情。😊
因此,要修復我們的命令,我們只需要在對地址進行強制類型轉換之前將其對齊到 0x10。我們可以像下面這樣做:
<address>?&?0xFFFFFFFFFFFFFFF0或更好的版本:
<address>?&?~0xF讓我們在我們的命令中使用它:
dx?Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?(*(__int64*)&nt!PspCreateProcessNotifyRoutine)?&?~0xf)Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?(*(__int64*)&nt!PspCreateProcessNotifyRoutine)?&?~0xf)????????????????RundownProtect???[Type:?_EX_RUNDOWN_REF]Function?????????:?0xfffff8074ea7f310?[Type:?void?*]Context??????????:?0x0?[Type:?void?*]看起來好些了。這次讓我們確認 Function 確實指向了一個函數:
ln?0xfffff8074ea7f310 Browse?module Set?bu?breakpoint(fffff807`4ea7f310)???nt!ViCreateProcessCallback???| (fffff807`4ea7f330)???nt!RtlStringCbLengthW Exact?matches:nt!ViCreateProcessCallback?(void)看起來好多了!現在我們可以將這種類型轉換定義為一個合成方法,并使用它來獲取數組中所有例程的函數地址:
dx?-r0?@$getCallbackRoutine?=?(a?=>?Debugger.Utility.Analysis.SyntheticTypes.CreateInstance("_EX_CALLBACK_ROUTINE_BLOCK",?(__int64)(a?&?~0xf)))dx?((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0).Select(a?=>?@$getCallbackRoutine(a).Function)((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0).Select(a?=>?@$getCallbackRoutine(a).Function)????????????????[0]??????????????:?0xfffff8074ea7f310?[Type:?void?*][1]??????????????:?0xfffff8074ff97220?[Type:?void?*][2]??????????????:?0xfffff80750a41330?[Type:?void?*][3]??????????????:?0xfffff8074f8ab420?[Type:?void?*][4]??????????????:?0xfffff8075106d9f0?[Type:?void?*][5]??????????????:?0xfffff807516dd930?[Type:?void?*][6]??????????????:?0xfffff8074ff252c0?[Type:?void?*][7]??????????????:?0xfffff807520b6aa0?[Type:?void?*][8]??????????????:?0xfffff80753a63cf0?[Type:?void?*]如果我們能看到符號而不是地址,這會更有趣。我們已經知道如何通過執行傳統命令 ln 來獲取符號,但這一次我們將使用 .printf。
首先,我們將編寫一個輔助函數 @$getsym,它將運行命令 printf "%y", <address>:
dx?-r0?@$getsym?=?(x?=>?Debugger.Utility.Control.ExecuteCommand(".printf\"%y\",?"?+?((__int64)x).ToDisplayString("x"))[0])然后我們使用這個輔助函數打印每個函數地址對應的符號:
dx?((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0).Select(a?=>?@$getsym(@$getCallbackRoutine(a).Function))((void**[0x40])&nt!PspCreateProcessNotifyRoutine).Where(a?=>?a?!=?0).Select(a?=>?@$getsym(@$getCallbackRoutine(a).Function))????????????????[0]??????????????:?nt!ViCreateProcessCallback?(fffff807`4ea7f310)[1]??????????????:?cng!CngCreateProcessNotifyRoutine?(fffff807`4ff97220)[2]??????????????:?WdFilter!MpCreateProcessNotifyRoutineEx?(fffff807`50a41330)[3]??????????????:?ksecdd!KsecCreateProcessNotifyRoutine?(fffff807`4f8ab420)[4]??????????????:?tcpip!CreateProcessNotifyRoutineEx?(fffff807`5106d9f0)[5]??????????????:?iorate!IoRateProcessCreateNotify?(fffff807`516dd930)[6]??????????????:?CI!I_PEProcessNotify?(fffff807`4ff252c0)[7]??????????????:?dxgkrnl!DxgkProcessNotify?(fffff807`520b6aa0)[8]??????????????:?peauth+0x43cf0?(fffff807`53a63cf0)你看,好太多了!
斷點(Breakpoints)
條件斷點(Conditional Breakpoint)
調試時,條件斷點是一個巨大的痛點。在舊的 MASM 語法中,它們幾乎不可用。我花了幾個小時試圖讓它們以我想要的方式工作,但結果是如此糟糕,以至于我甚至不知道我在試圖做什么,更不用說為什么它不過濾任何東西或者如何修復它。
好吧,這些日子已經過去了。我們現在可以在條件斷點中使用 dx 查詢,語法為:bp /w "dx query" <address>。
例如,假設我們正在調試一個 Wow64 進程打開文件的問題。NtOpenProcess 函數一直在被調用,但是我們只關心來自 Wow64 進程的調用,這并不是現代操作系統上的大多數進程(譯注: Wow64 進程指的是運行在 64 位系統上的 32 位進程,64 位進程逐漸成為主流)。因此,為了避免在走狗屎運之前無助的經歷 100 次調試中斷或者避免與 MASM 風格的條件斷點做斗爭,我們可以這樣做:
bp?/w?"@$curprocess.KernelObject.WoW64Process?!=?0" nt!NtOpenProcess然后讓系統運行起來,當再次中斷時,我們可以檢查它是否奏效:
Breakpoint?3?hit nt!NtOpenProcess: fffff807`2e96b7e0?4883ec38????????sub?????rsp,38hdx?$curprocess.KernelObject.WoW64Process@$curprocess.KernelObject.WoW64Process??:?0xffffc10f5163b390?[Type:?_EWOW64PROCESS?*][+0x000]?Peb??????????????:?0xf88000?[Type:?void?*][+0x008]?Machine??????????:?0x14c?[Type:?unsigned?short][+0x00c]?NtdllType????????:?PsWowX86SystemDll?(1)?[Type:?_SYSTEM_DLL_TYPE]dx?@$curprocess.Name@$curprocess.Name?:?IpOverUsbSvc.exeLength???????????:?0x10觸發斷點的進程是一個 WoW64 進程!對于那些曾經嘗試過在 MASM 中使用條件斷點的人來說,dx 真是一個大救星。
其它斷點選項(Other Breakpoint Options)
在 Debugger.Utility.Control 下,還有其他一些有趣的斷點選項:
SetBreakpointAtSourceLocation —— 允許我們在模塊對應的源文件中設置斷點,語法如下:
dx Debugger.Utility.Control.SetBreakpointAtSourceLocation("MyModule!myFile.cpp", "172")
SetBreakpointAtOffset —— 在函數內部偏移設置斷點 —— dx Debugger.Utility.Control.SetBreakpointAtOffset("NtOpenFile", 8, “nt")
SetBreakpointForReadWrite —— 類似于傳統的 ba 命令,但其語法更具可讀性。它允許我們設置斷點,以便在任何讀或寫某個地址時中斷。它的默認配置為 type = Hardware Write 并且 size = 1。(譯注: 原文中的函數名是 SetBreakpointForReadWriteFile,應該是筆誤。)
比如,讓我們在讀取 Ci!g_CiOptions(一個 4 字節大小的變量)時中斷下來:
讓系統繼續運行,斷點幾乎立即命中:
0:?kd>?g Breakpoint?0?hit CI!CiValidateImageHeader+0x51b: fffff807`2f6fcb1b?740c?je?CI!CiValidateImageHeader+0x529?(fffff807`2f6fcb29)CI!CiValidateImageHeader 在驗證鏡像頭時會讀取此全局變量。在這個特定的示例中,我們將看到讀取這個變量是很頻繁的,修改此變量則加有趣,因為它可以向我們展示篡改簽名驗證的嘗試。
值得注意的是,這些命令不僅僅是設置了一個斷點,實際上會返回給我們一個可以操作的對象,包含屬性 IsEnabled , Condition(允許我們設置一個條件),PassCount(告訴我們這個斷點已經訪問了多少次),還有更多其它屬性。
文件系統(FileSystem)
在 Debugger.Utility 下提供了 FileSystem 模塊,允許我們在調試器中查詢和控制主機上的文件系統(不是正在被調試的機器):
dx?-r1?Debugger.Utility.FileSystemDebugger.Utility.FileSystemCreateFile?[CreateFile(path,?[disposition])?-?Creates?a?file?at?the?specified?path?and?returns?a?file?object.??'disposition'?can?be?one?of?'CreateAlways'?or?'CreateNew']CreateTempFile???[CreateTempFile()?-?Creates?a?temporary?file?in?the?%TEMP%?folder?and?returns?a?file?object]CreateTextReader?[CreateTextReader(file?|?path,?[encoding])?-?Creates?a?text?reader?over?the?specified?file.??If?a?path?is?passed?instead?of?a?file,?a?file?is?opened?at?the?specified?path.??'encoding'?can?be?'Utf16',?'Utf8',?or?'Ascii'.??'Ascii'?is?the?default]CreateTextWriter?[CreateTextWriter(file?|?path,?[encoding])?-?Creates?a?text?writer?over?the?specified?file.??If?a?path?is?passed?instead?of?a?file,?a?file?is?created?at?the?specified?path.??'encoding'?can?be?'Utf16',?'Utf8',?or?'Ascii'.??'Ascii'?is?the?default]CurrentDirectory?:?C:\WINDOWS\system32DeleteFile???????[DeleteFile(path)?-?Deletes?a?file?at?the?specified?path]FileExists???????[FileExists(path)?-?Checks?for?the?existance?of?a?file?at?the?specified?path]OpenFile?????????[OpenFile(path)?-?Opens?a?file?read/write?at?the?specified?path]TempDirectory????:?C:\Users\yshafir\AppData\Local\Temp我們可以創建文件、打開文件、寫入文件、刪除文件或檢查某個文件是否存在于某個特定路徑中。來看一個簡單的例子,讓我們轉儲當前目錄(C:\Windows\System32)的內容:
dx?-r1?Debugger.Utility.FileSystem.CurrentDirectory.FilesDebugger.Utility.FileSystem.CurrentDirectory.Files????????????????[0x0]????????????:?C:\WINDOWS\system32\07409496-a423-4a3e-b620-2cfb01a9318d_HyperV-ComputeNetwork.dll[0x1]????????????:?C:\WINDOWS\system32\1[0x2]????????????:?C:\WINDOWS\system32\103[0x3]????????????:?C:\WINDOWS\system32\108[0x4]????????????:?C:\WINDOWS\system32\11[0x5]????????????:?C:\WINDOWS\system32\113...[0x44]???????????:?C:\WINDOWS\system32\93[0x45]???????????:?C:\WINDOWS\system32\98[0x46]???????????:?C:\WINDOWS\system32\@AppHelpToast.png[0x47]???????????:?C:\WINDOWS\system32\@AudioToastIcon.png[0x48]???????????:?C:\WINDOWS\system32\@BackgroundAccessToastIcon.png[0x49]???????????:?C:\WINDOWS\system32\@bitlockertoastimage.png[0x4a]???????????:?C:\WINDOWS\system32\@edptoastimage.png[0x4b]???????????:?C:\WINDOWS\system32\@EnrollmentToastIcon.png[0x4c]???????????:?C:\WINDOWS\system32\@language_notification_icon.png[0x4d]???????????:?C:\WINDOWS\system32\@optionalfeatures.png[0x4e]???????????:?C:\WINDOWS\system32\@VpnToastIcon.png[0x4f]???????????:?C:\WINDOWS\system32\@WiFiNotificationIcon.png[0x50]???????????:?C:\WINDOWS\system32\@windows-hello-V4.1.gif[0x51]???????????:?C:\WINDOWS\system32\@WindowsHelloFaceToastIcon.png[0x52]???????????:?C:\WINDOWS\system32\@WindowsUpdateToastIcon.contrast-black.png[0x53]???????????:?C:\WINDOWS\system32\@WindowsUpdateToastIcon.contrast-white.png[0x54]???????????:?C:\WINDOWS\system32\@WindowsUpdateToastIcon.png[0x55]???????????:?C:\WINDOWS\system32\@WirelessDisplayToast.png[0x56]???????????:?C:\WINDOWS\system32\@WwanNotificationIcon.png[0x57]???????????:?C:\WINDOWS\system32\@WwanSimLockIcon.png[0x58]???????????:?C:\WINDOWS\system32\aadauthhelper.dll[0x59]???????????:?C:\WINDOWS\system32\aadcloudap.dll[0x5a]???????????:?C:\WINDOWS\system32\aadjcsp.dll[0x5b]???????????:?C:\WINDOWS\system32\aadtb.dll[0x5c]???????????:?C:\WINDOWS\system32\aadWamExtension.dll[0x5d]???????????:?C:\WINDOWS\system32\AboutSettingsHandlers.dll[0x5e]???????????:?C:\WINDOWS\system32\AboveLockAppHost.dll[0x5f]???????????:?C:\WINDOWS\system32\accessibilitycpl.dll[0x60]???????????:?C:\WINDOWS\system32\accountaccessor.dll[0x61]???????????:?C:\WINDOWS\system32\AccountsRt.dll[0x62]???????????:?C:\WINDOWS\system32\AcGenral.dll...我們可以選擇刪除其中的一個文件:
dx?-r1?Debugger.Utility.FileSystem.CurrentDirectory.Files[1].Delete()或者通過 DeleteFile 刪除:
dx?Debugger.Utility.FileSystem.DeleteFile(“C:\\WINDOWS\\system32\\71”)注意,在這個模塊中,路徑必須使用雙反斜杠("\\"),就像我們自己調用 Win32 API 時一樣。
作為最后一個練習,我們將把在這里學到的東西放在一起 —— 我們將在內核變量上創建一個斷點,從調用棧中獲取訪問它的符號,并將訪問它的符號寫入主機上的一個文件中。
讓我們把它分成幾個步驟:
打開一個文件,以便寫入結果。
創建一個 text writer,我們將使用它寫入文件。
創建訪問變量的斷點。在本例中,我們將選擇 nt!PsInitialSystemProcess 并設置讀斷點。我們將使用舊的 MASM 語法來設置一個斷點,每次斷點命中時,會執行一個 dx 命令并繼續運行:ba r4 <address> "dx <command>; g”
我們的命令將使用 @$curstack 來獲取訪問該變量的函數地址,然后使用前面編寫的 @$getsym 輔助函數來查找該地址對應的符號。然后使用 text writer 將結果寫入文件。
最后,關閉文件。
整合在一起:
dx?-r0?@$getsym?=?(x?=>?Debugger.Utility.Control.ExecuteCommand(".printf\"%y\",?"?+?((__int64)x).ToDisplayString("x"))[0])dx?@$tmpFile?=?Debugger.Utility.FileSystem.TempDirectory.OpenFile("log.txt")dx?@$txtWriter?=?Debugger.Utility.FileSystem.CreateTextWriter(@$tmpFile)ba?r4?nt!PsInitialSystemProcess?"dx?@$txtWriter.WriteLine(@$getsym(@$curstack.Frames[0].Attributes.InstructionOffset));?g"我們讓系統想運行多久就運行多久,當我們想停止記錄日志時,我們可以禁用或清除斷點并使用 dx @$tmpFile.Close() 關閉文件。
現在我們可以打開 @$tmpFile 并查看結果:
view-tempFile就是這樣!記錄關于調試器的信息是多么簡單啊!
這就是我們 WinDbg 系列的全部內容!本系列的所有腳本都將被上傳到 github ,還有一些新的沒有包含在這里的腳本。我鼓勵您進一步研究這個數據模型,因為我們甚至沒有涵蓋它包含的所有不同方法。編寫你自己的工具,并與世界分享它們 :)
譯注: github 地址是 https://github.com/yardenshafir/WinDbg_Scripts)
盡管本指南很長,但這些甚至還不是新數據模型中的所有可能選項。我甚至沒有提到新增的對 ?Javascript 的支持!
你可以在這篇精彩的文章中獲得更多關于在 WinDbg 中使用 Javascript 的信息,以及對令人興奮的 TTD (time travel debugging) 的新支持。
總結
以上是生活随笔為你收集整理的难译 | windbg 乐趣之道(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 生活话语
- 下一篇: 用Jplayer做的一个带动画的播放器