Debugging Storm

This page details some debugging strategies that are useful when debuggin Storm and C++ code, especially when mixed. The page also details some additional quirks due to how the garbage collector behaves, and how to deal with them.

Signals and Exceptions

The garbage collector occasionally write protects data that is usually writable, so that it may be notified when data is accessed, and thereby avoid scanning parts of the heap. This is sometimes confusing during debugging, as a seemingly innocent write to memory may suddenly jump into the garbage collector. On Windows, this is typically not a problem, due to the existence of the system-wide exception handling mechanisms. On Linux, however, an access violation sends the SIGSEGV signal to the process, which typically traps the debugger even if the signal is later handled.

As such, on Linux it is useful to tell the debugger to ignore the SIGSEGV signal. This can be done by issuing the following command, either interactively or in a .gdbinit file.

handle SIGSEGV nostop noprint

If it is interesting to debug genuine segmentation faults, one can instead put a breakpoint on the function handleSegv in Compiler/SystemException.cpp. This function is only called in the case of a "true" segmentation fault. Similarly, on Windows, the function throwAccessError in the same file serves the same purpose.

On Linux, a further two signals are used to coordinate threads, namely SIG34 and SIG35. These signals should therefore be ignored in the same manner when using GDB:

handle SIG34 nostop noprint
handle SIG35 nostop noprint

Due to the use of signals, it is of extra importance to pay attention to the return values from system calls that may fail with errno set to EINTR. These situations tend to happen more often in Storm than in other programs due to the garbage collector, even though the signals are handled with the resume system calls flag set.

Breakpoints

The function storm::dbgBreak() is useful for breaking the program at an arbitrary point. It is exposed to Storm as core.debug.dbgBreak(), which makes it useful in Storm code as well.

On Linux, the function raises the signal SIGINT, which is easily captured inside GDB.

On Windows, it calls the function DebugBreak(), which breaks into the debugger. Historically, this would bring up the dialog "This program has stopped working..." which would give you the option to open a debugger (sometimes called Just In Time or JIT debugging). Recently, it seems like this is disabled by default. To use this method you therefore either need to run Storm under a debugger (e.g., launch it from Visual Studio), or modify the registry to enable JIT debugging. In the repository, the file scripts/ShowCrashUI.reg contains the keys that need to be modified.

Generated code

If you are developing languages or language extensions, it can sometimes be convenient to see the final machine code that is generated instead of the intermediate representation. This can be done by the following C++ code:

// See the transformed machine code (l is a Listing):
PLN(engine().arena()->transform(l, null));

// See the binary representation as well:
PLN(new (engine()) Binary(engine().arena(), l, true));