cpython/Modules/_remote_debugging/frames.c

352 lines
13 KiB
C
Raw Normal View History

/******************************************************************************
* Remote Debugging Module - Frame Functions
*
* This file contains functions for parsing interpreter frames and
* managing stack chunks from remote process memory.
******************************************************************************/
#include "_remote_debugging.h"
/* ============================================================================
* STACK CHUNK MANAGEMENT FUNCTIONS
* ============================================================================ */
void
cleanup_stack_chunks(StackChunkList *chunks)
{
for (size_t i = 0; i < chunks->count; ++i) {
PyMem_RawFree(chunks->chunks[i].local_copy);
}
PyMem_RawFree(chunks->chunks);
}
static int
process_single_stack_chunk(
RemoteUnwinderObject *unwinder,
uintptr_t chunk_addr,
StackChunkInfo *chunk_info
) {
// Start with default size assumption
size_t current_size = _PY_DATA_STACK_CHUNK_SIZE;
char *this_chunk = PyMem_RawMalloc(current_size);
if (!this_chunk) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate stack chunk buffer");
return -1;
}
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, chunk_addr, current_size, this_chunk) < 0) {
PyMem_RawFree(this_chunk);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read stack chunk");
return -1;
}
// Check actual size and reread if necessary
size_t actual_size = GET_MEMBER(size_t, this_chunk, offsetof(_PyStackChunk, size));
if (actual_size != current_size) {
this_chunk = PyMem_RawRealloc(this_chunk, actual_size);
if (!this_chunk) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to reallocate stack chunk buffer");
return -1;
}
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, chunk_addr, actual_size, this_chunk) < 0) {
PyMem_RawFree(this_chunk);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to reread stack chunk with correct size");
return -1;
}
current_size = actual_size;
}
chunk_info->remote_addr = chunk_addr;
chunk_info->size = current_size;
chunk_info->local_copy = this_chunk;
return 0;
}
int
copy_stack_chunks(RemoteUnwinderObject *unwinder,
uintptr_t tstate_addr,
StackChunkList *out_chunks)
{
uintptr_t chunk_addr;
StackChunkInfo *chunks = NULL;
size_t count = 0;
size_t max_chunks = 16;
if (read_ptr(unwinder, tstate_addr + (uintptr_t)unwinder->debug_offsets.thread_state.datastack_chunk, &chunk_addr)) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read initial stack chunk address");
return -1;
}
chunks = PyMem_RawMalloc(max_chunks * sizeof(StackChunkInfo));
if (!chunks) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate stack chunks array");
return -1;
}
while (chunk_addr != 0) {
// Grow array if needed
if (count >= max_chunks) {
max_chunks *= 2;
StackChunkInfo *new_chunks = PyMem_RawRealloc(chunks, max_chunks * sizeof(StackChunkInfo));
if (!new_chunks) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to grow stack chunks array");
goto error;
}
chunks = new_chunks;
}
// Process this chunk
if (process_single_stack_chunk(unwinder, chunk_addr, &chunks[count]) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to process stack chunk");
goto error;
}
// Get next chunk address and increment count
chunk_addr = GET_MEMBER(uintptr_t, chunks[count].local_copy, offsetof(_PyStackChunk, previous));
count++;
}
out_chunks->chunks = chunks;
out_chunks->count = count;
return 0;
error:
for (size_t i = 0; i < count; ++i) {
PyMem_RawFree(chunks[i].local_copy);
}
PyMem_RawFree(chunks);
return -1;
}
void *
find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr)
{
for (size_t i = 0; i < chunks->count; ++i) {
uintptr_t base = chunks->chunks[i].remote_addr + offsetof(_PyStackChunk, data);
size_t payload = chunks->chunks[i].size - offsetof(_PyStackChunk, data);
if (remote_ptr >= base && remote_ptr < base + payload) {
return (char *)chunks->chunks[i].local_copy + (remote_ptr - chunks->chunks[i].remote_addr);
}
}
return NULL;
}
/* ============================================================================
* FRAME PARSING FUNCTIONS
* ============================================================================ */
int
is_frame_valid(
RemoteUnwinderObject *unwinder,
uintptr_t frame_addr,
uintptr_t code_object_addr
) {
if ((void*)code_object_addr == NULL) {
return 0;
}
void* frame = (void*)frame_addr;
if (GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) == FRAME_OWNED_BY_INTERPRETER) {
return 0; // C frame
}
if (GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) != FRAME_OWNED_BY_GENERATOR
&& GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner) != FRAME_OWNED_BY_THREAD) {
PyErr_Format(PyExc_RuntimeError, "Unhandled frame owner %d.\n",
GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner));
set_exception_cause(unwinder, PyExc_RuntimeError, "Unhandled frame owner type in async frame");
return -1;
}
return 1;
}
int
parse_frame_object(
RemoteUnwinderObject *unwinder,
PyObject** result,
uintptr_t address,
uintptr_t* address_of_code_object,
uintptr_t* previous_frame
) {
char frame[SIZEOF_INTERP_FRAME];
*address_of_code_object = 0;
Py_ssize_t bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
address,
SIZEOF_INTERP_FRAME,
frame
);
if (bytes_read < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read interpreter frame");
return -1;
}
*previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
uintptr_t code_object = GET_MEMBER_NO_TAG(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.executable);
int frame_valid = is_frame_valid(unwinder, (uintptr_t)frame, code_object);
if (frame_valid != 1) {
return frame_valid;
}
uintptr_t instruction_pointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.instr_ptr);
// Get tlbc_index for free threading builds
int32_t tlbc_index = 0;
#ifdef Py_GIL_DISABLED
if (unwinder->debug_offsets.interpreter_frame.tlbc_index != 0) {
tlbc_index = GET_MEMBER(int32_t, frame, unwinder->debug_offsets.interpreter_frame.tlbc_index);
}
#endif
*address_of_code_object = code_object;
return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
}
int
parse_frame_from_chunks(
RemoteUnwinderObject *unwinder,
PyObject **result,
uintptr_t address,
uintptr_t *previous_frame,
uintptr_t *stackpointer,
StackChunkList *chunks
) {
void *frame_ptr = find_frame_in_chunks(chunks, address);
if (!frame_ptr) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Frame not found in stack chunks");
return -1;
}
char *frame = (char *)frame_ptr;
*previous_frame = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.previous);
*stackpointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.stackpointer);
uintptr_t code_object = GET_MEMBER_NO_TAG(uintptr_t, frame_ptr, unwinder->debug_offsets.interpreter_frame.executable);
int frame_valid = is_frame_valid(unwinder, (uintptr_t)frame, code_object);
if (frame_valid != 1) {
return frame_valid;
}
uintptr_t instruction_pointer = GET_MEMBER(uintptr_t, frame, unwinder->debug_offsets.interpreter_frame.instr_ptr);
// Get tlbc_index for free threading builds
int32_t tlbc_index = 0;
#ifdef Py_GIL_DISABLED
if (unwinder->debug_offsets.interpreter_frame.tlbc_index != 0) {
tlbc_index = GET_MEMBER(int32_t, frame, unwinder->debug_offsets.interpreter_frame.tlbc_index);
}
#endif
return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
}
/* ============================================================================
* FRAME CHAIN PROCESSING
* ============================================================================ */
int
process_frame_chain(
RemoteUnwinderObject *unwinder,
uintptr_t initial_frame_addr,
StackChunkList *chunks,
PyObject *frame_info,
uintptr_t gc_frame)
{
uintptr_t frame_addr = initial_frame_addr;
uintptr_t prev_frame_addr = 0;
const size_t MAX_FRAMES = 1024;
size_t frame_count = 0;
while ((void*)frame_addr != NULL) {
PyObject *frame = NULL;
uintptr_t next_frame_addr = 0;
uintptr_t stackpointer = 0;
if (++frame_count > MAX_FRAMES) {
PyErr_SetString(PyExc_RuntimeError, "Too many stack frames (possible infinite loop)");
set_exception_cause(unwinder, PyExc_RuntimeError, "Frame chain iteration limit exceeded");
return -1;
}
// Try chunks first, fallback to direct memory read
if (parse_frame_from_chunks(unwinder, &frame, frame_addr, &next_frame_addr, &stackpointer, chunks) < 0) {
PyErr_Clear();
uintptr_t address_of_code_object = 0;
if (parse_frame_object(unwinder, &frame, frame_addr, &address_of_code_object ,&next_frame_addr) < 0) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to parse frame object in chain");
return -1;
}
}
if (frame == NULL && PyList_GET_SIZE(frame_info) == 0) {
// If the first frame is missing, the chain is broken:
const char *e = "Failed to parse initial frame in chain";
PyErr_SetString(PyExc_RuntimeError, e);
return -1;
}
PyObject *extra_frame = NULL;
// This frame kicked off the current GC collection:
if (unwinder->gc && frame_addr == gc_frame) {
_Py_DECLARE_STR(gc, "<GC>");
extra_frame = &_Py_STR(gc);
}
// Otherwise, check for native frames to insert:
else if (unwinder->native &&
// We've reached an interpreter trampoline frame:
frame == NULL &&
// Bottommost frame is always native, so skip that one:
next_frame_addr &&
// Only suppress native frames if GC tracking is enabled and the next frame will be a GC frame:
!(unwinder->gc && next_frame_addr == gc_frame))
{
_Py_DECLARE_STR(native, "<native>");
extra_frame = &_Py_STR(native);
}
if (extra_frame) {
// Use "~" as file and 0 as line, since that's what pstats uses:
PyObject *extra_frame_info = make_frame_info(
unwinder, _Py_LATIN1_CHR('~'), _PyLong_GetZero(), extra_frame);
if (extra_frame_info == NULL) {
return -1;
}
int error = PyList_Append(frame_info, extra_frame_info);
Py_DECREF(extra_frame_info);
if (error) {
const char *e = "Failed to append extra frame to frame info list";
set_exception_cause(unwinder, PyExc_RuntimeError, e);
return -1;
}
}
if (frame) {
if (prev_frame_addr && frame_addr != prev_frame_addr) {
const char *f = "Broken frame chain: expected frame at 0x%lx, got 0x%lx";
PyErr_Format(PyExc_RuntimeError, f, prev_frame_addr, frame_addr);
Py_DECREF(frame);
const char *e = "Frame chain consistency check failed";
set_exception_cause(unwinder, PyExc_RuntimeError, e);
return -1;
}
if (PyList_Append(frame_info, frame) == -1) {
Py_DECREF(frame);
const char *e = "Failed to append frame to frame info list";
set_exception_cause(unwinder, PyExc_RuntimeError, e);
return -1;
}
Py_DECREF(frame);
}
prev_frame_addr = next_frame_addr;
frame_addr = next_frame_addr;
}
return 0;
}