本文章是由機器翻譯。

偵錯工具 Api

撰寫偵錯工具視窗延伸

安德路 Richards

下載程式碼範例

生產問題的疑難排解可以是任何工程師可以執行的最令人沮喪工作其中之一。 它也可以是最收穫工作。 使用 Microsoft 支援服務,我被面對這每一天。 為什麼應用程式並未故障? 為什麼沒有它擱置? 為什麼它會遇到效能問題?

學習如何偵錯可以是一件吃力的工作,而且是需要許多數保持熟練的一般練習這些技巧的其中一個。 但是很重要的技術是 「 周圍的開發人員。 儘管如此,由 bottling 的幾個技術偵錯的專家,所有技術層級的偵錯工具工程師可以非常複雜的偵錯邏輯以執行輕鬆執行命令。

無數疑難排解技術可用來取得損毀,但最珍貴的根本原因,並以偵錯技術工程師最 fruitful — 是處理序傾印。 處理序傾印一次的擷取包含處理程序記憶體快照的集。 依據 「 傾印 」 工具,這可能是整個地址空間或部分。

任何應用程式就會擲回未處理例外狀況時,Windows 會自動建立小量傾印到 Windows 錯誤報告 (WER)。 此外,您可以手動建立透過 Userdump.exe 工具的傾印檔案。 Sysinternals 工具 ProcDump(technet.microsoft.com/sysinternals/dd996900) 會成為偏好的處理序傾印工具的 Microsoft 支援服務,因為它可以擷取傾印根據各式各樣不同的觸發程序,可以產生各種大小的傾印。 但是一旦您有傾印資料,您可以做什麼用它協助偵錯嗎?

不同版本的 Visual Studio 支援開啟傾印檔案 (.dmp),但若要使用 「 最佳 」 工具是偵錯工具從偵錯工具的視窗。 這些工具對於完全依據單一的偵錯引擎都支援兩個偵錯工具擴充的 Api。 在本文中我要輕鬆涵括了基礎的建造自訂偵錯工具擴充功能,所以您可以分析這些傾印檔案 (和也 live 系統)。

設定工具

偵錯工具的視窗 (microsoft.com/whdc/devtools/debugging) 是 Windows SDK 和 Windows 驅動程式套件 (WDK) 的可安裝及可轉散發元件。 當我撰寫這時,目前的版本是 6.12,並且可用於版本 7.1 Windows SDK 或 WDK。 我建議使用的最新版本,因為偵錯引擎必須許多有用的功能,包括更好的堆疊查核行程。

請說出的偵錯工具 Windows 的指導方針您應該編譯使用 WDK 建置環境的偵錯工具擴充功能。 我使用最新版的 WDK (版本 7.1.0 組建 7600.16385.1),但任何版本的 WDK 或其先前的形態驅動程式開發套件 (DDK) 就夠了。 在建置時使用 WDK 副檔名,您會使用 x64 和 x86 可用建置環境。

利用最少的努力,您也可以採用我要在 Windows SDK 中建置的專案建置環境或 Visual Studio。

記下一個警告:WDK 也不喜歡路徑名稱中的空格。 請確定您編譯從未中斷的路徑。 例如,使用像 C:\Projects 代替 C:\Users\Andrew Richards\Documents\Projects。

不論如何建置副檔名時,您必須偵錯工具 SDK,也就是針對 Windows 偵錯工具的元件的標頭和程式庫檔案。 參考的標頭和程式庫檔案時,這份文件中的範例會使用我 x 86 路徑 (C:\debuggers_x86\sdk)。 如果您選擇安裝偵錯工具其他位置,請記得更新的路徑,並加上引號時不必容納空格的路徑。

使用偵錯工具

對於 Windows 偵錯工具偵錯工具是架構無關。 任何版本的偵錯工具可以偵錯任何目標架構。 常見的範例使用 x 64 偵錯工具來偵錯 x x86 應用程式。 偵錯工具已發行以供 x86,x64 (amd64) 和 ia64 架構下,但它可以偵錯 x86,x 64,ia64 架構下,ARM,EBC 和 PowerPC (Xbox) 應用程式。 您可以安裝所有的偵錯工具版本--並排。

