Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Lib/test/test_external_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2780,7 +2780,7 @@
with self.assertRaises(RuntimeError) as cm:
RemoteUnwinder(os.getpid())

self.assertIn(

Check failure on line 2783 in Lib/test/test_external_inspection.py

View workflow job for this annotation

GitHub Actions / Emscripten / build and test

test_unsupported_platform_error (test.test_external_inspection.TestUnsupportedPlatformHandling.test_unsupported_platform_error)

AssertionError: 'Reading the PyRuntime section is not supported on this platform' not found in 'PyRuntime address lookup failed during debug offsets initialization'
"Reading the PyRuntime section is not supported on this platform",
str(cm.exception),
)
Expand Down Expand Up @@ -4687,6 +4687,28 @@
else:
self.fail("ProcessLookupError never raised for dead process")

def test_dead_process_before_first_async_read(self):
script_body = """\
import asyncio
sock.sendall(b"ready")
sock.recv(16)
"""
with self._target_process(script_body) as (p, client_socket, _):
_wait_for_signal(client_socket, b"ready")
unwinders = [
RemoteUnwinder(p.pid, debug=debug)
for debug in (False, True)
]

p.kill()
p.wait()

for debug, unwinder in zip((False, True), unwinders):
for method in ("get_async_stack_trace", "get_all_awaited_by"):
with self.subTest(debug=debug, method=method):
with self.assertRaises(ProcessLookupError):
getattr(unwinder, method)()


@requires_remote_subprocess_debugging()
class TestFrameChainLimits(RemoteInspectionTestBase):
Expand Down
22 changes: 0 additions & 22 deletions Modules/_remote_debugging/_remote_debugging.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ extern "C" {
# if TARGET_OS_OSX
# include <libproc.h>
# include <sys/types.h>
# define MAX_NATIVE_THREADS 4096
# endif
#endif

Expand Down Expand Up @@ -104,18 +103,7 @@ typedef struct {

#ifdef MS_WINDOWS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define STATUS_NO_MORE_ENTRIES ((NTSTATUS)0x8000001AL)
typedef enum _WIN32_THREADSTATE {
WIN32_THREADSTATE_INITIALIZED = 0,
WIN32_THREADSTATE_READY = 1,
WIN32_THREADSTATE_RUNNING = 2,
WIN32_THREADSTATE_STANDBY = 3,
WIN32_THREADSTATE_TERMINATED = 4,
WIN32_THREADSTATE_WAITING = 5,
WIN32_THREADSTATE_TRANSITION = 6,
WIN32_THREADSTATE_UNKNOWN = 7
} WIN32_THREADSTATE;
#endif

/* ============================================================================
Expand Down Expand Up @@ -387,14 +375,6 @@ typedef struct {
#ifdef Py_GIL_DISABLED
uint32_t tlbc_generation;
_Py_hashtable_t *tlbc_cache;
#endif
#ifdef __APPLE__
uint64_t thread_id_offset;
int thread_id_offset_initialized;
#endif
#ifdef MS_WINDOWS
PVOID win_process_buffer;
ULONG win_process_buffer_size;
#endif
// Thread stopping state (only on platforms that support it)
#ifdef Py_REMOTE_DEBUG_SUPPORTS_BLOCKING
Expand Down Expand Up @@ -681,8 +661,6 @@ extern int find_running_frame(
uintptr_t *frame
);

extern int get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread_id);

extern PyObject* unwind_stack_for_thread(
RemoteUnwinderObject *unwinder,
uintptr_t *current_tstate,
Expand Down
35 changes: 19 additions & 16 deletions Modules/_remote_debugging/asyncio.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle)
address = search_windows_map_for_section(handle, "AsyncioD", L"_asyncio",
NULL);
if (address == 0) {
if (!_Py_RemoteDebug_HasPermissionError()) {
if (!_Py_RemoteDebug_IsFatalReadError()) {
PyObject *exc = PyErr_GetRaisedException();
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
_PyErr_ChainExceptions1(exc);
Expand All @@ -33,23 +33,16 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle)
address = search_linux_map_for_section(handle, "AsyncioDebug", "python",
NULL);
if (address == 0) {
if (!_Py_RemoteDebug_HasPermissionError()) {
if (!_Py_RemoteDebug_IsFatalReadError()) {
PyObject *exc = PyErr_GetRaisedException();
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
_PyErr_ChainExceptions1(exc);
}
}
#elif defined(__APPLE__) && TARGET_OS_OSX
// On macOS, try libpython first, then fall back to python
address = search_map_for_section(handle, "AsyncioDebug", "libpython",
NULL);
if (address == 0 && !_Py_RemoteDebug_HasPermissionError()) {
PyErr_Clear();
address = search_map_for_section(handle, "AsyncioDebug", "python",
NULL);
}
address = search_loaded_images_for_section(handle, "AsyncioDebug", NULL);
if (address == 0) {
if (!_Py_RemoteDebug_HasPermissionError()) {
if (!_Py_RemoteDebug_IsFatalReadError()) {
PyObject *exc = PyErr_GetRaisedException();
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
_PyErr_ChainExceptions1(exc);
Expand Down Expand Up @@ -99,12 +92,22 @@ ensure_async_debug_offsets(RemoteUnwinderObject *unwinder)
return -1;
}
if (result < 0) {
if (!_Py_RemoteDebug_HasPermissionError()) {
PyErr_Clear();
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
set_exception_cause(unwinder, PyExc_RuntimeError,
"AsyncioDebug section unavailable - asyncio module may not be loaded in target process");
if (_Py_RemoteDebug_IsFatalReadError()) {
return -1;
}
PyErr_Clear();
/* Image discovery can fail without reporting that the target exited.
Read a known runtime address before reporting unavailable asyncio. */
char runtime_byte;
if (_Py_RemoteDebug_ReadRemoteMemory(&unwinder->handle,
unwinder->runtime_start_address, sizeof(runtime_byte),
&runtime_byte) < 0)
{
return -1;
}
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
set_exception_cause(unwinder, PyExc_RuntimeError,
"AsyncioDebug section unavailable - asyncio module may not be loaded in target process");
return -1;
}

Expand Down
41 changes: 2 additions & 39 deletions Modules/_remote_debugging/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -384,12 +384,6 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
return -1;
}

