Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CPDB — CPyte AST-first native debugger

A debugging middle layer for the CPyte compiler (WEW). It turns an ordinary AOT-compiled CPyte binary into an AST-native debuggee so you can step over statements and expressions of the source program — not raw machine instructions — on macOS (lldb), Linux (ptrace), and Windows (Debug API).

The pipeline keeps the debug info inside the binary:

.cpy ──▶ debug_build() ──▶ .dbg (binary with cpdbd_ tracer stubs, boxes)
        build_ast_model() ──▶ AST model (mirrors runtime node_nids)
        build_sidecar()   ──▶ .cpdb.json metadata (functions, steps, boxes, source)
                                     │
        binary + model + sidecar ──▶ CPDBSession ──▶ backend ∈ {lldb, ptrace, win}

Quick start

# CLI (build, launch under lldb, drive with commands):
PYTHONPATH=$PWD python -m cpdb.cli examples/fib.cpy

# Same session, non-interactive:
python -m cpdb.cli examples/fib.cpy --commands "n;n;s;n;v;c" --quit-exit

# GUI (tkinter, Thonny-style):
python -m cpdb.gui examples/fib.cpy

CLI

python -m cpdb.cli [options] file.cpy

Flag Meaning
-o/--out PATH output binary/asset directory (default .cache)
-O/--opt N opt level passed to debug_build
--no-gc, --no-userspace build flags
--no-build skip build; use --binary + existing sidecar
--binary PATH run a pre-built binary
-x/--commands "a;b;c" run a semicolon-separated command script, then exit
--quit-exit exit 0 as soon as the target exits
--echo-stops print every stop in the command script
--input LINE queue a stdin line for the program (repeatable)

REPL commands: n/<enter> step over · s step into · u step out · c/r continue · b [file:]LINE breakpoint · d ID delete breakpoint · on ID/off ID enable/disable breakpoint · heap/hp object-model view · v variables · p NAME print value · bt/w backtrace/stack · tree recursion tree · in TEXT feed a stdin line · l list · ? help · q quit.

--input LINE may be repeated to pre-queue stdin lines before the program starts.

Process view (call stack · recursion · variables)

While paused, the session exposes the whole process, not just the current statement:

  • Call stacksession.call_stack() runs thread backtrace and folds the pending tracer nid into the next user frame, dropping dyld/runtime frames. parse_backtrace turns the raw lines into CallFrame(depth, function, nid).
  • Recursion treesession.recursion_tree() returns the current call path as nodes keyed (function, depth) with rec_depth (consecutive frames of this function) and cumulative visits. Useful to see how deep a recursive call is and how many times each frame was entered.
  • Variable changesread_locals() diffs against the last read per function and tags each DebugVariable new / changed / same; changed values carry their previous value so you can watch state mutate between steps.
  • Insight — the detailed trail that plain variable reads cannot show: session.variable_history(fn) lists every value a name ever held (reusing a name accumulates instead of overwriting), decision_trail() logs each valued step (if/while/for truth, assignments) with its node id, and call_ledger()/function_ledger() track enter/return transitions (including recursion) per function, with the final frame unwind recorded at exit. The GUI Insight panel does this each stop; the CLI has insight / ih.

Program input (stdin)

Programs that call input() still work. Type a line in the GUI Input box (or pass --input LINE / use in LINE in the CLI) — the line is queued and flushed into the inferior's stdin (a file the session owns) right before the next run/continue. input() past the end of the flushed data returns its EOF default (0 / empty) — it never blocks, so nothing ever stalls.

GUI

python -m cpdb.gui file.cpy opens a tkinter app with the source view (current statement highlighted, breakpoint markers), a Call Stack and Recursion Tree panel, a variables tree that highlights new/changed values (green new, yellow changed, italic arguments), a Watch Expressions panel (re-evaluated every stop against the current frame's locals, so names like n + 1 or len(s) > 3 work), an Insight panel with the per-name reuse history, decision trail and call/return ledger, a breakpoints editor, a program-input box, and a program-output console.

Source-pane gestures: click a line to toggle a breakpoint there, or double-click to run-to-cursor (resume to that line, then stops). Clicking a Call Stack row shows that frame's locals in the Variables panel — for recursive frames the per-function debug box forces a "live innermost copy" note (see AGENTS.md). The GUI drives the exact same CPDBSession as the CLI, so stepping behavior is identical.

Backends

Platform Backend Mechanism Status
macOS lldb pipe to lldb MI; temp AST breakpoints by symbol working (end-to-end, tested)
Linux ptrace ctypes PTRACE_TRACEME/GETREGSET; 0xCC / BRK#0 traps; ELF symbol parsing for PIE bias; /proc/<pid>/mem reads implemented; parser unit-tested (needs Linux host to run)
Windows win CreateProcessW(...DEBUG_ONLY_THIS_PROCESS), WaitForDebugEvent/ContinueDebugEvent, OutputDebugString capture lifecycle implemented; breakpoint machinery stubbed (NotImplementedError)

cpdb.backends.base.factory() selects by sys.platform.

Value rendering

cpdb.values.decode_value turns a live box slot (an alloca address recorded in the sidecar as type/size) into a readable DebugValue:

  • integer-like types (i8/i32/int/long/i64/double) → signed number
  • string types → bounded, NUL-terminated C string read via the backend
  • dynamic variables → best-effort string/pointer render
  • anything else → hex pointer

Testing

PYTHONPATH=$PWD python -m pytest tests -q

Pure unit tests run anywhere (values, ELF parser, GUI helpers). End-to-end tests build a real binary and drive lldb; they auto-skip where lldb is not available.

Repository layout

cpdb/
  session.py        CPDBSession: step engine, breakpoints, locals, plan/schedule
  traverse.py       TraversalEngine: AST walking, current statement, rdzv table
  astmodel.py       Python mirror of the compiled AST (nid -> node/func)
  compiler.py       debug_build(), Sidecar serialization (functions/steps/boxes/source)
  values.py         decode_value()
  cli.py / gui/     product surface
  backends/         lldb.py / ptrace.py / win.py / base.py (DebugBackend ABC)
tests/              unit + end-to-end suites
examples/           fib.cpy, loops.cpy, hello.cpy

Note: the compiler-side debug metadata emitter lives in the WEW source tree (cpyte/bytecoding.py, tracer contract cpdbd_*); this repo consumes it.

About

Cpyte official ast-based debugger

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages