5.3.15 · D5Build Systems & Toolchain

Question bank — GDB debugging — breakpoints, watchpoints, step, next, backtrace

1,650 words8 min readBack to topic

Before you start, two words that recur below:

  • trap — a special CPU event that pauses your program and hands control to the debugger. A breakpoint is one kind of trap.
  • frame — the little box of local variables and bookkeeping that belongs to one active function call (see Stack frames & calling conventions).

True or false — justify

TF — "A breakpoint changes what your program computes while GDB watches it."
False. A software breakpoint only swaps one byte for 0xCC and restores it before the instruction runs, so the program computes the same result; only the timing changes.
TF — "A watchpoint stops at a specific line of source code."
False. That is a breakpoint (a location). A watchpoint stops when an expression's value changes — it may fire on any line that happens to write that variable.
TF — "step and next differ only when the current line contains a function call."
True. On a line with no call, both execute exactly one source line and land in the same place; the "dive vs stay" distinction only exists when there is a call to dive into.
TF — "Hardware watchpoints are always faster than software ones, so GDB should always use them."
True on speed, but GDB can't always use them: there are only ~4 debug registers (CPU debug registers DR0–DR7) each covering ≤8 bytes, so when they run out it must fall back to slow software watchpoints.
TF — "Compiling with -O2 never affects what GDB can show you."
False. Optimization (Optimization levels -O0 -O2 -O3) can delete, merge, or keep variables only in registers, producing <optimized out> — debug with -O0 instead.
TF — "backtrace shows the functions your program will call next."
False. It shows the chain of calls currently active — how you got here, not where you're going. It reads the stack that already exists.
TF — "A hardware watchpoint stops the program by re-reading the variable after every single line."
False. That is the software watchpoint. Hardware watchpoints let the CPU trap automatically on a write to the watched address, so no per-line re-reading happens.
TF — "If break factorial never fires, GDB must be broken."
False. Far more likely: the function was inlined by the optimizer, or you set the breakpoint but never issued run/continue. Check info breakpoints for a hit count.
TF — "finish and next do the same thing when you're on the last line of a function."
False. next runs one line and may still be inside the function (or hit the return); finish runs until the current function returns and additionally prints its return value.

Spot the error

Error — "I'll watch result before run, so I catch its very first change."
result is a local of factorial; before run it isn't in scope yet, so GDB may refuse or create a slow software watchpoint. Break inside the function first, then watch the now-in-scope local.
Error — "The crash line is list.c:88, so the bug is on line 88."
The crash location is where a bad value was used; the bug is usually where that value was created, often one frame up. Use bt and inspect the caller's frame.
Error — "I compiled gcc bug.c -o bug (no flags) and now GDB won't show result."
Without -g there are no debug symbols (Compilation & -g debug symbols), so GDB sees raw addresses, not variable names or line numbers. Recompile with -g.
Error — "next skipped my function entirely — GDB missed it."
next didn't skip it; it ran the whole call to completion without stopping inside. That's its job. Use step to descend into the call.
Error — "My watchpoint fires but says Old value = <garbage> on the first hit — memory is corrupt."
On the first stop the "old value" is just whatever bytes were there before the variable was initialized; it isn't corruption. Compare subsequent changes, not the initial junk.
Error — "I set a breakpoint, hit it, typed continue, and it never stopped again — the breakpoint vanished."
Breakpoints persist across continue; it didn't stop again because execution simply never reached that line a second time (e.g. the function was called only once).

Why questions

Why does a software breakpoint use a 1-byte instruction (0xCC) rather than a longer one?
A trap must fit inside the space of the smallest instruction it might replace; one byte can always be written over the first byte of any instruction without clobbering following instructions' start points.
Why does GDB back the instruction pointer up by 1 after hitting 0xCC?
The CPU advanced past the trap byte when it fired; to re-run the real (restored) original instruction, GDB must rewind the pointer to that instruction's address.
Why is bt recommended as the first command after a crash?
It instantly reveals the whole call chain, telling you who passed the bad value in — orienting your entire investigation before you inspect any single variable.
Why does the software-watchpoint slowdown grow with the number of source lines ?
Because each of the lines pays the fixed trap-and-re-evaluate cost ; more lines means more traps, so the extra time scales linearly with .
Why can a SIGSEGV land you inside GDB with a live prompt instead of just killing the program?
The OS delivers the segfault as a signal (Signals — SIGSEGV SIGTRAP); GDB intercepts it, freezes the process at the faulting instruction, and gives you a prompt to inspect the state.
Why does GDB prefer hardware watchpoints when it can use them?
They offload the check to the CPU's debug registers, so the program runs at nearly full speed () instead of single-stepping every line.
Why do dedicated memory tools like Valgrind exist if GDB can already watch memory?
GDB watches specific expressions you name; tools like Memory debugging — Valgrind & AddressSanitizer instrument every access to catch leaks and out-of-bounds errors you didn't know to watch.
Why is picking a stop point and observing state described as "the scientific method"?
You form a hypothesis about the bug, set a stop point as an experiment, observe the actual state, and refine — exactly the loop described in The scientific method in debugging.

Edge cases

Edge — "What does next do on a line that has no code (a blank line or comment)?"
GDB maps source lines to instructions; a blank/comment line has none, so next lands on the next line that does have code — you never "waste" a step on empty lines.
Edge — "What happens if you watch a variable that then goes out of scope?"
GDB automatically deletes the watchpoint (and tells you) once the frame it belonged to is gone, because the address it tracked is no longer meaningful.
Edge — "You set a breakpoint inside a recursive function — does it fire once or every call?"
It fires on every entry that reaches that line, once per recursive call, so you'll stop repeatedly; use a hit-count condition or ignore to skip the first N.
Edge — "The debug registers are all full and you request a 5th watchpoint — does GDB refuse?"
No, it silently falls back to a software watchpoint for the extra one, which is why one watch can suddenly be far slower — check info watchpoints for hw vs sw.
Edge — "finish is run in main (the outermost frame) — what happens?"
There's no caller to return to, so GDB refuses with an error like "can't finish the outermost frame"; finish needs a caller frame to run back into.
Edge — "You step at the last line of a function — where do you land?"
You return to the caller's line (one frame up), because executing the function's final line hands control back to whoever called it — step follows control flow across the return.
Edge — "A watched 4-byte variable is written one byte at a time — how many times does the watchpoint fire?"
Potentially once per write that changes the watched bytes; a hardware watchpoint traps on each write to that range, so partial updates can each trigger a stop.

Recall One-line self-test before you move on

Cover these; a confident sentence for each means you own the concept.

  • Location vs value: which is a breakpoint, which is a watchpoint? ::: Breakpoint = location (line/function); watchpoint = value change of an expression.
  • Why bt first on a crash? ::: It names the caller who handed over the bad value — where the real bug usually lives.
  • Why -O0 for debugging? ::: The optimizer won't delete or register-hide the variables you want to inspect.