Display Binary Numbers and Struct Data with Printf in WinDbg

Was comparing an application behavior between Windows XP and Windows 10 and needed to check the value of some structs, without symbol information for them. The values I wanted to check were specific bits in the struct passed as the 2nd parameter to a function. In this case, I wanted to display the contents of DCB struct, but in an easy-to-read format when comparing traces. In addition, was working in an environment where it was difficult to copy files in/out so using/writing an extension wasn’t a suitable option.

This is fairly straightforward with WinDbg Preview and the JavaScript capability, and normally have used the SyntheticTypes script to parse simple C headers with the relevant struct https://github.com/microsoft/WinDbg-Samples/tree/master/SyntheticTypes

We can use .formats command but this offers limited if any formatting capability.

0:000> .formats $t0
Evaluate expression:
  Hex:     7642b9ad
  Decimal: 1984084397
  Octal:   16620534655
  Binary:  01110110 01000010 10111001 10101101
  Chars:   vB..
  Time:    Mon Nov 15 09:33:17 2032
  Float:   low 9.87375e+032 high 0
  Double:  9.80268e-315

If you can use the JavaScript capability I would use it instead of this approach.

There may be better ways to achieve this with legacy WinDbg and native scripting, but this approach while tedious does work.

One approach can use a combination of boolean and, left shift and right shifts to extract specific bits. For a 32-bit value position “31” would be the first binary digit, and position “0” would have the final digit.

This can be done concisely with a loop, however, in WinDbg this is extremely slow, taking about a minute on my machine to run the loop. Here we will assume the value we want to display in binary is stored in $t0.

0:000> .for (r $t1 = 0x1F; $t1 >= 0 ; r $t1 = $t1-1) { .printf "%d",($t0 & ( 1 << $t1 )) >> $t1 }
01110110010000101011100110101101

A speedier version is:

0:000>  .printf "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d",($t0 & ( 1 << 0x1F)) >> 0x1F,($t0 & ( 1 << 0x1E)) >> 0x1E,($t0 & ( 1 << 0x1D)) >> 0x1D,($t0 & ( 1 << 0x1C)) >> 0x1C,($t0 & ( 1 << 0x1B)) >> 0x1B,($t0 & ( 1 << 0x1A)) >> 0x1A,($t0 & ( 1 << 0x19)) >> 0x19,($t0 & ( 1 << 0x18)) >> 0x18,($t0 & ( 1 << 0x17)) >> 0x17,($t0 & ( 1 << 0x16)) >> 0x16,($t0 & ( 1 << 0x15)) >> 0x15,($t0 & ( 1 << 0x14)) >> 0x14,($t0 & ( 1 << 0x13)) >> 0x13,($t0 & ( 1 << 0x12)) >> 0x12,($t0 & ( 1 << 0x11)) >> 0x11,($t0 & ( 1 << 0x10)) >> 0x10,($t0 & ( 1 << 0xF)) >> 0xF,($t0 & ( 1 << 0xE)) >> 0xE,($t0 & ( 1 << 0xD)) >> 0xD,($t0 & ( 1 << 0xC)) >> 0xC,($t0 & ( 1 << 0xB)) >> 0xB,($t0 & ( 1 << 0xA)) >> 0xA,($t0 & ( 1 << 0x9)) >> 0x9,($t0 & ( 1 << 0x8)) >> 0x8,($t0 & ( 1 << 0x7)) >> 0x7,($t0 & ( 1 << 0x6)) >> 0x6,($t0 & ( 1 << 0x5)) >> 0x5,($t0 & ( 1 << 0x4)) >> 0x4,($t0 & ( 1 << 0x3)) >> 0x3,($t0 & ( 1 << 0x2)) >> 0x2,($t0 & ( 1 << 0x1)) >> 0x1,($t0 & ( 1 << 0x0)) >> 0x0
01110110010000101011100110101101

If we look at our struct debugging on a target that has debugging symbols, we can see some values are not just individual “bits” but take up multiple positions i.e. bits 15-31 for fDummy2

