Visual Studio調試本機代碼


本文講述本機應用程序的一些常見調試問題和調試技術。 本節闡述的技術屬於高級別技術。

調試優化的代碼

當編譯器優化代碼時,它將重新定位並重組指令, 這會得到更高效的編譯的代碼。 由於這種調整,調試器並不總能確定與一組指令對應的源代碼。

優化可能影響到:

  • 本地變量(可被優化器移除或移動到調試器無法識別的位置)。

  • 函數內部的位置(當優化器合並代碼塊時發生變化的位置)。

  • 調用堆棧上框架的函數名稱(如果優化器合並兩個函數,則函數名稱可能是錯誤的)。

但是,假定所有框架都有符號,則在調用堆棧上看到的框架幾乎總是正確的。 在下列情況下調用堆棧上的框架將是錯誤的:有堆棧損壞,有用匯編語言編寫的函數,或者有操作系統框架在調用堆棧上沒有匹配的符號。全局和靜態變量總是正確顯示。 結構布局也是這樣。 如果您有指向結構的指針而且指針的值是正確的,那么結構的每個成員變量都將顯示正確值。出於這些限制原因,只要有可能,就應使用程序的“未優化”版本進行調試。 默認情況下,優化在 Visual C++ 程序的“Debug”配置中關閉,在“Release”配置中打開。

但是,bug 可能僅在程序的優化版本中出現。 在此情況下,必須調試優化的代碼。

在“Debug”生成配置中打開優化

  1. 創建新項目時,請選擇 Win32 Debug目標。 接下來要一直使用 Win32 Debug目標,直至程序已進行全面調試,可以生成 Win32 Release目標為止。 調試器並不優化 Win32 Debug目標。

  2. 在解決方案資源管理器中選擇項目。

  3. 在“視圖”菜單上,單擊“屬性頁”。

  4. 在“屬性頁”對話框中,確保在“配置”下拉列表框中選擇了 Debug。

  5. 在左邊的文件夾視圖中,選擇 C/C++ 文件夾。

  6. 在“C++”文件夾下選擇 Optimization。

  7. 在右邊的屬性列表中找到“Optimization”。 它旁邊的設置可能顯示為“Disabled (/Od)”。 選擇其他選項(“Minimum Size (/O1)”、“Maximum Speed (/O2)”、“Full Optimization (/Ox)”或“Custom”)之一。

  8. 如果為“Optimization”選擇了“Custom”選項,現在便可為屬性列表中顯示的其他任何屬性設置選項。

調試優化的代碼時,請使用“反匯編”窗口以了解實際創建和執行了哪些指令。 設置斷點時,需要注意斷點可能隨指令一起移動。 例如,考慮以下代碼:

for (x=0; x<10; x++)

假定在該行設置了一個斷點。 可能希望該斷點被命中 10 次,但如果代碼進行了優化,則只會命中該斷點一次。 因為第一個指令將 x 的值設置為 0。 編譯器認定該指令只需執行一次,將其移出循環。 斷點隨之移動。 而比較和遞增 x 的指令仍留在循環內。 當查看“反匯編”窗口時,單步執行單元自動設置為“指令”以允許更大控制,這在逐句通過優化的代碼時很有用。

DebugBreak 和 __debugbreak

可以在代碼中的任意點調用 DebugBreak Win32 函數或 __debugbreak 內部類型。 DebugBreak 和 __debugbreak 具有與在該位置設置斷點相同的效果。因為 DebugBreak 是系統函數調用,所以必須安裝系統調試符號以確保中斷后顯示正確的調用堆棧信息。 否則,調試器可能在顯示一幀調用堆棧信息后就停止顯示。 如果使用 __debugbreak,則不需要符號。

斷言

斷言語句指定在程序的某些特定點應為真的條件。 如果該條件不為真,則斷言失敗,中斷程序的執行,並顯示“斷言失敗”對話框。

Visual C++ 支持基於下列構造的斷言語句:

  • MFC 程序的 MFC 斷言。

  • 使用 ATL 的程序的 ATLASSERT。

  • 使用 C 運行庫的程序的 CRT 斷言。

  • 其他 C/C++ 程序的 ANSI assert 函數。

