May 2011

Volume 26 Number 05

Debugger Engine API - Writing a Debugging Tools for Windows Extension, Part 2: Output

By Andrew Richards | May 2011

In this second installment of my series about the Debugger API, I’m going to show you how you can enhance the output generated by your Debugger Engine (DbgEng) extension. You can fall into a number of traps when doing this, though. I hope to highlight all of the pitfalls for you.

Before reading on, you’ll want to have read the previous installment to understand what a debugger extension is (and how I’m building and testing the examples in this article). You can read it at msdn.microsoft.com/magazine/gg650659.

Debugger Markup Language

Debugger Markup Language (DML) is an HTML-inspired markup language. It supports emphasis via bolding/italics/underlining and navigation via hyperlinks. DML was added to the Debugger API in version 6.6.

The Windows SDK for Windows Vista first shipped version 6.6.7.5 of this API and supported x86, x64 and IA64. The Windows 7/.NET 3.5 SDK/WDK shipped the next release (version 6.11.1.404). The Windows 7/.NET 4 SDK/WDK ships the current release (version 6.12.2.633). The Windows 7/.NET 4 release vehicle is the only way to get the latest version of the Debugging Tools for Windows from Microsoft. There’s no direct download of the x86, x64 or IA64 packages available. Note that these subsequent releases don’t expand on the DML-related APIs defined in version 6.6. They do, however, have worthwhile fixes relating to DML support.

‘Hello DML World’

As you can probably guess, the markup used by DML for emphasis is the same markup as used by HTML. To mark text as bold, use “<b>…</b>”; for italic, use “<i>…</i>”; and for underline use “<u>…</u>”. Figure 1 shows an example command that outputs “Hello DML World!” with these three types of markup.

Figure 1 !hellodml Implementation

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

To test the extension, I have a script called test_windbg.cmd in the Example04 folder of the code download accompanying this article. The script copies the extension to the C:\Debuggers_x86 folder. The script then starts WinDbg, loads the extension and launches a new instance of Notepad (as the debug target). If everything has gone according to plan, I can type “!hellodml” in the debugger’s command prompt and see a “Hello DML World!” response with bold, italic and underline markup:

0:000> !hellodml
Hello DML World!

I also have a script called test_ntsd.cmd that does the same steps, but loads the NTSD debugger. If I type “!hellodml” in this debugger’s command prompt, I’ll see the “Hello DML World!” response, but with no markup. The DML is converted to text because NTSD is a text-only debug client. All markup is stripped when DML is outputted to a text (only)-based client:

0:000> !hellodml
Hello DML World!

Markup

Much like with HTML, you need to be careful to start and end any markup. Figure 2 has a simple extension command (!echoasdml) that echoes the command argument as DML with markers before and after the DML output as text output.

Figure 2 !echoasdml Implementation

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

This example sequence shows what happens if you don’t close markup:

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]

The “<b>Hello” command leaves bold enabled, causing all following extension and prompt output to be displayed as bold. This is regardless of whether the text was outputted in text or DML mode. As you’d expect, the subsequent closing of the bold markup reverts the state.

Another common issue is when the string has XML tags within it. The output could be either truncated, as happens in the first example here, or the XML tags could be stripped:

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

You handle this in the same way as you would with HTML: escape sequence the string before output. You can do this yourself or have the debugger do it for you. The four characters that need escaping are &, <, > and ". The equivalent escaped versions are: “&amp;”, “&lt;”, “&gt;” and “&quot;”.

Figure 3 shows an example of escape sequencing.

Figure 3 !echoasdmlescape Implementation

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], "&quot;", 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;
}

The echoasdmlescape command allocates a new buffer that is six times the size of the original. This is enough space to handle an argument string with purely " characters. The function iterates over the argument string (which is always ANSI) and adds the appropriate text to the buffer. It then uses the escape-sequenced buffer with the %s formatter passed to the IDebugClient::ControlledOutput function. The !echoasdmlescape command echoes the argument without the string being interpreted as DML markup:

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

Note that with some strings, you may still not get the output you expect, given the input provided. These inconsistencies don’t have anything to do with the escape sequencing (or DML); they’re caused by the debugger’s parser. The two cases of note are the " character (string content) and the “;” character (command termination):

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]

You don’t have to go through this escape sequencing effort yourself, though. The debugger supports a special formatter for this case. Instead of generating the escape sequenced string and then using the %s formatter, you can just use the %Y{t} formatter on the original string.

You can also avoid the escape sequencing effort if you use a memory formatter. The %ma, %mu, %msa and %msu formatters allow you to output a string directly from the target’s address space; the debugging engine handles the reading of the string and the display, as shown in Figure 4.

Figure 4 **Reading the String and Display from a Memory Formatter **

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]

In the second and third examples in Figure 4, the strings printed with %s are truncated or omitted due to the < and > characters, but the %ma and %Y{t} output is correct. The Test02 application is in Figure 5.

Figure 5 Test02 Implementation

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

The !memorydml implementation is in Figure 6.

Figure 6 !memorydml Implementation

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

The test scripts (in the Example05 folder) have been changed to load a dump of the Test02 application instead of launching Notepad so that you have a string to output.

So, the easiest way to implement the display of strings from the target’s address space is to just use %ma and so on. If you need to manipulate a string that has been read prior to display, or have just made a string of your own, then apply the escape sequencing via %Y{t}. If you need to pass the string as the format string, then apply the escape sequencing yourself. Alternatively, split the output into multiple IDebugControl::ControlledOutput calls and just use the DML output control (DEBUG_OUTCTL_AMBIENT_DML) on the DML part of the content, outputting the rest as TEXT (DEBUG_OUTCTL_AMBIENT_TEXT) with no escape sequencing.

The other constraint in this area is the length limit of IDebugClient::ControlledOutput and IDebugClient::Output; they can only output about 16,000 characters at a time. I’ve found that I regularly hit this limit with ControlledOutput when doing DML output. The markup and the escape sequencing can easily bloat a string past 16,000 characters while still looking relatively small in the output window.

When you’re building large strings and are doing the escape sequencing yourself, you’ll need to make sure that you cut up the string at appropriate points. Don’t cut them up within the DML markup or within an escape sequence. Otherwise, they won’t be interpreted correctly.

There are two ways to achieve a hyperlink in the debugger: you can use <link> or <exec> markup. In both markups, the enclosed text is displayed with underscore and hypertext coloring (usually blue). The command that’s executed in both cases is the “cmd” member. It’s similar to the “href” member of the <a> markup in HTML:

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

The difference between <link> and <exec> can be hard to see. Actually, it took me quite a bit of research to get to the bottom of it. The difference is only observable in the Command Browser (Ctrl+N) window, not the Output window (Alt+1). In both types of window, the output of the <link> or <exec> link is displayed in the associated output window. The difference is what happens to the command prompt of each window.

In the Output window, the content of the command prompt doesn’t change. If there’s some unexecuted text there, it remains unchanged.

In the Command Browser window, when a <link> is invoked, the command is added to the list of commands and the command prompt is set to the command. But when an <exec> is invoked, the command isn’t added to the list and the command prompt isn’t changed. By not changing the command history, it’s possible to make a sequence of hyperlinks that can guide the user through a decision process. The most common example of this is the displaying of help. The navigation around help is well-suited to hyperlinking, yet it’s desirable not to log the navigation.

User Preference

So how do you know if the user even wants to see DML? In a few scenarios, data issues can occur when the output is converted to text, be it by the action of saving the text to a log file (.logopen) or copying and pasting from the output window. Data can be lost due to DML abbreviation, or the data may be superfluous due to the inability to do navigation via the text version of the output.

Similarly, if it’s onerous to generate the DML output, this effort should be avoided if it’s known that this output will be content-converted. Lengthy operations usually involve memory scans and symbol resolution.

Equally, users might just prefer not to have DML in their output.

In this abbreviation example that’s <link>-based, you’d only get “Object found at 0x0C876C32” outputted and miss the important piece of information (the data type of the address):

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

The correct way of handling this is to have a condition that avoids the abbreviation when DML isn’t enabled. Here’s an example of how you could fix this:

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

The .prefer_dml setting is the closest you can get to a user preference (so you can make this conditional decision about abbreviated or superfluous output). The setting is used to control whether the debugger runs DML-enhanced versions of the built-in commands and operations by default. Although it isn’t explicitly meant to specify whether DML should be used globally (within extensions), it’s a good substitute.

The only downside of this preference is that the preference defaults to off, and most debugging engineers don’t know that the .prefer_dml command exists.

Note that an extension, not the debugging engine, has to have code to detect the “.prefer_dml” preference or the “ability” (I’ll explain “ability” shortly). The debugging engine won’t strip the DML output for you based on this setting; if the debugger is DML-capable, it will output in DML regardless.

To get the current “.prefer_dml” preference, you need to do a QueryInterface on the passed IDebugClient interface for the IDebugControl interface. You then use the GetEngineOptions function to get the current DEBUG_ENGOPT_XXX bitmask. If the DEBUG_ENGOPT_PREFER_DML bit is set, .prefer_dml is enabled. Figure 7 has an example implementation of a user preference function.

Figure 7 PreferDML Implementation

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

You may be thinking that you don’t want to call the GetEngineOptions function in every command to determine the preference. Can’t we be notified of the change? After all, it probably won’t change very often. Yes, you can be more optimal, but there’s a catch.