0:000> dx -r1 ((DcbTest!_DCB *)0x4ffb9c)
((DcbTest!_DCB *)0x4ffb9c)                 : 0x4ffb9c [Type: _DCB *]
    [+0x000] DCBlength        : 0x1c [Type: unsigned long]
    [+0x004] BaudRate         : 0x2580 [Type: unsigned long]
    [+0x008 ( 0: 0)] fBinary          : 0x1 [Type: unsigned long]
    [+0x008 ( 1: 1)] fParity          : 0x0 [Type: unsigned long]
    [+0x008 ( 2: 2)] fOutxCtsFlow     : 0x1 [Type: unsigned long]
    [+0x008 ( 3: 3)] fOutxDsrFlow     : 0x0 [Type: unsigned long]
    [+0x008 ( 5: 4)] fDtrControl      : 0x2 [Type: unsigned long]
    [+0x008 ( 6: 6)] fDsrSensitivity  : 0x1 [Type: unsigned long]
    [+0x008 ( 7: 7)] fTXContinueOnXoff : 0x0 [Type: unsigned long]
    [+0x008 ( 8: 8)] fOutX            : 0x1 [Type: unsigned long]
    [+0x008 ( 9: 9)] fInX             : 0x0 [Type: unsigned long]
    [+0x008 (10:10)] fErrorChar       : 0x1 [Type: unsigned long]
    [+0x008 (11:11)] fNull            : 0x0 [Type: unsigned long]
    [+0x008 (13:12)] fRtsControl      : 0x3 [Type: unsigned long]
    [+0x008 (14:14)] fAbortOnError    : 0x1 [Type: unsigned long]
    [+0x008 (31:15)] fDummy2          : 0x19999 [Type: unsigned long]
    [+0x00c] wReserved        : 0xcccc [Type: unsigned short]
    [+0x00e] XonLim           : 0x7b [Type: unsigned short]
    [+0x010] XoffLim          : 0x1c8 [Type: unsigned short]
    [+0x012] ByteSize         : 0x8 [Type: unsigned char]
    [+0x013] Parity           : 0x4 [Type: unsigned char]
    [+0x014] StopBits         : 0x2 [Type: unsigned char]
    [+0x015] XonChar          : 23 [Type: char]
    [+0x016] XoffChar         : 45 '-' [Type: char]
    [+0x017] ErrorChar        : 67 'C' [Type: char]
    [+0x018] EofChar          : 89 'Y' [Type: char]
    [+0x019] EvtChar          : 98 'b' [Type: char]
    [+0x01a] wReserved1       : 0xcccc [Type: unsigned short]

We can get the binary value by taking those bit positions. In this case, our value is the 2nd parameter in the 32-bit standard calling convention. As we have just hit a breakpoint where the function starts we find the struct base address at poi(@esp+8) and add 8 for the offset where our 32-bit value we are breaking apart begins.

0:000> r $t0= poi(poi(@esp+8)+8)
0:000> .printf "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d",($t0 & ( 1 << 0x1F)) >> 0x1F,($t0 & ( 1 << 0x1E)) >> 0x1E,($t0 & ( 1 << 0x1D)) >> 0x1D,($t0 & ( 1 << 0x1C)) >> 0x1C,($t0 & ( 1 << 0x1B)) >> 0x1B,($t0 & ( 1 << 0x1A)) >> 0x1A,($t0 & ( 1 << 0x19)) >> 0x19,($t0 & ( 1 << 0x18)) >> 0x18,($t0 & ( 1 << 0x17)) >> 0x17,($t0 & ( 1 << 0x16)) >> 0x16,($t0 & ( 1 << 0x15)) >> 0x15,($t0 & ( 1 << 0x14)) >> 0x14,($t0 & ( 1 << 0x13)) >> 0x13,($t0 & ( 1 << 0x12)) >> 0x12,($t0 & ( 1 << 0x11)) >> 0x11,($t0 & ( 1 << 0x10)) >> 0x10,($t0 & ( 1 << 0xF)) >> 0xF
11001100110011001   

To display it as a number we can add the bits together and use left shifts to convert it back to a value we can display as hex or decimal.

