Skip to content

VOL 00 / CH 04 / LESSON 03

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:

python
item["id"] == 99999

The 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 ​

python
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:

bash
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 ./program

After entering GDB:

text
break main
run
next
print value
backtrace
continue
quit

After exiting GDB, clean up:

bash
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.

→ Next: Comprehensive Exercise

Built with VitePress | Software Systems Atlas