tl;dr: You can write C code using signal
, setjmp
, longjmp
.
You have multiple choices to handle SIGSEGV
:
- spawing sub process using
subprocess
library
- forking using
multiprocessing
library
- writing custom signal handler
Subprocess and fork have already been describe, so I will focus on signal handler point of view.
Writing signal handler
From a kernel perspective, there is no difference between SIGSEGV
and any other signals like SIGUSR1
, SIGQUIT
, SIGINT
, etc.
In fact, some libraries (like JVM) use them as way of communication.
Unfortunately you can't override signal handler from python code. See doc:
It makes little sense to catch synchronous errors like SIGFPE or SIGSEGV that are caused by an invalid operation in C code. Python will return from the signal handler to the C code, which is likely to raise the same signal again, causing Python to apparently hang. From Python 3.3 onwards, you can use the faulthandler module to report on synchronous errors.
This mean, error management should be done in C code.
You can write custom signal handler and use setjmp
and longjmp
to save and restore stack context.
For example, here is a simple CPython C extension:
#include <signal.h>
#include <setjmp.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static jmp_buf jmpctx;
void handle_segv(int signo)
{
longjmp(jmpctx, 1);
}
static PyObject *
install_sig_handler(PyObject *self, PyObject *args)
{
signal(SIGSEGV, handle_segv);
Py_RETURN_TRUE;
}
static PyObject *
trigger_segfault(PyObject *self, PyObject *args)
{
if (!setjmp(jmpctx))
{
// Assign a value to NULL pointer will trigger a seg fault
int *x = NULL;
*x = 42;
Py_RETURN_TRUE; // Will never be called
}
Py_RETURN_FALSE;
}
static PyMethodDef SpamMethods[] = {
{"install_sig_handler", install_sig_handler, METH_VARARGS, "Install SIGSEGV handler"},
{"trigger_segfault", trigger_segfault, METH_VARARGS, "Trigger a segfault"},
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef spammodule = {
PyModuleDef_HEAD_INIT,
"crash",
"Crash and recover",
-1,
SpamMethods,
};
PyMODINIT_FUNC
PyInit_crash(void)
{
return PyModule_Create(&spammodule);
}
And the caller app:
import crash
print("Install custom sighandler")
crash.install_sig_handler()
print("bad_func: before")
retval = crash.trigger_segfault()
print("bad_func: after (retval:", retval, ")")
This will produces following output:
Install custom sighandler
bad_func: before
bad_func: after (retval: False )
Pros and cons
Pros:
- From an OS perspective, the app just catch
SIGSEGV
as a regular signal. Error handling will be fast.
- It does not need forking (not always possible if your app hold various kind of file descriptor, socket, ...)
- It does not need spawning sub processes (not always possible and much slower method).
Cons:
- Might cause memory leak.
- Might hide undefined / dangerous behavior
Keep in mind that segmentation fault is a really serious error !
Always try to first fix it instead of hiding it.
Few links and references: