cpython: b0680b5a5215 (original) (raw)

--- a/Doc/library/debug.rst +++ b/Doc/library/debug.rst @@ -10,7 +10,8 @@ allowing you to identify bottlenecks in .. toctree:: bdb.rst

new file mode 100644 --- /dev/null +++ b/Doc/library/faulthandler.rst @@ -0,0 +1,129 @@ +:mod:faulthandler --- Dump the Python traceback +================================================= + +.. module:: faulthandler

+This module contains functions to dump the Python traceback explicitly, on a +fault, after a timeout or on a user signal. Call :func:faulthandler.enable to +install fault handlers for :const:SIGSEGV, :const:SIGFPE, :const:SIGBUS +and :const:SIGILL signals. You can also enable them at startup by setting the +:envvar:PYTHONFAULTHANDLER environment variable or by using :option:-X +faulthandler command line option. + +The fault handler is compatible with system fault handlers like Apport or +the Windows fault handler. The module uses an alternative stack for signal +handlers, if the :c:func:sigaltstack function is available, to be able to +dump the traceback even on a stack overflow. + +The fault handler is called on catastrophic cases and so can only use +signal-safe functions (e.g. it cannot allocate memory on the heap). That's why +the traceback is limited: only support ASCII encoding (use the +backslashreplace error handler), limit each string to 100 characters, don't +print the source code (only the filename, the function name and the line +number), limit to 100 frames and 100 threads. + +By default, the Python traceback is written to :data:sys.stderr. Start your +graphical applications in a terminal and run your server in foreground to see +the traceback, or specify a log file to :func:faulthandler.enable(). + +The module is implemented in C to be able to dump a traceback on a crash or +when Python is blocked (e.g. deadlock). + +.. versionadded:: 3.3 + + +Dump the traceback +------------------ + +.. function:: dump_traceback(file=sys.stderr, all_threads=False) +

+ +Fault handler state +------------------- + +.. function:: enable(file=sys.stderr, all_threads=False) +

+.. function:: disable() +

+.. function:: is_enabled() +

+ +Dump the tracebacks after a timeout +----------------------------------- + +.. function:: dump_tracebacks_later(timeout, repeat=False, file=sys.stderr, exit=False) +

+.. function:: cancel_dump_traceback_later() +

+ +Dump the traceback on a user signal +----------------------------------- + +.. function:: register(signum, file=sys.stderr, all_threads=False) +

+.. function:: unregister(signum) +

+ +File descriptor issue +--------------------- + +:func:enable, :func:dump_traceback_later and :func:register keep the +file descriptor of their file argument. If the file is closed and its file +descriptor is reused by a new file, or if :func:os.dup2 is used to replace +the file descriptor, the traceback will be written into a different file. Call +these functions again each time that the file is replaced. + + +Example +------- + +Example of a segmentation fault on Linux: :: +

+

+

--- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -498,6 +498,13 @@ These environment variables influence Py separated string, it is equivalent to specifying :option:-W multiple times. +.. envvar:: PYTHONFAULTHANDLER +

Debug-mode variables

~~~~~~~~~~~~~~~~~~~~

--- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -68,6 +68,14 @@ New, Improved, and Deprecated Modules

--- a/Include/traceback.h +++ b/Include/traceback.h @@ -5,6 +5,8 @@ extern "C" { #endif +#include "pystate.h" + struct _frame; /* Traceback interface / @@ -28,6 +30,44 @@ PyAPI_FUNC(int) _Py_DisplaySourceLine(Py PyAPI_DATA(PyTypeObject) PyTraceBack_Type; #define PyTraceBack_Check(v) (Py_TYPE(v) == &PyTraceBack_Type) +/ Write the Python traceback into the file 'fd'. For example: +

+

+PyAPI_DATA(int) _Py_DumpTraceback(

+ +/* Write the traceback of all threads into the file 'fd'. current_thread can be

+PyAPI_DATA(const char*) _Py_DumpTracebackThreads(

+ + #ifdef __cplusplus } #endif

--- a/Lib/test/regrtest.py +++ b/Lib/test/regrtest.py @@ -157,6 +157,7 @@ option '-uall,-gui'. """ import builtins +import faulthandler import getopt import json import os @@ -490,6 +491,7 @@ def main(tests=None, testdir=None, verbo next_single_test = alltests[alltests.index(selected[0])+1] except IndexError: next_single_test = None

@@ -1551,6 +1553,9 @@ def _make_temp_dir_for_build(TEMPDIR): return TEMPDIR, TESTCWD if name == 'main':

+ # Remove regrtest.py's own directory from the module search path. Despite # the elimination of implicit relative imports, this is still needed to # ensure that submodules of the test package do not inappropriately appear

--- a/Lib/test/script_helper.py +++ b/Lib/test/script_helper.py @@ -56,11 +56,12 @@ def assert_python_failure(*args, **env_v """ return _assert_python(False, *args, **env_vars) -def spawn_python(*args): +def spawn_python(*args, **kw): cmd_line = [sys.executable, '-E'] cmd_line.extend(args) return subprocess.Popen(cmd_line, stdin=subprocess.PIPE,

def kill_python(p): p.stdin.close()

new file mode 100644 --- /dev/null +++ b/Lib/test/test_faulthandler.py @@ -0,0 +1,469 @@ +from contextlib import contextmanager +import faulthandler +import re +import signal +import subprocess +import sys +from test import support, script_helper +import tempfile +import unittest + +try:

+except ImportError:

+else:

+ +def expected_traceback(lineno1, lineno2, header, count=1):

+ +@contextmanager +def temporary_filename():

+ +class FaultHandlerTests(unittest.TestCase):

+

+

+

+^Fatal Python error: {name} + +{header}:

+

+import faulthandler +faulthandler.enable() +faulthandler._read_null() +""".strip(),

+

+import faulthandler +faulthandler.enable() +faulthandler._sigsegv() +""".strip(),

+

+import faulthandler +faulthandler.enable() +faulthandler._sigfpe() +""".strip(),

+

+import faulthandler +faulthandler.enable() +faulthandler._sigbus() +""".strip(),

+

+import faulthandler +faulthandler.enable() +faulthandler._sigill() +""".strip(),

+

+import faulthandler +faulthandler._fatal_error(b'xyz') +""".strip(),

+

+import faulthandler +faulthandler.enable() +faulthandler._stack_overflow() +""".strip(),

+

+import faulthandler +faulthandler.enable() +faulthandler._read_null(True) +""".strip(),

+

+import faulthandler +output = open({filename}, 'wb') +faulthandler.enable(output) +faulthandler._read_null(True) +""".strip().format(filename=repr(filename)),

+

+import faulthandler +faulthandler.enable(all_threads=True) +faulthandler._read_null(True) +""".strip(),

+

+import faulthandler +faulthandler.enable() +faulthandler.disable() +faulthandler._read_null() +""".strip()

+

+

+import faulthandler + +def funcB():

+ +def funcA():

+ +funcA() +""".strip()

+

+

+import faulthandler +from threading import Thread, Event +import time + +def dump():

+ +class Waiter(Thread):

+

+

+ +waiter = Waiter() +waiter.start() +waiter.running.wait() +dump() +waiter.stop.set() +waiter.join() +""".strip()

+^Thread 0x[0-9a-f]+: +(?: File ".*threading.py", line [0-9]+ in wait +)? File ".*threading.py", line [0-9]+ in wait

+

+

+

+import faulthandler +import time + +def func(repeat, cancel, timeout):

+

+ +timeout = 0.5 +repeat = {repeat} +cancel = {cancel} +if {has_filename}:

+else:

+faulthandler.dump_tracebacks_later(timeout,

+func(repeat, cancel, timeout) +if file is not None:

+""".strip()

+

+

+

+

+

+

+

+

+import faulthandler +import os +import signal + +def func(signum):

+ +signum = signal.SIGUSR1 +if {has_filename}:

+else:

+faulthandler.register(signum, file=file, all_threads={all_threads}) +func(signum) +if file is not None:

+""".strip()

+

+

+

+ + +def test_main():

+ +if name == "main":

--- a/Misc/NEWS +++ b/Misc/NEWS @@ -87,6 +87,8 @@ Core and Builtins Library ------- +- Issue #11393: Add the new faulthandler module. +

--- a/Modules/Setup.dist +++ b/Modules/Setup.dist @@ -127,6 +127,9 @@ itertools itertoolsmodule.c # Functio

builtin module avoids some bootstrapping problems and reduces overhead.

zipimport zipimport.c +# faulthandler module +faulthandler faulthandler.c +

The rest of the modules listed in this file are all commented out by

default. Usually they can be detected and built as dynamically

loaded modules by the new setup.py script added in Python 2.1. If

new file mode 100644 --- /dev/null +++ b/Modules/faulthandler.c @@ -0,0 +1,971 @@ +#include "Python.h" +#include "pythread.h" +#include <signal.h> +#include <object.h> +#include <frameobject.h> +#include <signal.h> + +#ifdef WITH_THREAD +# define FAULTHANDLER_LATER +#endif + +#ifndef MS_WINDOWS

+# define FAULTHANDLER_USER +#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 {

+} fault_handler_t; + +static struct {

+} fatal_error = {0, NULL, -1, 0}; + +#ifdef FAULTHANDLER_LATER +static struct {

+} thread; +#endif + +#ifdef FAULTHANDLER_USER +typedef struct {

+} user_signal_t; + +static user_signal_t user_signals; + +/ the following macros come from Python: Modules/signalmodule.c / +#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

+#endif +#ifdef SIGILL

+#endif

+}; +static const unsigned char faulthandler_nsignals = [](#l11.104)

+ +#ifdef HAVE_SIGALTSTACK +static stack_t stack; +#endif + + +/* Get the file descriptor of a file by calling its fileno() method and then

+static PyObject* +faulthandler_get_fileno(PyObject *file, int *p_fd) +{

+

+

+

+

+

+} + +static PyObject* +faulthandler_dump_traceback_py(PyObject *self,

+{

+

+

+

+

+} + + +/* Handler of SIGSEGV, SIGFPE, SIGBUS and SIGILL signals. +

+static void +faulthandler_fatal_error(

+#ifdef HAVE_SIGACTION

+#endif +) +{

+

+

+

+#ifdef HAVE_SIGACTION

+#else

+#endif

+

+

+

+

+ +#ifndef MS_WINDOWS

+#else

+#endif +} + +/* Install handler for fatal signals (SIGSEGV, SIGFPE, ...). / + +static PyObject +faulthandler_enable(PyObject *self, PyObject *args, PyObject *kwargs) +{

+#ifdef HAVE_SIGACTION

+#endif

+

+

+

+

+

+#ifdef HAVE_SIGACTION

+#ifdef HAVE_SIGALTSTACK

+#endif

+#else

+#endif

+} + +static void +faulthandler_disable(void) +{

+

+#ifdef HAVE_SIGACTION

+#else

+#endif

+

+} + +static PyObject* +faulthandler_disable_py(PyObject *self) +{

+} + +static PyObject* +faulthandler_is_enabled(PyObject *self) +{

+} + +#ifdef FAULTHANDLER_LATER + +static void +faulthandler_thread(void *unused) +{

+

+

+

+

+

+} + +static void +faulthandler_cancel_dump_traceback_later(void) +{

+} + +static PyObject* +faulthandler_dump_traceback_later(PyObject *self,

+{

+

+

+

+

+

+

+

+} + +static PyObject* +faulthandler_cancel_dump_traceback_later_py(PyObject *self) +{

+} +#endif /* FAULTHANDLER_LATER / + +#ifdef FAULTHANDLER_USER +/ Handler of user signals (e.g. SIGUSR1). +

+static void +faulthandler_user(int signum) +{

+

+

+

+} + +static PyObject* +faulthandler_register(PyObject *self,

+{

+#ifdef HAVE_SIGACTION

+#endif

+

+

+

+

+

+

+#ifdef HAVE_SIGACTION

+#ifdef HAVE_SIGALTSTACK

+#endif

+#else

+#endif

+

+

+} + +static int +faulthandler_unregister(user_signal_t *user, int signum) +{

+#ifdef HAVE_SIGACTION

+#else

+#endif

+} + +static PyObject* +faulthandler_unregister_py(PyObject *self, PyObject *args) +{

+

+

+

+} +#endif /* FAULTHANDLER_USER */ + + +static PyObject * +faulthandler_read_null(PyObject *self, PyObject *args) +{

+ +} + +static PyObject * +faulthandler_sigsegv(PyObject *self, PyObject *args) +{ +#if defined(MS_WINDOWS)

+

+#else

+#endif

+} + +static PyObject * +faulthandler_sigfpe(PyObject *self, PyObject *args) +{

+} + +#ifdef SIGBUS +static PyObject * +faulthandler_sigbus(PyObject *self, PyObject *args) +{

+} +#endif + +#ifdef SIGILL +static PyObject * +faulthandler_sigill(PyObject *self, PyObject *args) +{ +#if defined(MS_WINDOWS)

+#else

+#endif

+} +#endif + +static PyObject * +faulthandler_fatal_error_py(PyObject *self, PyObject *args) +{

+} + +#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION) +static PyObject * +faulthandler_stack_overflow(PyObject *self) +{

+} +#endif + + +static int +faulthandler_traverse(PyObject *module, visitproc visit, void *arg) +{ +#ifdef FAULTHANDLER_USER

+#endif + +#ifdef FAULTHANDLER_LATER

+#endif +#ifdef FAULTHANDLER_USER

+#endif

+} + +PyDoc_STRVAR(module_doc, +"faulthandler module."); + +static PyMethodDef module_methods[] = {

+#ifdef FAULTHANDLER_LATER

+#endif + +#ifdef FAULTHANDLER_USER

+#endif +

+#ifdef SIGBUS

+#endif +#ifdef SIGILL

+#endif

+#if defined(HAVE_SIGALTSTACK) && defined(HAVE_SIGACTION)

+#endif

+}; + +static struct PyModuleDef module_def = {

+}; + +PyMODINIT_FUNC +PyInit_faulthandler(void) +{

+} + +/* Call faulthandler.enable() if PYTHONFAULTHANDLER environment variable is

+static int +faulthandler_env_options(void) +{

+

+

+

+

+} + +int _PyFaulthandler_Init(void) +{ +#ifdef HAVE_SIGALTSTACK

+

+#endif +#ifdef FAULTHANDLER_LATER

+#endif +

+} + +void _PyFaulthandler_Fini(void) +{ +#ifdef FAULTHANDLER_USER

+#endif + +#ifdef FAULTHANDLER_LATER

+#endif + +#ifdef FAULTHANDLER_USER

+#endif +

+#ifdef HAVE_SIGALTSTACK

+#endif +}

--- a/Modules/main.c +++ b/Modules/main.c @@ -100,6 +100,7 @@ static char *usage_5 = " The default module search path uses %s.\n" "PYTHONCASEOK : ignore case in 'import' statements (Windows).\n" "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n" +"PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.\n" ; static int

--- a/PC/config.c +++ b/PC/config.c @@ -12,6 +12,7 @@ extern PyObject* PyInit_audioop(void); extern PyObject* PyInit_binascii(void); extern PyObject* PyInit_cmath(void); extern PyObject* PyInit_errno(void); +extern PyObject* PyInit_faulthandler(void); extern PyObject* PyInit_gc(void); extern PyObject* PyInit_math(void); extern PyObject* PyInit__md5(void); @@ -82,6 +83,7 @@ struct _inittab _PyImport_Inittab[] = { {"binascii", PyInit_binascii}, {"cmath", PyInit_cmath}, {"errno", PyInit_errno},

--- a/PCbuild/pythoncore.vcproj +++ b/PCbuild/pythoncore.vcproj @@ -1087,6 +1087,10 @@ > <File + RelativePath="..\Modules\faulthandler.c" + > + + <File RelativePath="..\Modules\gcmodule.c" >

--- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -70,6 +70,8 @@ extern void _PyUnicode_Init(void); extern void _PyUnicode_Fini(void); extern int _PyLong_Init(void); extern void PyLong_Fini(void); +extern int _PyFaulthandler_Init(void); +extern void _PyFaulthandler_Fini(void); #ifdef WITH_THREAD extern void _PyGILState_Init(PyInterpreterState *, PyThreadState *); @@ -286,6 +288,10 @@ Py_InitializeEx(int install_sigs) _PyImportHooks_Init();

+ /* Initialize _warnings. / _PyWarnings_Init(); @@ -454,6 +460,9 @@ Py_Finalize(void) / Destroy the database used by PyImport{Fixup,Find}Extension */ _PyImport_Fini();

+ /* Debugging stuff */ #ifdef COUNT_ALLOCS dump_counts(stdout); @@ -2100,11 +2109,23 @@ cleanup: void Py_FatalError(const char *msg) {

+ fprintf(stderr, "Fatal Python error: %s\n", msg); fflush(stderr); /* it helps in Windows debug build */ if (PyErr_Occurred()) { PyErr_PrintEx(0); }

+ #ifdef MS_WINDOWS { size_t len = strlen(msg);

--- a/Python/traceback.c +++ b/Python/traceback.c @@ -13,6 +13,11 @@ #define OFF(x) offsetof(PyTracebackObject, x) +#define PUTS(fd, str) write(fd, str, strlen(str)) +#define MAX_STRING_LENGTH 100 +#define MAX_FRAME_DEPTH 100 +#define MAX_NTHREADS 100 + /* Method from Parser/tokenizer.c */ extern char * PyTokenizer_FindEncoding(int); @@ -402,3 +407,233 @@ PyTraceBack_Print(PyObject *v, PyObject err = tb_printinternal((PyTracebackObject )v, f, limit); return err; } + +/ Reverse a string. For example, "abcd" becomes "dcba". +

+static void +reverse_string(char *text, const size_t len) +{

+} + +/* Format an integer in range [0; 999999] to decimal,

+static void +dump_decimal(int fd, int value) +{

+} + +/* Format an integer in range [0; 0xffffffff] to hexdecimal of 'width' digits,

+static void +dump_hexadecimal(int width, unsigned long value, int fd) +{

+} + +/* Write an unicode object into the file fd using ascii+backslashreplace. +

+static void +dump_ascii(int fd, PyObject *text) +{

+

+

+

+#ifdef Py_UNICODE_WIDE

+#endif

+#ifdef Py_UNICODE_WIDE

+#endif

+} + +/* Write a frame into the file fd: "File "xxx", line xxx in xxx". +

+static void +dump_frame(int fd, PyFrameObject *frame) +{

+

+

+

+

+} + +static int +dump_traceback(int fd, PyThreadState *tstate, int write_header) +{

+

+

+} + +int +_Py_DumpTraceback(int fd, PyThreadState *tstate) +{

+} + +/* Write the thread identifier into the file 'fd': "Current thread 0xHHHH:" if

+static void +write_thread_id(int fd, PyThreadState *tstate, int is_current) +{

+} + +const char* +_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,

+{

+

+

+

+} +

--- a/configure +++ b/configure @@ -9261,7 +9261,7 @@ for ac_func in alarm accept4 setitimer g select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid [](#l17.4) setgid sethostname [](#l17.5) setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf [](#l17.6)

--- a/configure.in +++ b/configure.in @@ -2507,7 +2507,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer g select sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid [](#l18.4) setgid sethostname [](#l18.5) setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf [](#l18.6)

--- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -710,6 +710,9 @@ /* Define to 1 if you have the sigaction' function. */[](#l19.4) #undef HAVE_SIGACTION[](#l19.5) [](#l19.6) +/* Define to 1 if you have the sigaltstack' function. / +#undef HAVE_SIGALTSTACK + / Define to 1 if you have the `siginterrupt' function. */ #undef HAVE_SIGINTERRUPT