斷言可以用於:

  • 捕捉邏輯錯誤。

  • 檢查某操作的結果。

  • 測試錯誤條件,這些錯誤條件應已處理。

MFC 和 C 運行庫斷言

當調試器由於 MFC 或 C 運行庫斷言而暫停時,它定位到源文件中的斷言發生點(如果源可用)。 斷言消息顯示在“輸出”窗口以及“斷言失敗”對話框中。 如果希望保存斷言消息以供將來參考,可以將斷言消息從“輸出”窗口復制到某個文本窗口。 “輸出”窗口可能還包含其他錯誤信息。 請仔細檢查這些消息,因為它們提供了有關確定斷言失敗原因的線索。

通過在代碼中大量使用斷言,可以在開發期間捕捉許多錯誤。 為所做的每個假定編寫一個斷言是很好的規則。 例如,如果假定某個參數不為 NULL,請使用一條斷言語句檢查該假定。

_DEBUG

僅當定義了 _DEBUG 時斷言語句才編譯。 未定義 _DEBUG 時,編譯器將斷言作為空語句處理。 因此,斷言語句在最終發布程序中系統開銷為零;可以在代碼中大量使用斷言語句,而不影響“Release”版本的性能,並且不必使用 #ifdef 指令。

使用斷言的副作用

當向代碼添加斷言時,請確保這些斷言沒有副作用。 例如,考慮以下斷言:

ASSERT(nM++ > 0); // Don't do this!

因為在程序的“Release”版本中不計算 ASSERT 表達式,所以 nM 在“Debug”版本和“Release”版本中會有不同值。 在 MFC 中,可以使用 VERIFY 宏代替 ASSERT。 在“Release”版本中,VERIFY 計算該表達式,但不檢查結果。

在斷言語句中使用函數調用時應特別小心,因為計算函數可能會有意外的副作用。

ASSERT ( myFnctn(0)==1 ) // unsafe if myFnctn has side effects
VERIFY ( myFnctn(0)==1 ) // safe

VERIFY 在“Debug”版本和“Release”版本中都調用 myFnctn,因此可以使用它。 但在“Release”版本中仍會有因不必要的函數調用而產生的系統開銷。

調試內聯匯編代碼

調試器提供了兩個用於調試內聯程序集代碼的窗口,即“反匯編”窗口和“寄存器”窗口。

調試內聯程序集代碼

  1. 使用“反匯編”窗口查看程序集指令。

     

  2. 使用“寄存器”窗口查看寄存器內容。

MFC 調試技術

AfxDebugBreak

MFC 提供特殊的 AfxDebugBreak 函數,以供在源代碼中對斷點進行硬編碼:

AfxDebugBreak( );

在 Intel 平台上,AfxDebugBreak 將生成以下代碼,它在源代碼而不是內核代碼中中斷:

_asm int 3

在其他平台上,AfxDebugBreak 僅調用 DebugBreak。

確保在創建發布版本時移除 AfxDebugBreak 語句,或使用 #ifdef _DEBUG 環繞這些語句。

TRACE 宏

若要在調試器的“輸出”窗口中顯示來自程序的消息,可以使用 ATLTRACE 宏或 MFC TRACE 宏。 與斷言類似,跟蹤宏只在程序的“Debug”版本中起作用,在“Release”版本中編譯時將消失。

下面的示例顯示幾種 TRACE 宏的用法。 與 printf 類似,TRACE 宏可處理許多參數。

int x = 1;
int y = 16;
float z = 32.0;
TRACE( "This is a TRACE statement\n" );

TRACE( "The value of x is %d\n", x );

TRACE( "x = %d and y = %d\n", x, y );

TRACE( "x = %d and y = %x and z = %f\n", x, y, z );

TRACE 宏可正確處理 char* 參數和 wchar_t* 參數。 下面的示例說明如何將 TRACE 宏與不同字符串參數類型配合使用。

TRACE( "This is a test of the TRACE macro that uses an ANSI string: %s %d\n", "The number is:", 2);

