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