windbg的時間旅行實現對 C# 程式的終極調試

来源:https://www.cnblogs.com/huangxincheng/archive/2022/05/13/16265551.html
-Advertisement-
Play Games

一:什麼是時間旅行 簡而言之就是把程式的執行流拍成vlog,這樣就可以對 vlog 快進或者倒退,還可以分享給別人做進一步的分析,是不是想都不敢想。 很開心的是 windbg preview 版本中已經實現了,叫做 時間旅行調試 TTD,相比傳統的 靜態分析 不知道好多少倍。 為了能提起大家興趣,我 ...


一:什麼是時間旅行

簡而言之就是把程式的執行流拍成vlog,這樣就可以對 vlog 快進或者倒退,還可以分享給別人做進一步的分析,是不是想都不敢想。

很開心的是 windbg preview 版本中已經實現了,叫做 時間旅行調試 TTD,相比傳統的 靜態分析 不知道好多少倍。

為了能提起大家興趣,我就舉二個例子吧。

二:二個有趣的例子

1. 查看程式都觸發了第幾代垃圾回收

為了方便說明,我就用誘導GC手工觸發,然後再觀察都觸發了哪一代的 GC ,參考代碼如下:


        static void Main(string[] args)
        {
            List<string> list = new List<string>();

            //1. 第一次觸發GC
            GC.Collect();

            Console.WriteLine("觸發full gc");

            //2. 第二次觸發GC
            GC.Collect(0);

            Console.WriteLine("觸發 0 代 gc");

            //3.第二次觸發GC
            GC.Collect(1);

            Console.WriteLine("觸發 1 代 gc");
        }

接下來用 windbg 的 launch executeable (advanced) 來附加程式,勾選 Record,然後在彈框中將 vlog 保存到指定目錄,最後點擊 Record 就可以啦!

運行完後,windbg 會自動載入我的 D:\test\ConsoleApp104.run 的 vlog 文件,因為 gc 觸發的底層函數是coreclr!WKS::GCHeap::GarbageCollectGeneration ,所以我們用 bp 給它下一個斷點,運行多次 g 命令。


0:000> bp coreclr!WKS::GCHeap::GarbageCollectGeneration
Bp expression 'coreclr!WKS::GCHeap::GarbageCollectGeneration' could not be resolved, adding deferred bp
0:000> g
Time Travel Position: 3079F:63E
eax=00000001 ebx=00000002 ecx=00000002 edx=00000008 esi=00000002 edi=00000002
eip=02fc4256 esp=0057f204 ebp=0057f214 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
coreclr!WKS::GCHeap::GarbageCollectGeneration:
02fc4256 55              push    ebp
0:000> g
Time Travel Position: 34661:AF
eax=00000001 ebx=00000002 ecx=00000000 edx=00000008 esi=00000000 edi=00000002
eip=02fc4256 esp=0057f1f8 ebp=0057f208 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
coreclr!WKS::GCHeap::GarbageCollectGeneration:
02fc4256 55              push    ebp
0:000> g
Breakpoint 0 hit
Time Travel Position: 346A5:2CD
eax=00000001 ebx=00000002 ecx=00000001 edx=00000008 esi=00000001 edi=00000002
eip=02fc4256 esp=0057f1f8 ebp=0057f208 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
coreclr!WKS::GCHeap::GarbageCollectGeneration:
02fc4256 55              push    ebp

從輸出中可以很清楚的看到,命中了三次 GarbageCollectGeneration 回收,而且從上面的 ecx 寄存器看依次是 2,0,1,對應著 gc 的2代回收,0代回收, 1代回收, 這比只有一個靜態的 dump 是不是有優勢的多,要知道我這裡的 ConsoleApp101.run 文件是可以分發給別人分析的哦。

2. 查看新生成的線程曾今都執行了什麼代碼

這個例子源自朋友遇到的一個問題,他的程式跑著跑著,發現 ThreadPool 中有400多待命的工作線程,線程棧大概如下:


0:011> k
 # ChildEBP RetAddr      
