faulthandler-2.4/0000775000175000017500000000000012413253577014325 5ustar haypohaypo00000000000000faulthandler-2.4/faulthandler.egg-info/0000775000175000017500000000000012413253577020470 5ustar haypohaypo00000000000000faulthandler-2.4/faulthandler.egg-info/SOURCES.txt0000664000175000017500000000034412413253574022352 0ustar haypohaypo00000000000000AUTHORS COPYING MANIFEST.in README TODO faulthandler.c setup.py tests.py traceback.c faulthandler.egg-info/PKG-INFO faulthandler.egg-info/SOURCES.txt faulthandler.egg-info/dependency_links.txt faulthandler.egg-info/top_level.txtfaulthandler-2.4/faulthandler.egg-info/dependency_links.txt0000664000175000017500000000000112413253573024532 0ustar haypohaypo00000000000000 faulthandler-2.4/faulthandler.egg-info/top_level.txt0000664000175000017500000000001512413253573023212 0ustar haypohaypo00000000000000faulthandler faulthandler-2.4/faulthandler.egg-info/PKG-INFO0000664000175000017500000000455112413253573021566 0ustar haypohaypo00000000000000Metadata-Version: 1.1 Name: faulthandler Version: 2.4 Summary: Display the Python traceback on a crash Home-page: http://faulthandler.readthedocs.org/ Author: Victor Stinner Author-email: victor.stinner@gmail.com License: BSD (2-clause) Description: +++++++++++++ Fault handler +++++++++++++ Fault handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals: display the Python traceback and restore the previous handler. Allocate an alternate stack for this handler, if sigaltstack() is available, to be able to allocate memory on the stack, even on stack overflow (not available on Windows). Import the module and call faulthandler.enable() to enable the fault handler. The fault handler is called on catastrophic cases and so it can only use signal-safe functions (eg. it doesn't allocate memory on the heap). That's why the traceback is limited: it only supports ASCII encoding (use the backslashreplace error handler for non-ASCII characters) and limits each string to 100 characters, doesn't print the source code in the traceback (only the filename, the function name and the line number), is limited to 100 frames and 100 threads. By default, the Python traceback is written to the standard error stream. Start your graphical applications in a terminal and run your server in foreground to see the traceback, or pass a file to faulthandler.enable(). faulthandler is implemented in C using signal handlers to be able to dump a traceback on a crash or when Python is blocked (eg. deadlock). Website: http://faulthandler.readthedocs.org/ faulthandler is part of Python since Python 3.3: http://docs.python.org/dev/library/faulthandler.html Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Natural Language :: English Classifier: Programming Language :: C Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Debuggers Classifier: Topic :: Software Development :: Libraries :: Python Modules faulthandler-2.4/faulthandler.c0000664000175000017500000007631212413243541017141 0ustar haypohaypo00000000000000/* * faulthandler module * * Written by Victor Stinner. */ #include "Python.h" #include "pythread.h" #include #ifdef MS_WINDOWS # include #endif #ifdef HAVE_SYS_RESOURCE_H # include #endif #define VERSION 0x204 /* 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 /* cast size_t to int because write() takes an int on Windows (anyway, the length is smaller than 30 characters) */ #define PUTS(fd, str) write(fd, str, (int)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; PyObject *file; int fd; int all_threads; int chain; _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 static void faulthandler_user(int signum); #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; } if (file == Py_None) { PyErr_SetString(PyExc_RuntimeError, "sys.stderr is None"); 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 for 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 explicitly call the previous handler, because the 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(signum, &handler->previous, NULL); #else (void)signal(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 are thus 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 explicitly call 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 immediately 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_traceback_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; PyThreadState *tstate; int fd; char *header; size_t header_len; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|iOi:dump_traceback_later", kwlist, &timeout, &repeat, &file, &exit)) return NULL; if (timeout <= 0) { PyErr_SetString(PyExc_ValueError, "timeout must be greater than 0"); return NULL; } tstate = get_thread_state(); if (tstate == NULL) 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.interp = tstate->interp; fault_alarm.exit = exit; fault_alarm.header = header; fault_alarm.header_len = header_len; alarm(timeout); Py_RETURN_NONE; } static PyObject* faulthandler_cancel_dump_traceback_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 static int faulthandler_register(int signum, int chain, _Py_sighandler_t *p_previous) { #ifdef HAVE_SIGACTION struct sigaction action; 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; if (chain) { /* 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 return sigaction(signum, &action, p_previous); #else _Py_sighandler_t previous; previous = signal(signum, faulthandler_user); if (p_previous != NULL) *p_previous = previous; return (previous == SIG_ERR); #endif } /* 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) _Py_DumpTraceback(user->fd, tstate); } #ifdef HAVE_SIGACTION if (user->chain) { (void)sigaction(signum, &user->previous, NULL); errno = save_errno; /* call the previous signal handler */ raise(signum); save_errno = errno; (void)faulthandler_register(signum, user->chain, NULL); errno = save_errno; } #else if (user->chain) { errno = save_errno; /* call the previous signal handler */ user->previous(signum); } #endif } 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_py(PyObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"signum", "file", "all_threads", "chain", NULL}; int signum; PyObject *file = NULL; int all_threads = 1; int chain = 0; int fd; user_signal_t *user; _Py_sighandler_t previous; PyThreadState *tstate; int err; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|Oii:register", kwlist, &signum, &file, &all_threads, &chain)) 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) { err = faulthandler_register(signum, chain, &previous); if (err) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } user->previous = previous; } Py_XDECREF(user->file); Py_INCREF(file); user->file = file; user->fd = fd; user->all_threads = all_threads; user->chain = chain; 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 void faulthandler_suppress_crash_report(void) { #ifdef MS_WINDOWS UINT mode; /* Configure Windows to not display the Windows Error Reporting dialog */ mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); SetErrorMode(mode | SEM_NOGPFAULTERRORBOX); #endif #ifdef HAVE_SYS_RESOURCE_H struct rlimit rl; /* Disable creation of core dump */ if (getrlimit(RLIMIT_CORE, &rl) != 0) { rl.rlim_cur = 0; setrlimit(RLIMIT_CORE, &rl); } #endif #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 } static PyObject * faulthandler_read_null(PyObject *self, PyObject *args) { volatile int *x; volatile int y; faulthandler_suppress_crash_report(); x = NULL; y = *x; return PyLong_FromLong(y); } static void faulthandler_raise_sigsegv(void) { faulthandler_suppress_crash_report(); #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 explicitly calling 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 } static PyObject * faulthandler_sigsegv(PyObject *self, PyObject *args) { int release_gil = 0; if (!PyArg_ParseTuple(args, "|i:_read_null", &release_gil)) return NULL; if (release_gil) { Py_BEGIN_ALLOW_THREADS faulthandler_raise_sigsegv(); Py_END_ALLOW_THREADS } else { faulthandler_raise_sigsegv(); } 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; faulthandler_suppress_crash_report(); z = x / y; /* If the division by zero didn't raise a SIGFPE (e.g. on PowerPC), raise it manually. */ raise(SIGFPE); /* This line is never reached, but we pretend to make something with z to silence a compiler warning. */ return PyLong_FromLong(z); } static PyObject * faulthandler_sigabrt(PyObject *self, PyObject *args) { faulthandler_suppress_crash_report(); abort(); Py_RETURN_NONE; } static PyObject * faulthandler_raise_signal(PyObject *self, PyObject *args) { int signum, err; if (PyArg_ParseTuple(args, "i:raise_signal", &signum) < 0) return NULL; faulthandler_suppress_crash_report(); err = raise(signum); if (err) return PyErr_SetFromErrno(PyExc_OSError); if (PyErr_CheckSignals() < 0) return NULL; Py_RETURN_NONE; } 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) static 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; char *sp = (char *)&depth, *stop; faulthandler_suppress_crash_report(); 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_traceback_later", (PyCFunction)faulthandler_dump_traceback_later, METH_VARARGS|METH_KEYWORDS, PyDoc_STR("dump_traceback_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_traceback_later", (PyCFunction)faulthandler_cancel_dump_traceback_later_py, METH_NOARGS, PyDoc_STR("cancel_dump_traceback_later():\ncancel the previous call " "to dump_traceback_later().")}, #endif #ifdef FAULTHANDLER_USER {"register", (PyCFunction)faulthandler_register_py, METH_VARARGS|METH_KEYWORDS, PyDoc_STR("register(signum, file=sys.stderr, all_threads=True, chain=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")}, {"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_NOARGS, PyDoc_STR("_read_null(): read from NULL, raise " "a SIGSEGV or SIGBUS signal depending on the platform")}, {"_sigsegv", faulthandler_sigsegv, METH_VARARGS, PyDoc_STR("_sigsegv(release_gil=False): raise a SIGSEGV signal")}, {"_sigabrt", faulthandler_sigabrt, METH_NOARGS, PyDoc_STR("_sigabrt(): raise a SIGABRT signal")}, {"_sigfpe", (PyCFunction)faulthandler_sigfpe, METH_NOARGS, PyDoc_STR("_sigfpe(): raise a SIGFPE signal")}, {"_raise_signal", (PyCFunction)faulthandler_raise_signal, METH_VARARGS, PyDoc_STR("raise_signal(signum): raise a signal")}, {"_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} /* sentinel */ }; #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef module_def = { PyModuleDef_HEAD_INIT, "faulthandler", module_doc, 0, /* non negative size to be able to unload the module */ module_methods, NULL, faulthandler_traverse, NULL, NULL }; #endif PyMODINIT_FUNC #if PY_MAJOR_VERSION >= 3 PyInit_faulthandler(void) #else initfaulthandler(void) #endif { PyObject *m, *version; #ifdef HAVE_SIGALTSTACK int err; #endif #if PY_MAJOR_VERSION >= 3 m = PyModule_Create(&module_def); #else m = Py_InitModule3("faulthandler", module_methods, module_doc); #endif if (m == NULL) { #if PY_MAJOR_VERSION >= 3 return NULL; #else return; #endif } #ifdef HAVE_SIGALTSTACK /* Try to allocate an alternate stack for faulthandler() signal handler to * be able to allocate memory on the stack, even on a stack overflow. If it * fails, ignore the error. */ stack.ss_flags = 0; stack.ss_size = SIGSTKSZ; stack.ss_sp = PyMem_Malloc(stack.ss_size); if (stack.ss_sp != NULL) { err = sigaltstack(&stack, NULL); if (err) { PyMem_Free(stack.ss_sp); stack.ss_sp = NULL; } } #endif (void)Py_AtExit(faulthandler_unload); version = Py_BuildValue("(ii)", VERSION >> 8, VERSION & 0xFF); if (version == NULL) goto error; PyModule_AddObject(m, "version", version); #if PY_MAJOR_VERSION >= 3 version = PyUnicode_FromFormat("%i.%i", VERSION >> 8, VERSION & 0xFF); #else version = PyString_FromFormat("%i.%i", VERSION >> 8, VERSION & 0xFF); #endif if (version == NULL) goto error; PyModule_AddObject(m, "__version__", version); #if PY_MAJOR_VERSION >= 3 return m; #else return; #endif error: #if PY_MAJOR_VERSION >= 3 Py_DECREF(m); return NULL; #else return; #endif } static void faulthandler_unload(void) { #ifdef FAULTHANDLER_USER unsigned int signum; #endif #ifdef FAULTHANDLER_LATER /* later */ alarm(0); if (fault_alarm.header != NULL) { free(fault_alarm.header); fault_alarm.header = NULL; } /* Don't call Py_CLEAR(fault_alarm.file): this function is called too late, by Py_AtExit(). Destroy a Python object here raise strange errors. */ #endif #ifdef FAULTHANDLER_USER /* user */ if (user_signals != NULL) { for (signum=0; signum < NSIG; signum++) { faulthandler_unregister(&user_signals[signum], signum); /* Don't call Py_CLEAR(user->file): this function is called too late, by Py_AtExit(). Destroy a Python object here raise strange errors. */ } free(user_signals); user_signals = NULL; } #endif /* don't release file: faulthandler_unload_fatal_error() is called too late */ fatal_error.file = NULL; faulthandler_disable(); #ifdef HAVE_SIGALTSTACK if (stack.ss_sp != NULL) { PyMem_Free(stack.ss_sp); stack.ss_sp = NULL; } #endif } faulthandler-2.4/MANIFEST.in0000664000175000017500000000012212121736230016042 0ustar haypohaypo00000000000000include AUTHORS include COPYING include MANIFEST.in include tests.py include TODO faulthandler-2.4/TODO0000664000175000017500000000010512110031411014760 0ustar haypohaypo00000000000000 * sync doc * write a tool to "preload" faulthandler and enable it faulthandler-2.4/README0000664000175000017500000000257712312550756015215 0ustar haypohaypo00000000000000+++++++++++++ Fault handler +++++++++++++ Fault handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals: display the Python traceback and restore the previous handler. Allocate an alternate stack for this handler, if sigaltstack() is available, to be able to allocate memory on the stack, even on stack overflow (not available on Windows). Import the module and call faulthandler.enable() to enable the fault handler. The fault handler is called on catastrophic cases and so it can only use signal-safe functions (eg. it doesn't allocate memory on the heap). That's why the traceback is limited: it only supports ASCII encoding (use the backslashreplace error handler for non-ASCII characters) and limits each string to 100 characters, doesn't print the source code in the traceback (only the filename, the function name and the line number), is limited to 100 frames and 100 threads. By default, the Python traceback is written to the standard error stream. Start your graphical applications in a terminal and run your server in foreground to see the traceback, or pass a file to faulthandler.enable(). faulthandler is implemented in C using signal handlers to be able to dump a traceback on a crash or when Python is blocked (eg. deadlock). Website: http://faulthandler.readthedocs.org/ faulthandler is part of Python since Python 3.3: http://docs.python.org/dev/library/faulthandler.html faulthandler-2.4/PKG-INFO0000664000175000017500000000455112413253577015427 0ustar haypohaypo00000000000000Metadata-Version: 1.1 Name: faulthandler Version: 2.4 Summary: Display the Python traceback on a crash Home-page: http://faulthandler.readthedocs.org/ Author: Victor Stinner Author-email: victor.stinner@gmail.com License: BSD (2-clause) Description: +++++++++++++ Fault handler +++++++++++++ Fault handler for SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals: display the Python traceback and restore the previous handler. Allocate an alternate stack for this handler, if sigaltstack() is available, to be able to allocate memory on the stack, even on stack overflow (not available on Windows). Import the module and call faulthandler.enable() to enable the fault handler. The fault handler is called on catastrophic cases and so it can only use signal-safe functions (eg. it doesn't allocate memory on the heap). That's why the traceback is limited: it only supports ASCII encoding (use the backslashreplace error handler for non-ASCII characters) and limits each string to 100 characters, doesn't print the source code in the traceback (only the filename, the function name and the line number), is limited to 100 frames and 100 threads. By default, the Python traceback is written to the standard error stream. Start your graphical applications in a terminal and run your server in foreground to see the traceback, or pass a file to faulthandler.enable(). faulthandler is implemented in C using signal handlers to be able to dump a traceback on a crash or when Python is blocked (eg. deadlock). Website: http://faulthandler.readthedocs.org/ faulthandler is part of Python since Python 3.3: http://docs.python.org/dev/library/faulthandler.html Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Natural Language :: English Classifier: Programming Language :: C Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Debuggers Classifier: Topic :: Software Development :: Libraries :: Python Modules faulthandler-2.4/AUTHORS0000664000175000017500000000041512410644767015377 0ustar haypohaypo00000000000000Authors ======= Victor Stinner Contributors ============ Dan Sully - minor fixes Guido van Rossum - enhance traceback output Martin (gzlist) - improved Windows support faulthandler-2.4/setup.cfg0000664000175000017500000000007312413253577016146 0ustar haypohaypo00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 faulthandler-2.4/setup.py0000775000175000017500000000434012413253463016035 0ustar haypohaypo00000000000000#!/usr/bin/env python # Todo list to prepare a release: # - run ./run_tests.py # - run tests on FreeBSD and Windows # - set VERSION in faulthandler.c # - set VERSION in setup.py # - set VERSION in doc/conf.py # - set release date in the ChangeLog (README file) # - git commit -a # - git tag -a faulthandler-x.y -m "tag version x.y" # - git push # - git push --tags # - python setup.py register sdist upload # - Build 32-bi and 64-bit wheel packages on Windows: # # - python2.6 setup.py bdist_wheel upload # - python2.7 setup.py bdist_wheel upload # - python3.1 setup.py bdist_wheel upload # - python3.2 setup.py bdist_wheel upload # # - update the website # # After the release: # - increment VERSION in faulthandler.c # - increment VERSION in setup.py # - add a new empty section in the Changelog for the new version # - git commit -a # - git push from __future__ import with_statement try: # setuptools supports bdist_wheel from setuptools import setup, Extension except ImportError: from distutils.core import setup, Extension from os.path import join as path_join import sys if sys.version_info >= (3,3): print("ERROR: faulthandler is a builtin module since Python 3.3") sys.exit(1) VERSION = "2.4" FILES = ['faulthandler.c', 'traceback.c'] CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Natural Language :: English', 'Programming Language :: C', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Debuggers', 'Topic :: Software Development :: Libraries :: Python Modules', ] with open('README') as f: long_description = f.read().strip() options = { 'name': "faulthandler", 'version': VERSION, 'license': "BSD (2-clause)", 'description': 'Display the Python traceback on a crash', 'long_description': long_description, 'url': "http://faulthandler.readthedocs.org/", 'author': 'Victor Stinner', 'author_email': 'victor.stinner@gmail.com', 'ext_modules': [Extension('faulthandler', FILES)], 'classifiers': CLASSIFIERS, } setup(**options) faulthandler-2.4/tests.py0000664000175000017500000006266712413252525016052 0ustar haypohaypo00000000000000from __future__ import with_statement from contextlib import contextmanager import datetime import faulthandler import os import re import signal import subprocess import sys import tempfile import unittest from textwrap import dedent try: import threading HAVE_THREADS = True except ImportError: HAVE_THREADS = False TIMEOUT = 1 Py_REF_DEBUG = hasattr(sys, 'gettotalrefcount') try: from test.support import SuppressCrashReport except ImportError: try: import resource except ImportError: resource = None class SuppressCrashReport: """Try to prevent a crash report from popping up. On Windows, don't display the Windows Error Reporting dialog. On UNIX, disable the creation of coredump file. """ old_value = None def __enter__(self): """On Windows, disable Windows Error Reporting dialogs using SetErrorMode. On UNIX, try to save the previous core file size limit, then set soft limit to 0. """ if sys.platform.startswith('win'): # see http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621.aspx # GetErrorMode is not available on Windows XP and Windows Server 2003, # but SetErrorMode returns the previous value, so we can use that import ctypes self._k32 = ctypes.windll.kernel32 SEM_NOGPFAULTERRORBOX = 0x02 self.old_value = self._k32.SetErrorMode(SEM_NOGPFAULTERRORBOX) self._k32.SetErrorMode(self.old_value | SEM_NOGPFAULTERRORBOX) else: if resource is not None: try: self.old_value = resource.getrlimit(resource.RLIMIT_CORE) resource.setrlimit(resource.RLIMIT_CORE, (0, self.old_value[1])) except (ValueError, OSError): pass if sys.platform == 'darwin': # Check if the 'Crash Reporter' on OSX was configured # in 'Developer' mode and warn that it will get triggered # when it is. # # This assumes that this context manager is used in tests # that might trigger the next manager. value = subprocess.Popen(['/usr/bin/defaults', 'read', 'com.apple.CrashReporter', 'DialogType'], stdout=subprocess.PIPE).communicate()[0] if value.strip() == b'developer': sys.stdout.write("this test triggers the Crash " "Reporter, that is intentional") sys.stdout.flush() return self def __exit__(self, *ignore_exc): """Restore Windows ErrorMode or core file behavior to initial value.""" if self.old_value is None: return if sys.platform.startswith('win'): self._k32.SetErrorMode(self.old_value) else: if resource is not None: try: resource.setrlimit(resource.RLIMIT_CORE, self.old_value) except (ValueError, OSError): pass try: skipIf = unittest.skipIf except AttributeError: import functools def skipIf(test, reason): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): if not test: return func(*args, **kw) else: print("skip %s: %s" % (func.__name__, reason)) return wrapper return decorator try: from resource import setrlimit, RLIMIT_CORE, error as resource_error except ImportError: prepare_subprocess = None else: def prepare_subprocess(): # don't create core file try: setrlimit(RLIMIT_CORE, (0, 0)) except (ValueError, resource_error): pass def spawn_python(*args): args = (sys.executable,) + args return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def expected_traceback(lineno1, lineno2, header, min_count=1): regex = header regex += ' File "", line %s in func\n' % lineno1 regex += ' File "", line %s in ' % lineno2 if 1 < min_count: return '^' + (regex + '\n') * (min_count - 1) + regex else: return '^' + regex + '$' @contextmanager def temporary_filename(): filename = tempfile.mktemp() try: yield filename finally: try: os.unlink(filename) except OSError: pass class FaultHandlerTests(unittest.TestCase): def get_output(self, code, filename=None): """ Run the specified code in Python (in a new child process) and read the output from the standard error or from a file (if filename is set). Return the output lines as a list. Strip the reference count from the standard error for Python debug build, and replace "Current thread 0x00007f8d8fbd9700" by "Current thread XXX". """ code = dedent(code).strip() with SuppressCrashReport(): process = spawn_python('-c', code) stdout, stderr = process.communicate() exitcode = process.wait() output = re.sub(br"\[\d+ refs\]\r?\n?", b"", stdout).strip() output = output.decode('ascii', 'backslashreplace') if filename: self.assertEqual(output, '') with open(filename, "rb") as fp: output = fp.read() output = output.decode('ascii', 'backslashreplace') output = re.sub('Current thread 0x[0-9a-f]+', 'Current thread XXX', output) return output.splitlines(), exitcode def check_fatal_error(self, code, line_number, name_regex, filename=None, all_threads=True, other_regex=None): """ Check that the fault handler for fatal errors is enabled and check the traceback from the child process output. Raise an error if the output doesn't match the expected format. """ if all_threads: header = 'Current thread XXX (most recent call first)' else: header = 'Stack (most recent call first)' regex = """ ^Fatal Python error: {name} {header}: File "", line {lineno} in """ regex = dedent(regex).format( lineno=line_number, name=name_regex, header=re.escape(header)).strip() 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) @skipIf(sys.platform.startswith('aix'), "the first page of memory is a mapped read-only on AIX") def test_read_null(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._read_null() """, 3, # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion '(?:Segmentation fault|Bus error|Illegal instruction)') def test_sigsegv(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._sigsegv() """, 3, 'Segmentation fault') def test_sigabrt(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._sigabrt() """, 3, 'Aborted') @skipIf(sys.platform == 'win32', "SIGFPE cannot be caught on Windows") def test_sigfpe(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._sigfpe() """, 3, 'Floating point exception') @skipIf(not hasattr(signal, 'SIGBUS'), 'need signal.SIGBUS') def test_sigbus(self): self.check_fatal_error(""" import faulthandler import signal faulthandler.enable() faulthandler._raise_signal(signal.SIGBUS) """, 5, 'Bus error') @skipIf(not hasattr(signal, 'SIGILL'), 'need signal.SIGILL') def test_sigill(self): self.check_fatal_error(""" import faulthandler import signal faulthandler.enable() faulthandler._raise_signal(signal.SIGILL) """, 5, 'Illegal instruction') def test_fatal_error(self): if sys.version_info >= (2, 6): arg = "b'xyz'" else: arg = "'xyz'" message = "xyz\n" if sys.platform.startswith('win'): # When running unit tests with Microsoft Windows SDK, # Py_FatalError() displays the message "This application has # requested the Runtime to terminate it in an unusual way. Please # contact the application's support team for more information.". # Just ignore this message, it is not related to faulthandler. message += r"(.|\n)*" message += "Fatal Python error: Aborted" self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._fatal_error(%s) """ % arg, 3, message) @skipIf(sys.platform.startswith('openbsd') and HAVE_THREADS, "Issue #12868: sigaltstack() doesn't work on " "OpenBSD if Python is compiled with pthread") @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() """, 3, '(?:Segmentation fault|Bus error)', other_regex='unable to raise a stack overflow') def test_gil_released(self): self.check_fatal_error(""" import faulthandler faulthandler.enable() faulthandler._sigsegv(True) """, 3, 'Segmentation fault') def test_enable_file(self): with temporary_filename() as filename: self.check_fatal_error(""" import faulthandler output = open({filename}, 'wb') faulthandler.enable(output) faulthandler._sigsegv() """.format(filename=repr(filename)), 4, 'Segmentation fault', filename=filename) def test_enable_single_thread(self): self.check_fatal_error(""" import faulthandler faulthandler.enable(all_threads=False) faulthandler._sigsegv() """, 3, 'Segmentation fault', all_threads=False) def test_disable(self): code = """ import faulthandler faulthandler.enable() faulthandler.disable() faulthandler._sigsegv() """ not_expected = 'Fatal Python error' stderr, exitcode = self.get_output(code) stderr = '\n'.join(stderr) self.assertTrue(not_expected not in stderr, "%r is present in %r" % (not_expected, stderr)) self.assertNotEqual(exitcode, 0) def test_is_enabled(self): orig_stderr = sys.stderr try: # regrtest may replace sys.stderr by io.StringIO object, but # faulthandler.enable() requires that sys.stderr has a fileno() # method sys.stderr = sys.__stderr__ 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() finally: sys.stderr = orig_stderr def test_disabled_by_default(self): # By default, the module should be disabled code = "import faulthandler; print(faulthandler.is_enabled())" args = (sys.executable, '-c', code) # don't use assert_python_ok() because it always enable faulthandler process = subprocess.Popen(args, stdout=subprocess.PIPE) output, _ = process.communicate() exitcode = process.wait() self.assertEqual(output.rstrip(), b"False") self.assertEqual(exitcode, 0) 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 {has_filename}: with open({filename}, "wb") as fp: faulthandler.dump_traceback(fp, all_threads=False) else: faulthandler.dump_traceback(all_threads=False) def funcA(): funcB() funcA() """ code = code.format( filename=repr(filename), has_filename=bool(filename), ) if filename: lineno = 7 else: lineno = 9 expected = [ 'Stack (most recent call first):', ' File "", line %s in funcB' % lineno, ' File "", line 12 in funcA', ' File "", line 14 in ' ] trace, exitcode = self.get_output(code, filename) self.assertEqual(trace, expected) self.assertEqual(exitcode, 0) def test_dump_traceback(self): self.check_dump_traceback(None) def test_dump_traceback_file(self): with temporary_filename() as filename: self.check_dump_traceback(filename) def test_truncate(self): maxlen = 500 func_name = 'x' * (maxlen + 50) truncated = 'x' * maxlen + '...' code = """ import faulthandler def {func_name}(): faulthandler.dump_traceback(all_threads=False) {func_name}() """ code = code.format( func_name=func_name, ) expected = [ 'Stack (most recent call first):', ' File "", line 4 in %s' % truncated, ' File "", line 6 in ' ] trace, exitcode = self.get_output(code) self.assertEqual(trace, expected) self.assertEqual(exitcode, 0) @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 {filename}: with open({filename}, "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() """ code = code.format(filename=repr(filename)) output, exitcode = self.get_output(code, filename) output = '\n'.join(output) if filename: lineno = 9 else: lineno = 11 regex = """ ^Thread 0x[0-9a-f]+ \(most recent call first\): (?: 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 \(most recent call first\): File "", line {lineno} in dump File "", line 29 in $ """ regex = dedent(regex.format(lineno=lineno)).strip() 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_traceback_later(self, repeat, cancel, filename, loops): """ 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(timeout, repeat, cancel, file, loops): for loop in range(loops): faulthandler.dump_traceback_later(timeout, repeat=repeat, file=file) if cancel: faulthandler.cancel_dump_traceback_later() # sleep twice because time.sleep() is interrupted by # signals and dump_traceback_later() uses SIGALRM for loop in range(2): time.sleep(timeout * 1.25) faulthandler.cancel_dump_traceback_later() timeout = {timeout} repeat = {repeat} cancel = {cancel} loops = {loops} if {has_filename}: file = open({filename}, "wb") else: file = None func(timeout, repeat, cancel, file, loops) if file is not None: file.close() """ code = code.format( timeout=TIMEOUT, repeat=repeat, cancel=cancel, loops=loops, has_filename=bool(filename), filename=repr(filename), ) trace, exitcode = self.get_output(code, filename) trace = '\n'.join(trace) if not cancel: count = loops if repeat: count *= 2 header = r'Timeout \(%s\)!\nCurrent thread XXX \(most recent call first\):\n' % timeout_str regex = expected_traceback(12, 23, header, min_count=count) self.assertRegex(trace, regex) else: self.assertEqual(trace, '') self.assertEqual(exitcode, 0) @skipIf(not hasattr(faulthandler, 'dump_traceback_later'), 'need faulthandler.dump_traceback_later()') def check_dump_traceback_later(self, repeat=False, cancel=False, file=False, twice=False): if twice: loops = 2 else: loops = 1 if file: with temporary_filename() as filename: self._check_dump_traceback_later(repeat, cancel, filename, loops) else: self._check_dump_traceback_later(repeat, cancel, None, loops) def test_dump_traceback_later(self): self.check_dump_traceback_later() def test_dump_traceback_later_repeat(self): self.check_dump_traceback_later(repeat=True) def test_dump_traceback_later_cancel(self): self.check_dump_traceback_later(cancel=True) def test_dump_traceback_later_file(self): self.check_dump_traceback_later(file=True) def test_dump_traceback_later_twice(self): self.check_dump_traceback_later(twice=True) @skipIf(not hasattr(faulthandler, "register"), "need faulthandler.register") def check_register(self, filename=False, all_threads=False, unregister=False, chain=False): """ Register a handler displaying the traceback on a user signal. Raise the signal and check the written traceback. If chain is True, check that the previous signal handler is called. Raise an error if the output doesn't match the expected format. """ signum = signal.SIGUSR1 code = """ import faulthandler import os import signal import sys def func(signum): os.kill(os.getpid(), signum) def handler(signum, frame): handler.called = True handler.called = False exitcode = 0 signum = {signum} unregister = {unregister} chain = {chain} if {has_filename}: file = open({filename}, "wb") else: file = None if chain: signal.signal(signum, handler) faulthandler.register(signum, file=file, all_threads={all_threads}, chain={chain}) if unregister: faulthandler.unregister(signum) func(signum) if chain and not handler.called: if file is not None: output = file else: output = sys.stderr output.write("Error: signal handler not called!\\n") exitcode = 1 if file is not None: file.close() sys.exit(exitcode) """ code = code.format( filename=repr(filename), has_filename=bool(filename), all_threads=all_threads, signum=signum, unregister=unregister, chain=chain, ) trace, exitcode = self.get_output(code, filename) trace = '\n'.join(trace) if not unregister: if all_threads: regex = 'Current thread XXX \(most recent call first\):\n' else: regex = 'Stack \(most recent call first\):\n' regex = expected_traceback(7, 28, regex) self.assertRegex(trace, regex) else: self.assertEqual(trace, '') if unregister: self.assertNotEqual(exitcode, 0) else: self.assertEqual(exitcode, 0) def test_register(self): self.check_register() def test_unregister(self): self.check_register(unregister=True) def test_register_file(self): with temporary_filename() as filename: self.check_register(filename=filename) def test_register_threads(self): self.check_register(all_threads=True) def test_register_chain(self): self.check_register(chain=True) @contextmanager def check_stderr_none(self): stderr = sys.stderr try: sys.stderr = None err = '' try: yield except Exception as exc: err = exc self.assertEqual(str(err), "sys.stderr is None") finally: sys.stderr = stderr def test_stderr_None(self): # Issue #21497: provide an helpful error if sys.stderr is None, # instead of just an attribute error: "None has no attribute fileno". with self.check_stderr_none(): faulthandler.enable() with self.check_stderr_none(): faulthandler.dump_traceback() if hasattr(faulthandler, 'dump_traceback_later'): with self.check_stderr_none(): faulthandler.dump_traceback_later(1) if hasattr(faulthandler, "register"): with self.check_stderr_none(): faulthandler.register(signal.SIGUSR1) if not hasattr(unittest.TestCase, 'assertRegex'): # Copy/paste from Python 3.3: just replace (str, bytes) by str def assertRegex(self, text, expected_regex, msg=None): """Fail the test unless the text matches the regular expression.""" if isinstance(expected_regex, str): assert expected_regex, "expected_regex must not be empty." expected_regex = re.compile(expected_regex) if not expected_regex.search(text): msg = msg or "Regex didn't match" msg = '%s: %r not found in %r' % (msg, expected_regex.pattern, text) raise self.failureException(msg) if __name__ == "__main__": unittest.main() faulthandler-2.4/traceback.c0000664000175000017500000001517012413247031016400 0ustar haypohaypo00000000000000#include "Python.h" #include #if PY_MAJOR_VERSION >= 3 # define PYSTRING_CHECK PyUnicode_Check #else # define PYSTRING_CHECK PyString_Check #endif #define PUTS(fd, str) write(fd, str, (int)strlen(str)) #define MAX_STRING_LENGTH 500 #define MAX_FRAME_DEPTH 100 #define MAX_NTHREADS 100 /* 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 hexadecimal of 'width' digits, and write it into the file fd. This function is signal safe. */ static void dump_hexadecimal(int fd, unsigned long value, int width) { 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; unsigned long ch; #if PY_MAJOR_VERSION >= 3 Py_UNICODE *u; size = PyUnicode_GET_SIZE(text); u = PyUnicode_AS_UNICODE(text); #else char *s; 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++) { ch = *u; if (' ' <= ch && ch < 0x7f) { /* printable ASCII character */ char c = (char)ch; write(fd, &c, 1); } else if (ch <= 0xff) { PUTS(fd, "\\x"); dump_hexadecimal(fd, ch, 2); } else #ifdef Py_UNICODE_WIDE if (ch <= 0xffff) #endif { PUTS(fd, "\\u"); dump_hexadecimal(fd, ch, 4); #ifdef Py_UNICODE_WIDE } else { PUTS(fd, "\\U"); dump_hexadecimal(fd, ch, 8); #endif } } #else for (i=0; i < size; i++, s++) { ch = *s; if (' ' <= ch && ch <= 126) { /* printable ASCII character */ write(fd, s, 1); } else { PUTS(fd, "\\x"); dump_hexadecimal(fd, ch, 2); } } #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(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, "Stack (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(fd, (unsigned long)tstate->thread_id, sizeof(unsigned long)*2); PUTS(fd, " (most recent call first):\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; /* Get the current interpreter from the current thread */ tstate = PyInterpreterState_ThreadHead(interp); if (tstate == NULL) return "unable to get the thread head state"; /* Dump the traceback of each thread */ tstate = PyInterpreterState_ThreadHead(interp); nthreads = 0; do { if (nthreads != 0) write(fd, "\n", 1); if (nthreads >= MAX_NTHREADS) { PUTS(fd, "...\n"); break; } write_thread_id(fd, tstate, tstate == current_thread); dump_traceback(fd, tstate, 0); tstate = PyThreadState_Next(tstate); nthreads++; } while (tstate != NULL); return NULL; } faulthandler-2.4/COPYING0000664000175000017500000000272612110031411015336 0ustar haypohaypo00000000000000Copyright 2010 Victor Stinner. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY VICTOR STINNER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL VICTOR STINNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of Victor Stinner.