這個靈活度不是內文 」 普遍瞭解,不過。 並非所有的偵錯工具擴充功能以及偵錯工具引擎不會採用目標架構。 有些偵錯工具擴充功能會假設目標的指標大小會與偵錯工具的指標大小相同。 同樣地,使用錯誤的硬式編碼暫存器 (代替 rsp,例如 esp),而不要例如 $csp pseudo-register。

如果您無法偵錯工具擴充功能發生問題,您應該嘗試執行目標環境的相同架構是為了偵錯工具。 這可能會解決不良的假設寫入延伸模組。

每個應用程式建置類型和相關聯的處理器架構隨附自己的偵錯問題讓您頭痛集。 針對偵錯組建中產生組譯工具是相當呈線性關係,但是組譯工具產生在發行組建經過最佳化,並可以類似的之杯足球聯賽 」。 在 x86 架構,框架指標省略 (FPO) 會播放 (最新的偵錯工具處理這個格式) 的呼叫堆疊重建與浩劫。 在 x64 架構、 函式參數和區域變數會儲存在暫存器。 傾印擷取時,它們可能已被推入堆疊,或可能不存在的暫存器重複使用受限於。

經驗是這裡的重點。 若要精確,一個人的經驗是這裡的重點。 您只需要 bottle 中其他的偵錯工具擴充其實用知識。 之前我將其自動化為偵錯工具擴充功能只需要幾個類似偵錯序列的重複次數。 我已經使用過多我忘記我未使用的基礎的偵錯命令的相同項目如何 my 擴充部分。

使用偵錯工具 Api

有兩個偵錯工具擴充的 Api:已取代的 WdbgExts API (wdbgexts.h) 和目前的 DbgEng API (dbgeng.h)。

WdbgExts 延伸模組的基礎設定在初始化 (WinDbgExtensionDllInit) 全域呼叫:

WINDBG_EXTENSION_APIS ExtensionApis;

全域提供執行功能,例如 dprintf("\n") 和 GetExpression("@$csp") 而不需任何命名空間所需的功能。 這種類型擴充的類似執行在 Win32 程式設計時,您可以撰寫程式碼。

DbgEng 延伸模組的基礎偵錯工具介面。 IDebugClient 介面是以傳給您的偵錯引擎為每個呼叫的參數。 介面會支援 QueryInterface 不斷增加的偵錯工具介面範圍存取。 這種類型擴充的類似進行 COM 程式設計時,您可以撰寫程式碼。

您仍可進行的兩個延伸型別組合。 您公開 (expose) 的副檔名為 DbgEng,但在執行階段透過 IDebugControl::GetWindbgExtensionApis64 呼叫加入 WdbgExts API 的功能。 例如,我曾寫過傳統"Hello World"為 C.中副檔名為 DbgEng 如果您喜歡 c + +,請參閱偵錯工具 SDK 中的 ExtException 類別 (。 \inc\engextcpp.cpp)。

編譯為 MyExt.dll 的副檔名 (在來源檔案中所示的 TARGETNAME 圖 1)。 它會公開呼叫的命令! helloworld。 擴充動態連結至 Microsoft Visual C 執行階段 (MSVCRT)。 如果您想要使用靜態、 變更 USE_MSVCRT = 1 陳述式 USE_LIBCMT = 1 來源檔案中的。

圖 1來源

TARGETNAME=MyExt
TARGETTYPE=DYNLINK
_NT_TARGET_VERSION=$(_NT_TARGET_VERSION_WINXP)
DLLENTRY=_DllMainCRTStartup
!if "$(DBGSDK_INC_PATH)" != ""
INCLUDES = $(DBGSDK_INC_PATH);$(INCLUDES)
!endif
!if "$(DBGSDK_LIB_PATH)" == ""
DBGSDK_LIB_PATH = $(SDK_LIB_PATH)
!else
DBGSDK_LIB_PATH = $(DBGSDK_LIB_PATH)\$(TARGET_DIRECTORY)
!endif
TARGETLIBS=$(SDK_LIB_PATH)\kernel32.lib \
           $(DBGSDK_LIB_PATH)\dbgeng.lib
USE_MSVCRT=1
UMTYPE=windows
MSC_WARNING_LEVEL = /W4 /WX
SOURCES= dbgexts.rc      \
         dbgexts.cpp     \
         myext.cpp

DebugExtensionInitialize 函式 (請參閱 圖 2) 載入延伸模組時,會呼叫。 設定參數的版本使用的 EXT_MAJOR_VER 和 EXT_MINOR_VER 的 DEBUG_EXTENSION_VERSION 巨集的簡單事宜 # 定義我已經加入標頭檔:

// dbgexts.h
#include <windows.h>
#include <dbgeng.h>
#define EXT_MAJOR_VER  1
#define EXT_MINOR_VER  0

圖 2dbgexts.cpp

// dbgexts.cpp
#include "dbgexts.h"
extern "C" HRESULT CALLBACK
DebugExtensionInitialize(PULONG Version, PULONG Flags) {
  *Version = DEBUG_EXTENSION_VERSION(EXT_MAJOR_VER, EXT_MINOR_VER);
  *Flags = 0;  // Reserved for future use.
  return S_OK;
}
extern "C" void CALLBACK
DebugExtensionNotify(ULONG Notify, ULONG64 Argument) {
  UNREFERENCED_PARAMETER(Argument);
  switch (Notify) {
    // A debugging session is active. The session may not necessarily be suspended.
    case DEBUG_NOTIFY_SESSION_ACTIVE:
      break;
    // No debugging session is active.
    case DEBUG_NOTIFY_SESSION_INACTIVE:
      break;
    // The debugging session has suspended and is now accessible.
    case DEBUG_NOTIFY_SESSION_ACCESSIBLE:
      break;
    // The debugging session has started running and is now inaccessible.
    case DEBUG_NOTIFY_SESSION_INACCESSIBLE:
      break;
  }
  return;
}
extern "C" void CALLBACK
DebugExtensionUninitialize(void) {
  return;
}

版本值被回報為偵錯工具.chain 命令中的 API 版本。 若要變更的檔案版本,檔案描述著作權和其他值,您需要編輯 dbgexts.rc 檔案:

myext.dll: image 6.1.7600.16385, API 1.0.0, built Wed Oct 13 20:25:10 2010
  [path: C:\Debuggers_x86\myext.dll]

Flags 參數將保留,而且應該會設定為零。 此函式必須傳回 S_OK。

工作階段變更其使用中或可存取的狀態時,會呼叫 DebugExtensionNotify 函式。 若要排除未使用的參數編譯器警告 UNREFERENCED_PARAMETER 巨集被包裹引數參數。

我已將新增的完整性,通知參數的 switch 陳述式,但我沒有加入任何功能的程式碼在這個區域中。 Switch 陳述式處理四個工作階段狀態的變更:

  • 當您附加至目標時,就會發生 DEBUG_NOTIFY_SESSION_ACTIVE。
  • DEBUG_NOTIFY_SESSION_INACTIVE 發生於目標就會變成卸離 (透過.detach 或 qd)。
  • 如果目標暫止 (一個中斷點,例如點閱率),函式會傳遞 DEBUG_NOTIFY_SESSION_ACCESSIBLE。
  • 如果執行中,目標履歷表函式會傳遞 DEBUG_NOTIFY_SESSION_INACCESSIBLE。

延伸模組卸載時,會呼叫 DebugExtensionUninitialize 函數。

若要公開每個延伸模組命令被宣告為型別 PDEBUG_EXTENSION_CALL 的函式。 函式的名稱是延伸模組命令的名稱。 因為我撰寫"Hello World",我已命名函式 helloworld (請參閱 圖 3)。

圖 3 MyExt.cpp

// MyExt.cpp
#include "dbgexts.h"
HRESULT CALLBACK 
helloworld(PDEBUG_CLIENT pDebugClient, PCSTR args) {
  UNREFERENCED_PARAMETER(args);
  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl))) {
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "Hello World!\n");
    pDebugControl->Release();
  }
  return S_OK;
}

請注意,加入慣例是將使用小寫的函式名稱。 因為我使用頭戴式 WDK 建置環境,myext.def 檔也需要變更。 延伸模組命令的名稱必須被加入,讓它匯出:

