Skip to content

Commit 6cc82bb

Browse files
committed
gh-157605: Distinguish C frames from cleared Python frames
1 parent 8aec82f commit 6cc82bb

2 files changed

Lines changed: 45 additions & 4 deletions

File tree

‎Lib/test/test_external_inspection.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,42 @@ def test_empty_native_thread_stack(self):
17191719
lock.release()
17201720
_testcapi.join_temporary_c_thread()
17211721

1722+
@skip_if_not_supported
1723+
@unittest.skipIf(
1724+
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
1725+
"Test only runs on Linux with process_vm_readv support",
1726+
)
1727+
def test_popping_python_frame_is_not_native(self):
1728+
script = """\
1729+
def leaf(depth):
1730+
if depth:
1731+
leaf(depth - 1)
1732+
1733+
while True:
1734+
leaf(300)
1735+
"""
1736+
with _managed_subprocess([sys.executable, "-c", script]) as process:
1737+
for _ in busy_retry(SHORT_TIMEOUT):
1738+
try:
1739+
unwinder = RemoteUnwinder(
1740+
process.pid, native=True, gc=False, cache_frames=False,
1741+
)
1742+
except RuntimeError:
1743+
continue
1744+
break
1745+
samples = 0
1746+
for _ in range(10_000):
1747+
try:
1748+
frames = unwinder.get_stack_trace()[0].threads[0].frame_info
1749+
except TRANSIENT_ERRORS:
1750+
continue
1751+
names = [frame.funcname for frame in frames]
1752+
if "leaf" not in names:
1753+
continue
1754+
samples += 1
1755+
self.assertNotIn(("leaf", "<native>"), zip(names, names[1:]))
1756+
self.assertGreater(samples, 1000)
1757+
17221758
@skip_if_not_supported
17231759
@unittest.skipIf(
17241760
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,

‎Modules/_remote_debugging/frames.c‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,21 +163,23 @@ find_frame_in_chunks(StackChunkList *chunks, uintptr_t remote_ptr)
163163
* FRAME PARSING FUNCTIONS
164164
* ============================================================================ */
165165

166+
enum { FRAME_PARSE_INTERPRETER = 2 };
167+
166168
int
167169
is_frame_valid(
168170
RemoteUnwinderObject *unwinder,
169171
uintptr_t frame_addr,
170172
uintptr_t code_object_addr
171173
) {
172174
if ((void*)code_object_addr == NULL) {
173-
return 0;
175+
return 0; // Frame being cleared
174176
}
175177

176178
void* frame = (void*)frame_addr;
177179

178180
char owner = GET_MEMBER(char, frame, unwinder->debug_offsets.interpreter_frame.owner);
179181
if (owner == FRAME_OWNED_BY_INTERPRETER) {
180-
return 0; // C frame or sentinel base frame
182+
return FRAME_PARSE_INTERPRETER; // C frame or sentinel base frame
181183
}
182184

183185
if (owner != FRAME_OWNED_BY_GENERATOR && owner != FRAME_OWNED_BY_THREAD) {
@@ -313,6 +315,7 @@ process_frame_chain(
313315
ctx->last_frame_visited = 0;
314316

315317
while ((void*)frame_addr != NULL) {
318+
int parse_result = 0;
316319
PyObject *frame = NULL;
317320
uintptr_t next_frame_addr = 0;
318321
uintptr_t stackpointer = 0;
@@ -326,14 +329,15 @@ process_frame_chain(
326329
assert(frame_count <= MAX_FRAMES);
327330

328331
if (ctx->chunks && ctx->chunks->count > 0) {
329-
if (parse_frame_from_chunks(unwinder, &frame, frame_addr, &next_frame_addr, &stackpointer, ctx->chunks) == 0) {
332+
parse_result = parse_frame_from_chunks(
333+
unwinder, &frame, frame_addr, &next_frame_addr, &stackpointer, ctx->chunks);
334+
if (parse_result == 0) {
330335
goto parsed_frame;
331336
}
332337
PyErr_Clear();
333338
}
334339
{
335340
uintptr_t address_of_code_object = 0;
336-
int parse_result;
337341
if (ctx->prefetch.frame && ctx->prefetch.frame_addr == frame_addr) {
338342
parse_result = parse_frame_buffer(
339343
unwinder, &frame, ctx->prefetch.frame,
@@ -368,6 +372,7 @@ process_frame_chain(
368372
// returning into.
369373
else if (unwinder->native &&
370374
frame == NULL &&
375+
parse_result == FRAME_PARSE_INTERPRETER &&
371376
next_frame_addr &&
372377
PyList_GET_SIZE(ctx->frame_info) > 0 &&
373378
!(unwinder->gc && next_frame_addr == ctx->gc_frame))

0 commit comments

Comments
 (0)