调试器引擎 API

编写 Debugging Tools for Windows 扩展,第 2 部分:输出

Andrew Richards

下载代码示例

http://download.csdn.net/detail/whatday/7133071

在有关调试器 API 的系列文章的第二篇中,我将向您展示如何增强由调试器引擎 (DbgEng) 扩展所生成的输出。 在此过程中,您可能会遭遇各种各样的陷阱。 我希望能为您指出所有这些陷阱。

在继续阅读之前,您可能需要先阅读上一篇文章,了解调试器扩展是什么(以及我如何构建和测试本文中的示例)。 该文章位于 msdn.microsoft.com/magazine/gg650659。

调试器标记语言

调试器标记语言 (DML) 是一种受到 HTML 启发的标记语言。 它支持通过粗体/斜体/加下划线表示强调以及通过超链接进行导航。 调试器 API 中自 6.6 版开始添加了 DML。

Windows SDK for Windows Vista 最早提供了此 API 的 6.6.7.5 版,并且支持 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 获得 Debugging Tools for Windows 最新版本的唯一途径。 不提供可直接下载的 x86、x64 和 IA64 程序包。 请注意,这些后续版本并未扩展 6.6 版中定义的 DML 相关 API, 而是对 DML 相关支持进行了有价值的修正。

“Hello DML World”

您可能会猜出来,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;
}

为了测试此扩展,我在本文随附的下载代码的 Example04 文件夹中提供了一个名为 test_windbg.cmd 的脚本。 该脚本会将扩展复制到 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 被输出到仅基于文本的客户端时,会去除所有标记:

0:000> !hellodml 
Hello DML World!

标记

与 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 
[Start DML] 
Hello World 
[End DML]

0:000> !echoasdml <b>Hello World</b> 
[Start DML] 
Hello World  
[End DML] 
 
0:000> !echoasdml <b>Hello
[Start DML]
Hello
[End DML]

0:000> !echoasdml World</b>
[Start DML]
World
[End DML]

“<b>Hello”命令使粗体始终处于启用状态,从而导致后续的所有扩展和提示输出均显示为粗体, 而无论文字是以文本模式输出还是以 DML 模式输出。 不难想象,后面的结束 bold 标记将还原文字状态。

另一种常见问题出现在字符串中包含 XML 标记时。 其结果可能是截断输出(如本文第一个示例中所示),也可能是去除 XML 标记:

0:000> !echoasdml <xml 
[Start DML] 
  
0:000> !echoasdml <xml>Hello World</xml>
[Start DML]
Hello World
[End 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], "&", 5);e+=5;break;case '<':memcpy(&szEscape[e], "<", 4);e+=4;break;case '>':memcpy(&szEscape[e], ">", 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),并将适当的文本添加到缓冲区中。 然后,它将经过转义序列处理的缓冲区与 %s 格式化程序一起传递给 IDebugClient::ControlledOutput 函数。 !echoasdmlescape 命令会回显该参数,而不将字符串解释为 DML 标记:

0:000> !echoasdmlescape <xml 
[Start DML] 
<xml 
[End DML] 
 
0:000> !echoasdmlescape <xml>Hello World</xml>
[Start DML]
<xml>Hello World</xml>
[End DML]

请注意,对于有些字符串,即使您提供输入,也不会得到预期的输出。 这些不一致与转义序列(或 DML)没有任何关系,而是由调试器的分析程序引起的。 两种需要注意的情况是 " 字符(字符串内容)和“;”字符(命令终止):

0:000> !echoasdmlescape "Hello World" 
[Start DML] 
Hello World 
[End DML] 
 
0:000> !echoasdmlescape Hello World;
[Start DML]
Hello World
[End DML]
 
0:000> !echoasdmlescape "Hello World;"
[Start DML]
Hello World;
[End DML]

但是您不需要自行处理这项转义序列工作。 调试器支持一种特殊的格式化程序,可用于这种情况。 您不需要生成经过转义序列处理的字符串,然后再使用 %s 格式化程序,而只需对原始字符串使用 %Y{t} 格式化程序。

如果您使用内存格式化程序,也可以避免此项转义序列工作。 %ma、%mu、%msa 和 %msu 格式化程序可以从目标地址空间直接输出字符串;调试引擎将负责读取并显示字符串,如图 4 所示。

图 4 从内存格式化程序读取并显示字符串

