本文章是由機器翻譯。

偵錯工具引擎 API

撰寫適合 Windows 延伸模組的偵錯工具,第 2 篇:輸出

安德路 Richards

下載程式碼範例

在第二個本期的我關於偵錯工具 API 的數列,我將告訴您如何,您可以增強您偵錯工具引擎 (DbgEng) 的擴充所產生的輸出。這麼做時,不過,您可以分為的設陷數量。我希望反白顯示所有的陷阱了。

之後,再閱讀上,您會想要有讀取先前的一篇,若要了解何種偵錯工具擴充功能是 (以及如何我是建置和測試這份文件中的範例)。您可以讀取它在msdn.microsoft.com/magazine/gg650659

偵錯工具標記語言

偵錯工具標記語言 (DML) 是 HTML 當場就迫不及待標記語言。它支援透過粗體/斜體/底線強調和透過超連結巡覽。DML 已加入至版本 6.6 偵錯工具 API。

Windows Vista Windows SDK 首先出貨版本 6.6.7.5 此 api,並支援 x86、 x64 和 IA64。Windows 7 /。NET 3.5 SDK/WDK 出貨下一個發行版本 (版本 6.11.1.404)。Windows 7 /。NET 4 SDK/WDK 船目前的版本 (版本 6.12.2.633)。Windows 7 /。NET 4 發行載具是從 Microsoft 取得偵錯工具的視窗的最新版本的唯一方法。可用的 x86、 x64 或 IA64 封裝沒有直接下載。請注意版本 6.6 中定義的 DML 相關 Api 不詳述這些後續版本。它們,不過,沒有值得 DML 支援與相關的修正程式。

' Hello DML 世界 '

您大概猜得到,用來強調 DML 的標記是 HTML 被使用時的相同標記。若要標記的文字為粗體,請使用"<b> … </b>"; 斜體,使用"<i> … </i>"; 並為底線使用"<u> … </u>"。圖 1顯示命令輸出"Hello DML World!"與標記這些三種類型的範例。

圖 1**! hellodml 實作**

HRESULT CALLBACK 
hellodml(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  UNREFERENCED_PARAMETER(args);

  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    pDebugControl->ControlledOutput(
      DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL,  
      "<b>Hello</b> <i>DML</i> <u>World!</u>\n");
    pDebugControl->Release();
  }
  return S_OK;
}

若要測試擴充功能,我必須呼叫 test_windbg.cmd 本文所附的程式碼下載的 Example04 資料夾中的指令碼。 指令碼會將延伸模組複製到 C:\Debuggers_x86 資料夾。 指令碼然後啟動 WinDbg,載入擴充功能,並啟動 [記事本] (做為偵錯目標) 的新執行個體。 如果所有項目已經根據到計劃,我可以鍵入"! hellodml"在 [偵錯工具的命令提示字元,請參閱使用粗體、 斜體和底線標記的"Hello dml World!"回應:

0: 000 > ! hellodml
Hello DML World!

我也有稱為 test_ntsd.cmd 的相同的步驟,但載入 NTSD 偵錯工具的指令碼。 如果我輸入"! hellodml"在此偵錯工具的命令提示字元中,我看到"Hello dml World!"的回應,但沒有標記。 DML 被轉換成文字,因為 NTSD 是一個純文字的偵錯用戶端。 當 DML outputted 為文字 (僅限) 時,會移除掉所有標記-依據用戶端:

0: 000 > ! hellodml
DML 大家好 !

標記

太像使用 HTML 時,您需要仔細地開始和結束任何標記。 圖 2有簡單擴充命令 (! echoasdml),會回應命令引數當做 DML,具有標記之前, 和之後的 DML 輸出做為文字輸出。

圖 2**! echoasdml 實作**

HRESULT CALLBACK 
echoasdml(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "[Start DML]\n");
    pDebugControl->ControlledOutput(
      DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_NORMAL, "%s\n", args);
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "[End DML]\n");
    pDebugControl->Release();
  }
  return S_OK;
}

本範例序列如果您不要關閉標記,會發生什麼事所示:

0: 000 > ! echoasdml Hello World
[啟動 DML]
大家好
[結束 DML]

0: 000 > ! echoasdml <b> Hello World </b>
[啟動 DML]
大家好
[結束 DML]
 
0: 000 > ! echoasdml <b> Hello
[啟動 DML]
大家好
[結束 DML]

0: 000 > ! echoasdml 世界 </b>
[啟動 DML]
世界
[結束 DML]

"<b> Hello"命令樹葉粗體啟用,導致所有以下擴充和提示字元輸出顯示為粗體。 這是不論是否已在文字或 DML 模式 outputted 文字。 您所預期,後續關閉粗體的標記會還原狀態。

字串具有其中的 XML 標記時另一個常見的問題。 輸出可能會是截斷,如會發生在第一個範例中,或無法移除 XML 標記:

0: 000 > ! echoasdml < xml
[啟動 DML]
 
0: 000 > ! echoasdml <xml> Hello World </xml>
[啟動 DML]
大家好
[結束 DML]

您處理這個相同的方式如同 HTML:逸出序列之前輸出字串。 您可以自己要親自執行,或替您完成將偵錯工具。 需要逸出的四個字元是 &、 <,> 和"。 對等的逸出的版本如下:"& amp;","& lt;","& gt;"和"& quot;"。

圖 3示範逸出序列。

圖 3**! echoasdmlescape 實作**

HRESULT CALLBACK 
echoasdmlescape(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "[Start DML]\n");
    if ((args != NULL) && (strlen(args) > 0))
    {
      char* szEscape = (char*)malloc(strlen(args) * 6);
      if (szEscape == NULL)
      {
        pDebugControl->Release();
        return E_OUTOFMEMORY;
      }
      size_t n=0; size_t e=0;
      for (; n<strlen(args); n++)
      {
        switch (args[n])
        {
          case '&':
                memcpy(&szEscape[e], "&amp;", 5);
                e+=5;
                break;
          case '<':
                memcpy(&szEscape[e], "&lt;", 4);
                e+=4;
                break;
          case '>':
                memcpy(&szEscape[e], "&gt;", 4);
                e+=4;
                break;
          case '"':
                memcpy(&szEscape[e], "'", 6);
                e+=6;
                break;
          default:
                szEscape[e] = args[n];
                e+=1;
                break;
        }
      }
      szEscape[e++] = '\0';
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML,  
        DEBUG_OUTPUT_NORMAL, "%s\n", szEscape);
      free(szEscape);
    }
    pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "[End DML]\n");
    pDebugControl->Release();
  }
  return S_OK;
}

[Echoasdmlescape] 指令會配置新的緩衝區的原始大小六次。 這是空間不足,無法完全處理一個引數字串 」 字元。 此函式會逐一查看 (這是 ANSI) 的引數字串,並將適當的文字加入至緩衝區。 然後會與傳遞至 IDebugClient::ControlledOutput 函式 %s 格式子一起使用逸出編序緩衝區。 ! Echoasdmlescape 命令回音引數沒有被解譯為 DML 標記的字串:

0: 000 > ! echoasdmlescape < xml
[啟動 DML]
< xml
[結束 DML]
 
0: 000 > ! echoasdmlescape <xml> Hello World </xml>
[啟動 DML]
<xml> Hello World </xml>
[結束 DML]

請注意與某些字串,您可能仍未看到您預期,輸出指定所提供的輸入。 這些不一致之處沒有任何項目如何處理逸出序列 (或 DML); 它們是由偵錯工具的剖析器所造成。 附註的兩個案例是" 字元 (字串內容) 和";"字元 (命令終止):

0: 000 > ! echoasdmlescape"Hello World"
[啟動 DML]
大家好
[結束 DML]
 
0: 000 > ! echoasdmlescape Hello World 中;
[啟動 DML]
大家好
[結束 DML]
 
0: 000 > ! echoasdmlescape"Hello World";
[啟動 DML]
大家好;
[結束 DML]

您不需要經過如此逸出序列大費周章自己,不過。 偵錯工具支援特殊的格式子,這種情況下。 而不是產生的逸出編序字串,然後使用 [%s 格式子,只要在原始字串上使用 %y {t} 格式子。

如果您使用的記憶體,格式子,您也可以避免逸出序列投入比。 %Ma、 %mu、 %msa 和 %msu 格式子可以讓您輸出字串,直接從目標的位址空間; 偵錯引擎處理讀取的字串和顯示],如所示圖 4