00 0564fc6c 7531f0ca     ntdll!NtRemoveIoCompletion+0xc
01 0564fc6c 78480b69     KERNELBASE!GetQueuedCompletionStatus+0x2a
02 0564fcb8 7847d92b     coreclr!CLRLifoSemaphore::WaitForSignal+0x29 [d:\a\_work\1\s\src\vm\synch.cpp @ 654] 
03 0564fd08 7847cf04     coreclr!CLRLifoSemaphore::Wait+0x13b [d:\a\_work\1\s\src\vm\synch.cpp @ 897] 
04 0564fdd4 783f2910     coreclr!ThreadpoolMgr::WorkerThreadStart+0x234 [d:\a\_work\1\s\src\vm\win32threadpool.cpp @ 2121] 
05 0564ff70 7703fa29     coreclr!Thread::intermediateThreadProc+0x50 [d:\a\_work\1\s\src\vm\threads.cpp @ 2110] 
06 0564ff80 772175f4     KERNEL32!BaseThreadInitThunk+0x19
07 0564ffdc 772175c4     ntdll!__RtlUserThreadStart+0x2f
08 0564ffec 00000000     ntdll!_RtlUserThreadStart+0x1b

因為給我的是 靜態dump,所以我無法尋找 11號線程 曾今執行了什麼托管代碼,因為時間不能倒流,但現在有了 TTD,真的可以讓時間倒流啦。。。太有意思了,哈哈,既然能倒流,那就一定有辦法找到破綻。

為了方便講解,寫一個簡單例子。


        static void Main(string[] args)
        {
            Task.Factory.StartNew(() =>
            {
                Console.WriteLine("我是 task 線程");
            });

            Console.ReadLine();
        }

接下來我們一起探究下最後生成的 Thread WorkThread 曾今都執行了什麼? 深挖思路大概是這樣的。

先將進度條拉到底,然後用 !bpmd System_Private_CoreLib System.Threading.Tasks.Task.InnerInvoke 下一個斷點,最後將時間倒流,看誰命中了這個 task。


0:000> g
TTD: End of trace reached.
(4f20.4d0c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: 36F51:0
eax=00070053 ebx=00000000 ecx=8a60f857 edx=77237170 esi=7845e6c0 edi=00000000
eip=771a7000 esp=0602fe90 ebp=0602ff70 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
771a7000 ea09701a773300  jmp     0033:771A7009

0:009> !bpmd System_Private_CoreLib System.Threading.Tasks.Task.InnerInvoke
MethodDesc = 06A29704
Setting breakpoint: bp 05A915C7 [System.Threading.Tasks.Task.InnerInvoke()]
Adding pending breakpoints...

0:009> g-
Breakpoint 1 hit
Time Travel Position: 32DF4:AC
eax=05a915c0 ebx=00000000 ecx=0349a864 edx=0349a864 esi=0349a864 edi=0349a7c8
eip=05a915c7 esp=066afa14 ebp=066afa1c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
System_Private_CoreLib!System.Threading.Tasks.Task.InnerInvoke()$##6002185+0x7:
05a915c7 8b7e04          mov     edi,dword ptr [esi+4] ds:002b:0349a868=0349a800

0:008> !clrstack 
OS Thread Id: 0x44a8 (8)
Child SP       IP Call Site
066AFA14 05a915c7 System.Threading.Tasks.Task.InnerInvoke() [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2437]
066AFA24 05a915bb System.Threading.Tasks.Task+c.<.cctor>b__274_0(System.Object) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2427]
066AFA2C 05a91567 System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread, System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 289]
066AFA5C 05a912d1 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2389]
066AFABC 05a911d7 
066AFACC 05a9118b System.Threading.Tasks.Task.ExecuteFromThreadPool(System.Threading.Thread) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2312]
066AFAD0 05a90e58 System.Threading.ThreadPoolWorkQueue.Dispatch() [/_/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs @ 663]
066AFB1C 05a90c6f 
066AFD10 784fa0ef [DebuggerU2MCatchHandlerFrame: 066afd10] 

熟悉 Task 的朋友應該知道: System.Threading.Tasks.Task.InnerInvoke 的下一步就是執行我的回調函數,而此時 回調函數 還沒有被 JIT 編譯,這時候我們可以在 bp clrjit!CILJit::compileMethod 中去攔截 JIT 對此方法的編譯,然後從 compileMethod 中提取 mt


0:008> bp clrjit!CILJit::compileMethod
0:008> g
Breakpoint 1 hit
Time Travel Position: 32E36:C18
eax=7933ad50 ebx=066af5c8 ecx=792c8770 edx=066af5c8 esi=7932d164 edi=00cbbf90
eip=792c8770 esp=066af3ec ebp=066af44c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
clrjit!CILJit::compileMethod:
792c8770 55              push    ebp

0:008> kb
 # ChildEBP RetAddr      Args to Child              
00 066af44c 78428db2     7933ad50 066af5c8 066af4f0 clrjit!CILJit::compileMethod [d:\a\_work\1\s\src\jit\ee_il_dll.cpp @ 294] 
...

0:008> dp 066af4f0 L1
066af4f0  06a2ae04

0:008> !dumpmd 06a2ae04
Method Name:          ConsoleApp1.dll!Unknown
Class:                032fa0dc
MethodTable:          06a2ae14
mdToken:              06000005
Module:               02c5d7d0
IsJitted:             no
Current CodeAddr:     ffffffff
Version History:
  ILCodeVersion:      00000000
  ReJIT ID:           0
  IL Addr:            00000000
     CodeAddr:           00000000  (MinOptJitted)
     NativeCodeVersion:  00000000

很奇怪的是提取的 md 目前還不能顯示完全名字,不過沒關係,我們繼續 g ,然後再重覆執行一下命令。


0:009> g
TTD: End of trace reached.
(4f20.4d0c): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: 36F51:0
eax=00070053 ebx=00000000 ecx=8a60f857 edx=77237170 esi=7845e6c0 edi=00000000
eip=771a7000 esp=0602fe90 ebp=0602ff70 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
771a7000 ea09701a773300  jmp     0033:771A7009
0:009> !dumpmd 06a2ae04
Method Name:          ConsoleApp1.Program+<>c.<Main>b__0_0()
Class:                032fa0dc
MethodTable:          06a2ae14
mdToken:              06000005
Module:               02c5d7d0
IsJitted:             yes
Current CodeAddr:     06133300
Version History:
  ILCodeVersion:      00000000
  ReJIT ID:           0
  IL Addr:            00000000
     CodeAddr:           06133300  (MinOptJitted)
     NativeCodeVersion:  00000000

當時間線結束的時候,我們終於看到了,原來 Task 執行的是 ConsoleApp1.Program+<>c.<Main>b__0_0() 方法,那這個方法邏輯是什麼呢? 可以用 ILSpy 查看。

總的來說,要復現還是挺考驗基本功的。

圖片名稱

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • day1 Spring IOC 和 AOP 為內核 IOC inverse of control 控制反轉 AOP aspect oriented programing 面向切麵編程 展現層 WEB SpringMVC 持久層 DAO Spring JDBCTemplate 業務層 SERIVICE ...
  • 背景 在測試環境上遇到一個詭異的問題,部分業務邏輯會記錄用戶ID到資料庫,但記錄的數據會串,比如當前用戶的操作記錄會被其他用戶覆蓋, 而且這個現象是每次重啟後一小段時間內就正常 問題 線上程池內部使用了InheritableThreadLocal獲取用戶信息由於沒有及時remove,線程復用後,拿到 ...
  • “整篇文章較長,乾貨很多!建議收藏後,分章節閱讀。” 一、設計方案 整體設計方案思維導圖: 整篇文章,也將按照這個結構來講解。 若有重點關註部分,可點擊章節目錄直接跳轉! 二、項目背景 針對TOP250排行榜的數據,開發一套可視化數據大屏系統,展示各維度數據分析結果。 TOP250排行榜 三、電影爬 ...
  • OAuth2客戶端的配置參數非常多,雖然Id Server通過控制台可視化解決了創建OAuth2客戶端的問題。但是如何進一步降低OAuth2的使用難度,把創建的OAuth2客戶端轉化為配置成為了剛需,從技術角度上感覺也並不是很難實現。 我們先來看看效果,點擊配置生成按鈕即可直接生成Spring Se ...
  • 轉載:https://www.bilibili.com/video/BV1VQ4y1P7Fq?spm_id_from=333.1007.top_right_bar_window_history.content.click 概念 Stream是Java8 API的新成員,它允許以聲明性方式處理數據集合 ...
  • 嗨害嗨,作業來嘍 背包問題 01背包和完全背包問題都是一個背景下的:我有一個容量為M的背包,現在地上有N個物品,我跟個小偷似的眼裡只有i個物品的價值vi和重量wi,現在我要做的就是為了偷的東西更值錢拿走一些東西,使它們的價值是所有方案里最大的 01背包 背景如上,01背包就是我眼前的這些東西都是孤品 ...
  • 超鏈接(Hyperlink)可以看做是一個“熱點”,它可以從當前Web頁定義的位置跳轉到其他位置,包括當前頁的某個位置、Internet、本地硬碟或區域網上的其他文件,甚至跳轉到聲音、圖片等多媒體文件。瀏覽Web頁是超鏈接最普遍的一種應用,通過超鏈接還可以獲得不同形態的服務,如文件傳輸、資料查詢、電 ...
  • 1. 效果展示 先來直接欣賞效果: 2. 準備 創建一個WPF工程,比如站長使用 .NET 7 創建名為 Dashboard3 的WPF項目,添加一些圖片資源,項目目錄如下: 2.1 圖片資源 可在網站 iconfont 下載 關閉、最小化 圖標,用於視窗右上角顯示: 有看到美女圖片沒?在百度圖片或 ...