0:000> !memorydml test02!g_ptr1 
[Start DML] 
Error (  %ma):File not found 
Error (%Y{t}):File not found 
Error (   %s):File not found 
[End DML] 
 
0:000> !memorydml test02!g_ptr2
[Start DML]
Error (  %ma):Value is < 0
Error (%Y{t}):Value is < 0
Error (   %s):Value is [End DML]
  
0:000> !memorydml test02!g_ptr3
[Start DML]
Error (  %ma):Missing <xml> element
Error (%Y{t}):Missing <xml> element
Error (   %s):Missing  element
[End DML]

在图 4 所示的第二和第三个示例中,以 %s 显示的字符串将由于 < 和 > 字符而被截断或忽略,但是 %ma 和 %Y{t} 的输出是正确的。 Test02 应用程序如图 5 所示。

图 5 Test02 实现

 // 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 symbolULONG64 ulAddress = 0;if ((args != NULL) && (strlen(args) > 0) && SUCCEEDED(pDebugSymbols->GetOffsetByName(args, &ulAddress))){    // Read the value of the pointer from the target address spaceULONG64 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 %spDebugControl->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 应用程序的转储而不是启动记事本,以便您获得要输出的字符串。

因此,实现从目标地址空间显示字符串的最简单方法是直接使用 %ma 等等。 如果您在显示之前需要对已经读取的字符串进行处理,或者自行生成了字符串,则可以通过 %Y{t} 应用转义序列。 如果您需要将字符串作为格式字符串传入,则需要自行应用转义序列。 另外,将输出拆分到多个 IDebugControl::ControlledOutput 调用中,并且对内容的 DML 部分使用 DML 输出控制 (DEBUG_OUTCTL_AMBIENT_DML),无需使用转义序列,就可以将其余部分作为文本 (DEBUG_OUTCTL_AMBIENT_TEXT) 输出。

但这里存在一项约束:IDebugClient::ControlledOutput 和 IDebugClient::Output 的长度限制;它们一次最多只能输出大约 16,000 个字符。 我发现,在执行 DML 输出时,ControlledOutput 经常会达到此限制。 标记和转义序列很容易让字符串超过 16,000 个字符,而字符串在输出窗口中看起来仍然不那么长。

当您构建超长字符串并且自行执行转义序列操作时,您需要确保在适当的位置切断字符串。 切勿在 DML 标记或转义序列内切断字符串。 否则,字符串就不能得到正确解释。

超链接

有两种方法可以在调试器中实现超链接:使用 <link> 或 <exec> 标记。 这两种标记中包含的文本在显示时会带有下划线,使用超文本颜色(通常为蓝色)。 这两种情况下执行的命令都是“cmd”成员。 此成员与 HTML 中 <a> 标记的“href”成员类似:

<link> 与 <exec> 之间的差别很难看出来。 实际上,我花了不少时间才研究明白。 唯一可以观察到的差别出现在“命令浏览器”窗口 (Ctrl+N) 中,而不是“输出”窗口 (Alt+1) 中。 在这两种窗口中,<link> 或 <exec> 链接的输出均显示在关联的输出窗口中。 差别在于每个窗口的命令提示符处出现的情况。

在“输出”窗口中,命令提示符的内容不会改变。 如果存在未执行的文本,它仍然会保持不变。

在“命令浏览器”窗口中,当 <link> 被调用时,该命令会被添加到命令列表中,并且命令提示符会设置为该命令。 但是当 <exec> 被调用时,该命令不会被添加到命令列表中,命令提示符也不会改变。 由于不更改命令历史记录,因此就有可能生成一系列超链接来引导用户浏览决策过程。 显示帮助就是最常见的例子。 在帮助中导航非常适合超链接,而且不记录导航过程也是合理的。

用户首选项

那您如何判断用户是否希望查看 DML 呢? 在有些情况下,将输出转换为文本时会发生数据问题,此时就需要将文本保存到日志文件 (.logopen) 中,或者从输出窗口复制并粘贴文本。 数据可能会由于 DML 缩写而丢失,也可能会由于无法通过文本版的输出进行导航而显得冗长。

同样,如果生成 DML 输出的操作很繁琐,则在确定此输出将进行内容转换时应避免进行此项工作。 长时间执行的操作通常都会涉及内存扫描和符号解析。

同样,用户可能就是不想在其输出中看到 DML。

在下面这个基于 <link> 的缩写示例中,只会输出“Object found at 0x0C876C32”,而重要的信息(地址的数据类型)会丢失:

