Custom Debugger Auto-Expansion Tips
There is a fair amount of existing information (plus the official “EEAddIn” sample) on the subject of adding custom tool tips when hovering over various data types in the debugger. But lately I’ve run across a bunch of people who have never heard of it, so I figured I would post some details and a few of the extensions for standard Win32 types which I have in my collection.
For those new to the topic, there is some extensibility built into the VC debugger which allows you to supply custom text to the tool-tips which popup when you hover over a variable. For example, when you hover over a structure, the debugger will pop up a balloon which looks like:
Using a custom handler, we can query the object and provide our own output to make it look like:
The main idea is to turn something which might not contain very useful or actionable information into something with more context and meaning; thus making it easier to debug your application.
The above mentioned sample shows how to do this for the SYSTEMTIME and FILETIME structures, so here I will provide some functions which can be used to display custom text for the WIN32_FIND_DATAA, PROCESS_INFORMATION, CRITICAL_SECTION(1), and OVERLAPPED structs.
Struct |
||
CRITICAL_SECTION |
Before |
|
After |
| |
OVERLAPPED |
Before |
|
After |
| |
PROCESS_INFORMATION |
Before |
|
After |
|
To start with, we will get the helper functions out of the way. Since I’m a nice guy, I support the “nBase” parameter which allows the debugger’s user to get back information in either base 10 or base 16 depending on their preference. The below function does the number to text formatting depending on the base. The return value is the number of bytes written to the string, which allows us too easily (and performantly(2)) concatenate strings. I have a handful of sprint_type functions, but I will just supply one of them as a sample, the others (which you will see used below) should be fairly self explanatory:
size_t sprint_int(char *buffer, size_t maxLen, int val, unsigned base)
{
if(base == 8)
return sprintf_s(buffer, maxLen, "%o", val);
else if(base == 10)
return sprintf_s(buffer, maxLen, "%d", val);
else if(base == 16)
return sprintf_s(buffer, maxLen, "0x%08x", val);
else
return sprintf_s(buffer, maxLen, "%d", val);
}
WIN32_FIND_DATAA
HRESULT __stdcall OS_WIN32_FIND_DATAA(DWORD dwAddress, DEBUGHELPER *pHelper, int nBase,
BOOL fReserved1, char *pResult, size_t max, DWORD dwReserved2)
{
WIN32_FIND_DATAA findData = {0};
DWORD bytesRead = 0;
HRESULT hr = S_OK;
hr = pHelper->ReadDebuggeeMemoryEx(pHelper, pHelper->GetRealAddress(pHelper), sizeof(findData), &findData, &bytesRead);
if(FAILED(hr))
{
return hr;
}
if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
sprintf_s(pResult, max, "\"%s\" (DIR)", findData.cFileName);
}
else
{
// package up the file size into a single 64bit value
LARGE_INTEGER size;
size.HighPart = findData.nFileSizeHigh;
size.LowPart = findData.nFileSizeLow;
double dblSize = (double)size.QuadPart;
// write out the filename + the file size using appropriate units
if(size.QuadPart > 1073741824)
sprintf_s(pResult, max, "\"%s\" (%.2lf GB)", findData.cFileName, dblSize / 1073741824.0);
else if(size.QuadPart > 1048576)
sprintf_s(pResult, max, "\"%s\" (%.2lf MB)", findData.cFileName, dblSize / 1048576.0);
else if(size.QuadPart > 1024)
sprintf_s(pResult, max, "\"%s\" (%.2lf KB)", findData.cFileName, dblSize / 1024.0);
else
sprintf_s(pResult, max, "\"%s\" (%I64u B)", findData.cFileName, size.QuadPart);
}
return S_OK;
}
Feel free (if needed) to expand on this to add in the file’s attributes and a timestamp.
CRITICAL_SECTION
HRESULT __stdcall OS_CRITICAL_SECTION(DWORD dwAddress, DEBUGHELPER *pHelper, int nBase,
BOOL fReserved1, char *pResult, size_t max, DWORD dwReserved2)
{
CRITICAL_SECTION cs = {0};
RTL_CRITICAL_SECTION_DEBUG csDebug = {0};
DWORD bytesRead = 0;
HRESULT hr = S_OK;
unsigned slen = 0;
hr = pHelper->ReadDebuggeeMemoryEx(pHelper, pHelper->GetRealAddress(pHelper), sizeof(cs), &cs, &bytesRead);
if(FAILED(hr))
{
return hr;
}
// write out the primary count values
slen += sprintf_s(pResult+slen, max-slen, "lock=");
slen += sprint_int(pResult+slen, max-slen, cs.LockCount, nBase);
slen += sprintf_s(pResult+slen, max-slen, " recursion=");
slen += sprint_int(pResult+slen, max-slen, cs.RecursionCount, nBase);
slen += sprintf_s(pResult+slen, max-slen, " spin=");
slen += sprint_unsigned(pResult+slen, max-slen, cs.SpinCount, nBase);
// if the debugging information looks valid, grab some information from
// the debug struct
if(cs.DebugInfo && !(cs.SpinCount&RTL_CRITICAL_SECTION_FLAG_NO_DEBUG_INFO))
{
hr = pHelper->ReadDebuggeeMemory(pHelper, (DWORD)cs.DebugInfo, sizeof(csDebug), &csDebug, &bytesRead);
if(SUCCEEDED(hr) && (bytesRead == sizeof(csDebug)))
{
slen += sprintf_s(pResult+slen, max-slen, " [entry=");
slen += sprint_unsigned(pResult+slen, max-slen, csDebug.EntryCount, nBase);
slen += sprintf_s(pResult+slen, max-slen, " contention=");
slen += sprint_unsigned(pResult+slen, max-slen, csDebug.ContentionCount, nBase);
slen += sprintf_s(pResult+slen, max-slen, "]");
}
}
return S_OK;
}
OVERLAPPED
HRESULT __stdcall OS_OVERLAPPED(DWORD dwAddress, DEBUGHELPER *pHelper, int nBase,
BOOL fReserved1, char *pResult, size_t max, DWORD dwReserved2)
{
OVERLAPPED over = {0};
DWORD bytesRead = 0;
HRESULT hr = S_OK;
unsigned slen = 0;
hr = pHelper->ReadDebuggeeMemoryEx(pHelper, pHelper->GetRealAddress(pHelper), sizeof(over), &over, &bytesRead);
if(FAILED(hr))
{
return hr;
}
// package up the offset values into a single 64bit value
LARGE_INTEGER offset = {0};
offset.LowPart = over.Offset;
offset.HighPart = over.OffsetHigh;
slen += sprintf_s(pResult+slen, max-slen, "offset=");
slen += sprint_u64(pResult+slen, max-slen, offset.QuadPart, nBase);
slen += sprintf_s(pResult+slen, max-slen, " event=0x%08x", over.hEvent);
return S_OK;
}
PROCESS_INFORMATION
HRESULT __stdcall OS_PROCESS_INFORMATION(DWORD dwAddress, DEBUGHELPER *pHelper, int nBase,
BOOL fReserved1, char *pResult, size_t max, DWORD dwReserved2)
{
PROCESS_INFORMATION procInfo = {0};
DWORD bytesRead = 0;
HRESULT hr = S_OK;
unsigned slen = 0;
hr = pHelper->ReadDebuggeeMemoryEx(pHelper, pHelper->GetRealAddress(pHelper), sizeof(procInfo), &procInfo, &bytesRead);
if(FAILED(hr))
{
return hr;
}
// first see if the process still exists
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procInfo.dwProcessId);
if(hProc == NULL)
{
sprintf_s(pResult, max, "handle=%p PID=%u (exited)", procInfo.hProcess, procInfo.dwProcessId);
return S_OK;
}
// attempt to query the filename of the process
char exeName[MAX_PATH+1] = {0};
if(GetProcessImageFileName(hProc, exeName, MAX_PATH))
{
char *filePart = strrchr(exeName, '\\');
if(filePart) // strip off the leading device\path information
slen += sprintf_s(pResult+slen, max-slen, "\"%s\"", filePart+1);
else
slen += sprintf_s(pResult+slen, max-slen, "\"%s\"", exeName);
}
else
{
// couldn't get the filename, so display the handle value instead
slen += sprintf_s(pResult+slen, max-slen, "handle=%p", procInfo.hProcess);
}
// if the process has exited, but there are still outstanding handles to
// it, then we can still get exit code status
DWORD exitCode = 0;
GetExitCodeProcess(hProc, &exitCode);
// write out the PID and the status
if(exitCode == STILL_ACTIVE)
slen += sprintf_s(pResult+slen, max-slen, " PID=%u (running)", procInfo.dwProcessId);
else
slen += sprintf_s(pResult+slen, max-slen, " PID=%u (exited: %lu)", procInfo.dwProcessId, exitCode);
CloseHandle(hProc);
return S_OK;
}
Setup
And as shown in the EEAddIn sample, you will want to use a .DEF file to specify the function exports; this allows you to use non-mangled names in the autoexp.dat file.
debugTypes.def
EXPORTS
OS_WIN32_FIND_DATAA
OS_PROCESS_INFORMATION
OS_CRITICAL_SECTION
OS_OVERLAPPED
Now just copy the built DLL into a directory where the IDE can find it (3) and add the following entries to the [AutoExpand] section of the autoexp.dat file, which should be located in the visual studio install path under "\Common7\Packages\Debugger". Since my DLL was named DebugTypes.dll, you will want to replace the filename below with your DLL’s name.
autoexp.dat
_WIN32_FIND_DATAA=$ADDIN(DebugTypes.dll,OS_WIN32_FIND_DATAA)
_PROCESS_INFORMATION=$ADDIN(DebugTypes.dll,OS_PROCESS_INFORMATION)
_RTL_CRITICAL_SECTION=$ADDIN(DebugTypes.dll,OS_CRITICAL_SECTION)
_OVERLAPPED=$ADDIN(DebugTypes.dll,OS_OVERLAPPED)
Footnotes
(1) A CRITICAL_SECTION is really just a typedef for the RTL_CRITICAL_SECTION struct. So you will see "_RTL_CRITICAL_SECTION" in the autoexp.dat file instead.
(2) By keeping track of the length as you go, you avoid the performance hit typically associated with a strcat() type of function call. And no, I don't think that "performantly" is a real word.
(3) I use a Post-Build event in my project to automatically copy the DLL into the Visual Studio install path:
copy "$(TargetPath)" "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE"
Comments
Anonymous
May 29, 2010
Great article. Trying to use addin to display custom array elements which are stored in heap memory. As soon as I set my array object's pointer to the elements heap address, the ReadDebuggeeMemoryEx call fails to return my array object that contains the heap pointer. Prior to setting the pointer it read it fine. Thanks for any suggestions.Anonymous
June 09, 2010
Hello, this is very interesting. Thus I met an issue I didn't solve yet, and I though you could help me about it (I already posted my issue on MSDN forums, no solution have been posted). I'm actually trying to implement an expression evaluator for Visual Studio 9 debug. It should make me able to visualize the contents of a variable whose class( still unknown ) is derived from an abstract class (say I2dBufferAccessor) . So the beginning of the code could be like: /.../ I2dBufferAccessor* accessor; helper->ReadDebuggeeMemory(pHelper, pHelper->GetRealAddress(pHelper), sizeof(vlam::ia::I2dBufferAccessor*), accessor, &n_got)); ...unfortunately this code doesn't work... And I can't figure out how to do, since I can't instanciate an I2dBufferAccessor variable, since it's an abstract class! I hope I've been clear and you will be able to help me. Thank you for your attention. (and sorry for my english) Maxime