目錄
處理器體繫結構
虛擬變數
格式說明符
可視化呼叫慣例
錯誤碼
調試安全環境
多年來,Visual C++ 一直包含一組用於調試的虛擬變數和格式說明符。虛擬變數是指這樣的內容:可以輸入到偵錯工具監看式視窗中,使其顯示某種不必與任何 C++ 變數關聯的值。遺憾的是,還沒有人對虛擬變數進行過詳細介紹。我自已掌握的資訊尚不足以詳細介紹所有虛擬變數,在此僅就我認為最有用的一些虛擬變數和格式說明符與大家共用。我希望此處的討論可以促使您進一步閱讀有關本主題的更多內容。
在介紹虛擬變數(有時稱為偽寄存器)之前,我需要簡要介紹一下處理器體繫結構和寄存器,因為瞭解一點背景知識可以更好地理解虛擬變數的價值。這還有助您從一個側面理解 64 位元版本應用程式與傳統 32 位版本應用程式的不同。此專欄的主要內容是 Visual C++,而不是處理器體繫結構的細節,所以我將只介紹 x86 和 x64 處理器體繫結構。閱讀 MSDN 庫中的文檔,您可以瞭解更多有關 Itanium 處理器引入的不同之處的內容。
處理器體繫結構
要全面掌握處理器體繫結構有一定的難度。這就是 C 語言及其後續語言對軟體行業帶來如此巨大影響的原因。至少您應該瞭解 x86 和 x64 處理器體繫結構代表什麼,因為它們是絕大部分使用者使用的處理器類型。Visual C++ 和 Windows 也支援 Itanium 處理器,但是很少有開發人員願意以這種處理器為目標。
不管怎樣,在過去的幾十年中,源於 8 位 Intel 8080 處理器的 x86 一直在市場上佔主導地位。AMD 和 Intel 對 x64 進行了現實的選擇,以保留與 x86 的回溯相容性。而 Itanium 引進了一個強大的新體繫結構,這種體繫結構不受傳統的 x86 的限制。當然,這樣做的結果是這種體繫結構支援的應用程式相當少,但這是 David Cutler(他領導了 Windows NT 開發)及其可移植作業系統觀點的實證,這種觀點認為 Windows 這些年來已經可以相對輕鬆地採用新的體繫結構。
雖然 C++ 編譯器後隱藏著處理器體繫結構之間的很多不同之處,但開發人員可以清楚地看出這種變化帶來的一個好處,即轉向單個呼叫慣例。x64 版本的 C++ 編譯器只支援單個呼叫慣例,而 x86 版本的編譯器支援多個呼叫慣例。這種變化很受歡迎,因為它可以消除由呼叫慣例不匹配引起的多個潛在錯誤,尤其是在Managed 程式碼互操作中。在Managed 程式碼互操作中,Microsoft .NET Framework 編譯器(如 C# 和 Visual Basic .NET )無法確定 C++ 標頭檔中的原始呼叫慣例。實際上,它們必須依賴於手動定義的屬性,而這些屬性很容易被錯誤定義,從而導致各種堆棧損壞錯誤。
例如,在調用成員函數前,本機 C++ 呼叫慣例 thiscall 使用 ecx 寄存器儲存“this”指標。該資訊有助於調試,但依賴於呼叫慣例,如果只是查看某些組譯工具代碼和寄存器值,呼叫慣例可能並不明顯。這種情況在 x64 上就簡單得多,因為“this”指標始終作為第一個參數插入,因此儲存在 rcx 寄存器中。
當然,在調試過程中使用寄存器是特定於處理器的,但令人欣慰的是我們可以探索 x86 和 x64 之間的關係。x86 體繫結構引入了子寄存器的概念,子寄存器構成父寄存器的下半部分。例如,ax 是一個 16 位的子寄存器,位於 eax 寄存器的下半部分。x64 體繫結構提供了 64 位元寄存器來取代 32 位 x86 寄存器,從而實現了寄存器擴充。例如,在 x64 上,32 位 eax 子寄存器構成 64 位元 rax 寄存器的後半部分。eax 和 rax 均有各自的作用,分別由 x86 和 x64 用於儲存函數返回的指標或整數。毋庸置疑,要使在 x86 上啟動並執行函數返回 64 位元值,需要使用兩個寄存器。
虛擬變數
最常見的虛擬變數可能是 $err,該變數顯示函數 SetLastError 設定的上一錯誤值。顯示的值代表將由 GetLastError 函數返回的值。
虛擬變數還可用於顯示處理器寄存器的值。例如,$ecx 顯示 x86 體繫結構的 ecx 寄存器的值。對於新一代使用 .NET 的開發人員而言,如何查看處理器寄存器的值可能有點高深莫測,但這樣做確實可以協助診斷明顯的錯誤或只瞭解程式運行時的行為。假設您正在 x86 上使用本機 C++ 呼叫慣例,可以使用 $ecx 顯示“this”指標的地址。您還可以在兩個體繫結構上使用 $eax 顯示 32 位傳回值。根據您的調試需要,還可以方便地使用更多寄存器。
其他有用的虛擬變數包括 $handles 和 $user,前者顯示在進程中開啟的核心對象的控制代碼個數,後者顯示有關當前進程和線程令牌的十分詳細的資訊。$user 可用於調試與類比相關的代碼。圖 1 列出了一些常見的虛擬變數。
圖 1 虛擬變數
| 虛擬變數 |
說明 |
| $handles |
核心對象的控制代碼個數 |
| $vframe |
當前堆疊框架地址 |
| $TID |
當前線程標識符 |
| $registername |
指定寄存器的內容 |
| $clk |
刻度表示的時間 |
| $user |
進程和線程令牌資訊 |