Object found at <link cmd="dt login!CSession 0x0C876C32">0x0C876C32</link>

对于这种情况,正确的处理方法是设定一个条件,以便在未启用 DML 时避免缩写。 下面是如何修正此问题的示例:

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

.prefer_dml 设置是最接近用户首选项的设置(因此您可以做出缩写或冗长输出的条件决策)。 该设置用于控制,在默认情况下,调试器是否运行内置命令和操作的 DML 增强版。 尽管它并不明确意味着指定是否(在扩展中)全局使用 DML,但它是一个不错的替代品。

此首选项的唯一缺点是它默认处于关闭状态,而大多数调试工程师不知道还有 .prefer_dml 命令。

请注意,扩展而不是调试引擎必须有代码来检测“.prefer_dml”首选项或“ability”(我将简单介绍一下“ability”)。 调试引擎不会根据此项设置来清除 DML 输出;如果调试器支持 DML,则它总是会以 DML 格式输出。

为了获取当前的“.prefer_dml”首选项,您需要对传入的 IDebugClient 接口(用于 IDebugControl 接口)执行 QueryInterface。 然后,使用 GetEngineOptions 函数来获取当前的 DEBUG_ENGOPT_XXX 位掩码。 如果设置了 DEBUG_ENGOPT_PREFER_DML 位,则 .prefer_dml 被启用。 图 7 提供了一个用户首选项函数的实现示例。

图 7 PreferDML 实现

          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,并在 Flags 参数中设置 DEBUG_CES_ENGINE_OPTIONS 位。 Argument 参数包含一个 DEBUG_ENGOPT_XXX 位掩码,就像 GetEngineOptions 返回的一样。

问题是对于一个 IDebugClient 对象而言,任何时候都只能注册一个事件回调。 如果有两个(或更多)扩展希望注册事件回调(包括更重要的通知,如模块加载/卸载、线程启动/停止、进程启动/停止和异常),某些对象就会失去机会。 如果您修改传入的 IDebugClient 对象,这里的“某些对象”就是指调试程序!

如果您想要实现 IDebugEventCallbacks 回调,则需要通过 IDebugClient::CreateClient 生成自己的 IDebugClient 对象。 然后,将您的回调与这个(新)IDebugClient 对象相关联,并让其负责 IDebugClient 的生存期。

为了简单起见,在每次需要确定 DEBUG_ENGOPT_PREFER_DML 值时调用 GetEngineOptions,效果会更好。正如前文所述,您应该对传入的 IDebugClient 接口(用于 IDebugControl 接口)调用 QueryInterface,然后调用 GetEngineOptions 以确保您具有当前(且正确)的首选项。

调试客户端的能力

那您如何判断调试器是否支持 DML 呢?

如果调试器不支持 DML,数据可能会丢失、冗长,或者所需的工作会非常繁重,就像用户首选项面对的情况一样。 正如前文所述,NTSD 是一个仅支持文本的调试器;如果向其输出 DML,则调试引擎将执行内容转换,以便从输出中删除 DML。

为了获知调试客户端的能力,您需要对传入的 IDebugClient 接口(用于 IDebugAdvanced2 接口)执行 QueryInterface。 然后通过 DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE 请求类型来执行 Request 函数。 当至少有一个“输出回调”支持 DML 时,HRESULT 包含 S_OK;否则它返回 S_FALSE。 重申一下,该标记并不意味着支持所有回调,而意味着支持至少一种 回调。

在看起来仅支持文本的环境(如 NTSD)中,您仍然会遇到条件输出问题。 如果某个扩展在 NTSD 中注册了一个支持 DML 的输出回调(通过从 IDebugOutputCallbacks2::GetInterestMask 返回 DEBUG_OUTCBI_DML 或 DEBUG_OUTCBI_ANY_FORMAT),它将导致 Request 函数返回 S_OK。 幸运的是,这样的扩展非常罕见。 如果存在这样的扩展,它们应该检查 DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE 的状态,并相应地设置其能力(在公布其 DML 能力之前)。 有关支持 DML 的回调的更多信息,请参见本系列文章的下一篇。

图 8 提供了一个能力函数的实现示例。

图 8 AbilityDML 实现

          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;
}

请注意,MSDN 库中并未提供 DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE 请求类型和 IDebugOutputCallbacks2 接口的详细说明。

考虑到这些潜在的缺点,因此处理用户首选项和客户端能力的最佳方法是:


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

