tl;dr:您可以使用signal、setjmp、longjmp 编写 C 代码。
您有多种选择来处理SIGSEGV:
- 使用
subprocess库生成子进程
- 使用
multiprocessing库分叉
- 编写自定义信号处理程序
子进程和分叉已经描述过了,所以我将重点关注信号处理程序的观点。
编写信号处理程序
从内核的角度来看,SIGSEGV 与SIGUSR1、SIGQUIT、SIGINT 等任何其他信号之间没有区别。
事实上,一些库(如 JVM)使用它们作为通信方式。
很遗憾,您不能从 python 代码中覆盖信号处理程序。见doc:
捕获由 C 代码中的无效操作引起的同步错误(如 SIGFPE 或 SIGSEGV)几乎没有意义。 Python 将从信号处理程序返回到 C 代码,这很可能再次引发相同的信号,导致 Python 明显挂起。从 Python 3.3 开始,您可以使用 faulthandler 模块报告同步错误。
这意味着,错误管理应该在 C 代码中完成。
您可以编写自定义信号处理程序并使用setjmp 和longjmp 来保存和恢复堆栈上下文。
例如,这是一个简单的 CPython C 扩展:
#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);
}
和调用者应用程序:
import crash
print("Install custom sighandler")
crash.install_sig_handler()
print("bad_func: before")
retval = crash.trigger_segfault()
print("bad_func: after (retval:", retval, ")")
这将产生以下输出:
Install custom sighandler
bad_func: before
bad_func: after (retval: False )
优点和缺点
优点:
- 从操作系统的角度来看,应用程序只是将
SIGSEGV 捕获为常规信号。错误处理会很快。
- 它不需要分叉(如果您的应用程序包含各种文件描述符、套接字等,则并非总是可能)
- 它不需要生成子进程(并非总是可行,而且速度慢得多)。
缺点:
- 可能会导致内存泄漏。
- 可能会隐藏未定义/危险的行为
请记住,分段错误是一个非常严重的错误!
始终尝试先修复它而不是隐藏它。
很少的链接和参考: