Skip to content
Lesson 2.4Following Instructions and Addresses

Breakpoints: One Moment of Program State

Pause on an instruction or memory access, inspect registers, memory, and the call stack, and continue without losing the thread.

Lesson 2.4
Read or listenPrint chapter or book
About 12 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 2.4Key ideas, reference and questions

A one-page summary of “Breakpoints: One Moment of Program State” you can keep beside your notes. Plain Markdown.

Lesson 2.4 — Breakpoints: One Moment of Program State: cheatsheet

  • Remember: A breakpoint says that something happened, not why. The action you just took and the call stack tell you the reason.
  • Also useful: Think of a sticky note over one word in a book: the debugger covers a byte with a marker and keeps the original word safe to put back.

Key terms

  • u32: Watching four bytes does not tell the debugger that they are a u32.
  • nop: nop means “no operation.” Replacing an instruction with one or more nop bytes is a quick, temporary way to test what that instruction does.

Review

  • Q: What is the main purpose of a breakpoint in a reversing experiment? A: Pause one path so registers, memory, and the stack can be read
  • Q: A write breakpoint on the player's health fires while damage is taken. What is the best next step? A: Check the call stack, then heal, load, and respawn to compare
Download cheatsheet (.md)

A breakpoint asks the debugger to pause when a specific event happens. The two kinds we use most are:

  • code breakpoint — pause when the CPU reaches an instruction;
  • memory breakpoint — pause when code reads, writes, or executes at an address.

The pause is useful because the thread’s register state and instruction location are still available. A code breakpoint normally stops before its instruction; a data watchpoint may report an access after the instruction performed it. Inspect the triggering access and the state at the pause before deciding which side of that instruction the debugger shows.

A breakpoint tells you that one condition happened, not why. A write breakpoint on health may fire for damage, healing, loading, respawning, and initialization, all through the same instruction. The action you performed just before the pause, and the call stack at the pause, tell those uses apart.

A process can contain many threads. When one thread hits a breakpoint, another may have updated nearby state just before the debugger stopped everything. The snapshot is real, but it is not automatically the whole causal story.

Four kinds of context explain a pause:

  • time: what player action immediately preceded the pause;
  • thread: which thread executed the instruction;
  • data: which registers and memory locations the instruction used; usually only two registers matter, the one that forms the memory address and the one the value moves into or out of;
  • control: which call path and branches led here.

Repeating the same action shows whether the breakpoint is useful. A useful breakpoint fires at a consistent point for the behavior you are studying. A noisy breakpoint may still be correct, but it needs a stronger filter such as a specific thread, object pointer, call site, or condition.

This is the difference between a trace and an explanation. A trace records what executed. An explanation shows why the input and current state selected that path, and survives repeated tests.

A hardware breakpoint uses CPU debug registers to watch a small address range. It is ideal when you know the gold address but not the instruction that changes it.

DR00x0550A034
DR1unused
DR2unused
DR3unused
DR7which of DR0 to DR3 are on, what each watches, and how many bytes
One thread's debug registers with a write watchpoint on four bytes of gold. Four address slots are all there are.

The debug registers can watch for a write, for a read or write, or for an execute — x86 has no read-only setting. A debugger that offers a pure read breakpoint implements it another way, usually with page protection.

Choose the narrowest event that answers your question:

  • write when looking for code that changes a value;
  • read when looking for code that uses a value;
  • access only when you truly need both.

Access breakpoints can fire constantly. More pauses do not automatically mean more understanding.

Hardware watchpoints are limited resources—x86 provides only a small number of debug-address registers per thread—and supported widths/alignment are restricted. A debugger may emulate larger memory watches with guarded pages, which is noisier because unrelated data on the same page can trigger.

Be precise about the event:

  • an instruction reads when it needs the old bytes;
  • it writes when it stores new bytes;
  • a read-modify-write instruction such as sub [address], eax does both;
  • execute means the watched bytes are being treated as code.

Watching four bytes does not tell the debugger that they are a u32. Width is about overlapping addresses. Type meaning still comes from the instruction and behavior.

Once a memory breakpoint identifies the instruction that writes gold, a code breakpoint can stop at that instruction on later runs. That makes it easier to compare its callers and branch conditions without watching every write to the field.

On x86, a debugger often implements a software breakpoint by temporarily replacing the first byte of an instruction with 0xCC, the int3 instruction.

Use a concrete three-byte subtraction as the byte-level example below: sub dword ptr [edx+4], eax subtracts the value in eax from a four-byte field at address edx+4. The debugger changes only its first instruction byte to set the breakpoint.

0x007CCD9129
42
04
sub dword ptr [edx+4], eax
The three-byte subtraction before a software breakpoint is installed.
0x007CCD91CC
42
04
int3
The same bytes with a breakpoint set. The CPU now executes int3 and stops; the debugger keeps the original 29 to put back.

When the CPU executes int3, Windows reports a breakpoint exception. The debugger:

  1. pauses the process;
  2. restores the original byte;
  3. lets you inspect or step;
  4. replaces the breakpoint when execution continues.

That bookkeeping is why you should remove breakpoints through the debugger instead of editing the byte by hand.

Three views of the same subtraction bytes: original 29 42 04, breakpoint CC 42 04, and restored 29 42 04. The debugger retains the original 29 throughout.
The subtraction starts as 29 42 04. The debugger keeps 29 while CC replaces the first byte, then restores 29 before stepping. Optional slow replay shows set, trap, and restore; Pause returns to this overview.

It also means a software breakpoint is not invisible. While it is set, the program’s own code genuinely contains 0xCC where the original byte used to be, and nothing prevents the program from reading its own bytes and noticing. A routine that adds up the bytes of a function and compares the total against a figure recorded when the game was built will disagree the moment you place a breakpoint inside that function.

Notice what such a routine has actually established: that its code changed. It has not detected a debugger, and it cannot tell your breakpoint apart from any other modification to those bytes. Chapter 13 returns to this, because it is the same self-checking idea used to notice a patch.

How a software breakpoint pauses and resumes

Section titled “How a software breakpoint pauses and resumes”

The original instruction still has to execute. A debugger normally handles the 0xCC pause in two events:

the CPU executes int3
breakpoint event
restore the original byte
move the instruction pointer back
to the restored instruction
turn on one-instruction stepping
the real instruction runs
single-step event
write 0xCC back
turn stepping off, then
continue or pause for the user

The instruction pointer needs rewinding because the CPU has already consumed the one-byte int3. The one-instruction step lets the real instruction run before the breakpoint byte is restored. Lesson 6.10 models this as explicit Armed and Stepping states so the byte is never restored at the wrong time.

On x86, single stepping uses the trap flag in the thread’s flags register. When set, the CPU raises a debug exception after the next instruction. The flag belongs to one thread, which is why debugger state records thread IDs.

Follow the subtraction example through both events. The saved 29 byte must be executable for one instruction before CC can safely be installed again:

Restore, execute once, then re-arm. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

save original byte 29; write CC
CPU stops at the breakpoint
restore 29 and rewind the instruction pointer
run one original instruction with the trap flag
after its single-step stop, write CC again
report whether original work ran once

Result: original instruction executed 1 time(s). Byte restoration and re-arm order are separate operations.

Final values: order = after; site = 0x007CCD91; byte = CC; executed = 1; ip = 0x007CCD91.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with the selected inputs and follow how the next operation changes the state.

    order = after.

  2. Step 2, line 2. The breakpoint interrupts before the original subtraction completes.

    order = after; site = 0x007CCD91; byte = CC; executed = 0.

  3. Step 3, line 3. Restore byte 29 and return the instruction pointer to its start.

    order = after; site = 0x007CCD91; byte = 29; executed = 0; ip = 0x007CCD91.

  4. Step 4, line 4. The trap flag permits one original instruction, then stops the CPU.

    order = after; site = 0x007CCD91; byte = 29; executed = 1; ip = 0x007CCD91.

  5. Step 5, line 5. Only now re-arm the breakpoint.

    order = after; site = 0x007CCD91; byte = CC; executed = 1; ip = 0x007CCD91.

  6. Step 6, line 6. Result: original instruction executed 1 time(s). Byte restoration and re-arm order are separate operations.

    order = after; site = 0x007CCD91; byte = CC; executed = 1; ip = 0x007CCD91.

Watch it happenA breakpoint needs one instruction to get past itself

Three code bytes, 29 42 04, at 0x007CCD91. The debugger swaps the first byte for CC and keeps the original. When the CPU hits it, the debugger restores the 29, rewinds the instruction pointer, runs exactly one real instruction with the trap flag set, and only after the single-step event writes CC back.

Read the steps
  1. The breakpoint is armed: the first byte is CC (int3) instead of the original 29. The debugger keeps the 29 so it can put the subtraction back.
  2. The CPU reaches 0x007CCD91 and executes the one-byte int3. Windows reports a breakpoint event. The instruction pointer has already moved past that byte, to 0x007CCD92.
  3. Restore and rewind: write the saved 29 back and move eip to 0x007CCD91, so the whole subtraction is available again.
  4. Run one real instruction: turn on the trap flag for this thread and continue. The restored subtraction executes, and only then does the CPU report a one-instruction step.
  5. The single-step event: the original instruction has finished, and eip is at the next one, 0x007CCD94. This second event is the moment the debugger can put the breakpoint back without trapping on it immediately.
  6. Re-arm: write CC back at 0x007CCD91 and turn stepping off. The debugger can now continue, or keep the process paused.

The breakpoint event restores the instruction; the single-step event restores the breakpoint. Those are different moments.

When paused at a call, you have choices:

Action What it does
Step into Enter the called function
Step over Run the called function and pause after it
Run to return Continue until the current function returns
Resume Continue until another event pauses the process
step into
step over
run to return
paused at
call calculate_damage
first instruction
inside calculate_damage
the instruction
after the call

Step into code you need to understand. Step over code that is not part of your question.

“Step over” is not one special CPU instruction. At a call, the debugger can place a temporary breakpoint at the instruction after the call and resume. If the call never returns normally, that temporary breakpoint may never fire.

“Run to return” relies on the current call frame and can be confused by tail calls, exceptions, corrupted stacks, or optimized code. These controls are convenient ways to express a debugging goal, not guarantees about source-level functions.

nop means “no operation.” Replacing an instruction with one or more nop bytes is a quick, temporary way to test what that instruction does. This is a separate action from setting a breakpoint: it changes what the game executes after you resume.

sub [ecx+30h], eax

If this removes gold and you temporarily replace the whole instruction with nop bytes, recruiting may stop costing gold. That result supports your theory.

NOPing an instruction removes all of its effects, including effects that are easy to overlook. Arithmetic instructions often update flags used by the next conditional branch. A floating-point store may also pop a value from an x87 stack. A test that appears visually correct can still damage later control flow or machine state.

29
41
30
sub [ecx+30h], eax: three bytes
The original instruction.
90
90
90
nop
nop
nop
All three bytes replaced. The subtraction is gone, and so are the flags it would have set.
90
90
30
two nops
leftover
Only two bytes replaced. The CPU decodes the leftover 30 as the start of an xor instruction that swallows the bytes after it.

Before patching, ask:

  1. How many complete bytes does the instruction occupy?
  2. Which registers, memory, flags, or special stacks does it change?
  3. Does later code rely on those changes?
  4. What should visibly change in the game if the patch works?
  5. How will the original bytes be restored?

Later, our debugger will receive the same breakpoint events from Windows. We will model them with an enum:

enum DebugEvent {
Breakpoint { thread_id: u32, address: usize },
ProcessExited { code: u32 },
Other,
}

An enum gives each event a clear shape. Pattern matching then forces us to decide how every event should be handled instead of silently ignoring surprises.

The next lesson uses x64dbg’s built-in breakpoint controls rather than a custom debugger. Start with the gold address, watch a write, and use the paused registers and instruction to check whether affordability guards the gold write. The enum becomes useful later when we write our own event-handling code.

Knowledge check

Pause and inspect

What is the main purpose of a breakpoint in a reversing experiment?

Finished Lesson 2.4?

Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.

Chapter 2: 10 lessons

This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.