!ifdml 实现(请参见图 9)展示了 PreferDML 和 AbilityDML 函数生成条件 DML 输出的实际效果。 请注意,在绝大多数情况下,不需要类似于此的条件语句;您可以安全地依赖于调试器引擎内容转换。

图 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 contentif (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;
}

使用 test_windbg.cmd 测试脚本来加载 WinDbg,则 !ifdml 的输出为:

0:000> .prefer_dml 0 
DML versions of commands off by default 
0:000> !ifdml 
Hello TEXT World!

0:000> .prefer_dml 1 
DML versions of commands on by default 
0:000> !ifdml 
Hello DML World!

使用 test_ntsd.cmd 测试脚本来加载 NTSD,则 !ifdml 的输出为:

0:000> .prefer_dml 0 
DML versions of commands off by default 
0:000> !ifdml 
Hello TEXT World! 
 
0:000> .prefer_dml 1
DML versions of commands on by default
0:000> !ifdml
Hello TEXT World!

受控制的输出

若要输出 DML,您需要使用 IDebugControl::ControlledOutput 函数:

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

ControlledOutput 和 Output 之间的差别在于 OutputControl 参数。 此参数基于 DEBUG_OUTCTL_XXX 常量。 此参数有两个部分:低位表示输出的范围,高位表示选项。 由高位来启用 DML。

对于低位,有且仅有一个基于 DEBUG_OUTCTL_XXX 范围的常量是必须使用的。 该值指示将输出到何处。该值可以指示:输出到所有调试器客户端 (DEBUG_OUTCTL_ALL_CLIENTS),只输出到与 IDebugControl 接口相关联的 IDebugClient (DEBUG_OUTCTL_THIS_CLIENT),输出到其他所有客户端 (DEBUG_OUTCTL_ALL_OTHER_CLIENTS),不输出到任何客户端 (DEBUG_OUTCTL_IGNORE),或者只输出到日志文件 (DEBUG_OUTCTL_LOG_ONLY)。

高位是一个位掩码,也在 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 调用,则应该对任何相关输出使用 Execute 调用的 OutputControl 参数。 IDebugControl::Output 本身就会这么做,但在使用 IDebugControl::ControlledOutput 时,调用者必须负责获取 OutputControl 值。 问题是没有办法从 IDebugControl 接口(或其他任何接口)实际检索当前的输出控制值。 但并非毫无希望;有一些特殊的 DEBUG_OUTCTL_XXX“环境”常量用于处理切换 DEBUG_OUTCTL_DML 位的工作。 当您使用某个环境变量时,会遵循当前的输出控制,并且仅仅相应地设置 DEBUG_OUTCTL_DML 位。

您无需同时传递高位 DEBUG_OUTCTL_DML 常量与某个低位常量,而只需传递 DEBUG_OUTCTL_AMBIENT_DML 以启用 DML 输出,或者传递 DEBUG_OUTCTL_AMBIENT_TEXT 以禁用 DML 输出:

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

Mask 参数

我在示例中设置的另一个参数是 Mask 参数。 您应该根据要输出的文本,将 Mask 参数设置为适当的 DEBUG_OUTPUT_XXX 常量。 请注意,Mask 参数基于 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,删除所有工作区,设置颜色(以及您需要的其他任何设置),然后保存工作区。 我喜欢将“错误”的前景色设置为红色,将“警告”的前景色设置为绿色,将“详细”的前景色设置为蓝色,将“扩展警告”的前景色设置为紫色,将“符号”的前景色设置为灰色。 默认工作区将成为未来所有调试会话的模板。

图 11 显示了未启用(顶部)和启用(底部)详细选项的 !maskdml 输出。

图 11 采用配色方案的 !maskdml

结语

利用 DML 来增强任何扩展都很简单。 通过极少量的基础代码,也很容易遵循用户首选项。 为了正确生成输出,绝对值得投入额外的时间。 特别是,在输出被缩写或冗长时,尽力同时提供基于文本和基于 DML 的输出,并且适当地引导这种输出。

在关于调试器引擎 API 的下一篇文章中,我将深入探讨调试器扩展与调试器之间可能存在的关系。 我将概要介绍调试器客户端和调试器回调。 在此过程中,我将探讨 DEBUG_OUTPUT_XXX 和 DEBUG_OUTCTL_XXX 常量的本质问题。

我将在此基础上实现 Son of Strike(简称 SOS)调试器扩展的封装。 我将利用 DML 来增强 SOS 输出,并演示如何利用内置调试器命令和其他扩展来检索扩展所需的信息。

