時間移動偵錯 - 範例應用程式逐步解說

具有時鐘的時間移動偵錯標誌。

此實驗室引進時間移動偵錯 (TTD),使用具有程式代碼缺陷的小型範例程式。 TTD 可用來對問題進行偵錯、識別和根本原因。 雖然這個小型程式中的問題很容易找到,但一般程式可用於更複雜的程序代碼。 此一般程式摘要如下。

  1. 擷取失敗程式的時間移動追蹤。
  2. 使用 dx (顯示除錯程式物件模型表示式) 命令來尋找儲存在錄製中的例外狀況事件。
  3. 使用 !tt (時間移動) 命令,前往追蹤中例外狀況事件的位置。
  4. 從追蹤單一步驟的該點往回移動,直到有問題的錯誤程式代碼進入範圍為止。
  5. 在範圍內的錯誤程序代碼中,查看局部值,並開發變數的假設,該變數可能包含不正確的值。
  6. 使用不正確的值來判斷變數的記憶體位址。
  7. 使用 ba (Break on Access) 命令,在可疑變數的地址上設定記憶體存取 (ba 斷點。
  8. 使用 g- 執行回可疑變數的最後一個記憶體存取點。
  9. 查看該位置,或之前幾個指示是否為程式代碼瑕疵點。 如果是這樣,您就會完成。 如果不正確的值來自其他變數,請在第二個變數的存取斷點上設定另一個斷點。
  10. 使用 g- 回到第二個可疑變數上的最後一個記憶體存取點。 查看該位置或先前的幾個指示是否包含程式碼瑕疵。 如果是這樣,您就會完成。
  11. 重複此程式,直到設定造成錯誤之不正確值的程式代碼找到為止。

雖然此程式中所述的一般技術適用於一組廣泛的程式代碼問題,但有需要唯一方法的唯一程式碼問題。 逐步解說中說明的技術應該可擴充您的偵錯工具集,並說明 TTD 追蹤的一些可能。

實驗室目標

完成此實驗室之後,您將能夠使用一般程式與時間移動追蹤來找出程式代碼中的問題。

實驗室設定

您需要下列硬體才能完成實驗室。

  • 執行 Windows 10 或 Windows 11 的膝上型電腦或桌面電腦(主機)

您需要下列軟體才能完成實驗室。

  • WinDbg。 如需安裝 WinDbg 的詳細資訊,請參閱 WinDbg - 安裝
  • Visual Studio 建置範例 C++ 程序代碼。

實驗室有下列三個區段。

第1節:建置範例程序代碼

在第 1 節中,您將使用 Visual Studio 建置範例程式代碼。

在 Visual Studio 中建立範例應用程式

  1. 在 Microsoft Visual Studio 中,按兩下 [檔案>>專案/方案...],然後按兩下 Visual C++ 範本。

    選取 Win32 控制台應用程式。

    提供 DisplayGreeting 的專案名稱,然後按兩下 [確定]。

  2. 取消核取安全性開發生命週期 (SDL) 檢查。

    Visual Studio 中的 Win32 應用程式精靈設定。

  3. 按兩下 [ 完成]。

  4. 將下列文字貼到 Visual Studio 中的 [DisplayGreeting.cpp] 窗格。

    // DisplayGreeting.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include <array>
    #include <stdio.h>
    #include <string.h>
    
    void GetCppConGreeting(wchar_t* buffer, size_t size)
    {
        wchar_t const* const message = L"HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!";
    
        wcscpy_s(buffer, size, message);
    }
    
    int main()
    {
         std::array <wchar_t, 50> greeting{};
         GetCppConGreeting(greeting.data(), sizeof(greeting));
    
         wprintf(L"%ls\n", greeting.data());
    
         return 0;
    }
    
  5. 在 Visual Studio 中,按兩下 [項目>顯示][Greeting 屬性]。 然後按兩下 C/C++ 和程式代碼產生

    設定下列屬性。

    設定
    安全性檢查 停用安全性檢查 (/GS-)
    基本執行階段檢查 預設

    注意

    雖然不建議使用這些設定,但可以想像有人會建議使用這些設定來加速編碼或協助某些測試環境的情況。

  6. 在 Visual Studio 中,按兩下 [建置>建置方案]。

    如果一切順利,組建視窗應該會顯示訊息,指出組建成功。

  7. 找出建置的範例應用程式檔案

    在 方案總管 中,以滑鼠右鍵按兩下 DisplayGreeting 項目,然後選取 [檔案總管中的開啟資料夾]。

    流覽至包含範例所符合 exe 和符號 pdb 檔案的 Debug 資料夾。 例如,如果這是專案儲存在的資料夾,您可以流覽至 C:\Projects\DisplayGreeting\Debug

  8. 使用程式代碼瑕疵執行範例應用程式

    按兩下 exe 檔案以執行範例應用程式。

    執行DisplayGreeting.exe檔案的控制台螢幕快照。

    如果出現此對話框,請選擇 [ 關閉程式]

    顯示 [DisplayGreeting.exe已停止運作] 對話框的螢幕快照。

    在逐步解說的下一節中,我們將記錄範例應用程式的執行,以查看是否可以判斷發生此例外狀況的原因。

第2節:記錄「DisplayGreeting」 範例的追蹤

在第 2 節中,您將記錄範例 「DisplayGreeting」 應用程式的追蹤

若要啟動範例應用程式並記錄TTD追蹤,請遵循下列步驟。 如需錄製 TTD 追蹤的一般資訊,請參閱 時間移動偵錯 - 記錄追蹤

  1. 以 管理員 istrator 的形式執行 WinDbg,以便能夠記錄時間移動追蹤。

  2. 在 WinDbg 中,選取 [檔案>開始偵錯>啟動可執行檔] [進階]。

  3. 輸入您想要記錄的使用者模式可執行檔案路徑,或選取 [瀏覽 ] 以瀏覽至可執行檔。 如需在 WinDbg 中使用啟動可執行文件功能表的相關信息,請參閱 WinDbg - 啟動使用者模式會話

    WinDbg 的螢幕快照,其中顯示 [啟動可執行檔](進階) 畫面中的 [具有時間旅行偵錯的記錄] 複選框。

  4. 核取 [ 具有時間移動偵 錯的記錄] 方塊,以在啟動可執行檔時記錄追蹤。

  5. 按兩下 [設定] 和 [ 錄製 ] 以開始錄製。

  6. 出現 [設定錄製] 對話框時,按兩下 [錄製] 以啟動可執行檔並開始錄製。

    WinDbg 的螢幕快照,其中顯示 [設定錄製] 對話框,並將路徑設定為temp。

  7. 錄製對話框隨即出現,表示正在記錄追蹤。 不久之後,應用程式就會當機。

  8. 按兩下 [ 關閉程式],關閉 [DisplayGreeting 已停止運作] 對話框。

    顯示 DisplayGreeting 應用程式的對話框已停止運作。

  9. 當程式當機時,追蹤檔案將會關閉並寫出至磁碟。

    WinDbg 輸出的螢幕快照,其中顯示已編製索引的 1/1 主要畫面格。

  10. 調試程式會自動開啟追蹤檔案並編製索引。 索引編製是一個程式,可讓追蹤檔案進行有效率的偵錯。 對於較大的追蹤檔案,此索引編製程式需要較長的時間。

    (5120.2540): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: D:0 [Unindexed] Index
    !index
    Indexed 10/22 keyframes
    Indexed 20/22 keyframes
    Indexed 22/22 keyframes
    Successfully created the index in 755ms.
    

注意

主要畫面格是用於編製索引的追蹤位置。 主要畫面格會自動產生。 較大的追蹤將包含更多主要畫面格。

  1. 此時,您會在追蹤檔案的開頭,並準備好及時向前和向後移動。

    現在您已記錄 TTD 追蹤,您可以重新執行追蹤回溯或處理追蹤檔案,例如與同事共用。 如需使用追蹤檔案的詳細資訊,請參閱 時間移動偵錯 - 使用追蹤檔案

在此實驗室的下一節中,我們將分析追蹤檔案,以找出程式代碼的問題。

第3節:分析追蹤檔案錄製以識別程式代碼問題

在第 3 節中,您將分析追蹤檔案錄製,以識別程式代碼問題。

設定 WinDbg 環境

  1. 輸入下列命令,將本機符號位置新增至符號路徑並重載符號。

    .sympath+ C:\MyProjects\DisplayGreeting\Debug
    .reload
    
  2. 輸入下列命令,將您的本機程式代碼位置新增至來源路徑。

    .srcpath+ C:\MyProjects\DisplayGreeting\DisplayGreeting
    
  3. 若要能夠檢視堆疊和局部變數的狀態,請在 WinDbg 功能區上選取 [檢視] 和 [局部變數],然後選取 [檢視] 和 [檢視和 [檢視堆棧]。 組織視窗可讓您同時檢視它們、原始程式碼和命令視窗。

  4. 在 WinDbg 功能區上,選取 [來源] 和 [開放原始碼檔案]。 找出DisplayGreeting.cpp檔案並加以開啟。

檢查例外狀況

  1. 載入追蹤檔案時,會顯示發生例外狀況的資訊。

    2fa8.1fdc): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68ef8100 ebx=00000000 ecx=77a266ac edx=69614afc esi=6961137c edi=004da000
    eip=77a266ac esp=0023f9b4 ebp=0023fc04 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:0023fac0=00000000
    
  2. 使用 dx 命令來列出錄製中的所有事件。 例外狀況事件會列在 事件中。

    0:000> dx -r1 @$curprocess.TTD.Events
    ...
    [0x2c]           : Module Loaded at position: 9967:0
    [0x2d]           : Exception at 9BDC:0
    [0x2e]           : Thread terminated at 9C43:0
    ...
    
    

    注意

    在此逐步解說中,會使用三個期間來指出已移除多餘的輸出。

  3. 按兩下 [例外狀況] 事件以顯示該 TTD 事件的相關信息。

    0:000> dx -r1 @$curprocess.TTD.Events[17]
    @$curprocess.TTD.Events[17]                 : Exception at 68:0
        Type             : Exception
        Position         : 68:0 [Time Travel]
        Exception        : Exception of type Hardware at PC: 0X540020
    
  4. 按兩下 [例外狀況] 字段,進一步向下切入例外狀況數據。

    0:000> dx -r1 @$curprocess.TTD.Events[17].Exception
    @$curprocess.TTD.Events[17].Exception                 : Exception of type Hardware at PC: 0X540020
        Position         : 68:0 [Time Travel]
        Type             : Hardware
        ProgramCounter   : 0x540020
        Code             : 0xc0000005
        Flags            : 0x0
        RecordAddress    : 0x0
    

    例外狀況數據表示這是CPU擲回的硬體錯誤。 它也提供0xc0000005的例外狀況代碼,指出這是存取違規。 這通常表示我們嘗試寫入無法存取的記憶體。

  5. 按兩下例外狀況事件中的 [Time Travel] 連結,以移至追蹤中的該位置。

    0:000> dx @$curprocess.TTD.Events[17].Exception.Position.SeekTo()
    Setting position: 68:0
    
    @$curprocess.TTD.Events[17].Exception.Position.SeekTo()
    (16c8.1f28): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 68:0
    eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=00540020 esp=00effe4c 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 ??
    

    在此輸出中請注意,堆疊和基底指標指向兩個非常不同的位址。

    esp=00effe4c ebp=00520055
    

    這可能表示堆疊損毀 - 可能是傳回的函式,然後損毀堆疊。 若要驗證這一點,我們必須回到CPU狀態損毀之前,查看是否可以判斷堆疊損毀何時發生。

檢查局部變數並設定程式代碼斷點

在追蹤失敗時,通常會在錯誤處理程式碼的真正原因之後,最後執行幾個步驟。 隨著時間移動,我們可以一次返回指示,找出真正的根本原因。

  1. 從 [ 首頁] 功能區使用 [跳入返回 ] 命令來回退三個指示。 如此一來,請繼續檢查堆疊和記憶體視窗。

    命令視窗會在您退後三個指示時顯示時間移動位置與緩存器。

    0:000> t-
    Time Travel Position: 67:40
    eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=00540020 esp=00effe4c 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: 67:3F
    eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=0019193d esp=00effe48 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+0x4d:
    0019193d c3
    
    0:000> t-
    Time Travel Position: 67:39
    eax=0000004c ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046
    eip=00191935 esp=00effd94 ebp=00effe44 iopl=0         nv up ei pl nz ac po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
    DisplayGreeting!main+0x45:
    

    注意

    在此逐步解說中,命令輸出會顯示可用來取代 UI 功能表選項的命令,以允許使用者使用命令行使用喜好設定來使用命令行命令。

  2. 此時,追蹤我們的堆疊和基底指標具有更有意義的值,因此,我們似乎更接近發生損毀的程序代碼中的點。

    esp=00effd94 ebp=00effe44
    

    同樣感興趣的是,局部變數視窗包含來自目標應用程式的值,而原始程式碼視窗會反白顯示準備在追蹤中此時執行的程式碼行。

    WinDbg 的螢幕快照,其中顯示 [局部變數] 視窗與記憶體 ASCII 輸出和 [原始程式碼] 視窗。

  3. 若要進一步調查,我們可以開啟記憶體視窗,以檢視0x00effe44基底指標記憶體位址附近的內容。

  4. 若要顯示相關聯的 ASCII 字元,請從 [記憶體] 功能區選取 [文字],然後選取 [ASCII]。

    WinDbg 預覽版的螢幕快照,其中顯示記憶體 ASCII 輸出和原始碼視窗。

  5. 而不是指向指示的基底指標,而是指向我們的消息正文。 因此,這裏的東西不是正確的,這可能接近我們損毀堆疊的時間點。 為了進一步調查,我們將設定斷點。

注意

在這個非常小的範例中,只要查看程式代碼就很容易,但如果有數百行程式代碼和數十個子程式,這裡所述的技術可以用來減少找出問題所需的時間。

TTD 和斷點

使用斷點是一種常見方法,會在感興趣的某個事件暫停程式代碼執行。 TTD 可讓您設定斷點,並及時返回,直到記錄追蹤之後叫用該斷點為止。 在發生問題之後檢查進程狀態的能力,若要判斷斷點的最佳位置,可啟用TTD特有的其他偵錯工作流程。

記憶體存取斷點

您可以設定在存取記憶體位置時引發的斷點。 使用ba (break on access) 命令,並搭配下列語法。

ba <access> <size> <address> {options}
選項 描述

e

execute (當 CPU 從位址擷取指令時)

r

讀取/寫入(當 CPU 讀取或寫入位址時)

w

write (當 CPU 寫入位址時)

請注意,您只能在任何指定時間設定四個數據斷點,而且必須確定您正確對齊數據,否則不會觸發斷點(單字必須以 2 分隔的地址結束,dword 必須以 4 來分隔,而四字則以 0 或 8 為單位)。

設定基底指標的記憶體存取斷點中斷

  1. 此時,我們想要在追蹤中設定基底指標的寫入記憶體存取斷點 - ebp,在我們的範例中為 00effe44。 若要這樣做,請使用ba命令,並使用我們想要監視的位址。 我們想要監視四個字節的寫入,因此我們會指定 w4。

    0:000> ba w4 00effe44
    
  2. 選取 [檢視],然後選取 [斷點] 以確認它們已如預期般設定。

    顯示單一斷點的 WinDbg 斷點視窗螢幕快照。

  3. 從 [首頁] 功能表中,選取 [返回 ] 以及時返回,直到叫用斷點為止。

    0:000> g-
    Breakpoint 0 hit
    Time Travel Position: 5B:92
    eax=0000000f ebx=003db000 ecx=00000000 edx=00cc1a6c esi=00d41046 edi=0053fde8
    eip=00d4174a esp=0053fcf8 ebp=0053fde8 iopl=0         nv up ei pl nz ac pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
    DisplayGreeting!DisplayGreeting+0x3a:
    00d4174a c745e000000000  mov     dword ptr [ebp-20h],0 ss:002b:0053fdc8=cccccccc
    
  4. 選取 [檢視],然後選取 [局部變數]。 在局部變數視窗中,我們可以看到 目的地 變數只有訊息的一部分,而 來源 包含所有文字。 這項資訊支援堆疊損毀的想法。

    顯示 [局部變數] 視窗的 WinDbg 螢幕快照。

  5. 此時,我們可以檢查程式堆疊,以查看哪些程序代碼作用中。 從 [ 檢視 ] 功能區選取 [堆棧]。

    顯示 [堆疊] 視窗的 WinDbg 螢幕快照。

由於 Microsoft 提供的 wscpy_s() 函式不太可能有類似這樣的程式代碼錯誤,因此我們會在堆疊中進一步查看。 堆疊會顯示 Greeting!main 呼叫 Greeting!GetCppConGreeting。 在我們的非常小型的程式代碼範例中,我們此時只能開啟程序代碼,並很容易發現錯誤。 但是,為了說明可與較大型、更複雜的程式搭配使用的技術,我們將設定新的斷點以進一步調查。

設定 GetCppConGreeting 函式存取斷點的斷點

  1. 使用斷點視窗,以滑鼠右鍵按兩下現有的斷點並選取 [移除],以清除現有的斷點。

  2. 判斷 DisplayGreeting 的位址!使用 dx 命令的 GetCppConGreeting 函式。

    0:000> dx &DisplayGreeting!GetCppConGreeting
    &DisplayGreeting!GetCppConGreeting                 : 0xb61720 [Type: void (__cdecl*)(wchar_t *,unsigned int)]
        [Type: void __cdecl(wchar_t *,unsigned int)]
    
  3. 使用ba命令在記憶體存取上設定斷點。 由於函式只會從記憶體讀取來執行,因此我們需要設定 r - 讀取斷點。

    0:000> ba r4 b61720
    
  4. 確認 [硬體讀取] 斷點在斷點視窗中作用中。

    顯示單一硬體讀取斷點的 WinDbg 斷點視窗螢幕快照。

  5. 當我們想知道問候語字串的大小時,我們將設定監看視窗來顯示sizeof(greeting) 的值。 從 [檢視] 功能區中,選取 [ 監看 式] 並提供 sizeof(greeting)

    顯示 [監看式局部變數] 視窗的 WinDbg 螢幕快照。

  6. 在 [時間移動] 功能表上,使用 [時間移動] 來啟動 ,或使用 !tt 0命令移至追蹤的開頭。

    0:000> !tt 0
    Setting position to the beginning of the trace
    Setting position: 15:0
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000
    eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
    
  7. 在 [首頁] 功能表上,選取 [移至 ] 或使用 g 命令,在程序代碼中向前移動,直到叫用斷點為止。

    0:000> g
    Breakpoint 2 hit
    Time Travel Position: 4B:1AD
    eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046
    eip=00b61721 esp=00ddf7a4 ebp=00ddf864 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!GetCppConGreeting+0x1:
    00b61721 8bec            mov     ebp,esp
    
  8. 在 [首頁] 功能表上,選取 [跳出返回 ] 或使用 g-u 命令來返回一個步驟。

    0:000> g-u
    Time Travel Position: 4B:1AA
    eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046
    eip=00b61917 esp=00ddf7ac ebp=00ddf864 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!main+0x27:
    00b61917 e8def7ffff      call    DisplayGreeting!ILT+245(?GetCppConGreetingYAXPA_WIZ) (00b610fa)
    
  9. 看來我們找到了根本原因。 我們宣告的問候陣列長度為 50 個字元,而我們傳入 GetCppConGreeting 的 sizeof(greeting) 為 0x64,100)。

    顯示 DisplayGreeting 程式代碼的 WinDbg 螢幕快照,其中顯示 [監看局部變數] 視窗會顯示0x64。

    當我們進一步查看大小問題時,我們也注意到訊息長度為 75 個字元,包括字串字元的結尾 76 個字元。

    HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!
    
  10. 修正程式代碼的其中一種方法是將字元陣列的大小擴充為100。

    std::array <wchar_t, 100> greeting{};
    

    我們也需要在這一行程式代碼中將 sizeof(greeting) 變更為 size(greeting)。

     GetCppConGreeting(greeting.data(), size(greeting));
    
  11. 若要驗證這些修正程式,我們可以重新編譯程式代碼,並確認其執行時不會發生錯誤。