;-------------
;   MyExt.def
;-------------
EXPORTS
  helloworld
  DebugExtensionNotify
  DebugExtensionInitialize
  DebugExtensionUninitialize

引數參數會包含指令的引數的字串。 參數被傳遞為以 null 結尾的 ANSI 字串 (CP_ACP)。

PDebugClient 參數是可讓偵錯引擎進行互動的擴充功能的 IDebugClient 介面指標。 雖然的介面指標看起來像它是 COM 介面指標,它無法封送處理,或是在稍後存取。 它也不能從任何其他執行緒。 若要在其他的執行緒上執行作業,必須使用 IDebugClient::CreateClient 該執行緒上建立新的偵錯工具用戶端 (新介面指標,IDebugClient)。 這是唯一可以在其他的執行緒執行的函式。

(就像所有的介面) 的 IDebugClient 介面被衍生自 IUnknown。 您可以使用 QueryInterface 來存取其他 DbgEng 介面,不論它們較新版的 IDebugClient 介面 (IDebugClient4) 或不同的介面 (IDebugControl、 IDebugRegisters、 IDebugSymbols、 IDebugSystemObjects 等等)。 若要輸出至偵錯工具的文字,您需要 IDebugControl 介面。

我要幫助您進行開發的資料夾中有兩個非 SDK 檔案。 Make.cmd 指令碼的偵錯工具 SDK 收益及和 lib 路徑加入 WDK 建置環境,便會執行適當的組建命令:

@echo off
set DBGSDK_INC_PATH=C:\Debuggers_x86\sdk\inc
set DBGSDK_LIB_PATH=C:\Debuggers_x86\sdk\lib
set DBGLIB_LIB_PATH=C:\Debuggers_x86\sdk\lib
build -cZMg %1 %2

附註 WDK 建置環境本身決定是否將建置 x86 或 x64 二進位檔。 如果您想要建立多個架構,您必須開啟多個提示,然後在每個執行 make.cmd。 可以同時進行建置。

一旦建置完成後,我使用 (x86) test.cmd 指令碼,將已編譯的 i386 二進位碼檔案複製到 x 86 偵錯工具資料夾 (c:\Debuggers_x86),然後啟動 [記事本] 的執行個體附加偵錯工具與載入的擴充:

@echo off
copy objfre_win7_x86\i386\myext.dll c:\Debuggers_x86
copy objfre_win7_x86\i386\myext.pdb c:\Debuggers_x86
\Debuggers_x86\windbg.exe -a myext.dll -x notepad

如果所有項目已經與計劃,我可以鍵入"! helloworld 」 在 [偵錯工具命令提示字元] 和 [請參閱"Hello World!"回應:

0:000> !helloworld
Hello World!

符號解析度和讀取

"Hello World"應用程式可能很驚人,但您可以不會比較好。 我現在要用於此基礎結構新增指令,實際上目標之間的互動,並可協助您進行一些分析。 簡單的 test01 應用程式具有通用的指標,這項弱點已指派一個值:

// test01.cpp
#include <windows.h>
void* g_ptr;
int main(int argc, char* argv[]) {
  g_ptr = "This is a global string";
  Sleep(10000);
  return 0;
}

新! MyExt.cpp 中的 gptr 命令 (請參閱 圖 4) 將會解決 test01! g_ptr 全域性的讀取指標,然後再輸出相同的格式中找到的值"x test01! g_ptr":

0:000> x test01!g_ptr
012f3370 Test01!g_ptr = 0x012f20e4
0:000> !gptr
012f3370 test01!g_ptr = 0x012f20e4
<string>

圖 4 修訂 MyExt.cpp