编写 Debugging Tools for Windows 扩展,第 2 部分:输出 (windbg 插件 扩展)相关推荐

  1. 编写 Debugging Tools for Windows 扩展,第 3 部分:客户端和回调 (windbg 插件 扩展)

    调试器引擎 API 编写 Debugging Tools for Windows 扩展,第 3 部分:客户端和回调 Andrew Richards 下载代码示例 http://download.csd ...

  2. 编写 Debugging Tools for Windows 扩展,第 1 部分 (windbg 插件 扩展)

    调试器 API 编写 Debugging Tools for Windows 扩展 Andrew Richards 下载代码示例 http://download.csdn.net/detail/wha ...

  3. debugging tools for windows 10下载安装问题

    配置QT5.0中debugger的时候需要下载debugging tools for windows 10,于是去https://developer.microsoft.com/zh-cn/windo ...

  4. Debugging Tools for Windows (WinDbg)的使用

    安装 可能大家安装时,直接从外部的网址下载或者拷贝,一般这也是可行的,只不过可能安装的不是最新版本,或者找不到和自己当前系统匹配的版本,所以最简单的方法还是去微软的官网下载 下载地址:  Downlo ...

  5. 单独下载windbg(Debugging Tools for Windows)工具

    最近电脑出现了蓝屏,想研究下什么原因,但操作系统默认无法打开dmp文件,便去微软官网下载windbg,发现微软不单独提供windbg的下载.只在Windows SDK中才包含windbg工具,或者去下 ...

  6. QT配置调试器windbg (Debugging Tools for Windows)

    首先在这里下载调试器windbg (Debugging Tools for Windows) : http://download.csdn.net/download/zhang957411207/47 ...

  7. 如何使用Debugging Tools for Windows (windebug)简单的使用心得

    1.安装debug工具 ​ 下载页面地址:http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx 选择合适的版本安装 ​ 2. ...

  8. magento2 所需要php 扩展,Magento2如何通过Composer安装插件扩展

    本文将指导您使用Composer安装Magento 2扩展. 注意:按照说明进行操作时,请确保您知道自己在做什么.如果文章中的任何操作或术语引起混淆,请专业人员完成这项工作. 要通过Composer安 ...

  9. Debugging Tools for Windows__from WDK7

    1. 主要要用到两个工具: (1).WinDBG 这个主要用于 非IDE下 调试程序/查看信息等 (2).cdb.exe 这个主要是用在 Qt5.3.2 for VS10 的单步调试器 2. WDK7 ...

最新文章

  1. Android开发中完全退出程序的三种方法
  2. Mysql存储过程中的事务回滚
  3. Redis进阶-细说分布式锁
  4. 电子计算机信息工程都是做什么的,电子信息工程专业将来干什么 就业前景好不好...
  5. java课堂作业(一)
  6. codeforces CF438D The Child and Sequence 线段树
  7. python 网页编程_通过Python编程检索网页
  8. 计算机应用中的CAI,????按计算机应用的分类,CAI应属于()应用。
  9. opnet平台中切换模块的理解——切换的建模
  10. 【转载】谁动了摩卡的奶酪?
  11. 栈和队列常用函数详解
  12. CPDA数据分析师证书含金量高吗?
  13. C#Winform使用火狐firefox内核GeckoWebBrowser
  14. CentOS 编译运行 DPDK 19.11 流程
  15. 魔域进游戏老是显示服务器繁忙,魔域2014年春节-温情卡诺萨
  16. 日期转换和日历的使用方法
  17. 将ui文件转换为py文件
  18. putty永久设置session
  19. ArrayList源码分析与手写
  20. rh2288v3服务器硬盘故障,RH2288H V3服务器出现0x02000007告警

热门文章

  1. HashMap的put和get操作
  2. 自动化办公 Python 操控 Word
  3. C/S架构和B/S架构的概念和区别
  4. 年末重磅 | 12月Unity 2D新功能发布会现已开放报名!
  5. android之权限大全
  6. 用CSS Filter 可以实现相同的效果
  7. 对Web页面元素的绝对唯一引用方法
  8. java在线聊天项目0.4版本 制作服务端接收连接,客户端连接功能 新增客户端窗口打开时光标指向下边文本域功能,使用WindowListener监听WindowAdapter...
  9. Navigator 对象 深入研究
  10. VUE -- 自定义控件(标签)