一周排行
    -Advertisement-
    Play Games
  • 分組和樹形結構是不一樣的。 樹形結構是以遞歸形式存在。分組是以鍵值對存在的形式,類似於GroupBy這樣的形式。 舉個例子 ID NAME SEX Class 1 張三 男 1 2 李四 女 2 3 王二 男 1 當以Sex為分組依據時則是 Key Value 男 1 張三 男 1 3 王二 男 1 ...
  • NetCore中將SQLServer資料庫備份為Sql腳本 描述: 最近寫項目收到了一個需求, 就是將SQL Server資料庫備份為Sql腳本, 如果是My Sql之類的還好說, 但是在網上搜了一大堆, 全是教你怎麼操作SSMS的, 就很d疼! 解決方案: 通過各種查找資料, 還有一些老哥的幫助, ...
  • 我的Notion Clowd.Squirrel Squirrel.Windows 是一組工具和適用於.Net的庫,用於管理 Desktop Windows 應用程式的安裝和更新。 Squirrel.Windows 對 Windows 應用程式的實現語言沒有任何要求,甚至無需服務端即可完成增量更新。 ...
  • 轉載請註明來源 https://www.cnblogs.com/brucejiao/p/16188865.html 謝謝! 轉載請註明來源 https://www.cnblogs.com/brucejiao/p/16188865.html 謝謝! 轉載請註明來源 https://www.cnblog ...
  • 1. Netty源碼研究筆記(3)——Channel系列 依舊是通過先縱向再橫向的研究方法,在開篇中,我們發現不管是Sever還是Client,最終的啟動是通過調用channel的對應方法來完成的,而這個動作實際在channel綁定的eventLoop中執行。 接下來,我們繼續EchoSever、E ...
  • 大家好,今天給大家介紹一款輕量、快速、穩定可編排的組件式規則引擎框架LiteFlow。 一、LiteFlow的介紹 LiteFlow官方網站和代碼倉庫地址 官方網站:https://yomahub.com/liteflow Gitee托管倉庫:https://gitee.com/dromara/li ...
  • 我使用Spring AOP實現了用戶操作日誌功能 今天答辯完了,復盤了一下系統,發現還是有一些東西值得拿出來和大家分享一下。 需求分析 系統需要對用戶的操作進行記錄,方便未來溯源 首先想到的就是在每個方法中,去實現記錄的邏輯,但是這樣做肯定是不現實的,首先工作量大,其次違背了軟體工程設計原則(開閉原 ...
  • 《零基礎學Java》 繪製幾何圖形 Java可以分別使用 Graphics 和 Graphics2D 繪製圖形,Graphics類 使用不同的方法繪製不同的圖形(drawLine()方法可f以繪製線、drawRect()方法用於繪製矩形、drawOval()方法用於繪製橢圓形)。 Graphics類 ...
  • 本期教程人臉識別第三方平臺為虹軟科技,本文章講解的是人臉識別RGB活體追蹤技術,免費的功能很多可以自行搭配,希望在你看完本章課程有所收穫。 ...
  • 很多人都喜歡使用黑色的主題樣式,包括我自己,使用了差不多三年的黑色主題,但是個人覺得在進行視窗轉換的時候很廢眼睛。 比如IDEA是全黑的,然後需要看PDF或者WORD又變成白色的了,這樣來回切換導致眼睛很累,畢竟現在網頁以及大部分軟體的界面都是白色的。那麼還是老老實實的使用原來比較順眼的模式吧。 1 ...