self->runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(&self->handle);
if (self->runtime_start_address == 0) {
set_exception_cause(self, PyExc_RuntimeError, "Failed to get Python runtime address");
return -1;
}

if (_Py_RemoteDebug_ReadDebugOffsets(&self->handle,
&self->runtime_start_address,
&self->debug_offsets) < 0)
Expand All @@ -404,20 +398,8 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
return -1;
}

// Try to read async debug offsets, but don't fail if they're not available
self->async_debug_offsets_available = 1;
int async_debug_result = read_async_debug(self);
if (async_debug_result == PY_REMOTE_DEBUG_INVALID_ASYNC_DEBUG_OFFSETS) {
return -1;
}
if (async_debug_result < 0) {
if (_Py_RemoteDebug_HasPermissionError()) {
return -1;
}
PyErr_Clear();
memset(&self->async_debug_offsets, 0, sizeof(self->async_debug_offsets));
self->async_debug_offsets_available = 0;
}
self->async_debug_offsets_available = 0;
memset(&self->async_debug_offsets, 0, sizeof(self->async_debug_offsets));

if (populate_initial_state_data(all_threads, self, self->runtime_start_address,
&self->interpreter_addr ,&self->tstate_addr) < 0)
Expand Down Expand Up @@ -456,15 +438,6 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
}
#endif

#if defined(__APPLE__)
self->thread_id_offset = 0;
self->thread_id_offset_initialized = 0;
#endif

#ifdef MS_WINDOWS
self->win_process_buffer = NULL;
self->win_process_buffer_size = 0;
#endif
#ifdef __linux__
self->thread_tids = NULL;
self->thread_tids_capacity = 0;
Expand Down Expand Up @@ -1347,11 +1320,6 @@ RemoteUnwinder_dealloc(PyObject *op)
if (self->code_object_cache) {
_Py_hashtable_destroy(self->code_object_cache);
}
#ifdef MS_WINDOWS
if (self->win_process_buffer != NULL) {
PyMem_Free(self->win_process_buffer);
}
#endif

#ifdef Py_GIL_DISABLED
if (self->tlbc_cache) {
Expand Down Expand Up @@ -1406,11 +1374,6 @@ init_runtime_offsets(RuntimeOffsets *offsets, pid_t pid, int debug)
set_exception_cause(offsets, PyExc_RuntimeError, "Failed to initialize process handle");
return -1;
}
offsets->runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(&offsets->handle);
if (offsets->runtime_start_address == 0) {
set_exception_cause(offsets, PyExc_RuntimeError, "Failed to get Python runtime address");
goto error;
}
if (_Py_RemoteDebug_ReadDebugOffsets(&offsets->handle,
&offsets->runtime_start_address,
&offsets->debug_offsets) < 0)
Expand Down
112 changes: 36 additions & 76 deletions Modules/_remote_debugging/threads.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
#include <sys/wait.h>
#endif

#ifdef MS_WINDOWS
enum {
/* ThreadSystemThreadInformation is not named in the Windows SDK. */
WIN32_THREAD_SYSTEM_INFORMATION = 40,
WIN32_THREAD_RUNNING = 2,
};
#endif

/* Bound traversal of corrupted remote exception chains. */
#define MAX_EXCEPTION_CHAIN_DEPTH (2 << 15)

Expand Down Expand Up @@ -158,45 +166,22 @@ find_running_frame(
* THREAD STATUS FUNCTIONS
* ============================================================================ */

int
get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread_id) {
static int
get_thread_status(RemoteUnwinderObject *unwinder, unsigned long tid) {
#if defined(__APPLE__) && TARGET_OS_OSX
if (!unwinder->thread_id_offset_initialized) {
uint64_t *tids = (uint64_t *)PyMem_Malloc(MAX_NATIVE_THREADS * sizeof(uint64_t));
if (!tids) {
// Non-fatal: thread status is best-effort
return THREAD_STATE_UNKNOWN;
}
int n = proc_pidinfo(unwinder->handle.pid, PROC_PIDLISTTHREADS, 0, tids, MAX_NATIVE_THREADS * sizeof(uint64_t)) / sizeof(uint64_t);
if (n <= 0) {
PyMem_Free(tids);
return THREAD_STATE_UNKNOWN;
}
uint64_t min_offset = UINT64_MAX;
for (int i = 0; i < n; i++) {
uint64_t offset = tids[i] - pthread_id;
if (offset < min_offset) {
min_offset = offset;
}
}
unwinder->thread_id_offset = min_offset;
unwinder->thread_id_offset_initialized = 1;
PyMem_Free(tids);
}
struct proc_threadinfo ti;
uint64_t tid_with_offset = pthread_id + unwinder->thread_id_offset;
if (proc_pidinfo(unwinder->handle.pid, PROC_PIDTHREADINFO, tid_with_offset, &ti, sizeof(ti)) != sizeof(ti)) {
struct proc_threadinfo thread;
int size = proc_pidinfo(unwinder->handle.pid, PROC_PIDTHREADID64INFO,
tid, &thread, sizeof(thread));
if (size != sizeof(thread)) {
return THREAD_STATE_UNKNOWN;
}
if (ti.pth_run_state == TH_STATE_RUNNING) {
return THREAD_STATE_RUNNING;
}
return THREAD_STATE_IDLE;
return thread.pth_run_state == TH_STATE_RUNNING
? THREAD_STATE_RUNNING : THREAD_STATE_IDLE;
#elif defined(__linux__)
char stat_path[256];
char buffer[2048] = "";

snprintf(stat_path, sizeof(stat_path), "/proc/%d/task/%" PRIu64 "/stat", unwinder->handle.pid, tid);
snprintf(stat_path, sizeof(stat_path), "/proc/%d/task/%lu/stat", unwinder->handle.pid, tid);

int fd = open(stat_path, O_RDONLY);
if (fd == -1) {
Expand Down Expand Up @@ -232,46 +217,24 @@ get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread
return THREAD_STATE_UNKNOWN;
}
#elif defined(MS_WINDOWS)
ULONG n;
NTSTATUS status = NtQuerySystemInformation(
SystemProcessInformation,
unwinder->win_process_buffer,
unwinder->win_process_buffer_size,
&n
);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
// Buffer was too small so we reallocate a larger one and try again.
unwinder->win_process_buffer_size = n;
PVOID new_buffer = PyMem_Realloc(unwinder->win_process_buffer, n);
if (!new_buffer) {
// Match Linux/macOS: degrade gracefully on alloc failure
return THREAD_STATE_UNKNOWN;
}
unwinder->win_process_buffer = new_buffer;
return get_thread_status(unwinder, tid, pthread_id);
}
if (status != STATUS_SUCCESS) {
HANDLE thread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)tid);
if (thread == NULL) {
return THREAD_STATE_UNKNOWN;
}

SYSTEM_PROCESS_INFORMATION *pi = (SYSTEM_PROCESS_INFORMATION *)unwinder->win_process_buffer;
while ((ULONG)(ULONG_PTR)pi->UniqueProcessId != unwinder->handle.pid) {
if (pi->NextEntryOffset == 0) {
// Process not found (may have exited)
return THREAD_STATE_UNKNOWN;
}
pi = (SYSTEM_PROCESS_INFORMATION *)(((BYTE *)pi) + pi->NextEntryOffset);
}

SYSTEM_THREAD_INFORMATION *ti = (SYSTEM_THREAD_INFORMATION *)((char *)pi + sizeof(SYSTEM_PROCESS_INFORMATION));
for (size_t i = 0; i < pi->NumberOfThreads; i++, ti++) {
if (ti->ClientId.UniqueThread == (HANDLE)tid) {
return ti->ThreadState != WIN32_THREADSTATE_RUNNING ? THREAD_STATE_IDLE : THREAD_STATE_RUNNING;
}
SYSTEM_THREAD_INFORMATION info;
ULONG size = 0;
NTSTATUS result = NtQueryInformationThread(
thread, (THREADINFOCLASS)WIN32_THREAD_SYSTEM_INFORMATION,
&info, sizeof(info), &size);
CloseHandle(thread);
if (result != STATUS_SUCCESS || size != sizeof(info) ||
(ULONG_PTR)info.ClientId.UniqueProcess != (ULONG_PTR)unwinder->handle.pid ||
(ULONG_PTR)info.ClientId.UniqueThread != tid)
{
return THREAD_STATE_UNKNOWN;
}

// Thread not found (may have exited)
return THREAD_STATE_UNKNOWN;
return info.ThreadState == WIN32_THREAD_RUNNING
? THREAD_STATE_RUNNING : THREAD_STATE_IDLE;
#else
return THREAD_STATE_UNKNOWN;
#endif
Expand Down Expand Up @@ -398,7 +361,8 @@ unwind_stack_for_thread(
STATS_ADD(unwinder, memory_bytes_read, SIZEOF_INTERP_FRAME);
}

long tid = GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.native_thread_id);
unsigned long tid = GET_MEMBER(unsigned long, ts,
unwinder->debug_offsets.thread_state.native_thread_id);

// Calculate thread status using flags (always)
int status_flags = 0;
Expand Down Expand Up @@ -490,13 +454,9 @@ unwind_stack_for_thread(
status_flags |= THREAD_STATUS_HAS_EXCEPTION;
}

// Check CPU status
long pthread_id = GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.thread_id);

// Optimization: only check CPU status if needed by mode because it's expensive
int cpu_status = THREAD_STATE_UNKNOWN;
if (unwinder->mode == PROFILING_MODE_CPU || unwinder->mode == PROFILING_MODE_ALL) {
cpu_status = get_thread_status(unwinder, tid, pthread_id);
cpu_status = get_thread_status(unwinder, tid);
}

if (cpu_status == THREAD_STATE_UNKNOWN) {
Expand Down Expand Up @@ -597,7 +557,7 @@ unwind_stack_for_thread(
}
}
if (thread_id == NULL) {
thread_id = PyLong_FromLongLong(tid);
thread_id = PyLong_FromUnsignedLong(tid);
if (thread_id == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID");
goto error;
Expand Down
Loading
Loading