Skip to content

Commit 1752b51

Browse files
authored
gh-115773: Add tests to exercise the _Py_DebugOffsets structure (#115774)
1 parent d53560d commit 1752b51

10 files changed

+818
-36
lines changed

Diff for: Include/internal/pycore_runtime.h

+42-35
Original file line numberDiff line numberDiff line change
@@ -55,74 +55,81 @@ typedef struct _Py_DebugOffsets {
5555
uint64_t version;
5656
// Runtime state offset;
5757
struct _runtime_state {
58-
off_t finalizing;
59-
off_t interpreters_head;
58+
uint64_t finalizing;
59+
uint64_t interpreters_head;
6060
} runtime_state;
6161

6262
// Interpreter state offset;
6363
struct _interpreter_state {
64-
off_t next;
65-
off_t threads_head;
66-
off_t gc;
67-
off_t imports_modules;
68-
off_t sysdict;
69-
off_t builtins;
70-
off_t ceval_gil;
71-
off_t gil_runtime_state_locked;
72-
off_t gil_runtime_state_holder;
64+
uint64_t next;
65+
uint64_t threads_head;
66+
uint64_t gc;
67+
uint64_t imports_modules;
68+
uint64_t sysdict;
69+
uint64_t builtins;
70+
uint64_t ceval_gil;
71+
uint64_t gil_runtime_state_locked;
72+
uint64_t gil_runtime_state_holder;
7373
} interpreter_state;
7474

7575
// Thread state offset;
7676
struct _thread_state{
77-
off_t prev;
78-
off_t next;
79-
off_t interp;
80-
off_t current_frame;
81-
off_t thread_id;
82-
off_t native_thread_id;
77+
uint64_t prev;
78+
uint64_t next;
79+
uint64_t interp;
80+
uint64_t current_frame;
81+
uint64_t thread_id;
82+
uint64_t native_thread_id;
8383
} thread_state;
8484

8585
// InterpreterFrame offset;
8686
struct _interpreter_frame {
87-
off_t previous;
88-
off_t executable;
89-
off_t instr_ptr;
90-
off_t localsplus;
91-
off_t owner;
87+
uint64_t previous;
88+
uint64_t executable;
89+
uint64_t instr_ptr;
90+
uint64_t localsplus;
91+
uint64_t owner;
9292
} interpreter_frame;
9393

9494
// CFrame offset;
9595
struct _cframe {
96-
off_t current_frame;
97-
off_t previous;
96+
uint64_t current_frame;
97+
uint64_t previous;
9898
} cframe;
9999

100100
// Code object offset;
101101
struct _code_object {
102-
off_t filename;
103-
off_t name;
104-
off_t linetable;
105-
off_t firstlineno;
106-
off_t argcount;
107-
off_t localsplusnames;
108-
off_t localspluskinds;
109-
off_t co_code_adaptive;
102+
uint64_t filename;
103+
uint64_t name;
104+
uint64_t linetable;
105+
uint64_t firstlineno;
106+
uint64_t argcount;
107+
uint64_t localsplusnames;
108+
uint64_t localspluskinds;
109+
uint64_t co_code_adaptive;
110110
} code_object;
111111

112112
// PyObject offset;
113113
struct _pyobject {
114-
off_t ob_type;
114+
uint64_t ob_type;
115115
} pyobject;
116116

117117
// PyTypeObject object offset;
118118
struct _type_object {
119-
off_t tp_name;
119+
uint64_t tp_name;
120120
} type_object;
121121

122122
// PyTuple object offset;
123123
struct _tuple_object {
124-
off_t ob_item;
124+
uint64_t ob_item;
125125
} tuple_object;
126+
127+
// Unicode object offset;
128+
struct _unicode_object {
129+
uint64_t state;
130+
uint64_t length;
131+
size_t asciiobject_size;
132+
} unicode_object;
126133
} _Py_DebugOffsets;
127134

128135
/* Full Python runtime state */

Diff for: Include/internal/pycore_runtime_init.h

+5
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ extern PyTypeObject _PyExc_MemoryError;
8383
.tuple_object = { \
8484
.ob_item = offsetof(PyTupleObject, ob_item), \
8585
}, \
86+
.unicode_object = { \
87+
.state = offsetof(PyUnicodeObject, _base._base.state), \
88+
.length = offsetof(PyUnicodeObject, _base._base.length), \
89+
.asciiobject_size = sizeof(PyASCIIObject), \
90+
}, \
8691
}, \
8792
.allocators = { \
8893
.standard = _pymem_allocators_standard_INIT(runtime), \

Diff for: Lib/test/test_external_inspection.py

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import unittest
2+
import os
3+
import textwrap
4+
import importlib
5+
import sys
6+
from test.support import os_helper, SHORT_TIMEOUT
7+
from test.support.script_helper import make_script
8+
9+
import subprocess
10+
11+
PROCESS_VM_READV_SUPPORTED = False
12+
13+
try:
14+
from _testexternalinspection import PROCESS_VM_READV_SUPPORTED
15+
from _testexternalinspection import get_stack_trace
16+
except ImportError:
17+
unittest.skip("Test only runs when _testexternalinspection is available")
18+
19+
def _make_test_script(script_dir, script_basename, source):
20+
to_return = make_script(script_dir, script_basename, source)
21+
importlib.invalidate_caches()
22+
return to_return
23+
24+
class TestGetStackTrace(unittest.TestCase):
25+
26+
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux", "Test only runs on Linux and MacOS")
27+
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support")
28+
def test_remote_stack_trace(self):
29+
# Spawn a process with some realistic Python code
30+
script = textwrap.dedent("""\
31+
import time, sys, os
32+
def bar():
33+
for x in range(100):
34+
if x == 50:
35+
baz()
36+
def baz():
37+
foo()
38+
39+
def foo():
40+
fifo = sys.argv[1]
41+
with open(sys.argv[1], "w") as fifo:
42+
fifo.write("ready")
43+
time.sleep(1000)
44+
45+
bar()
46+
""")
47+
stack_trace = None
48+
with os_helper.temp_dir() as work_dir:
49+
script_dir = os.path.join(work_dir, "script_pkg")
50+
os.mkdir(script_dir)
51+
fifo = f"{work_dir}/the_fifo"
52+
os.mkfifo(fifo)
53+
script_name = _make_test_script(script_dir, 'script', script)
54+
try:
55+
p = subprocess.Popen([sys.executable, script_name, str(fifo)])
56+
with open(fifo, "r") as fifo_file:
57+
response = fifo_file.read()
58+
self.assertEqual(response, "ready")
59+
stack_trace = get_stack_trace(p.pid)
60+
except PermissionError:
61+
self.skipTest("Insufficient permissions to read the stack trace")
62+
finally:
63+
os.remove(fifo)
64+
p.kill()
65+
p.terminate()
66+
p.wait(timeout=SHORT_TIMEOUT)
67+
68+
69+
expected_stack_trace = [
70+
'foo',
71+
'baz',
72+
'bar',
73+
'<module>'
74+
]
75+
self.assertEqual(stack_trace, expected_stack_trace)
76+
77+
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux", "Test only runs on Linux and MacOS")
78+
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support")
79+
def test_self_trace(self):
80+
stack_trace = get_stack_trace(os.getpid())
81+
self.assertEqual(stack_trace[0], "test_self_trace")
82+
83+
if __name__ == "__main__":
84+
unittest.main()

Diff for: Modules/Setup

+1
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ PYTHONPATH=$(COREPYTHONPATH)
285285
#_testcapi _testcapimodule.c
286286
#_testimportmultiple _testimportmultiple.c
287287
#_testmultiphase _testmultiphase.c
288+
#_testexternalinspection _testexternalinspection.c
288289
#_testsinglephase _testsinglephase.c
289290

290291
# ---

Diff for: Modules/Setup.stdlib.in

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@
171171
@MODULE__TESTIMPORTMULTIPLE_TRUE@_testimportmultiple _testimportmultiple.c
172172
@MODULE__TESTMULTIPHASE_TRUE@_testmultiphase _testmultiphase.c
173173
@MODULE__TESTMULTIPHASE_TRUE@_testsinglephase _testsinglephase.c
174+
@MODULE__TESTEXTERNALINSPECTION_TRUE@_testexternalinspection _testexternalinspection.c
174175
@MODULE__CTYPES_TEST_TRUE@_ctypes_test _ctypes/_ctypes_test.c
175176

176177
# Limited API template modules; must be built as shared modules.

0 commit comments

Comments
 (0)