使用來源視窗設定斷點

  1. 執行此調查的另一種方式是按下任何一行程式代碼來設定斷點。 例如,按兩下來源視窗中 std:array 定義行的右側,就會設定該處的斷點。

    WinDbg 中 [來源] 視窗的螢幕快照,其中設定了 std::array 上的斷點。

  2. 在 [時間移動] 功能表上,使用 [時間移動] 啟動 命令來移至追蹤的開頭。

    0:000> !tt 0
    Setting position to the beginning of the trace
    Setting position: 15:0
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000
    eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
    
  3. 在 [首頁] 功能區上, 按兩下 [移至 ] 傳回,直到到達斷點為止。

    Breakpoint 0 hit
    Time Travel Position: 5B:AF
    eax=0000000f ebx=00c20000 ecx=00000000 edx=00000000 esi=013a1046 edi=00effa60
    eip=013a17c1 esp=00eff970 ebp=00effa60 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!DisplayGreeting+0x41:
    013a17c1 8bf4            mov     esi,esp
    

設定問候語變數之存取斷點的斷點

執行此調查的另一個替代方式是設定可疑變數的斷點,並檢查哪些程式代碼正在變更它們。 例如,若要在 GetCppConGreeting 方法的問候語變數上設定斷點,請使用這個程式。

