6.3 KiB
LLDB Helpers for Agents
Use these Python scripts inside LLDB when investigating DuckDB code or a failing sqllogictest. They provide SQL-level navigation and make C++ values easier to inspect. Read the relevant helper's README and implementation before modifying it.
Start a Session
Run LLDB from the repository root so test paths and test data resolve correctly.
Start with the reldebug build recommended in the root AGENTS.md:
lldb -- build/reldebug/test/unittest test/sql/order/test_limit.test
Import the helpers you need at the LLDB prompt:
command script import scripts/lldb/sqllogictest_breakpoints/sql_break.py
command script import scripts/lldb/pointer_print/pointer_print.py
command script import scripts/lldb/print_array/print_array.py
command script import scripts/lldb/filter_boundschecks/filter_checks.py
These are LLDB plugins, not standalone Python programs. Imports apply to the current
debugger session; no ~/.lldbinit changes are needed. Use absolute script paths if
LLDB was started from another directory.
The SQL helpers need the unittest executable, a stopped process, and sqllogictest
frames on thread 1. They use query_break in test/sqlite/sqllogic_command.cpp.
If that hook has no breakpoint locations or required locals are optimized out,
use make debug and build/debug/test/unittest for the investigation.
The printing and stepping helpers also work with the DuckDB shell executable.
Find the SQL Behind a C++ Stop
After importing sql_break.py, stop at the first SQL hook:
breakpoint set --name query_break --one-shot true
run
sql_current_statement
sql_current_statement prints the test file, line, statement kind, connection, SQL,
and active loop values. It also works from a deeper C++ breakpoint when the
sqllogictest caller is still on thread 1's stack.
Use sql_current_statement --json to capture these fields as a JSON object rather
than parsing the display text.
Use these commands to move through the test:
sql_next_statement
sql_next_matching_statement --kind statement_error
sql_next_matching_statement --file test/sql/join --kind query --loop i=3
sql_next_matching_statement --connection con2
sql_next_matching_statement --sql "JOIN" --kind query
Choose filters that match the test being debugged. Supported filters are:
--file <substring>and--line <n>.--line-min <n>and--line-max <n>for an inclusive range; do not combine these with--line.--kind query|statement|statement_ok|statement_error.--connection <name>for an exact connection-name match.--sql <substring>for a case-sensitive match against the expanded SQL.--loop <name>or--loop <name>=<value>; repeat for multiple loop constraints.
Filters combine with AND. Use the location reported by sql_current_statement
when choosing a line filter.
For repeated stops, install a persistent rule and advance to it:
sql_watch_statement --kind query --loop i=3
sql_list_watches
sql_next_watch
sql_delete_watch
sql_next_watch <id> selects one rule; omitting the ID considers all installed
rules. sql_delete_watch <id> removes one, omitting the ID removes the most
recently added rule, and sql_delete_watch all removes all rules.
SQL watches are breakpoint rules, not memory watchpoints. Ordinary continue
does not stop on them. sql_next_statement, sql_next_matching_statement, and
sql_next_watch temporarily auto-continue other breakpoints and restore their
prior settings afterward. Once positioned at the desired SQL, use ordinary
continue or stepping to investigate it with C++ breakpoints.
These helpers assume thread 1 is executing the test; selecting a worker thread does not change that assumption. See sqllogictest_breakpoints/README.md.
Inspect Pointers and Arrays
Importing pointer_print.py enables the duckdb type category and aliases p to
duckdb-p. Use it to inspect a DuckDB smart pointer directly:
duckdb-p my_unique_ptr
duckdb-p some_state.shared_buffer
duckdb-p /x my_optional_ptr
The formatter displays the pointee type and address, exposes its children, and
renders null pointers as nullptr. It reads wrapper storage without relying on
emitted operator*() or .get() symbols. It supports DuckDB's unique, shared,
optional, buffer, arena, and listed unsafe pointer types.
Use expression -- <expr> when you need LLDB's expression command directly.
See pointer_print/README.md for the supported types.
Use print_array for a raw pointer to multiple elements in the selected frame:
print_array result_sel.sel_vector 8
print_array row_ids count
print_array "data_ptr + offset" "count - offset"
The pointer must be non-null and the size must evaluate to a positive integer. Quote arguments containing spaces. Choose a small size within the known valid element count: the helper cannot check the allocation's bounds. See print_array/README.md.
Step Past Wrapper Checks
Importing filter_checks.py immediately adds step-avoid patterns for small
optional_ptr, unique_ptr, shared_ptr, and vector helper frames.
duckdb-step-avoid-show
duckdb-step-avoid-disable
duckdb-step-avoid-enable
Disable the additions when investigating the wrappers or their checks themselves. Disabling restores the regexp saved before enabling the additions. See filter_boundschecks/README.md.
Changing a Helper
Keep command syntax and import side effects documented in the corresponding README.
Preserve callback registration derived from MODULE_NAME so scripts can be renamed.
Validate behavior inside LLDB: a Python syntax check alone does not exercise LLDB's
API, expression evaluation, formatters, or breakpoint callbacks. For SQL navigation
changes, use a small sqllogictest and verify matching, watch deletion, and restoration
of other breakpoints' auto-continue settings.
Run the integration suite from the repository root:
python3 scripts/lldb/tests/run_tests.py --unittest build/debug/test/unittest \
--coverage-dir /tmp/duckdb-lldb-coverage
The suite needs LLDB with Python support, a C++ compiler, and permission to launch
local processes under the debugger. Use a build with a resolvable query_break
symbol and inspectable locals. See tests/README.md for coverage
details and runner options.