WinDbg - 時程表
時間移動偵錯 (TTD) 可讓使用者記錄追蹤,這是程式執行的記錄。 時間軸是執行期間發生的事件的可視化表示法。 這些事件可以是的位置:斷點、記憶體讀取/寫入、函數調用和傳回,以及例外狀況。
使用時間軸視窗快速檢視重要事件、了解相對位置,並輕鬆地跳到TTD追蹤檔案中的位置。 使用多個時間軸,以可視化方式探索時間移動追蹤中的事件,並探索事件相互關聯。
開啟 TTD 追蹤檔案並顯示重要事件時,就會顯示時間軸視窗,而不需要手動建立數據模型查詢。 同時,所有時間的移動物件都可供使用,以允許更複雜的數據查詢。
如需建立和使用時間移動追蹤檔案的詳細資訊,請參閱 時間移動偵錯 – 概觀。
時程表的類型
時間軸視窗可以顯示下列事件:
- 例外狀況 (您可以進一步篩選特定例外狀況代碼)
- 中斷點
- 函數調用(以模組形式搜尋!function)
- 記憶體存取權(讀取/寫入/在兩個記憶體位址之間執行)
將滑鼠停留在每個事件上,以透過工具提示取得詳細資訊。 按兩下事件將會執行事件的查詢,並顯示詳細資訊。 按兩下事件將會跳到 TTD 追蹤檔案中的該位置。
例外狀況
當您載入追蹤檔案且時間軸為使用中時,它會自動在錄製中顯示任何例外狀況。
當您將滑鼠停留在斷點資訊上時,會顯示例外狀況類型和例外狀況程序代碼。
您可以使用選擇性例外狀況代碼欄位進一步篩選特定例外狀況代碼。
您也可以為特定例外狀況類型新增時程表。
中斷點
新增斷點之後,您可以在時間軸上顯示該斷點時的位置。 這可以完成,例如使用 bp Set Breakpoint 命令。 當您將滑鼠停留在斷點上方時,會顯示與斷點相關聯的位址和指令指標。
清除斷點時,會自動移除相關聯的斷點時間軸。
函式呼叫
您可以在時間軸上顯示函數調用的位置。 若要這樣做,請以 的形式 module!function
提供搜尋,例如 TimelineTestCode!multiplyTwo
。 您也可以指定通配符,例如 TimelineTestCode!m*
。
當您將滑鼠停留在函式上時,會呼叫函式名稱、輸入參數、其值,以及傳回值會顯示。 此範例會顯示 緩衝區 和 大小 ,因為這些是 DisplayGreeting 的參數!GetCppConGreeting。
記憶體存取
使用記憶體存取時程表,在讀取或寫入特定記憶體範圍,或執行程式代碼的位置時顯示。 啟動和停止位址可用來定義兩個記憶體位址之間的範圍。
當您將滑鼠停留在記憶體存取專案上時,會顯示值和指令指標。
使用時程表
停留在時間軸上時,垂直灰色線條會跟隨游標。 垂直藍線表示追蹤中的目前位置。
按兩下放大鏡圖示,放大和縮小時程表。
在頂端時程表控件區域中,使用矩形來平移時間軸的檢視。 拖曳矩形的外部分隔符,以調整目前時間軸檢視的大小。
滑鼠移動
使用 Ctrl + 滾動輪放大縮小字體功能。
使用 Shift + 滾動輪從側向側移動流覽。
時間軸偵錯技術
為了示範偵錯時間軸技術, 這裡會重複使用時間移動偵錯逐步 解說。 此示範假設您已完成建置範例程式代碼的前兩個步驟,並使用該處所述的前兩個步驟建立 TTD 錄製。
在此案例中,第一個步驟是在時間移動追蹤中尋找例外狀況。 您可以按兩下時間軸上唯一的例外狀況來完成。
查看命令視窗時,我們看到在單擊例外狀況時發出下列命令。
(2dcc.6600): Break instruction exception - code 80000003 (first/second chance not available)
Time Travel Position: CC:0
@$curprocess.TTD.Events.Where(t => t.Type == "Exception")[0x0].Position.SeekTo()
選取 [檢視>>緩存器],即可在時間軸的此時顯示緩存器,以開始我們的調查。
在命令輸出中,請注意堆疊 (esp) 和基底指標 (ebp) 指向兩個非常不同的位址。 這可能表示堆疊損毀 - 可能是傳回的函式,然後損毀堆疊。 若要驗證這一點,我們必須回到CPU狀態損毀之前,查看是否可以判斷堆疊損毀何時發生。
如此一來,我們將檢查局部變數和堆疊的值。
選取 [檢視>>局部變數] 以顯示本機值。
選取 [檢視>>堆疊] 以顯示程式代碼執行堆疊。
在追蹤失敗時,通常會在錯誤處理程式碼的真正原因之後,最後執行幾個步驟。 隨著時間移動,我們可以一次返回指示,找出真正的根本原因。
從 [ 首頁] 功能區使用 [跳入返回 ] 命令來回退三個指示。 如此一來,請繼續檢查堆疊、局部變數和註冊視窗。
命令視窗會在您退後三個指示時顯示時間移動位置與緩存器。
0:000> t-
Time Travel Position: CB:41
eax=00000000 ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=00540020 esp=003cf7d0 ebp=00520055 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
00540020 ?? ???
0:000> t-
Time Travel Position: CB:40
eax=00000000 ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=00061767 esp=003cf7cc ebp=00520055 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
DisplayGreeting!main+0x57:
00061767 c3 ret
0:000> t-
Time Travel Position: CB:3A
eax=0000004c ebx=00564000 ecx=c0d21d62 edx=7a1e4a6c esi=00061299 edi=00061299
eip=0006175f esp=003cf718 ebp=003cf7c8 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
DisplayGreeting!main+0x4f:
0006175f 33c0 xor eax,eax
此時,追蹤我們的堆疊和基底指標具有更有意義的值,因此,我們似乎更接近發生損毀的程序代碼中的點。
esp=003cf718 ebp=003cf7c8
同樣感興趣的是,局部變數視窗包含來自目標應用程式的值,而原始程式碼視窗正在醒目提示已準備好在追蹤的此時在我們的原始程式碼中執行的程式碼行。
若要進一步調查,我們可以開啟記憶體視窗,以檢視堆疊指標 (esp) 記憶體位址附近的內容。 在此範例中,其值為 003cf7c8。 選取 [記憶體>>文字>>ASCII ] 以顯示儲存在該位址的 ASCII 文字。
記憶體存取時程表
識別感興趣的記憶體位置之後,請使用該值新增記憶體取時程表。 按兩下 [ + 新增時程表 ],然後填入起始位址。 我們將查看 4 個字節,因此請將該位址新增至 003cf7c8 的起始地址,我們有 003cf7cb。 預設值是查看所有記憶體寫入,但您也可以查看只在該位址的寫入或程式代碼執行。
我們現在可以反向周遊時間軸,以檢查此時移動追蹤此記憶體位置的寫入時間點,以查看我們可以找到的內容。 按兩下時間軸中的這個位置,我們會看到局部變數值所複製字串的不同值。 目的地值似乎不完整,就好像字串的長度不正確。
斷點時程表
使用斷點是一種常見方法,會在感興趣的某個事件暫停程式代碼執行。 TTD 可讓您設定斷點,並及時返回,直到記錄追蹤之後叫用該斷點為止。 在發生問題之後檢查進程狀態的能力,若要判斷斷點的最佳位置,可啟用TTD特有的其他偵錯工作流程。
若要探索替代的時間軸偵錯技術,請按兩下時間軸中的例外狀況,然後使用 [首頁] 功能區上的 [跳入返回] 命令,再次往回移動三個步驟。
在這個非常小的範例中,只要查看程式代碼就很容易,但如果有數百行程式代碼和數十個子程式,這裡所述的技術可以用來減少找出問題所需的時間。
如先前所述,基底指標 (esp) 而不是指向指令,而是指向我們的消息正文。
使用ba命令在記憶體存取上設定斷點。 我們將設定 w - 寫入斷點,以查看寫入記憶體區域何時寫入。
0:000> ba w4 003cf7c8
雖然我們將使用簡單的記憶體存取斷點,但斷點可以建構為更複雜的條件語句。 如需詳細資訊,請參閱 bp、bu、bm(設定斷點)。
從 [首頁] 功能表中,選取 [返回 ] 以及時返回,直到叫用斷點為止。
此時,我們可以檢查程式堆疊,以查看哪些程序代碼作用中。
由於 Microsoft 提供的 wscpy_s() 函式不太可能有類似這樣的程式代碼錯誤,因此我們會在堆疊中進一步查看。 堆疊會顯示 Greeting!main 呼叫 Greeting!GetCppConGreeting。 在我們的非常小型的程式代碼範例中,我們此時只能開啟程序代碼,並很容易發現錯誤。 但是為了說明可與較大型、更複雜的程式搭配使用的技術,我們將設定新增函數調用時程表。
函數調用時程表
點選單擊 [ + 新增時程表 ],然後填入 DisplayGreeting!GetCppConGreeting
函式搜尋字串的 。
[開始] 和 [結束位置] 複選框表示追蹤中函式呼叫的開始和結束。
我們可以使用 dx 命令來顯示函數調用物件,以查看與函數調用的 [開始位置] 和 [結束位置] 對應的相關聯 TimeStart 和 TimeEnd 欄位。
dx @$cursession.TTD.Calls("DisplayGreeting!GetCppConGreeting")[0x0]
EventType : 0x0
ThreadId : 0x6600
UniqueThreadId : 0x2
TimeStart : 6D:BD [Time Travel]
SystemTimeStart : Thursday, October 31, 2019 23:36:05
TimeEnd : 6D:742 [Time Travel]
SystemTimeEnd : Thursday, October 31, 2019 23:36:05
Function : DisplayGreeting!GetCppConGreeting
FunctionAddress : 0x615a0
ReturnAddress : 0x61746
Parameters
必須核取 [開始] 或 [結束] 或 [開始] 和 [結束位置] 方塊。
由於我們的程式代碼既不是遞歸或重新進入,所以在呼叫 GetCppConGreeting 方法時,在時間行上很容易找到。 GetCppConGreeting 的呼叫也會與斷點以及我們定義的記憶體存取事件同時發生。 因此,我們似乎已縮小程式碼區域,仔細查看應用程式當機的根本原因。
檢視多個時程表來探索程式代碼執行
雖然我們的程式代碼範例很小,但使用多個時間軸的技術可讓時間移動追蹤進行視覺探索。 您可以查看追蹤檔案以詢問問題,例如「何時在叫用斷點之前存取記憶體區域?」。
查看其他相互關聯並尋找您可能未預期的事情的能力,會區分時間軸工具,使用命令行命令與時間移動追蹤互動。
時間軸書籤
將WinDbg 中重要的 Time Travel 位置加上書籤,而不是手動將位置貼到記事本。 書籤可讓您更輕鬆地檢視追蹤中相對於其他事件的不同位置,並標註它們。
您可以提供書籤的描述性名稱。
透過 [檢視時程>表] 中提供的 [時程表] 視窗存取書籤。 當您將滑鼠停留在書籤上方時,它會顯示書簽名稱。
您可以以滑鼠右鍵按下書籤,以移至該位置、重新命名或刪除書籤。
注意
在調試程式的 1.2402.24001.0 版中,無法使用書籤功能。