本逐步解說的這個部分假設您仍然位於上一節的斷點。

  1. [檢視 ] 和 [ 局部變數]。 在 [局部變數] 視窗中, 問候語 可在目前的內容中使用,因此我們將能夠判斷其記憶體位置。

  2. 使用 dx 命令來檢查問候語陣列。

    0:000> dx &greeting
    &greeting                 : 0xddf800 [Type: std::array<wchar_t,50> *]
       [+0x000] _Elems           : "꽘棶檙瞝???" [Type: wchar_t [50]]
    

    在此追蹤中, 問候語 位於 ddf800 的記憶體中。

  3. 使用斷點視窗,以滑鼠右鍵按兩下現有的斷點並選取 [移除],以清除任何現有的斷點。

  4. 使用我們想要監視寫入存取的記憶體位址,使用 ba 命令設定斷點。

    ba w4 ddf800
    
  5. 在 [時間移動] 功能表上,使用 [時間移動] 啟動 命令來移至追蹤的開頭。

    0:000> !tt 0
    Setting position to the beginning of the trace
    Setting position: 15:0
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 15:0
    eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000
    eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    ntdll!LdrpInitializeProcess+0x1d1c:
    77a266ac 83bdbcfeffff00  cmp     dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
    
  6. 在 [首頁] 功能表上,選取 [移至 ] 以向前移動至問候陣列的第一個記憶體存取點。

    0:000> g-
    Breakpoint 0 hit
    Time Travel Position: 5B:9C
    eax=cccccccc ebx=002b1000 ecx=00000000 edx=68d51a6c esi=013a1046 edi=001bf7d8
    eip=013a1735 esp=001bf6b8 ebp=001bf7d8 iopl=0         nv up ei pl nz na po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
    DisplayGreeting!GetCppConGreeting+0x25:
    013a1735 c745ec04000000  mov     dword ptr [ebp-14h],4 ss:002b:001bf7c4=cccccccc
    

    或者,我們可以前往追蹤的結尾,並透過程式代碼反向運作,以找出陣列記憶體位置寫入追蹤的最後一個點。

使用 TTD。用來檢視記憶體存取的記憶體物件

另一個判斷追蹤記憶體中哪些點已存取的方法,是使用TTD。記憶體物件和 dx 命令。

  1. 使用 dx 命令來檢查問候語陣列。

    0:000> dx &greeting
    &greeting                 : 0xddf800 [Type: std::array<wchar_t,50> *]
       [+0x000] _Elems           : "꽘棶檙瞝???" [Type: wchar_t [50]]
    

    在此追蹤中, 問候語 位於 ddf800 的記憶體中。

  2. 使用 dx 命令查看記憶體中的四個字節,從具有讀取寫入存取權的地址開始。

    0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")
    @$cursession.TTD.Memory(0x1bf7d0,0x1bf7d4, "rw")                
        [0x0]           
        [0x1]           
        [0x2]           
        [0x3]           
        [0x4]           
        [0x5]           
        [0x6]           
        [0x7]           
        [0x8]           
        [0x9]           
        [0xa]           
        ...         
    
  3. 按兩下任何出現的專案,以顯示有關該記憶體存取次數的詳細資訊。

    0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5]
    @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5]                
        EventType        : MemoryAccess
        ThreadId         : 0x710
        UniqueThreadId   : 0x2
        TimeStart        : 27:3C1 [Time Travel]
        TimeEnd          : 27:3C1 [Time Travel]
        AccessType       : Write
        IP               : 0x6900432f
        Address          : 0xddf800
        Size             : 0x4
        Value            : 0xddf818
    
  4. 按兩下 [時間移動] 以在時間點放置追蹤。

    0:000> dx @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo()
    @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo()
    (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available)
    Time Travel Position: 27:3C1
    eax=00ddf81c ebx=00fa2000 ecx=00ddf818 edx=ffffffff esi=00000000 edi=00b61046
    eip=6900432f esp=00ddf804 ebp=00ddf810 iopl=0         nv up ei pl nz ac po nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
    ucrtbased!_register_onexit_function+0xf:
    6900432f 51              push    ecx
    
  5. 如果我們對追蹤中最後一次出現讀取/寫入記憶體取感興趣,我們可以按兩下清單中的最後一個專案,或附加 。Last() 函式到 dx 命令的結尾。

    0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last()
    @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last()                
        EventType        : MemoryAccess
        ThreadId         : 0x710
        UniqueThreadId   : 0x2
        TimeStart        : 53:100E [Time Travel]
        TimeEnd          : 53:100E [Time Travel]
        AccessType       : Read
        IP               : 0x690338e4
        Address          : 0xddf802
        Size             : 0x2
        Value            : 0x45
    
  6. 然後,我們可以按兩下 [時間移動] 以移至追蹤中的該位置,並使用本實驗室稍早所述的技術進一步查看該程式碼執行。

如需TTD的詳細資訊。記憶體物件,請參閱 TTD。Memory 物件

摘要

在這個非常小的範例中,問題可能是藉由查看幾行程式代碼來判斷,但在較大的程式中,此處提供的技術可用來減少找出問題所需的時間。

記錄追蹤之後,就可以共用追蹤和重現步驟,而且問題在任何計算機上都會隨選重現。

另請參閱

時間移動偵錯 - 概觀

時間移動偵錯 - 錄製

時間移動偵錯 - 重新執行追蹤

時間移動偵錯 - 使用追蹤檔案