圖 4讀取的字串和顯示從記憶體格式子

0: 000 > ! memorydml test02! g_ptr1
[啟動 DML]
錯誤(%ma):找不到檔案
錯誤(%y {t}):找不到檔案
錯誤(%s):找不到檔案
[結束 DML]
 
0: 000 > ! memorydml test02! g_ptr2
[啟動 DML]
錯誤(%ma):值是 < 0
錯誤(%y {t}):值是 < 0
錯誤(%s):值是 [結束 DML]
 
0: 000 > ! memorydml test02! g_ptr3
[啟動 DML]
錯誤(%ma):遺失 <xml> 項目
錯誤(%y {t}):遺失 <xml> 項目
錯誤(%s):遺漏的項目
[結束 DML]

第二個和第三個範例中圖 4,列印有 %s 的字串會被截斷或省略,則因為 < 和 > 字元,但 %ma 並 %y {t} 的輸出是否正確。 Test02 應用程式處於圖 5

圖 5Test02 實作

// Test02.cpp : Defines the entry point for the console application.
//

#include <windows.h>

void* g_ptr1;
void* g_ptr2;
void* g_ptr3;

int main(int argc, char* argv[])
{
  g_ptr1 = "File not found";
  g_ptr2 = "Value is < 0";
  g_ptr3 = "Missing <xml> element";
  Sleep(10000);
  return 0;
}

! Memorydml 實作是採用圖 6

圖 6**! memorydml 實作**

HRESULT CALLBACK 
memorydml(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  IDebugDataSpaces* pDebugDataSpaces;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugDataSpaces), 
    (void **)&pDebugDataSpaces)))
  {
    IDebugSymbols* pDebugSymbols;
    if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugSymbols), 
      (void **)&pDebugSymbols)))
    {
      IDebugControl* pDebugControl;
      if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
        (void **)&pDebugControl)))
      {
        // Resolve the symbol
        ULONG64 ulAddress = 0;
        if ((args != NULL) && (strlen(args) > 0) && 
          SUCCEEDED(pDebugSymbols->GetOffsetByName(args, &ulAddress)))
        {   // Read the value of the pointer from the target address space
          ULONG64 ulPtr = 0;
          if (SUCCEEDED(pDebugDataSpaces->
            ReadPointersVirtual(1, ulAddress, &ulPtr)))
          {
            char szBuffer[256];
            ULONG ulBytesRead = 0;
            if (SUCCEEDED(pDebugDataSpaces->ReadVirtual(
              ulPtr, szBuffer, 255, &ulBytesRead)))
            {
              szBuffer[ulBytesRead] = '\0';

              // Output the value via %ma and %s
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_TEXT,
                DEBUG_OUTPUT_NORMAL, "[Start DML]\n");
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
                DEBUG_OUTPUT_ERROR, "<b>Error</b> (  %%ma): %ma\n", ulPtr);
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
                DEBUG_OUTPUT_ERROR, "<b>Error</b> (%%Y{t}): %Y{t}\n", szBuffer);
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
                DEBUG_OUTPUT_ERROR, "<b>Error</b> (   %%s): %s\n", szBuffer);
              pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_TEXT, 
                DEBUG_OUTPUT_NORMAL, "[End DML]\n");
            }
          }
        }
        pDebugControl->Release();
      }
      pDebugSymbols->Release();
    }
    pDebugDataSpaces->Release();
  }
  return S_OK;
}

[Example05] 資料夾中) 中的測試指令碼已載入的 Test02 應用程式,而非啟動 「 記事本 」,以便您有一個字串,以輸出傾印。

因此,實作從目標的位址空間的字串的顯示最簡單方法是只使用 %」,以此類推。 如果您需要操作已經讀取之前顯示,或剛才所建立您自己的圖片的字串,然後套用透過 %y {t} 逸出序列的字串。 如果您需要將字串傳遞為格式字串,然後套用排序自己逸出。 或者,將輸出分割成多個 IDebugControl::ControlledOutput 呼叫,只是使用 DML 組件的內容,與未逸出序列輸出其餘為文字 (DEBUG_OUTCTL_AMBIENT_TEXT) 上的 [DML 輸出控制 (DEBUG_OUTCTL_AMBIENT_DML)。

此區域中的其他條件約束是 IDebugClient::ControlledOutput 和 IDebugClient::Output; 的長度限制 它們只能一次輸出大約 16000 名字元。 我發現我定期達到這個限制與 ControlledOutput,當執行 DML 輸出。 過大的標記和逸出序列可以輕易地狀況超過 16000 字元字串時仍然在 [輸出] 視窗中尋找 [相對較小。

當您正在建立大型字串,並在進行排序自己逸出時,您必須確定您剪下於適當的時間點字串。 不要剪它們最多在 DML 標記或字串內的逸出序列。 否則,它們將不會正確解譯。

超連結

有兩種方式可以達到偵錯工具中的超連結:您可以使用 <link> 或者 <exec> 標記。 在這兩個標記括住的文字會顯示底線,而 「 超文字色彩 (通常是藍色)。 在這兩種情況中執行的命令是"cmd"的成員。 它很類似 <a> 的"href"成員 以 HTML 標記:

    <link cmd="dps @$csp @$csp+0x80">Stack</link>
    <exec cmd="dps @$csp @$csp+0x80">Stack</exec>

<link> 之間的差異 和 <exec> 很難看到。 事實上,我只花相當多研究以跳至底部。 不同的是只有在命令瀏覽器 (Ctrl + N)] 視窗中,沒有 [輸出] 視窗 (Alt + 1) observable。 在這兩種類型的視窗中,<link> 的輸出 或者 <exec> 連結會顯示在相關聯的輸出視窗。 不同的是每個視窗的命令提示字元會發生什麼事。

在 [輸出] 視窗中,不會變更命令提示字元的內容。 如果沒有有一些未執行的文字,則保持不變。

在命令瀏覽器視窗中,當 <link> 已叫用,將命令加入至命令的清單及命令提示字元設定為 [[] 指令。 但當 <exec> 已叫用,命令不會新增到清單而不變更命令提示字元。 不會變更命令歷程記錄,它仍可進行一系列可以指導使用者執行決策程序的超連結。 這最常見的範例會顯示說明。 周圍說明導覽方式適合於超連結,同時又不希望記錄瀏覽。

使用者喜好設定

那麼,要如何知道使用者甚至是否以查看 DML? 在少數情況下,資料可以發生問題時輸出轉換成文字,是藉由將文字儲存到記錄檔 (.logopen),或是複製並貼上從 [輸出] 視窗的動作。 可能發生資料遺失 DML 縮寫受限於,否則資料可能會如何透過輸出的文字版本的瀏覽無法受限於過多。

同樣地,如果它是很繁重來產生 DML 輸出,如此大費周章應避免使用知道此輸出會是內容轉換。 記憶體掃描和符號解析度,通常都牽涉到漫長作業。

同樣地,使用者可能只是不希望 DML 在其輸出。

在此是 <link> 的縮寫範例-為基礎,您可以只取得 「 物件位於 0x0C876C32"outputted 而且遺漏重要的資訊 (地址的資料型別) 部分:

物件位於 < 連結 cmd ="dt 登入!CSession 0x0C876C32"> 0x0C876C32 </link>

處理這種的正確方法是有 DML 未啟用時,可以避免縮寫的條件。 以下是範例的方式可以解決這:

if (DML)
  Object found at <link cmd="dt login!CSession 0x0C876C32">0x0C876C32</link>
else
  Object found at 0x0C876C32 (login!CSession)

.Prefer_dml 設定為最接近的 (所以您可以進行縮寫或多餘輸出這個條件決定),您可以移至某個使用者喜好設定。 預設情況下,設定用來控制偵錯工具是否執行 DML 增強的內建的命令和版本作業。 雖然它不明確是要指定是否應使用 DML 內的全域範圍 (擴充),就很好的替代。

此偏好設定的唯一缺點是喜好設定預設為關閉,而大部分的偵錯工程師不知道.prefer_dml 命令是否存在。

請注意一項擴充功能不偵錯引擎,讓程式碼,以偵測".prefer_dml"喜好設定或 「 能力 」 (我會解釋"可以"短時間內)。 偵錯引擎將不會刪除 DML 輸出,讓您根據這項設定。 如果偵錯工具是 DML 功能,它會輸出中 DML 不管。

若要取得目前的".prefer_dml"喜好設定,您不必執行 IDebugControl 介面的傳遞 IDebugClient 介面上 QueryInterface。 然後,您會使用 GetEngineOptions 函式以取得目前的 DEBUG_ENGOPT_XXX 位元遮罩。 如果設定的 DEBUG_ENGOPT_PREFER_DML 位元,則會啟用.prefer_dml。 圖 7有的使用者喜好設定函式中執行範例。

圖 7PreferDML 實作

BOOL PreferDML(PDEBUG_CLIENT pDebugClient)
{
  BOOL bPreferDML = FALSE;
  IDebugControl* pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)& pDebugControl)))
  {
    ULONG ulOptions = 0;
    if (SUCCEEDED(pDebugControl->GetEngineOptions(&ulOptions)))
    {
      bPreferDML = (ulOptions & DEBUG_ENGOPT_PREFER_DML);
    }
  pDebugControl->Release();
  }
  return bPreferDML;
}

