mirror of
https://github.com/python/cpython.git
synced 2026-01-12 02:10:09 +00:00
When using blocking mode in the remote debugging profiler, ptrace calls to seize threads can fail with EPERM if the thread has exited between listing and attaching, is in a special kernel state, or is already being traced. Previously this raised a RuntimeError that was caught by the Python sampling loop,and retried indefinitely since EPERM is a persistent condition that will not resolve on its own. Treat EPERM the same as ESRCH by returning 1 (skip this thread) instead of -1 (fatal error). This allows profiling to continue with the threads that can be traced rather than entering an endless retry loop printing the same error message repeatedly.
798 lines
25 KiB
C
798 lines
25 KiB
C
/******************************************************************************
|
|
* Remote Debugging Module - Thread Functions
|
|
*
|
|
* This file contains functions for iterating threads and determining
|
|
* thread status in remote process memory.
|
|
******************************************************************************/
|
|
|
|
#include "_remote_debugging.h"
|
|
|
|
#ifndef MS_WINDOWS
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
#include <dirent.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/wait.h>
|
|
#endif
|
|
|
|
/* ============================================================================
|
|
* THREAD ITERATION FUNCTIONS
|
|
* ============================================================================ */
|
|
|
|
int
|
|
iterate_threads(
|
|
RemoteUnwinderObject *unwinder,
|
|
thread_processor_func processor,
|
|
void *context
|
|
) {
|
|
uintptr_t thread_state_addr;
|
|
unsigned long tid = 0;
|
|
const size_t MAX_THREADS = 8192;
|
|
size_t thread_count = 0;
|
|
|
|
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
unwinder->interpreter_addr + (uintptr_t)unwinder->debug_offsets.interpreter_state.threads_main,
|
|
sizeof(void*),
|
|
&thread_state_addr))
|
|
{
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read main thread state");
|
|
return -1;
|
|
}
|
|
|
|
while (thread_state_addr != 0 && thread_count < MAX_THREADS) {
|
|
thread_count++;
|
|
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
thread_state_addr + (uintptr_t)unwinder->debug_offsets.thread_state.native_thread_id,
|
|
sizeof(tid),
|
|
&tid))
|
|
{
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread ID");
|
|
return -1;
|
|
}
|
|
|
|
// Call the processor function for this thread
|
|
if (processor(unwinder, thread_state_addr, tid, context) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
// Move to next thread
|
|
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
thread_state_addr + (uintptr_t)unwinder->debug_offsets.thread_state.next,
|
|
sizeof(void*),
|
|
&thread_state_addr))
|
|
{
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read next thread state");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* INTERPRETER STATE AND THREAD DISCOVERY FUNCTIONS
|
|
* ============================================================================ */
|
|
|
|
int
|
|
populate_initial_state_data(
|
|
int all_threads,
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t runtime_start_address,
|
|
uintptr_t *interpreter_state,
|
|
uintptr_t *tstate
|
|
) {
|
|
uintptr_t interpreter_state_list_head =
|
|
(uintptr_t)unwinder->debug_offsets.runtime_state.interpreters_head;
|
|
|
|
uintptr_t address_of_interpreter_state;
|
|
int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
runtime_start_address + interpreter_state_list_head,
|
|
sizeof(void*),
|
|
&address_of_interpreter_state);
|
|
if (bytes_read < 0) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter state address");
|
|
return -1;
|
|
}
|
|
|
|
if (address_of_interpreter_state == 0) {
|
|
PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Interpreter state is NULL");
|
|
return -1;
|
|
}
|
|
|
|
*interpreter_state = address_of_interpreter_state;
|
|
|
|
if (all_threads) {
|
|
*tstate = 0;
|
|
return 0;
|
|
}
|
|
|
|
uintptr_t address_of_thread = address_of_interpreter_state +
|
|
(uintptr_t)unwinder->debug_offsets.interpreter_state.threads_main;
|
|
|
|
if (_Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle,
|
|
address_of_thread,
|
|
sizeof(void*),
|
|
tstate) < 0) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read main thread state address");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
find_running_frame(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t address_of_thread,
|
|
uintptr_t *frame
|
|
) {
|
|
if ((void*)address_of_thread != NULL) {
|
|
int err = read_ptr(
|
|
unwinder,
|
|
address_of_thread + (uintptr_t)unwinder->debug_offsets.thread_state.current_frame,
|
|
frame);
|
|
if (err) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read current frame pointer");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
*frame = (uintptr_t)NULL;
|
|
return 0;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* THREAD STATUS FUNCTIONS
|
|
* ============================================================================ */
|
|
|
|
int
|
|
get_thread_status(RemoteUnwinderObject *unwinder, uint64_t tid, uint64_t pthread_id) {
|
|
#if defined(__APPLE__) && TARGET_OS_OSX
|
|
if (unwinder->thread_id_offset == 0) {
|
|
uint64_t *tids = (uint64_t *)PyMem_Malloc(MAX_NATIVE_THREADS * sizeof(uint64_t));
|
|
if (!tids) {
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
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;
|
|
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)) {
|
|
return THREAD_STATE_UNKNOWN;
|
|
}
|
|
if (ti.pth_run_state == TH_STATE_RUNNING) {
|
|
return THREAD_STATE_RUNNING;
|
|
}
|
|
return THREAD_STATE_IDLE;
|
|
#elif defined(__linux__)
|
|
char stat_path[256];
|
|
char buffer[2048] = "";
|
|
|
|
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) {
|
|
return THREAD_STATE_UNKNOWN;
|
|
}
|
|
|
|
if (read(fd, buffer, 2047) == 0) {
|
|
close(fd);
|
|
return THREAD_STATE_UNKNOWN;
|
|
}
|
|
close(fd);
|
|
|
|
char *p = strchr(buffer, ')');
|
|
if (!p) {
|
|
return THREAD_STATE_UNKNOWN;
|
|
}
|
|
|
|
p += 2; // Skip ") "
|
|
if (*p == ' ') {
|
|
p++;
|
|
}
|
|
|
|
switch (*p) {
|
|
case 'R': // Running
|
|
return THREAD_STATE_RUNNING;
|
|
case 'S': // Interruptible sleep
|
|
case 'D': // Uninterruptible sleep
|
|
case 'T': // Stopped
|
|
case 'Z': // Zombie
|
|
case 'I': // Idle kernel thread
|
|
return THREAD_STATE_IDLE;
|
|
default:
|
|
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) {
|
|
return -1;
|
|
}
|
|
unwinder->win_process_buffer = new_buffer;
|
|
return get_thread_status(unwinder, tid, pthread_id);
|
|
}
|
|
if (status != STATUS_SUCCESS) {
|
|
return -1;
|
|
}
|
|
|
|
SYSTEM_PROCESS_INFORMATION *pi = (SYSTEM_PROCESS_INFORMATION *)unwinder->win_process_buffer;
|
|
while ((ULONG)(ULONG_PTR)pi->UniqueProcessId != unwinder->handle.pid) {
|
|
if (pi->NextEntryOffset == 0) {
|
|
// We didn't find the process
|
|
return -1;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
#else
|
|
return THREAD_STATE_UNKNOWN;
|
|
#endif
|
|
}
|
|
|
|
/* ============================================================================
|
|
* STACK UNWINDING FUNCTIONS
|
|
* ============================================================================ */
|
|
|
|
typedef struct {
|
|
unsigned int initialized:1;
|
|
unsigned int bound:1;
|
|
unsigned int unbound:1;
|
|
unsigned int bound_gilstate:1;
|
|
unsigned int active:1;
|
|
unsigned int finalizing:1;
|
|
unsigned int cleared:1;
|
|
unsigned int finalized:1;
|
|
unsigned int :24;
|
|
} _thread_status;
|
|
|
|
PyObject*
|
|
unwind_stack_for_thread(
|
|
RemoteUnwinderObject *unwinder,
|
|
uintptr_t *current_tstate,
|
|
uintptr_t gil_holder_tstate,
|
|
uintptr_t gc_frame
|
|
) {
|
|
PyObject *frame_info = NULL;
|
|
PyObject *thread_id = NULL;
|
|
PyObject *result = NULL;
|
|
StackChunkList chunks = {0};
|
|
|
|
char ts[SIZEOF_THREAD_STATE];
|
|
int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
|
|
&unwinder->handle, *current_tstate, (size_t)unwinder->debug_offsets.thread_state.size, ts);
|
|
if (bytes_read < 0) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read thread state");
|
|
goto error;
|
|
}
|
|
STATS_INC(unwinder, memory_reads);
|
|
STATS_ADD(unwinder, memory_bytes_read, unwinder->debug_offsets.thread_state.size);
|
|
|
|
long tid = GET_MEMBER(long, ts, unwinder->debug_offsets.thread_state.native_thread_id);
|
|
|
|
// Read GC collecting state from the interpreter (before any skip checks)
|
|
uintptr_t interp_addr = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.interp);
|
|
|
|
// Read the GC runtime state from the interpreter state
|
|
uintptr_t gc_addr = interp_addr + unwinder->debug_offsets.interpreter_state.gc;
|
|
char gc_state[SIZEOF_GC_RUNTIME_STATE];
|
|
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, gc_addr, unwinder->debug_offsets.gc.size, gc_state) < 0) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read GC state");
|
|
goto error;
|
|
}
|
|
STATS_INC(unwinder, memory_reads);
|
|
STATS_ADD(unwinder, memory_bytes_read, unwinder->debug_offsets.gc.size);
|
|
|
|
// Calculate thread status using flags (always)
|
|
int status_flags = 0;
|
|
|
|
// Check GIL status
|
|
int has_gil = 0;
|
|
int gil_requested = 0;
|
|
#ifdef Py_GIL_DISABLED
|
|
int active = GET_MEMBER(_thread_status, ts, unwinder->debug_offsets.thread_state.status).active;
|
|
has_gil = active;
|
|
(void)gil_requested; // unused
|
|
#else
|
|
// Read holds_gil directly from thread state
|
|
has_gil = GET_MEMBER(int, ts, unwinder->debug_offsets.thread_state.holds_gil);
|
|
|
|
// Check if thread is actively requesting the GIL
|
|
if (unwinder->debug_offsets.thread_state.gil_requested != 0) {
|
|
gil_requested = GET_MEMBER(int, ts, unwinder->debug_offsets.thread_state.gil_requested);
|
|
}
|
|
|
|
// Set GIL_REQUESTED flag if thread is waiting
|
|
if (!has_gil && gil_requested) {
|
|
status_flags |= THREAD_STATUS_GIL_REQUESTED;
|
|
}
|
|
#endif
|
|
if (has_gil) {
|
|
status_flags |= THREAD_STATUS_HAS_GIL;
|
|
// gh-142207 for remote debugging.
|
|
gil_requested = 0;
|
|
}
|
|
|
|
// Check exception state (both raised and handled exceptions)
|
|
int has_exception = 0;
|
|
|
|
// Check current_exception (exception being raised/propagated)
|
|
uintptr_t current_exception = GET_MEMBER(uintptr_t, ts,
|
|
unwinder->debug_offsets.thread_state.current_exception);
|
|
if (current_exception != 0) {
|
|
has_exception = 1;
|
|
}
|
|
|
|
// Check exc_state.exc_value (exception being handled in except block)
|
|
// exc_state is embedded in PyThreadState, so we read it directly from
|
|
// the thread state buffer. This catches most cases; nested exception
|
|
// handlers where exc_info points elsewhere are rare.
|
|
if (!has_exception) {
|
|
uintptr_t exc_value = GET_MEMBER(uintptr_t, ts,
|
|
unwinder->debug_offsets.thread_state.exc_state +
|
|
unwinder->debug_offsets.err_stackitem.exc_value);
|
|
if (exc_value != 0) {
|
|
has_exception = 1;
|
|
}
|
|
}
|
|
|
|
if (has_exception) {
|
|
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 = -1;
|
|
if (unwinder->mode == PROFILING_MODE_CPU || unwinder->mode == PROFILING_MODE_ALL) {
|
|
cpu_status = get_thread_status(unwinder, tid, pthread_id);
|
|
}
|
|
|
|
if (cpu_status == -1) {
|
|
status_flags |= THREAD_STATUS_UNKNOWN;
|
|
} else if (cpu_status == THREAD_STATE_RUNNING) {
|
|
status_flags |= THREAD_STATUS_ON_CPU;
|
|
}
|
|
|
|
// Check if we should skip this thread based on mode
|
|
int should_skip = 0;
|
|
if (unwinder->skip_non_matching_threads) {
|
|
if (unwinder->mode == PROFILING_MODE_CPU) {
|
|
// Skip if not on CPU
|
|
should_skip = !(status_flags & THREAD_STATUS_ON_CPU);
|
|
} else if (unwinder->mode == PROFILING_MODE_GIL) {
|
|
// Skip if doesn't have GIL
|
|
should_skip = !(status_flags & THREAD_STATUS_HAS_GIL);
|
|
} else if (unwinder->mode == PROFILING_MODE_EXCEPTION) {
|
|
// Skip if thread doesn't have an exception active
|
|
should_skip = !(status_flags & THREAD_STATUS_HAS_EXCEPTION);
|
|
}
|
|
// PROFILING_MODE_WALL and PROFILING_MODE_ALL never skip
|
|
}
|
|
|
|
if (should_skip) {
|
|
// Advance to next thread and return NULL to skip processing
|
|
*current_tstate = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.next);
|
|
return NULL;
|
|
}
|
|
|
|
uintptr_t frame_addr = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.current_frame);
|
|
uintptr_t base_frame_addr = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.base_frame);
|
|
|
|
frame_info = PyList_New(0);
|
|
if (!frame_info) {
|
|
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create frame info list");
|
|
goto error;
|
|
}
|
|
|
|
// In cache mode, copying stack chunks is more expensive than direct memory reads
|
|
if (!unwinder->cache_frames) {
|
|
if (copy_stack_chunks(unwinder, *current_tstate, &chunks) < 0) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to copy stack chunks");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
uintptr_t addrs[FRAME_CACHE_MAX_FRAMES];
|
|
FrameWalkContext ctx = {
|
|
.frame_addr = frame_addr,
|
|
.base_frame_addr = base_frame_addr,
|
|
.gc_frame = gc_frame,
|
|
.chunks = &chunks,
|
|
.frame_info = frame_info,
|
|
.frame_addrs = addrs,
|
|
.num_addrs = 0,
|
|
.max_addrs = FRAME_CACHE_MAX_FRAMES,
|
|
};
|
|
assert(ctx.max_addrs == FRAME_CACHE_MAX_FRAMES);
|
|
|
|
if (unwinder->cache_frames) {
|
|
// Use cache to avoid re-reading unchanged parent frames
|
|
ctx.last_profiled_frame = GET_MEMBER(uintptr_t, ts,
|
|
unwinder->debug_offsets.thread_state.last_profiled_frame);
|
|
if (collect_frames_with_cache(unwinder, &ctx, tid) < 0) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to collect frames");
|
|
goto error;
|
|
}
|
|
// Update last_profiled_frame for next sample
|
|
uintptr_t lpf_addr =
|
|
*current_tstate + (uintptr_t)unwinder->debug_offsets.thread_state.last_profiled_frame;
|
|
if (_Py_RemoteDebug_WriteRemoteMemory(&unwinder->handle, lpf_addr,
|
|
sizeof(uintptr_t), &frame_addr) < 0) {
|
|
PyErr_Clear(); // Non-fatal
|
|
}
|
|
} else {
|
|
// No caching - process entire frame chain with base_frame validation
|
|
if (process_frame_chain(unwinder, &ctx) < 0) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process frame chain");
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
*current_tstate = GET_MEMBER(uintptr_t, ts, unwinder->debug_offsets.thread_state.next);
|
|
|
|
thread_id = PyLong_FromLongLong(tid);
|
|
if (thread_id == NULL) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread ID");
|
|
goto error;
|
|
}
|
|
|
|
RemoteDebuggingState *state = RemoteDebugging_GetStateFromObject((PyObject*)unwinder);
|
|
result = PyStructSequence_New(state->ThreadInfo_Type);
|
|
if (result == NULL) {
|
|
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to create ThreadInfo");
|
|
goto error;
|
|
}
|
|
|
|
// Always use status_flags
|
|
PyObject *py_status = PyLong_FromLong(status_flags);
|
|
if (py_status == NULL) {
|
|
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to create thread status");
|
|
goto error;
|
|
}
|
|
|
|
// py_status contains status flags (bitfield)
|
|
PyStructSequence_SetItem(result, 0, thread_id);
|
|
PyStructSequence_SetItem(result, 1, py_status); // Steals reference
|
|
PyStructSequence_SetItem(result, 2, frame_info); // Steals reference
|
|
|
|
cleanup_stack_chunks(&chunks);
|
|
return result;
|
|
|
|
error:
|
|
Py_XDECREF(frame_info);
|
|
Py_XDECREF(thread_id);
|
|
Py_XDECREF(result);
|
|
cleanup_stack_chunks(&chunks);
|
|
return NULL;
|
|
}
|
|
|
|
/* ============================================================================
|
|
* PROCESS STOP FUNCTIONS
|
|
* ============================================================================ */
|
|
|
|
#if defined(__APPLE__) && TARGET_OS_OSX
|
|
|
|
void
|
|
_Py_RemoteDebug_InitThreadsState(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
st->task = MACH_PORT_NULL;
|
|
st->suspended = 0;
|
|
}
|
|
|
|
int
|
|
_Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
kern_return_t kr = task_suspend(unwinder->handle.task);
|
|
if (kr != KERN_SUCCESS) {
|
|
if (kr == MACH_SEND_INVALID_DEST) {
|
|
PyErr_Format(PyExc_ProcessLookupError,
|
|
"Process %d has terminated", unwinder->handle.pid);
|
|
} else {
|
|
PyErr_Format(PyExc_RuntimeError,
|
|
"task_suspend failed for PID %d: kern_return_t %d",
|
|
unwinder->handle.pid, kr);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
st->task = unwinder->handle.task;
|
|
st->suspended = 1;
|
|
_Py_RemoteDebug_ClearCache(&unwinder->handle);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
_Py_RemoteDebug_ResumeAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
if (!st->suspended || st->task == MACH_PORT_NULL) {
|
|
return;
|
|
}
|
|
task_resume(st->task);
|
|
st->task = MACH_PORT_NULL;
|
|
st->suspended = 0;
|
|
}
|
|
|
|
#elif defined(__linux__)
|
|
|
|
void
|
|
_Py_RemoteDebug_InitThreadsState(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
st->tids = NULL;
|
|
st->count = 0;
|
|
}
|
|
|
|
static int
|
|
read_thread_ids(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
char task_path[64];
|
|
snprintf(task_path, sizeof(task_path), "/proc/%d/task", unwinder->handle.pid);
|
|
|
|
DIR *dir = opendir(task_path);
|
|
if (dir == NULL) {
|
|
st->tids = NULL;
|
|
st->count = 0;
|
|
if (errno == ENOENT || errno == ESRCH) {
|
|
PyErr_Format(PyExc_ProcessLookupError,
|
|
"Process %d has terminated", unwinder->handle.pid);
|
|
} else {
|
|
PyErr_SetFromErrnoWithFilename(PyExc_OSError, task_path);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
st->count = 0;
|
|
|
|
struct dirent *entry;
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
if (entry->d_name[0] < '1' || entry->d_name[0] > '9') {
|
|
continue;
|
|
}
|
|
char *endptr;
|
|
long tid = strtol(entry->d_name, &endptr, 10);
|
|
if (*endptr != '\0' || tid <= 0) {
|
|
continue;
|
|
}
|
|
if (st->count >= unwinder->thread_tids_capacity) {
|
|
size_t new_cap = unwinder->thread_tids_capacity == 0 ? 64 : unwinder->thread_tids_capacity * 2;
|
|
pid_t *new_tids = PyMem_RawRealloc(unwinder->thread_tids, new_cap * sizeof(pid_t));
|
|
if (new_tids == NULL) {
|
|
closedir(dir);
|
|
st->tids = NULL;
|
|
st->count = 0;
|
|
PyErr_NoMemory();
|
|
return -1;
|
|
}
|
|
unwinder->thread_tids = new_tids;
|
|
unwinder->thread_tids_capacity = new_cap;
|
|
}
|
|
unwinder->thread_tids[st->count++] = (pid_t)tid;
|
|
}
|
|
|
|
st->tids = unwinder->thread_tids;
|
|
closedir(dir);
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
detach_threads(_Py_RemoteDebug_ThreadsState *st, size_t up_to)
|
|
{
|
|
for (size_t j = 0; j < up_to; j++) {
|
|
ptrace(PTRACE_DETACH, st->tids[j], NULL, NULL);
|
|
}
|
|
}
|
|
|
|
static int
|
|
seize_thread(pid_t tid)
|
|
{
|
|
if (ptrace(PTRACE_SEIZE, tid, NULL, 0) == 0) {
|
|
return 0;
|
|
}
|
|
if (errno == ESRCH) {
|
|
return 1; // Thread gone, skip
|
|
}
|
|
if (errno == EPERM) {
|
|
// Thread may have exited, be in a special state, or already be traced.
|
|
// Skip rather than fail - this avoids endless retry loops when
|
|
// threads transiently become inaccessible.
|
|
return 1;
|
|
}
|
|
if (errno == EINVAL || errno == EIO) {
|
|
// Fallback for older kernels
|
|
if (ptrace(PTRACE_ATTACH, tid, NULL, NULL) == 0) {
|
|
int status;
|
|
waitpid(tid, &status, __WALL);
|
|
return 0;
|
|
}
|
|
if (errno == ESRCH || errno == EPERM) {
|
|
return 1; // Thread gone or inaccessible
|
|
}
|
|
}
|
|
return -1; // Real error
|
|
}
|
|
|
|
int
|
|
_Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
if (read_thread_ids(unwinder, st) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
for (size_t i = 0; i < st->count; i++) {
|
|
pid_t tid = st->tids[i];
|
|
|
|
int ret = seize_thread(tid);
|
|
if (ret == 1) {
|
|
continue; // Thread gone, skip
|
|
}
|
|
if (ret < 0) {
|
|
detach_threads(st, i);
|
|
PyErr_Format(PyExc_RuntimeError, "Failed to seize thread %d: %s", tid, strerror(errno));
|
|
st->tids = NULL;
|
|
st->count = 0;
|
|
return -1;
|
|
}
|
|
|
|
if (ptrace(PTRACE_INTERRUPT, tid, NULL, NULL) == -1 && errno != ESRCH) {
|
|
detach_threads(st, i + 1);
|
|
PyErr_Format(PyExc_RuntimeError, "Failed to interrupt thread %d: %s", tid, strerror(errno));
|
|
st->tids = NULL;
|
|
st->count = 0;
|
|
return -1;
|
|
}
|
|
|
|
int status;
|
|
if (waitpid(tid, &status, __WALL) == -1 && errno != ECHILD && errno != ESRCH) {
|
|
detach_threads(st, i + 1);
|
|
PyErr_Format(PyExc_RuntimeError, "waitpid failed for thread %d: %s", tid, strerror(errno));
|
|
st->tids = NULL;
|
|
st->count = 0;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
_Py_RemoteDebug_ResumeAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
if (st->tids == NULL || st->count == 0) {
|
|
return;
|
|
}
|
|
detach_threads(st, st->count);
|
|
st->tids = NULL;
|
|
st->count = 0;
|
|
}
|
|
|
|
#elif defined(MS_WINDOWS)
|
|
|
|
void
|
|
_Py_RemoteDebug_InitThreadsState(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
st->hProcess = NULL;
|
|
st->suspended = 0;
|
|
}
|
|
|
|
int
|
|
_Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
static NtSuspendProcessFunc pNtSuspendProcess = NULL;
|
|
static int tried_load = 0;
|
|
|
|
if (!tried_load) {
|
|
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
|
|
if (hNtdll) {
|
|
pNtSuspendProcess = (NtSuspendProcessFunc)GetProcAddress(hNtdll, "NtSuspendProcess");
|
|
}
|
|
tried_load = 1;
|
|
}
|
|
|
|
if (pNtSuspendProcess == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "NtSuspendProcess not available");
|
|
return -1;
|
|
}
|
|
|
|
NTSTATUS status = pNtSuspendProcess(unwinder->handle.hProcess);
|
|
if (status >= 0) {
|
|
st->hProcess = unwinder->handle.hProcess;
|
|
st->suspended = 1;
|
|
_Py_RemoteDebug_ClearCache(&unwinder->handle);
|
|
return 0;
|
|
}
|
|
|
|
PyErr_Format(PyExc_RuntimeError, "NtSuspendProcess failed: 0x%lx", status);
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
_Py_RemoteDebug_ResumeAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
if (!st->suspended || st->hProcess == NULL) {
|
|
return;
|
|
}
|
|
|
|
static NtResumeProcessFunc pNtResumeProcess = NULL;
|
|
static int tried_load = 0;
|
|
|
|
if (!tried_load) {
|
|
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
|
|
if (hNtdll) {
|
|
pNtResumeProcess = (NtResumeProcessFunc)GetProcAddress(hNtdll, "NtResumeProcess");
|
|
}
|
|
tried_load = 1;
|
|
}
|
|
|
|
if (pNtResumeProcess != NULL) {
|
|
pNtResumeProcess(st->hProcess);
|
|
}
|
|
st->hProcess = NULL;
|
|
st->suspended = 0;
|
|
}
|
|
|
|
#else
|
|
|
|
void
|
|
_Py_RemoteDebug_InitThreadsState(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
(void)unwinder;
|
|
(void)st;
|
|
}
|
|
|
|
int
|
|
_Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
(void)unwinder;
|
|
(void)st;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
_Py_RemoteDebug_ResumeAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_ThreadsState *st)
|
|
{
|
|
(void)unwinder;
|
|
(void)st;
|
|
}
|
|
|
|
#endif
|