blob: d40031abbdc0c7347d37a02d56a9b84e13c8adda [file] [log] [blame]
/******************************************************************************
* 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 == 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) {
return 1; // Thread gone
}
}
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