[3.14] gh-125434: Display thread name in faulthandler on Windows (#142011)

* gh-125434: Display thread name in faulthandler on Windows (#140675)

(cherry picked from commit 313145eab5)

* gh-125434: Fix non-ASCII thread names in faulthandler on Windows (#140700)

Add _Py_DumpWideString() function to dump a wide string as ASCII. It
supports surrogate pairs.

Replace _Py_EncodeLocaleRaw() with _Py_DumpWideString()
in write_thread_name().

(cherry picked from commit 80f20f58b2)
This commit is contained in:
Victor Stinner 2025-11-27 13:05:37 +01:00 committed by GitHub
parent f47e928574
commit ad60d8963e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 121 additions and 14 deletions

View file

@ -103,6 +103,8 @@ extern int _Py_WriteIndent(int, PyObject *);
PyAPI_FUNC(void) _Py_InitDumpStack(void); PyAPI_FUNC(void) _Py_InitDumpStack(void);
PyAPI_FUNC(void) _Py_DumpStack(int fd); PyAPI_FUNC(void) _Py_DumpStack(int fd);
extern void _Py_DumpTraceback_Init(void);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -0,0 +1,2 @@
Display thread name in :mod:`faulthandler` on Windows. Patch by Victor
Stinner.

View file

@ -503,6 +503,7 @@ pycore_init_runtime(_PyRuntimeState *runtime,
_PyRuntimeState_SetFinalizing(runtime, NULL); _PyRuntimeState_SetFinalizing(runtime, NULL);
_Py_InitVersion(); _Py_InitVersion();
_Py_DumpTraceback_Init();
status = _Py_HashRandomization_Init(config); status = _Py_HashRandomization_Init(config);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {

View file

@ -70,6 +70,13 @@ class traceback "PyTracebackObject *" "&PyTraceback_Type"
#include "clinic/traceback.c.h" #include "clinic/traceback.c.h"
#ifdef MS_WINDOWS
typedef HRESULT (WINAPI *PF_GET_THREAD_DESCRIPTION)(HANDLE, PCWSTR*);
static PF_GET_THREAD_DESCRIPTION pGetThreadDescription = NULL;
#endif
static PyObject * static PyObject *
tb_create_raw(PyTracebackObject *next, PyFrameObject *frame, int lasti, tb_create_raw(PyTracebackObject *next, PyFrameObject *frame, int lasti,
int lineno) int lineno)
@ -974,6 +981,52 @@ _Py_DumpASCII(int fd, PyObject *text)
} }
} }
#ifdef MS_WINDOWS
static void
_Py_DumpWideString(int fd, wchar_t *str)
{
Py_ssize_t size = wcslen(str);
int truncated;
if (MAX_STRING_LENGTH < size) {
size = MAX_STRING_LENGTH;
truncated = 1;
}
else {
truncated = 0;
}
for (Py_ssize_t i=0; i < size; i++) {
Py_UCS4 ch = str[i];
if (' ' <= ch && ch <= 126) {
/* printable ASCII character */
dump_char(fd, (char)ch);
}
else if (ch <= 0xff) {
PUTS(fd, "\\x");
_Py_DumpHexadecimal(fd, ch, 2);
}
else if (Py_UNICODE_IS_HIGH_SURROGATE(ch)
&& Py_UNICODE_IS_LOW_SURROGATE(str[i+1])) {
ch = Py_UNICODE_JOIN_SURROGATES(ch, str[i+1]);
i++; // Skip the low surrogate character
PUTS(fd, "\\U");
_Py_DumpHexadecimal(fd, ch, 8);
}
else {
Py_BUILD_ASSERT(sizeof(wchar_t) == 2);
PUTS(fd, "\\u");
_Py_DumpHexadecimal(fd, ch, 4);
}
}
if (truncated) {
PUTS(fd, "...");
}
}
#endif
/* Write a frame into the file fd: "File "xxx", line xxx in xxx". /* Write a frame into the file fd: "File "xxx", line xxx in xxx".
This function is signal safe. This function is signal safe.
@ -1122,23 +1175,12 @@ _Py_DumpTraceback(int fd, PyThreadState *tstate)
# endif # endif
#endif #endif
/* Write the thread identifier into the file 'fd': "Current thread 0xHHHH:\" if
is_current is true, "Thread 0xHHHH:\n" otherwise.
This function is signal safe. */
// Write the thread name
static void static void
write_thread_id(int fd, PyThreadState *tstate, int is_current) write_thread_name(int fd, PyThreadState *tstate)
{ {
if (is_current) #ifndef MS_WINDOWS
PUTS(fd, "Current thread 0x");
else
PUTS(fd, "Thread 0x");
_Py_DumpHexadecimal(fd,
tstate->thread_id,
sizeof(unsigned long) * 2);
// Write the thread name
#if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP) #if defined(HAVE_PTHREAD_GETNAME_NP) || defined(HAVE_PTHREAD_GET_NAME_NP)
char name[100]; char name[100];
pthread_t thread = (pthread_t)tstate->thread_id; pthread_t thread = (pthread_t)tstate->thread_id;
@ -1157,6 +1199,49 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current)
} }
} }
#endif #endif
#else
// Windows implementation
if (pGetThreadDescription == NULL) {
return;
}
HANDLE thread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, tstate->thread_id);
if (thread == NULL) {
return;
}
wchar_t *name;
HRESULT hr = pGetThreadDescription(thread, &name);
if (!FAILED(hr)) {
if (name[0] != 0) {
PUTS(fd, " [");
_Py_DumpWideString(fd, name);
PUTS(fd, "]");
}
LocalFree(name);
}
CloseHandle(thread);
#endif
}
/* Write the thread identifier into the file 'fd': "Current thread 0xHHHH:\" if
is_current is true, "Thread 0xHHHH:\n" otherwise.
This function is signal safe (except on Windows). */
static void
write_thread_id(int fd, PyThreadState *tstate, int is_current)
{
if (is_current)
PUTS(fd, "Current thread 0x");
else
PUTS(fd, "Thread 0x");
_Py_DumpHexadecimal(fd,
tstate->thread_id,
sizeof(unsigned long) * 2);
write_thread_name(fd, tstate);
PUTS(fd, " (most recent call first):\n"); PUTS(fd, " (most recent call first):\n");
} }
@ -1351,3 +1436,20 @@ _Py_InitDumpStack(void)
(void)backtrace(callstack, 1); (void)backtrace(callstack, 1);
#endif #endif
} }
void
_Py_DumpTraceback_Init(void)
{
#ifdef MS_WINDOWS
if (pGetThreadDescription != NULL) {
return;
}
HMODULE kernelbase = GetModuleHandleW(L"kernelbase.dll");
if (kernelbase != NULL) {
pGetThreadDescription = (PF_GET_THREAD_DESCRIPTION)GetProcAddress(
kernelbase, "GetThreadDescription");
}
#endif
}