0:000> .printf "0x%X",((($t0 & ( 1 << 0x1F)) >> 1F) << 0x10) + ((($t0 & ( 1 << 0x1E)) >> 1E) << 0xF) + ((($t0 & ( 1 << 0x1D)) >> 1D) << 0xE) + ((($t0 & ( 1 << 0x1C)) >> 1C) << 0xD) + ((($t0 & ( 1 << 0x1B)) >> 1B) << 0xC) + ((($t0 & ( 1 << 0x1A)) >> 1A) << 0xB) + ((($t0 & ( 1 << 0x19)) >> 19) << 0xA) + ((($t0 & ( 1 << 0x18)) >> 18) << 0x9) + ((($t0 & ( 1 << 0x17)) >> 17) << 0x8) + ((($t0 & ( 1 << 0x16)) >> 16) << 0x7) + ((($t0 & ( 1 << 0x15)) >> 15) << 0x6) + ((($t0 & ( 1 << 0x14)) >> 14) << 0x5) + ((($t0 & ( 1 << 0x13)) >> 13) << 0x4) + ((($t0 & ( 1 << 0x12)) >> 12) << 0x3) + ((($t0 & ( 1 << 0x11)) >> 11) << 0x2) + ((($t0 & ( 1 << 0x10)) >> 10) << 0x1) + ((($t0 & ( 1 << 0xF)) >> F) << 0x0)
0x19999

This is fairly tedious to type out so used a simple PowerShell script I can use to generate the value to type in and set the text to the clipboard to paste into WinDbg:

$bitStart = 31
$bitEnd = 15

$cmd = ""
For ($i = $bitStart;$i -ge $bitEnd;$i --)
{
    $cmd+=[String]::Format("(((`$t0 & ( 1 << 0x{0:X})) >> {0:X}) << 0x{1:X})",$i,$i-$bitEnd)
    if ($i -ne $bitEnd) { $cmd += " + " }
}

$cmd | Set-Clipboard

Combining these techniques we can print out our struct now in an easy to read format. (Well the output is easy to read, not the command…)

