WinDbg is a great debugger, but it has lots of commands, so it takes time to get comfortable with it. I’ll be very brief and concise so that I don’t bore you to death! To do this, I’ll only show you the essential commands and the most important options. We’ll see additional commands and options when we need them in the next chapters.
To avoid problems, use the 32-bit version of WinDbg to debug 32-bit executables and the 64-bit version to debug 64-bit executables.
Alternatively, you can switch WinDbg between the 32-bit and 64-bit modes with the following command:
Open a new instance of WinDbg (if you’re debugging a process with WinDbg, close WinDbg and reopen it).
Under File→Symbol File Path enter
Save the workspace (File→Save Workspace).
The asterisks are delimiters. WinDbg will use the first directory we specified above as a local cache for symbols. The paths/urls after the second asterisk (separated by ‘;‘, if more than one) specify the locations where the symbols can be found.
Adding Symbols during Debugging
To append a symbol search path to the default one during debugging, use
(The command without the ‘+‘ would replace the default search path rather than append to it.)
Now reload the symbols:
Symbols, if available, are loaded when needed. To see what modules have symbols loaded, use
The x command supports wildcards and can be used to search for symbols in one or more modules. For instance, we can search for all the symbols in kernel32 whose name starts with virtual this way:
0:000> x kernel32!virtual* 757d4b5f kernel32!VirtualQueryExStub (<no parameter info>) 7576d950 kernel32!VirtualAllocExStub (<no parameter info>) 757f66f1 kernel32!VirtualAllocExNuma (<no parameter info>) 757d4b4f kernel32!VirtualProtectExStub (<no parameter info>) 757542ff kernel32!VirtualProtectStub (<no parameter info>) 7576d975 kernel32!VirtualFreeEx (<no parameter info>) 7575184b kernel32!VirtualFree (<no parameter info>) 75751833 kernel32!VirtualAlloc (<no parameter info>) 757543ef kernel32!VirtualQuery (<no parameter info>) 757510c8 kernel32!VirtualProtect (<no parameter info>) 757ff14d kernel32!VirtualProtectEx (<no parameter info>) 7575183e kernel32!VirtualFreeStub (<no parameter info>) 75751826 kernel32!VirtualAllocStub (<no parameter info>) 7576d968 kernel32!VirtualFreeExStub (<no parameter info>) 757543fa kernel32!VirtualQueryStub (<no parameter info>) 7576eee1 kernel32!VirtualUnlock (<no parameter info>) 7576ebdb kernel32!VirtualLock (<no parameter info>) 7576d95d kernel32!VirtualAllocEx (<no parameter info>) 757d4b3f kernel32!VirtualAllocExNumaStub (<no parameter info>) 757ff158 kernel32!VirtualQueryEx (<no parameter info>)
The wildcards can also be used in the module part:
0:000> x *!messagebox* 7539fbd1 USER32!MessageBoxIndirectA (<no parameter info>) 7539fcfa USER32!MessageBoxExW (<no parameter info>) 7539f7af USER32!MessageBoxWorker (<no parameter info>) 7539fcd6 USER32!MessageBoxExA (<no parameter info>) 7539fc9d USER32!MessageBoxIndirectW (<no parameter info>) 7539fd1e USER32!MessageBoxA (<no parameter info>) 7539fd3f USER32!MessageBoxW (<no parameter info>) 7539fb28 USER32!MessageBoxTimeoutA (<no parameter info>) 7539facd USER32!MessageBoxTimeoutW (<no parameter info>)
You can force WinDbg to load symbols for all modules with
This takes a while. Go to Debug→Break to stop the operation.
or press F1 to open help window.
To get help for a specific command type
where <command> is the command you’re interested in, or press F1 and select the tab Index where you can search for the topic/command you want.
You can either debug a new process or a process already running:
- Run a new process to debug with File→Open Executable.
- Attach to a process already running with File→Attach to a Process.
To debug a program remotely there are at least two options:
- If you’re already debugging a program locally on machine A, you can enter the following command (choose the port you want):
This will start a server within WinDbg.
On machine B, run WinDbg and go to File→Connect to Remote Session and enter
tcp:Port=1234,Server=<IP of Machine A>
specifying the right port and IP.
- On machine A, run dbgsrv with the following command:
dbgsrv.exe -t tcp:port=1234
This will start a server on machine A.
On machine B, run WinDbg, go to File→Connect to Remote Stub and enter
tcp:Port=1234,Server=<IP of Machine A>
with the appropriate parameters.
You’ll see that File→Open Executable is disabled, but you can choose File→Attach to a Process. In that case, you’ll see the list of processes on machine A.
To stop the server on machine A you can use Task Manager and kill dbgsrv.exe.
When you load an executable or attach to a process, WinDbg will list the loaded modules. If you want to list the modules again, enter
To list a specific module, say ntdll.dll, use
lmf m ntdll
To get the image header information of a module, say ntdll.dll, type
The ‘!‘ means that the command is an extension, i.e. an external command which is exported from an external DLL and called inside WinDbg. Users can create their own extensions to extend WinDbg’s functionality.
You can also use the start address of the module:
0:000> lmf m ntdll start end module name 77790000 77910000 ntdll ntdll.dll 0:000> !dh 77790000
WinDbg supports expressions, meaning that when a value is required, you can type the value directly or you can type an expression that evaluates to a value.
For instance, if EIP is 77c6cb70, then
You can also use symbols:
Numbers are by default in base 16. To be explicit about the base used, add a prefix:
0x123: base 16 (hexadecimal)
0n123: base 10 (decimal)
0t123: base 8 (octal)
0y111: base 2 (binary)
Use the command .format to display a value in many formats:
0:000> .formats 123 Evaluate expression: Hex: 00000000`00000123 Decimal: 291 Octal: 0000000000000000000443 Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00000001 00100011 Chars: .......# Time: Thu Jan 01 01:04:51 1970 Float: low 4.07778e-043 high 0 Double: 1.43773e-321
To evaluate an expression use ‘?‘:
Registers and Pseudo-registers
WinDbg supports several pseudo-registers that hold certain values. Pseudo-registers are indicated by the prefix ‘$‘.
When using registers or pseudo-registers, one can add the prefix ‘@‘ which tells WinDbg that what follows is a register and not a symbol. If ‘@‘ is not used, WinDbg will first try to interpret the name as a symbol.
Here are a few examples of pseudo-registers:
- $teb or @$teb (address of the TEB)
- $peb or @$peb (address of the PEB)
- $thread or @$thread (current thread)
To break on a specific exception, use the command sxe. For instance, to break when a module is loaded, type
sxe ld <module name 1>,...,<module name N>
sxe ld user32
To see the list of exceptions type
To ignore an exception, use sxi:
This cancels out the effect of our first command.
WinDbg breaks on single-chance exceptions and second-chance exceptions. They’re not different kinds of exceptions. As soon as there’s an exception, WinDbg stops the execution and says that there’s been a single-chance exception. Single-chance means that the exception hasn’t been sent to the debuggee yet. When we resume the execution, WinDbg sends the exception to the debuggee. If the debuggee doesn’t handle the exception, WinDbg stops again and says that there’s been a second-chance exception.
When we examine EMET 5.2, we’ll need to ignore single-chance single step exceptions. To do that, we can use the following command:
When you put a software breakpoint on one instruction, WinDbg saves to memory the first byte of the instruction and overwrites it with 0xCC which is the opcode for “int 3“.
When the “int 3” is executed, the breakpoint is triggered, the execution stops and WinDbg restores the instruction by restoring its first byte.
To put a software breakpoint on the instruction at the address 0x4110a0 type
You can also specify the number of passes required to activate the breakpoint:
bp 4110a0 3
This means that the breakpoint will be ignored the first 2 times it’s encountered.
To resume the execution (and stop at the first breakpoint encountered) type
which is short for “go“.
To run until a certain address is reached (containing code), type
g <code location>
Internally, WinDbg will put a software breakpoint on the specified location (like ‘bp‘), but will remove the breakpoint after it has been triggered. Basically, ‘g‘ puts a one-time software breakpoint.
Hardware breakpoints use specific registers of the CPU and are more versatile than software breakpoints. In fact, one can break on execution or on memory access.
Hardware breakpoints don’t modify any code so they can be used even with self modifying code. Unfortunately, you can’t set more than 4 breakpoints.
In its simplest form, the format of the command is
ba <mode> <size> <address> <passes (default=1)>
where <mode> can be
- ‘e‘ for execute
- ‘r‘ for read/write memory access
- ‘w‘ for write memory access
<size> specifies the size of the location, in bytes, to monitor for access (it’s always 1 when <mode> is ‘e‘).
<address> is the location where to put the breakpoint and <passes> is the number of passes needed to activate the breakpoint (see ‘bp‘ for an example of its usage).
Note: It’s not possible to use hardware breakpoints for a process before it has started because hardware breakpoints are set by modifying CPU registers (dr0, dr1, etc…) and when a process starts and its threads are created the registers are reset.
To list the breakpoints type
where ‘bl‘ stands for breakpoint list.
0:000> bl 0 e 77c6cb70 0002 (0002) 0:**** ntdll!CsrSetPriorityClass+0x40
where the fields, from left to right, are as follows:
- 0: breakpoint ID
- e: breakpoint status; can be (e)nabled or (d)isabled
- 77c6cb70: memory address
- 0002 (0002): the number of passes remaining before the activation, followed by the total number of passes to wait for the activation (i.e. the value specified when the breakpoint was created).
- 0:****: the associated process and thread. The asterisks mean that the breakpoint is not thread-specific.
- ntdll!CsrSetPriorityClass+0x40: the module, function and offset where the breakpoint is located.
To disable a breakpoint type
bd <breakpoint id>
To delete a breakpoint use
bc <breakpoint ID>
To delete all the breakpoints type
If you want to execute a certain command automatically every time a breakpoint is triggered, you can specify the command like this:
bp 40a410 ".echo \"Here are the registers:\n\"; r"
Here’s another example:
bp jscript9+c2c47 ".printf \"new Array Data: addr = 0x%p\\n\",eax;g"
There are at least 3 types of stepping:
- step-in / trace (command: t)
This command breaks after every single instruction. If you are on a call or int, the command breaks on the first instruction of the called function or int handler, respectively.
- step-over (command: p)
This command breaks after every single instruction without following calls or ints, i.e. if you are on a call or int, the command breaks on the instruction right after the call or int.
- step-out (command: gu)
This command (go up) resume execution and breaks right after the next ret instruction. It’s used to exit functions.
There two other commands for exiting functions:
- tt (trace to next return): it’s equivalent to using the command ‘t‘ repeatedly and stopping on the first ret encountered.
- pt (step to next return): it’s equivalent to using the command ‘p‘ repeatedly and stopping on the first ret encountered.
Note that tt goes inside functions so, if you want to get to the ret instruction of the current function, use pt instead.
The difference between pt and gu is that pt breaks on the ret instruction, whereas gu breaks on the instruction right after.
Here are the variants of ‘p‘ and ‘t‘:
- pa/ta <address>: step/trace to address
- pc/tc: step/trace to next call/int instruction
- pt/tt: step/trace to next ret (discussed above at point 3)
- pct/tct: step/trace to next call/int or ret
- ph/th: step/trace to next branching instruction
To display the contents of memory, you can use ‘d‘ or one of its variants:
- db: display bytes
- dw: display words (2 bytes)
- dd: display dwords (4 bytes)
- dq: display qwords (8 bytes)
- dyb: display bits
- da: display null-terminated ASCII strings
- du: display null-terminated Unicode strings
Type .hh d for seeing other variants.
The command ‘d‘ displays data in the same format as the most recent d* command (or db if there isn’t one).
The (simplified) format of these commands is
Here, the asterisk is used to represent all the variations we listed above and the square brackets indicate that range is optional. If range is missing, d* will display the portion of memory right after the portion displayed by the most recent d* command.
Ranges can be specified many ways:
- <start address> <end address>
db 77cac000 77cac0ff
- <start address> L<number of elements>
dd 77cac000 L10
displays 10 dwords starting with the one at 77cac000.
Note: for ranges larger than 256 MB, we must use L? instead of L to specify the number of elements.
- <start address>
When only the starting point is specified, WinDbg will display 128 bytes.
You can edit memory by using
e[d|w|b] <address> [<new value 1> ... <new value N>]
where [d|w|b] is optional and specifies the size of the elements to edit (d = dword, w = word, b = byte).
If the new values are omitted, WinDbg will ask you to enter them interactively.
Here’s an example:
ed eip cc cc
This overwrites the first two dwords at the address in eip with the value 0xCC.
To search memory use the ‘s‘ command. Its format is:
s [-d|-w|-b|-a|-u] <start address> L?<number of elements> <search values>
where d, w, b, a and u means dword, word, byte, ascii and unicode.
<search values> is the sequence of values to search.
s -d eip L?1000 cc cc
searches for the two consecutive dwords 0xcc 0xcc in the memory interval [eip, eip + 1000*4 – 1].
Sometimes you need to dereference a pointer. The operator to do this is poi:
In this command, poi(ebp+4) evaluates to the dword (or qword, if in 64-bit mode) at the address ebp+4.
To display the registers, type
To display specific registers, say eax and edx, type
r eax, edx
To print the first 3 instructions pointed to by EIP, use
u EIP L3
where ‘u‘ is short for unassemble and ‘L‘ lets you specify the number of lines to display.
To display the call stack use
Here are the commands used to display structures:
|!teb||Displays the TEB (Thread Environment Block).|
|$teb||Address of the TEB.|
|!peb||Displays the PEB (Process Environment Block).|
|$peb||Address of the PEB.|
|!exchain||Displays the current exception handler chain.|
|!vadump||Displays the list of memory pages and info.|
|!lmi <module name>||Displays information for the specified module.|
|!slist <address> [ <symbol> [<offset>] ]||Displays a singly-linked list, where:|
|dt <struct name>||Displays the structure <struct name>.|
|dt <struct name> <field>||Displays the field <field> of the structure <struct name>.|
|dt <struct name> <address>||Displays the data at <address> as a structure of type <struct name> (you need symbols for <struct name>).|
|dg <first selector> [<last selector>]||Displays the segment descriptor for the specified selectors.|
Save the workspace (File→Save Workspace) after setting up the windows.