mirror of
https://github.com/python/cpython.git
synced 2026-10-28 10:25:05 +08:00
gh-145559: Add PyUnstable_DumpTraceback() and PyUnstable_DumpTracebackThreads() (#148145)
Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
12 files changed
+109
-76
No files matched your search
@@ -1348,3 +1348,67 @@ Tracebacks
|
||||
|
||||
This function returns ``0`` on success, and returns ``-1`` with an
|
||||
exception set on failure.
|
||||
|
||||
.. c:function:: const char* PyUnstable_DumpTraceback(int fd, PyThreadState *tstate)
|
||||
|
||||
Write a trace of the Python stack in *tstate* into the file *fd*. The format
|
||||
looks like::
|
||||
|
||||
Traceback (most recent call first):
|
||||
File "xxx", line xxx in <xxx>
|
||||
File "xxx", line xxx in <xxx>
|
||||
...
|
||||
File "xxx", line xxx in <xxx>
|
||||
|
||||
This function is meant to debug situations such as segfaults, fatal errors,
|
||||
and similar. The file and function names it outputs are encoded to ASCII with
|
||||
backslashreplace and truncated to 500 characters. It writes only the first
|
||||
100 frames; further frames are truncated with the line ``...``.
|
||||
|
||||
This function will return ``NULL`` on success, or an error message on error.
|
||||
|
||||
This function is intended for use in crash scenarios such as signal handlers
|
||||
for SIGSEGV, where the interpreter may be in an inconsistent state. Given
|
||||
that it reads interpreter data structures that may be partially modified, the
|
||||
function might produce incomplete output or it may even crash itself.
|
||||
|
||||
The caller does not need to hold an :term:`attached thread state`, nor does
|
||||
*tstate* need to be attached.
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
.. c:function:: const char* PyUnstable_DumpTracebackThreads(int fd, PyInterpreterState *interp, PyThreadState *current_tstate, Py_ssize_t max_threads)
|
||||
|
||||
Write the traces of all Python threads in *interp* into the file *fd*.
|
||||
|
||||
If *interp* is ``NULL`` then this function will try to identify the current
|
||||
interpreter using thread-specific storage. If it cannot, it will return an
|
||||
error.
|
||||
|
||||
If *current_tstate* is not ``NULL`` then it will be used to identify what the
|
||||
current thread is in the written output. If it is ``NULL`` then this function
|
||||
will identify the current thread using thread-specific storage. It is not an
|
||||
error if the function is unable to get the current Python thread state.
|
||||
|
||||
This function will return ``NULL`` on success, or an error message on error.
|
||||
|
||||
This function is meant to debug debug situations such as segfaults, fatal
|
||||
errors, and similar. It calls :c:func:`PyUnstable_DumpTraceback` for each
|
||||
thread. It only writes the tracebacks of the first *max_threads* threads,
|
||||
further output is truncated with the line ``...``. If *max_threads* is 0, the
|
||||
function will use a default value of 100 for the argument.
|
||||
|
||||
This function is intended for use in crash scenarios such as signal handlers
|
||||
for SIGSEGV, where the interpreter may be in an inconsistent state. Given
|
||||
that it reads interpreter data structures that may be partially modified, the
|
||||
function might produce incomplete output or it may even crash itself.
|
||||
|
||||
The caller does not need to hold an :term:`attached thread state`, nor does
|
||||
*current_tstate* need to be attached.
|
||||
|
||||
.. warning::
|
||||
On the :term:`free-threaded build`, this function is not thread-safe. If
|
||||
another thread deletes its :term:`thread state` while this function is being
|
||||
called, the process will likely crash.
|
||||
|
||||
.. versionadded:: next
|
||||
@@ -2352,6 +2352,11 @@ New features
|
||||
Python 3.14.
|
||||
(Contributed by Victor Stinner in :gh:`142417`.)
|
||||
|
||||
* Add :c:func:`PyUnstable_DumpTraceback` and
|
||||
:c:func:`PyUnstable_DumpTracebackThreads` functions to output Python
|
||||
stacktraces.
|
||||
(Contributed by Alex Malyshev in :gh:`145559`.)
|
||||
|
||||
Changed C APIs
|
||||
--------------
|
||||
|
||||
|
||||
@@ -11,3 +11,11 @@ struct _traceback {
|
||||
int tb_lasti;
|
||||
int tb_lineno;
|
||||
};
|
||||
|
||||
PyAPI_FUNC(const char*) PyUnstable_DumpTraceback(int fd, PyThreadState *tstate);
|
||||
|
||||
PyAPI_FUNC(const char*) PyUnstable_DumpTracebackThreads(
|
||||
int fd,
|
||||
PyInterpreterState *interp,
|
||||
PyThreadState *current_tstate,
|
||||
Py_ssize_t max_threads);
|
||||
@@ -14,56 +14,6 @@ PyAPI_FUNC(int) _Py_DisplaySourceLine(PyObject *, PyObject *, int, int, int *, P
|
||||
// Export for 'pyexact' shared extension
|
||||
PyAPI_FUNC(void) _PyTraceback_Add(const char *, const char *, int);
|
||||
|
||||
/* Write the Python traceback into the file 'fd'. For example:
|
||||
|
||||
Traceback (most recent call first):
|
||||
File "xxx", line xxx in <xxx>
|
||||
File "xxx", line xxx in <xxx>
|
||||
...
|
||||
File "xxx", line xxx in <xxx>
|
||||
|
||||
This function is written for debug purpose only, to dump the traceback in
|
||||
the worst case: after a segmentation fault, at fatal error, etc. That's why,
|
||||
it is very limited. Strings are truncated to 100 characters and encoded to
|
||||
ASCII with backslashreplace. It doesn't write the source code, only the
|
||||
function name, filename and line number of each frame. Write only the first
|
||||
100 frames: if the traceback is truncated, write the line " ...".
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
extern void _Py_DumpTraceback(
|
||||
int fd,
|
||||
PyThreadState *tstate);
|
||||
|
||||
/* Write the traceback of all threads into the file 'fd'. current_thread can be
|
||||
NULL.
|
||||
|
||||
Return NULL on success, or an error message on error.
|
||||
|
||||
This function is written for debug purpose only. It calls
|
||||
_Py_DumpTraceback() for each thread, and so has the same limitations. It
|
||||
only write the traceback of the first 100 threads: write "..." if there are
|
||||
more threads.
|
||||
|
||||
If current_tstate is NULL, the function tries to get the Python thread state
|
||||
of the current thread. It is not an error if the function is unable to get
|
||||
the current Python thread state.
|
||||
|
||||
If interp is NULL, the function tries to get the interpreter state from
|
||||
the current Python thread state, or from
|
||||
_PyGILState_GetInterpreterStateUnsafe() in last resort.
|
||||
|
||||
It is better to pass NULL to interp and current_tstate, the function tries
|
||||
different options to retrieve this information.
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
extern const char* _Py_DumpTracebackThreads(
|
||||
int fd,
|
||||
PyInterpreterState *interp,
|
||||
PyThreadState *current_tstate,
|
||||
Py_ssize_t max_threads);
|
||||
|
||||
/* Write a Unicode object into the file descriptor fd. Encode the string to
|
||||
ASCII using the backslashreplace error handler.
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Rename ``_Py_DumpTraceback`` and ``_Py_DumpTracebackThreads`` to
|
||||
:c:func:`PyUnstable_DumpTraceback` and
|
||||
:c:func:`PyUnstable_DumpTracebackThreads`.
|
||||
+15
-13
@@ -7,7 +7,7 @@
|
||||
#include "pycore_runtime.h" // _Py_ID()
|
||||
#include "pycore_signal.h" // Py_NSIG
|
||||
#include "pycore_time.h" // _PyTime_FromSecondsObject()
|
||||
#include "pycore_traceback.h" // _Py_DumpTracebackThreads
|
||||
#include "pycore_traceback.h" // _Py_DumpStack()
|
||||
#ifdef HAVE_UNISTD_H
|
||||
# include <unistd.h> // _exit()
|
||||
#endif
|
||||
@@ -206,14 +206,15 @@ faulthandler_dump_traceback(int fd, int all_threads,
|
||||
PyThreadState *tstate = PyGILState_GetThisThreadState();
|
||||
|
||||
if (all_threads == 1) {
|
||||
(void)_Py_DumpTracebackThreads(fd, NULL, tstate, max_threads);
|
||||
(void)PyUnstable_DumpTracebackThreads(fd, NULL, tstate, max_threads);
|
||||
}
|
||||
else {
|
||||
if (all_threads == FT_IGNORE_ALL_THREADS) {
|
||||
PUTS(fd, "<Cannot show all threads while the GIL is disabled>\n");
|
||||
}
|
||||
if (tstate != NULL)
|
||||
_Py_DumpTraceback(fd, tstate);
|
||||
if (tstate != NULL) {
|
||||
PyUnstable_DumpTraceback(fd, tstate);
|
||||
}
|
||||
}
|
||||
|
||||
reentrant = 0;
|
||||
@@ -277,17 +278,18 @@ faulthandler_dump_traceback_py_impl(PyObject *module, PyObject *file,
|
||||
/* gh-128400: Accessing other thread states while they're running
|
||||
* isn't safe if those threads are running. */
|
||||
_PyEval_StopTheWorld(interp);
|
||||
errmsg = _Py_DumpTracebackThreads(fd, NULL, tstate, max_threads);
|
||||
errmsg = PyUnstable_DumpTracebackThreads(fd, NULL, tstate, max_threads);
|
||||
_PyEval_StartTheWorld(interp);
|
||||
if (errmsg != NULL) {
|
||||
PyErr_SetString(PyExc_RuntimeError, errmsg);
|
||||
Py_XDECREF(file);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_Py_DumpTraceback(fd, tstate);
|
||||
errmsg = PyUnstable_DumpTraceback(fd, tstate);
|
||||
}
|
||||
if (errmsg != NULL) {
|
||||
PyErr_SetString(PyExc_RuntimeError, errmsg);
|
||||
Py_XDECREF(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_XDECREF(file);
|
||||
|
||||
if (PyErr_CheckSignals())
|
||||
@@ -713,8 +715,8 @@ faulthandler_thread(void *unused)
|
||||
|
||||
(void)_Py_write_noraise(thread.fd, thread.header, (int)thread.header_len);
|
||||
|
||||
errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, NULL,
|
||||
thread.max_threads);
|
||||
errmsg = PyUnstable_DumpTracebackThreads(thread.fd, thread.interp, NULL,
|
||||
thread.max_threads);
|
||||
ok = (errmsg == NULL);
|
||||
|
||||
if (thread.exit)
|
||||
|
||||
@@ -57,6 +57,6 @@ try {
|
||||
// Show JavaScript exception and traceback
|
||||
console.warn(e);
|
||||
// Show Python exception and traceback
|
||||
Module.__Py_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
|
||||
Module.PyUnstable_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -189,6 +189,6 @@ try {
|
||||
// Show JavaScript exception and traceback
|
||||
console.warn(e);
|
||||
// Show Python exception and traceback
|
||||
Module.__Py_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
|
||||
Module.PyUnstable_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "pycore_setobject.h" // _PySet_NextEntry()
|
||||
#include "pycore_stats.h" // _PyStats_InterpInit()
|
||||
#include "pycore_sysmodule.h" // _PySys_ClearAttrString()
|
||||
#include "pycore_traceback.h" // _Py_DumpTracebackThreads()
|
||||
#include "pycore_traceback.h" // PyUnstable_TracebackThreads()
|
||||
#include "pycore_tuple.h" // _PyTuple_FromPair
|
||||
#include "pycore_typeobject.h" // _PyTypes_InitTypes()
|
||||
#include "pycore_typevarobject.h" // _Py_clear_generic_types()
|
||||
@@ -3348,9 +3348,9 @@ _Py_FatalError_DumpTracebacks(int fd, PyInterpreterState *interp,
|
||||
|
||||
/* display the current Python stack */
|
||||
#ifndef Py_GIL_DISABLED
|
||||
_Py_DumpTracebackThreads(fd, interp, tstate, 0);
|
||||
PyUnstable_DumpTracebackThreads(fd, interp, tstate, 0);
|
||||
#else
|
||||
_Py_DumpTraceback(fd, tstate);
|
||||
PyUnstable_DumpTraceback(fd, tstate);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+7
-6
@@ -1167,10 +1167,11 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
|
||||
|
||||
The caller is responsible to call PyErr_CheckSignals() to call Python signal
|
||||
handlers if signals were received. */
|
||||
void
|
||||
_Py_DumpTraceback(int fd, PyThreadState *tstate)
|
||||
const char*
|
||||
PyUnstable_DumpTraceback(int fd, PyThreadState *tstate)
|
||||
{
|
||||
dump_traceback(fd, tstate, 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)
|
||||
@@ -1264,16 +1265,16 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current)
|
||||
The caller is responsible to call PyErr_CheckSignals() to call Python signal
|
||||
handlers if signals were received. */
|
||||
const char* _Py_NO_SANITIZE_THREAD
|
||||
_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
|
||||
PyThreadState *current_tstate,
|
||||
Py_ssize_t max_threads)
|
||||
PyUnstable_DumpTracebackThreads(int fd, PyInterpreterState *interp,
|
||||
PyThreadState *current_tstate,
|
||||
Py_ssize_t max_threads)
|
||||
{
|
||||
if (max_threads == 0) {
|
||||
max_threads = DEFAULT_MAX_NTHREADS;
|
||||
}
|
||||
|
||||
if (current_tstate == NULL) {
|
||||
/* _Py_DumpTracebackThreads() is called from signal handlers by
|
||||
/* PyUnstable_DumpTracebackThreads() is called from signal handlers by
|
||||
faulthandler.
|
||||
|
||||
SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL are synchronous signals
|
||||
|
||||
@@ -9788,7 +9788,7 @@ fi
|
||||
|
||||
as_fn_append LINKFORSHARED " -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js"
|
||||
as_fn_append LINKFORSHARED " -sEXPORTED_RUNTIME_METHODS=FS,callMain,ENV,HEAPU32,TTY,ERRNO_CODES"
|
||||
as_fn_append LINKFORSHARED " -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__Py_DumpTraceback,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET"
|
||||
as_fn_append LINKFORSHARED " -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET"
|
||||
as_fn_append LINKFORSHARED " -sSTACK_SIZE=5MB"
|
||||
as_fn_append LINKFORSHARED " -sTEXTDECODER=2"
|
||||
|
||||
|
||||
+1
-1
@@ -2406,7 +2406,7 @@ AS_CASE([$ac_sys_system],
|
||||
dnl Include file system support
|
||||
AS_VAR_APPEND([LINKFORSHARED], [" -sFORCE_FILESYSTEM -lidbfs.js -lnodefs.js -lproxyfs.js -lworkerfs.js"])
|
||||
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_RUNTIME_METHODS=FS,callMain,ENV,HEAPU32,TTY,ERRNO_CODES"])
|
||||
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__Py_DumpTraceback,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET"])
|
||||
AS_VAR_APPEND([LINKFORSHARED], [" -sEXPORTED_FUNCTIONS=_main,_Py_Version,__PyRuntime,_PyGILState_GetThisThreadState,__PyEM_EMSCRIPTEN_TRAMPOLINE_OFFSET"])
|
||||
AS_VAR_APPEND([LINKFORSHARED], [" -sSTACK_SIZE=5MB"])
|
||||
dnl Avoid bugs in JS fallback string decoding path
|
||||
AS_VAR_APPEND([LINKFORSHARED], [" -sTEXTDECODER=2"])
|
||||
|
||||
Reference in new issue
Block a user