🌐 AI搜索 & 代理 主页
Skip to content

Commit 180daaa

Browse files
pablogsalclaude
andcommitted
Clear stale last_profiled_frame values when initializing RemoteUnwinder
When a new RemoteUnwinder is created to profile a process that was previously profiled by another unwinder, the last_profiled_frame values in thread states may be stale. This caused the new unwinder to stop frame walking early (when hitting the stale marker) but fail to find cached frames (since the cache is empty in the new unwinder), resulting in empty frame lists. Fix by clearing last_profiled_frame for all threads when a new RemoteUnwinder with cache_frames=True is initialized. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0b8b45b commit 180daaa

File tree

3 files changed

+64
-0
lines changed

3 files changed

+64
-0
lines changed

Modules/_remote_debugging/_remote_debugging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ extern int process_frame_chain(
374374
/* Frame cache functions */
375375
extern int frame_cache_init(RemoteUnwinderObject *unwinder);
376376
extern void frame_cache_cleanup(RemoteUnwinderObject *unwinder);
377+
extern int clear_last_profiled_frames(RemoteUnwinderObject *unwinder);
377378
extern void frame_cache_invalidate_stale(RemoteUnwinderObject *unwinder, PyObject *result);
378379
extern int frame_cache_lookup_and_extend(
379380
RemoteUnwinderObject *unwinder,

Modules/_remote_debugging/frames.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,63 @@ frame_cache_cleanup(RemoteUnwinderObject *unwinder)
405405
Py_CLEAR(unwinder->frame_cache);
406406
}
407407

408+
// Clear last_profiled_frame for all threads in the target process.
409+
// This must be called at the start of profiling to avoid stale values
410+
// from previous profilers causing us to stop frame walking early.
411+
int
412+
clear_last_profiled_frames(RemoteUnwinderObject *unwinder)
413+
{
414+
uintptr_t current_interp = unwinder->interpreter_addr;
415+
uintptr_t zero = 0;
416+
417+
while (current_interp != 0) {
418+
// Get first thread in this interpreter
419+
uintptr_t tstate_addr;
420+
if (_Py_RemoteDebug_PagedReadRemoteMemory(
421+
&unwinder->handle,
422+
current_interp + unwinder->debug_offsets.interpreter_state.threads_head,
423+
sizeof(void*),
424+
&tstate_addr) < 0) {
425+
// Non-fatal: just skip clearing
426+
PyErr_Clear();
427+
return 0;
428+
}
429+
430+
// Iterate all threads in this interpreter
431+
while (tstate_addr != 0) {
432+
// Clear last_profiled_frame
433+
uintptr_t lpf_addr = tstate_addr + unwinder->debug_offsets.thread_state.last_profiled_frame;
434+
if (_Py_RemoteDebug_WriteRemoteMemory(&unwinder->handle, lpf_addr,
435+
sizeof(uintptr_t), &zero) < 0) {
436+
// Non-fatal: just continue
437+
PyErr_Clear();
438+
}
439+
440+
// Move to next thread
441+
if (_Py_RemoteDebug_PagedReadRemoteMemory(
442+
&unwinder->handle,
443+
tstate_addr + unwinder->debug_offsets.thread_state.next,
444+
sizeof(void*),
445+
&tstate_addr) < 0) {
446+
PyErr_Clear();
447+
break;
448+
}
449+
}
450+
451+
// Move to next interpreter
452+
if (_Py_RemoteDebug_PagedReadRemoteMemory(
453+
&unwinder->handle,
454+
current_interp + unwinder->debug_offsets.interpreter_state.next,
455+
sizeof(void*),
456+
&current_interp) < 0) {
457+
PyErr_Clear();
458+
break;
459+
}
460+
}
461+
462+
return 0;
463+
}
464+
408465
// Remove cache entries for threads not seen in the result
409466
// result structure: list of InterpreterInfo, where InterpreterInfo[1] is threads list,
410467
// and ThreadInfo[0] is the thread_id

Modules/_remote_debugging/module.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,12 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
386386
return -1;
387387
}
388388

389+
// Clear stale last_profiled_frame values from previous profilers
390+
// This prevents us from stopping frame walking early due to stale values
391+
if (cache_frames) {
392+
clear_last_profiled_frames(self);
393+
}
394+
389395
return 0;
390396
}
391397

0 commit comments

Comments
 (0)