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 FileSymbol File Path enter


Save the workspace (FileSave 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

.sympath+ c:\symbolpath

(The command without the ‘+‘ would replace the default search path rather than append to it.)
Now reload the symbols:


Checking Symbols

Symbols, if available, are loaded when needed. To see what modules have symbols loaded, use

x *!

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 DebugBreak to stop the operation.


Just type


or press F1 to open help window.
To get help for a specific command type

.hh <command>

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.

Debugging Modes


You can either debug a new process or a process already running:

  1. Run a new process to debug with FileOpen Executable.
  2. Attach to a process already running with FileAttach to a Process.


To debug a program remotely there are at least two options:

  1. If you’re already debugging a program locally on machine A, you can enter the following command (choose the port you want):
    .server tcp:port=1234

    This will start a server within WinDbg.
    On machine B, run WinDbg and go to FileConnect to Remote Session and enter

    tcp:Port=1234,Server=<IP of Machine A>

    specifying the right port and IP.

  2. 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 FileConnect to Remote Stub and enter

    tcp:Port=1234,Server=<IP of Machine A>

    with the appropriate parameters.
    You’ll see that FileOpen Executable is disabled, but you can choose FileAttach 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

!dh ntdll

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

bp 77c6cb71


bp EIP+1

are equivalent.
You can also use symbols:

u ntdll!CsrSetPriorityClass+0x41

and registers:

dd ebp+4

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 ‘?‘:

? eax+4

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>

For instance,

sxe ld user32

To see the list of exceptions type


To ignore an exception, use sxi:

sxi ld

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:

sxd sse


Software Breakpoints

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

bp 4110a0

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

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

  1. e‘ for execute
  2. r‘ for read/write memory access
  3. 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.

Handling Breakpoints

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

bc *

Breakpoint Commands

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:

  1. 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.
  2. 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.
  3. 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

Displaying Memory

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

d* [range]

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:

  1. <start address> <end address>
    For instance,

    db 77cac000 77cac0ff
  2. <start address> L<number of elements>
    For instance,

    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.

  3. <start address>
    When only the starting point is specified, WinDbg will display 128 bytes.

Editing Memory

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.

Searching Memory

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.
For instance,

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:

dd poi(ebp+4)

In this command, poi(ebp+4) evaluates to the dword (or qword, if in 64-bit mode) at the address ebp+4.

Miscellaneous Commands

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


Dumping Structures

Here are the commands used to display structures:

!tebDisplays the TEB (Thread Environment Block).
$tebAddress of the TEB.
!pebDisplays the PEB (Process Environment Block).
$pebAddress of the PEB.
!exchainDisplays the current exception handler chain.
!vadumpDisplays 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:

  • <address> is the address of the pointer to the first node of the list
  • <symbol> is the name of the structure of the nodes
  • <offset> is the offset of the field “next” within the node
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.

Suggested SETUP


Save the workspace (FileSave Workspace) after setting up the windows.

The following two tabs change content below.

Massimiliano Tomassoli

Computer scientist, software developer, reverse engineer and student of computer security (+ piano player & music composer)

Latest posts by Massimiliano Tomassoli (see all)

Leave a Reply

13 Comments on "WinDbg"

Notify of

Sort by:   newest | oldest | most voted
1 month 9 days ago

Very Nice!
but how can put a breakpoint when EIP is in 0x00400000 – 0x00401000
like “Alt + m” and “f2” with OllyDBG?

1 year 23 days ago

Great Article! is there any way to colour the different code sections ? I feel it makes it easier to browse through the disassembly and the memory. Thanks!

1 year 6 months ago

Very Nice Course !
One question where to get symbols for windbg

1 year 8 months ago

Quick hint: !wow64exts.sw
This UNDOCUMENTED command will switch windbg between 32 bit and 64 bit modes.

1 year 8 months ago

Not documented under windbg, I would say. But here is a documentation fox x64:

1 year 8 months ago

Nice articles. Could be this created in PDF files? or One PDF covering all those? Will be easy for read. Thanks a lot