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

Commit f42a1a7

Browse files
committed
Add remote introspection support
1 parent 3d351d7 commit f42a1a7

File tree

6 files changed

+168
-9
lines changed

6 files changed

+168
-9
lines changed

Include/internal/pycore_debug_offsets.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ typedef struct _Py_DebugOffsets {
124124
uint64_t tlbc_index;
125125
} interpreter_frame;
126126

127+
struct _interpreter_frame_metadata {
128+
uint64_t executable_kinds;
129+
} interpreter_frame_metadata;
130+
127131
// Code object offset;
128132
struct _code_object {
129133
uint64_t size;
@@ -139,6 +143,10 @@ typedef struct _Py_DebugOffsets {
139143
uint64_t co_tlbc;
140144
} code_object;
141145

146+
struct _jit_executable {
147+
uint64_t code;
148+
} jit_executable;
149+
142150
// PyObject offset;
143151
struct _pyobject {
144152
uint64_t size;
@@ -293,6 +301,9 @@ typedef struct _Py_DebugOffsets {
293301
.stackpointer = offsetof(_PyInterpreterFrame, stackpointer), \
294302
.tlbc_index = _Py_Debug_interpreter_frame_tlbc_index, \
295303
}, \
304+
.interpreter_frame_metadata = { \
305+
.executable_kinds = (uintptr_t)&PyUnstable_ExecutableKinds, \
306+
}, \
296307
.code_object = { \
297308
.size = sizeof(PyCodeObject), \
298309
.filename = offsetof(PyCodeObject, co_filename), \
@@ -306,6 +317,9 @@ typedef struct _Py_DebugOffsets {
306317
.co_code_adaptive = offsetof(PyCodeObject, co_code_adaptive), \
307318
.co_tlbc = _Py_Debug_code_object_co_tlbc, \
308319
}, \
320+
.jit_executable = { \
321+
.code = offsetof(PyUnstable_PyJitExecutable, je_code), \
322+
}, \
309323
.pyobject = { \
310324
.size = sizeof(PyObject), \
311325
.ob_type = offsetof(PyObject, ob_type), \

Lib/test/test_external_inspection.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3060,5 +3060,104 @@ def test_get_stats_disabled_raises(self):
30603060
client_socket.sendall(b"done")
30613061

30623062

3063+
class TestNonCodeExecutable(RemoteInspectionTestBase):
3064+
@skip_if_not_supported
3065+
@unittest.skipIf(
3066+
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
3067+
"Test only runs on Linux with process_vm_readv support",
3068+
)
3069+
def test_remote_stack_trace(self):
3070+
port = find_unused_port()
3071+
script = textwrap.dedent(
3072+
f"""\
3073+
import time, sys, socket, threading
3074+
import _testinternalcapi
3075+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
3076+
sock.connect(('localhost', {port}))
3077+
3078+
def bar():
3079+
for x in range(100):
3080+
if x == 50:
3081+
_testinternalcapi.call_with_jit_frame(baz, foo, ())
3082+
3083+
def baz():
3084+
pass
3085+
3086+
def foo():
3087+
sock.sendall(b"ready:thread\\n"); time.sleep(10_000)
3088+
3089+
t = threading.Thread(target=bar)
3090+
t.start()
3091+
sock.sendall(b"ready:main\\n"); t.join()
3092+
"""
3093+
)
3094+
3095+
with os_helper.temp_dir() as work_dir:
3096+
script_dir = os.path.join(work_dir, "script_pkg")
3097+
os.mkdir(script_dir)
3098+
3099+
server_socket = _create_server_socket(port)
3100+
script_name = _make_test_script(script_dir, "script", script)
3101+
client_socket = None
3102+
3103+
try:
3104+
with _managed_subprocess([sys.executable, script_name]) as p:
3105+
client_socket, _ = server_socket.accept()
3106+
server_socket.close()
3107+
server_socket = None
3108+
3109+
_wait_for_signal(
3110+
client_socket, [b"ready:main", b"ready:thread"]
3111+
)
3112+
3113+
try:
3114+
stack_trace = get_stack_trace(p.pid)
3115+
except PermissionError:
3116+
self.skipTest(
3117+
"Insufficient permissions to read the stack trace"
3118+
)
3119+
3120+
thread_expected_stack_trace = [
3121+
FrameInfo([script_name, 15, "foo"]),
3122+
# external frame line number is function start
3123+
FrameInfo([script_name, 11, "baz"]),
3124+
FrameInfo([script_name, 9, "bar"]),
3125+
FrameInfo([threading.__file__, ANY, "Thread.run"]),
3126+
FrameInfo(
3127+
[
3128+
threading.__file__,
3129+
ANY,
3130+
"Thread._bootstrap_inner",
3131+
]
3132+
),
3133+
FrameInfo(
3134+
[threading.__file__, ANY, "Thread._bootstrap"]
3135+
),
3136+
]
3137+
3138+
# Find expected thread stack
3139+
found_thread = self._find_thread_with_frame(
3140+
stack_trace,
3141+
lambda f: f.funcname == "foo" and f.lineno == 15,
3142+
)
3143+
self.assertIsNotNone(
3144+
found_thread, "Expected thread stack trace not found"
3145+
)
3146+
self.assertEqual(
3147+
found_thread.frame_info, thread_expected_stack_trace
3148+
)
3149+
3150+
# Check main thread
3151+
main_frame = FrameInfo([script_name, 19, "<module>"])
3152+
found_main = self._find_frame_in_trace(
3153+
stack_trace, lambda f: f == main_frame
3154+
)
3155+
self.assertIsNotNone(
3156+
found_main, "Main thread stack trace not found"
3157+
)
3158+
finally:
3159+
_cleanup_sockets(client_socket, server_socket)
3160+
3161+
30633162
if __name__ == "__main__":
30643163
unittest.main()

Modules/_remote_debugging/_remote_debugging.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ typedef struct {
234234
RemoteDebuggingState *cached_state;
235235
FrameCacheEntry *frame_cache; // preallocated array of FRAME_CACHE_MAX_THREADS entries
236236
UnwinderStats stats; // statistics for performance analysis
237+
uintptr_t frame_executable_types[PyUnstable_EXECUTABLE_KINDS];
237238
#ifdef Py_GIL_DISABLED
238239
uint32_t tlbc_generation;
239240
_Py_hashtable_t *tlbc_cache;
@@ -327,7 +328,7 @@ extern long read_py_long(RemoteUnwinderObject *unwinder, uintptr_t address);
327328
* CODE OBJECT FUNCTION DECLARATIONS
328329
* ============================================================================ */
329330

330-
extern int parse_code_object(
331+
extern int parse_executable_object(
331332
RemoteUnwinderObject *unwinder,
332333
PyObject **result,
333334
uintptr_t address,
@@ -459,6 +460,8 @@ extern int populate_initial_state_data(
459460
uintptr_t *tstate
460461
);
461462

463+
extern int populate_frame_executable_types(RemoteUnwinderObject *unwinder);
464+
462465
extern int find_running_frame(
463466
RemoteUnwinderObject *unwinder,
464467
uintptr_t address_of_thread,

Modules/_remote_debugging/code_objects.c

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,12 +234,12 @@ make_frame_info(RemoteUnwinderObject *unwinder, PyObject *file, PyObject *line,
234234
}
235235

236236
int
237-
parse_code_object(RemoteUnwinderObject *unwinder,
238-
PyObject **result,
239-
uintptr_t address,
240-
uintptr_t instruction_pointer,
241-
uintptr_t *previous_frame,
242-
int32_t tlbc_index)
237+
parse_executable_object(RemoteUnwinderObject *unwinder,
238+
PyObject **result,
239+
uintptr_t address,
240+
uintptr_t instruction_pointer,
241+
uintptr_t *previous_frame,
242+
int32_t tlbc_index)
243243
{
244244
void *key = (void *)address;
245245
CachedCodeMetadata *meta = NULL;
@@ -273,6 +273,23 @@ parse_code_object(RemoteUnwinderObject *unwinder,
273273
goto error;
274274
}
275275

276+
// Check for non code type executables
277+
uintptr_t code_type = GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.pyobject.ob_type);
278+
if (code_type != unwinder->frame_executable_types[PyUnstable_EXECUTABLE_KIND_PY_FUNCTION]) {
279+
if (code_type != unwinder->frame_executable_types[PyUnstable_EXECUTABLE_KIND_JIT]) {
280+
// Unsupported executable type, report the frame as being invalid
281+
return 0;
282+
}
283+
real_address = GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.jit_executable.code);
284+
if (_Py_RemoteDebug_PagedReadRemoteMemory(
285+
&unwinder->handle, real_address, SIZEOF_CODE_OBJ, code_object) < 0)
286+
{
287+
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read code object");
288+
goto error;
289+
}
290+
}
291+
292+
276293
func = read_py_str(unwinder,
277294
GET_MEMBER(uintptr_t, code_object, unwinder->debug_offsets.code_object.qualname), 1024);
278295
if (!func) {

Modules/_remote_debugging/frames.c

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@
1111
* STACK CHUNK MANAGEMENT FUNCTIONS
1212
* ============================================================================ */
1313

14+
int
15+
populate_frame_executable_types(RemoteUnwinderObject *unwinder)
16+
{
17+
uintptr_t executable_kinds_addr =
18+
unwinder->debug_offsets.interpreter_frame_metadata.executable_kinds;
19+
20+
int bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
21+
&unwinder->handle,
22+
executable_kinds_addr,
23+
sizeof(uintptr_t) * PyUnstable_EXECUTABLE_KINDS,
24+
(void *)unwinder->frame_executable_types);
25+
26+
if (bytes_read < 0) {
27+
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read frame executable types");
28+
return -1;
29+
}
30+
31+
return 0;
32+
}
33+
1434
void
1535
cleanup_stack_chunks(StackChunkList *chunks)
1636
{
@@ -209,7 +229,7 @@ parse_frame_object(
209229
#endif
210230

211231
*address_of_code_object = code_object;
212-
return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
232+
return parse_executable_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
213233
}
214234

215235
int
@@ -246,7 +266,7 @@ parse_frame_from_chunks(
246266
}
247267
#endif
248268

249-
return parse_code_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
269+
return parse_executable_object(unwinder, result, code_object, instruction_pointer, previous_frame, tlbc_index);
250270
}
251271

252272
/* ============================================================================

Modules/_remote_debugging/module.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,12 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
348348
return -1;
349349
}
350350

351+
if (populate_frame_executable_types(self) < 0)
352+
{
353+
set_exception_cause(self, PyExc_RuntimeError, "Failed to populate initial state data");
354+
return -1;
355+
}
356+
351357
self->code_object_cache = _Py_hashtable_new_full(
352358
_Py_hashtable_hash_ptr,
353359
_Py_hashtable_compare_direct,

0 commit comments

Comments
 (0)