Undocumented WinDBG

Abstraction and encapsulation are good because they make it easier to build complex systems, however, there are times you have to peek inside the abstraction and demistify the encapsulation. This is especially true for debugging and performance tuning (I will not talk about reverse engineering this time). Familiar yourself with the right tools are very important, and one way to achieve this is to debug into these tools...

Before getting started, I would like to wetting your appetite with a few questions:

  1. Are there undocumented commands in the Windows Debuggers?
  2. WinDBG supports child process debugging via .childdbg, does Visual Studio Debugger provide the same thing?
  3. Why do I need to put symbols on the target machine while remote debugging .NET applications in Visual Studio 2010?
  4. Why does the command foobarbaz not working as expected?

Let's revisit the commands in WinDBG. There are three types of commands available:

  1. Debugger Commands
    These are the internal commands, examples are bp, g and pt. Internal commands are case insensitive in most cases (as and aS are exceptions), you can get a brief help of these commands by typing a single question mark ( ? ).
  2. Meta Commands (a.k.a. Dot Commands)
    Meta commands always start with a dot, examples are .childdbg, .dvalloc and  .dvfree. Meta commands are case insensitive, you can get a brief help of these commands using the meta command  .help.
  3. Extension Commands
    Extension commands are provided by separate debugger extension modules, example are !dh and  !sym. Debugging Tools for Windows has provided the SDK for implementing such extension DLLs, and it's fully documented (Writing New Debugger Extensions). Extension commands are case sensitive, some extensions (e.g. Son of Strike) has exported a same function under different names (e.g. SOS!CLRStack, SOS!ClrStack and SOS!clrstack).

The documented way of working with extensions contains a few commands like .chain, .extmatch, .load, .loadby, .setdll.unload and .unloadall, an undocumented command  .extcmds is also available.

 0:000>  .extmatch /e dbghelp * 
!dbghelp.chksym
!dbghelp.dh
!dbghelp.homedir
!dbghelp.lmi
!dbghelp.stackdbg
!dbghelp.sym

However, once you understand how these extension DLLs work, you can dump the PE/COFF Export Directory to get a full list:

 0:000>  .sympath . 
Symbol search path is: .
Expanded Symbol search path is: .

0:000>  .reload /f dbghelp.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Debugging Tools for Windows (x64)\dbghelp.dll -

0:000> x dbghelp!* 
00000000`68013dd0 dbghelp!ExtensionApiVersion (<no parameter info>)
00000000`68013de0 dbghelp!WinDbgExtensionDllInit (<no parameter info>)
00000000`68014090 dbghelp!fptr (<no parameter info>)
00000000`68014130 dbghelp!vc7fpo (<no parameter info>)
00000000`680141b0 dbghelp!stackdbg (<no parameter info>)
00000000`680146e0 dbghelp!stack_force_ebp (<no parameter info>)
00000000`680148c0 dbghelp!sym (<no parameter info>)
00000000`68014a50 dbghelp!symsrv (<no parameter info>)
00000000`68014bd0 dbghelp!lminfo (<no parameter info>)
00000000`68015060 dbghelp!lmi (<no parameter info>)
00000000`680164e0 dbghelp!itoldyouso (<no parameter info>)
00000000`68016720 dbghelp!chksym (<no parameter info>)
00000000`68016960 dbghelp!block (<no parameter info>)
00000000`68016b50 dbghelp!omap (<no parameter info>)
00000000`68016e10 dbghelp!homedir (<no parameter info>)
00000000`68017030 dbghelp!srcfiles (<no parameter info>)
00000000`68018a90 dbghelp!dh (<no parameter info>)
 0:000>  !itoldyouso
!IToldYouSo <module> [symbol]

!IToldYouSo tests the validity of a module against a symbol file.
The module can be specified by either its name or base address.
If a symbol file is not specified, then the loaded symbol is tested.
Otherwise, if a pdb or dbg symbol file path is specified, it is tested
against the loaded module.

Most of the meta commands are implemented in dbgeng.dll, a few are implemented inside specific debuggers (e.g. .browse and .wtitle are WinDBG specific meta commands). Some meta commands are only available for certain modes/targets/platforms, for example, .kdfiles is only available for live kernel debugging on x86-based and Itanium-based processors.

Some meta commands might be undocumented (e.g. .aliascmds and  .sxcmds) or documented separately (e.g. .perfer_dml is documented in dml.doc which can be found under the WinDBG installation folder, .jdinfo is only documented in .help), there is no centralized place to get all these commands.

The following command would dump all the dot commands exported by dbgeng.dll (dbgeng!DotCommand is an exception, which is not a meta command):

 0:000> x dbgeng!Dot* 
00000000`517aba70 dbgeng!DotRestart = <no type information>
00000000`51801c70 dbgeng!DotExtMatch = <no type information>
00000000`517a9ac0 dbgeng!DotLogAppend = <no type information>
00000000`517ae600 dbgeng!DotTTime = <no type information>
00000000`517a3d70 dbgeng!DotEchoTimestamps = <no type information>
00000000`517aacd0 dbgeng!DotPromptAllow = <no type information>
00000000`517a53e0 dbgeng!DotEventStr = <no type information>
00000000`517aa0b0 dbgeng!DotOCommand = <no type information>
00000000`5179de00 dbgeng!DotPageIn = <no type information>
00000000`517a09f0 dbgeng!DotCache = <no type information>
00000000`517aaa80 dbgeng!DotPrintf = <no type information>
00000000`517a60a0 dbgeng!DotFnRet = <no type information>
00000000`5179d3c0 dbgeng!DotThread = <no type information>
00000000`517a9a40 dbgeng!DotLocale = <no type information>
00000000`517a46f0 dbgeng!DotEndSrv = <no type information>
00000000`517a33b0 dbgeng!DotDumpOff = <no type information>
00000000`517a5900 dbgeng!DotExr = <no type information>
00000000`517a2880 dbgeng!DotCxr = <no type information>
00000000`517a4f50 dbgeng!DotEvents = <no type information>
00000000`5179fb30 dbgeng!DotApplyDbp = <no type information>
00000000`517ae350 dbgeng!DotTrap = <no type information>
00000000`517a98c0 dbgeng!DotKFrames = <no type information>
00000000`517aca70 dbgeng!DotSleep = <no type information>
00000000`5179f9e0 dbgeng!DotAllowImageMapping = <no type information>
00000000`517a3e20 dbgeng!DotEcxr = <no type information>
00000000`517a5e30 dbgeng!DotFixImports = <no type information>
00000000`517aa3b0 dbgeng!DotOpenDump = <no type information>
00000000`517a0500 dbgeng!DotBpSync = <no type information>
00000000`5179d9b0 dbgeng!DotProcess = <no type information>
00000000`517ac280 dbgeng!DotServers = <no type information>
00000000`5179dd80 dbgeng!DotKernelKill = <no type information>
00000000`517a2ea0 dbgeng!DotDmlStart = <no type information>
00000000`517ab890 dbgeng!DotRecordBranches = <no type information>
00000000`517a06c0 dbgeng!DotBugCheck = <no type information>
00000000`517a2bd0 dbgeng!DotDebugSwWow = <no type information>
00000000`517a1510 dbgeng!DotContext = <no type information>
00000000`517a4740 dbgeng!DotEnumTag = <no type information>
00000000`517a8b40 dbgeng!DotIf = <no type information>
00000000`5179db50 dbgeng!DotFiber = <no type information>
00000000`517ab810 dbgeng!DotReboot = <no type information>
00000000`517a03a0 dbgeng!DotBpCmds = <no type information>
00000000`5183b550 dbgeng!DotDmlFlow = <no type information>
00000000`517ace20 dbgeng!DotSrcPath = <no type information>
00000000`517a3380 dbgeng!DotDumpDebug = <no type information>
00000000`517adf50 dbgeng!DotProcessInfo = <no type information>
00000000`5179f950 dbgeng!DotAllowBpBaConvert = <no type information>
00000000`517ab960 dbgeng!DotReCxr = <no type information>
00000000`518568c0 dbgeng!DotPCmd = <no type information>
00000000`517aba10 dbgeng!DotReload = <no type information>
00000000`5179fe60 dbgeng!DotAttach = <no type information>
00000000`517aa070 dbgeng!DotNoVersion = <no type information>
00000000`518eb230 dbgeng!DotLines = <no type information>
00000000`517a2dd0 dbgeng!DotDmlFile = <no type information>
00000000`517a7740 dbgeng!DotFormats = <no type information>
00000000`517accb0 dbgeng!DotSrcNoisy = <no type information>
00000000`517a3170 dbgeng!DotDumpCab = <no type information>
00000000`517a8e00 dbgeng!DotIgnoreWowKdContext = <no type information>
00000000`517ad880 dbgeng!DotTList = <no type information>
00000000`517ab3c0 dbgeng!DotReadMem = <no type information>
00000000`517acb20 dbgeng!DotSrcFix = <no type information>
00000000`517a5670 dbgeng!DotExPtr = <no type information>
00000000`517a6540 dbgeng!DotForceBranchTrace = <no type information>
00000000`517a3540 dbgeng!DotDumpPOff = <no type information>
00000000`517aec40 dbgeng!DotWake = <no type information>
00000000`517a0370 dbgeng!DotBlock = <no type information>
00000000`517abcb0 dbgeng!DotSendFile = <no type information>
00000000`517a62d0 dbgeng!DotFor = <no type information>
00000000`517a3140 dbgeng!DotDrivers = <no type information>
00000000`517a9b10 dbgeng!DotLogFile = <no type information>
00000000`517a37d0 dbgeng!DotDvAlloc = <no type information>
00000000`517ab0a0 dbgeng!DotPop = <no type information>
00000000`517d9c20 dbgeng!DotDump = <no type information>
00000000`517a7c30 dbgeng!DotFrame = <no type information>
00000000`517af540 dbgeng!DotHelp = <no type information>
00000000`517a9be0 dbgeng!DotLogOpen = <no type information>
00000000`517af8b0 dbgeng!DotCommand = <no type information>
00000000`517a9c00 dbgeng!DotNetSyms = <no type information>
00000000`517ace80 dbgeng!DotStepFilter = <no type information>
00000000`517a1660 dbgeng!DotContinue = <no type information>
00000000`517a05d0 dbgeng!DotBreakin = <no type information>
00000000`517a4170 dbgeng!DotEnableLongStatus = <no type information>
00000000`517a6680 dbgeng!DotForceSystemInit = <no type information>
00000000`517a5f90 dbgeng!DotFnEnt = <no type information>
00000000`517a0e70 dbgeng!DotCatch = <no type information>
00000000`517a9840 dbgeng!DotKdFiles = <no type information>
00000000`517a0ea0 dbgeng!DotChain = <no type information>
00000000`517a2380 dbgeng!DotCrash = <no type information>
00000000`5183a420 dbgeng!DotAsm = <no type information>
00000000`517ac900 dbgeng!DotShowSymFailures = <no type information>
00000000`517ac1b0 dbgeng!DotServer = <no type information>
00000000`517ac800 dbgeng!DotShowReadFailures = <no type information>
00000000`517a6810 dbgeng!DotForEach = <no type information>
00000000`517e9d60 dbgeng!DotEventLog = <no type information>
00000000`517ad210 dbgeng!DotSymOpt = <no type information>
00000000`517a6620 dbgeng!DotForceRadixOutput = <no type information>
00000000`517a7160 dbgeng!DotFpo = <no type information>
00000000`517a3fa0 dbgeng!DotEffMach = <no type information>
00000000`517a7d80 dbgeng!DotFrameRel = <no type information>
00000000`517ae430 dbgeng!DotTss = <no type information>
00000000`51725a30 dbgeng!DotAliasCmds = <no type information>
00000000`517a41d0 dbgeng!DotEnableUnicode = <no type information>
00000000`517aa030 dbgeng!DotNoShell = <no type information>
00000000`517aa540 dbgeng!DotOutMask = <no type information>
00000000`517a9880 dbgeng!DotKdTrans = <no type information>
00000000`517a3d20 dbgeng!DotEchoTime = <no type information>
00000000`517aa800 dbgeng!DotPreferDml = <no type information>
00000000`517a9a10 dbgeng!DotLeave = <no type information>
00000000`517a7ec0 dbgeng!DotFrameEbpFix = <no type information>
00000000`517a05a0 dbgeng!DotBreak = <no type information>
00000000`517a3a30 dbgeng!DotDvFree = <no type information>
00000000`517a54a0 dbgeng!DotExpr = <no type information>
00000000`517a30b0 dbgeng!DotDbgDbg = <no type information>
00000000`517a5930 dbgeng!DotExtCmds = <no type information>
00000000`517aed10 dbgeng!DotWriteMem = <no type information>
00000000`517ad040 dbgeng!DotSxCmds = <no type information>
00000000`517a59e0 dbgeng!DotExtPath = <no type information>
00000000`517aafc0 dbgeng!DotPush = <no type information>
00000000`517ad0e0 dbgeng!DotSymFix = <no type information>
00000000`517a2420 dbgeng!DotCreate = <no type information>
00000000`517ac2b0 dbgeng!DotShell = <no type information>
00000000`517a3010 dbgeng!DotDo = <no type information>
00000000`517aec90 dbgeng!DotWhile = <no type information>
00000000`517a9d40 dbgeng!DotNetUse = <no type information>
00000000`517ae170 dbgeng!DotTimeZone = <no type information>
00000000`517a0f90 dbgeng!DotChildDbg = <no type information>
00000000`517a1130 dbgeng!DotClients = <no type information>
00000000`517a4680 dbgeng!DotEndPSrv = <no type information>
00000000`517a8910 dbgeng!DotHoldMem = <no type information>
00000000`517a8ea0 dbgeng!DotImgScan = <no type information>
00000000`517ad3e0 dbgeng!DotTListFromNtQuerySystemInformation = <no type information>
00000000`517a4b40 dbgeng!DotEventCode = <no type information>
00000000`517a3630 dbgeng!DotDumpAddRgn = <no type information>
00000000`517a9790 dbgeng!DotJdInfo = <no type information>
00000000`517ad330 dbgeng!DotSymPath = <no type information>
00000000`517ade40 dbgeng!DotTime = <no type information>
00000000`517a5430 dbgeng!DotExePath = <no type information>
00000000`517a8d60 dbgeng!DotIgnoreMissingPages = <no type information>
00000000`517aeaa0 dbgeng!DotTxtSym = <no type information>
00000000`517a3c70 dbgeng!DotEchoCpuNum = <no type information>
00000000`517a3c20 dbgeng!DotEcho = <no type information>
00000000`517aa010 dbgeng!DotNoEngErr = <no type information>
00000000`517aa7d0 dbgeng!DotPCache = <no type information>
00000000`5179f8b0 dbgeng!DotAllowExecCmds = <no type information>
00000000`517a1ab0 dbgeng!DotCorDll = <no type information>
00000000`517a26e0 dbgeng!DotCreateDir = <no type information>
00000000`517a1860 dbgeng!DotCopySym = <no type information>
00000000`517a9af0 dbgeng!DotLogClose = <no type information>
00000000`517a4230 dbgeng!DotTypeOpt = <no type information>
00000000`517a0b70 dbgeng!DotCall = <no type information>
00000000`517abc10 dbgeng!DotSecure = <no type information>
00000000`517aa230 dbgeng!DotOFilter = <no type information>
00000000`517a11f0 dbgeng!DotCloseHandle = <no type information>
00000000`517ab180 dbgeng!DotQuitLock = <no type information>
00000000`517a9990 dbgeng!DotLastEvent = <no type information>
00000000`517a2220 dbgeng!DotCorStack = <no type information>