TRACE( L"This is a test of the TRACE macro that uses a UNICODE string: %s %d\n", L"The number is:", 2);

TRACE( _T("This is a test of the TRACE macro that uses a TCHAR string: %s %d\n"), _T("The number is:"), 2);

CRT 調試技術

使用CRT 調試庫

C 運行庫提供廣泛的調試支持。 若要使用 CRT 調試庫之一,必須鏈接 /DEBUG,並用 /MDd、/MTd 或 /LDd 編譯。CRT 調試的主要定義和宏可在 CRTDBG.h 頭文件中找到。

CRT 調試庫中的函數編譯時帶有調試信息(/Z7、/Zd、/Zi、/ZI(調試信息格式)),不進行優化。 某些函數包含斷言以驗證傳遞給它們的參數,並且提供源代碼。 使用此類源代碼,可以單步執行 CRT 函數,以確認這些函數按預期方式工作並檢查錯誤的參數或內存狀態。 (某些 CRT 技術是專有技術,不提供用於異常處理、浮點和少數其他例程的源代碼。)

安裝 Visual C++ 時,可以選擇在硬盤上安裝 C 運行庫源代碼。 如果不安裝源代碼,將需要 CD-ROM 才能單步執行 CRT 函數。

用於報告的宏

可以使用在 CRTDBG.H 中定義的 _RPTn 和 _RPTFn 宏替換 printf 語句進行調試。 未定義 _DEBUG 時,這些宏在發布版本中自動消失,因此不必將它們括在 #ifdef 內。

表 2

函數

_RPT0, _RPT1, _RPT2, _RPT3, _RPT4

向四個參數輸出一個消息字符串和零。

對於從 _RPT1 到 _RPT4,消息字符串作為參數的 printf 樣式的格式化字符串。

_RPTF0、_RPTF1、_RPTF2、_RPTF4

與 _RPTn 相同,但這些宏還輸出其所在的文件名和行號。

請看下面的示例:

#ifdef _DEBUG
    if ( someVar > MAX_SOMEVAR )
        printf( "OVERFLOW! In NameOfThisFunc( ),
               someVar=%d, otherVar=%d.\n",
               someVar, otherVar );
#endif

該代碼將 someVar 和 otherVar 的值輸出到 stdout。 可以使用以下對 _RPTF2 的調用報告同樣的值另加文件名和行號:

if (someVar > MAX_SOMEVAR) _RPTF2(_CRT_WARN, "In NameOfThisFunc( ), someVar= %d, otherVar= %d\n", someVar, otherVar );

如果發現某特定應用程序需要調試報告,而 C 運行庫提供的宏不提供該報告,則可以編寫專門設計的宏來符合您自己的要求。 例如,可以在其中一個頭文件中包含以下代碼來定義名為 ALERT_IF2 的宏:

 
#ifndef _DEBUG                  /* For RELEASE builds */
#define  ALERT_IF2(expr, msg, arg1, arg2)  do {} while (0)
#else                           /* For DEBUG builds   */
#define  ALERT_IF2(expr, msg, arg1, arg2) \
    do { \
        if ((expr) && \
            (1 == _CrtDbgReport(_CRT_ERROR, \
                __FILE__, __LINE__, msg, arg1, arg2))) \
            _CrtDbgBreak( ); \
    } while (0)
#endif

對 ALERT_IF2 的一個調用可以執行本主題開始處的 printf 代碼的所有函數:

