共用方式為


C++動態偵錯 (預覽)

這很重要

C++動態偵錯目前處於 預覽。 這項資訊與發行前版本功能有關,此功能可能會在發行前大幅修改。 Microsoft針對此處提供的資訊,不提供任何明示或默示擔保。

此預覽功能從 Visual Studio 2022 17.14 版 Preview 2 開始提供,僅適用於 x64 專案。

透過C++動態偵錯,您可以偵錯優化的程序代碼,就好像未優化一樣。 這項功能適用於需要優化程式代碼效能優點的開發人員,例如需要高幀速率的遊戲開發人員。 透過C++動態偵錯,您可以享受未優化程式代碼的偵錯體驗,而不需要犧牲優化組建的效能優勢。

除錯已優化的程式碼會面臨挑戰。 編譯程式會重新置放並重新組織指示,以優化程序代碼。 結果是更有效率的程序代碼,但表示:

  • 優化工具可以移除局部變數,或將它們移至調試程式未知的位置。
  • 當優化器合併程式代碼區塊時,函式內的程式碼可能無法再與原始程式碼一致。
  • 如果優化器合併兩個函式,則呼叫堆疊上函式的函式名稱可能會錯誤。

過去,開發人員在偵錯優化程式代碼的過程中處理這些問題和其他問題。 現在已消除這些挑戰,因為有了C++動態偵錯,您可以逐步執行優化的程序代碼,就好像它未優化一樣。

除了產生優化的二進位檔之外,使用 /dynamicdeopt 編譯會產生偵錯期間所使用的未優化二進位檔。 當您新增斷點或逐步執行函式(包括 __forceinline 函式)時,偵錯工具會載入未優化的二進位檔案。 然後,您可以偵錯函式的未優化代碼,而不是優化代碼。 您可以進行偵錯,就像偵錯未優化的代碼一樣,同時仍能享受程式其他部分的優化效能優勢。

試用 C++ 動態除錯