0:000> r $t0 = poi(poi(@esp+8)+8);.printf "DCBlength=%i\nBaudRate=%i\nfBinary=%d\nfParity=%d\nfOutxCtsFlow=%d\nfOutxDsrFlow=%d\nfDtrControl=%d\nfDsrSensitivity=%d\nfTXContinueOnXoff=%d\nfOutX=%d\nfInX=%d\nfErrorChar=%d\nfNull=%d\nfRtsControl=%d\nfAbortOnError=%d\n",poi(poi(@esp+8)),poi(poi(@esp+8)+4),($t0 & ( 1 << 0x0)) >> 0x0,($t0 & ( 1 << 0x1)) >> 0x1,($t0 & ( 1 << 0x2)) >> 0x2,($t0 & ( 1 << 0x3)) >> 0x3,((($t0 & ( 1 << 0x5)) >> 5) << 0x1) + ((($t0 & ( 1 << 0x4)) >> 4) << 0x0),($t0 & ( 1 << 0x6)) >> 0x6,($t0 & ( 1 << 0x7)) >> 0x7,($t0 & ( 1 << 0x8)) >> 0x8,($t0 & ( 1 << 0x9)) >> 0x9,($t0 & ( 1 << 0xA)) >> 0xA,($t0 & ( 1 << 0xB)) >> 0xB,((($t0 & ( 1 << 0xD)) >> D) << 0x1) + ((($t0 & ( 1 << 0xC)) >> C) << 0x0),($t0 & ( 1 << 0xE)) >> 0xE;r $t0=poi(poi(@esp+8)+0xe);r $t0=poi(poi(@esp+8)+0xe);.printf "XonLim=%i\nXoffLim=%i\n",((($t0 & ( 1 << 0xF)) >> F) << 0xF) + ((($t0 & ( 1 << 0xE)) >> E) << 0xE) + ((($t0 & ( 1 << 0xD)) >> D) << 0xD) + ((($t0 & ( 1 << 0xC)) >> C) << 0xC) + ((($t0 & ( 1 << 0xB)) >> B) << 0xB) + ((($t0 & ( 1 << 0xA)) >> A) << 0xA) + ((($t0 & ( 1 << 0x9)) >> 9) << 0x9) + ((($t0 & ( 1 << 0x8)) >> 8) << 0x8) + ((($t0 & ( 1 << 0x7)) >> 7) << 0x7) + ((($t0 & ( 1 << 0x6)) >> 6) << 0x6) + ((($t0 & ( 1 << 0x5)) >> 5) << 0x5) + ((($t0 & ( 1 << 0x4)) >> 4) << 0x4) + ((($t0 & ( 1 << 0x3)) >> 3) << 0x3) + ((($t0 & ( 1 << 0x2)) >> 2) << 0x2) + ((($t0 & ( 1 << 0x1)) >> 1) << 0x1) + ((($t0 & ( 1 << 0x0)) >> 0) << 0x0),((($t0 & ( 1 << 0x1F)) >> 1F) << 0xF) + ((($t0 & ( 1 << 0x1E)) >> 1E) << 0xE) + ((($t0 & ( 1 << 0x1D)) >> 1D) << 0xD) + ((($t0 & ( 1 << 0x1C)) >> 1C) << 0xC) + ((($t0 & ( 1 << 0x1B)) >> 1B) << 0xB) + ((($t0 & ( 1 << 0x1A)) >> 1A) << 0xA) + ((($t0 & ( 1 << 0x19)) >> 19) << 0x9) + ((($t0 & ( 1 << 0x18)) >> 18) << 0x8) + ((($t0 & ( 1 << 0x17)) >> 17) << 0x7) + ((($t0 & ( 1 << 0x16)) >> 16) << 0x6) + ((($t0 & ( 1 << 0x15)) >> 15) << 0x5) + ((($t0 & ( 1 << 0x14)) >> 14) << 0x4) + ((($t0 & ( 1 << 0x13)) >> 13) << 0x3) + ((($t0 & ( 1 << 0x12)) >> 12) << 0x2) + ((($t0 & ( 1 << 0x11)) >> 11) << 0x1) + ((($t0 & ( 1 << 0x10)) >> 10) << 0x0);r $t0=poi(poi(@esp+8)+0x12);.printf "ByteSize=%d\nParity=%d\nStopBits=%d\nXonChar='%C'\nXoffChar='%C'\nErrorChar='%C'\nEofChar='%C'\nEvtChar='%C'\n",((($t0 & ( 1 << 0x7)) >> 7) << 0x7) + ((($t0 & ( 1 << 0x6)) >> 6) << 0x6) + ((($t0 & ( 1 << 0x5)) >> 5) << 0x5) + ((($t0 & ( 1 << 0x4)) >> 4) << 0x4) + ((($t0 & ( 1 << 0x3)) >> 3) << 0x3) + ((($t0 & ( 1 << 0x2)) >> 2) << 0x2) + ((($t0 & ( 1 << 0x1)) >> 1) << 0x1) + ((($t0 & ( 1 << 0x0)) >> 0) << 0x0),((($t0 & ( 1 << 0xF)) >> F) << 0x7) + ((($t0 & ( 1 << 0xE)) >> E) << 0x6) + ((($t0 & ( 1 << 0xD)) >> D) << 0x5) + ((($t0 & ( 1 << 0xC)) >> C) << 0x4) + ((($t0 & ( 1 << 0xB)) >> B) << 0x3) + ((($t0 & ( 1 << 0xA)) >> A) << 0x2) + ((($t0 & ( 1 << 0x9)) >> 9) << 0x1) + ((($t0 & ( 1 << 0x8)) >> 8) << 0x0),((($t0 & ( 1 << 0x17)) >> 17) << 0x7) + ((($t0 & ( 1 << 0x16)) >> 16) << 0x6) + ((($t0 & ( 1 << 0x15)) >> 15) << 0x5) + ((($t0 & ( 1 << 0x14)) >> 14) << 0x4) + ((($t0 & ( 1 << 0x13)) >> 13) << 0x3) + ((($t0 & ( 1 << 0x12)) >> 12) << 0x2) + ((($t0 & ( 1 << 0x11)) >> 11) << 0x1) + ((($t0 & ( 1 << 0x10)) >> 10) << 0x0),poi(poi(@esp+8)+0x15),poi(poi(@esp+8)+0x16),poi(poi(@esp+8)+0x17),poi(poi(@esp+8)+0x18),poi(poi(@esp+8)+0x19)
DCBlength=28
BaudRate=9600
fBinary=1
fParity=0
fOutxCtsFlow=1
fOutxDsrFlow=0
fDtrControl=2
fDsrSensitivity=1
fTXContinueOnXoff=0
fOutX=1
fInX=0
fErrorChar=1
fNull=0
fRtsControl=3
fAbortOnError=1
XonLim=123
XoffLim=456
ByteSize=8
Parity=4
StopBits=2
XonChar=''
XoffChar='-'
ErrorChar='C'
EofChar='Y'
EvtChar='b'

We can also split a 32-bit number stored in $t0 into four separate 1 byte values using this approach, allowing us to print an 8-bit value as a number. And provides an alternative in older versions of WinDbg which don’t support %C for ASCII char output.

0:000> r $t0 = poi(poi(@esp+8)+0x15)
0:000> r $t1 = ((($t0 & ( 1 << 0x7)) >> 7) << 0x7) + ((($t0 & ( 1 << 0x6)) >> 6) << 0x6) + ((($t0 & ( 1 << 0x5)) >> 5) << 0x5) + ((($t0 & ( 1 << 0x4)) >> 4) << 0x4) + ((($t0 & ( 1 << 0x3)) >> 3) << 0x3) + ((($t0 & ( 1 << 0x2)) >> 2) << 0x2) + ((($t0 & ( 1 << 0x1)) >> 1) << 0x1) + ((($t0 & ( 1 << 0x0)) >> 0) << 0x0);r $t2 = ((($t0 & ( 1 << 0xF)) >> F) << 0x7) + ((($t0 & ( 1 << 0xE)) >> E) << 0x6) + ((($t0 & ( 1 << 0xD)) >> D) << 0x5) + ((($t0 & ( 1 << 0xC)) >> C) << 0x4) + ((($t0 & ( 1 << 0xB)) >> B) << 0x3) + ((($t0 & ( 1 << 0xA)) >> A) << 0x2) + ((($t0 & ( 1 << 0x9)) >> 9) << 0x1) + ((($t0 & ( 1 << 0x8)) >> 8) << 0x0);r $t3 = ((($t0 & ( 1 << 0x17)) >> 17) << 0x7) + ((($t0 & ( 1 << 0x16)) >> 16) << 0x6) + ((($t0 & ( 1 << 0x15)) >> 15) << 0x5) + ((($t0 & ( 1 << 0x14)) >> 14) << 0x4) + ((($t0 & ( 1 << 0x13)) >> 13) << 0x3) + ((($t0 & ( 1 << 0x12)) >> 12) << 0x2) + ((($t0 & ( 1 << 0x11)) >> 11) << 0x1) + ((($t0 & ( 1 << 0x10)) >> 10) << 0x0);r $t4 = ((($t0 & ( 1 << 0x1F)) >> 1F) << 0x7) + ((($t0 & ( 1 << 0x1E)) >> 1E) << 0x6) + ((($t0 & ( 1 << 0x1D)) >> 1D) << 0x5) + ((($t0 & ( 1 << 0x1C)) >> 1C) << 0x4) + ((($t0 & ( 1 << 0x1B)) >> 1B) << 0x3) + ((($t0 & ( 1 << 0x1A)) >> 1A) << 0x2) + ((($t0 & ( 1 << 0x19)) >> 19) << 0x1) + ((($t0 & ( 1 << 0x18)) >> 18) << 0x0)
0:000> .printf "%i, %i, %i, %i",$t1,$t2,$t3,$t4
23, 45, 67, 89

About chentiangemalc

specializes in end-user computing technologies. disclaimer 1) use at your own risk. test any solution in your environment. if you do not understand the impact/consequences of what you're doing please stop, and ask advice from somebody who does. 2) views are my own at the time of posting and do not necessarily represent my current view or the view of my employer and family members/relatives. 3) over the years Microsoft/Citrix/VMWare have given me a few free shirts, pens, paper notebooks/etc. despite these gifts i will try to remain unbiased.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a comment