Also, you might have noticed that .elif and .else donnot have their corresponding exports like .if does, they just look like meta commands (the document called them Command Tokens). To understand how the command line got parsed and executed (like a simplified version of compiler front end), I would recommend debugging the debugger by setting a breakpoint on the Win32 Beep function (kernel32!Beep or KERNELBASE!Beep) and use .beep, below is what I got on my Windows XP box:

 0:000> k
kernel32!Beep
windbg!DirectCommand+0x12a
windbg!CmdExecuteCmd+0x90
windbg!WinCommand::OnNotify+0x467
windbg!WinBase::BaseProc+0xc91
USER32!InternalCallWinProc+0x28
USER32!UserCallWinProcCheckWow+0x150
USER32!SendMessageWorker+0x4a5
USER32!SendMessageW+0x7f
MSFTEDIT!CW32System::SendMessage+0x3d
MSFTEDIT!CTxtWinHost::TxNotify+0x97
MSFTEDIT!RichEditWndProc+0x19b
USER32!InternalCallWinProc+0x28
USER32!UserCallWinProcCheckWow+0x150
USER32!DispatchMessageWorker+0x306
USER32!DispatchMessageW+0xf
windbg!ProcessNonDlgMessage+0x2a2
windbg!ProcessPendingMessages+0x64
windbg!wmain+0x24a
windbg!_initterm_e+0x163
kernel32!BaseProcessStart+0x23