What you can do is register an IDebugEventCallbacks implementation via IDebugClient::SetEventCallbacks. In the implementation, you register an interest in the DEBUG_EVENT_CHANGE_ENGINE_STATE notification. When IDebugControl::SetEngineOptions is called, the debugger invokes IDebugEventCallbacks::ChangeEngineState with the DEBUG_CES_ENGINE_OPTIONS bit set in the Flags parameter. The Argument parameter contains a DEBUG_ENGOPT_XXX bitmask like GetEngineOptions returns.

The catch is that only one event callback can be registered at any one time for an IDebugClient object. If two (or more) extensions want to register for event callbacks (which includes more important notifications such as module load/unload, thread start/stop, process start/stop and exceptions), someone is going to miss out. And if you modify the passed IDebugClient object, that someone will be the debugger!

If you want to implement the IDebugEventCallbacks callback, you need to make your own IDebugClient object via IDebugClient::CreateClient. You then associate your callback with this (new) IDebugClient object and become responsible for the lifetime of the IDebugClient.

For simplicity’s sake, you’re better off calling GetEngineOptions each time you need to determine the DEBUG_ENGOPT_PREFER_DML value. As mentioned before, you should call QueryInterface on the passed IDebugClient interface for the IDebugControl interface, and then call GetEngineOptions to be sure that you have the current (and correct) preference.

Ability of the Debugging Client

So how do you know if the debugger even supports DML?

If the debugger doesn’t support DML, data can be lost, superfluous or the effort can be onerous, much like the user preference. As mentioned, NTSD is a text-only debugger, and if DML is outputted to it, the debugging engine does content conversion to remove the DML from the output.

To get the debugging client’s ability, you need to do a QueryInterface on the passed IDebugClient interface for the IDebugAdvanced2 interface. You then use the Request function with the DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE request type. The HRESULT contains S_OK when at least one Output Callback is DML-aware, otherwise it returns S_FALSE. To reiterate, the flag doesn’t mean that all callbacks are aware; it means that atleast one is.

In seemingly text-only environments (such as NTSD) you can still get into the conditional output issues. If an extension registers an output callback that’s DML-aware (by returning DEBUG_OUTCBI_DML or DEBUG_OUTCBI_ANY_FORMAT from IDebugOutputCallbacks2::GetInterestMask) within NTSD, it will cause the Request function to return S_OK. Luckily, these extensions are quite rare. If they do exist, they should be checking the state of DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE and setting their ability accordingly (prior to their proclamation of their DML ability). Check out the next installment of this series for more information about DML-aware callbacks.

Figure 8 has an example implementation of an ability function.

Figure 8 AbilityDML Implementation

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

Note that the DEBUG_REQUEST_CURRENT_OUTPUT_CALLBACKS_ARE_DML_AWARE request type and IDebugOutputCallbacks2 interface aren’t documented in the MSDN Library yet.

Keeping the potential shortfalls in mind, the best way of handling user preference and client ability is:

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

The !ifdml implementation (in Figure 9) shows the PreferDML and AbilityDML functions in action so that conditional DML output is generated. Note that in the vast majority of cases, there’s no need to have a conditional statement such as this; you can safely rely on the debugger engine content conversion.

Figure 9 !ifdml Implementation

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

Using the test_windbg.cmd test script to load WinDbg, the output of !ifdml is:

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!

Using the test_ntsd.cmd test script to load NTSD, the output of !ifdml is:

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!

Controlled Output

To output DML, you need to use the IDebugControl::ControlledOutput function:

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

The difference between ControlledOutput and Output is the OutputControl parameter. This parameter is based on the DEBUG_OUTCTL_XXX constants. There are two parts to this parameter: the lower bits represent the scope of the output, and the higher bits represent the options. It’s a higher bit that enables DML.

One—and only one—of the DEBUG_OUTCTL_XXX scope-based constants must be used for the lower bits. The value directs where the output is to go. This can be to all debugger clients (DEBUG_OUTCTL_ALL_CLIENTS), just the IDebugClient associated with the IDebugControl interface (DEBUG_OUTCTL_THIS_CLIENT), all other clients (DEBUG_OUTCTL_ALL_OTHER_CLIENTS), nowhere at all (DEBUG_OUTCTL_IGNORE) or just the log file (DEBUG_OUTCTL_LOG_ONLY).

The higher bits are a bit mask and are also defined in the DEBUG_OUTCTL_XXX constants. There are constants to specify text- or DML-based output (DEBUG_OUTCTL_DML), if the output isn’t logged (DEBUG_OUTCTL_NOT_LOGGED) and whether a client’s output mask is honored (DEBUG_OUTCTL_OVERRIDE_MASK).

Output Control

In all the examples, I’ve set the ControlledOutput parameter to DEBUG_OUTCTL_AMBIENT_DML. Reading the documentation on MSDN, you might say that I could’ve also used DEBUG_OUTCTL_ALL_CLIENTS | DEBUG_OUTCTL_DML. However, this wouldn’t honor the IDebugControl output control preference.

If the extension’s command was invoked by IDebugControl::Execute, the OutputControl parameter of the Execute call should be used for any related output. IDebugControl::Output does this inherently, but when using IDebugControl::ControlledOutput, the responsibility of knowing the OutputControl value is the caller’s. The issue is that there’s no way to actually retrieve the current output control value from the IDebugControl interface (or any other interface). All is not lost, though; there are special DEBUG_OUTCTL_XXX “ambient” constants to handle the toggling of the DEBUG_OUTCTL_DML bit. When you use an ambient constant, the current output control is honored and just the DEBUG_OUTCTL_DML bit is set accordingly.

Instead of passing one of the lower constants with the higher DEBUG_OUTCTL_DML constant, you simply pass DEBUG_OUTCTL_AMBIENT_DML to enable DML output, or DEBUG_OUTCTL_AMBIENT_TEXT to disable DML output:

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

The Mask Parameter

Another parameter that I’ve been setting in the examples is the Mask parameter. You should set the Mask to an appropriate DEBUG_OUTPUT_XXX constant based on the text being outputted. Note that the Mask parameter is based on the DEBUG_OUTPUT_XXX constants; this isn’t to be confused with the DEBUG_OUTCTL_XXX constants.

The most common values that you’d use are DEBUG_OUTPUT_NORMAL for normal (general) output, DEBUG_OUTPUT_WARNING for warning output and DEBUG_OUTPUT_ERROR for error output. You should use DEBUG_OUTPUT_EXTENSION_WARNING when your extension has an issue.

The DEBUG_OUTPUT_XXX output flags are similar to stdout and stderr used with console output. Each output flag is an individual output channel. It’s up to the receivers (callbacks) to decide which of these channels will be listened to, how they’re to be combined (if it all) and how they’re to be displayed. For example, WinDbg by default displays all output flags except the DEBUG_OUTPUT_VERBOSE output flag in the Output window. You can toggle this behavior via View | Verbose Output (Ctrl+Alt+V).

The !maskdml implementation (see Figure 10) outputs a description styled with the associated output flag.

Figure 10 !maskdml Implementation

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

If you toggle Verbose Output after the command has run, the omitted DEBUG_OUTPUT_VERBOSE output won’t be shown; the output is lost.

WinDbg supports colorization of each output flag. In the View | Options dialog, you can specify a foreground and background color for each output flag. The color settings are saved in the workspace. To set them globally, launch WinDbg, delete all the workspaces, set the colors (and any other setting you would like) and save the workspace. I like to set the foreground color of Error to red, Warning to green, Verbose to blue, Extension Warning to purple and Symbols to gray. The default workspace will be the template for all future debugging sessions.

Figure 11 shows the output of !maskdml without (top) and with (bottom) verbose enabled.

!maskdml with Color Scheme

Figure 11 !maskdml with Color Scheme

Break!

It’s easy to enhance any extension with DML. And, with a little bit of infrastructure code, it’s also easy to honor the user preference. It’s definitely worth spending some extra time to generate output correctly. In particular, strive to always provide both text- and DML-based output when the output is abbreviated or superfluous, and direct this output appropriately.

In the next installment about the Debugger Engine API, I’m going to delve deeper into the relationship a debugger extension can have with the debugger. I’ll give you an overview of the architecture of debugger clients and debugger callbacks. In doing so, we’ll get into the nitty-gritty of the DEBUG_OUTPUT_XXX and DEBUG_OUTCTL_XXX constants.

I’ll use this foundation to implement an encapsulation of the Son of Strike, or SOS, debugger extension. I’ll enhance the SOS output with DML and demonstrate how you can leverage the built-in debugger commands and other extensions to retrieve information required by your extensions.

If you’re just interested in debugging and want to learn more, you should check out the “Advanced Windows Debugging and Troubleshooting” (NTDebugging) blog at blogs.msdn.com/b/ntdebugging—there are lots of training and case study articles to read.

Microsoft is always looking for talented debugging engineers. If you’re interested in joining the team, the job title to search for is “Escalation Engineer” at Microsoft Careers (careers.microsoft.com).


Andrew Richards is a Microsoft senior escalation engineer for Exchange Server. He has a passion for support tools and is continually creating debugger extensions and applications that simplify the job of support engineers.

Thanks to the following technical expert for reviewing this article: Drew Bliss