diff -Nru faulthandler-1.3/debian/changelog faulthandler-2.0/debian/changelog --- faulthandler-1.3/debian/changelog 2011-02-08 16:42:10.000000000 +0000 +++ faulthandler-2.0/debian/changelog 2011-10-13 23:06:31.000000000 +0000 @@ -1,3 +1,19 @@ +faulthandler (2.0-1) unstable; urgency=low + + [ Miriam Ruiz ] + * New upstream release + * Fixed order of debhelper commands in debian/rules: moved dh_pysupport + before dh_strip -a + * Upgraded Standards-Version from 3.9.1 to 3.9.2 + * Added targets build-arch and build-indep to debian/rules + * Changed homepage in debian/control to + https://github.com/haypo/faulthandler/wiki/ + + [ Jakub Wilk ] + * Add Vcs-* fields. + + -- Miriam Ruiz Fri, 14 Oct 2011 00:52:14 +0200 + faulthandler (1.3-1) unstable; urgency=low * Initial release. Closes: #612363 diff -Nru faulthandler-1.3/debian/control faulthandler-2.0/debian/control --- faulthandler-1.3/debian/control 2011-02-08 01:23:51.000000000 +0000 +++ faulthandler-2.0/debian/control 2011-10-13 23:05:54.000000000 +0000 @@ -5,8 +5,10 @@ Uploaders: Miriam Ruiz Build-Depends: debhelper (>= 7), dh-buildinfo, quilt, python-all-dev, python-support, python-setuptools -Standards-Version: 3.9.1 -Homepage: http://pypi.python.org/pypi/faulthandler/ +Standards-Version: 3.9.2 +Homepage: https://github.com/haypo/faulthandler/wiki/ +Vcs-Svn: svn://svn.debian.org/python-modules/packages/faulthandler/trunk/ +Vcs-Browser: http://svn.debian.org/viewsvn/python-modules/packages/faulthandler/trunk/ Package: python-faulthandler Architecture: any diff -Nru faulthandler-1.3/debian/rules faulthandler-2.0/debian/rules --- faulthandler-1.3/debian/rules 2011-02-08 01:23:51.000000000 +0000 +++ faulthandler-2.0/debian/rules 2011-10-13 22:57:17.000000000 +0000 @@ -10,7 +10,11 @@ [ ! -d debian/patches ] || $(MAKE) -f /usr/share/quilt/quilt.make patch touch $@ -build: $(PYVERS:%=build-python%) +build-arch: $(PYVERS:%=build-python%) + +build-indep: + +build: build-arch build-indep build-python%: config-stamp dh_testdir @@ -47,10 +51,10 @@ dh_install -a dh_installman -a dh_link -a + dh_pysupport -a dh_strip -a dh_compress -a dh_fixperms -a - dh_pysupport -a [ ! -e /usr/bin/dh_buildinfo ] || dh_buildinfo -a dh_installdeb -a dh_shlibdeps -a @@ -59,4 +63,4 @@ dh_builddeb -a binary: binary-indep binary-arch -.PHONY: config build clean binary-indep binary-arch binary install +.PHONY: config build build-arch build-indep clean binary-indep binary-arch binary install diff -Nru faulthandler-1.3/faulthandler/backtrace.c faulthandler-2.0/faulthandler/backtrace.c --- faulthandler-1.3/faulthandler/backtrace.c 2011-01-30 23:24:40.000000000 +0000 +++ faulthandler-2.0/faulthandler/backtrace.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,345 +0,0 @@ -#include "faulthandler.h" -#include - -#if PY_MAJOR_VERSION >= 3 -# define PYSTRING_CHECK PyUnicode_Check -# define PYINT_CHECK PyLong_Check -# define PYINT_ASLONG PyLong_AsLong -#else -# define PYSTRING_CHECK PyString_Check -# define PYINT_CHECK PyInt_Check -# define PYINT_ASLONG PyInt_AsLong -#endif - -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 */ - -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 */ - -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 */ - -static void -dump_ascii(int fd, PyObject *text) -{ - Py_ssize_t i, size; -#if PY_MAJOR_VERSION >= 3 - Py_UNICODE *u; - char c; - - size = PyUnicode_GET_SIZE(text); - u = PyUnicode_AS_UNICODE(text); - 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 - char *s; - unsigned char c; - - size = PyString_GET_SIZE(text); - s = PyString_AS_STRING(text); - 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 -} - -/* Write a frame into the file fd: "File "xxx", line xxx in xxx" */ - -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); -} - -/* Write the current Python backtrace 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 " ...". - */ - -void -faulthandler_dump_backtrace(int fd, PyThreadState *tstate, int write_header) -{ - PyFrameObject *frame; - unsigned int depth; - - frame = _PyThreadState_GetFrame(tstate); - if (frame == NULL) - return; - - if (write_header) - PUTS(fd, "Traceback (most recent call first):\n"); - 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++; - } -} - -static void -write_thread_id(int fd, PyThreadState *tstate, - unsigned int local_id, int is_current) -{ - if (is_current) - PUTS(fd, "Current thread #"); - else - PUTS(fd, "Thread #"); - dump_decimal(fd, local_id); - PUTS(fd, " (0x"); - dump_hexadecimal(sizeof(long)*2, (unsigned long)tstate->thread_id, fd); - PUTS(fd, "):\n"); -} - -/* - * Dump the backtrace of all threads. - * - * Return NULL on success, or an error message on error. - */ -const char* -faulthandler_dump_backtrace_threads(int fd, PyThreadState *current_thread) -{ - PyInterpreterState *interp; - PyThreadState *tstate; - int newline; - unsigned int local_id; - - /* Get the current interpreter from the current thread */ - interp = current_thread->interp; - if (interp == NULL) - return "unable to get the interpreter"; - - tstate = PyInterpreterState_ThreadHead(interp); - if (tstate == NULL) - return "unable to get the thread head state"; - - /* Count the number of threads */ - local_id = 0; - do - { - local_id++; - tstate = PyThreadState_Next(tstate); - } while (tstate != NULL); - - /* Dump the backtrace of each thread */ - tstate = PyInterpreterState_ThreadHead(interp); - newline = 0; - do - { - if (newline) - write(fd, "\n", 1); - else - newline = 1; - write_thread_id(fd, tstate, local_id, tstate == current_thread); - faulthandler_dump_backtrace(fd, tstate, 0); - tstate = PyThreadState_Next(tstate); - local_id--; - } while (tstate != NULL); - - return NULL; -} - -int -faulthandler_get_fileno(PyObject *file) -{ - PyObject *result; - long fd_long; - int fd; - - result = PyObject_CallMethod(file, "fileno", ""); - if (result == NULL) - return -1; - - 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 -1; - } - - result = PyObject_CallMethod(file, "flush", ""); - if (result != NULL) - Py_DECREF(result); - else { - /* ignore flush() error */ - PyErr_Clear(); - } - return fd; -} - -PyObject* -faulthandler_dump_backtrace_py(PyObject *self, - PyObject *args, PyObject *kwargs) -{ - static char *kwlist[] = {"file", "all_threads", NULL}; - PyObject *file = NULL; - int all_threads = 0; - PyThreadState *tstate; - const char *errmsg; - int fd; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|Oi:dump_backtrace", kwlist, - &file, &all_threads)) - return NULL; - - if (file == NULL) { - file = PySys_GetObject("stderr"); - if (file == NULL) { - PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr"); - return NULL; - } - } - - fd = faulthandler_get_fileno(file); - if (fd == -1) - return NULL; - - /* The caller holds the GIL and so PyThreadState_Get() can be used */ - tstate = PyThreadState_Get(); - if (tstate == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "unable to get the current thread state"); - return NULL; - } - - if (all_threads) { - errmsg = faulthandler_dump_backtrace_threads(fd, tstate); - if (errmsg != NULL) { - PyErr_SetString(PyExc_RuntimeError, errmsg); - return NULL; - } - } - else { - faulthandler_dump_backtrace(fd, tstate, 1); - } - Py_RETURN_NONE; -} - diff -Nru faulthandler-1.3/faulthandler/faulthandler.h faulthandler-2.0/faulthandler/faulthandler.h --- faulthandler-1.3/faulthandler/faulthandler.h 2011-01-30 23:57:44.000000000 +0000 +++ faulthandler-2.0/faulthandler/faulthandler.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,58 +0,0 @@ -#ifndef FAULTHANDLER_HEADER -#define FAULTHANDLER_HEADER - -#include "Python.h" -#include - -#ifdef SIGALRM -# define FAULTHANDLER_LATER -#endif - -#define MAX_FRAME_DEPTH 100 - -#define PUTS(fd, str) write(fd, str, strlen(str)) - -extern int faulthandler_enabled; - -void faulthandler_init(void); - -void faulthandler_fatal_error( - int signum); - -int faulthandler_get_fileno(PyObject *file); - -PyObject* faulthandler_enable(PyObject *self, - PyObject *args); -PyObject* faulthandler_disable_py(PyObject *self); -PyObject* faulthandler_isenabled(PyObject *self); - -void faulthandler_dump_backtrace(int fd, PyThreadState *tstate, int write_header); -const char* faulthandler_dump_backtrace_threads( - int fd, - PyThreadState *current_thread); - -PyObject* faulthandler_dump_backtrace_py(PyObject *self, - PyObject *args, - PyObject *kwargs); - -#ifdef FAULTHANDLER_LATER -PyObject* faulthandler_dumpbacktrace_later(PyObject *self, - PyObject *args, - PyObject *kwargs); -PyObject* faulthandler_cancel_dumpbacktrace_later_py(PyObject *self); -void faulthandler_cancel_dumpbacktrace_later(void); -#endif - -PyObject* faulthandler_sigsegv(PyObject *self, PyObject *args); -PyObject* faulthandler_sigfpe(PyObject *self, PyObject *args); - -#if defined(SIGBUS) -PyObject* faulthandler_sigbus(PyObject *self, PyObject *args); -#endif - -#if defined(SIGILL) -PyObject* faulthandler_sigill(PyObject *self, PyObject *args); -#endif - -#endif - diff -Nru faulthandler-1.3/faulthandler/handler.c faulthandler-2.0/faulthandler/handler.c --- faulthandler-1.3/faulthandler/handler.c 2011-01-30 23:50:10.000000000 +0000 +++ faulthandler-2.0/faulthandler/handler.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,350 +0,0 @@ -#include "faulthandler.h" -#include - -/* Forward */ -static void faulthandler_unload(void); -static void faulthandler_disable(void); - -#ifdef HAVE_SIGACTION -typedef struct sigaction _Py_sighandler_t; -#else -typedef PyOS_sighandler_t _Py_sighandler_t; -#endif - -#ifdef HAVE_SIGALTSTACK -static stack_t stack; -#endif - -int faulthandler_enabled = 0; - -static PyObject *fatal_error_file = NULL; -/* fileno(stderr)=2: the value is replaced in faulthandler_enable() */ -static int fatal_error_fd = 2; - -typedef struct { - int signum; - int enabled; - const char* name; - _Py_sighandler_t previous; -} fault_handler_t; - -static struct { - PyObject *file; - int fd; - int delay; - int repeat; - int all_threads; -} fault_alarm; - -static int fault_signals[] = { -#ifdef SIGBUS - SIGBUS, -#endif -#ifdef SIGILL - SIGILL, -#endif - SIGFPE, - /* define SIGSEGV at the end to make it the default choice if searching the - handler fails in faulthandler_fatal_error() */ - SIGSEGV -}; -#define NFAULT_SIGNALS (sizeof(fault_signals) / sizeof(fault_signals[0])) -static fault_handler_t fault_handlers[NFAULT_SIGNALS]; - -/* Fault handler: display the current Python backtrace and restore the previous - handler. It should only use signal-safe functions. The previous handler will - be called when the fault handler exits, because the fault will occur - again. */ - -void -faulthandler_fatal_error(int signum) -{ - const int fd = fatal_error_fd; - unsigned int i; - fault_handler_t *handler; - PyThreadState *tstate; - - /* restore the previous handler */ - for (i=0; i < NFAULT_SIGNALS; i++) { - handler = &fault_handlers[i]; - if (handler->signum == signum) - break; - } -#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"); - - /* SIGSEGV, SIGFPE, 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(); - if (tstate == NULL) - return; - - faulthandler_dump_backtrace(fd, tstate, 1); -} - -PyObject* -faulthandler_enable(PyObject *self, PyObject *args) -{ - PyObject *file = NULL; - unsigned int i; - fault_handler_t *handler; -#ifdef HAVE_SIGACTION - struct sigaction action; - int err; -#endif - - if (!PyArg_ParseTuple(args, "|O:enable", &file)) - return NULL; - - if (file == NULL) { - file = PySys_GetObject("stderr"); - if (file == NULL) { - PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr"); - return NULL; - } - } - - Py_XDECREF(fatal_error_file); - Py_INCREF(file); - fatal_error_file = file; - fatal_error_fd = faulthandler_get_fileno(file); - if (fatal_error_fd == -1) - return NULL; - - if (!faulthandler_enabled) { - faulthandler_enabled = 1; - - for (i=0; i < NFAULT_SIGNALS; i++) { - handler = &fault_handlers[i]; -#ifdef HAVE_SIGACTION - action.sa_handler = faulthandler_fatal_error; - sigemptyset(&action.sa_mask); - action.sa_flags = 0; -#ifdef HAVE_SIGALTSTACK - if (stack.ss_sp != NULL) - action.sa_flags |= SA_ONSTACK; -#endif - err = sigaction(handler->signum, &action, &handler->previous); - if (!err) - handler->enabled = 1; -#else - handler->previous = signal(handler->signum, - faulthandler_fatal_error); - if (handler->previous != SIG_ERR) - handler->enabled = 1; -#endif - } - } - Py_RETURN_NONE; -} - -static void -faulthandler_disable() -{ - unsigned int i; - fault_handler_t *handler; - - Py_CLEAR(fatal_error_file); - - if (faulthandler_enabled) { - for (i=0; i < NFAULT_SIGNALS; i++) { - handler = &fault_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; - } - } - faulthandler_enabled = 0; -} - -PyObject* -faulthandler_disable_py(PyObject *self) -{ - faulthandler_disable(); - Py_RETURN_NONE; -} - -PyObject* -faulthandler_isenabled(PyObject *self) -{ - return PyBool_FromLong(faulthandler_enabled); -} - -#ifdef FAULTHANDLER_LATER -/* - * Handler of the SIGALRM signal: dump the backtrace 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. - */ -static void -faulthandler_alarm(int signum) -{ - int ok; - PyThreadState *tstate; - - /* 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(); - if (tstate == NULL) { - /* unable to get the current thread, do nothing */ - return; - } - - if (fault_alarm.all_threads) { - const char* errmsg; - - errmsg = faulthandler_dump_backtrace_threads(fault_alarm.fd, tstate); - ok = (errmsg == NULL); - } - else { - faulthandler_dump_backtrace(fault_alarm.fd, tstate, 1); - ok = 1; - } - - if (ok && fault_alarm.repeat) - alarm(fault_alarm.delay); - else - faulthandler_cancel_dumpbacktrace_later(); -} - -PyObject* -faulthandler_dumpbacktrace_later(PyObject *self, PyObject *args, PyObject *kwargs) -{ - static char *kwlist[] = {"delay", "repeat", "file", "all_threads", NULL}; - int delay; - PyOS_sighandler_t previous; - int repeat = 0; - PyObject *file = NULL; - int all_threads = 0; - int fd; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "i|iOi:dump_backtrace_later", kwlist, - &delay, &repeat, &file, &all_threads)) - return NULL; - if (delay <= 0) { - PyErr_SetString(PyExc_ValueError, "delay must be greater than 0"); - return NULL; - } - - if (file == NULL || file == Py_None) { - file = PySys_GetObject("stderr"); - if (file == NULL) { - PyErr_SetString(PyExc_RuntimeError, "unable to get sys.stderr"); - return NULL; - } - } - - fd = faulthandler_get_fileno(file); - if (fd == -1) - return NULL; - - previous = signal(SIGALRM, faulthandler_alarm); - if (previous == SIG_ERR) { - PyErr_SetString(PyExc_RuntimeError, "unable to set SIGALRM handler"); - return NULL; - } - - Py_INCREF(file); - fault_alarm.file = file; - fault_alarm.fd = fd; - fault_alarm.delay = delay; - fault_alarm.repeat = repeat; - fault_alarm.all_threads = all_threads; - - alarm(delay); - - Py_RETURN_NONE; -} - -void -faulthandler_cancel_dumpbacktrace_later() -{ - Py_CLEAR(fault_alarm.file); - alarm(0); -} - -PyObject* -faulthandler_cancel_dumpbacktrace_later_py(PyObject *self) -{ - faulthandler_cancel_dumpbacktrace_later(); - Py_RETURN_NONE; -} -#endif - -void -faulthandler_init() -{ - unsigned int i; - fault_handler_t *handler; - - faulthandler_enabled = 0; - - for (i=0; i < NFAULT_SIGNALS; i++) { - handler = &fault_handlers[i]; - handler->signum = fault_signals[i]; - handler->enabled = 0; - if (handler->signum == SIGFPE) - handler->name = "Floating point exception"; -#ifdef SIGBUS - else if (handler->signum == SIGBUS) - handler->name = "Bus error"; -#endif -#ifdef SIGILL - else if (handler->signum == SIGILL) - handler->name = "Illegal instruction"; -#endif - else - handler->name = "Segmentation fault"; - } - -#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 = SS_ONSTACK; - stack.ss_size = SIGSTKSZ; - stack.ss_sp = PyMem_Malloc(stack.ss_size); - if (stack.ss_sp != NULL) { - (void)sigaltstack(&stack, NULL); - } -#endif - - (void)Py_AtExit(faulthandler_unload); -} - -static void -faulthandler_unload(void) -{ -#ifdef FAULTHANDLER_LATER - faulthandler_cancel_dumpbacktrace_later(); -#endif - faulthandler_disable(); -#ifdef HAVE_SIGALTSTACK - if (stack.ss_sp != NULL) { - PyMem_Free(stack.ss_sp); - stack.ss_sp = NULL; - } -#endif -} - diff -Nru faulthandler-1.3/faulthandler/module.c faulthandler-2.0/faulthandler/module.c --- faulthandler-1.3/faulthandler/module.c 2011-01-30 23:57:22.000000000 +0000 +++ faulthandler-2.0/faulthandler/module.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,104 +0,0 @@ -/* - * Fault handler for SIGSEGV, SIGFPE, SIGBUS and SIGILL signals: display the - * Python backtrace 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. - */ - -#include "faulthandler.h" - -#define VERSION 0x103 - -PyDoc_STRVAR(module_doc, -"faulthandler module."); - -static PyMethodDef module_methods[] = { - {"enable", faulthandler_enable, METH_VARARGS, - PyDoc_STR("enable(file=sys.stderr): enable the fault handler")}, - {"disable", (PyCFunction)faulthandler_disable_py, METH_NOARGS, - PyDoc_STR("disable(): disable the fault handler")}, - {"isenabled", (PyCFunction)faulthandler_isenabled, METH_NOARGS, - PyDoc_STR("isenabled()->bool: check if the handler is enabled")}, - {"dumpbacktrace", - (PyCFunction)faulthandler_dump_backtrace_py, METH_VARARGS|METH_KEYWORDS, - PyDoc_STR("dumpbacktrace(file=sys.stderr, all_threads=False): " - "dump the backtrace of the current thread, or of all threads " - "if all_threads is True, into file")}, -#ifdef FAULTHANDLER_LATER - {"dumpbacktrace_later", - (PyCFunction)faulthandler_dumpbacktrace_later, METH_VARARGS|METH_KEYWORDS, - PyDoc_STR("dumpbacktrace_later(delay, repeat=False, file=sys.stderr, all_threads=False): " - "dump the backtrace of the current thread, or of all threads " - "if all_threads is True, in delay seconds, or each delay " - "seconds if repeat is True.")}, - {"cancel_dumpbacktrace_later", - (PyCFunction)faulthandler_cancel_dumpbacktrace_later_py, METH_NOARGS, - PyDoc_STR("cancel_dumpbacktrace_later(): cancel the previous call " - "to dumpbacktrace_later().")}, -#endif - {"sigsegv", faulthandler_sigsegv, METH_VARARGS, - PyDoc_STR("sigsegv(release_gil=False): raise a SIGSEGV 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 - {NULL, NULL} /* terminator */ -}; - -#if PY_MAJOR_VERSION >= 3 -static struct PyModuleDef module_def = { - PyModuleDef_HEAD_INIT, - "faulthandler", - module_doc, - -1, - module_methods, - NULL, - NULL, - NULL, - NULL -}; -#endif - - -PyMODINIT_FUNC -#if PY_MAJOR_VERSION >= 3 -PyInit_faulthandler(void) -#else -initfaulthandler(void) -#endif -{ - PyObject *m, *version; - -#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 - } - - faulthandler_init(); - -#if PY_MAJOR_VERSION >= 3 - version = PyLong_FromLong(VERSION); -#else - version = PyInt_FromLong(VERSION); -#endif - PyModule_AddObject(m, "version", version); - -#if PY_MAJOR_VERSION >= 3 - return m; -#endif -} - diff -Nru faulthandler-1.3/faulthandler/tests.c faulthandler-2.0/faulthandler/tests.c --- faulthandler-1.3/faulthandler/tests.c 2010-12-24 12:51:50.000000000 +0000 +++ faulthandler-2.0/faulthandler/tests.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,47 +0,0 @@ -#include "faulthandler.h" - -PyObject * -faulthandler_sigsegv(PyObject *self, PyObject *args) -{ - int *x = NULL, y; - int release_gil = 0; - if (!PyArg_ParseTuple(args, "|i", &release_gil)) - return NULL; - if (release_gil) { - Py_BEGIN_ALLOW_THREADS - y = *x; - Py_END_ALLOW_THREADS - } else - y = *x; - return PyLong_FromLong(y); - -} - -PyObject * -faulthandler_sigfpe(PyObject *self, PyObject *args) -{ - int x = 1, y = 0, z; - z = x / y; - return PyLong_FromLong(z); -} - -#ifdef SIGBUS -PyObject * -faulthandler_sigbus(PyObject *self, PyObject *args) -{ - while(1) - raise(SIGBUS); - Py_RETURN_NONE; -} -#endif - -#ifdef SIGILL -PyObject * -faulthandler_sigill(PyObject *self, PyObject *args) -{ - while(1) - raise(SIGILL); - Py_RETURN_NONE; -} -#endif - diff -Nru faulthandler-1.3/faulthandler.c faulthandler-2.0/faulthandler.c --- faulthandler-1.3/faulthandler.c 1970-01-01 00:00:00.000000000 +0000 +++ faulthandler-2.0/faulthandler.c 2011-05-09 23:28:00.000000000 +0000 @@ -0,0 +1,1095 @@ +/* + * 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 +} diff -Nru faulthandler-1.3/PKG-INFO faulthandler-2.0/PKG-INFO --- faulthandler-1.3/PKG-INFO 2011-01-31 00:07:20.000000000 +0000 +++ faulthandler-2.0/PKG-INFO 2011-05-09 23:33:35.000000000 +0000 @@ -1,7 +1,7 @@ Metadata-Version: 1.0 Name: faulthandler -Version: 1.3 -Summary: Display the Python backtrace on a crash +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 @@ -10,25 +10,33 @@ Fault handler +++++++++++++ - Fault handler for SIGSEGV, SIGFPE, SIGBUS and SIGILL signals: display the - Python backtrace 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. + 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 backtrace is limited: it only supports ASCII encoding (use the - backslashreplace error handler for non-ASCII characters) and it doesn't print - the source code in the backtrace (only the filename, the function name and the - line number). - - The Python backtrace is written to the standard error stream. Start your - graphical applications in a terminal and run your server in foreground to see - the backtrace. + 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(). - Website: https://github.com/haypo/faulthandler/wiki/ + 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 @@ -39,7 +47,7 @@ $ python >>> import faulthandler >>> faulthandler.enable() - >>> faulthandler.sigsegv() + >>> faulthandler._sigsegv() Fatal Python error: Segmentation fault Traceback (most recent call first): @@ -66,49 +74,147 @@ 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): enable the fault handler + * enable(file=sys.stderr, all_threads=False): enable the fault handler * disable(): disable the fault handler - * isenabled(): get the status of the fault handler + * is_enabled(): get the status of the fault handler - Dump the current backtrace: + Dump the current traceback: - * dumpbacktrace(file=sys.stderr, all_threads=False): dump backtrace of the + * 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 - * dumpbacktrace_later(delay, repeat=False, file=sys.stderr, all_threads=False): - dump the backtrace of the current thread, or of all threads if all_threads - is True, in delay seconds, or each delay seconds if repeat is True. If the - function is called twice, the new call replaces previous parameters. Call - cancel_dumpbacktrace_later() to call the next request. - * cancel_dumpbacktrace_later(): cancel the previous call to - dumpbacktrace_later() + * 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() - dumpbacktrace_later() is implemented using the SIGALRM signal and the alarm() + 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 dumpbacktrace_later() keep an internal reference to the output - file. Use disable() and cancel_dumpbacktrace_later() to clear this reference. + 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: - * sigbus(): raise a SIGBUS signal (Bus error) - * sigfpe(): raise a SIGFPE signal (Floating point exception), do a division by + * _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 + * _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. - sigbus() and sigill() are not available on all operation systems. + register(), unregister(), sigbus() and sigill() are not available on all + operation systems. - The version can be read in the "version" attribute: use "version >> 8" to get - the major version, and "version & 255" to get the minor version. + 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) ------------------------ @@ -155,9 +261,9 @@ Python debuggers: - * tipper: write the backtrace of the current thread into a file on SIGUSR1 + * tipper: write the traceback of the current thread into a file on SIGUSR1 signal: http://pypi.python.org/pypi/tipper/ - * crier: write the backtrace of the current thread into a file + * 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: @@ -166,7 +272,7 @@ Application fault handlers: * The GNU libc has a fault handler in debug/segfault.c - * XEmacs has a fault handler displaying the Lisp backtrace + * XEmacs has a fault handler displaying the Lisp traceback * RPy has a fault handler System-wide fault handlers: @@ -184,7 +290,7 @@ * http://bugs.python.org/issue8863 (may 2010): Display Python backtrace on SIGSEGV, SIGFPE and fatal error - * http://bugs.python.org/issue3999 (sept. 2009): + * http://bugs.python.org/issue3999 (sept. 2008): Real segmentation fault handler Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable @@ -194,5 +300,6 @@ 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 diff -Nru faulthandler-1.3/README faulthandler-2.0/README --- faulthandler-1.3/README 2011-01-31 00:04:53.000000000 +0000 +++ faulthandler-2.0/README 2011-05-09 23:32:25.000000000 +0000 @@ -2,25 +2,33 @@ Fault handler +++++++++++++ -Fault handler for SIGSEGV, SIGFPE, SIGBUS and SIGILL signals: display the -Python backtrace 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. +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 backtrace is limited: it only supports ASCII encoding (use the -backslashreplace error handler for non-ASCII characters) and it doesn't print -the source code in the backtrace (only the filename, the function name and the -line number). - -The Python backtrace is written to the standard error stream. Start your -graphical applications in a terminal and run your server in foreground to see -the backtrace. +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(). -Website: https://github.com/haypo/faulthandler/wiki/ +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 @@ -31,7 +39,7 @@ $ python >>> import faulthandler >>> faulthandler.enable() - >>> faulthandler.sigsegv() + >>> faulthandler._sigsegv() Fatal Python error: Segmentation fault Traceback (most recent call first): @@ -58,49 +66,147 @@ 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): enable the fault handler + * enable(file=sys.stderr, all_threads=False): enable the fault handler * disable(): disable the fault handler - * isenabled(): get the status of the fault handler + * is_enabled(): get the status of the fault handler -Dump the current backtrace: +Dump the current traceback: - * dumpbacktrace(file=sys.stderr, all_threads=False): dump backtrace of the + * 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 - * dumpbacktrace_later(delay, repeat=False, file=sys.stderr, all_threads=False): - dump the backtrace of the current thread, or of all threads if all_threads - is True, in delay seconds, or each delay seconds if repeat is True. If the - function is called twice, the new call replaces previous parameters. Call - cancel_dumpbacktrace_later() to call the next request. - * cancel_dumpbacktrace_later(): cancel the previous call to - dumpbacktrace_later() + * 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() -dumpbacktrace_later() is implemented using the SIGALRM signal and the alarm() +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 dumpbacktrace_later() keep an internal reference to the output -file. Use disable() and cancel_dumpbacktrace_later() to clear this reference. +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: - * sigbus(): raise a SIGBUS signal (Bus error) - * sigfpe(): raise a SIGFPE signal (Floating point exception), do a division by + * _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 + * _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. -sigbus() and sigill() are not available on all operation systems. +register(), unregister(), sigbus() and sigill() are not available on all +operation systems. -The version can be read in the "version" attribute: use "version >> 8" to get -the major version, and "version & 255" to get the minor version. +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) ------------------------ @@ -147,9 +253,9 @@ Python debuggers: - * tipper: write the backtrace of the current thread into a file on SIGUSR1 + * tipper: write the traceback of the current thread into a file on SIGUSR1 signal: http://pypi.python.org/pypi/tipper/ - * crier: write the backtrace of the current thread into a file + * 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: @@ -158,7 +264,7 @@ Application fault handlers: * The GNU libc has a fault handler in debug/segfault.c - * XEmacs has a fault handler displaying the Lisp backtrace + * XEmacs has a fault handler displaying the Lisp traceback * RPy has a fault handler System-wide fault handlers: @@ -176,6 +282,6 @@ * http://bugs.python.org/issue8863 (may 2010): Display Python backtrace on SIGSEGV, SIGFPE and fatal error - * http://bugs.python.org/issue3999 (sept. 2009): + * http://bugs.python.org/issue3999 (sept. 2008): Real segmentation fault handler diff -Nru faulthandler-1.3/setup.py faulthandler-2.0/setup.py --- faulthandler-1.3/setup.py 2011-01-30 23:53:22.000000000 +0000 +++ faulthandler-2.0/setup.py 2011-05-09 23:31:58.000000000 +0000 @@ -1,14 +1,15 @@ #!/usr/bin/env python # Todo list to prepare a release: -# - run python tests.py +# - run ./run_tests.py # - run tests on FreeBSD and Windows -# - set VERSION in faulthandler/module.c +# - 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 -# - git push --tags origin +# - 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 @@ -16,7 +17,7 @@ # - update the website # # After the release: -# - increment VERSION in faulthandler/module.c +# - 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 @@ -25,10 +26,15 @@ 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 = "1.3" +VERSION = "2.0" -FILENAMES = ('backtrace.c', 'handler.c', 'module.c', 'tests.c') +FILES = ['faulthandler.c', 'traceback.c'] CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', @@ -38,6 +44,7 @@ 'Natural Language :: English', 'Programming Language :: C', 'Programming Language :: Python', + 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Debuggers', 'Topic :: Software Development :: Libraries :: Python Modules', ] @@ -49,14 +56,12 @@ 'name': "faulthandler", 'version': VERSION, 'license': "BSD (2-clause)", - 'description': 'Display the Python backtrace on a crash', + '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', - [path_join('faulthandler', filename) for filename in FILENAMES] - )], + 'ext_modules': [Extension('faulthandler', FILES)], 'classifiers': CLASSIFIERS, } diff -Nru faulthandler-1.3/tests.py faulthandler-2.0/tests.py --- faulthandler-1.3/tests.py 2011-01-30 23:54:49.000000000 +0000 +++ faulthandler-2.0/tests.py 2011-05-09 13:47:19.000000000 +0000 @@ -1,11 +1,22 @@ from __future__ import with_statement -import faulthandler; faulthandler.disable() +from contextlib import contextmanager +import datetime +import faulthandler import os +import re +import signal import subprocess import sys -import unittest -import re import tempfile +import unittest + +try: + import threading + HAVE_THREADS = True +except ImportError: + HAVE_THREADS = False + +TIMEOUT = 1 Py_REF_DEBUG = hasattr(sys, 'gettotalrefcount') @@ -24,307 +35,501 @@ return wrapper return decorator -def decode_output(output): - return output.decode('ascii', 'backslashreplace') - -def read_file(filename): - with open(filename, "rb") as fp: - output = fp.read() - return decode_output(output) +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): - code = '\n'.join(code) + 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], - stderr=subprocess.PIPE) + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **options) stdout, stderr = process.communicate() - output = decode_output(stderr) - if Py_REF_DEBUG: - output = re.sub(r"\[\d+ refs\]\r?\n?$", "", output) - return output - - def check_enabled(self, code, line_number, name, filename=None): - line = ' File "", line %s in ' % line_number - expected = [ - 'Fatal Python error: ' + name, - '', - 'Traceback (most recent call first):', - line] - output = self.get_output(code) + exitcode = process.wait() + output = stdout.decode('ascii', 'backslashreplace') + output = re.sub(r"\[\d+ refs\]\r?\n?$", "", output) if filename: - output = read_file(filename) - lines = output.splitlines() - self.assertEqual(lines, expected) + 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_enabled( - ("import faulthandler; faulthandler.enable()", - "faulthandler.sigsegv()"), - 2, + 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_enabled( - ("import faulthandler; faulthandler.enable(); " - "faulthandler.sigfpe()",), - 1, + self.check_fatal_error(""" +import faulthandler +faulthandler.enable() +faulthandler._sigfpe() +""".strip(), + 3, 'Floating point exception') - @skipIf(not hasattr(faulthandler, 'sigbus'), - "need faulthandler.sigbus()") + @skipIf(not hasattr(faulthandler, '_sigbus'), + "need faulthandler._sigbus()") def test_sigbus(self): - self.check_enabled( - ("import faulthandler; faulthandler.enable()", - "faulthandler.sigbus()"), - 2, + self.check_fatal_error(""" +import faulthandler +faulthandler.enable() +faulthandler._sigbus() +""".strip(), + 3, 'Bus error') - @skipIf(not hasattr(faulthandler, 'sigill'), - "need faulthandler.sigill()") + @skipIf(not hasattr(faulthandler, '_sigill'), + "need faulthandler._sigill()") def test_sigill(self): - self.check_enabled( - ("import faulthandler; faulthandler.enable()", - "faulthandler.sigill()"), - 2, + 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_enabled( - ("import faulthandler; faulthandler.enable()", - "faulthandler.sigsegv(True)"), - 2, - 'Segmentation fault') + self.check_fatal_error(""" +import faulthandler +faulthandler.enable() +faulthandler._read_null(True) +""".strip(), + 3, + '(?:Segmentation fault|Bus error)') def test_enable_file(self): - with tempfile.NamedTemporaryFile() as f: - self.check_enabled( - ("from __future__ import with_statement", - "import faulthandler", - "output = open(%r, 'wb')" % f.name, - "faulthandler.enable(output)", - "faulthandler.sigsegv(True)"), - 5, - 'Segmentation fault', - filename=f.name) - - def check_disabled(self, *code): + 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 = self.get_output(code) + 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_disabled(self): - self.check_disabled( - "import faulthandler", - "faulthandler.sigsegv()") - - def test_enable_disable(self): - self.check_disabled( - "import faulthandler", - "faulthandler.enable()", - "faulthandler.disable()", - "faulthandler.sigsegv()") - - def test_isenabled(self): - self.assertFalse(faulthandler.isenabled()) - faulthandler.enable() - self.assertTrue(faulthandler.isenabled()) - faulthandler.disable() - self.assertFalse(faulthandler.isenabled()) - - def check_dumpbacktrace(self, filename): - code = ( - 'from __future__ import with_statement', - 'import faulthandler', - '', - 'def funcB():', - ' if %r:' % (bool(filename),), - ' with open(%r, "wb") as fp:' % (filename,), - ' faulthandler.dumpbacktrace(fp)', - ' else:', - ' faulthandler.dumpbacktrace()', - '', - 'def funcA():', - ' funcB()', - '', - 'funcA()', - ) + 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 = [ - 'Traceback (most recent call first):', + 'Current thread XXX:', ' File "", line %s in funcB' % lineno, ' File "", line 12 in funcA', ' File "", line 14 in ' ] - trace = self.get_output(code) - if filename: - trace = read_file(filename) - trace = trace.splitlines() + trace, exitcode = self.get_output(code, filename) self.assertEqual(trace, expected) + self.assertEqual(exitcode, 0) - def test_dumpbacktrace(self): - self.check_dumpbacktrace(None) - with tempfile.NamedTemporaryFile() as f: - self.check_dumpbacktrace(f.name) - - def check_dumpbacktrace_threads(self, filename): - output = self.get_output(( - 'from __future__ import with_statement', - 'import faulthandler', - 'from threading import Thread', - 'import time', - '', - 'def dump():', - ' if %r:' % (bool(filename),), - ' with open(%r, "wb") as fp:' % (filename,), - ' faulthandler.dumpbacktrace(fp, all_threads=True)', - ' else:', - ' faulthandler.dumpbacktrace(all_threads=True)', - '', - 'class Waiter(Thread):', - ' def __init__(self):', - ' Thread.__init__(self)', - ' self.stop = False', - '', - ' def run(self):', - ' while not self.stop:', - ' time.sleep(0.1)', - '', - 'waiter = Waiter()', - 'waiter.start()', - 'time.sleep(0.1)', - 'dump()', - 'waiter.stop = True', - 'waiter.join()', - )) - if filename: - output = read_file(filename) - # Normalize newlines for Windows - lines = '\n'.join(output.splitlines()) + 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 = '\n'.join(( - 'Thread #2 \\(0x[0-9a-f]+\\):', - ' File "", line 20 in run', - ' File ".*threading.py", line [0-9]+ in __?bootstrap_inner', - ' File ".*threading.py", line [0-9]+ in __?bootstrap', - '', - 'Current thread #1 \\(0x[0-9a-f]+\\):', - ' File "", line %s in dump' % lineno, - ' File "", line 25 in ', - )) - self.assertTrue(re.match(regex, lines), - "<<<%s>>> doesn't match" % lines) - - def test_dumpbacktrace_threads(self): - self.check_dumpbacktrace_threads(None) - with tempfile.NamedTemporaryFile() as tmp: - self.check_dumpbacktrace_threads(tmp.name) - - def _check_dumpbacktrace_later(self, repeat, cancel, - filename, all_threads): - code = ( - 'import faulthandler', - 'import time', - '', - 'def slow_function(repeat, cancel):', - ' if not repeat:', - ' loops = 2', - ' else:', - ' loops = 3', - ' dump = True', - ' for x in range(loops):', - ' a = time.time()', - ' time.sleep(2)', - ' b = time.time()', - ' diff = (b - a)', - ' if dump:', - ' # sleep() interrupted after 1 second', - ' assert diff < 2.0', - ' else:', - ' assert diff >= 2.0', - ' if repeat and cancel and 1 <= x:', - ' faulthandler.cancel_dumpbacktrace_later()', - ' dump = False', - ' cancel = False', - ' if not repeat:', - ' dump = False', - ' if repeat and (not cancel):', - ' faulthandler.cancel_dumpbacktrace_later()', - '', - 'repeat = %s' % repeat, - 'cancel = %s' % cancel, - 'if %s:' % bool(filename), - ' file = open(%r, "wb")' % filename, - 'else:', - ' file = None', - 'faulthandler.dumpbacktrace_later(1, ', - ' repeat=repeat, all_threads=%s, file=file)' % all_threads, - 'slow_function(repeat, cancel)', - 'if file is not None:', - ' file.close()', - ) - stderr = self.get_output(code) - if filename: - trace = read_file(filename) + 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: - trace = stderr - if all_threads: - trace = re.sub( - r'Current thread #1 \(0x[0-9a-f]+\)', - 'Current thread #1 (...)', - trace) - trace = trace.splitlines() + self.assertEqual(trace, '') + self.assertEqual(exitcode, 0) - if all_threads: - expected = ['Current thread #1 (...):'] + @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: - expected = ['Traceback (most recent call first):'] - expected.extend(( - ' File "", line 12 in slow_function', - ' File "", line 37 in ')) - if repeat: - if cancel: - expected *= 2 + 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: - expected *= 3 - self.assertEqual(trace, expected, - "%r != %r: repeat=%s, cancel=%s, use_filename=%s, all_threads=%s" - % (trace, expected, repeat, cancel, bool(filename), all_threads)) - - @skipIf(not hasattr(faulthandler, 'dumpbacktrace_later'), - 'need faulthandler.dumpbacktrace_later()') - def check_dumpbacktrace_later(self, repeat=False, cancel=False, - all_threads=False, filename=False): - if filename: - with tempfile.NamedTemporaryFile() as f: - self._check_dumpbacktrace_later(repeat, cancel, f.name, all_threads) + regex = 'Traceback \(most recent call first\):\n' + regex = expected_traceback(6, 17, regex) + self.assertRegex(trace, regex) else: - self._check_dumpbacktrace_later(repeat, cancel, None, all_threads) - - def test_dumpbacktrace_later(self): - self.check_dumpbacktrace_later() + self.assertEqual(trace, '') + if unregister: + self.assertNotEqual(exitcode, 0) + else: + self.assertEqual(exitcode, 0) - def test_dumpbacktrace_later_repeat(self): - self.check_dumpbacktrace_later(repeat=True) + def test_register(self): + self.check_register() - def test_dumpbacktrace_later_repeat_cancel(self): - self.check_dumpbacktrace_later(repeat=True, cancel=True) + def test_unregister(self): + self.check_register(unregister=True) - def test_dumpbacktrace_later_threads(self): - self.check_dumpbacktrace_later(all_threads=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) - def test_dumpbacktrace_later_file(self): - self.check_dumpbacktrace_later(filename=True) if __name__ == "__main__": unittest.main() - diff -Nru faulthandler-1.3/traceback.c faulthandler-2.0/traceback.c --- faulthandler-1.3/traceback.c 1970-01-01 00:00:00.000000000 +0000 +++ faulthandler-2.0/traceback.c 2011-04-05 22:56:32.000000000 +0000 @@ -0,0 +1,289 @@ +#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; +} +