CPython-aware GDB and LLDB scripts in Tools/gdb/, py-bt, py-list, and inspecting live interpreter frames.
Native debuggers are essential when working on CPython internals. Python-level tools can show frames, variables, exceptions, and source lines, but they cannot fully explain crashes inside the C runtime. When a bug involves reference counts, object layout, frame state, memory corruption, invalid pointers, or C extension behavior, you need a native debugger.
On Linux, CPython developers usually use GDB. On macOS, LLDB is common. Both can inspect C stack frames, breakpoints, registers, heap objects, shared libraries, and raw memory.
84.1 Why Native Debugging Matters
CPython is a C program that executes Python code. A Python exception and a C crash are different events.
A Python exception is managed by the interpreter:
raise ValueError("bad input")The interpreter stores exception state, unwinds Python frames, runs handlers, and reports a traceback.
A C crash happens below Python’s exception machinery:
PyObject *op = NULL;
Py_INCREF(op); /* invalid */This may produce a segmentation fault before Python can raise anything.
Native debuggers help diagnose:
segmentation faults
assertion failures
abort calls
deadlocks
invalid reference counts
use-after-free bugs
stack overflows
C extension crashes
interpreter state corruption84.2 Build Configuration for Debugging
Use a debug build:
./configure --with-pydebug
make -j8A debug build keeps more checks enabled and makes many interpreter errors fail earlier.
For better source-level debugging, avoid aggressive optimization:
./configure --with-pydebug CFLAGS="-O0 -g3"
make -j8This gives the debugger clearer stack frames and more reliable local variables.
A common practical build is:
make clean
./configure --with-pydebug CFLAGS="-O0 -g3"
make -j884.3 Starting CPython Under GDB
Run the local interpreter under GDB:
gdb --args ./python script.pyInside GDB:
runIf the process crashes, get a backtrace:
btA fuller backtrace:
bt fullThis shows C frames and local variables.
You can also pass module execution:
gdb --args ./python -m test test_gcThen:
run84.4 Starting CPython Under LLDB
On macOS:
lldb -- ./python script.pyInside LLDB:
runAfter a crash:
btTo inspect a frame:
frame select 0
frame variableTo print a C expression:
expr some_variableLLDB syntax differs from GDB, but the debugging model is similar.
84.5 C Backtraces vs Python Backtraces
A C backtrace shows native function calls.
Example shape:
#0 PyObject_Free
#1 list_dealloc
#2 Py_DECREF
#3 _PyEval_EvalFrameDefault
#4 PyEval_EvalCode
#5 run_mod
#6 pyrun_file
#7 Py_RunMain
#8 pymain_main
#9 mainA Python traceback shows Python frames:
Traceback (most recent call last):
File "script.py", line 7, in <module>
f()
File "script.py", line 4, in f
g()
File "script.py", line 2, in g
crash()Both are useful. The Python traceback tells you what user code was executing. The C backtrace tells you where the interpreter failed.
84.6 CPython GDB Helpers
CPython includes GDB helper commands for inspecting Python state.
These are usually loaded from:
Tools/gdb/libpython.pyWhen configured correctly, GDB gains commands such as:
py-bt
py-bt-full
py-list
py-print
py-localsThese commands translate internal CPython structures into Python-level information.
Example:
py-btOutput shape:
Traceback (most recent call first):
File "script.py", line 12, in inner
File "script.py", line 16, in outer
File "script.py", line 20, in <module>This is often much more useful than a raw C backtrace alone.
84.7 Loading GDB Helpers Manually
If GDB does not load CPython helpers automatically, load them manually:
source Tools/gdb/libpython.pyThen verify:
help py-btSome systems restrict auto-loading for security. GDB may print a warning telling you to add a safe path.
Example .gdbinit entry:
add-auto-load-safe-path /path/to/cpythonUse the absolute path to your CPython checkout.
84.8 Important GDB Commands
Basic execution:
run
continue
next
step
finishBacktrace and frames:
bt
bt full
frame 0
up
downBreakpoints:
break PyErr_SetString
break _PyEval_EvalFrameDefault
break list_dealloc
break dictresizePrinting values:
print op
print *op
print Py_TYPE(op)
print Py_REFCNT(op)Examining memory:
x/16gx op
x/32xb opWatchpoints:
watch op->ob_refcntWatchpoints are useful when a reference count changes unexpectedly.
84.9 Important LLDB Commands
Basic execution:
run
continue
next
step
finishBacktrace and frames:
bt
frame select 0
up
downBreakpoints:
breakpoint set --name PyErr_SetString
breakpoint set --name _PyEval_EvalFrameDefault
breakpoint set --name list_deallocPrinting values:
expr op
expr *op
expr Py_TYPE(op)
expr Py_REFCNT(op)Memory:
memory read op
memory read --format x --count 16 opLLDB expression parsing can be stricter than GDB. For complex macros, you may need to inspect fields directly.
84.10 Breaking on Python Exceptions
Python exceptions are normal control flow. Many exceptions are raised, caught, and ignored internally.
Breaking on every PyErr_SetString can be noisy:
break PyErr_SetStringThis stops whenever CPython sets an exception with a string message.
More targeted options include:
break PyErr_SetObject
break PyErr_Format
break PyErr_NoMemoryThis is useful when you need to find where a specific exception originates.
A practical pattern:
break PyErr_SetString if exception == PyExc_TypeErrorConditional breakpoints reduce noise.
84.11 Breaking on Fatal Errors
Fatal runtime failures often call:
Py_FatalError
_Py_FatalErrorFunc
abortUseful breakpoints:
break Py_FatalError
break _Py_FatalErrorFunc
break abortThen:
run
bt fullThis stops before the process terminates, preserving context.
84.12 Debugging a Segmentation Fault
Suppose this command crashes:
./python script.pyRun:
gdb --args ./python script.pyInside GDB:
run
bt full
py-bt
frame 0Then inspect important variables:
info locals
print op
print *op
print Py_TYPE(op)
print Py_REFCNT(op)Common patterns:
op == NULL
invalid NULL dereference
Py_REFCNT(op) <= 0
use-after-free or double decref
Py_TYPE(op) invalid
memory corruption
crash in dealloc
object lifetime bug
crash far from changed code
earlier corruption84.13 Debugging Reference Count Bugs
Reference count bugs often appear as crashes during deallocation.
Example failure path:
extra Py_DECREF
→ object freed too early
→ pointer reused later
→ crash in unrelated codeUseful strategies:
break on the object allocator
break on the deallocator
watch object reference count
run focused tests repeatedly
use debug build and refleak testsExample watchpoint:
watch ((PyObject *)op)->ob_refcntThen continue:
continueGDB stops whenever the reference count changes.
This is powerful but slow.
84.14 Debugging Garbage Collector Bugs
GC bugs often involve invalid traversal, clearing, or tracking.
Useful breakpoints:
break collect
break delete_garbage
break PyObject_GC_Track
break PyObject_GC_UnTrackCommon causes:
object tracked before full initialization
tp_traverse misses a contained reference
tp_clear fails to clear a strong reference
object deallocated while still tracked
wrong allocator used for GC objectFor container types, inspect:
tp_traverse
tp_clear
tp_dealloc
tp_alloc
tp_freeA GC crash usually means the object graph metadata has become inconsistent.
84.15 Debugging the Evaluation Loop
The evaluation loop executes bytecode instructions.
Important function:
_PyEval_EvalFrameDefaultA breakpoint here is often too broad because every Python frame passes through it.
More targeted strategies:
break on a specific opcode handler
break after a specific exception
break on a function called by the opcode
use py-bt to identify Python frame
inspect frame object and instruction pointerUseful values include:
current frame
code object
instruction pointer
stack pointer
locals arrayThe exact internal names vary across CPython versions, so read the local variables in the active frame.
84.16 Debugging Import Failures
Import bugs can involve Python and C code.
Useful breakpoints:
break PyImport_ImportModule
break PyImport_ImportModuleLevelObjectPython-level helpers:
import importlib
import sys
print(sys.meta_path)
print(sys.path)
print(sys.modules)Import failures may be caused by:
wrong module state
partially initialized module
recursive import
bad extension module initialization
incorrect sys.path
failed loader protocolUse both Python-level inspection and C-level breakpoints.
84.17 Debugging C Extensions
When debugging a C extension, start CPython under the debugger and load the extension normally:
gdb --args ./python -c "import myext; myext.run()"Set breakpoints in extension functions:
break myext_runIf symbols are missing, rebuild the extension with debug symbols:
CFLAGS="-O0 -g3" python setup.py build_ext --inplaceCommon extension crash causes:
returning borrowed references as new references
missing Py_INCREF before returning
incorrect argument parsing
using Python APIs without the GIL
wrong module state handling
invalid buffer lifetime84.18 Debugging Deadlocks
Deadlocks require a different approach.
When the process hangs, attach to it:
gdb -p <pid>Then:
thread apply all btThis prints all native thread stacks.
Look for threads blocked on:
GIL acquisition
mutex locks
condition variables
I/O
thread joins
fork-related locks
import lockOn LLDB:
process attach --pid <pid>
thread backtrace allDeadlocks often require examining every thread, not just the main thread.
84.19 Debugging Test Failures Under GDB
Run a specific CPython test under GDB:
gdb --args ./python -m test -v test_gcFor a single test method, you can often run the test module directly:
gdb --args ./python Lib/test/test_gc.pyWhen using regrtest, remember that some tests spawn subprocesses. If the crash happens in a child process, configure the debugger to follow forks:
set follow-fork-mode childor disable parallelism and subprocess-heavy options where possible.
84.20 Core Files
A core file is a saved memory image of a crashed process.
Enable core dumps on Unix-like systems:
ulimit -c unlimitedAfter a crash:
gdb ./python coreThen:
bt full
py-btCore files are useful when crashes happen outside an interactive debugger, such as CI or long-running tests.
84.21 Practical Debugging Order
For a crash:
1. Rebuild with --with-pydebug and debug symbols.
2. Reproduce with the smallest command.
3. Run under GDB or LLDB.
4. Capture C backtrace.
5. Capture Python backtrace if possible.
6. Inspect top frame locals.
7. Inspect suspicious PyObject pointers.
8. Check refcount and type pointer.
9. Add targeted breakpoints.
10. Use watchpoints for lifetime bugs.For a reference leak:
1. Run the relevant test with -R.
2. Reduce to one failing test.
3. Inspect success and error paths.
4. Check new, borrowed, and stolen references.
5. Add temporary logging if needed.
6. Use watchpoints only after identifying an object.For a deadlock:
1. Attach to the hung process.
2. Dump all thread backtraces.
3. Identify blocked locks.
4. Check GIL, import lock, and condition variables.
5. Reproduce with fewer threads.84.22 Core Principle
GDB and LLDB let you inspect CPython as the C runtime that it is.
Use Python tracebacks to understand the program. Use native backtraces to understand the interpreter. Use CPython debugger helpers to connect both views. For internals work, that combined view is often the difference between guessing and diagnosing.