From 0ebdc69f3978886e40a621f06df66c36cf11080a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 12:27:25 -0500 Subject: [PATCH 01/51] Initial implementation for glibc --- Modules/faulthandler.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index b62362f277797e..cb2276097fc1bb 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -212,6 +212,43 @@ faulthandler_dump_traceback(int fd, int all_threads, reentrant = 0; } +static void +faulthandler_dump_c_stack(int fd, PyInterpreterState *interp) +{ + static volatile int reentrant = 0; + + if (reentrant) + return; + + reentrant = 1; + + #include +#define SIZE 128 + void *callstack[128]; + int frames = backtrace(callstack, SIZE); + char **strings = backtrace_symbols(callstack, SIZE); + FILE *fp = fdopen(fd, "w"); + if (strings == NULL) + { + fputs("", fp); + } + else { + fputs("\nCurrent thread's C stack trace:\n", fp); + for (int i = frames; i >= 0; --i) + { + fprintf(fp, " %s\n", strings[i]); + } + + if (frames == SIZE) + { + fprintf(fp, "\n"); + } + } +#undef SIZE + + reentrant = 0; +} + static PyObject* faulthandler_dump_traceback_py(PyObject *self, PyObject *args, PyObject *kwargs) @@ -322,6 +359,7 @@ faulthandler_fatal_error(int signum) faulthandler_dump_traceback(fd, fatal_error.all_threads, fatal_error.interp); + faulthandler_dump_c_stack(fd, fatal_error.interp); _Py_DumpExtensionModules(fd, fatal_error.interp); From 0ccf9fbbdf3b45eaf7b55f62e8ed96f7b35ddb52 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 12:51:48 -0500 Subject: [PATCH 02/51] Make it cross-platform buildable. --- Modules/faulthandler.c | 65 +++++++++++++++++++++++++++++++----------- configure | 6 ++++ configure.ac | 2 +- pyconfig.h.in | 3 ++ 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index cb2276097fc1bb..f1eb5fd5a211ac 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -10,6 +10,10 @@ #ifdef HAVE_UNISTD_H # include // _exit() #endif +#ifdef HAVE_EXECINFO_H +# include // backtrace(), backtrace_symbols() +#endif + #include // sigaction() #include // abort() #if defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK) && defined(HAVE_PTHREAD_H) @@ -212,39 +216,68 @@ faulthandler_dump_traceback(int fd, int all_threads, reentrant = 0; } +#ifdef HAVE_EXECINFO_H static void -faulthandler_dump_c_stack(int fd, PyInterpreterState *interp) +faulthandler_stack_dump_impl(int fd) { - static volatile int reentrant = 0; - - if (reentrant) - return; - - reentrant = 1; - - #include #define SIZE 128 - void *callstack[128]; +#define ENTRY_SIZE 256 + void *callstack[SIZE]; int frames = backtrace(callstack, SIZE); char **strings = backtrace_symbols(callstack, SIZE); - FILE *fp = fdopen(fd, "w"); if (strings == NULL) { - fputs("", fp); + // XXX Handle with perror? + PUTS(fd, ""); } else { - fputs("\nCurrent thread's C stack trace:\n", fp); for (int i = frames; i >= 0; --i) { - fprintf(fp, " %s\n", strings[i]); + char entry_str[ENTRY_SIZE]; + snprintf(entry_str, ENTRY_SIZE, " %s\n", strings[i]); + size_t length = strlen(entry_str) + 1; + if (length == ENTRY_SIZE) + { + /* We exceeded the size, make it look prettier */ + + // Add ellipsis to last 3 characters (but before the newline and null terminator) + for (int x = 0; x < 3; ++x) + entry_str[ENTRY_SIZE - (x + 3)] = '.'; + + entry_str[ENTRY_SIZE - 2] = '\n'; + entry_str[ENTRY_SIZE - 1] = '\0'; + } + _Py_write_noraise(fd, entry_str, length); } if (frames == SIZE) { - fprintf(fp, "\n"); + PUTS(fd, " "); } } +#undef ENTRY_SIZE #undef SIZE +} +#else +static void +faulthandler_stack_dump_impl(int fd) +{ + PUTS(fd, " \n"); +} +#endif + +static void +faulthandler_dump_c_stack(int fd) +{ + static volatile int reentrant = 0; + + if (reentrant) + return; + + reentrant = 1; + + PUTS(fd, "\nCurrent thread's C stack trace (most recent call first):\n"); + faulthandler_stack_dump_impl(fd); reentrant = 0; } @@ -359,7 +392,7 @@ faulthandler_fatal_error(int signum) faulthandler_dump_traceback(fd, fatal_error.all_threads, fatal_error.interp); - faulthandler_dump_c_stack(fd, fatal_error.interp); + faulthandler_dump_c_stack(fd); _Py_DumpExtensionModules(fd, fatal_error.interp); diff --git a/configure b/configure index e59c7046305d46..5236b7504c188c 100755 --- a/configure +++ b/configure @@ -10900,6 +10900,12 @@ if test "x$ac_cv_header_errno_h" = xyes then : printf "%s\n" "#define HAVE_ERRNO_H 1" >>confdefs.h +fi +ac_fn_c_check_header_compile "$LINENO" "execinfo.h" "ac_cv_header_execinfo_h" "$ac_includes_default" +if test "x$ac_cv_header_execinfo_h" = xyes +then : + printf "%s\n" "#define HAVE_EXECINFO_H 1" >>confdefs.h + fi ac_fn_c_check_header_compile "$LINENO" "fcntl.h" "ac_cv_header_fcntl_h" "$ac_includes_default" if test "x$ac_cv_header_fcntl_h" = xyes diff --git a/configure.ac b/configure.ac index 074e2ce3dd3024..cd98696d21d35b 100644 --- a/configure.ac +++ b/configure.ac @@ -2929,7 +2929,7 @@ AC_DEFINE([STDC_HEADERS], [1], # checks for header files AC_CHECK_HEADERS([ \ - alloca.h asm/types.h bluetooth.h conio.h direct.h dlfcn.h endian.h errno.h fcntl.h grp.h \ + alloca.h asm/types.h bluetooth.h conio.h direct.h dlfcn.h endian.h errno.h execinfo.h fcntl.h grp.h \ io.h langinfo.h libintl.h libutil.h linux/auxvec.h sys/auxv.h linux/fs.h linux/limits.h linux/memfd.h \ linux/netfilter_ipv4.h linux/random.h linux/soundcard.h linux/sched.h \ linux/tipc.h linux/wait.h netdb.h net/ethernet.h netinet/in.h netpacket/packet.h poll.h process.h pthread.h pty.h \ diff --git a/pyconfig.h.in b/pyconfig.h.in index 1ca83fd2f2ca1b..828ac02356e5e8 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -331,6 +331,9 @@ /* Define if you have the 'eventfd' function. */ #undef HAVE_EVENTFD +/* Define to 1 if you have the header file. */ +#undef HAVE_EXECINFO_H + /* Define to 1 if you have the `execv' function. */ #undef HAVE_EXECV From 52945e51732cfb67dce6eff30056ac34b3f1170b Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 12:52:52 -0500 Subject: [PATCH 03/51] Call it on Windows. --- Modules/faulthandler.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index f1eb5fd5a211ac..6c700362e249ee 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -469,6 +469,7 @@ faulthandler_exc_handler(struct _EXCEPTION_POINTERS *exc_info) faulthandler_dump_traceback(fd, fatal_error.all_threads, fatal_error.interp); + faulthandler_dump_c_stack(fd); /* call the next exception handler */ return EXCEPTION_CONTINUE_SEARCH; From 9bdd80266f7e25b346b873cb344f0c175f674a65 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 12:54:34 -0500 Subject: [PATCH 04/51] Add a whatsnew entry --- Doc/whatsnew/3.14.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 97a37a82f76b9b..c91bc8b4c92fab 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -367,6 +367,12 @@ errno (Contributed by James Roy in :gh:`126585`.) +faulthandler +------------ + +* Add support for printing the C stack trace on systems that support it. + + fractions --------- From 66f2641265dd16a5e71086fef12088745fb0321d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:04:25 -0500 Subject: [PATCH 05/51] Add dump_c_stack --- Include/internal/pycore_faulthandler.h | 1 + Modules/faulthandler.c | 50 +++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/Include/internal/pycore_faulthandler.h b/Include/internal/pycore_faulthandler.h index 6dd7d8d7ca9792..78cd657e6ae5ae 100644 --- a/Include/internal/pycore_faulthandler.h +++ b/Include/internal/pycore_faulthandler.h @@ -56,6 +56,7 @@ struct _faulthandler_runtime_state { #ifdef MS_WINDOWS void *exc_handler; #endif + int c_stack; } fatal_error; struct { diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 6c700362e249ee..0b7ace4b5951a2 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -266,6 +266,13 @@ faulthandler_stack_dump_impl(int fd) } #endif +static void +faulthandler_dump_c_stack_nocheck(int fd) +{ + PUTS(fd, "Current thread's C stack trace (most recent call first):\n"); + faulthandler_stack_dump_impl(fd); +} + static void faulthandler_dump_c_stack(int fd) { @@ -276,8 +283,11 @@ faulthandler_dump_c_stack(int fd) reentrant = 1; - PUTS(fd, "\nCurrent thread's C stack trace (most recent call first):\n"); - faulthandler_stack_dump_impl(fd); + if (fatal_error.c_stack) + { + PUTS(fd, "\n"); + faulthandler_dump_c_stack_nocheck(fd); + } reentrant = 0; } @@ -323,6 +333,32 @@ faulthandler_dump_traceback_py(PyObject *self, Py_RETURN_NONE; } +static PyObject * +faulthandler_dump_c_stack_py(PyObject *self, + PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"file", NULL}; + PyObject *file = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "|Op:dump_c_stack", kwlist, + &file)) + return NULL; + + int fd = faulthandler_get_fileno(&file); + if (fd < 0) { + return NULL; + } + + faulthandler_dump_c_stack_nocheck(fd); + + if (PyErr_CheckSignals()) { + return NULL; + } + + Py_RETURN_NONE; +} + static void faulthandler_disable_fatal_handler(fault_handler_t *handler) { @@ -564,14 +600,15 @@ faulthandler_enable(void) static PyObject* faulthandler_py_enable(PyObject *self, PyObject *args, PyObject *kwargs) { - static char *kwlist[] = {"file", "all_threads", NULL}; + static char *kwlist[] = {"file", "all_threads", "c_stack", NULL}; PyObject *file = NULL; int all_threads = 1; int fd; + int c_stack = 1; PyThreadState *tstate; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|Op:enable", kwlist, &file, &all_threads)) + "|Op:enable", kwlist, &file, &all_threads, &c_stack)) return NULL; fd = faulthandler_get_fileno(&file); @@ -587,6 +624,7 @@ faulthandler_py_enable(PyObject *self, PyObject *args, PyObject *kwargs) fatal_error.fd = fd; fatal_error.all_threads = all_threads; fatal_error.interp = PyThreadState_GetInterpreter(tstate); + fatal_error.c_stack = c_stack; if (faulthandler_enable() < 0) { return NULL; @@ -1277,6 +1315,10 @@ static PyMethodDef module_methods[] = { PyDoc_STR("dump_traceback($module, /, file=sys.stderr, all_threads=True)\n--\n\n" "Dump the traceback of the current thread, or of all threads " "if all_threads is True, into file.")}, + {"dump_c_stack", + _PyCFunction_CAST(faulthandler_dump_c_stack_py), METH_VARARGS|METH_KEYWORDS, + PyDoc_STR("dump_c_stack($module, /, file=sys.stderr)\n--\n\n" + "Dump the C stack of the current thread.")}, {"dump_traceback_later", _PyCFunction_CAST(faulthandler_dump_traceback_later), METH_VARARGS|METH_KEYWORDS, PyDoc_STR("dump_traceback_later($module, /, timeout, repeat=False, file=sys.stderr, exit=False)\n--\n\n" From 0591013ec8f4a04ac83aa7ba1381d1faab5ce73d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:07:26 -0500 Subject: [PATCH 06/51] Reverse the loop. --- Modules/faulthandler.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 0b7ace4b5951a2..1ef82999940c45 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -227,11 +227,10 @@ faulthandler_stack_dump_impl(int fd) char **strings = backtrace_symbols(callstack, SIZE); if (strings == NULL) { - // XXX Handle with perror? - PUTS(fd, ""); + PUTS(fd, " "); } else { - for (int i = frames; i >= 0; --i) + for (int i = 0; i < frames; ++i) { char entry_str[ENTRY_SIZE]; snprintf(entry_str, ENTRY_SIZE, " %s\n", strings[i]); From 64707a29cd37a27c1ce9eec9e7dbb4ff45e1f338 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:13:47 -0500 Subject: [PATCH 07/51] Add documentation entries. --- Doc/library/faulthandler.rst | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst index 4067d7912b88b2..f75b01cbfa26df 100644 --- a/Doc/library/faulthandler.rst +++ b/Doc/library/faulthandler.rst @@ -66,10 +66,24 @@ Dumping the traceback Added support for passing file descriptor to this function. +Dumping the C stack +------------------- + +.. versionadded:: next + +.. function:: dump_c_stack(file=sys.stderr) + + Dump the C stack trace of the current thread into *file*. + + This is done with `GNU backtraces `_. + + If the system doesn't support the C-level :c:expr:`backtrace()` or :c:expr:`backtrace_symbols()` functions, + then an error message is displayed instead of the C stack. + Fault handler state ------------------- -.. function:: enable(file=sys.stderr, all_threads=True) +.. function:: enable(file=sys.stderr, all_threads=True, c_stack=True) Enable the fault handler: install handlers for the :const:`~signal.SIGSEGV`, :const:`~signal.SIGFPE`, :const:`~signal.SIGABRT`, :const:`~signal.SIGBUS` @@ -81,6 +95,10 @@ Fault handler state The *file* must be kept open until the fault handler is disabled: see :ref:`issue with file descriptors `. + If *c_stack* is ``True``, then the C stack trace is printed after the Python + traceback, unless the system doesn't support it. See :func:`dump_c_stack` for + more information on compatibility. + .. versionchanged:: 3.5 Added support for passing file descriptor to this function. @@ -91,6 +109,9 @@ Fault handler state The dump now mentions if a garbage collector collection is running if *all_threads* is true. + .. versionchanged:: next + The dump now displays the C stack trace if *c_stack* is true. + .. function:: disable() Disable the fault handler: uninstall the signal handlers installed by From 8a5b7848f9d0ec0e566400a1dc9e039eff9e8a9e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:15:39 -0500 Subject: [PATCH 08/51] Make the whatsnew entry more specific. --- Doc/whatsnew/3.14.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index c91bc8b4c92fab..944d2ae6484ba9 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -370,7 +370,8 @@ errno faulthandler ------------ -* Add support for printing the C stack trace on systems that support it. +* Add support for printing the C stack trace on systems that support it via + :func:`faulthandler.dump_c_stack` or via the *c_stack* argument in :func:`faulthandler.c_stack`. fractions From 4f0935862d648690702e1272aee57510d502cb13 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:26:58 -0500 Subject: [PATCH 09/51] Add tests. --- Lib/test/test_faulthandler.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index 60815be96e14eb..17db411608c6d6 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -93,6 +93,7 @@ def check_error(self, code, lineno, fatal_error, *, fd=None, know_current_thread=True, py_fatal_error=False, garbage_collecting=False, + c_stack=True, function=''): """ Check that the fault handler for fatal errors is enabled and check the @@ -100,11 +101,12 @@ def check_error(self, code, lineno, fatal_error, *, Raise an error if the output doesn't match the expected format. """ + address_expr = "0x[0-9a-f]+" if all_threads: if know_current_thread: - header = 'Current thread 0x[0-9a-f]+' + header = f'Current thread {address_expr}' else: - header = 'Thread 0x[0-9a-f]+' + header = f'Thread {address_expr}' else: header = 'Stack' regex = [f'^{fatal_error}'] @@ -115,6 +117,9 @@ def check_error(self, code, lineno, fatal_error, *, if garbage_collecting: regex.append(' Garbage-collecting') regex.append(fr' File "", line {lineno} in {function}') + if c_stack: + regex.append("Current thread's C stack (most recent call first):") + regex.append(r" (\/.+\(\+.+\) \[0x[0-9a-f]+\])|(<.+>)") regex = '\n'.join(regex) if other_regex: From e1aa6192359380cf3e732e73f8e797432ac24e66 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:32:02 -0500 Subject: [PATCH 10/51] Add blurb from whatsnew. --- .../Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst diff --git a/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst b/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst new file mode 100644 index 00000000000000..0978081fbd0638 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst @@ -0,0 +1,3 @@ +Add support for printing the C stack trace on systems that support it via +:func:`faulthandler.dump_c_stack` or via the *c_stack* argument in +:func:`faulthandler.c_stack`. From 6b515d36486b532463d816bc352a33d1c616fe99 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:37:54 -0500 Subject: [PATCH 11/51] Fix failing Sphinx build. --- Doc/library/faulthandler.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst index f75b01cbfa26df..6334ea6c6742a6 100644 --- a/Doc/library/faulthandler.rst +++ b/Doc/library/faulthandler.rst @@ -77,7 +77,7 @@ Dumping the C stack This is done with `GNU backtraces `_. - If the system doesn't support the C-level :c:expr:`backtrace()` or :c:expr:`backtrace_symbols()` functions, + If the system doesn't support the C-level ``backtrace()`` or ``backtrace_symbols`` functions, then an error message is displayed instead of the C stack. Fault handler state From c69ee9d5c8051c34a0c5efe7b2af45c9768e2236 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:39:00 -0500 Subject: [PATCH 12/51] Fix PyArg_ParseTupleAndKeywords format string. --- Modules/faulthandler.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 1ef82999940c45..2add6a61d3a620 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -340,7 +340,7 @@ faulthandler_dump_c_stack_py(PyObject *self, PyObject *file = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|Op:dump_c_stack", kwlist, + "|p:dump_c_stack", kwlist, &file)) return NULL; @@ -607,7 +607,7 @@ faulthandler_py_enable(PyObject *self, PyObject *args, PyObject *kwargs) PyThreadState *tstate; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|Op:enable", kwlist, &file, &all_threads, &c_stack)) + "|Opp:enable", kwlist, &file, &all_threads, &c_stack)) return NULL; fd = faulthandler_get_fileno(&file); From f08b1dd977eb26dd6d93c21d307cfcc547d96d81 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:45:53 -0500 Subject: [PATCH 13/51] Fix Sphinx warnings. --- Doc/whatsnew/3.14.rst | 2 +- .../next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 944d2ae6484ba9..148240fc925228 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -371,7 +371,7 @@ faulthandler ------------ * Add support for printing the C stack trace on systems that support it via - :func:`faulthandler.dump_c_stack` or via the *c_stack* argument in :func:`faulthandler.c_stack`. + :func:`faulthandler.dump_c_stack` or via the *c_stack* argument in :func:`faulthandler.enable`. fractions diff --git a/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst b/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst index 0978081fbd0638..c4d2938a50010d 100644 --- a/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst +++ b/Misc/NEWS.d/next/Library/2024-12-21-13-31-55.gh-issue-127604.etL5mf.rst @@ -1,3 +1,3 @@ Add support for printing the C stack trace on systems that support it via :func:`faulthandler.dump_c_stack` or via the *c_stack* argument in -:func:`faulthandler.c_stack`. +:func:`faulthandler.enable`. From dbb6d25e4f31a792d57d0f6fc16341d6f0dbb405 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:47:22 -0500 Subject: [PATCH 14/51] Add ignore to test_inspect --- Lib/test/test_inspect/test_inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 2c950e46b3ed8a..8d2b92b2d1f21d 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5726,7 +5726,7 @@ def test_errno_module_has_signatures(self): def test_faulthandler_module_has_signatures(self): import faulthandler - unsupported_signature = {'dump_traceback', 'dump_traceback_later', 'enable'} + unsupported_signature = {'dump_traceback', 'dump_traceback_later', 'enable', 'dump_c_traceback'} unsupported_signature |= {name for name in ['register'] if hasattr(faulthandler, name)} self._test_module_has_signatures(faulthandler, unsupported_signature=unsupported_signature) From faf1a3ec9647b314cde59001989497f27ee2d5ad Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:47:47 -0500 Subject: [PATCH 15/51] Fix name. --- Lib/test/test_inspect/test_inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 8d2b92b2d1f21d..d966796d13b7a4 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -5726,7 +5726,7 @@ def test_errno_module_has_signatures(self): def test_faulthandler_module_has_signatures(self): import faulthandler - unsupported_signature = {'dump_traceback', 'dump_traceback_later', 'enable', 'dump_c_traceback'} + unsupported_signature = {'dump_traceback', 'dump_traceback_later', 'enable', 'dump_c_stack'} unsupported_signature |= {name for name in ['register'] if hasattr(faulthandler, name)} self._test_module_has_signatures(faulthandler, unsupported_signature=unsupported_signature) From ec832aa0b7d2bec27cba4476398dcf6040ee0174 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 13:56:39 -0500 Subject: [PATCH 16/51] Ignore the reentrant variable in the C analyzer. --- Tools/c-analyzer/cpython/ignored.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index c8c30a7985aa2e..ab46fc6ffbd052 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -161,6 +161,7 @@ Python/sysmodule.c - _preinit_xoptions - # thread-safety # XXX need race protection? Modules/faulthandler.c faulthandler_dump_traceback reentrant - +Modules/faulthandler.c faulthandler_dump_c_stack reentrant - Python/pylifecycle.c _Py_FatalErrorFormat reentrant - Python/pylifecycle.c fatal_error reentrant - From 524f1674b6d707efd6a3ef334f6a42a92b50adf0 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 14:03:51 -0500 Subject: [PATCH 17/51] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Modules/faulthandler.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 2add6a61d3a620..13811a8f6d37f7 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -220,18 +220,16 @@ faulthandler_dump_traceback(int fd, int all_threads, static void faulthandler_stack_dump_impl(int fd) { -#define SIZE 128 -#define ENTRY_SIZE 256 +#define BACKTRACE_SIZE 128 +#define TRACEBACK_ENTRY_MAX_SIZE 256 void *callstack[SIZE]; int frames = backtrace(callstack, SIZE); char **strings = backtrace_symbols(callstack, SIZE); - if (strings == NULL) - { + if (strings == NULL) { PUTS(fd, " "); } else { - for (int i = 0; i < frames; ++i) - { + for (int i = 0; i < frames; ++i) { char entry_str[ENTRY_SIZE]; snprintf(entry_str, ENTRY_SIZE, " %s\n", strings[i]); size_t length = strlen(entry_str) + 1; @@ -249,8 +247,7 @@ faulthandler_stack_dump_impl(int fd) _Py_write_noraise(fd, entry_str, length); } - if (frames == SIZE) - { + if (frames == SIZE) { PUTS(fd, " "); } } @@ -282,8 +279,7 @@ faulthandler_dump_c_stack(int fd) reentrant = 1; - if (fatal_error.c_stack) - { + if (fatal_error.c_stack) { PUTS(fd, "\n"); faulthandler_dump_c_stack_nocheck(fd); } From dd08bcbdece0fe1cd0affd8507832a7c4daf5e75 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 14:05:26 -0500 Subject: [PATCH 18/51] Use manpage references. --- Doc/library/faulthandler.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst index 6334ea6c6742a6..7f9478cd9c7566 100644 --- a/Doc/library/faulthandler.rst +++ b/Doc/library/faulthandler.rst @@ -75,9 +75,7 @@ Dumping the C stack Dump the C stack trace of the current thread into *file*. - This is done with `GNU backtraces `_. - - If the system doesn't support the C-level ``backtrace()`` or ``backtrace_symbols`` functions, + If the system doesn't support the C-level :manpage:`backtrace` or :manpage:`backtrace_symbols` functions, then an error message is displayed instead of the C stack. Fault handler state From bda3dcd29370a6ba6558395cc54abdc8ccba9879 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 14:06:25 -0500 Subject: [PATCH 19/51] Add issue number to whatsnew. --- Doc/whatsnew/3.14.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 148240fc925228..55a067c10d30ae 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -372,6 +372,7 @@ faulthandler * Add support for printing the C stack trace on systems that support it via :func:`faulthandler.dump_c_stack` or via the *c_stack* argument in :func:`faulthandler.enable`. + (Contributed by Peter Bierma in :gh:`127604`.) fractions From f3fcea192f8a266fc571a2e887518c98a1d132ea Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 14:09:21 -0500 Subject: [PATCH 20/51] Rename macros. --- Modules/faulthandler.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 13811a8f6d37f7..d4cc39a3959097 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -222,37 +222,40 @@ faulthandler_stack_dump_impl(int fd) { #define BACKTRACE_SIZE 128 #define TRACEBACK_ENTRY_MAX_SIZE 256 - void *callstack[SIZE]; - int frames = backtrace(callstack, SIZE); - char **strings = backtrace_symbols(callstack, SIZE); + void *callstack[BACKTRACE_SIZE]; + int frames = backtrace(callstack, BACKTRACE_SIZE); + char **strings = backtrace_symbols(callstack, BACKTRACE_SIZE); if (strings == NULL) { PUTS(fd, " "); } else { for (int i = 0; i < frames; ++i) { - char entry_str[ENTRY_SIZE]; - snprintf(entry_str, ENTRY_SIZE, " %s\n", strings[i]); + char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; + snprintf(entry_str, TRACEBACK_ENTRY_MAX_SIZE, " %s\n", strings[i]); size_t length = strlen(entry_str) + 1; - if (length == ENTRY_SIZE) - { + if (length == TRACEBACK_ENTRY_MAX_SIZE) { /* We exceeded the size, make it look prettier */ - // Add ellipsis to last 3 characters (but before the newline and null terminator) - for (int x = 0; x < 3; ++x) - entry_str[ENTRY_SIZE - (x + 3)] = '.'; + // Add ellipsis to last 3 characters + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 5] = '.'; + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 4] = '.'; + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 3] = '.'; + + // Ensure trailing newline + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 2] = '\n'; - entry_str[ENTRY_SIZE - 2] = '\n'; - entry_str[ENTRY_SIZE - 1] = '\0'; + // Ensure that it's null-terminated + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 1] = '\0'; } _Py_write_noraise(fd, entry_str, length); } - if (frames == SIZE) { + if (frames == BACKTRACE_SIZE) { PUTS(fd, " "); } } -#undef ENTRY_SIZE -#undef SIZE +#undef BACKTRACE_SIZE +#undef TRACEBACK_ENTRY_MAX_SIZE } #else static void From db97dd278cd7e7c2c18e688cb357a6c3ff796aa6 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 14:11:40 -0500 Subject: [PATCH 21/51] Reduce number of printed calls. --- Modules/faulthandler.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index d4cc39a3959097..ed155ea271e417 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -220,13 +220,13 @@ faulthandler_dump_traceback(int fd, int all_threads, static void faulthandler_stack_dump_impl(int fd) { -#define BACKTRACE_SIZE 128 +#define BACKTRACE_SIZE 16 #define TRACEBACK_ENTRY_MAX_SIZE 256 void *callstack[BACKTRACE_SIZE]; int frames = backtrace(callstack, BACKTRACE_SIZE); char **strings = backtrace_symbols(callstack, BACKTRACE_SIZE); if (strings == NULL) { - PUTS(fd, " "); + PUTS(fd, " \n"); } else { for (int i = 0; i < frames; ++i) { @@ -251,7 +251,7 @@ faulthandler_stack_dump_impl(int fd) } if (frames == BACKTRACE_SIZE) { - PUTS(fd, " "); + PUTS(fd, " \n"); } } #undef BACKTRACE_SIZE From c17457fa67271c6d9a4d10b331d36b17850574bb Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 14:50:55 -0500 Subject: [PATCH 22/51] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/faulthandler.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst index 7f9478cd9c7566..2d309210656b25 100644 --- a/Doc/library/faulthandler.rst +++ b/Doc/library/faulthandler.rst @@ -75,8 +75,9 @@ Dumping the C stack Dump the C stack trace of the current thread into *file*. - If the system doesn't support the C-level :manpage:`backtrace` or :manpage:`backtrace_symbols` functions, - then an error message is displayed instead of the C stack. + If the system does not support the C-level :manpage:`backtrace(3)` + or :manpage:`backtrace_symbols(3)` functions, then an error message + is displayed instead of the C stack. Fault handler state ------------------- @@ -94,7 +95,7 @@ Fault handler state :ref:`issue with file descriptors `. If *c_stack* is ``True``, then the C stack trace is printed after the Python - traceback, unless the system doesn't support it. See :func:`dump_c_stack` for + traceback, unless the system does not support it. See :func:`dump_c_stack` for more information on compatibility. .. versionchanged:: 3.5 From d5f7d4b403292173c8d9d07b8914842aa2061792 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 21 Dec 2024 14:51:58 -0500 Subject: [PATCH 23/51] Address code review. --- Modules/faulthandler.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index ed155ea271e417..5a37522699ea17 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -235,15 +235,12 @@ faulthandler_stack_dump_impl(int fd) size_t length = strlen(entry_str) + 1; if (length == TRACEBACK_ENTRY_MAX_SIZE) { /* We exceeded the size, make it look prettier */ - // Add ellipsis to last 3 characters entry_str[TRACEBACK_ENTRY_MAX_SIZE - 5] = '.'; entry_str[TRACEBACK_ENTRY_MAX_SIZE - 4] = '.'; entry_str[TRACEBACK_ENTRY_MAX_SIZE - 3] = '.'; - // Ensure trailing newline entry_str[TRACEBACK_ENTRY_MAX_SIZE - 2] = '\n'; - // Ensure that it's null-terminated entry_str[TRACEBACK_ENTRY_MAX_SIZE - 1] = '\0'; } @@ -253,6 +250,8 @@ faulthandler_stack_dump_impl(int fd) if (frames == BACKTRACE_SIZE) { PUTS(fd, " \n"); } + + free(strings); } #undef BACKTRACE_SIZE #undef TRACEBACK_ENTRY_MAX_SIZE From 0c84f8a081475c345ee0772f2d7fac84ad07949c Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Dec 2024 10:36:04 -0500 Subject: [PATCH 24/51] Explicitly check for backtrace() and backtrace_symbols() --- Modules/faulthandler.c | 2 +- configure | 154 +++++++++++++++++++++++------------------ configure.ac | 6 +- pyconfig.h.in | 6 ++ 4 files changed, 98 insertions(+), 70 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 5a37522699ea17..2745ac31734893 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -216,7 +216,7 @@ faulthandler_dump_traceback(int fd, int all_threads, reentrant = 0; } -#ifdef HAVE_EXECINFO_H +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && defined(HAVE_BACKTRACE_SYMBOLS) static void faulthandler_stack_dump_impl(int fd) { diff --git a/configure b/configure index 5236b7504c188c..fa6c0516daa8c6 100755 --- a/configure +++ b/configure @@ -2301,6 +2301,68 @@ fi } # ac_fn_c_try_run +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +printf %s "checking for $2... " >&6; } +if eval test \${$3+y} +then : + printf %s "(cached) " >&6 +else $as_nop + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. */ + +#include +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main (void) +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO" +then : + eval "$3=yes" +else $as_nop + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func + # ac_fn_c_check_type LINENO TYPE VAR INCLUDES # ------------------------------------------- # Tests whether TYPE exists after having included INCLUDES, setting cache @@ -2547,68 +2609,6 @@ rm -f conftest.val } # ac_fn_c_compute_int -# ac_fn_c_check_func LINENO FUNC VAR -# ---------------------------------- -# Tests whether FUNC exists, setting the cache variable VAR accordingly -ac_fn_c_check_func () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -printf %s "checking for $2... " >&6; } -if eval test \${$3+y} -then : - printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -/* Define $2 to an innocuous variant, in case declares $2. - For example, HP-UX 11i declares gettimeofday. */ -#define $2 innocuous_$2 - -/* System header to define __stub macros and hopefully few prototypes, - which can conflict with char $2 (); below. */ - -#include -#undef $2 - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char $2 (); -/* The GNU C library defines this for functions which it implements - to always fail with ENOSYS. Some functions are actually named - something starting with __ and the normal name is an alias. */ -#if defined __stub_$2 || defined __stub___$2 -choke me -#endif - -int -main (void) -{ -return $2 (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO" -then : - eval "$3=yes" -else $as_nop - eval "$3=no" -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext -fi -eval ac_res=\$$3 - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -printf "%s\n" "$ac_res" >&6; } - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_check_func - # ac_fn_check_decl LINENO SYMBOL VAR INCLUDES EXTRA-OPTIONS FLAG-VAR # ------------------------------------------------------------------ # Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR @@ -10900,12 +10900,6 @@ if test "x$ac_cv_header_errno_h" = xyes then : printf "%s\n" "#define HAVE_ERRNO_H 1" >>confdefs.h -fi -ac_fn_c_check_header_compile "$LINENO" "execinfo.h" "ac_cv_header_execinfo_h" "$ac_includes_default" -if test "x$ac_cv_header_execinfo_h" = xyes -then : - printf "%s\n" "#define HAVE_EXECINFO_H 1" >>confdefs.h - fi ac_fn_c_check_header_compile "$LINENO" "fcntl.h" "ac_cv_header_fcntl_h" "$ac_includes_default" if test "x$ac_cv_header_fcntl_h" = xyes @@ -11558,6 +11552,30 @@ fi fi +# for faulthandler + for ac_header in execinfo.h +do : + ac_fn_c_check_header_compile "$LINENO" "execinfo.h" "ac_cv_header_execinfo_h" "$ac_includes_default" +if test "x$ac_cv_header_execinfo_h" = xyes +then : + printf "%s\n" "#define HAVE_EXECINFO_H 1" >>confdefs.h + ac_fn_c_check_func "$LINENO" "backtrace" "ac_cv_func_backtrace" +if test "x$ac_cv_func_backtrace" = xyes +then : + printf "%s\n" "#define HAVE_BACKTRACE 1" >>confdefs.h + +fi +ac_fn_c_check_func "$LINENO" "backtrace_symbols" "ac_cv_func_backtrace_symbols" +if test "x$ac_cv_func_backtrace_symbols" = xyes +then : + printf "%s\n" "#define HAVE_BACKTRACE_SYMBOLS 1" >>confdefs.h + +fi + +fi + +done + # bluetooth/bluetooth.h has been known to not compile with -std=c99. # http://permalink.gmane.org/gmane.linux.bluez.kernel/22294 SAVE_CFLAGS=$CFLAGS diff --git a/configure.ac b/configure.ac index cd98696d21d35b..432f4637f122ed 100644 --- a/configure.ac +++ b/configure.ac @@ -2929,7 +2929,7 @@ AC_DEFINE([STDC_HEADERS], [1], # checks for header files AC_CHECK_HEADERS([ \ - alloca.h asm/types.h bluetooth.h conio.h direct.h dlfcn.h endian.h errno.h execinfo.h fcntl.h grp.h \ + alloca.h asm/types.h bluetooth.h conio.h direct.h dlfcn.h endian.h errno.h fcntl.h grp.h \ io.h langinfo.h libintl.h libutil.h linux/auxvec.h sys/auxv.h linux/fs.h linux/limits.h linux/memfd.h \ linux/netfilter_ipv4.h linux/random.h linux/soundcard.h linux/sched.h \ linux/tipc.h linux/wait.h netdb.h net/ethernet.h netinet/in.h netpacket/packet.h poll.h process.h pthread.h pty.h \ @@ -2944,6 +2944,10 @@ AC_CHECK_HEADERS([ \ AC_HEADER_DIRENT AC_HEADER_MAJOR +# for faulthandler +AC_CHECK_HEADERS([execinfo.h], + [AC_CHECK_FUNCS(backtrace backtrace_symbols)]) + # bluetooth/bluetooth.h has been known to not compile with -std=c99. # http://permalink.gmane.org/gmane.linux.bluez.kernel/22294 SAVE_CFLAGS=$CFLAGS diff --git a/pyconfig.h.in b/pyconfig.h.in index 828ac02356e5e8..a1868fe0621483 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -89,6 +89,12 @@ /* Define to 1 if you have the `atanh' function. */ #undef HAVE_ATANH +/* Define to 1 if you have the `backtrace' function. */ +#undef HAVE_BACKTRACE + +/* Define to 1 if you have the `backtrace_symbols' function. */ +#undef HAVE_BACKTRACE_SYMBOLS + /* Define if you have the 'bind' function. */ #undef HAVE_BIND From 81989970c09e5ba3f3d6a838ccaebbd7a1cf6f0d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 22 Dec 2024 12:05:05 -0500 Subject: [PATCH 25/51] Handle no frames returned. --- Modules/faulthandler.c | 53 +++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 2745ac31734893..da9e599fb0ef91 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -220,39 +220,44 @@ faulthandler_dump_traceback(int fd, int all_threads, static void faulthandler_stack_dump_impl(int fd) { -#define BACKTRACE_SIZE 16 +#define BACKTRACE_SIZE 32 #define TRACEBACK_ENTRY_MAX_SIZE 256 void *callstack[BACKTRACE_SIZE]; int frames = backtrace(callstack, BACKTRACE_SIZE); + if (frames == 0) { + // Some systems won't return anything for the stack trace + PUTS(fd, " \n"); + return; + } + char **strings = backtrace_symbols(callstack, BACKTRACE_SIZE); if (strings == NULL) { - PUTS(fd, " \n"); + PUTS(fd, " \n"); + return; } - else { - for (int i = 0; i < frames; ++i) { - char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; - snprintf(entry_str, TRACEBACK_ENTRY_MAX_SIZE, " %s\n", strings[i]); - size_t length = strlen(entry_str) + 1; - if (length == TRACEBACK_ENTRY_MAX_SIZE) { - /* We exceeded the size, make it look prettier */ - // Add ellipsis to last 3 characters - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 5] = '.'; - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 4] = '.'; - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 3] = '.'; - // Ensure trailing newline - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 2] = '\n'; - // Ensure that it's null-terminated - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 1] = '\0'; - } - _Py_write_noraise(fd, entry_str, length); - } - - if (frames == BACKTRACE_SIZE) { - PUTS(fd, " \n"); + for (int i = 0; i < frames; ++i) { + char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; + snprintf(entry_str, TRACEBACK_ENTRY_MAX_SIZE, " %s\n", strings[i]); + size_t length = strlen(entry_str) + 1; + if (length == TRACEBACK_ENTRY_MAX_SIZE) { + /* We exceeded the size, make it look prettier */ + // Add ellipsis to last 3 characters + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 5] = '.'; + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 4] = '.'; + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 3] = '.'; + // Ensure trailing newline + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 2] = '\n'; + // Ensure that it's null-terminated + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 1] = '\0'; } + _Py_write_noraise(fd, entry_str, length); + } - free(strings); + if (frames == BACKTRACE_SIZE) { + PUTS(fd, " \n"); } + + free(strings); #undef BACKTRACE_SIZE #undef TRACEBACK_ENTRY_MAX_SIZE } From 0f670f0b5058799ba9a2609d89c74237457f1386 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 25 Dec 2024 15:17:50 -0500 Subject: [PATCH 26/51] Move the function that calls backtrace() to within the core. --- Include/internal/pycore_traceback.h | 4 ++ Modules/faulthandler.c | 67 +---------------------------- Python/traceback.c | 59 +++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/Include/internal/pycore_traceback.h b/Include/internal/pycore_traceback.h index 10922bff98bd4b..f5050b95bc78dc 100644 --- a/Include/internal/pycore_traceback.h +++ b/Include/internal/pycore_traceback.h @@ -100,6 +100,10 @@ extern int _PyTraceBack_Print( extern int _Py_WriteIndentedMargin(int, const char*, PyObject *); extern int _Py_WriteIndent(int, PyObject *); +// Export for the faulthandler module +PyAPI_FUNC(void) +_Py_DumpStack(int fd); + #ifdef __cplusplus } #endif diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index da9e599fb0ef91..de9d2b995a7126 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -10,9 +10,6 @@ #ifdef HAVE_UNISTD_H # include // _exit() #endif -#ifdef HAVE_EXECINFO_H -# include // backtrace(), backtrace_symbols() -#endif #include // sigaction() #include // abort() @@ -216,66 +213,6 @@ faulthandler_dump_traceback(int fd, int all_threads, reentrant = 0; } -#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && defined(HAVE_BACKTRACE_SYMBOLS) -static void -faulthandler_stack_dump_impl(int fd) -{ -#define BACKTRACE_SIZE 32 -#define TRACEBACK_ENTRY_MAX_SIZE 256 - void *callstack[BACKTRACE_SIZE]; - int frames = backtrace(callstack, BACKTRACE_SIZE); - if (frames == 0) { - // Some systems won't return anything for the stack trace - PUTS(fd, " \n"); - return; - } - - char **strings = backtrace_symbols(callstack, BACKTRACE_SIZE); - if (strings == NULL) { - PUTS(fd, " \n"); - return; - } - for (int i = 0; i < frames; ++i) { - char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; - snprintf(entry_str, TRACEBACK_ENTRY_MAX_SIZE, " %s\n", strings[i]); - size_t length = strlen(entry_str) + 1; - if (length == TRACEBACK_ENTRY_MAX_SIZE) { - /* We exceeded the size, make it look prettier */ - // Add ellipsis to last 3 characters - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 5] = '.'; - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 4] = '.'; - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 3] = '.'; - // Ensure trailing newline - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 2] = '\n'; - // Ensure that it's null-terminated - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 1] = '\0'; - } - _Py_write_noraise(fd, entry_str, length); - } - - if (frames == BACKTRACE_SIZE) { - PUTS(fd, " \n"); - } - - free(strings); -#undef BACKTRACE_SIZE -#undef TRACEBACK_ENTRY_MAX_SIZE -} -#else -static void -faulthandler_stack_dump_impl(int fd) -{ - PUTS(fd, " \n"); -} -#endif - -static void -faulthandler_dump_c_stack_nocheck(int fd) -{ - PUTS(fd, "Current thread's C stack trace (most recent call first):\n"); - faulthandler_stack_dump_impl(fd); -} - static void faulthandler_dump_c_stack(int fd) { @@ -288,7 +225,7 @@ faulthandler_dump_c_stack(int fd) if (fatal_error.c_stack) { PUTS(fd, "\n"); - faulthandler_dump_c_stack_nocheck(fd); + _Py_DumpStack(fd); } reentrant = 0; @@ -352,7 +289,7 @@ faulthandler_dump_c_stack_py(PyObject *self, return NULL; } - faulthandler_dump_c_stack_nocheck(fd); + _Py_DumpStack(fd); if (PyErr_CheckSignals()) { return NULL; diff --git a/Python/traceback.c b/Python/traceback.c index e819909b6045c3..4688dd7990c01c 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -21,6 +21,9 @@ #ifdef HAVE_UNISTD_H # include // lseek() #endif +#ifdef HAVE_EXECINFO_H +# include // backtrace(), backtrace_symbols() +#endif #define OFF(x) offsetof(PyTracebackObject, x) @@ -1101,3 +1104,59 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, return NULL; } +/* This is for faulthandler. + * Apparently, backtrace() doesn't play well across DLL boundaries on macOS */ +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && defined(HAVE_BACKTRACE_SYMBOLS) +void +_Py_DumpStack(int fd) +{ +#define BACKTRACE_SIZE 32 +#define TRACEBACK_ENTRY_MAX_SIZE 256 + PUTS(fd, "Current thread's C stack trace (most recent call first):\n"); + void *callstack[BACKTRACE_SIZE]; + int frames = backtrace(callstack, BACKTRACE_SIZE); + if (frames == 0) { + // Some systems won't return anything for the stack trace + PUTS(fd, " \n"); + return; + } + + char **strings = backtrace_symbols(callstack, BACKTRACE_SIZE); + if (strings == NULL) { + PUTS(fd, " \n"); + return; + } + for (int i = 0; i < frames; ++i) { + char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; + snprintf(entry_str, TRACEBACK_ENTRY_MAX_SIZE, " %s\n", strings[i]); + size_t length = strlen(entry_str) + 1; + if (length == TRACEBACK_ENTRY_MAX_SIZE) { + /* We exceeded the size, make it look prettier */ + // Add ellipsis to last 3 characters + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 5] = '.'; + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 4] = '.'; + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 3] = '.'; + // Ensure trailing newline + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 2] = '\n'; + // Ensure that it's null-terminated + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 1] = '\0'; + } + _Py_write_noraise(fd, entry_str, length); + } + + if (frames == BACKTRACE_SIZE) { + PUTS(fd, " \n"); + } + + free(strings); +#undef BACKTRACE_SIZE +#undef TRACEBACK_ENTRY_MAX_SIZE +} +#else +void +_Py_DumpStack(int fd) +{ + PUTS(fd, "Current thread's C stack trace (most recent call first):\n"); + PUTS(fd, " \n"); +} +#endif From 892a08530b0e468277dda5c0f487682d44803ed6 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 26 Jan 2025 14:36:58 -0500 Subject: [PATCH 27/51] Fix test case. --- Lib/test/test_faulthandler.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index 7ba1faaaea646a..f455e8dd37e5c6 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -122,16 +122,15 @@ def check_error(self, code, lineno, fatal_error, *, regex.append(fr'{header} \(most recent call first\):') if garbage_collecting: regex.append(' Garbage-collecting') - regex.append(fr' File "", line {lineno} in {function}') - if c_stack: - regex.append("Current thread's C stack (most recent call first):") - regex.append(r" (\/.+\(\+.+\) \[0x[0-9a-f]+\])|(<.+>)") if support.Py_GIL_DISABLED and py_fatal_error and not know_current_thread: regex.append(" ") else: if garbage_collecting and not all_threads_disabled: regex.append(' Garbage-collecting') regex.append(fr' File "", line {lineno} in {function}') + if c_stack: + regex.append("Current thread's C stack (most recent call first):") + regex.append(r" (\/.+\(\+.+\) \[0x[0-9a-f]+\])|(<.+>)") regex = '\n'.join(regex) if other_regex: From 896abd1d34c09d6ce4ab1be176eaf9479e6a806a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 26 Jan 2025 14:45:37 -0500 Subject: [PATCH 28/51] Run make regen-configure --- configure | 80 ++++++--------------------------------------------- pyconfig.h.in | 4 +-- 2 files changed, 11 insertions(+), 73 deletions(-) diff --git a/configure b/configure index 753a54710d5308..32f8778a9cda7f 100755 --- a/configure +++ b/configure @@ -2315,15 +2315,15 @@ printf %s "checking for $2... " >&6; } if eval test \${$3+y} then : printf %s "(cached) " >&6 -else $as_nop - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ /* Define $2 to an innocuous variant, in case declares $2. For example, HP-UX 11i declares gettimeofday. */ #define $2 innocuous_$2 /* System header to define __stub macros and hopefully few prototypes, - which can conflict with char $2 (); below. */ + which can conflict with char $2 (void); below. */ #include #undef $2 @@ -2334,7 +2334,7 @@ else $as_nop #ifdef __cplusplus extern "C" #endif -char $2 (); +char $2 (void); /* The GNU C library defines this for functions which it implements to always fail with ENOSYS. Some functions are actually named something starting with __ and the normal name is an alias. */ @@ -2353,11 +2353,13 @@ _ACEOF if ac_fn_c_try_link "$LINENO" then : eval "$3=yes" -else $as_nop - eval "$3=no" +else case e in #( + e) eval "$3=no" ;; +esac fi rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext + conftest$ac_exeext conftest.$ac_ext ;; +esac fi eval ac_res=\$$3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 @@ -2620,70 +2622,6 @@ rm -f conftest.val } # ac_fn_c_compute_int -# ac_fn_c_check_func LINENO FUNC VAR -# ---------------------------------- -# Tests whether FUNC exists, setting the cache variable VAR accordingly -ac_fn_c_check_func () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -printf %s "checking for $2... " >&6; } -if eval test \${$3+y} -then : - printf %s "(cached) " >&6 -else case e in #( - e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -/* Define $2 to an innocuous variant, in case declares $2. - For example, HP-UX 11i declares gettimeofday. */ -#define $2 innocuous_$2 - -/* System header to define __stub macros and hopefully few prototypes, - which can conflict with char $2 (void); below. */ - -#include -#undef $2 - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char $2 (void); -/* The GNU C library defines this for functions which it implements - to always fail with ENOSYS. Some functions are actually named - something starting with __ and the normal name is an alias. */ -#if defined __stub_$2 || defined __stub___$2 -choke me -#endif - -int -main (void) -{ -return $2 (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO" -then : - eval "$3=yes" -else case e in #( - e) eval "$3=no" ;; -esac -fi -rm -f core conftest.err conftest.$ac_objext conftest.beam \ - conftest$ac_exeext conftest.$ac_ext ;; -esac -fi -eval ac_res=\$$3 - { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -printf "%s\n" "$ac_res" >&6; } - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_c_check_func - # ac_fn_check_decl LINENO SYMBOL VAR INCLUDES EXTRA-OPTIONS FLAG-VAR # ------------------------------------------------------------------ # Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR diff --git a/pyconfig.h.in b/pyconfig.h.in index 058681e0ff78d8..d01038ae0e1647 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -89,10 +89,10 @@ /* Define to 1 if you have the 'atanh' function. */ #undef HAVE_ATANH -/* Define to 1 if you have the `backtrace' function. */ +/* Define to 1 if you have the 'backtrace' function. */ #undef HAVE_BACKTRACE -/* Define to 1 if you have the `backtrace_symbols' function. */ +/* Define to 1 if you have the 'backtrace_symbols' function. */ #undef HAVE_BACKTRACE_SYMBOLS /* Define if you have the 'bind' function. */ From cab079efab230602004637ba09f0680a96c9dc0d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 27 Jan 2025 10:08:27 -0500 Subject: [PATCH 29/51] Extract behavior in own function. --- Python/traceback.c | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index 4a5917d1f37071..8f8ac7bcc6a773 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1104,6 +1104,27 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, return NULL; } +#define TRACEBACK_ENTRY_MAX_SIZE 256 + +static void +format_entry(char *entry_str, const char *the_entry, Py_ssize_t *length_ptr) +{ + int length = PyOS_snprintf(entry_str, TRACEBACK_ENTRY_MAX_SIZE, " %s\n", the_entry); + if (length == TRACEBACK_ENTRY_MAX_SIZE) { + /* We exceeded the size, make it look prettier */ + // Add ellipsis to last 3 characters + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 5] = '.'; + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 4] = '.'; + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 3] = '.'; + // Ensure trailing newline + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 2] = '\n'; + // Ensure that it's null-terminated + entry_str[TRACEBACK_ENTRY_MAX_SIZE - 1] = '\0'; + } + + *length_ptr = (Py_ssize_t)length; +} + /* This is for faulthandler. * Apparently, backtrace() doesn't play well across DLL boundaries on macOS */ #if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && defined(HAVE_BACKTRACE_SYMBOLS) @@ -1111,7 +1132,6 @@ void _Py_DumpStack(int fd) { #define BACKTRACE_SIZE 32 -#define TRACEBACK_ENTRY_MAX_SIZE 256 PUTS(fd, "Current thread's C stack trace (most recent call first):\n"); void *callstack[BACKTRACE_SIZE]; int frames = backtrace(callstack, BACKTRACE_SIZE); @@ -1128,19 +1148,8 @@ _Py_DumpStack(int fd) } for (int i = 0; i < frames; ++i) { char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; - snprintf(entry_str, TRACEBACK_ENTRY_MAX_SIZE, " %s\n", strings[i]); - size_t length = strlen(entry_str) + 1; - if (length == TRACEBACK_ENTRY_MAX_SIZE) { - /* We exceeded the size, make it look prettier */ - // Add ellipsis to last 3 characters - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 5] = '.'; - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 4] = '.'; - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 3] = '.'; - // Ensure trailing newline - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 2] = '\n'; - // Ensure that it's null-terminated - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 1] = '\0'; - } + Py_ssize_t length; + format_entry(entry_str, strings[i], &length); _Py_write_noraise(fd, entry_str, length); } From 178407108e3322a227e8805c3c286de302e8db1f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 27 Jan 2025 10:09:02 -0500 Subject: [PATCH 30/51] PEP-7 --- Modules/faulthandler.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 69b9c6d694bc05..7281447ec68e15 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -224,8 +224,9 @@ faulthandler_dump_c_stack(int fd) { static volatile int reentrant = 0; - if (reentrant) + if (reentrant) { return; + } reentrant = 1; From 3304e2a1a04aba5e864678c3482cf30a2402ef2f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 4 Feb 2025 11:54:05 -0500 Subject: [PATCH 31/51] Update pycore_traceback.h Co-authored-by: Victor Stinner --- Include/internal/pycore_traceback.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Include/internal/pycore_traceback.h b/Include/internal/pycore_traceback.h index f5050b95bc78dc..24a51d47eb7a40 100644 --- a/Include/internal/pycore_traceback.h +++ b/Include/internal/pycore_traceback.h @@ -101,8 +101,7 @@ extern int _Py_WriteIndentedMargin(int, const char*, PyObject *); extern int _Py_WriteIndent(int, PyObject *); // Export for the faulthandler module -PyAPI_FUNC(void) -_Py_DumpStack(int fd); +PyAPI_FUNC(void) _Py_DumpStack(int fd); #ifdef __cplusplus } From aa97f2447a837bf1150ffe87ce0189902154b856 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 5 Feb 2025 17:00:01 -0500 Subject: [PATCH 32/51] Use address_expr --- Lib/test/test_faulthandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index f455e8dd37e5c6..522ca24d2219ae 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -130,7 +130,7 @@ def check_error(self, code, lineno, fatal_error, *, regex.append(fr' File "", line {lineno} in {function}') if c_stack: regex.append("Current thread's C stack (most recent call first):") - regex.append(r" (\/.+\(\+.+\) \[0x[0-9a-f]+\])|(<.+>)") + regex.append(fr" (\/.+\(\+.+\) \[{address_expr}\])|(<.+>)") regex = '\n'.join(regex) if other_regex: From 50b4964c96e73ba787b72c7ba74b0fdbd559dc86 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 5 Feb 2025 17:02:03 -0500 Subject: [PATCH 33/51] Fix wrong argument parsing keycode. --- Modules/faulthandler.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 7281447ec68e15..d6f2d14f4ecb74 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -286,13 +286,13 @@ faulthandler_dump_traceback_py(PyObject *self, static PyObject * faulthandler_dump_c_stack_py(PyObject *self, - PyObject *args, PyObject *kwargs) + PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"file", NULL}; PyObject *file = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|p:dump_c_stack", kwlist, + "|O:dump_c_stack", kwlist, &file)) return NULL; From 533b1db6e32f43597e2d7d0e83a9bedeb25ca780 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Wed, 5 Feb 2025 17:40:15 -0500 Subject: [PATCH 34/51] Add tests for dump_c_stack() --- Lib/test/test_faulthandler.py | 45 +++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index 522ca24d2219ae..782986d56500b5 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -45,6 +45,13 @@ def temporary_filename(): finally: os_helper.unlink(filename) + +ADDRESS_EXPR = "0x[0-9a-f]+" +C_STACK_REGEX = [ + r"Current thread's C stack trace \(most recent call first\):", + fr" ((\/.+)+\(.*\+{ADDRESS_EXPR}\) \[{ADDRESS_EXPR}\])|(<.+>)" +] + class FaultHandlerTests(unittest.TestCase): def get_output(self, code, filename=None, fd=None): @@ -101,16 +108,15 @@ def check_error(self, code, lineno, fatal_error, *, Raise an error if the output doesn't match the expected format. """ - address_expr = "0x[0-9a-f]+" all_threads_disabled = ( all_threads and (not sys._is_gil_enabled()) ) if all_threads and not all_threads_disabled: if know_current_thread: - header = f'Current thread {address_expr}' + header = f'Current thread {ADDRESS_EXPR}' else: - header = f'Thread {address_expr}' + header = f'Thread {ADDRESS_EXPR}' else: header = 'Stack' regex = [f'^{fatal_error}'] @@ -129,8 +135,7 @@ def check_error(self, code, lineno, fatal_error, *, regex.append(' Garbage-collecting') regex.append(fr' File "", line {lineno} in {function}') if c_stack: - regex.append("Current thread's C stack (most recent call first):") - regex.append(fr" (\/.+\(\+.+\) \[{address_expr}\])|(<.+>)") + regex.extend(C_STACK_REGEX) regex = '\n'.join(regex) if other_regex: @@ -941,5 +946,35 @@ def run(self): _, exitcode = self.get_output(code) self.assertEqual(exitcode, 0) + def check_c_stack(self, output): + starting_line = output.pop(0) + self.assertRegex(starting_line, C_STACK_REGEX[0]) + self.assertGreater(len(output), 0) + + for line in output: + with self.subTest(line=line): + if line != '': # Ignore trailing or leading newlines + self.assertRegex(line, C_STACK_REGEX[1]) + + + def test_dump_c_stack(self): + code = dedent(""" + import faulthandler + faulthandler.dump_c_stack() + """) + output, exitcode = self.get_output(code) + self.assertEqual(exitcode, 0) + self.check_c_stack(output) + + + def test_dump_c_stack_file(self): + import tempfile + + with tempfile.TemporaryFile("w+") as tmp: + faulthandler.dump_c_stack(file=tmp) + tmp.flush() # Just in case + tmp.seek(0) + self.check_c_stack(tmp.read().split("\n")) + if __name__ == "__main__": unittest.main() From 58b358068a0911ccfd096a771542dc0e2c1e09e1 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 25 Feb 2025 19:29:24 -0500 Subject: [PATCH 35/51] Start homemade implementation of backtrace_symbols() --- Python/traceback.c | 87 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/Python/traceback.c b/Python/traceback.c index 8f8ac7bcc6a773..cf4f9450b8fc05 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1127,7 +1127,92 @@ format_entry(char *entry_str, const char *the_entry, Py_ssize_t *length_ptr) /* This is for faulthandler. * Apparently, backtrace() doesn't play well across DLL boundaries on macOS */ -#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && defined(HAVE_BACKTRACE_SYMBOLS) +#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) + +#if __ELF_NATIVE_CLASS == 32 +# define WORD_WIDTH 8 +#else +/* We assume 64bits. */ +# define WORD_WIDTH 16 +#endif +void +py_backtrace_symbols(void *const *array, Py_ssize_t size, char **result, Py_ssize_t result_size) +{ + Dl_info info[size]; + int status[size]; + Py_ssize_t total = 0; + char **result; + /* Fill in the information we can get from `dladdr'. */ + for (Py_ssize_t i = 0; i < size; ++i) + { + struct link_map *map; + status[i] = dladdr1(array[i], &info[i], &map, RTLD_DL_LINKMAP); + if (status[i] && info[i].dli_fname && info[i].dli_fname[0] != '\0') { + /* We have some info, compute the length of the string which will be + "(+offset) [address]. */ + total += (strlen (info[i].dli_fname ?: "") + + strlen (info[i].dli_sname ?: "") + + 3 + WORD_WIDTH + 3 + WORD_WIDTH + 5); + /* The load bias is more useful to the user than the load + address. The use of these addresses is to calculate an + address in the ELF file, so its prelinked bias is not + something we want to subtract out. */ + info[i].dli_fbase = (void *) map->l_addr; + } + else { + total += 5 + WORD_WIDTH; + } + } + /* Allocate memory for the result. */ + result = (char **) malloc(size * sizeof (char *) + total); + if (result == NULL) { + return NULL; + } + char *last = (char *) (result + size); + for (Py_ssize_t i = 0; i < size; ++i) + { + result[i] = last; + if (status[i] + && info[i].dli_fname != NULL + && info[i].dli_fname[0] != '\0') { + if (info[i].dli_sname == NULL) { + /* We found no symbol name to use, so describe it as + relative to the file. */ + info[i].dli_saddr = info[i].dli_fbase; + } + if (info[i].dli_sname == NULL + && info[i].dli_saddr == 0) { + last += 1 + sprintf(last, "%s(%s) [%p]", + info[i].dli_fname ?: "", + info[i].dli_sname ?: "", + array[i]); + } + else { + char sign; + ptrdiff_t offset; + if (array[i] >= (void *)info[i].dli_saddr) { + sign = '+'; + offset = array[i] - info[i].dli_saddr; + } + else { + sign = '-'; + offset = info[i].dli_saddr - array[i]; + } + last += 1 + sprintf(last, + "%s(%s%c%#tx) [%p]", + info[i].dli_fname ?: "", + info[i].dli_sname ?: "", + sign, offset, array[i]); + } + } + else { + last += 1 + sprintf(last, "[%p]", array[i]); + } + } + assert(last <= (char *) result + size * sizeof (char *) + total); + return result; +} + void _Py_DumpStack(int fd) { From 95833e7d1256ce7b9d25b776790a3e0ffe8cf017 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 08:32:48 -0400 Subject: [PATCH 36/51] Untested implementation. --- Python/traceback.c | 92 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index cc401b817ddc70..8019b211052ddd 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1278,6 +1278,92 @@ py_backtrace_symbols(void *const *array, Py_ssize_t size, char **result, Py_ssiz return result; } +#if __ELF_NATIVE_CLASS == 32 +# define WORD_WIDTH 8 +#else +/* We assume 64bits. */ +# define WORD_WIDTH 16 +#endif + +/* Based on glibc's implementation of backtrace_symbols(), but only uses stack memory. */ +char ** +_Py_backtrace_symbols(void *const *array, Py_ssize_t size) +{ + Dl_info info[size]; + int status[size]; + Py_ssize_t total = 0; + char **result; + /* Fill in the information we can get from `dladdr'. */ + for (Py_ssize_t i = 0; i < size; ++i) + { + struct link_map *map; + status[i] = dladdr1(array[i], &info[i], &map, RTLD_DL_LINKMAP); + if (status[i] + && info[i].dli_fname + && info[i].dli_fname[0] != '\0') { + /* We have some info, compute the length of the string which will be + "(+offset) [address]. */ + total += (strlen (info[cnt].dli_fname ?: "") + + strlen (info[cnt].dli_sname ?: "") + + 3 + WORD_WIDTH + 3 + WORD_WIDTH + 5); + /* The load bias is more useful to the user than the load + address. The use of these addresses is to calculate an + address in the ELF file, so its prelinked bias is not + something we want to subtract out. */ + info[cnt].dli_fbase = (void *) map->l_addr; + } + else { + total += 5 + WORD_WIDTH; + } + } + char **result[size + total]; + char *last = (char *) (result + size); + for (Py_ssize_t i = 0; i < size; ++i) { + result[i] = last; + if (status[i] == NULL + || info[i].dli_fname == NULL + || info[i].dli_fname[0] == '\0' + ) { + last += 1 + sprintf(last, "[%p]", array[i]); + continue; + } + + if (info[i].dli_sname == NULL) { + /* We found no symbol name to use, so describe it as + relative to the file. */ + info[i].dli_saddr = info[cnt].dli_fbase; + } + + if (info[i].dli_sname == NULL + && info[i].dli_saddr == 0) { + last += 1 + sprintf(last, "%s(%s) [%p]", + info[i].dli_fname ?: "", + info[i].dli_sname ?: "", + array[i]); + } + else { + char sign; + ptrdiff_t offset; + if (array[i] >= (void *) info[i].dli_saddr) { + sign = '+'; + offset = array[i] - info[i].dli_saddr; + } + else { + sign = '-'; + offset = info[i].dli_saddr - array[i]; + } + last += 1 + sprintf(last, "%s(%s%c%#tx) [%p]", + info[i].dli_fname ?: "", + info[cnt].dli_sname ?: "", + sign, offset, array[i]); + } + assert (last <= (char *) result + size + total); + } + + return result; +} +#undef WORD_WIDTH + void _Py_DumpStack(int fd) { @@ -1291,11 +1377,7 @@ _Py_DumpStack(int fd) return; } - char **strings = backtrace_symbols(callstack, BACKTRACE_SIZE); - if (strings == NULL) { - PUTS(fd, " \n"); - return; - } + char **strings = _Py_backtrace_symbols(callstack, BACKTRACE_SIZE); for (int i = 0; i < frames; ++i) { char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; Py_ssize_t length; From 3e2701d75657ddb4ea4482e27db865e8b0232c67 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 08:47:09 -0400 Subject: [PATCH 37/51] Sorta works. --- Python/traceback.c | 139 ++++++++------------------------------------- 1 file changed, 25 insertions(+), 114 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index 8019b211052ddd..dacfecc19795c7 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -21,7 +21,8 @@ #ifdef HAVE_EXECINFO_H # include // backtrace(), backtrace_symbols() #endif - +#include +#include #define OFF(x) offsetof(PyTracebackObject, x) #define PUTS(fd, str) (void)_Py_write_noraise(fd, str, strlen(str)) @@ -1169,7 +1170,7 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, return NULL; } -#define TRACEBACK_ENTRY_MAX_SIZE 256 +#define TRACEBACK_ENTRY_MAX_SIZE 128 static void format_entry(char *entry_str, const char *the_entry, Py_ssize_t *length_ptr) @@ -1190,94 +1191,6 @@ format_entry(char *entry_str, const char *the_entry, Py_ssize_t *length_ptr) *length_ptr = (Py_ssize_t)length; } -/* This is for faulthandler. - * Apparently, backtrace() doesn't play well across DLL boundaries on macOS */ -#if defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) - -#if __ELF_NATIVE_CLASS == 32 -# define WORD_WIDTH 8 -#else -/* We assume 64bits. */ -# define WORD_WIDTH 16 -#endif -void -py_backtrace_symbols(void *const *array, Py_ssize_t size, char **result, Py_ssize_t result_size) -{ - Dl_info info[size]; - int status[size]; - Py_ssize_t total = 0; - char **result; - /* Fill in the information we can get from `dladdr'. */ - for (Py_ssize_t i = 0; i < size; ++i) - { - struct link_map *map; - status[i] = dladdr1(array[i], &info[i], &map, RTLD_DL_LINKMAP); - if (status[i] && info[i].dli_fname && info[i].dli_fname[0] != '\0') { - /* We have some info, compute the length of the string which will be - "(+offset) [address]. */ - total += (strlen (info[i].dli_fname ?: "") - + strlen (info[i].dli_sname ?: "") - + 3 + WORD_WIDTH + 3 + WORD_WIDTH + 5); - /* The load bias is more useful to the user than the load - address. The use of these addresses is to calculate an - address in the ELF file, so its prelinked bias is not - something we want to subtract out. */ - info[i].dli_fbase = (void *) map->l_addr; - } - else { - total += 5 + WORD_WIDTH; - } - } - /* Allocate memory for the result. */ - result = (char **) malloc(size * sizeof (char *) + total); - if (result == NULL) { - return NULL; - } - char *last = (char *) (result + size); - for (Py_ssize_t i = 0; i < size; ++i) - { - result[i] = last; - if (status[i] - && info[i].dli_fname != NULL - && info[i].dli_fname[0] != '\0') { - if (info[i].dli_sname == NULL) { - /* We found no symbol name to use, so describe it as - relative to the file. */ - info[i].dli_saddr = info[i].dli_fbase; - } - if (info[i].dli_sname == NULL - && info[i].dli_saddr == 0) { - last += 1 + sprintf(last, "%s(%s) [%p]", - info[i].dli_fname ?: "", - info[i].dli_sname ?: "", - array[i]); - } - else { - char sign; - ptrdiff_t offset; - if (array[i] >= (void *)info[i].dli_saddr) { - sign = '+'; - offset = array[i] - info[i].dli_saddr; - } - else { - sign = '-'; - offset = info[i].dli_saddr - array[i]; - } - last += 1 + sprintf(last, - "%s(%s%c%#tx) [%p]", - info[i].dli_fname ?: "", - info[i].dli_sname ?: "", - sign, offset, array[i]); - } - } - else { - last += 1 + sprintf(last, "[%p]", array[i]); - } - } - assert(last <= (char *) result + size * sizeof (char *) + total); - return result; -} - #if __ELF_NATIVE_CLASS == 32 # define WORD_WIDTH 8 #else @@ -1286,60 +1199,59 @@ py_backtrace_symbols(void *const *array, Py_ssize_t size, char **result, Py_ssiz #endif /* Based on glibc's implementation of backtrace_symbols(), but only uses stack memory. */ -char ** -_Py_backtrace_symbols(void *const *array, Py_ssize_t size) +void +_Py_backtrace_symbols(void *const *array, Py_ssize_t size, + Py_ssize_t line_size, char **result) { Dl_info info[size]; int status[size]; Py_ssize_t total = 0; - char **result; /* Fill in the information we can get from `dladdr'. */ for (Py_ssize_t i = 0; i < size; ++i) { struct link_map *map; - status[i] = dladdr1(array[i], &info[i], &map, RTLD_DL_LINKMAP); + status[i] = dladdr1(array[i], &info[i], (void **)&map, RTLD_DL_LINKMAP); if (status[i] && info[i].dli_fname && info[i].dli_fname[0] != '\0') { /* We have some info, compute the length of the string which will be "(+offset) [address]. */ - total += (strlen (info[cnt].dli_fname ?: "") - + strlen (info[cnt].dli_sname ?: "") + total += (strlen (info[i].dli_fname ?: "") + + strlen (info[i].dli_sname ?: "") + 3 + WORD_WIDTH + 3 + WORD_WIDTH + 5); /* The load bias is more useful to the user than the load address. The use of these addresses is to calculate an address in the ELF file, so its prelinked bias is not something we want to subtract out. */ - info[cnt].dli_fbase = (void *) map->l_addr; + info[i].dli_fbase = (void *) map->l_addr; } else { total += 5 + WORD_WIDTH; } } - char **result[size + total]; char *last = (char *) (result + size); for (Py_ssize_t i = 0; i < size; ++i) { result[i] = last; - if (status[i] == NULL + if (!status[i] || info[i].dli_fname == NULL || info[i].dli_fname[0] == '\0' ) { - last += 1 + sprintf(last, "[%p]", array[i]); + last += 1 + PyOS_snprintf(last, line_size, "[%p]", array[i]); continue; } if (info[i].dli_sname == NULL) { /* We found no symbol name to use, so describe it as relative to the file. */ - info[i].dli_saddr = info[cnt].dli_fbase; + info[i].dli_saddr = info[i].dli_fbase; } if (info[i].dli_sname == NULL && info[i].dli_saddr == 0) { - last += 1 + sprintf(last, "%s(%s) [%p]", - info[i].dli_fname ?: "", - info[i].dli_sname ?: "", - array[i]); + last += 1 + PyOS_snprintf(last, line_size, "%s(%s) [%p]", + info[i].dli_fname ?: "", + info[i].dli_sname ?: "", + array[i]); } else { char sign; @@ -1352,15 +1264,12 @@ _Py_backtrace_symbols(void *const *array, Py_ssize_t size) sign = '-'; offset = info[i].dli_saddr - array[i]; } - last += 1 + sprintf(last, "%s(%s%c%#tx) [%p]", - info[i].dli_fname ?: "", - info[cnt].dli_sname ?: "", - sign, offset, array[i]); + last += 1 + PyOS_snprintf(last, line_size, "%s(%s%c%#tx) [%p]", + info[i].dli_fname ?: "", + info[i].dli_sname ?: "", + sign, offset, array[i]); } - assert (last <= (char *) result + size + total); } - - return result; } #undef WORD_WIDTH @@ -1377,7 +1286,8 @@ _Py_DumpStack(int fd) return; } - char **strings = _Py_backtrace_symbols(callstack, BACKTRACE_SIZE); + char *strings[BACKTRACE_SIZE]; + _Py_backtrace_symbols(callstack, BACKTRACE_SIZE, TRACEBACK_ENTRY_MAX_SIZE, strings); for (int i = 0; i < frames; ++i) { char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; Py_ssize_t length; @@ -1389,10 +1299,10 @@ _Py_DumpStack(int fd) PUTS(fd, " \n"); } - free(strings); #undef BACKTRACE_SIZE #undef TRACEBACK_ENTRY_MAX_SIZE } +/* #else void _Py_DumpStack(int fd) @@ -1401,3 +1311,4 @@ _Py_DumpStack(int fd) PUTS(fd, " \n"); } #endif +*/ From 56127faf163937f7ea0ebb12cd5371460b1705f4 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 09:14:47 -0400 Subject: [PATCH 38/51] NULL-initialize the arrays. --- Python/traceback.c | 48 ++++++++++++++++------------------------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index dacfecc19795c7..dab5d704b2f12a 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1191,48 +1191,33 @@ format_entry(char *entry_str, const char *the_entry, Py_ssize_t *length_ptr) *length_ptr = (Py_ssize_t)length; } -#if __ELF_NATIVE_CLASS == 32 -# define WORD_WIDTH 8 -#else -/* We assume 64bits. */ -# define WORD_WIDTH 16 -#endif - /* Based on glibc's implementation of backtrace_symbols(), but only uses stack memory. */ void _Py_backtrace_symbols(void *const *array, Py_ssize_t size, Py_ssize_t line_size, char **result) { - Dl_info info[size]; - int status[size]; - Py_ssize_t total = 0; - /* Fill in the information we can get from `dladdr'. */ + Dl_info info[size] = {}; + int status[size] = {}; + /* Fill in the information we can get from dladdr() */ for (Py_ssize_t i = 0; i < size; ++i) { struct link_map *map; + assert(array[i] != NULL); status[i] = dladdr1(array[i], &info[i], (void **)&map, RTLD_DL_LINKMAP); - if (status[i] - && info[i].dli_fname + if (status[i] != 0 + && info[i].dli_fname != NULL && info[i].dli_fname[0] != '\0') { - /* We have some info, compute the length of the string which will be - "(+offset) [address]. */ - total += (strlen (info[i].dli_fname ?: "") - + strlen (info[i].dli_sname ?: "") - + 3 + WORD_WIDTH + 3 + WORD_WIDTH + 5); - /* The load bias is more useful to the user than the load - address. The use of these addresses is to calculate an - address in the ELF file, so its prelinked bias is not - something we want to subtract out. */ - info[i].dli_fbase = (void *) map->l_addr; - } - else { - total += 5 + WORD_WIDTH; + /* The load bias is more useful to the user than the load + address. The use of these addresses is to calculate an + address in the ELF file, so its prelinked bias is not + something we want to subtract out */ + info[i].dli_fbase = (void *) map->l_addr; } } char *last = (char *) (result + size); for (Py_ssize_t i = 0; i < size; ++i) { result[i] = last; - if (!status[i] + if (status[i] == 0 || info[i].dli_fname == NULL || info[i].dli_fname[0] == '\0' ) { @@ -1242,13 +1227,13 @@ _Py_backtrace_symbols(void *const *array, Py_ssize_t size, if (info[i].dli_sname == NULL) { /* We found no symbol name to use, so describe it as - relative to the file. */ + relative to the file. */ info[i].dli_saddr = info[i].dli_fbase; } if (info[i].dli_sname == NULL && info[i].dli_saddr == 0) { - last += 1 + PyOS_snprintf(last, line_size, "%s(%s) [%p]", + last += 1 + PyOS_snprintf("%s(%s) [%p]", info[i].dli_fname ?: "", info[i].dli_sname ?: "", array[i]); @@ -1271,14 +1256,13 @@ _Py_backtrace_symbols(void *const *array, Py_ssize_t size, } } } -#undef WORD_WIDTH void _Py_DumpStack(int fd) { #define BACKTRACE_SIZE 32 PUTS(fd, "Current thread's C stack trace (most recent call first):\n"); - void *callstack[BACKTRACE_SIZE]; + void *callstack[BACKTRACE_SIZE] = {}; int frames = backtrace(callstack, BACKTRACE_SIZE); if (frames == 0) { // Some systems won't return anything for the stack trace @@ -1287,7 +1271,7 @@ _Py_DumpStack(int fd) } char *strings[BACKTRACE_SIZE]; - _Py_backtrace_symbols(callstack, BACKTRACE_SIZE, TRACEBACK_ENTRY_MAX_SIZE, strings); + _Py_backtrace_symbols(callstack, frames, TRACEBACK_ENTRY_MAX_SIZE, strings); for (int i = 0; i < frames; ++i) { char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; Py_ssize_t length; From b70bd4339ccd39b117866b43244c41df9da080dc Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 09:19:19 -0400 Subject: [PATCH 39/51] Use dprintf() --- Python/traceback.c | 55 ++++++++++------------------------------------ 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index dab5d704b2f12a..4a5c1dfba0fd79 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1170,31 +1170,9 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, return NULL; } -#define TRACEBACK_ENTRY_MAX_SIZE 128 - -static void -format_entry(char *entry_str, const char *the_entry, Py_ssize_t *length_ptr) -{ - int length = PyOS_snprintf(entry_str, TRACEBACK_ENTRY_MAX_SIZE, " %s\n", the_entry); - if (length == TRACEBACK_ENTRY_MAX_SIZE) { - /* We exceeded the size, make it look prettier */ - // Add ellipsis to last 3 characters - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 5] = '.'; - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 4] = '.'; - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 3] = '.'; - // Ensure trailing newline - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 2] = '\n'; - // Ensure that it's null-terminated - entry_str[TRACEBACK_ENTRY_MAX_SIZE - 1] = '\0'; - } - - *length_ptr = (Py_ssize_t)length; -} - /* Based on glibc's implementation of backtrace_symbols(), but only uses stack memory. */ void -_Py_backtrace_symbols(void *const *array, Py_ssize_t size, - Py_ssize_t line_size, char **result) +_Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) { Dl_info info[size] = {}; int status[size] = {}; @@ -1214,14 +1192,12 @@ _Py_backtrace_symbols(void *const *array, Py_ssize_t size, info[i].dli_fbase = (void *) map->l_addr; } } - char *last = (char *) (result + size); for (Py_ssize_t i = 0; i < size; ++i) { - result[i] = last; if (status[i] == 0 || info[i].dli_fname == NULL || info[i].dli_fname[0] == '\0' ) { - last += 1 + PyOS_snprintf(last, line_size, "[%p]", array[i]); + dprintf(fd, " [%p]\n", array[i]); continue; } @@ -1233,10 +1209,10 @@ _Py_backtrace_symbols(void *const *array, Py_ssize_t size, if (info[i].dli_sname == NULL && info[i].dli_saddr == 0) { - last += 1 + PyOS_snprintf("%s(%s) [%p]", - info[i].dli_fname ?: "", - info[i].dli_sname ?: "", - array[i]); + dprintf(fd, " %s(%s) [%p]\n", + info[i].dli_fname ?: "", + info[i].dli_sname ?: "", + array[i]); } else { char sign; @@ -1249,10 +1225,10 @@ _Py_backtrace_symbols(void *const *array, Py_ssize_t size, sign = '-'; offset = info[i].dli_saddr - array[i]; } - last += 1 + PyOS_snprintf(last, line_size, "%s(%s%c%#tx) [%p]", - info[i].dli_fname ?: "", - info[i].dli_sname ?: "", - sign, offset, array[i]); + dprintf(fd, " %s(%s%c%#tx) [%p]\n", + info[i].dli_fname ?: "", + info[i].dli_sname ?: "", + sign, offset, array[i]); } } } @@ -1270,21 +1246,12 @@ _Py_DumpStack(int fd) return; } - char *strings[BACKTRACE_SIZE]; - _Py_backtrace_symbols(callstack, frames, TRACEBACK_ENTRY_MAX_SIZE, strings); - for (int i = 0; i < frames; ++i) { - char entry_str[TRACEBACK_ENTRY_MAX_SIZE]; - Py_ssize_t length; - format_entry(entry_str, strings[i], &length); - _Py_write_noraise(fd, entry_str, length); - } - + _Py_backtrace_symbols_fd(fd, callstack, frames); if (frames == BACKTRACE_SIZE) { PUTS(fd, " \n"); } #undef BACKTRACE_SIZE -#undef TRACEBACK_ENTRY_MAX_SIZE } /* #else From 7a070ab10e58a7873d6ec3df5f03a076e578b232 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 09:31:51 -0400 Subject: [PATCH 40/51] Add proper configure guards. --- Python/traceback.c | 10 +++++----- configure | 11 +++++++---- configure.ac | 2 +- pyconfig.h.in | 3 +++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index 4a5c1dfba0fd79..f3b71363891e29 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -18,11 +18,12 @@ #ifdef HAVE_UNISTD_H # include // lseek() #endif -#ifdef HAVE_EXECINFO_H +#if defined(HAVE_EXECINFO_H) && defined(HAVE_DLFCN_H) && defined(HAVE_LINK_H) # include // backtrace(), backtrace_symbols() +# include // dladdr1() +# include // struct DL_info +# define CAN_C_BACKTRACE #endif -#include -#include #define OFF(x) offsetof(PyTracebackObject, x) #define PUTS(fd, str) (void)_Py_write_noraise(fd, str, strlen(str)) @@ -1170,6 +1171,7 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, return NULL; } +#ifdef CAN_C_BACKTRACE /* Based on glibc's implementation of backtrace_symbols(), but only uses stack memory. */ void _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) @@ -1253,7 +1255,6 @@ _Py_DumpStack(int fd) #undef BACKTRACE_SIZE } -/* #else void _Py_DumpStack(int fd) @@ -1262,4 +1263,3 @@ _Py_DumpStack(int fd) PUTS(fd, " \n"); } #endif -*/ diff --git a/configure b/configure index f83f81d98778eb..5acde22894eaad 100755 --- a/configure +++ b/configure @@ -11876,12 +11876,15 @@ fi # for faulthandler - for ac_header in execinfo.h + for ac_header in execinfo.h link.h do : - ac_fn_c_check_header_compile "$LINENO" "execinfo.h" "ac_cv_header_execinfo_h" "$ac_includes_default" -if test "x$ac_cv_header_execinfo_h" = xyes + as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | sed "$as_sed_sh"` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes" then : - printf "%s\n" "#define HAVE_EXECINFO_H 1" >>confdefs.h + cat >>confdefs.h <<_ACEOF +#define `printf "%s\n" "HAVE_$ac_header" | sed "$as_sed_cpp"` 1 +_ACEOF ac_fn_c_check_func "$LINENO" "backtrace" "ac_cv_func_backtrace" if test "x$ac_cv_func_backtrace" = xyes then : diff --git a/configure.ac b/configure.ac index d3bf6aaaa38ac4..a022487792af2e 100644 --- a/configure.ac +++ b/configure.ac @@ -2986,7 +2986,7 @@ AC_HEADER_DIRENT AC_HEADER_MAJOR # for faulthandler -AC_CHECK_HEADERS([execinfo.h], +AC_CHECK_HEADERS([execinfo.h link.h], [AC_CHECK_FUNCS(backtrace backtrace_symbols)]) # bluetooth/bluetooth.h has been known to not compile with -std=c99. diff --git a/pyconfig.h.in b/pyconfig.h.in index 1bd9421baa3885..44c32e4f66b3e3 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -714,6 +714,9 @@ /* Define to 1 if you have the 'linkat' function. */ #undef HAVE_LINKAT +/* Define to 1 if you have the header file. */ +#undef HAVE_LINK_H + /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_AUXVEC_H From e9c3d7ce4fac413d3d709a5d57fcb82d978c6e52 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 09:53:48 -0400 Subject: [PATCH 41/51] Use faulthandler formatting. --- Lib/test/test_faulthandler.py | 2 +- Python/traceback.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index 040ae044df2f0f..371c63adce9412 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -59,7 +59,7 @@ def temporary_filename(): ADDRESS_EXPR = "0x[0-9a-f]+" C_STACK_REGEX = [ r"Current thread's C stack trace \(most recent call first\):", - fr" ((\/.+)+\(.*\+{ADDRESS_EXPR}\) \[{ADDRESS_EXPR}\])|(<.+>)" + fr'( Binary file ".+"(, at .*(\+|-){ADDRESS_EXPR})? \[{ADDRESS_EXPR}\])|(<.+>)' ] class FaultHandlerTests(unittest.TestCase): diff --git a/Python/traceback.c b/Python/traceback.c index f3b71363891e29..3716d95d1f9542 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1199,7 +1199,7 @@ _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) || info[i].dli_fname == NULL || info[i].dli_fname[0] == '\0' ) { - dprintf(fd, " [%p]\n", array[i]); + dprintf(fd, " Binary file '' [%p]\n", array[i]); continue; } @@ -1211,7 +1211,7 @@ _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) if (info[i].dli_sname == NULL && info[i].dli_saddr == 0) { - dprintf(fd, " %s(%s) [%p]\n", + dprintf(fd, " Binary file \"%s\", at %s [%p]\n", info[i].dli_fname ?: "", info[i].dli_sname ?: "", array[i]); @@ -1227,7 +1227,7 @@ _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) sign = '-'; offset = info[i].dli_saddr - array[i]; } - dprintf(fd, " %s(%s%c%#tx) [%p]\n", + dprintf(fd, " Binary file \"%s\", at %s%c%#tx [%p]\n", info[i].dli_fname ?: "", info[i].dli_sname ?: "", sign, offset, array[i]); From 195a539cd0c828ae59b9cd746cdbf46ad874b1d3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 10:12:15 -0400 Subject: [PATCH 42/51] Protect against compilers without VLAs. --- Python/traceback.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index 3716d95d1f9542..7e0154e33a4a41 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -25,6 +25,17 @@ # define CAN_C_BACKTRACE #endif +#if defined(__STDC_NO_VLA__) && (__STDC_NO_VLA__ == 1) +/* Use alloca() for VLAs. */ +# define VLA(type, name, size) type *name = alloca(size) +#elif !defined(__STDC_NO_VLA__) || (__STDC_NO_VLA__ == 0) +/* Use actual C VLAs.*/ +# define VLA(type, name, size) type name[size] +#elif defined(CAN_C_BACKTRACE) +/* VLAs are not possible. Disable C stack trace functions. */ +# undef CAN_C_BACKTRACE +#endif + #define OFF(x) offsetof(PyTracebackObject, x) #define PUTS(fd, str) (void)_Py_write_noraise(fd, str, strlen(str)) @@ -1176,13 +1187,12 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp, void _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) { - Dl_info info[size] = {}; - int status[size] = {}; + VLA(Dl_info, info, size); + VLA(int, status, size); /* Fill in the information we can get from dladdr() */ for (Py_ssize_t i = 0; i < size; ++i) { struct link_map *map; - assert(array[i] != NULL); status[i] = dladdr1(array[i], &info[i], (void **)&map, RTLD_DL_LINKMAP); if (status[i] != 0 && info[i].dli_fname != NULL @@ -1240,7 +1250,7 @@ _Py_DumpStack(int fd) { #define BACKTRACE_SIZE 32 PUTS(fd, "Current thread's C stack trace (most recent call first):\n"); - void *callstack[BACKTRACE_SIZE] = {}; + VLA(void *, callstack, BACKTRACE_SIZE); int frames = backtrace(callstack, BACKTRACE_SIZE); if (frames == 0) { // Some systems won't return anything for the stack trace From 9dd6c3b5ef7fab5e91e5cda63a2b2eebdb136122 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 10:42:10 -0400 Subject: [PATCH 43/51] Fix incorrect formatting when no symbol name is available. --- Python/traceback.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index 7e0154e33a4a41..0b1d675fca8619 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1221,9 +1221,8 @@ _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) if (info[i].dli_sname == NULL && info[i].dli_saddr == 0) { - dprintf(fd, " Binary file \"%s\", at %s [%p]\n", - info[i].dli_fname ?: "", - info[i].dli_sname ?: "", + dprintf(fd, " Binary file \"%s\" [%p]\n", + info[i].dli_fname ?: "", array[i]); } else { From ce9c39fca46776029d903886e6ab2501e7a05368 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 18:30:45 -0400 Subject: [PATCH 44/51] Don't use GNU extension. --- Python/traceback.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index 0b1d675fca8619..3191cd7b51bc74 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1222,7 +1222,7 @@ _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) if (info[i].dli_sname == NULL && info[i].dli_saddr == 0) { dprintf(fd, " Binary file \"%s\" [%p]\n", - info[i].dli_fname ?: "", + info[i].dli_fname, array[i]); } else { @@ -1237,8 +1237,8 @@ _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) offset = info[i].dli_saddr - array[i]; } dprintf(fd, " Binary file \"%s\", at %s%c%#tx [%p]\n", - info[i].dli_fname ?: "", - info[i].dli_sname ?: "", + info[i].dli_fname, + info[i].dli_sname, sign, offset, array[i]); } } From c344ad7c2e492308656d06f33d0e2e22350fc2cf Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sun, 13 Apr 2025 18:32:41 -0400 Subject: [PATCH 45/51] Handle NULL. --- Python/traceback.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/traceback.c b/Python/traceback.c index 3191cd7b51bc74..701ae52b778247 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1236,9 +1236,10 @@ _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) sign = '-'; offset = info[i].dli_saddr - array[i]; } + const char *symbol_name = info[i].dli_sname != NULL ? info[i].dli_sname : ""; dprintf(fd, " Binary file \"%s\", at %s%c%#tx [%p]\n", info[i].dli_fname, - info[i].dli_sname, + symbol_name, sign, offset, array[i]); } } From e899792726a7c2ccf9331adce2765471ffae2ecf Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 14 Apr 2025 07:51:31 -0400 Subject: [PATCH 46/51] Fix newlines in the whatsnew. --- Doc/whatsnew/3.14.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index dba6634e583091..1e697a3625c790 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -677,6 +677,7 @@ errno * Add :data:`errno.EHWPOISON` error code. (Contributed by James Roy in :gh:`126585`.) + faulthandler ------------ @@ -684,12 +685,14 @@ faulthandler :func:`faulthandler.dump_c_stack` or via the *c_stack* argument in :func:`faulthandler.enable`. (Contributed by Peter Bierma in :gh:`127604`.) + fnmatch ------- * Added :func:`fnmatch.filterfalse` for excluding names matching a pattern. (Contributed by Bénédikt Tran in :gh:`74598`.) + fractions --------- From b136f71d0f6893f0fb96bd326117c9dd43e67d64 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 14 Apr 2025 07:52:32 -0400 Subject: [PATCH 47/51] Fix stray newline change. --- Modules/faulthandler.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 14b02522dec7d9..982325a9e7217c 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -9,7 +9,6 @@ #include "pycore_sysmodule.h" // _PySys_GetRequiredAttr() #include "pycore_time.h" // _PyTime_FromSecondsObject() #include "pycore_traceback.h" // _Py_DumpTracebackThreads - #ifdef HAVE_UNISTD_H # include // _exit() #endif From 2c381b9763987748f1dad060349a82796865ca39 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 14 Apr 2025 08:26:01 -0400 Subject: [PATCH 48/51] Add configure guard for dladdr1() --- Python/traceback.c | 4 +++- configure | 8 +++++++- configure.ac | 4 ++-- pyconfig.h.in | 3 +++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Python/traceback.c b/Python/traceback.c index 701ae52b778247..2e3f48e8c8c48c 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -22,7 +22,9 @@ # include // backtrace(), backtrace_symbols() # include // dladdr1() # include // struct DL_info -# define CAN_C_BACKTRACE +# if defined(HAVE_BACKTRACE) && defined(HAVE_BACKTRACE_SYMBOLS) && defined(HAVE_DLADDR1) +# define CAN_C_BACKTRACE +# endif #endif #if defined(__STDC_NO_VLA__) && (__STDC_NO_VLA__ == 1) diff --git a/configure b/configure index 5acde22894eaad..82bb98309a795f 100755 --- a/configure +++ b/configure @@ -11876,7 +11876,7 @@ fi # for faulthandler - for ac_header in execinfo.h link.h + for ac_header in execinfo.h link.h dlfcn.h do : as_ac_Header=`printf "%s\n" "ac_cv_header_$ac_header" | sed "$as_sed_sh"` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" @@ -11896,6 +11896,12 @@ if test "x$ac_cv_func_backtrace_symbols" = xyes then : printf "%s\n" "#define HAVE_BACKTRACE_SYMBOLS 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "dladdr1" "ac_cv_func_dladdr1" +if test "x$ac_cv_func_dladdr1" = xyes +then : + printf "%s\n" "#define HAVE_DLADDR1 1" >>confdefs.h + fi fi diff --git a/configure.ac b/configure.ac index a022487792af2e..11bfdc6e8b8d75 100644 --- a/configure.ac +++ b/configure.ac @@ -2986,8 +2986,8 @@ AC_HEADER_DIRENT AC_HEADER_MAJOR # for faulthandler -AC_CHECK_HEADERS([execinfo.h link.h], - [AC_CHECK_FUNCS(backtrace backtrace_symbols)]) +AC_CHECK_HEADERS([execinfo.h link.h dlfcn.h], + [AC_CHECK_FUNCS(backtrace backtrace_symbols dladdr1)]) # bluetooth/bluetooth.h has been known to not compile with -std=c99. # http://permalink.gmane.org/gmane.linux.bluez.kernel/22294 diff --git a/pyconfig.h.in b/pyconfig.h.in index 44c32e4f66b3e3..c8a0174bd38aee 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -295,6 +295,9 @@ /* Define to 1 if you have the 'dladdr' function. */ #undef HAVE_DLADDR +/* Define to 1 if you have the 'dladdr1' function. */ +#undef HAVE_DLADDR1 + /* Define to 1 if you have the header file. */ #undef HAVE_DLFCN_H From 52c074882c27d014ac1e7f6ca7700237dde063c5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 14 Apr 2025 08:45:05 -0400 Subject: [PATCH 49/51] Add documentation about compatibility. --- Doc/library/faulthandler.rst | 24 +++++++++++++++++++++--- Doc/whatsnew/3.14.rst | 5 +++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst index 5d3c6606d911f2..b7dfad4744bda9 100644 --- a/Doc/library/faulthandler.rst +++ b/Doc/library/faulthandler.rst @@ -75,9 +75,27 @@ Dumping the C stack Dump the C stack trace of the current thread into *file*. - If the system does not support the C-level :manpage:`backtrace(3)` - or :manpage:`backtrace_symbols(3)` functions, then an error message - is displayed instead of the C stack. + If the Python build does not support it or the operating system + does not provide a stack trace, then this prints an error in place + of a dumped C stack. + +.. _c-stack-compatibility + +C Stack Compatibility +********************* + +If the system does not support the C-level :manpage:`backtrace(3)`, +:manpage:`backtrace_symbols(3)`, or :manpage:`dladdr(3)`, then C stack dumps +will not work. An error will be printed instead of the stack. + +Additionally, some compilers do not support :term:`CPython's ` +implementation of C stack dumps. As a result, a different error may be printed +instead of the stack, even if the the operating system supports dumping stacks. + +.. note:: + + Dumping C stacks can be arbitrarily slow, depending on the DWARF level + of the binaries in the call stack. Fault handler state ------------------- diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 1e697a3625c790..6494c34da7b190 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -681,8 +681,9 @@ errno faulthandler ------------ -* Add support for printing the C stack trace on systems that support it via - :func:`faulthandler.dump_c_stack` or via the *c_stack* argument in :func:`faulthandler.enable`. +* Add support for printing the C stack trace on systems that + :ref:`support it ` via :func:`faulthandler.dump_c_stack` + or via the *c_stack* argument in :func:`faulthandler.enable`. (Contributed by Peter Bierma in :gh:`127604`.) From bd470263f558dc5d27c8b1ef10c92ff91670600e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 14 Apr 2025 08:47:41 -0400 Subject: [PATCH 50/51] Fix sphinx role. --- Doc/library/faulthandler.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/faulthandler.rst b/Doc/library/faulthandler.rst index b7dfad4744bda9..8823c8f8edf567 100644 --- a/Doc/library/faulthandler.rst +++ b/Doc/library/faulthandler.rst @@ -79,7 +79,7 @@ Dumping the C stack does not provide a stack trace, then this prints an error in place of a dumped C stack. -.. _c-stack-compatibility +.. _c-stack-compatibility: C Stack Compatibility ********************* From 07a20d06a1d5ead846dbbe0e0f42a0aa9e81bf01 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 14 Apr 2025 12:41:49 -0400 Subject: [PATCH 51/51] Fix styling. --- Modules/faulthandler.c | 3 ++- Python/traceback.c | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c index 982325a9e7217c..563ffd9fbbdadb 100644 --- a/Modules/faulthandler.c +++ b/Modules/faulthandler.c @@ -288,8 +288,9 @@ faulthandler_dump_c_stack_py(PyObject *self, if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O:dump_c_stack", kwlist, - &file)) + &file)) { return NULL; + } int fd = faulthandler_get_fileno(&file); if (fd < 0) { diff --git a/Python/traceback.c b/Python/traceback.c index 2e3f48e8c8c48c..7319382f053eae 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -1192,8 +1192,7 @@ _Py_backtrace_symbols_fd(int fd, void *const *array, Py_ssize_t size) VLA(Dl_info, info, size); VLA(int, status, size); /* Fill in the information we can get from dladdr() */ - for (Py_ssize_t i = 0; i < size; ++i) - { + for (Py_ssize_t i = 0; i < size; ++i) { struct link_map *map; status[i] = dladdr1(array[i], &info[i], (void **)&map, RTLD_DL_LINKMAP); if (status[i] != 0