faulthandler-2.0/0000755000175000017500000000000011562074717013060 5ustar haypohaypofaulthandler-2.0/faulthandler.c0000644000175000017500000007166511562074200015677 0ustar haypohaypo/* * faulthandler module * * Written by Victor Stinner. */ #include "Python.h" #include "pythread.h" #include #define VERSION 0x200 /* Allocate at maximum 100 MB of the stack to raise the stack overflow */ #define STACK_OVERFLOW_MAX_SIZE (100*1024*1024) #ifdef SIGALRM # define FAULTHANDLER_LATER #endif #ifndef MS_WINDOWS /* sigaltstack() is not available on Windows */ # define HAVE_SIGALTSTACK /* register() is useless on Windows, because only SIGSEGV, SIGABRT and SIGILL can be handled by the process, and these signals can only be used with enable(), not using register() */ # define FAULTHANDLER_USER #endif #if PY_MAJOR_VERSION >= 3 # define PYINT_CHECK PyLong_Check # define PYINT_ASLONG PyLong_AsLong #else # define PYINT_CHECK PyInt_Check # define PYINT_ASLONG PyInt_AsLong #endif #define PUTS(fd, str) write(fd, str, strlen(str)) #ifdef HAVE_SIGACTION typedef struct sigaction _Py_sighandler_t; #else typedef PyOS_sighandler_t _Py_sighandler_t; #endif typedef struct { int signum; int enabled; const char* name; _Py_sighandler_t previous; int all_threads; } fault_handler_t; static struct { int enabled; PyObject *file; int fd; int all_threads; PyInterpreterState *interp; } fatal_error = {0, NULL, -1, 0}; #ifdef FAULTHANDLER_LATER static struct { PyObject *file; int fd; int timeout; int repeat; PyInterpreterState *interp; int exit; char *header; size_t header_len; } fault_alarm; #endif #ifdef FAULTHANDLER_USER typedef struct { int enabled; int signum; PyObject *file; int fd; int all_threads; _Py_sighandler_t previous; PyInterpreterState *interp; } user_signal_t; static user_signal_t *user_signals; /* the following macros come from Python: Modules/signalmodule.c of Python 3.3 */ #if defined(PYOS_OS2) && !defined(PYCC_GCC) #define NSIG 12 #endif #ifndef NSIG # if defined(_NSIG) # define NSIG _NSIG /* For BSD/SysV */ # elif defined(_SIGMAX) # define NSIG (_SIGMAX + 1) /* For QNX */ # elif defined(SIGMAX) # define NSIG (SIGMAX + 1) /* For djgpp */ # else # define NSIG 64 /* Use a reasonable default value */ # endif #endif #endif /* FAULTHANDLER_USER */ static fault_handler_t faulthandler_handlers[] = { #ifdef SIGBUS {SIGBUS, 0, "Bus error", }, #endif #ifdef SIGILL {SIGILL, 0, "Illegal instruction", }, #endif {SIGFPE, 0, "Floating point exception", }, {SIGABRT, 0, "Aborted", }, /* define SIGSEGV at the end to make it the default choice if searching the handler fails in faulthandler_fatal_error() */ {SIGSEGV, 0, "Segmentation fault", } }; static const unsigned char faulthandler_nsignals = \ sizeof(faulthandler_handlers) / sizeof(faulthandler_handlers[0]); #ifdef HAVE_SIGALTSTACK static stack_t stack; #endif /* Forward */ static void faulthandler_unload(void); /* from traceback.c */ extern void _Py_DumpTraceback(int fd, PyThreadState *tstate); extern const char* _Py_DumpTracebackThreads( int fd, PyInterpreterState *interp, PyThreadState *current_thread); /* Get the file descriptor of a file by calling its fileno() method and then call its flush() method. If file is NULL or Py_None, use sys.stderr as the new file. On success, return the new file and write the file descriptor into *p_fd. On error, return NULL. */ static PyObject* faulthandler_get_fileno(PyObject *file, int *p_fd) { PyObject *result; long fd_long; int fd; if (file == NULL || file == Py_None) { file = PySys_GetObject("stderr"); if (file == NULL) { PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr"); return NULL; } } result = PyObject_CallMethod(file, "fileno", ""); if (result == NULL) return NULL; fd = -1; if (PYINT_CHECK(result)) { fd_long = PYINT_ASLONG(result); if (0 < fd_long && fd_long < INT_MAX) fd = (int)fd_long; } Py_DECREF(result); if (fd == -1) { PyErr_SetString(PyExc_RuntimeError, "file.fileno() is not a valid file descriptor"); return NULL; } result = PyObject_CallMethod(file, "flush", ""); if (result != NULL) Py_DECREF(result); else { /* ignore flush() error */ PyErr_Clear(); } *p_fd = fd; return file; } /* Get the state of the current thread: only call this function if the current thread holds the GIL. Raise an exception on error. */ static PyThreadState* get_thread_state(void) { PyThreadState *tstate = PyThreadState_Get(); if (tstate == NULL) { PyErr_SetString(PyExc_RuntimeError, "unable to get the current thread state"); return NULL; } return tstate; } static PyObject* faulthandler_dump_traceback_py(PyObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"file", "all_threads", NULL}; PyObject *file = NULL; int all_threads = 1; PyThreadState *tstate; const char *errmsg; int fd; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|Oi:dump_traceback", kwlist, &file, &all_threads)) return NULL; file = faulthandler_get_fileno(file, &fd); if (file == NULL) return NULL; tstate = get_thread_state(); if (tstate == NULL) return NULL; if (all_threads) { errmsg = _Py_DumpTracebackThreads(fd, tstate->interp, tstate); if (errmsg != NULL) { PyErr_SetString(PyExc_RuntimeError, errmsg); return NULL; } } else { _Py_DumpTraceback(fd, tstate); } Py_RETURN_NONE; } /* Handler of SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals. Display the current Python traceback, restore the previous handler and call the previous handler. On Windows, don't call explictly the previous handler, because Windows signal handler would not be called (for an unknown reason). The execution of the program continues at faulthandler_fatal_error() exit, but the same instruction will raise the same fault (signal), and so the previous handler will be called. This function is signal safe and should only call signal safe functions. */ static void faulthandler_fatal_error(int signum) { const int fd = fatal_error.fd; unsigned int i; fault_handler_t *handler = NULL; PyThreadState *tstate; int save_errno = errno; if (!fatal_error.enabled) return; for (i=0; i < faulthandler_nsignals; i++) { handler = &faulthandler_handlers[i]; if (handler->signum == signum) break; } if (handler == NULL) { /* faulthandler_nsignals == 0 (unlikely) */ return; } /* restore the previous handler */ #ifdef HAVE_SIGACTION (void)sigaction(handler->signum, &handler->previous, NULL); #else (void)signal(handler->signum, handler->previous); #endif handler->enabled = 0; PUTS(fd, "Fatal Python error: "); PUTS(fd, handler->name); PUTS(fd, "\n\n"); #ifdef WITH_THREAD /* SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL are synchronous signals and so are delivered to the thread that caused the fault. Get the Python thread state of the current thread. PyThreadState_Get() doesn't give the state of the thread that caused the fault if the thread released the GIL, and so this function cannot be used. Read the thread local storage (TLS) instead: call PyGILState_GetThisThreadState(). */ tstate = PyGILState_GetThisThreadState(); #else tstate = PyThreadState_Get(); #endif if (fatal_error.all_threads) _Py_DumpTracebackThreads(fd, fatal_error.interp, tstate); else { if (tstate != NULL) _Py_DumpTraceback(fd, tstate); } errno = save_errno; #ifdef MS_WINDOWS if (signum == SIGSEGV) { /* don't call explictly the previous handler for SIGSEGV in this signal handler, because the Windows signal handler would not be called */ return; } #endif /* call the previous signal handler: it is called immediatly if we use sigaction() thanks to SA_NODEFER flag, otherwise it is deferred */ raise(signum); } /* Install the handler for fatal signals, faulthandler_fatal_error(). */ static PyObject* faulthandler_enable(PyObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"file", "all_threads", NULL}; PyObject *file = NULL; int all_threads = 1; unsigned int i; fault_handler_t *handler; #ifdef HAVE_SIGACTION struct sigaction action; #endif int err; int fd; PyThreadState *tstate; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|Oi:enable", kwlist, &file, &all_threads)) return NULL; file = faulthandler_get_fileno(file, &fd); if (file == NULL) return NULL; tstate = get_thread_state(); if (tstate == NULL) return NULL; Py_XDECREF(fatal_error.file); Py_INCREF(file); fatal_error.file = file; fatal_error.fd = fd; fatal_error.all_threads = all_threads; fatal_error.interp = tstate->interp; if (!fatal_error.enabled) { fatal_error.enabled = 1; for (i=0; i < faulthandler_nsignals; i++) { handler = &faulthandler_handlers[i]; #ifdef HAVE_SIGACTION action.sa_handler = faulthandler_fatal_error; sigemptyset(&action.sa_mask); /* Do not prevent the signal from being received from within its own signal handler */ action.sa_flags = SA_NODEFER; #ifdef HAVE_SIGALTSTACK if (stack.ss_sp != NULL) { /* Call the signal handler on an alternate signal stack provided by sigaltstack() */ action.sa_flags |= SA_ONSTACK; } #endif err = sigaction(handler->signum, &action, &handler->previous); #else handler->previous = signal(handler->signum, faulthandler_fatal_error); err = (handler->previous == SIG_ERR); #endif if (err) { PyErr_SetFromErrno(PyExc_RuntimeError); return NULL; } handler->enabled = 1; } } Py_RETURN_NONE; } static void faulthandler_disable(void) { unsigned int i; fault_handler_t *handler; if (fatal_error.enabled) { fatal_error.enabled = 0; for (i=0; i < faulthandler_nsignals; i++) { handler = &faulthandler_handlers[i]; if (!handler->enabled) continue; #ifdef HAVE_SIGACTION (void)sigaction(handler->signum, &handler->previous, NULL); #else (void)signal(handler->signum, handler->previous); #endif handler->enabled = 0; } } Py_CLEAR(fatal_error.file); } static PyObject* faulthandler_disable_py(PyObject *self) { if (!fatal_error.enabled) { Py_INCREF(Py_False); return Py_False; } faulthandler_disable(); Py_INCREF(Py_True); return Py_True; } static PyObject* faulthandler_is_enabled(PyObject *self) { return PyBool_FromLong(fatal_error.enabled); } #ifdef FAULTHANDLER_LATER /* Handler of the SIGALRM signal. Dump the traceback of the current thread, or of all threads if fault_alarm.all_threads is true. On success, register itself again if fault_alarm.repeat is true. This function is signal safe and should only call signal safe functions. */ static void faulthandler_alarm(int signum) { PyThreadState *tstate; const char* errmsg; int ok; write(fault_alarm.fd, fault_alarm.header, fault_alarm.header_len); /* PyThreadState_Get() doesn't give the state of the current thread if the thread doesn't hold the GIL. Read the thread local storage (TLS) instead: call PyGILState_GetThisThreadState(). */ tstate = PyGILState_GetThisThreadState(); errmsg = _Py_DumpTracebackThreads(fault_alarm.fd, fault_alarm.interp, tstate); ok = (errmsg == NULL); if (ok && fault_alarm.repeat) alarm(fault_alarm.timeout); else /* don't call Py_CLEAR() here because it may call _Py_Dealloc() which is not signal safe */ alarm(0); if (fault_alarm.exit) _exit(1); } static char* format_timeout(double timeout) { unsigned long us, sec, min, hour; double intpart, fracpart; char buffer[100]; fracpart = modf(timeout, &intpart); sec = (unsigned long)intpart; us = (unsigned long)(fracpart * 1e6); min = sec / 60; sec %= 60; hour = min / 60; min %= 60; if (us != 0) PyOS_snprintf(buffer, sizeof(buffer), "Timeout (%lu:%02lu:%02lu.%06lu)!\n", hour, min, sec, us); else PyOS_snprintf(buffer, sizeof(buffer), "Timeout (%lu:%02lu:%02lu)!\n", hour, min, sec); return strdup(buffer); } static PyObject* faulthandler_dump_tracebacks_later(PyObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"timeout", "repeat", "file", "exit", NULL}; int timeout; PyOS_sighandler_t previous; int repeat = 0; PyObject *file = NULL; int exit = 0; int fd; char *header; size_t header_len; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|iOi:dump_tracebacks_later", kwlist, &timeout, &repeat, &file, &exit)) return NULL; if (timeout <= 0) { PyErr_SetString(PyExc_ValueError, "timeout must be greater than 0"); return NULL; } file = faulthandler_get_fileno(file, &fd); if (file == NULL) return NULL; /* format the timeout */ header = format_timeout(timeout); if (header == NULL) return PyErr_NoMemory(); header_len = strlen(header); previous = signal(SIGALRM, faulthandler_alarm); if (previous == SIG_ERR) { PyErr_SetString(PyExc_RuntimeError, "unable to set SIGALRM handler"); free(header); return NULL; } Py_XDECREF(fault_alarm.file); Py_INCREF(file); fault_alarm.file = file; fault_alarm.fd = fd; fault_alarm.timeout = timeout; fault_alarm.repeat = repeat; fault_alarm.exit = exit; fault_alarm.interp = PyThreadState_Get()->interp; fault_alarm.header = header; fault_alarm.header_len = header_len; alarm(timeout); Py_RETURN_NONE; } static PyObject* faulthandler_cancel_dump_tracebacks_later_py(PyObject *self) { alarm(0); Py_CLEAR(fault_alarm.file); free(fault_alarm.header); fault_alarm.header = NULL; Py_RETURN_NONE; } #endif /* FAULTHANDLER_LATER */ #ifdef FAULTHANDLER_USER /* Handler of user signals (e.g. SIGUSR1). Dump the traceback of the current thread, or of all threads if thread.all_threads is true. This function is signal safe and should only call signal safe functions. */ static void faulthandler_user(int signum) { user_signal_t *user; PyThreadState *tstate; int save_errno = errno; user = &user_signals[signum]; if (!user->enabled) return; #ifdef WITH_THREAD /* PyThreadState_Get() doesn't give the state of the current thread if the thread doesn't hold the GIL. Read the thread local storage (TLS) instead: call PyGILState_GetThisThreadState(). */ tstate = PyGILState_GetThisThreadState(); #else tstate = PyThreadState_Get(); #endif if (user->all_threads) _Py_DumpTracebackThreads(user->fd, user->interp, tstate); else { if (tstate == NULL) return; _Py_DumpTraceback(user->fd, tstate); } errno = save_errno; } static int check_signum(int signum) { unsigned int i; for (i=0; i < faulthandler_nsignals; i++) { if (faulthandler_handlers[i].signum == signum) { PyErr_Format(PyExc_RuntimeError, "signal %i cannot be registered, " "use enable() instead", signum); return 0; } } if (signum < 1 || NSIG <= signum) { PyErr_SetString(PyExc_ValueError, "signal number out of range"); return 0; } return 1; } static PyObject* faulthandler_register(PyObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"signum", "file", "all_threads", NULL}; int signum; PyObject *file = NULL; int all_threads = 1; int fd; user_signal_t *user; _Py_sighandler_t previous; #ifdef HAVE_SIGACTION struct sigaction action; #endif PyThreadState *tstate; int err; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|Oi:register", kwlist, &signum, &file, &all_threads)) return NULL; if (!check_signum(signum)) return NULL; tstate = get_thread_state(); if (tstate == NULL) return NULL; file = faulthandler_get_fileno(file, &fd); if (file == NULL) return NULL; if (user_signals == NULL) { user_signals = calloc(NSIG, sizeof(user_signal_t)); if (user_signals == NULL) return PyErr_NoMemory(); } user = &user_signals[signum]; if (!user->enabled) { #ifdef HAVE_SIGACTION action.sa_handler = faulthandler_user; sigemptyset(&action.sa_mask); /* if the signal is received while the kernel is executing a system call, try to restart the system call instead of interrupting it and return EINTR */ action.sa_flags = SA_RESTART; #ifdef HAVE_SIGALTSTACK if (stack.ss_sp != NULL) { /* Call the signal handler on an alternate signal stack provided by sigaltstack() */ action.sa_flags |= SA_ONSTACK; } #endif err = sigaction(signum, &action, &previous); #else previous = signal(signum, faulthandler_user); err = (previous == SIG_ERR); #endif if (err) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } } Py_XDECREF(user->file); Py_INCREF(file); user->file = file; user->fd = fd; user->all_threads = all_threads; user->previous = previous; user->interp = tstate->interp; user->enabled = 1; Py_RETURN_NONE; } static int faulthandler_unregister(user_signal_t *user, int signum) { if (!user->enabled) return 0; user->enabled = 0; #ifdef HAVE_SIGACTION (void)sigaction(signum, &user->previous, NULL); #else (void)signal(signum, user->previous); #endif user->fd = -1; return 1; } static PyObject* faulthandler_unregister_py(PyObject *self, PyObject *args) { int signum; user_signal_t *user; int change; if (!PyArg_ParseTuple(args, "i:unregister", &signum)) return NULL; if (!check_signum(signum)) return NULL; if (user_signals == NULL) Py_RETURN_FALSE; user = &user_signals[signum]; change = faulthandler_unregister(user, signum); Py_CLEAR(user->file); return PyBool_FromLong(change); } #endif /* FAULTHANDLER_USER */ static PyObject * faulthandler_read_null(PyObject *self, PyObject *args) { int *x = NULL, y; int release_gil = 0; if (!PyArg_ParseTuple(args, "|i:_read_null", &release_gil)) return NULL; if (release_gil) { Py_BEGIN_ALLOW_THREADS y = *x; Py_END_ALLOW_THREADS } else y = *x; return PyLong_FromLong(y); } static PyObject * faulthandler_sigsegv(PyObject *self, PyObject *args) { #if defined(MS_WINDOWS) /* For SIGSEGV, faulthandler_fatal_error() restores the previous signal handler and then gives back the execution flow to the program (without calling explicitly the previous error handler). In a normal case, the SIGSEGV was raised by the kernel because of a fault, and so if the program retries to execute the same instruction, the fault will be raised again. Here the fault is simulated by a fake SIGSEGV signal raised by the application. We have to raise SIGSEGV at lease twice: once for faulthandler_fatal_error(), and one more time for the previous signal handler. */ while(1) raise(SIGSEGV); #else raise(SIGSEGV); #endif Py_RETURN_NONE; } static PyObject * faulthandler_sigfpe(PyObject *self, PyObject *args) { /* Do an integer division by zero: raise a SIGFPE on Intel CPU, but not on PowerPC. Use volatile to disable compile-time optimizations. */ volatile int x = 1, y = 0, z; z = x / y; /* if the division by zero didn't raise a SIGFPE, raise it manually */ raise(SIGFPE); return PyLong_FromLong(z); } static PyObject * faulthandler_sigabrt(PyObject *self, PyObject *args) { #ifdef _MSC_VER /* Visual Studio: configure abort() to not display an error message nor open a popup asking to report the fault. */ _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); #endif abort(); Py_RETURN_NONE; } #ifdef SIGBUS static PyObject * faulthandler_sigbus(PyObject *self, PyObject *args) { raise(SIGBUS); Py_RETURN_NONE; } #endif #ifdef SIGILL static PyObject * faulthandler_sigill(PyObject *self, PyObject *args) { raise(SIGILL); Py_RETURN_NONE; } #endif static PyObject * faulthandler_fatal_error_py(PyObject *self, PyObject *args) { char *message; #if PY_MAJOR_VERSION >= 3 if (!PyArg_ParseTuple(args, "y:fatal_error", &message)) return NULL; #else if (!PyArg_ParseTuple(args, "s:fatal_error", &message)) return NULL; #endif Py_FatalError(message); Py_RETURN_NONE; } #if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION) void* stack_overflow(void *min_sp, void *max_sp, size_t *depth) { /* allocate 4096 bytes on the stack at each call */ unsigned char buffer[4096]; void *sp = &buffer; *depth += 1; if (sp < min_sp || max_sp < sp) return sp; buffer[0] = 1; buffer[4095] = 0; return stack_overflow(min_sp, max_sp, depth); } static PyObject * faulthandler_stack_overflow(PyObject *self) { size_t depth, size; void *sp = &depth, *stop; depth = 0; stop = stack_overflow(sp - STACK_OVERFLOW_MAX_SIZE, sp + STACK_OVERFLOW_MAX_SIZE, &depth); if (sp < stop) size = stop - sp; else size = sp - stop; PyErr_Format(PyExc_RuntimeError, "unable to raise a stack overflow (allocated %zu bytes " "on the stack, %zu recursive calls)", size, depth); return NULL; } #endif #if PY_MAJOR_VERSION >= 3 static int faulthandler_traverse(PyObject *module, visitproc visit, void *arg) { #ifdef FAULTHANDLER_USER unsigned int signum; #endif #ifdef FAULTHANDLER_LATER Py_VISIT(fault_alarm.file); #endif #ifdef FAULTHANDLER_USER if (user_signals != NULL) { for (signum=0; signum < NSIG; signum++) Py_VISIT(user_signals[signum].file); } #endif Py_VISIT(fatal_error.file); return 0; } #endif PyDoc_STRVAR(module_doc, "faulthandler module."); static PyMethodDef module_methods[] = { {"enable", (PyCFunction)faulthandler_enable, METH_VARARGS|METH_KEYWORDS, PyDoc_STR("enable(file=sys.stderr, all_threads=True): " "enable the fault handler")}, {"disable", (PyCFunction)faulthandler_disable_py, METH_NOARGS, PyDoc_STR("disable(): disable the fault handler")}, {"is_enabled", (PyCFunction)faulthandler_is_enabled, METH_NOARGS, PyDoc_STR("is_enabled()->bool: check if the handler is enabled")}, {"dump_traceback", (PyCFunction)faulthandler_dump_traceback_py, METH_VARARGS|METH_KEYWORDS, PyDoc_STR("dump_traceback(file=sys.stderr, all_threads=True): " "dump the traceback of the current thread, or of all threads " "if all_threads is True, into file")}, #ifdef FAULTHANDLER_LATER {"dump_tracebacks_later", (PyCFunction)faulthandler_dump_tracebacks_later, METH_VARARGS|METH_KEYWORDS, PyDoc_STR("dump_tracebacks_later(timeout, repeat=False, file=sys.stderrn, exit=False):\n" "dump the traceback of all threads in timeout seconds,\n" "or each timeout seconds if repeat is True. If exit is True, " "call _exit(1) which is not safe.")}, {"cancel_dump_tracebacks_later", (PyCFunction)faulthandler_cancel_dump_tracebacks_later_py, METH_NOARGS, PyDoc_STR("cancel_dump_tracebacks_later():\ncancel the previous call " "to dump_tracebacks_later().")}, #endif #ifdef FAULTHANDLER_USER {"register", (PyCFunction)faulthandler_register, METH_VARARGS|METH_KEYWORDS, PyDoc_STR("register(signum, file=sys.stderr, all_threads=True): " "register an handler for the signal 'signum': dump the " "traceback of the current thread, or of all threads if " "all_threads is True, into file")}, {"unregister", faulthandler_unregister_py, METH_VARARGS|METH_KEYWORDS, PyDoc_STR("unregister(signum): unregister the handler of the signal " "'signum' registered by register()")}, #endif {"_read_null", faulthandler_read_null, METH_VARARGS, PyDoc_STR("_read_null(release_gil=False): read from NULL, raise " "a SIGSEGV or SIGBUS signal depending on the platform")}, {"_sigsegv", faulthandler_sigsegv, METH_VARARGS, PyDoc_STR("_sigsegv(): raise a SIGSEGV signal")}, {"_sigabrt", faulthandler_sigabrt, METH_VARARGS, PyDoc_STR("_sigabrt(): raise a SIGABRT signal")}, {"_sigfpe", (PyCFunction)faulthandler_sigfpe, METH_NOARGS, PyDoc_STR("_sigfpe(): raise a SIGFPE signal")}, #ifdef SIGBUS {"_sigbus", (PyCFunction)faulthandler_sigbus, METH_NOARGS, PyDoc_STR("_sigbus(): raise a SIGBUS signal")}, #endif #ifdef SIGILL {"_sigill", (PyCFunction)faulthandler_sigill, METH_NOARGS, PyDoc_STR("_sigill(): raise a SIGILL signal")}, #endif {"_fatal_error", faulthandler_fatal_error_py, METH_VARARGS, PyDoc_STR("_fatal_error(message): call Py_FatalError(message)")}, #if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION) {"_stack_overflow", (PyCFunction)faulthandler_stack_overflow, METH_NOARGS, PyDoc_STR("_stack_overflow(): recursive call to raise a stack overflow")}, #endif {NULL, NULL} /* terminator */ }; #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef module_def = { PyModuleDef_HEAD_INIT, "faulthandler", module_doc, 0, /* non negative size to be able to unload the module */ module_methods, NULL, faulthandler_traverse, NULL, NULL }; #endif PyMODINIT_FUNC #if PY_MAJOR_VERSION >= 3 PyInit_faulthandler(void) #else initfaulthandler(void) #endif { PyObject *m, *version; #ifdef HAVE_SIGALTSTACK int err; #endif #if PY_MAJOR_VERSION >= 3 m = PyModule_Create(&module_def); #else m = Py_InitModule3("faulthandler", module_methods, module_doc); #endif if (m == NULL) { #if PY_MAJOR_VERSION >= 3 return NULL; #else return; #endif } #ifdef HAVE_SIGALTSTACK /* Try to allocate an alternate stack for faulthandler() signal handler to * be able to allocate memory on the stack, even on a stack overflow. If it * fails, ignore the error. */ stack.ss_flags = 0; stack.ss_size = SIGSTKSZ; stack.ss_sp = PyMem_Malloc(stack.ss_size); if (stack.ss_sp != NULL) { err = sigaltstack(&stack, NULL); if (err) { PyMem_Free(stack.ss_sp); stack.ss_sp = NULL; } } #endif (void)Py_AtExit(faulthandler_unload); version = Py_BuildValue("(ii)", VERSION >> 8, VERSION & 0xFF); if (version == NULL) goto error; PyModule_AddObject(m, "version", version); #if PY_MAJOR_VERSION >= 3 version = PyUnicode_FromFormat("%i.%i", VERSION >> 8, VERSION & 0xFF); #else version = PyString_FromFormat("%i.%i", VERSION >> 8, VERSION & 0xFF); #endif if (version == NULL) goto error; PyModule_AddObject(m, "__version__", version); #if PY_MAJOR_VERSION >= 3 return m; #else return; #endif error: #if PY_MAJOR_VERSION >= 3 Py_DECREF(m); return NULL; #else return; #endif } static void faulthandler_unload(void) { #ifdef FAULTHANDLER_USER unsigned int signum; #endif #ifdef FAULTHANDLER_LATER /* later */ alarm(0); if (fault_alarm.header != NULL) { free(fault_alarm.header); fault_alarm.header = NULL; } /* Don't call Py_CLEAR(fault_alarm.file): this function is called too late, by Py_AtExit(). Destroy a Python object here raise strange errors. */ #endif #ifdef FAULTHANDLER_USER /* user */ if (user_signals != NULL) { for (signum=0; signum < NSIG; signum++) { faulthandler_unregister(&user_signals[signum], signum); /* Don't call Py_CLEAR(user->file): this function is called too late, by Py_AtExit(). Destroy a Python object here raise strange errors. */ } free(user_signals); user_signals = NULL; } #endif /* don't release file: faulthandler_unload_fatal_error() is called too late */ fatal_error.file = NULL; faulthandler_disable(); #ifdef HAVE_SIGALTSTACK if (stack.ss_sp != NULL) { PyMem_Free(stack.ss_sp); stack.ss_sp = NULL; } #endif } faulthandler-2.0/tests.py0000644000175000017500000003674111561770147014606 0ustar haypohaypofrom __future__ import with_statement from contextlib import contextmanager import datetime import faulthandler import os import re import signal import subprocess import sys import tempfile import unittest try: import threading HAVE_THREADS = True except ImportError: HAVE_THREADS = False TIMEOUT = 1 Py_REF_DEBUG = hasattr(sys, 'gettotalrefcount') try: skipIf = unittest.skipIf except AttributeError: import functools def skipIf(test, reason): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): if not test: return func(*args, **kw) else: print("skip %s: %s" % (func.__name__, reason)) return wrapper return decorator try: from resource import setrlimit, RLIMIT_CORE, error as resource_error except ImportError: prepare_subprocess = None else: def prepare_subprocess(): # don't create core file try: setrlimit(RLIMIT_CORE, (0, 0)) except (ValueError, resource_error): pass def expected_traceback(lineno1, lineno2, header, count=1): regex = header regex += ' File "", line %s in func\n' % lineno1 regex += ' File "", line %s in ' % lineno2 if count != 1: regex = (regex + '\n') * (count - 1) + regex return '^' + regex + '$' @contextmanager def temporary_filename(): filename = tempfile.mktemp() try: yield filename finally: try: os.unlink(filename) except OSError: pass class FaultHandlerTests(unittest.TestCase): def get_output(self, code, filename=None): """ Run the specified code in Python (in a new child process) and read the output from the standard error or from a file (if filename is set). Return the output lines as a list. Strip the reference count from the standard error for Python debug build, and replace "Current thread 0x00007f8d8fbd9700" by "Current thread XXX". """ options = {} if prepare_subprocess: options['preexec_fn'] = prepare_subprocess process = subprocess.Popen( [sys.executable, '-c', code], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **options) stdout, stderr = process.communicate() exitcode = process.wait() output = stdout.decode('ascii', 'backslashreplace') output = re.sub(r"\[\d+ refs\]\r?\n?$", "", output) if filename: self.assertEqual(output, '') with open(filename, "rb") as fp: output = fp.read() output = output.decode('ascii', 'backslashreplace') output = re.sub('Current thread 0x[0-9a-f]+', 'Current thread XXX', output) return output.splitlines(), exitcode def check_fatal_error(self, code, line_number, name_regex, filename=None, all_threads=True, other_regex=None): """ Check that the fault handler for fatal errors is enabled and check the traceback from the child process output. Raise an error if the output doesn't match the expected format. """ if all_threads: header = r'Current thread XXX' else: header = r'Traceback \(most recent call first\)' regex = """ ^Fatal Python error: %s %s: File "", line %s in $ """.strip() regex = regex % (name_regex, header, line_number) if other_regex: regex += '|' + other_regex output, exitcode = self.get_output(code, filename) output = '\n'.join(output) self.assertRegex(output, regex) self.assertNotEqual(exitcode, 0) def test_read_null(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._read_null() """.strip(), 3, '(?:Segmentation fault|Bus error)') def test_sigsegv(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._sigsegv() """.strip(), 3, 'Segmentation fault') def test_sigabrt(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._sigabrt() """.strip(), 3, 'Aborted') @skipIf(sys.platform == 'win32', "SIGFPE cannot be caught on Windows") def test_sigfpe(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._sigfpe() """.strip(), 3, 'Floating point exception') @skipIf(not hasattr(faulthandler, '_sigbus'), "need faulthandler._sigbus()") def test_sigbus(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._sigbus() """.strip(), 3, 'Bus error') @skipIf(not hasattr(faulthandler, '_sigill'), "need faulthandler._sigill()") def test_sigill(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._sigill() """.strip(), 3, 'Illegal instruction') def test_fatal_error(self): if sys.version_info >= (2, 6): arg = "b'xyz'" else: arg = "'xyz'" message = "xyz\nFatal Python error: Aborted" self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._fatal_error(%s) """.strip() % (arg,), 3, message) @skipIf(not hasattr(faulthandler, '_stack_overflow'), 'need faulthandler._stack_overflow()') def test_stack_overflow(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._stack_overflow() """.strip(), 3, '(?:Segmentation fault|Bus error)', other_regex='unable to raise a stack overflow') def test_gil_released(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._read_null(True) """.strip(), 3, '(?:Segmentation fault|Bus error)') def test_enable_file(self): with temporary_filename() as filename: self.check_fatal_error(""" import faulthandler output = open(%r, 'wb') faulthandler.enable(output) faulthandler._read_null() """.strip() % (filename,), 4, '(?:Segmentation fault|Bus error)', filename=filename) def test_enable_single_thread(self): self.check_fatal_error(""" import faulthandler faulthandler.enable(all_threads=False) faulthandler._read_null() """.strip(), 3, '(?:Segmentation fault|Bus error)', all_threads=False) def test_disable(self): code = """ import faulthandler faulthandler.enable() faulthandler.disable() faulthandler._read_null() """.strip() not_expected = 'Fatal Python error' stderr, exitcode = self.get_output(code) stder = '\n'.join(stderr) self.assertTrue(not_expected not in stderr, "%r is present in %r" % (not_expected, stderr)) self.assertNotEqual(exitcode, 0) def test_is_enabled(self): was_enabled = faulthandler.is_enabled() try: faulthandler.enable() self.assertTrue(faulthandler.is_enabled()) faulthandler.disable() self.assertFalse(faulthandler.is_enabled()) finally: if was_enabled: faulthandler.enable() else: faulthandler.disable() def check_dump_traceback(self, filename): """ Explicitly call dump_traceback() function and check its output. Raise an error if the output doesn't match the expected format. """ code = """ from __future__ import with_statement import faulthandler def funcB(): if %s: with open(%s, "wb") as fp: faulthandler.dump_traceback(fp) else: faulthandler.dump_traceback() def funcA(): funcB() funcA() """.strip() code = code % (bool(filename), repr(filename)) if filename: lineno = 7 else: lineno = 9 expected = [ 'Current thread XXX:', ' File "", line %s in funcB' % lineno, ' File "", line 12 in funcA', ' File "", line 14 in ' ] trace, exitcode = self.get_output(code, filename) self.assertEqual(trace, expected) self.assertEqual(exitcode, 0) def test_dump_traceback(self): self.check_dump_traceback(None) def test_dump_traceback_file(self): with temporary_filename() as filename: self.check_dump_traceback(filename) @skipIf(not HAVE_THREADS, 'need threads') def check_dump_traceback_threads(self, filename): """ Call explicitly dump_traceback(all_threads=True) and check the output. Raise an error if the output doesn't match the expected format. """ code = """ from __future__ import with_statement import faulthandler from threading import Thread, Event import time def dump(): if %s: with open(%s, "wb") as fp: faulthandler.dump_traceback(fp, all_threads=True) else: faulthandler.dump_traceback(all_threads=True) class Waiter(Thread): # avoid blocking if the main thread raises an exception. daemon = True def __init__(self): Thread.__init__(self) self.running = Event() self.stop = Event() def run(self): self.running.set() self.stop.wait() waiter = Waiter() waiter.start() waiter.running.wait() dump() waiter.stop.set() waiter.join() """.strip() code = code % (bool(filename), repr(filename)) output, exitcode = self.get_output(code, filename) output = '\n'.join(output) if filename: lineno = 9 else: lineno = 11 regex = """ ^Thread 0x[0-9a-f]+: (?: File ".*threading.py", line [0-9]+ in [_a-z]+ ){1,3} File "", line 24 in run File ".*threading.py", line [0-9]+ in _?_bootstrap_inner File ".*threading.py", line [0-9]+ in _?_bootstrap Current thread XXX: File "", line %s in dump File "", line 29 in $ """.strip() regex = regex % (lineno,) self.assertRegex(output, regex) self.assertEqual(exitcode, 0) def test_dump_traceback_threads(self): self.check_dump_traceback_threads(None) def test_dump_traceback_threads_file(self): with temporary_filename() as filename: self.check_dump_traceback_threads(filename) def _check_dump_tracebacks_later(self, repeat, cancel, filename): """ Check how many times the traceback is written in timeout x 2.5 seconds, or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending on repeat and cancel options. Raise an error if the output doesn't match the expect format. """ timeout_str = str(datetime.timedelta(seconds=TIMEOUT)) code = """ import faulthandler import time def func(repeat, cancel, timeout): if cancel: faulthandler.cancel_dump_tracebacks_later() for loop in range(2): time.sleep(timeout * 1.25) faulthandler.cancel_dump_tracebacks_later() timeout = %s repeat = %s cancel = %s if %s: file = open(%s, "wb") else: file = None faulthandler.dump_tracebacks_later(timeout, repeat=repeat, file=file) func(repeat, cancel, timeout) if file is not None: file.close() """.strip() code = code % (TIMEOUT, repeat, cancel, bool(filename), repr(filename)) trace, exitcode = self.get_output(code, filename) trace = '\n'.join(trace) if not cancel: if repeat: count = 2 else: count = 1 header = r'Timeout \(%s\)!\nCurrent thread XXX:\n' % timeout_str regex = expected_traceback(8, 20, header, count=count) self.assertRegex(trace, regex) else: self.assertEqual(trace, '') self.assertEqual(exitcode, 0) @skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'), 'need faulthandler.dump_tracebacks_later()') def check_dump_tracebacks_later(self, repeat=False, cancel=False, file=False): if file: with temporary_filename() as filename: self._check_dump_tracebacks_later(repeat, cancel, filename) else: self._check_dump_tracebacks_later(repeat, cancel, None) def test_dump_tracebacks_later(self): self.check_dump_tracebacks_later() def test_dump_tracebacks_later_repeat(self): self.check_dump_tracebacks_later(repeat=True) def test_dump_tracebacks_later_cancel(self): self.check_dump_tracebacks_later(cancel=True) def test_dump_tracebacks_later_file(self): self.check_dump_tracebacks_later(file=True) @skipIf(not hasattr(faulthandler, "register"), "need faulthandler.register") def check_register(self, filename=False, all_threads=False, unregister=False): """ Register a handler displaying the traceback on a user signal. Raise the signal and check the written traceback. Raise an error if the output doesn't match the expected format. """ signum = signal.SIGUSR1 code = """ import faulthandler import os import signal def func(signum): os.kill(os.getpid(), signum) signum = %s unregister = %s if %s: file = open(%s, "wb") else: file = None faulthandler.register(signum, file=file, all_threads=%s) if unregister: faulthandler.unregister(signum) func(signum) if file is not None: file.close() """.strip() code = code % ( signum, unregister, bool(filename), repr(filename), all_threads, ) trace, exitcode = self.get_output(code, filename) trace = '\n'.join(trace) if not unregister: if all_threads: regex = 'Current thread XXX:\n' else: regex = 'Traceback \(most recent call first\):\n' regex = expected_traceback(6, 17, regex) self.assertRegex(trace, regex) else: self.assertEqual(trace, '') if unregister: self.assertNotEqual(exitcode, 0) else: self.assertEqual(exitcode, 0) def test_register(self): self.check_register() def test_unregister(self): self.check_register(unregister=True) def test_register_file(self): with temporary_filename() as filename: self.check_register(filename=filename) def test_register_threads(self): self.check_register(all_threads=True) if not hasattr(unittest.TestCase, 'assertRegex'): # Copy/paste from Python 3.3: just replace (str, bytes) by str def assertRegex(self, text, expected_regex, msg=None): """Fail the test unless the text matches the regular expression.""" if isinstance(expected_regex, str): assert expected_regex, "expected_regex must not be empty." expected_regex = re.compile(expected_regex) if not expected_regex.search(text): msg = msg or "Regex didn't match" msg = '%s: %r not found in %r' % (msg, expected_regex.pattern, text) raise self.failureException(msg) if __name__ == "__main__": unittest.main() faulthandler-2.0/README0000644000175000017500000002434711562074611013743 0ustar haypohaypo+++++++++++++ Fault handler +++++++++++++ Fault handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals: display the Python traceback and restore the previous handler. Allocate an alternate stack for this handler, if sigaltstack() is available, to be able to allocate memory on the stack, even on stack overflow (not available on Windows). Import the module and call faulthandler.enable() to enable the fault handler. The fault handler is called on catastrophic cases and so it can only use signal-safe functions (eg. it doesn't allocate memory on the heap). That's why the traceback is limited: it only supports ASCII encoding (use the backslashreplace error handler for non-ASCII characters) and limits each string to 100 characters, doesn't print the source code in the traceback (only the filename, the function name and the line number), is limited to 100 frames and 100 threads. By default, the Python traceback is written to the standard error stream. Start your graphical applications in a terminal and run your server in foreground to see the traceback, or pass a file to faulthandler.enable(). faulthandler is implemented in C using signal handlers to be able to dump a traceback on a crash or when Python is blocked (eg. deadlock). Website: https://github.com/haypo/faulthandler/wiki/ faulthandler is part of Python since Python 3.3: http://docs.python.org/dev/library/faulthandler.html Example ======= Example of a segmentation fault on Linux: :: $ python >>> import faulthandler >>> faulthandler.enable() >>> faulthandler._sigsegv() Fatal Python error: Segmentation fault Traceback (most recent call first): File "", line 1 in Segmentation fault Installation ============ To install faulthandler module, type the following command: :: python setup.py install Then you can test your setup using the following command: :: python tests.py You need a C compiler (eg. gcc) and Python headers to build the faulthandler module. Eg. on Fedora, you have to install python-devel package (sudo yum install python-devel). faulthandler module API ======================= There are 4 different ways to display the Python traceback: * enable(): on a crash * dump_tracebacks_later(): after a timeout (useful if your program hangs) * register(): by sending a signal (eg. SIGUSR1). It doesn't work on Windows. * dump_traceback(): explicitly Fault handler state (disabled by default): * enable(file=sys.stderr, all_threads=False): enable the fault handler * disable(): disable the fault handler * is_enabled(): get the status of the fault handler Dump the current traceback: * dump_traceback(file=sys.stderr, all_threads=False): dump traceback of the current thread, or of all threads if all_threads is True, into file * dump_tracebacks_later(timeout, repeat=False, file=sys.stderr, exit=False): dump the traceback of all threads in timeout seconds, or each timeout seconds if repeat is True. If the function is called twice, the new call replaces previous parameters. Exit immediatly if exit is True. * cancel_dump_tracebacks_later(): cancel the previous call to dump_tracebacks_later() dump_tracebacks_later() is implemented using the SIGALRM signal and the alarm() function: if the signal handler is called during a system call, the system call is interrupted (return EINTR). It it not available on Windows. enable() and dump_tracebacks_later() keep an internal reference to the output file. Use disable() and cancel_dump_tracebacks_later() to clear this reference. Dump the traceback on an user signal: * register(signum, file=sys.stderr, all_threads=False): register an handler for the signal 'signum': dump the traceback of the current thread, or of all threads if all_threads is True, into file". Not available on Windows. * unregister(signum): unregister the handler of the signal 'signum' registered by register(). Not available on Windows. Functions to test the fault handler: * _fatal_error(message): Exit Python with a fatal error, call Py_FatalError() with message. * _read_null(): read from the NULL pointer (raise SIGSEGV or SIGBUS depending on the platform) * _sigabrt(): raise a SIGABRT signal (Aborted) * _sigbus(): raise a SIGBUS signal (Bus error) * _sigfpe(): raise a SIGFPE signal (Floating point exception), do a division by zero * _sigill(): raise a SIGILL signal (Illegal instruction) * _sigsegv(): raise a SIGSEGV signal (Segmentation fault), read memory from NULL (address 0) * _stack_overflow(): raise a stack overflow error. Not available on all platforms. register(), unregister(), sigbus() and sigill() are not available on all operation systems. faulthandler.version_info is the module version as a tuple: (major, minor), faulthandler.__version__ is the module version a string (e.g. "2.0"). Changelog ========= Version 2.0 (2011-05-10) ------------------------ Major changes: * faulthandler is now part of Python 3.3 * enable() handles also the SIGABRT signal * Add exit option to dump_tracebacks_later(): if True, exit the program on timeout after dumping the traceback Other changes: * Change default value of the all_threads argument: dump all threads by default because under some rare conditions, it is not possible to get the current thread * Save/restore errno in signal handlers * dump_tracebacks_later() always dump all threads: remove all_threads option * Add faulthandler.__version__ attribute (module version as a string) * faulthandler.version is now a tuple * Rename: * dump_traceback_later() to dump_tracebacks_later() * cancel_dump_traceback_later() to cancel_dump_tracebacks_later() * sigsegv() to _sigsegv() * sigfpe() to _sigfpe() * sigbus() to _sigbus() * sigill() to _sigill() * register() and unregister() are no more available on Windows. They were useless: only SIGSEGV, SIGABRT and SIGILL can be handled by the application, and these signals can only be handled by enable(). * Add _fatal_error(), _read_null(), _sigabrt() and _stack_overflow() test functions * register() uses sigaction() SA_RESTART flag to try to not interrupt the current system call * The fault handler calls the previous signal handler, using sigaction() SA_NODEFER flag to call it immediatly * enable() raises an OSError if it was not possible to register a signal handler * Set module size to 0, instead of -1, to be able to unload the module with Python 3 * Fix a reference leak in dump_tracebacks_later() * Fix register() if it called twice with the same signal * Implement m_traverse for Python 3 to help the garbage collector * Move code from faulthandler/*.c to faulthandler.c and traceback.c: the code is simpler and it was easier to integrate faulthandler into Python 3.3 using one file (traceback.c already existed in Python) * register() uses a static list for all signals instead of reallocating memory each time a new signal is registered, because the list is shared with the signal handler which may be called anytime. Version 1.5 (2011-03-24) ------------------------ * Conform to the PEP 8: * Rename isenabled() to is_enabled() * Rename dumpbacktrace() to dump_traceback() * Rename dumpbacktrace_later() to dump_traceback_later() * Rename cancel_dumpbacktrace_later() to cancel_dump_traceback_later() * Limit strings to 100 characters * dump_traceback_later() signal handler doesn't clear its reference to the file, because Py_CLEAR() is not signal safe: you have to call explicitly cancel_dump_traceback_later() Version 1.4 (2011-02-14) ------------------------ * Add register() and unregister() functions * Add optional all_threads argument to enable() * Limit the backtrace to 100 threads * Allocate an alternative stack for the fatal signal handler to be able to display a backtrace on a stack overflow (define HAVE_SIGALTSTACK). Not available on Windows. Version 1.3 (2011-01-31) ------------------------ * Don't compile dumpbacktrace_later() and cancel_dumpbacktrace_later() on Windows because alarm() is missing Version 1.2 (2011-01-31) ------------------------ * Add dumpbacktrace_later() and cancel_dumpbacktrace_later() function * enable() and dumpbacktrace() get an optional file argument * Replace dumpbacktrace_threads() function by a new dumpbacktrace() argument: dumpbacktrace(all_threads=True) * enable() gets the file descriptor of sys.stderr instead of using the file descriptor 2 Version 1.1 (2011-01-03) ------------------------ * Disable the handler by default, because pkgutil may load the module and so enable the handler which is unexpected * Add dumpbacktrace() and dumpbacktrace_threads() functions * sigill() is available on Windows thanks to Martin's patch * Fix dump_ascii() for signed char type (eg. on FreeBSD) * Fix tests.py for Python 2.5 Version 1.0 (2010-12-24) ------------------------ * First public release Status ====== * 2011-01-31: Version 1.2 tested with Python 2.5, 2.6, 2.7, 3.1 and 3.2 on Debian Sid * 2010-12-24: Tested with Python 2.6, 3.1 and 3.2 on Debian Sid * 2010-12-24: Tested with Python 2.6 and 3.1 on Windows XP Similar projects ================ Python debuggers: * tipper: write the traceback of the current thread into a file on SIGUSR1 signal: http://pypi.python.org/pypi/tipper/ * crier: write the traceback of the current thread into a file (eg. /tmp/dump-) if a "request" file is created (eg. /tmp/crier-). Implemented using a thread. https://gist.github.com/737056 * Python WAD (Wrapped Application Debugger), not update since 2001: http://www.dabeaz.com/papers/Python2001/python.html Application fault handlers: * The GNU libc has a fault handler in debug/segfault.c * XEmacs has a fault handler displaying the Lisp traceback * RPy has a fault handler System-wide fault handlers: * Ubuntu uses Apport: https://wiki.ubuntu.com/Apport * The Linux kernel logs also segfaults into /var/log/kern.log (and /var/log/syslog). /proc/sys/kernel/core_pattern contols how coredumps are created. * Windows opens a popup on a fatal error asking if the error should be reported to Microsoft See also ======== * http://bugs.python.org/issue8863 (may 2010): Display Python backtrace on SIGSEGV, SIGFPE and fatal error * http://bugs.python.org/issue3999 (sept. 2008): Real segmentation fault handler faulthandler-2.0/setup.py0000755000175000017500000000376411562074556014610 0ustar haypohaypo#!/usr/bin/env python # Todo list to prepare a release: # - run ./run_tests.py # - run tests on FreeBSD and Windows # - set VERSION in faulthandler.c # - set VERSION in setup.py # - set release date in the ChangeLog (README file) # - git commit -a # - git tag -a faulthandler-x.y -m "tag version x.y" # - git push # - git push --tags # - python setup.py register sdist upload # - python2.6 setup.py bdist_wininst upload # - python2.7 setup.py bdist_wininst upload # - python3.1 setup.py bdist_wininst upload # - update the website # # After the release: # - increment VERSION in faulthandler.c # - increment VERSION in setup.py # - add a new empty section in the Changelog for the new version # - git commit -a # - git push from __future__ import with_statement from distutils.core import setup, Extension from os.path import join as path_join import sys if sys.version_info >= (3,3): print("ERROR: faulthandler is a builtin module since Python 3.3") sys.exit(1) VERSION = "2.0" FILES = ['faulthandler.c', 'traceback.c'] CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Natural Language :: English', 'Programming Language :: C', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Debuggers', 'Topic :: Software Development :: Libraries :: Python Modules', ] with open('README') as f: long_description = f.read().strip() options = { 'name': "faulthandler", 'version': VERSION, 'license': "BSD (2-clause)", 'description': 'Display the Python traceback on a crash', 'long_description': long_description, 'url': "https://github.com/haypo/faulthandler/wiki/", 'author': 'Victor Stinner', 'author_email': 'victor.stinner@haypocalc.com', 'ext_modules': [Extension('faulthandler', FILES)], 'classifiers': CLASSIFIERS, } setup(**options) faulthandler-2.0/PKG-INFO0000644000175000017500000003232411562074717014161 0ustar haypohaypoMetadata-Version: 1.0 Name: faulthandler Version: 2.0 Summary: Display the Python traceback on a crash Home-page: https://github.com/haypo/faulthandler/wiki/ Author: Victor Stinner Author-email: victor.stinner@haypocalc.com License: BSD (2-clause) Description: +++++++++++++ Fault handler +++++++++++++ Fault handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals: display the Python traceback and restore the previous handler. Allocate an alternate stack for this handler, if sigaltstack() is available, to be able to allocate memory on the stack, even on stack overflow (not available on Windows). Import the module and call faulthandler.enable() to enable the fault handler. The fault handler is called on catastrophic cases and so it can only use signal-safe functions (eg. it doesn't allocate memory on the heap). That's why the traceback is limited: it only supports ASCII encoding (use the backslashreplace error handler for non-ASCII characters) and limits each string to 100 characters, doesn't print the source code in the traceback (only the filename, the function name and the line number), is limited to 100 frames and 100 threads. By default, the Python traceback is written to the standard error stream. Start your graphical applications in a terminal and run your server in foreground to see the traceback, or pass a file to faulthandler.enable(). faulthandler is implemented in C using signal handlers to be able to dump a traceback on a crash or when Python is blocked (eg. deadlock). Website: https://github.com/haypo/faulthandler/wiki/ faulthandler is part of Python since Python 3.3: http://docs.python.org/dev/library/faulthandler.html Example ======= Example of a segmentation fault on Linux: :: $ python >>> import faulthandler >>> faulthandler.enable() >>> faulthandler._sigsegv() Fatal Python error: Segmentation fault Traceback (most recent call first): File "", line 1 in Segmentation fault Installation ============ To install faulthandler module, type the following command: :: python setup.py install Then you can test your setup using the following command: :: python tests.py You need a C compiler (eg. gcc) and Python headers to build the faulthandler module. Eg. on Fedora, you have to install python-devel package (sudo yum install python-devel). faulthandler module API ======================= There are 4 different ways to display the Python traceback: * enable(): on a crash * dump_tracebacks_later(): after a timeout (useful if your program hangs) * register(): by sending a signal (eg. SIGUSR1). It doesn't work on Windows. * dump_traceback(): explicitly Fault handler state (disabled by default): * enable(file=sys.stderr, all_threads=False): enable the fault handler * disable(): disable the fault handler * is_enabled(): get the status of the fault handler Dump the current traceback: * dump_traceback(file=sys.stderr, all_threads=False): dump traceback of the current thread, or of all threads if all_threads is True, into file * dump_tracebacks_later(timeout, repeat=False, file=sys.stderr, exit=False): dump the traceback of all threads in timeout seconds, or each timeout seconds if repeat is True. If the function is called twice, the new call replaces previous parameters. Exit immediatly if exit is True. * cancel_dump_tracebacks_later(): cancel the previous call to dump_tracebacks_later() dump_tracebacks_later() is implemented using the SIGALRM signal and the alarm() function: if the signal handler is called during a system call, the system call is interrupted (return EINTR). It it not available on Windows. enable() and dump_tracebacks_later() keep an internal reference to the output file. Use disable() and cancel_dump_tracebacks_later() to clear this reference. Dump the traceback on an user signal: * register(signum, file=sys.stderr, all_threads=False): register an handler for the signal 'signum': dump the traceback of the current thread, or of all threads if all_threads is True, into file". Not available on Windows. * unregister(signum): unregister the handler of the signal 'signum' registered by register(). Not available on Windows. Functions to test the fault handler: * _fatal_error(message): Exit Python with a fatal error, call Py_FatalError() with message. * _read_null(): read from the NULL pointer (raise SIGSEGV or SIGBUS depending on the platform) * _sigabrt(): raise a SIGABRT signal (Aborted) * _sigbus(): raise a SIGBUS signal (Bus error) * _sigfpe(): raise a SIGFPE signal (Floating point exception), do a division by zero * _sigill(): raise a SIGILL signal (Illegal instruction) * _sigsegv(): raise a SIGSEGV signal (Segmentation fault), read memory from NULL (address 0) * _stack_overflow(): raise a stack overflow error. Not available on all platforms. register(), unregister(), sigbus() and sigill() are not available on all operation systems. faulthandler.version_info is the module version as a tuple: (major, minor), faulthandler.__version__ is the module version a string (e.g. "2.0"). Changelog ========= Version 2.0 (2011-05-10) ------------------------ Major changes: * faulthandler is now part of Python 3.3 * enable() handles also the SIGABRT signal * Add exit option to dump_tracebacks_later(): if True, exit the program on timeout after dumping the traceback Other changes: * Change default value of the all_threads argument: dump all threads by default because under some rare conditions, it is not possible to get the current thread * Save/restore errno in signal handlers * dump_tracebacks_later() always dump all threads: remove all_threads option * Add faulthandler.__version__ attribute (module version as a string) * faulthandler.version is now a tuple * Rename: * dump_traceback_later() to dump_tracebacks_later() * cancel_dump_traceback_later() to cancel_dump_tracebacks_later() * sigsegv() to _sigsegv() * sigfpe() to _sigfpe() * sigbus() to _sigbus() * sigill() to _sigill() * register() and unregister() are no more available on Windows. They were useless: only SIGSEGV, SIGABRT and SIGILL can be handled by the application, and these signals can only be handled by enable(). * Add _fatal_error(), _read_null(), _sigabrt() and _stack_overflow() test functions * register() uses sigaction() SA_RESTART flag to try to not interrupt the current system call * The fault handler calls the previous signal handler, using sigaction() SA_NODEFER flag to call it immediatly * enable() raises an OSError if it was not possible to register a signal handler * Set module size to 0, instead of -1, to be able to unload the module with Python 3 * Fix a reference leak in dump_tracebacks_later() * Fix register() if it called twice with the same signal * Implement m_traverse for Python 3 to help the garbage collector * Move code from faulthandler/*.c to faulthandler.c and traceback.c: the code is simpler and it was easier to integrate faulthandler into Python 3.3 using one file (traceback.c already existed in Python) * register() uses a static list for all signals instead of reallocating memory each time a new signal is registered, because the list is shared with the signal handler which may be called anytime. Version 1.5 (2011-03-24) ------------------------ * Conform to the PEP 8: * Rename isenabled() to is_enabled() * Rename dumpbacktrace() to dump_traceback() * Rename dumpbacktrace_later() to dump_traceback_later() * Rename cancel_dumpbacktrace_later() to cancel_dump_traceback_later() * Limit strings to 100 characters * dump_traceback_later() signal handler doesn't clear its reference to the file, because Py_CLEAR() is not signal safe: you have to call explicitly cancel_dump_traceback_later() Version 1.4 (2011-02-14) ------------------------ * Add register() and unregister() functions * Add optional all_threads argument to enable() * Limit the backtrace to 100 threads * Allocate an alternative stack for the fatal signal handler to be able to display a backtrace on a stack overflow (define HAVE_SIGALTSTACK). Not available on Windows. Version 1.3 (2011-01-31) ------------------------ * Don't compile dumpbacktrace_later() and cancel_dumpbacktrace_later() on Windows because alarm() is missing Version 1.2 (2011-01-31) ------------------------ * Add dumpbacktrace_later() and cancel_dumpbacktrace_later() function * enable() and dumpbacktrace() get an optional file argument * Replace dumpbacktrace_threads() function by a new dumpbacktrace() argument: dumpbacktrace(all_threads=True) * enable() gets the file descriptor of sys.stderr instead of using the file descriptor 2 Version 1.1 (2011-01-03) ------------------------ * Disable the handler by default, because pkgutil may load the module and so enable the handler which is unexpected * Add dumpbacktrace() and dumpbacktrace_threads() functions * sigill() is available on Windows thanks to Martin's patch * Fix dump_ascii() for signed char type (eg. on FreeBSD) * Fix tests.py for Python 2.5 Version 1.0 (2010-12-24) ------------------------ * First public release Status ====== * 2011-01-31: Version 1.2 tested with Python 2.5, 2.6, 2.7, 3.1 and 3.2 on Debian Sid * 2010-12-24: Tested with Python 2.6, 3.1 and 3.2 on Debian Sid * 2010-12-24: Tested with Python 2.6 and 3.1 on Windows XP Similar projects ================ Python debuggers: * tipper: write the traceback of the current thread into a file on SIGUSR1 signal: http://pypi.python.org/pypi/tipper/ * crier: write the traceback of the current thread into a file (eg. /tmp/dump-) if a "request" file is created (eg. /tmp/crier-). Implemented using a thread. https://gist.github.com/737056 * Python WAD (Wrapped Application Debugger), not update since 2001: http://www.dabeaz.com/papers/Python2001/python.html Application fault handlers: * The GNU libc has a fault handler in debug/segfault.c * XEmacs has a fault handler displaying the Lisp traceback * RPy has a fault handler System-wide fault handlers: * Ubuntu uses Apport: https://wiki.ubuntu.com/Apport * The Linux kernel logs also segfaults into /var/log/kern.log (and /var/log/syslog). /proc/sys/kernel/core_pattern contols how coredumps are created. * Windows opens a popup on a fatal error asking if the error should be reported to Microsoft See also ======== * http://bugs.python.org/issue8863 (may 2010): Display Python backtrace on SIGSEGV, SIGFPE and fatal error * http://bugs.python.org/issue3999 (sept. 2008): Real segmentation fault handler Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Natural Language :: English Classifier: Programming Language :: C Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Debuggers Classifier: Topic :: Software Development :: Libraries :: Python Modules faulthandler-2.0/COPYING0000644000175000017500000000272611525234030014103 0ustar haypohaypoCopyright 2010 Victor Stinner. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY VICTOR STINNER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL VICTOR STINNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Victor Stinner. faulthandler-2.0/traceback.c0000644000175000017500000001463311546717040015145 0ustar haypohaypo#include "Python.h" #include #define MAX_STRING_LENGTH 100 #define MAX_FRAME_DEPTH 100 #define MAX_NTHREADS 100 #if PY_MAJOR_VERSION >= 3 # define PYSTRING_CHECK PyUnicode_Check #else # define PYSTRING_CHECK PyString_Check #endif #define PUTS(fd, str) write(fd, str, strlen(str)) /* Reverse a string. For example, "abcd" becomes "dcba". This function is signal safe. */ static void reverse_string(char *text, const size_t len) { char tmp; size_t i, j; if (len == 0) return; for (i=0, j=len-1; i < j; i++, j--) { tmp = text[i]; text[i] = text[j]; text[j] = tmp; } } /* Format an integer in range [0; 999999] to decimal, and write it into the file fd. This function is signal safe. */ static void dump_decimal(int fd, int value) { char buffer[7]; int len; if (value < 0 || 999999 < value) return; len = 0; do { buffer[len] = '0' + (value % 10); value /= 10; len++; } while (value); reverse_string(buffer, len); write(fd, buffer, len); } /* Format an integer in range [0; 0xffffffff] to hexdecimal of 'width' digits, and write it into the file fd. This function is signal safe. */ static void dump_hexadecimal(int width, unsigned long value, int fd) { const char *hexdigits = "0123456789abcdef"; int len; char buffer[sizeof(unsigned long) * 2 + 1]; len = 0; do { buffer[len] = hexdigits[value & 15]; value >>= 4; len++; } while (len < width || value); reverse_string(buffer, len); write(fd, buffer, len); } /* Write an unicode object into the file fd using ascii+backslashreplace. This function is signal safe. */ static void dump_ascii(int fd, PyObject *text) { Py_ssize_t i, size; int truncated; #if PY_MAJOR_VERSION >= 3 Py_UNICODE *u; char c; size = PyUnicode_GET_SIZE(text); u = PyUnicode_AS_UNICODE(text); #else char *s; unsigned char c; size = PyString_GET_SIZE(text); s = PyString_AS_STRING(text); #endif if (MAX_STRING_LENGTH < size) { size = MAX_STRING_LENGTH; truncated = 1; } else truncated = 0; #if PY_MAJOR_VERSION >= 3 for (i=0; i < size; i++, u++) { if (*u < 128) { c = (char)*u; write(fd, &c, 1); } else if (*u < 256) { PUTS(fd, "\\x"); dump_hexadecimal(2, *u, fd); } else #ifdef Py_UNICODE_WIDE if (*u < 65536) #endif { PUTS(fd, "\\u"); dump_hexadecimal(4, *u, fd); #ifdef Py_UNICODE_WIDE } else { PUTS(fd, "\\U"); dump_hexadecimal(8, *u, fd); #endif } } #else for (i=0; i < size; i++, s++) { c = *s; if (c < 128) { write(fd, s, 1); } else { PUTS(fd, "\\x"); dump_hexadecimal(2, c, fd); } } #endif if (truncated) PUTS(fd, "..."); } /* Write a frame into the file fd: "File "xxx", line xxx in xxx". This function is signal safe. */ static void dump_frame(int fd, PyFrameObject *frame) { PyCodeObject *code; int lineno; code = frame->f_code; PUTS(fd, " File "); if (code != NULL && code->co_filename != NULL && PYSTRING_CHECK(code->co_filename)) { write(fd, "\"", 1); dump_ascii(fd, code->co_filename); write(fd, "\"", 1); } else { PUTS(fd, "???"); } #if (PY_MAJOR_VERSION <= 2 && PY_MINOR_VERSION < 7) \ || (PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION < 2) /* PyFrame_GetLineNumber() was introduced in Python 2.7.0 and 3.2.0 */ lineno = PyCode_Addr2Line(frame->f_code, frame->f_lasti); #else lineno = PyFrame_GetLineNumber(frame); #endif PUTS(fd, ", line "); dump_decimal(fd, lineno); PUTS(fd, " in "); if (code != NULL && code->co_name != NULL && PYSTRING_CHECK(code->co_name)) dump_ascii(fd, code->co_name); else PUTS(fd, "???"); write(fd, "\n", 1); } static void dump_traceback(int fd, PyThreadState *tstate, int write_header) { PyFrameObject *frame; unsigned int depth; if (write_header) PUTS(fd, "Traceback (most recent call first):\n"); frame = _PyThreadState_GetFrame(tstate); if (frame == NULL) return; depth = 0; while (frame != NULL) { if (MAX_FRAME_DEPTH <= depth) { PUTS(fd, " ...\n"); break; } if (!PyFrame_Check(frame)) break; dump_frame(fd, frame); frame = frame->f_back; depth++; } } /* Write the current Python traceback into the file 'fd': Traceback (most recent call first): File "xxx", line xxx in File "xxx", line xxx in ... File "xxx", line xxx in Write only the first MAX_FRAME_DEPTH frames. If the traceback is truncated, write the line " ...". This function is signal safe. */ void _Py_DumpTraceback(int fd, PyThreadState *tstate) { dump_traceback(fd, tstate, 1); } /* Write the thread identifier into the file 'fd': "Current thread 0xHHHH:\" if is_current is true, "Thread 0xHHHH:\n" otherwise. This function is signal safe. */ static void write_thread_id(int fd, PyThreadState *tstate, int is_current) { if (is_current) PUTS(fd, "Current thread 0x"); else PUTS(fd, "Thread 0x"); dump_hexadecimal(sizeof(long)*2, (unsigned long)tstate->thread_id, fd); PUTS(fd, ":\n"); } /* Dump the traceback of all threads. Return NULL on success, or an error message on error. This function is signal safe. */ const char* _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, PyThreadState *current_thread) { PyThreadState *tstate; unsigned int nthreads; tstate = PyInterpreterState_ThreadHead(interp); if (tstate == NULL) return "unable to get the thread head state"; /* Dump the traceback of each thread */ tstate = PyInterpreterState_ThreadHead(interp); nthreads = 0; do { if (nthreads != 0) write(fd, "\n", 1); if (nthreads >= MAX_NTHREADS) { PUTS(fd, "...\n"); break; } write_thread_id(fd, tstate, tstate == current_thread); dump_traceback(fd, tstate, 0); tstate = PyThreadState_Next(tstate); nthreads++; } while (tstate != NULL); return NULL; } faulthandler-2.0/MANIFEST.in0000644000175000017500000000013111547704323014605 0ustar haypohaypoinclude COPYING include MANIFEST.in include tests.py include faulthandler/faulthandler.h