0:000> k
kernel32!Beep
ntsd!UiCommand+0x287
ntsd!MainLoop+0x48f
ntsd!main+0x232
ntsd!_initterm_e+0x163
kernel32!BaseProcessStart+0x23

0:000> k
kernel32!Beep
cdb!UiCommand+0x287
cdb!MainLoop+0x48f
cdb!main+0x232
cdb!_initterm_e+0x163
kernel32!BaseProcessStart+0x23

0:000> k
kernel32!Beep
kd!UiCommand+0x287
kd!MainLoop+0x48f
kd!main+0x1ed
kd!_initterm_e+0x163
kernel32!BaseProcessStart+0x23

In order to answer why Visual Studio lacks support for child process debugging, you need to know how the user mode native debugger works, especially how to write a debug event loop. I have an example in Data Breakpoints, and I've purposely left a note in the "A few things to mention" section #4. You can use these hints and debug into the Visual Studio Debugger.

Regarding the symbol for remote debugging, you will need some background of metadata (ECMA-335 Partition 2) and PDB, also a bit of ICorDebug and ICLRData (mscordacwks!CLRDataCreateInstance). Launch procmon.exe and see who is consuming the symbol files.

What if a WinDBG command fails to do the job, or is not working as expected? There is no magic, just grab a debugger and debug it, in fact the debugger team has presented you a gift called .dbgdbg to make things a little easier!

 0:000> uf dbgeng!DotReadMem
dbgeng!DotReadMem:
0213fc90 8bff            mov     edi,edi
0213fc92 55              push    ebp
0213fc93 8bec            mov     ebp,esp
0213fc95 81ec64080000    sub     esp,864h
0213fc9b a10c513302      mov     eax,dword ptr [dbgeng!__security_cookie (0233510c)]
0213fca0 33c5            xor     eax,ebp
0213fca2 8945fc          mov     dword ptr [ebp-4],eax
0213fca5 833d4450360200  cmp     dword ptr [dbgeng!g_Process (02365044)],0
0213fcac 750c            jne     dbgeng!DotReadMem+0x2a (0213fcba)

dbgeng!DotReadMem+0x1e:
0213fcae 6a00            push    0
0213fcb0 6818100000      push    1018h
0213fcb5 e8b6ba1400      call    dbgeng!ReportError (0228b770)

dbgeng!DotReadMem+0x2a:
0213fcba a1004d3302      mov     eax,dword ptr [dbgeng!g_SymOptions (02334d00)]
0213fcbf 2500000400      and     eax,40000h
0213fcc4 740c            je      dbgeng!DotReadMem+0x42 (0213fcd2)

dbgeng!DotReadMem+0x36:
0213fcc6 6a00            push    0
0213fcc8 682a100000      push    102Ah
0213fccd e89eba1400      call    dbgeng!ReportError (0228b770)

dbgeng!DotReadMem+0x42:
0213fcd2 c785c0f7ffff00000000 mov dword ptr [ebp-840h],0
0213fcdc 8d8dbcf7ffff    lea     ecx,[ebp-844h]
0213fce2 51              push    ecx
0213fce3 6a0d            push    0Dh
0213fce5 e876c41400      call    dbgeng!StringValue (0228c160)
0213fcea 8985c4f7ffff    mov     dword ptr [ebp-83Ch],eax
0213fcf0 8b154c1a3502    mov     edx,dword ptr [dbgeng!g_CurCmd (02351a4c)]
0213fcf6 8995e4f7ffff    mov     dword ptr [ebp-81Ch],edx
0213fcfc a14c1a3502      mov     eax,dword ptr [dbgeng!g_CurCmd (02351a4c)]
0213fd01 668b8dbcf7ffff  mov     cx,word ptr [ebp-844h]
0213fd08 668908          mov     word ptr [eax],cx
0213fd0b c785e8f7ffff00000100 mov dword ptr [ebp-818h],10000h
0213fd15 c785ecf7ffff00000000 mov dword ptr [ebp-814h],0
0213fd1f 6800001000      push    100000h
0213fd24 6a03            push    3
0213fd26 6a01            push    1
0213fd28 8d95e8f7ffff    lea     edx,[ebp-818h]
0213fd2e 52              push    edx
0213fd2f 8d85c8f7ffff    lea     eax,[ebp-838h]
0213fd35 50              push    eax
0213fd36 e8053b0400      call    dbgeng!GetRange (02183840)
0213fd3b 33c9            xor     ecx,ecx
0213fd3d 8b95e4f7ffff    mov     edx,dword ptr [ebp-81Ch]
0213fd43 66890a          mov     word ptr [edx],cx
0213fd46 6a00            push    0
0213fd48 6880000000      push    80h
0213fd4d 6a03            push    3
0213fd4f 6a00            push    0
0213fd51 6a00            push    0
0213fd53 6800000080      push    80000000h
0213fd58 8b85c4f7ffff    mov     eax,dword ptr [ebp-83Ch]
0213fd5e 50              push    eax
0213fd5f ff158c743302    call    dword ptr [dbgeng!kernel32_CreateFileW_Ptr (0233748c)]
0213fd65 8985f4f7ffff    mov     dword ptr [ebp-80Ch],eax
0213fd6b 83bdf4f7ffffff  cmp     dword ptr [ebp-80Ch],0FFFFFFFFh
0213fd72 7512            jne     dbgeng!DotReadMem+0xf6 (0213fd86)

As you can see, the .readmem command makes use of kernel32!CreateFileW with dwDesiredAccess = GENERIC_READ and dwShareMode = 0 (instead of FILE_SHARE_READ), that would explain why you got an error "Unable to open file" in some cases. A quick debugging showed CreateFileW failed and last error value is ERROR_SHARING_VIOLATION.

The documented syntax for .readmem is .readmem FileName Range, while there is no notes for how big the Range value can go, I'll leave this as a homework for you, cheers.