您可能想成您不想要在每個命令以判斷喜好設定中呼叫 GetEngineOptions 函式。 無法我們會通知的變更嗎? 畢竟,它可能不會經常變更。 是,您可能會較佳,但還有一點要注意。

您可以做的就是註冊透過 IDebugClient::SetEventCallbacks 的 IDebugEventCallbacks 實作。 在實作中,您可以註冊 DEBUG_EVENT_CHANGE_ENGINE_STATE 通知感興趣。 當呼叫 IDebugControl::SetEngineOptions 時,偵錯工具叫用 IDebugEventCallbacks::ChangeEngineState DEBUG_CES_ENGINE_OPTIONS 位元旗標參數中設定。 引數參數會包含 DEBUG_ENGOPT_XXX 位元遮罩像 GetEngineOptions 傳回。

比較麻煩的是該只有一個事件回呼可以註冊在任何時刻 IDebugClient 物件。 如果兩個 (以上) 的擴充程式想要註冊事件回呼 (其中包含例如模組載入/卸載]、 [執行緒啟動/停止]、 [處理程序開始/停止] 及 [例外狀況的更重要的是通知),有人會錯過。 而且如果您修改傳遞的 IDebugClient 物件,該人偵錯工具!

如果您想要實作 IDebugEventCallbacks 回呼,您必須先自己透過 IDebugClient::CreateClient 的 IDebugClient 物件。 然後將您的回呼與這個 (新) 的 IDebugClient 物件產生關聯,並成為負責 IDebugClient 的存留期。

為了簡單起見,最好是每次您需要判斷 DEBUG_ENGOPT_PREFER_DML 值呼叫 GetEngineOptions。 如同先前所述,您應該在傳遞 IDebugClient 介面上呼叫 QueryInterface 」 的 IDebugControl 介面,然後呼叫 GetEngineOptions 以確定您有目前 (及正確) 喜好設定。

偵錯用戶端的能力

那麼,要如何知道是否偵錯工具甚至支援 DML?

如果偵錯工具不支援 DML,資料可以是遺失、 多餘或努力可以是很繁重,如同使用者偏好設定。 NTSD 如所述,是一個純文字偵錯工具和偵錯引擎如果 DML outputted 給它,並不會內容轉換成從輸出中移除 DML。

若要取得偵錯用戶端的能力,您不必執行 IDebugAdvanced2 介面的傳遞 IDebugClient 介面上 QueryInterface。 然後,您會使用要求函式與 DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE 要求類型。 HRESULT 包含 S_OK DML 感知至少一個輸出回呼時,否則它會傳回 S_FALSE。 再次重申,旗標並不表示所有回呼都已經知道; 這意思是 最少是。

表面上純文字 (例如 NTSD) 環境中您還可以取得條件式輸出發生問題。 如果延伸註冊位於 DML 感知 (藉由從 IDebugOutputCallbacks2::GetInterestMask 傳回 DEBUG_OUTCBI_DML 或 DEBUG_OUTCBI_ANY_FORMAT) NTSD 輸出回呼,它會導致要求函式傳回 S_OK。 幸好,這些擴充功能是很少發生。 如果存在,它們應該會檢查 DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE,並據以設定其能力的狀態 (之前為其 proclamation 的 DML 能力)。 取出有關 DML 感知回呼本系列下一篇文章再見囉。

圖 8有能力函式中的執行範例。

圖 8AbilityDML 實作

BOOL AbilityDML(PDEBUG_CLIENT pDebugClient)
{
  BOOL bAbilityDML = FALSE;
  IDebugAdvanced2* pDebugAdvanced2;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugAdvanced2), 
    (void **)& pDebugAdvanced2)))
  {
    HRESULT hr = 0;
    if (SUCCEEDED(hr = pDebugAdvanced2->Request(
      DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE, 
      NULL, 0, NULL, 0, NULL)))
    {
      if (hr == S_OK) bAbilityDML = TRUE;
    }
    pDebugAdvanced2->Release();
  }
  return bAbilityDML;
}

請注意 DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE 要求類型和 IDebugOutputCallbacks2 介面不記錄在 MSDN Library 中尚未。

將潛在不足,可保存在心,處理使用者喜好設定和用戶端能力的最好方法是:

if (PreferDML(IDebugClient) && AbilityDML(IDebugClient))
  Object found at <link cmd="dt login!CSession 0x0C876C32">0x0C876C32</link>
else
  Object found at 0x0C876C32 (login!CSession)

! Ifdml 實作 (在圖 9),這樣會產生條件式 DML 輸出] 顯示作用中的 PreferDML 和 AbilityDML 函式。 請注意,在絕大多數的情況下,不需要擁有條件陳述式如下。 您可以安全地仰賴偵錯工具引擎內容轉換。

圖 9**! ifdml 實作**

HRESULT CALLBACK 
ifdml(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  UNREFERENCED_PARAMETER(args);

  PDEBUG_CONTROL pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    // A condition is usually not required;
    // Rely on content conversion when there isn't 
    // any abbreviation or superfluous content
    if (PreferDML(pDebugClient) && AbilityDML(pDebugClient))
    {
      pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
        DEBUG_OUTPUT_NORMAL, "<b>Hello</b> <i>DML</i> <u>World!</u>\n");
    }
    else
    {
      pDebugControl->ControlledOutput(
        DEBUG_OUTCTL_AMBIENT_TEXT, DEBUG_OUTPUT_NORMAL, 
        "Hello TEXT World!
\n");
    }
    pDebugControl->Release();
  }
  return S_OK;
}

若要載入 WinDbg 的輸出使用 test_windbg.cmd 測試指令碼! ifdml 是:

0: 000 > 0.prefer_dml
預設情況下關閉命令的 DML 版本
0: 000 > ! ifdml
文字大家好 !

0: 000 > 1.prefer_dml
根據預設值] 指令的 DML 版本
0: 000 > ! ifdml
Hello DML World!

若要載入 NTSD 的輸出使用 test_ntsd.cmd 測試指令碼! ifdml 是:

0: 000 > 0.prefer_dml
預設情況下關閉命令的 DML 版本
0: 000 > ! ifdml
文字大家好 !
 
0: 000 > 1.prefer_dml
根據預設值] 指令的 DML 版本
0: 000 > ! ifdml
文字大家好 !

控制的輸出

若要輸出 DML,您必須使用 IDebugControl::ControlledOutput 函式:

HRESULT ControlledOutput(
  [in]  ULONG OutputControl,
  [in]  ULONG Mask,
  [in]  PCSTR Format,
         ...
);

ControlledOutput 和輸出之間的差異是 OutputControl 參數。 這個參數根據 DEBUG_OUTCTL_XXX 常數。 有兩個組件至這個參數:較低的位元表示的輸出範圍和較高的位元表示選項。 它是較高的位元,可讓 DML。

一個,且只有一個 — DEBUG_OUTCTL_XXX 的範圍為基礎的常數必須使用較低的位元。 值會指示其中輸出會繼續。 這可能是所有偵錯工具用戶端 (DEBUG_OUTCTL_ALL_CLIENTS),只是沒有用在所有 (DEBUG_OUTCTL_IGNORE) 或只是記錄檔 (DEBUG_OUTCTL_LOG_ONLY) 所有其他用戶端 (DEBUG_OUTCTL_ALL_OTHER_CLIENTS) 相關聯的 IDebugControl 介面 (DEBUG_OUTCTL_THIS_CLIENT),IDebugClient。

較高的位元的位元遮罩,並且也定義在 DEBUG_OUTCTL_XXX 常數。 有常數來指定文字或 DML 基礎輸出 (DEBUG_OUTCTL_DML),如果輸出不到記錄 (DEBUG_OUTCTL_NOT_LOGGED) 以及用戶端的輸出遮罩是否接受 (DEBUG_OUTCTL_OVERRIDE_MASK)。

輸出控制

在所有範例中,我已將 ControlledOutput 參數設定為 DEBUG_OUTCTL_AMBIENT_DML。 讀取 MSDN 上的文件,您可能會說我已經也用於 DEBUG_OUTCTL_ALL_CLIENTS |DEBUG_OUTCTL_DML。 不過,這不會受影響的 IDebugControl 輸出控制喜好設定。

如果延伸模組的命令由 IDebugControl::Execute 所叫用,應使用 OutputControl 呼叫的參數執行的任何相關的輸出。 IDebugControl::Output 這原本就是,但當您使用 IDebugControl::ControlledOutput,知道 OutputControl 值的責任是呼叫者的。 這個問題是沒有任何方法不必費事擷取目前的輸出控制項值,從 IDebugControl 介面 (或任何其他介面)。 所有不會遺失,不過; 有特殊 DEBUG_OUTCTL_XXX 「 環境 」 常數,以便處理 DEBUG_OUTCTL_DML 位元的切換。 當您使用一個環境的常數時,接受目前的 [輸出] 控制項,並依此設定只是 DEBUG_OUTCTL_DML 位元。

而非傳遞給其中一種較低的常數與較高的 DEBUG_OUTCTL_DML 常數,您只是傳遞若要啟用 DML 輸出 DEBUG_OUTCTL_AMBIENT_DML 或 DEBUG_OUTCTL_AMBIENT_TEXT 若要停用 DML 輸出:

pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, ...);

遮罩參數

我已設定範例所示的另一個參數是遮罩的參數。 您應該將遮罩設定為適當的 DEBUG_OUTPUT_XXX 常數被 outputted 的文字。 請注意遮罩參數根據 DEBUG_OUTPUT_XXX 的常數。 這並不會混淆與 DEBUG_OUTCTL_XXX 常數代表。

您可以使用的最常見值會是 DEBUG_OUTPUT_NORMAL 標準 (一般) 的輸出,警告輸出的 DEBUG_OUTPUT_WARNING 和 DEBUG_OUTPUT_ERROR 的錯誤輸出。 當您的擴充功能有問題,您應該使用 DEBUG_OUTPUT_EXTENSION_WARNING。

DEBUG_OUTPUT_XXX 輸出旗標是類似於 stdout 和 stderr 搭配主控台輸出。 每個輸出旗標是個別的輸出通道。 它是由的接收者是誰 (回呼) 來決定哪一個這些通道會接聽到、 它們如何合併 (如果它),而且這是要顯示的方式。 比方說,WinDbg 預設情況下在 [輸出] 視窗中顯示 DEBUG_OUTPUT_VERBOSE 輸出旗標以外的所有輸出旗標。 您可以切換檢視透過這種行為 |詳細資訊輸出 (Ctrl + Alt + V)。

! Maskdml 實作 (請參閱圖 10) 輸出樣式以及具有以相關聯的輸出旗標的描述。

圖 10**! maskdml 實作**

HRESULT CALLBACK 
maskdml(PDEBUG_CLIENT pDebugClient, PCSTR args)
{
  UNREFERENCED_PARAMETER(args);

  PDEBUG_CONTROL pDebugControl;
  if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl), 
    (void **)&pDebugControl)))
  {
    pDebugControl->ControlledOutput(
      DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_NORMAL, 
      "<b>DEBUG_OUTPUT_NORMAL</b> - Normal output.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_ERROR, "<b>DEBUG_OUTPUT_ERROR</b> - Error output.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_WARNING, "<b>DEBUG_OUTPUT_WARNING</b> - Warnings.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_VERBOSE, "<b>DEBUG_OUTPUT_VERBOSE</b> 
      - Additional output.
\n");
    pDebugControl->ControlledOutput(
      DEBUG_OUTCTL_AMBIENT_DML, DEBUG_OUTPUT_PROMPT, 
      "<b>DEBUG_OUTPUT_PROMPT</b> - Prompt output.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_PROMPT_REGISTERS, "<b>DEBUG_OUTPUT_PROMPT_REGISTERS</b> 
      - Register dump before prompt.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_EXTENSION_WARNING, 
      "<b>DEBUG_OUTPUT_EXTENSION_WARNING</b> 
      - Warnings specific to extension operation.
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_DEBUGGEE, "<b>DEBUG_OUTPUT_DEBUGGEE</b> 
      - Debug output from the target (for example, OutputDebugString or  
      DbgPrint).
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML,  
      DEBUG_OUTPUT_DEBUGGEE_PROMPT, "<b>DEBUG_OUTPUT_DEBUGGEE_PROMPT</b> 
      - Debug input expected by the target (for example, DbgPrompt).
\n");
    pDebugControl->ControlledOutput(DEBUG_OUTCTL_AMBIENT_DML, 
      DEBUG_OUTPUT_SYMBOLS, "<b>DEBUG_OUTPUT_SYMBOLS</b> 
      - Symbol messages (for example, !sym noisy).
\n");
    pDebugControl->Release();
  }
  return S_OK;
}

如果命令執行之後,您會切換輸出詳細資訊,請省略的 DEBUG_OUTPUT_VERBOSE 輸出會顯示。 輸出將會遺失。

WinDbg 支援每個輸出標幟顏色的標示。在檢視中 |選項] 對話方塊中,您可以指定每個輸出旗標的前景和背景色彩。色彩設定被儲存在工作區。若要全域設定它們,啟動 WinDbg、 刪除所有工作區、 設定色彩 (和任何其他您想要的設定),並儲存工作區。我想要將錯誤的前景色彩設定為紅色,警告為綠色、 藍色的詳細資訊,延伸警告為紫色及成灰 50%的符號。預設工作區會是範本的所有未來的偵錯工作階段。

圖 11顯示的輸出! maskdml 而不需 (上圖) 與 (下方) 啟用詳細資訊。

圖 11**! 的色彩配置的 maskdml**

中斷!

很容易增強 DML 任何延伸模組。同時,利用最少的基礎程式碼,它也是容易受限於使用者喜好設定。絕對值得花一些額外的時間來正確產生輸出。特別是,致力於縮寫或多餘,輸出時,永遠提供這兩個文字和 DML 為基礎的輸出,並適當地導向這個輸出。

¦ B 下一個關於偵錯工具引擎 API,我將深入探究偵錯工具擴充功能可以讓偵錯工具的關係。我就給你的偵錯工具用戶端和偵錯工具回呼架構的概觀。這麼做,我們將會進入 DEBUG_OUTPUT_XXX 和 DEBUG_OUTCTL_XXX 常數的重點。

我會使用這種概念為主來實作兒子的攻擊,或 SOS,偵錯工具擴充功能的封裝。我將增強 DML SOS 輸出,並示範如何利用內建的偵錯工具命令,以及其它延伸模組擷取您的擴充功能所需的資訊。

如果您只想要偵錯,若要瞭解詳細資訊,您應該檢查的 「 進階 Windows 偵錯和疑難排解 」 (NTDebugging) 部落格,在blogs.msdn.com/b/ntdebugging— 有很多的訓練和個案研究的文件,以讀取。

Microsoft 一直在尋找,總有偵錯工程師。如果您想要加入團隊,來搜尋工作職稱是"重大問題工程師 」 在 Microsoft 對 (careers.microsoft.com)。

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

感謝至下列技術專家檢閱這份文件:Drew Bliss