首先,讓我們來回顧優化過的程式碼進行除錯的情況。 然後,您可以看到C++動態偵錯如何簡化程式。

  1. 在 Visual Studio 中建立新的 C++ 控制台應用程式專案。 以下列程式代碼取代 ConsoleApplication.cpp 檔案的內容:

    // Code generated by GitHub Copilot
    #include <iostream>
    #include <chrono>
    #include <thread>
    
    using namespace std;
    
    int step = 0;
    const int rows = 20;
    const int cols = 40;
    
    void printGrid(int grid[rows][cols])
    {
        cout << "Step: " << step << endl;
        for (int i = 0; i < rows; ++i)
        {
            for (int j = 0; j < cols; ++j)
            {
                cout << (grid[i][j] ? '*' : ' ');
            }
            cout << endl;
        }
    }
    
    int countNeighbors(int grid[rows][cols], int x, int y)
    {
        int count = 0;
        for (int i = -1; i <= 1; ++i)
        {
            for (int j = -1; j <= 1; ++j)
            {
                if (i == 0 && j == 0)
                {
                    continue;
                }
    
                int ni = x + i;
                int nj = y + j;
                if (ni >= 0 && ni < rows && nj >= 0 && nj < cols)
                {
                    count += grid[ni][nj];
                }
            }
        }
        return count;
    }
    
    void updateGrid(int grid[rows][cols])
    {
        int newGrid[rows][cols] = { 0 };
        for (int i = 0; i < rows; ++i)
        {
            for (int j = 0; j < cols; ++j)
            {
                int neighbors = countNeighbors(grid, i, j);
                if (grid[i][j] == 1)
                {
                    newGrid[i][j] = (neighbors < 2 || neighbors > 3) ? 0 : 1;
                }
                else
                {
                    newGrid[i][j] = (neighbors == 3) ? 1 : 0;
                }
            }
        }
        // Copy newGrid back to grid
        for (int i = 0; i < rows; ++i)
        {
            for (int j = 0; j < cols; ++j)
            {
                grid[i][j] = newGrid[i][j];
            }
        }
    }
    
    int main()
    {
        int grid[rows][cols] = { 0 };
    
        // Initial configuration (a simple glider)
        grid[1][2] = 1;
        grid[2][3] = 1;
        grid[3][1] = 1;
        grid[3][2] = 1;
        grid[3][3] = 1;
    
        while (true)
        {
            printGrid(grid);
            updateGrid(grid);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            cout << "\033[H\033[J"; // Clear the screen
            step++;
        }
    
        return 0;
    }
    
  2. [方案組態] 下拉式清單變更為 [發行]。 確定解決方案平臺下拉式清單設定為 x64

  3. 選取 [建置]>[重建方案]來重建。

  4. 在第 55 行上設定斷點:int neighbors = countNeighbors(grid, i, j);updateGrid()。 執行程式。

  5. 當您遇到斷點時,請檢視 Locals 視窗。 在主選單上,選取 偵錯>Windows>局部變數。 請注意,您無法在 [i 視窗中看到 [j] 或 [] 的值。 編譯程式已將其優化並移除。

  6. 嘗試在第 19 行上設定斷點,cout << (grid[i][j] ? '*' : ' ');printGrid()。 你無此權限。 這是預期的行為,因為編譯程式已優化程序代碼。

停止程式並啟用C++動態偵錯,然後再試一次

  1. [方案總管]中,以滑鼠右鍵按兩下專案,然後選取 [屬性] 以開啟專案屬性頁。

  2. 選取 [進階>使用 C++ 動態偵錯],並將設定變更為 [是]

    顯示進階項目屬性的螢幕快照。

    屬性頁會開啟至 [組態屬性] > [進階] > [使用 C++ 動態除錯]。 屬性設定為 [是]。

    此步驟會將 /dynamicdeopt 開關加入到編譯器和連結器中。 在幕後,它也會關閉C++優化參數 /GL/OPT:ICF。 這個設定不會覆寫您手動新增至命令列或其他已設定的優化參數,例如 /O1

  3. 選取 [建置]>[重建方案]來重建。 MSB8088 出現建置診斷程序代碼,這表示動態偵錯和整個程序優化不相容。 此錯誤表示編譯期間會自動關閉整個程式優化 (/GL)。

    您可以在項目屬性中手動關閉整個程式優化。 選取 [設定屬性]>[進階>整個程式優化],並將設定變更為 [關閉 ]。 現在,MSB8088 會被視為警告,但未來Visual Studio版本中可能會將其視為錯誤。

  4. 重新執行應用程式。

    現在,當您在第 55 行遇到斷點時,您會在 i 視窗中看到 j 的值。 [呼叫堆疊] 視窗顯示 updateGrid() 已取消最佳化,且檔案名稱為 life.alt.exe。 此替代二進位檔可用來偵錯優化程序代碼。

    顯示 updateGrid 函式偵錯的螢幕快照。

    函式 updateGrid 中會顯示斷點。 呼叫堆疊會顯示函式已被去優化,且檔名為 life.alt.exe。 [局部變數] 視窗會顯示 i 和 j 的值,以及函式中的其他局部變數。

    updateGrid() 函式會視需要取消優化,因為您在其中設定斷點。 如果您在偵錯時略過一個已優化的函式,它不會被取消優化。 如果您步入函式 ,則會被取消優化。 造成函式失去優化的主要方式是,如果你在其中設定斷點或逐步執行進入函式。

    您也可以在 呼叫堆疊 視窗中取消優化函式。 以滑鼠右鍵按下函式或選取的函式群組,然後在下一個專案 上選取 [Deoptimize]。 當您想要在呼叫堆疊的其他地方未設定斷點的優化函式中檢視局部變數時,這項功能很有用。 以這種方式取消優化的函式會在 斷點 視窗中分組,作為名為 已取消優化的函式的斷點群組。 如果您刪除斷點群組,相關聯的函式會還原為其優化狀態。

使用條件式和相依斷點

  1. 請嘗試在第 19 行的 cout << (grid[i][j] ? '*' : ' ');中的 printGrid() 再次設定斷點。 現在運作正常。 在函式中設定斷點會將其反優化,以便您能正常進行偵錯。

  2. 以滑鼠右鍵按兩下第 19 行的斷點,選取 [條件] ,並將條件設定為 [i == 10 && j== 10]。 然後選取 [只有在下列斷點被擊中時才啟用:] 複選框。 從下拉式清單中選取第 55 行上的斷點。 現在,第 19 行的斷點不會觸發,直到第 50 行的斷點被先觸發,然後當 grid[10][10] 即將輸出到控制台時。

    重點是,您可以在優化函式中設定條件式和依賴斷點,並使用在優化後的程式組建中可能無法供調試程式使用的局部變數和程式代碼行。

    顯示第 19 行的條件斷點設定的螢幕快照。

    條件斷點顯示在第 19 行, cout < < (grid[i][j] ? '*' : ' ');。 條件設定為 i == 10 && j== 10。 當選取「以下斷點觸發時僅啟用」的複選框時。 斷點下拉式清單會設定為 life.cpp 的第 55 行。

  3. 繼續執行應用程式。 當到達第 19 行的斷點時,您可以用滑鼠右鍵按一下第 15 行,然後選取 設定下一個語句 來重新執行循環。

    顯示 printGrid 函式偵錯的螢幕快照。

    條件和相依斷點在第 19 行觸發,cout < <(grid[i][j] ? '*' : ' ');。 [局部變數] 視窗會顯示 i 和 j 的值,以及函式中的其他局部變數。 [呼叫堆疊] 視窗顯示函式已取消最佳化,且檔案名稱為 life.alt.exe。

  4. 刪除所有斷點,使功能恢復到最佳化狀態。 在 Visual Studio 主選單上,選取 [偵錯]>[刪除所有斷點]。 然後,所有函式都會返回其優化狀態。

    如果您透過 [呼叫堆棧] 視窗中的 [下一次進入] 選項新增斷點,我們在此逐步解說中並未這麼做,您可以按右鍵點選 [已取消優化的函式] 群組,然後選取 [刪除],以僅將該群組中的函式還原到優化狀態。

    一個顯示斷點視窗的螢幕截圖。

    [斷點] 視窗會顯示 [非最佳化函式] 群組。 選取群組,開啟內容功能表,並在其中已選取 [刪除斷點群組]。

關閉C++動態偵錯

您可能需要在不去優化的情況下偵錯已優化的程式碼,或是在已優化的程式碼中放置斷點,並確保當斷點觸發時程式碼保持優化狀態。 當您觸發斷點時,有數種方式可以關閉動態除錯,或防止它使程式碼不被反優化:

  • 在 Visual Studio 主功能表上,選取 [[工具]>[選項]>[偵錯]>[一般]。 取消選取 [在可能時自動取消優化已偵錯的函式(.NET 8+,C++ 動態偵錯)] 複選框。 下一次調試程序啟動時,程式代碼會維持優化狀態。
  • 許多動態偵錯斷點都是兩個斷點:一個在優化二進位檔中,另一個是在未優化二進位檔中。 在 斷點 視窗中,選取 顯示欄位>函數。 清除與 alt 二進位檔相關聯的斷點。 配對中的另一個斷點會在優化後的代碼中中斷。
  • 當您進行偵錯時,請在 Visual Studio 主功能表上,選取 [偵錯>Windows>反組譯]。 請確定它有焦點。 當您在反組譯視窗中進入函式時,該函式不會被取消優化。
  • 不要將 /dynamicdeopt 傳遞至 cl.exelib.exelink.exe,以完全停用動態偵錯。 如果您要取用第三方函式庫且不能重建它們,請勿在最後的 /dynamicdeopt 期間傳遞 link.exe,以停用該二進位檔案的動態除錯功能。
  • 若要快速停用單一二進位檔的動態偵錯(例如,test.dll),請重新命名或刪除 alt 二進位檔(例如,test.alt.dll)。
  • 若要停用一或多個 .cpp 檔案的動態偵錯,請勿在建置檔案時傳遞 /dynamicdeopt。 您項目的其餘部分是使用動態偵錯所建置。

在 Unreal Engine 中啟用C++動態偵錯

Unreal Engine 5.6 支援適用於 Unreal 建置工具和 Unreal 建置加速器的C++動態偵錯。 有兩種方式可以啟用它:

  • 變更項目的 Target.cs 檔案,以包含 WindowsPlatform.bDynamicDebugging = true

  • 使用 開發編輯器 組態,並修改 BuildConfiguration.xml 以包含:

    <WindowsPlatform>
        <bDynamicDebugging>true</bDynamicDebugging>
    </WindowsPlatform>
    

針對 Unreal Engine 5.5 或更早版本,請從 GitHub 專案中的 挑選出 Unreal 建置工具的變更並整合到您的存放庫中。 然後,如上所示啟用 bDynamicDebugging。 您需要使用 Unreal Engine 5.6 的 Unreal Build Accelerator。 使用 ue5-main 的最新版本,或將以下內容新增至 BuildConfiguration.xml以停用 UBA:

<BuildConfiguration>
    <bAllowUBAExecutor>false</bAllowUBAExecutor>
    <bAllowUBALocalExecutor>false</bAllowUBALocalExecutor>
</BuildConfiguration>

如需設定 Unreal Engine 建置方式的詳細資訊,請參閱建置組態

故障排除

如果斷點未在反優化的函式中觸發:

  • 如果您跳出 [Deoptimized] 框架,除非呼叫端由於斷點而被取消優化,否則可能會處於已優化的代碼中,或是在前往當前函數的過程中介入了呼叫端。

  • 確定 alt.exealt.pdb 檔案已建置好。 針對 test.exetest.pdbtest.alt.exetest.alt.pdb 必須存在於相同的目錄中。 請確定已根據本文設定正確的組建參數。

  • debug directory 中存在一個 test.exe 項目,調試程式使用它來查找用於取消優化偵錯的 alt 執行檔。 開啟 x64 原生 Visual Studio 命令視窗,然後執行 link /dump /headers <your executable.exe> 以查看是否存在 deopt 項目。 deopt 項目會出現在 [Type] 數據行中,如本範例的最後一行所示:

      Debug Directories
    
            Time Type        Size      RVA  Pointer
        -------- ------- -------- -------- --------
        67CF0DA2 cv            30 00076330    75330    Format: RSDS, {7290497A-E223-4DF6-9D61-2D7F2C9F54A0}, 58, D:\work\shadow\test.pdb
        67CF0DA2 feat          14 00076360    75360    Counts: Pre-VC++ 11.00=0, C/C++=205, /GS=205, /sdl=0, guardN=204
        67CF0DA2 coffgrp      36C 00076374    75374
        67CF0DA2 deopt         22 00076708    75708    Timestamp: 0x67cf0da2, size: 532480, name: test.alt.exe
    

    如果 deopt 偵錯目錄專案不存在,請確認您將 /dynamicdeopt 傳遞給 cl.exelib.exelink.exe

  • 如果 /dynamicdeopt 未傳遞至 cl.exelib.exe,亦或未傳遞至所有的 link.exe.cpp.lib 和二進位檔,則動態取消優化將無法一致運作。 請確認在建置專案時已正確設置必要的開關。

如需已知問題的詳細資訊,請參閱 C++動態偵錯:優化組建的完整偵錯性

如果事情未如預期般運作,請在 開發者社群提交工單。 盡可能包含問題的相關信息。

一般注意事項

IncrediBuild 10.24 支援C++動態偵錯組建。
FastBuild v1.15 支援C++動態偵錯組建。

內嵌的函式會在需要時取消優化處理。 如果您在內嵌函式上設定斷點,調試程式會取消優化函式及其呼叫端。 斷點會在您預期的位置觸發,就好像程式是在沒有經過編譯器優化的情況下建置的一樣。

即使您停用函式內的斷點,函式仍會維持非優化狀態。 您必須移除函式的斷點,才能還原為其優化狀態。

許多動態偵錯斷點都是兩個斷點:一個在優化二進位檔中,另一個是在未優化二進位檔中。 基於這個理由,您會在 [斷點] 視窗中看到多個斷點。

用於已取消優化版本的編譯程式旗標與用於優化版本的旗標相同,但優化旗標和 /dynamicdeopt除外。 這種行為意味著您設定的任何用於定義巨集的旗標等,也會在取消優化的版本中被設置。

已取消優化的程式代碼與偵錯程式代碼不同。 已取消優化的程式代碼會使用與優化版本相同的優化旗標進行編譯,因此不包含依賴偵錯特定設定的判斷提示和其他程序代碼。

建置系統整合

C++動態偵錯要求編譯程式和鏈接器旗標必須以特定方式設定。 下列各節說明如何為沒有衝突參數的動態偵錯設定專用組態。

如果您的專案是使用 Visual Studio 建置系統所建置,則進行動態偵錯設定的好方法是使用 Configuration Manager 來複製發行或偵錯組態,並進行變更以配合動態偵錯。 下列兩節說明程式。

建立新的發行組態

  1. 在 Visual Studio 主選單上,選取 [建置>Configuration Manager] 開啟 Configuration Manager。

  2. 選取 [組態] 下拉式清單,然後選取 [<[新增...]>

    顯示 Configuration Manager 的螢幕快照。

    在 Configuration Manager 的 [項目內容] 下,[組態] 下拉式清單隨即開啟,並醒目提示

  3. [新增解決方案組態] 對話框 隨即開啟。 在 [名稱] 字段中,輸入新組態的名稱,例如 ReleaseDD。 確定 複製設定來源: 設定為 Release。 然後選取 [確定] 以建立新的組態。

    螢幕快照,顯示發行組建的 [新增專案組態] 對話框。

    [名稱] 欄位會設定為 ReleaseDD。 [複製設定的來源] 下拉式清單會設定為 [發行]。

  4. 新的組態會出現在 作用中解決方案組態 下拉式清單中。 選取 關閉

  5. 將 [組態] 下拉式清單設定為 [ReleaseDD],在 [方案總管] 中以滑鼠右鍵按兩下專案,然後選取 [屬性]

  6. 組態屬性>進階中,將 將 "使用 C++ 動態偵錯" 設定為 "是"

  7. 確定 整個程序優化 設定為

    顯示進階項目屬性的螢幕快照。

    屬性頁已打開至 [組態屬性] > [進階]。 使用C++動態偵錯。 屬性設定為 [是]。 整個程式優化設定為 [否]。

  8. 組態屬性>Linker>優化中,確保 啟用 COMDAT 摺疊 設定為 否 (/OPT:NOICF)

    顯示連結器優化專案屬性的螢幕快照。

    屬性頁會開啟至 [組態屬性] > [連結器] > [優化] > [啟用 CMDAT 折叠]。 屬性設定為 [否] (/OPT:NOICF)。

此設定將 /dynamicdeopt 開關新增至編譯程式和連結器。 在關閉 C++ 優化參數 /GL/OPT:ICF 後,您現在可以在新的組態中建置並執行專案,這讓您在需要與 C++ 動態偵錯搭配使用的優化發行組建時,獲得所需的配置。

您可以將與零售組建搭配使用的其他開關新增至此組態,以確保在使用動態偵錯時,開關設定正如您所預期的一樣開啟或關閉。 如需詳細了解不應與動態偵錯一起使用的切換,請參閱 不相容的選項

如需 Visual Studio 中組態的詳細資訊,請參閱 建立和編輯組態

建立新的偵錯組態

如果您想要使用偵錯二進位檔,但想要讓二進位檔執行得更快,您可以修改偵錯組態。

  1. 在 Visual Studio 主選單上,選取 [建置>Configuration Manager] 開啟 Configuration Manager。

  2. 選取 [組態] 下拉式清單,然後選取 [<[新增...]>

    顯示 Configuration Manager 的螢幕快照。

    在 Configuration Manager 中,在視窗的 [項目內容] 部分中,[組態] 下拉式清單隨即開啟,並醒目提示

  3. [新增項目組態] 對話框隨即開啟。 在 [名稱] 字段中,輸入新組態的名稱,例如 DebugDD。 確定 複製設定來源: 已設成 偵錯。 然後選取 [確定] 以建立新的組態。

    顯示用於偵錯組建的新專案設定對話框的螢幕擷取畫面。

    名稱欄位會設定為 DebugDD。 「複製設定來源」下拉式清單已設為「偵錯」。

  4. 新的組態會出現在 作用中解決方案組態 下拉式清單中。 選取 關閉

  5. 將 [組態] 下拉式清單設定為 [DebugDD],以滑鼠右鍵按兩下 [方案總管] 中的項目,然後選取 [[屬性]

  6. 組態屬性>C/C++>優化中,開啟您想要的優化。 例如,您可以將 優化 設定為 最大化速度 (/O2)

  7. C/C++>程式代碼產生中,將 基本執行時間檢查 設定為 Default

  8. C/C++>通用中,停用 支持 Just My Code 偵錯功能

  9. 偵錯資訊格式 設定為 Program Database (/Zi)

您可以將您在偵錯組建中使用的其他選項新增至此組態,這樣在使用動態偵錯時,您總是可以準確地開啟或關閉所預期的選項。 如需詳細了解不應與動態偵錯一起使用的切換,請參閱 不相容的選項

如需 Visual Studio 中組態的詳細資訊,請參閱 建立和編輯組態

自訂建置系統考慮事項

如果您有自訂組建系統,請確定您:

  • /dynamicdeopt 傳遞至 cl.exelib.exelink.exe
  • 請勿使用 /ZI、任何 /RTC 旗標或 /JMC

針對建置發行者:

  • 針對名為 test的項目,編譯程式會產生 test.alt.objtest.alt.exptest.objtest.exp。 連結器會產生 test.alt.exetest.alt.pdbtest.exetest.pdb
  • 您需要將新的工具集二進位檔 c2dd.dllc2.dll一起部署。

不相容的選項

某些編譯程式和連結器選項與C++動態偵錯不相容。 如果您使用 Visual Studio 專案設定開啟C++動態偵錯,除非您特別在其他命令行選項設定中設定不相容的選項,否則會自動關閉不相容的選項。

下列編譯程式選項與C++動態偵錯不相容:

/GH
/GL
/Gh
/RTC1 
/RTCc 
/RTCs 
/RTCu 
/ZI (/Zi is OK)
/ZW 
/clr 
/clr:initialAppDomain
/clr:netcore
/clr:newSyntax
/clr:noAssembly
/clr:pure
/clr:safe
/fastcap
/fsanitize=address
/fsanitize=kernel-address

下列連結器選項與C++動態偵錯不相容:

/DEBUG:FASTLINK
/INCREMENTAL
/OPT:ICF  You can specify /OPT:ICF but the debugging experience may be poor

另請參閱

/dynamicdeopt 編譯器標誌 (預覽)
/DYNAMICDEOPT 連結器標誌 (預覽)
C++動態偵錯:優化組建的完整偵錯能力
除錯優化程式碼