Skip to content
Merged
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
53 changes: 32 additions & 21 deletions Modules/_remote_debugging/_remote_debugging.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,35 @@ typedef struct {
size_t count;
} StackChunkList;

/*
* Context for frame chain traversal operations.
*/
typedef struct {
/* Inputs */
uintptr_t frame_addr; // Starting frame address
uintptr_t base_frame_addr; // Sentinel at bottom (for validation)
uintptr_t gc_frame; // GC frame address (0 if not tracking)
uintptr_t last_profiled_frame; // Last cached frame (0 if no cache)
StackChunkList *chunks; // Pre-copied stack chunks

/* Outputs */
PyObject *frame_info; // List to append FrameInfo objects
uintptr_t *frame_addrs; // Array of visited frame addresses
Py_ssize_t num_addrs; // Count of addresses collected
Py_ssize_t max_addrs; // Capacity of frame_addrs array
uintptr_t last_frame_visited; // Last frame address visited
int stopped_at_cached_frame; // Whether we stopped at cached frame
} FrameWalkContext;

/*
* Context for code object parsing.
*/
typedef struct {
uintptr_t code_addr; // Code object address in remote process
uintptr_t instruction_pointer; // Current instruction pointer
int32_t tlbc_index; // Thread-local bytecode index (free-threading)
} CodeObjectContext;

/* Function pointer types for iteration callbacks */
typedef int (*thread_processor_func)(
RemoteUnwinderObject *unwinder,
Expand Down Expand Up @@ -343,10 +372,7 @@ extern long read_py_long(RemoteUnwinderObject *unwinder, uintptr_t address);
extern int parse_code_object(
RemoteUnwinderObject *unwinder,
PyObject **result,
uintptr_t address,
uintptr_t instruction_pointer,
uintptr_t *previous_frame,
int32_t tlbc_index
const CodeObjectContext *ctx
);

extern PyObject *make_location_info(
Expand Down Expand Up @@ -420,17 +446,7 @@ extern void *find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr);

extern int process_frame_chain(
RemoteUnwinderObject *unwinder,
uintptr_t initial_frame_addr,
StackChunkList *chunks,
PyObject *frame_info,
uintptr_t base_frame_addr,
uintptr_t gc_frame,
uintptr_t last_profiled_frame,
int *stopped_at_cached_frame,
uintptr_t *frame_addrs,
Py_ssize_t *num_addrs,
Py_ssize_t max_addrs,
uintptr_t *out_last_frame_addr
FrameWalkContext *ctx
);

/* Frame cache functions */
Expand Down Expand Up @@ -460,12 +476,7 @@ extern int frame_cache_store(

extern int collect_frames_with_cache(
RemoteUnwinderObject *unwinder,
uintptr_t frame_addr,
StackChunkList *chunks,
PyObject *frame_info,
uintptr_t base_frame_addr,
uintptr_t gc_frame,
uintptr_t last_profiled_frame,
FrameWalkContext *ctx,
uint64_t thread_id);

/* ============================================================================
Expand Down
29 changes: 15 additions & 14 deletions Modules/_remote_debugging/code_objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ cache_tlbc_array(RemoteUnwinderObject *unwinder, uintptr_t code_addr, uintptr_t
PyErr_SetString(PyExc_RuntimeError, "TLBC array size exceeds maximum limit");
return 0; // Invalid size
}
assert(tlbc_size > 0 && tlbc_size <= MAX_TLBC_SIZE);

// Allocate and read the entire TLBC array
size_t array_data_size = tlbc_size * sizeof(void*);
Expand Down Expand Up @@ -156,8 +157,11 @@ parse_linetable(const uintptr_t addrq, const char* linetable, int firstlineno, L
const uint8_t* ptr = (const uint8_t*)(linetable);
uintptr_t addr = 0;
int computed_line = firstlineno; // Running accumulator, separate from output
const size_t MAX_LINETABLE_ENTRIES = 65536;
size_t entry_count = 0;

while (*ptr != '\0') {
while (*ptr != '\0' && entry_count < MAX_LINETABLE_ENTRIES) {
entry_count++;
uint8_t first_byte = *(ptr++);
uint8_t code = (first_byte >> 3) & 15;
size_t length = (first_byte & 7) + 1;
Expand Down Expand Up @@ -277,12 +281,9 @@ make_frame_info(RemoteUnwinderObject *unwinder, PyObject *file, PyObject *locati
int
parse_code_object(RemoteUnwinderObject *unwinder,
PyObject **result,
uintptr_t address,
uintptr_t instruction_pointer,
uintptr_t *previous_frame,
int32_t tlbc_index)
const CodeObjectContext *ctx)
{
void *key = (void *)address;
void *key = (void *)ctx->code_addr;
CachedCodeMetadata *meta = NULL;
PyObject *func = NULL;
PyObject *file = NULL;
Expand All @@ -291,9 +292,9 @@ parse_code_object(RemoteUnwinderObject *unwinder,
#ifdef Py_GIL_DISABLED
// In free threading builds, code object addresses might have the low bit set
// as a flag, so we need to mask it off to get the real address
uintptr_t real_address = address & (~1);
uintptr_t real_address = ctx->code_addr & (~1);
#else
uintptr_t real_address = address;
uintptr_t real_address = ctx->code_addr;
#endif

if (unwinder && unwinder->code_object_cache != NULL) {
Expand Down Expand Up @@ -360,12 +361,12 @@ parse_code_object(RemoteUnwinderObject *unwinder,
linetable = NULL;
}

uintptr_t ip = instruction_pointer;
uintptr_t ip = ctx->instruction_pointer;
ptrdiff_t addrq;

#ifdef Py_GIL_DISABLED
// Handle thread-local bytecode (TLBC) in free threading builds
if (tlbc_index == 0 || unwinder->debug_offsets.code_object.co_tlbc == 0 || unwinder == NULL) {
if (ctx->tlbc_index == 0 || unwinder->debug_offsets.code_object.co_tlbc == 0 || unwinder == NULL) {
// No TLBC or no unwinder - use main bytecode directly
addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
goto done_tlbc;
Expand All @@ -383,10 +384,12 @@ parse_code_object(RemoteUnwinderObject *unwinder,
tlbc_entry = get_tlbc_cache_entry(unwinder, real_address, unwinder->tlbc_generation);
}

if (tlbc_entry && tlbc_index < tlbc_entry->tlbc_array_size) {
if (tlbc_entry && ctx->tlbc_index < tlbc_entry->tlbc_array_size) {
assert(ctx->tlbc_index >= 0);
assert(tlbc_entry->tlbc_array_size > 0);
// Use cached TLBC data
uintptr_t *entries = (uintptr_t *)((char *)tlbc_entry->tlbc_array + sizeof(Py_ssize_t));
uintptr_t tlbc_bytecode_addr = entries[tlbc_index];
uintptr_t tlbc_bytecode_addr = entries[ctx->tlbc_index];

if (tlbc_bytecode_addr != 0) {
// Calculate offset from TLBC bytecode
Expand All @@ -401,8 +404,6 @@ parse_code_object(RemoteUnwinderObject *unwinder,
done_tlbc:
#else
// Non-free-threaded build, always use the main bytecode
(void)tlbc_index; // Suppress unused parameter warning
(void)unwinder; // Suppress unused parameter warning
addrq = (uint16_t *)ip - (uint16_t *)meta->addr_code_adaptive;
#endif
; // Empty statement to avoid C23 extension warning
Expand Down
8 changes: 8 additions & 0 deletions Modules/_remote_debugging/frame_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ frame_cache_find(RemoteUnwinderObject *unwinder, uint64_t thread_id)
return NULL;
}
for (int i = 0; i < FRAME_CACHE_MAX_THREADS; i++) {
assert(i >= 0 && i < FRAME_CACHE_MAX_THREADS);
if (unwinder->frame_cache[i].thread_id == thread_id) {
assert(unwinder->frame_cache[i].num_addrs <= FRAME_CACHE_MAX_FRAMES);
return &unwinder->frame_cache[i];
}
}
Expand Down Expand Up @@ -154,6 +156,8 @@ frame_cache_lookup_and_extend(
return 0;
}

assert(entry->num_addrs >= 0 && entry->num_addrs <= FRAME_CACHE_MAX_FRAMES);

// Find the index where last_profiled_frame matches
Py_ssize_t start_idx = -1;
for (Py_ssize_t i = 0; i < entry->num_addrs; i++) {
Expand All @@ -166,6 +170,7 @@ frame_cache_lookup_and_extend(
if (start_idx < 0) {
return 0; // Not found
}
assert(start_idx < entry->num_addrs);

Py_ssize_t num_frames = PyList_GET_SIZE(entry->frame_list);

Expand Down Expand Up @@ -225,6 +230,7 @@ frame_cache_store(
if (num_addrs > FRAME_CACHE_MAX_FRAMES) {
num_addrs = FRAME_CACHE_MAX_FRAMES;
}
assert(num_addrs >= 0 && num_addrs <= FRAME_CACHE_MAX_FRAMES);

FrameCacheEntry *entry = frame_cache_alloc_slot(unwinder, thread_id);
if (!entry) {
Expand All @@ -245,6 +251,8 @@ frame_cache_store(
entry->thread_id = thread_id;
memcpy(entry->addrs, addrs, num_addrs * sizeof(uintptr_t));
entry->num_addrs = num_addrs;
assert(entry->num_addrs == num_addrs);
assert(entry->thread_id == thread_id);

return 1;
}
Loading
Loading