Debugging

By default, the clang JIT as used by cppyy does not generate debugging information. This is first of all because it has proven to be not reliable in all cases, but also because in a production setting this information, being internal to the wrapper generation, goes unused. However, that does mean that a debugger that starts from python will not be able to step through JITed code into the C++ function that needs debugging, even when such information is available for that C++ function.

To enable debugging information in JITed code, set the EXTRA_CLING_ARGS envar to -g (and any further compiler options you need, e.g. add -O2 to debug optimized code).

On a crash in C++, the backend will attempt to provide a stack trace. This works quite well on Linux (through gdb) and decently on MacOS (through unwind), but is currently unreliable on MS Windows. To prevent printing of this trace, which can be slow to produce, set the envar CPPYY_CRASH_QUIET to ‘1’.

It is even more useful to obtain a traceback through the Python code that led up to the problem in C++. Many modern debuggers allow mixed-mode C++/Python debugging (for example gdb and MSVC), but cppyy can also turn abortive C++ signals (such as a segmentation violation) into Python exceptions, yielding a normal traceback. This is particularly useful when working with cross-inheritance and other cross-language callbacks.

To enable the signals to exceptions conversion, import the lowlevel module cppyy.ll and use:

import cppyy.ll
cppyy.ll.set_signals_as_exception(True)

Call set_signals_as_exception(False) to disable the conversion again. It is recommended to only have the conversion enabled around the problematic code, as it comes with a performance penalty. If the problem can be localized to a specific function, you can use its __sig2exc__ flag to only have the conversion active in that function. Finally, for convenient scoping, you can also use:

with cppyy.ll.signals_as_exception():
    # crashing code goes here

The translation of signals to exceptions is as follows (all of the exceptions are subclasses of cppyy.ll.FatalError):

C++ signal

Python exception

SIGSEGV

cppyy.ll.SegmentationViolation

SIGBUS

cppyy.ll.BusError

SIGABRT

cppyy.ll.AbortSignal

SIGILL

cppyy.ll.IllegalInstruction

As an example, consider the following cross-inheritance code that crashes with a segmentation violation in C++, because a nullptr is dereferenced:

import cppyy
import cppyy.ll

cppyy.cppdef("""
   class Base {
   public:
      virtual ~Base() {}
      virtual int runit() = 0;
   };

   int callback(Base* b) {
       return b->runit();
   }

   void segfault(int* i) { *i = 42; }
""")

class Derived(cppyy.gbl.Base):
    def runit(self):
        print("Hi, from Python!")
        cppyy.gbl.segfault(cppyy.nullptr)

If now used with signals_as_exception, e.g. like so:

d = Derived()
with cppyy.ll.signals_as_exception():
    cppyy.gbl.callback(d)

it produces the following, very informative, Python-side trace:

Traceback (most recent call last):
  File "crashit.py", line 25, in <module>
    cppyy.gbl.callback(d)
cppyy.ll.SegmentationViolation: int ::callback(Base* b) =>
    SegmentationViolation: void ::segfault(int* i) =>
    SegmentationViolation: segfault in C++; program state was reset

whereas without, there would be no Python-side information at all.