mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
Introduces LocationInfo struct sequence with end_lineno, col_offset, and end_col_offset fields. Adds opcodes parameter to RemoteUnwinder that extracts the currently executing opcode alongside its source span. Refactors linetable parsing to correctly accumulate line numbers separately from output values, fixing edge cases in computed_line.
351 lines
13 KiB
C
351 lines
13 KiB
C
/******************************************************************************
|
|
* 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, None as location (synthetic frame), None as opcode
|
|
PyObject *extra_frame_info = make_frame_info(
|
|
unwinder, _Py_LATIN1_CHR('~'), Py_None, extra_frame, Py_None);
|
|
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;
|
|
}
|