4.3 Conditional Breakpoints, Call Stacks, and Evaluation
Prerequisites: Breakpoints, Variables, and Single-Step Execution
Estimated time: 15 minutes
Exercise environment: Python debugger; the end-of-chapter GDB experiment requires a C compiler and GDB
You are ready to continue when: you can filter pauses, switch stack frames, and control evaluation side effects
Regular breakpoints have already identified the zero-weight path. As data volumes increase, you'll need to reduce unnecessary pauses and trace back along the call stack to the input source from the current function.
Conditional Breakpoints Are Not Free Observations
To check only the record numbered 99999, set the breakpoint condition to:
item["id"] == 99999The debugger asks for the value each time it passes through that location. High-frequency loops can significantly slow down, and conditions can throw exceptions or alter the state. Conditions should ideally be cheap, read-only expressions, avoiding functions that write to files, make requests, or modify objects. Both hit count and log breakpoints carry overhead.
Call Stack and Stack Frames
def get_discount_ratio(item_id):
return 0 if item_id == 42 else 0.1
def get_alloy_discount(item_id, weight):
ratio = get_discount_ratio(item_id)
return weight * ratio
def calculate_cost(item):
return item["weight"] - get_alloy_discount(item["id"], item["weight"])
print(calculate_cost({"id": 42, "weight": 100}))Save this as discount_trace.py; a normal run should print 100. Set a breakpoint on the return line in get_discount_ratio and start debugging.
When paused at the innermost level, the call stack generally includes the current get_discount_ratio, along with its caller get_alloy_discount, calculate_cost, and module entry. Different interfaces may place the current frame at the top or bottom. Selecting the caller's frame will only switch the local variable view; execution remains paused at the same point; selecting a frame does not replay execution history. The call stack for asynchronous, multithreaded, and optimized code may be incomplete or have a different structure.
ratio == 0 is merely an observation. The number 42 must be judged in conjunction with the requirements to determine whether it is a correct exception, data error, or unimplemented logic.
Watch and Debug Console
Watch is suitable for pure expressions, such as item["weight"] >= 0. Function calls may re-break at breakpoints, perform I/O, or modify state; recursive calls are not guaranteed to error, but may instead initiate more computations. The debug console can quickly validate hypotheses, but conclusions must be reproducible in a pristine new process.
GDB Quick Guide
First, confirm that the two tools exist, then create a minimal program in a temporary directory:
command -v cc
command -v gdb
gdb_workdir=$(mktemp -d)
cd "$gdb_workdir"
cat > program.c <<'C'
#include <stdio.h>
int main(void) {
int value = 42;
printf("value=%d\n", value);
return 0;
}
C
cc -Og -g program.c -o program
gdb ./programAfter entering GDB:
break main
run
next
print value
backtrace
continue
quitAfter exiting GDB, clean up:
cd
ls -ld -- "$gdb_workdir"
rm -r -- "$gdb_workdir"Both GDB and LLDB are often used for native programs. -g writes debug information, -Og retains appropriate optimization levels for debugging, but source code steps do not equate to individual machine instructions. Server environments can also use core dumps, remote debugging, logging, and profiling tools, and GDB is not the only option. Command details can be found in GDB's Continuing and Stepping and Backtraces.