ALERT_IF2(someVar > MAX_SOMEVAR, "OVERFLOW! In NameOfThisFunc( ), 
someVar=%d, otherVar=%d.\n", someVar, otherVar );

因為可以方便地更改自定義宏,以便向不同目標報告或多或少的信息(取決於怎樣更方便),所以該方法在調試要求不斷發展時尤其有用。

堆分配函數的“Debug”版本

C 運行庫包含堆分配函數的特殊“Debug”版本。 這些函數的名稱與發行版本相同,只是追加了“_dbg”。 本主題用 malloc 和 _malloc_dbg 作為示例,描述 CRT 函數的發行版本和 _dbg 版本之間的差異。

定義 _DEBUG 后,CRT 會將所有 malloc 調用映射到 _malloc_dbg。 因此,不需要用 _malloc_dbg 代替 malloc 來重寫代碼以獲得調試時的好處。

但您可能希望顯式調用 _malloc_dbg。 顯式調用 _malloc_dbg 具有一些附加的好處:

  • 跟蹤 _CLIENT_BLOCK 類型分配。

  • 存儲分配請求所在的源文件和行號。

如果不希望將 malloc 調用轉換為 _malloc_dbg,可以通過定義 _CRTDBG_MAP_ALLOC 來獲取源文件信息,而這導致預處理器將對 malloc 的所有調用直接映射到 _malloc_dbg,而不是依賴 malloc 周圍的包裝。

若要跟蹤客戶端塊中各種類型的分配,必須直接調用 _malloc_dbg,並將 blockType 參數設置為 _CLIENT_BLOCK。

未定義 _DEBUG 時,對 malloc 的調用將不受妨礙,並且對 _malloc_dbg 的調用將被解析為 malloc,忽略 _CRTDBG_MAP_ALLOC 的定義,並且不提供與分配請求有關的源文件信息。 因為 malloc 沒有塊類型參數,所以將對 _CLIENT_BLOCK 類型的請求作為標准分配處理。

調試本機 DLL

當調試 DLL 時,可以從以下開始調試:

  • 用於創建調用 DLL 的可執行文件的項目。

- 或 -

  • 用於創建 DLL 本身的項目。

如果有用於創建可執行文件的項目,則從該項目開始調試。 然后可以打開 DLL 的源文件,並在該文件中設置斷點,即使它不是用於創建可執行文件的項目的一部分。 有關更多信息,請參見斷點。

如果從創建 DLL 的項目開始調試,則必須指定在調試 DLL 時要使用的可執行文件。

為調試會話指定可執行文件

  1. 在“解決方案資源管理器”中,選擇用於創建 DLL 的項目。

  2. 從“視圖”菜單中,選定“屬性頁”。

  3. 在“屬性頁”對話框中,打開“配置屬性”文件夾並選擇“調試”類別。

  4. 在“命令”框中,指定用於容器的路徑名稱。 例如,C:\Program Files\MyApplication\MYAPP.EXE。

  5. 在“命令參數”框中,指定用於可執行文件的必要參數。

如果不在“項目 屬性頁”對話框中指定可執行文件,則在開始調試時將出現“調試會話的可執行文件”對話框。

調試注入的代碼

使用Attributes 可以大大簡化C++編程。一些Attributes由編譯器直接解釋。其他Attributes將代碼注入程序源代碼,然后由編譯器編譯。這段注入的代碼通過減少必須編寫的代碼量使編程變得更容易。但是,有時錯誤可能會導致應用程序在執行注入的代碼時失敗。當這種情況發生時,您可能需要查看注入的代碼。Visual Studio提供了兩種查看注入代碼的方法:

  • 您可以在反匯編窗口中查看注入的代碼。
  • 使用/Fx,可以創建包含原始代碼和注入代碼的合並源文件。

“反匯編”窗口顯示與源代碼和屬性注入的代碼相對應的匯編語言指令。此外,反匯編窗口可以顯示源代碼注釋。

打開源注釋

在“反匯編”窗口上單擊鼠標右鍵,然后從快捷菜單中選擇“顯示源代碼”。

如果知道源窗口中屬性的位置,可以使用快捷菜單在反匯編窗口中查找注入的代碼。

查看插入的代碼

  1. 調試器必須處於中斷模式。

  2. 在源代碼窗口中,將光標放在要查看其注入代碼的特性前面。

  3. 右擊並從快捷菜單中選定“轉到反匯編”。

    如果特性位置在當前執行點附近,則可以從“調試”菜單選擇“反匯編”窗口。

查看當前執行點處的反匯編代碼

    1. 調試器必須處於中斷模式。

    2. 從“調試”菜單中選擇“窗口”,然后單擊“反匯編”。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM