Proposal: pybind11-debug — a gdb wrapper that diagnoses "Caught an unknown exception!" and native crashes direct from python console
#6101
GabrielJMS
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
The problem
When a C++ exception escapes a bound function and matches none of the registered
translators, pybind11's final
catch (...)fallback (indetail/internals.h) raises:At that point every diagnostic is gone, the type, the message, and the C++ stack
have all been destroyed by unwinding. The most common real-world cause is an
exception type that doesn't derive from
std::exception, which is routine whenbinding large third-party C++ SDKs that ship their own exception hierarchies.
Users googling that message today find no actionable guidance in the docs.
The sibling failure mode, a segfault inside the extension, is even worse: the
interpreter dies with no traceback at all.
Both are diagnosable at the origin with gdb, but the incantations (breaking on
__cxa_throw, filtering out the thousands of benign import-time throws a CPythonprocess generates, reading the
std::type_infoout of the Itanium-ABI registers)are beyond what we can reasonably ask a typical extension user to reinvent.
Proposal
A single pure-stdlib module,
pybind11/debug.py, exposed aspybind11-debug(andpython -m pybind11.debug), following the existingpybind11-configCLI precedent:$ pybind11-debug my_script.py args...It runs the script under
gdb -batchwith an injected gdb-Python script that:__cxa_throw, before unwinding, and reports only throwswhose stack passes through an extension module (auto-detected via
importlib.machinery.EXTENSION_SUFFIXES;--targetnarrows the filter). Itprints the demangled exception type, a heuristically recovered message, and a
backtrace annotated with which frames belong to the extension — with
file:linewhen the extension was built with
-g, function names otherwise. Throws innamespace
pybind11(error_already_setetc.) are recognized and suppressed bydefault, since pybind11 translates those correctly itself.
backtrace at the faulting frame, then forwards the signal so
faulthandlercanprint the Python-side stack too (which script line triggered the crash).
1(something reported) /0(clean) /2(usage or platformerror), so it can be scripted in CI.
Before / after
Before — everything the user sees today:
After — transcript from a production extension binding a large C++ SDK (names
anonymized):
That took the bug from "unknown exception, somewhere" to an exact call site, and a
one-clause fix (catching the SDK's exception base class, which does not derive from
std::exception.Scope and cost
are a two-entry table, designed for extension). Clean
exit 2with guidanceelsewhere: MSVC users get pointed at the VS debugger's "break on C++ exceptions";
an lldb backend for macOS is plausible future work.
actionable error message. Zero import cost for the package (nothing imports the
module implicitly).
test_exceptionsalready hasthrows3(), whichthrows
MyException3, the canonical non-std::exceptiontype the test suiteasserts produces "Caught an unknown exception!". Tests self-skip without
gdb/Linux, with a canary fixture for containerized runners where ptrace is
restricted. CI cost: adding
gdbto oneapt-getline in one Linux job.docs/advanced/debugging.mdpage, cross-linked from the FAQcrash entry and the deadlock page's debugging tips.
Open questions
pybind11-debug/python -m pybind11.debug, or somethingnarrower like
pybind11-diagnose? Everything is behind onemain(), so arename is mechanical.
segfaults a child process via
ctypes.string_at(0). Comfortable with that inCI, or should the initial PR keep only the throw-path test?
wheel, would you take the docs page plus a link to it as a standalone package?
(I'd argue shipping is better: the tool encodes assumptions about pybind11's
namespace mangling that should stay versioned with the library itself.)
I have a working implementation and can open a PR (tool + tests + docs, ~600 lines total) if
there's interest.
All reactions