HRESULT CALLBACK 
gptr(PDEBUG_CLIENT pDebugClient, PCSTR args) {
  UNREFERENCED_PARAMETER(args);
  IDebugSymbols* pDebugSymbols;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugSymbols), 
    (void **)&pDebugSymbols))) {  
    // Resolve the symbol.
    ULONG64 ulAddress = 0;
    if (SUCCEEDED(pDebugSymbols->GetOffsetByName("test01!g_ptr", &ulAddress))) {
      IDebugDataSpaces* pDebugDataSpaces;
      if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugDataSpaces),
        (void **)&pDebugDataSpaces))) {  
        // Read the value of the pointer from the target address space.
        ULONG64 ulPtr = 0;
        if (SUCCEEDED(pDebugDataSpaces->ReadPointersVirtual(1, ulAddress, &ulPtr))) {
          PDEBUG_CONTROL pDebugControl;
          if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
            (void **)&pDebugControl))) {  
            // Output the values.
            pDebugControl->Output(DEBUG_OUTPUT_NORMAL, 
              "%p test01!g_ptr = 0x%p\n", ulAddress, ulPtr);
            pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "%ma\n", ulPtr);
            pDebugControl->Release();
          }
        }
        pDebugDataSpaces->Release();
      }
      pDebugSymbols->Release();
    }
  }
  return S_OK;
}

第一個步驟是判定 test01 位置! g_ptr 指標。 指標將會在不同的位置每次應用程式執行時,因為地址空間配置隨機 (ASLR) 將會變更的模組載入位址。 若要取得的位置,我可以使用 QueryInterface 來取得 IDebugSymbols 介面,然後再使用 [GetOffsetByName。 GetOffsetByName 函數使用的符號名稱,然後傳回地址的 64 位元的指標。 偵錯工具函式永遠傳回 64 位元指標 (ULONG64),所以 64 位元目標才能進行偵錯 32 位元偵錯工具。

請記住,這不是在目標地址空間中,將指標位址自己。 您只是無法讀取從它判斷它的值。 若要取得的指標值,我可以再次使用 QueryInterface,以取得 IDebugDataSpaces 介面,然後再使用 [ReadPointersVirtual。 這會從目標地址空間讀取指標。 ReadPointersVirtual 會自動調整指標大小和位元組由小到大的差異。 您不需要處理傳回的指標。

IDebugControl::Output 會採用相同的格式字串為 printf,不過也允許您參考的目標位址空間的格式子。 我使用 %ma 格式來印出的 ANSI 字串通用指標指向目標中的地址空間。 %P 格式器指標大小-識別,且應用於指標輸出 (您必須傳遞 ULONG64)。

我已經修改測試指令碼,以載入傾印檔案的 x86 版本的 test01 而非啟動 「 記事本 」:

@echo off
copy objfre_win7_x86\i386\myext.dll c:\Debuggers_x86
copy objfre_win7_x86\i386\myext.pdb c:\Debuggers_x86
\Debuggers_x86\windbg.exe -a myext.dll -y "..\Test01\x86;SRV*c:\symbols*http://msdl.microsoft.com/download/symbols" -z ..\Test01\x86\Test01.dmp

我也已設定為 x 86 資料夾和 Microsoft 公用符號伺服器 test01 的符號路徑,如此所有項目可以解析。此外,我所做不會相同於 x 86 測試指令碼,但與 x64 版本的測試應用程式的傾印檔案 x 64 測試指令碼:

@echo off
copy objfre_win7_x86\i386\myext.dll c:\Debuggers_x86
copy objfre_win7_x86\i386\myext.pdb c:\Debuggers_x86
\Debuggers_x64\windbg.exe -a myext.dll -y "..\Test01\x64;SRV*c:\symbols*http://msdl.microsoft.com/download/symbols" -z ..\Test01\x64\Test01.dmp

當我執行的指令碼,就會啟動偵錯工具,x86 開啟適當的傾印檔案,x86 版本擴充功能的載入及符號都可以解析。

同樣地,如果所有項目已計劃,我就可以輸入"x test01! g_ptr"和! gptr 偵錯工具命令提示字元,並查看類似的回應:

// x86 Target
0:000> x test01!g_ptr
012f3370 Test01!g_ptr = 0x012f20e4
0:000> !gptr
012f3370 test01!g_ptr = 0x012f20e4
This is a global string
// x64 Target
0:000> x test01!g_ptr
00000001`3fda35d0 Test01!g_ptr = 0x00000001`3fda21a0
0:000> !gptr
000000013fda35d0 test01!g_ptr = 0x000000013fda21a0
This is a global string

如果您重複使用 x 64 偵錯工具、 偵錯工具擴充和 x86 或 x64 傾印檔案的 amd64 編譯版本的測試,您會得到相同的結果。 也就是說,該擴充程式架構無關。

處理器類型和堆疊

我現在要再一次擴充此基礎結構。 讓我們來新增命令,以尋找目前的執行緒堆疊上的睡眠呼叫持續時間。 ! 催眠命令 (請參閱 圖 5) 將會解決和呼叫堆疊的符號,查看睡眠函式和讀取 DWORD,表示毫秒延遲時間,請再將輸出延遲值 (如果找到)。

圖 5催眠

HRESULT CALLBACK 
sleepy(PDEBUG_CLIENT4 Client, PCSTR args) {
  UNREFERENCED_PARAMETER(args);
  BOOL bFound = FALSE;
  IDebugControl* pDebugControl;
  if (SUCCEEDED(Client->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl))) {
    IDebugSymbols* pDebugSymbols;
    if (SUCCEEDED(Client->QueryInterface(__uuidof(IDebugSymbols), 
      (void **)&pDebugSymbols))) {
      DEBUG_STACK_FRAME* pDebugStackFrame = 
        (DEBUG_STACK_FRAME*)malloc(
        sizeof(DEBUG_STACK_FRAME) * MAX_STACK_FRAMES);
      if (pDebugStackFrame != NULL) {  
        // Get the Stack Frames.
        memset(pDebugStackFrame, 0, (sizeof(DEBUG_STACK_FRAME) * 
          MAX_STACK_FRAMES));
        ULONG Frames = 0;
        if (SUCCEEDED(pDebugControl->GetStackTrace(0, 0, 0, 
          pDebugStackFrame, MAX_STACK_FRAMES, &Frames)) && 
          (Frames > 0)) {
          ULONG ProcessorType = 0;
          ULONG SymSize = 0;
          char SymName[4096];
          memset(SymName, 0, 4096);
          ULONG64 Displacement = 0;
          if (SUCCEEDED(pDebugControl->GetEffectiveProcessorType(
            &ProcessorType))) {
            for (ULONG n=0; n<Frames; n++) {  
              // Use the Effective Processor Type and the contents 
              // of the frame to determine existence
              if (SUCCEEDED(pDebugSymbols->GetNameByOffset(
                pDebugStackFrame[n].InstructionOffset, SymName, 4096, 
                &SymSize, &Displacement)) && (SymSize > 0)) {
                if ((ProcessorType == IMAGE_FILE_MACHINE_I386) && 
                  (_stricmp(SymName, "KERNELBASE!Sleep") == 0) && 
                  (Displacement == 0xF)) {  
                  // Win7 x86; KERNELBASE!Sleep+0xF is usually in frame 3.
                  IDebugDataSpaces* pDebugDataSpaces;
                  if (SUCCEEDED(Client->QueryInterface(
                    __uuidof(IDebugDataSpaces), 
                    (void **)&pDebugDataSpaces))) {  
                    // The value is pushed immediately prior to 
                    // KERNELBASE!Sleep+0xF
                    DWORD dwMilliseconds = 0;
                    if (SUCCEEDED(pDebugDataSpaces->ReadVirtual(
                      pDebugStackFrame[n].StackOffset, &dwMilliseconds, 
                      sizeof(dwMilliseconds), NULL))) {
                      pDebugControl->Output(DEBUG_OUTPUT_NORMAL, 
                        "Sleeping for %ld msec\n", dwMilliseconds);
                      bFound = TRUE;
                    }
                    pDebugDataSpaces->Release();
                  }
                  if (bFound) break;
                }
                else if ((ProcessorType == IMAGE_FILE_MACHINE_AMD64) && 
                  (_stricmp(SymName, "KERNELBASE!SleepEx") == 0) && 
                  (Displacement == 0xAB)) {  
                  // Win7 x64; KERNELBASE!SleepEx+0xAB is usually in frame 1.
                  IDebugRegisters* pDebugRegisters;
                  if (SUCCEEDED(Client->QueryInterface(
                    __uuidof(IDebugRegisters), 
                    (void **)&pDebugRegisters))) {  
                    // The value is in the 'rsi' register.
                    ULONG rsiIndex = 0;
                    if (SUCCEEDED(pDebugRegisters->GetIndexByName(
                      "rsi", &rsiIndex)))
                    {
                      DEBUG_VALUE debugValue;
                      if (SUCCEEDED(pDebugRegisters->GetValue(
                        rsiIndex, &debugValue)) && 
                        (debugValue.Type == DEBUG_VALUE_INT64)) {  
                        // Truncate to 32bits for display.
                        pDebugControl->Output(DEBUG_OUTPUT_NORMAL, 
                          "Sleeping for %ld msec\n", debugValue.I32);
                        bFound = TRUE;
                      }
                    }
                    pDebugRegisters->Release();
                  }
                  if (bFound) break;
                }
              }
            }
          }
        }
        free(pDebugStackFrame);
      }
      pDebugSymbols->Release();
    }
    if (!bFound)
      pDebugControl->Output(DEBUG_OUTPUT_NORMAL, 
        "Unable to determine if Sleep is present\n");
    pDebugControl->Release();
  }
  return S_OK;
}

若要增加了一點複雜性的命令,命令會支援 test01 應用程式的 x86 和 x64 版本。 X86 和 x64 應用程式各有不同的呼叫慣例,因為命令必須要注意的目標的架構執行時。

第一個步驟是取得堆疊框架。 若要取得圖文框,我使用 QueryInterface 來取得 IDebugControl 介面,然後使用 GetStackTrace 來擷取每個堆疊框架的相關資訊。 GetStackTrace 會使用 DEBUG_STACK_FRAME 結構的陣列。 我永遠配置在堆積上的 DEBUG_STACK_FRAME 結構的陣列,讓我不會造成堆疊溢位。 如果您是 retrieving 堆疊溢位執行緒的目標,,可能會叫用您自己的堆疊限制,如果在堆疊上配置陣列。

如果成功 GetStackTrace,陣列會填入資訊已受到每個框架。 在這裡的豐功並不一定表示框架資訊正確無誤。 偵錯工具做到逐步的堆疊框架,但無法正確 (當它們遺漏了或被強制) 符號時,可以所做的錯誤。 如果您使用 「.reload /f /i 「 若要強制符號負載,不良的符號對齊方式將可能發生。

若要有效地使用的 DEBUG_STACK_FRAME 結構的每個內容,我需要知道目標的有效的處理器型別。 前面提過,可以是完全不同於偵錯工具擴充架構的目標架構。 有效的處理器型別 (.effmach) 是目前使用目標的架構。

處理器型別也可以是不同的處理器類型而使用的目標的主應用程式。 這最常見的範例是在 x 64 版本的 Windows x x86 應用程式透過 Windows 32 位元 Windows 64 位元 (WOW64) 上執行的目標時。 有效的處理器型別是 IMAGE_FILE_MACHINE_I386。 實際型別是 IMAGE_FILE_MACHINE_AMD64。

這表示您應該考慮為不論是否正在執行的 Windows x 86 版本或 x 64 版本的 Windows 上 x x86 應用程式 x x86 應用程式。 (這項規則的唯一例外是當您正在偵錯應用程式會呼叫該範圍陳述式 x86 處理程序)。

若要取得有效的處理器型別,我使用我已經有,並依照 GetEffectiveProcessorType 的 IDebugControl 介面。

如果 i386 為有效的處理器型別,我需要尋求 KERNELBASE!睡眠 + 0xf 函式。 如果所有符號會正確地都解析,此函式應該框架 3 中:

0:000> knL4
 # ChildEBP RetAddr  
00 001bf9dc 76fd48b4 ntdll!KiFastSystemCallRet
01 001bf9e0 752c1876 ntdll!NtDelayExecution+0xc
02 001bfa48 752c1818 KERNELBASE!SleepEx+0x65
03 001bfa58 012f1015 KERNELBASE!Sleep+0xf

如果有效的處理器型別是 AMD64,我會尋求 KERNELBASE!SleepEx + 0xab 函式。如果所有符號會正確地都解析,此函式應該框架 1 中:

0:000> knL2
 # Child-SP          RetAddr           Call Site
00 00000000'001cfc08 000007fe'fd9b1203 ntdll!NtDelayExecution+0xa
01 00000000'001cfc10 00000001'3fda101d KERNELBASE!SleepEx+0xab

不過,根據可用的符號解析的層級,我要尋找的函式符號可能或可能不是預期畫面格。 如果您開啟 test01 x86 傾印檔案,並不指定符號路徑,您所見的範例。 KERNELBASE!睡眠呼叫會在框架 1 而不是框架 3 是:

0:000> knL4
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 001bfa48 752c1818 ntdll!KiFastSystemCallRet
01 001bfa58 012f1015 KERNELBASE!Sleep+0xf
02 001bfaa4 75baf4e8 Test01+0x1015
03 001bfab0 76feaf77 kernel32!BaseThreadInitThunk+0x12

偵錯工具會警告您此可能犯下類似。 如果您想要您適應這類問題的擴充功能,您應該逐一框架當我使用,而不是預期畫面格,只看到。

若要決定睡眠函式存在,我必須查詢每個框架的符號。 如果有效的處理器型別和符號進行有效的組,已經找到函式。 請注意這個邏輯很容易發生問題,而且被用來簡化此範例。 符號可能會變更的組建與平台之間。 比方說,Windows Server 2008 是 kernel32!睡眠 + 0xf,但 Windows 7 是 KERNELBASE!睡眠 + 0xf。

若要取得該符號,我可以使用 QueryInterface 來取得 IDebugSymbol 介面。 然後,我會使用 GetNameByOffset 來取得指令位移位址的符號。

有兩個部分符號:符號名稱 (KERNELBASE!睡眠),並加上位移 (0xf)。 符號名稱為 amalgamation 的模組名稱和函式名稱 (<module>! <function>)。 加上位移是從的程式流程會返回後傳回呼叫的函式開頭的位元組位移。

函式沒有符號時,將會報告為只是大型加上位移的模組名稱 (Test01 + 0x1015)。

一旦我發現框架下, 一個步驟是擷取延遲。 當目標是 x86 架構時,延遲會在項目已推至堆疊將函式呼叫 (請注意這是脆弱的邏輯) 之前 DWORD 中:

// @$csp is the pseudo-register of @esp
0:000> dps @$csp
<snip>
001bfa4c  752c1818 KERNELBASE!Sleep+0xf
001bfa50  00002710
<snip>

DEBUG_STACK_FRAME 結構的 StackOffset 成員實際上指向這個地址,所以沒有指標算術時需要。若要取得值,我使用 QueryInterface 來取得 IDebugDataSpaces 介面,並依照 ReadVirtual DWORD 讀取的目標位址空間。

如果目標是基礎的 x64,延遲不是在堆疊中 — 它處於 rsi 暫存器 (這也是易損壞的邏輯,其框架內容相依性受限於):

0:000> r @rsi
rsi=0000000000002710

若要取得值,我可以使用 QueryInterface 來取得 IDebugRegisters 介面。我首先必須使用 GetIndexByName 來取得 rsi 暫存器的索引。我接著會使用 GetValue 讀取目標暫存器中的暫存器值。由於 rsi 是 64 位元暫存器,則會傳回值當做 INT64。因為 DEBUG_VALUE 結構是聯集,您只會參考 I32 成員,而非 I64 成員,才能取得截斷後的版本,表示傳遞至睡眠的 DWORD。

同樣地,在這兩種情況下,我使用 IDebugControl::Output 函式來輸出結果。

中斷!

在本文中我幾乎沒有粗略介紹所能達到。堆疊、 符號、 暫存器、 記憶體 I/O 及環境的資訊是但有許多事情一些您可以詢問和延伸內變更。

在未來的文章我會深入探究偵錯工具擴充功能可以讓偵錯工具的關係。偵錯工具用戶端和偵錯工具回呼,稍後我將討論,我要使用這些來封裝 SOS 偵錯工具擴充功能,讓您可以撰寫可以進行偵錯而不需要具備任何知識基礎的管理的應用程式的副檔名。NET 結構。

Andrew Richards 是 Microsoft] 資深重大問題工程師的 Exchange 伺服器。.他熱愛支援工具],並持續地建立 「 偵錯工具 」 擴充程式和簡化的技術支援工程師工作的應用程式。

多虧了要對下列技術專家,來檢閱這份文件:Drew Bliss,Jen-Lung Chiu,Mike Hendrickson,Ken Johnson,Brunda NagalingaiahMatt Weber