Skip to content

Commit

Permalink
pythongh-115773: Add tests to exercise the _Py_DebugOffsets structure (
Browse files Browse the repository at this point in the history
  • Loading branch information
pablogsal authored Feb 28, 2024
1 parent d53560d commit 1752b51
Show file tree
Hide file tree
Showing 10 changed files with 818 additions and 36 deletions.
77 changes: 42 additions & 35 deletions Include/internal/pycore_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,74 +55,81 @@ typedef struct _Py_DebugOffsets {
uint64_t version;
// Runtime state offset;
struct _runtime_state {
off_t finalizing;
off_t interpreters_head;
uint64_t finalizing;
uint64_t interpreters_head;
} runtime_state;

// Interpreter state offset;
struct _interpreter_state {
off_t next;
off_t threads_head;
off_t gc;
off_t imports_modules;
off_t sysdict;
off_t builtins;
off_t ceval_gil;
off_t gil_runtime_state_locked;
off_t gil_runtime_state_holder;
uint64_t next;
uint64_t threads_head;
uint64_t gc;
uint64_t imports_modules;
uint64_t sysdict;
uint64_t builtins;
uint64_t ceval_gil;
uint64_t gil_runtime_state_locked;
uint64_t gil_runtime_state_holder;
} interpreter_state;

// Thread state offset;
struct _thread_state{
off_t prev;
off_t next;
off_t interp;
off_t current_frame;
off_t thread_id;
off_t native_thread_id;
uint64_t prev;
uint64_t next;
uint64_t interp;
uint64_t current_frame;
uint64_t thread_id;
uint64_t native_thread_id;
} thread_state;

// InterpreterFrame offset;
struct _interpreter_frame {
off_t previous;
off_t executable;
off_t instr_ptr;
off_t localsplus;
off_t owner;
uint64_t previous;
uint64_t executable;
uint64_t instr_ptr;
uint64_t localsplus;
uint64_t owner;
} interpreter_frame;

// CFrame offset;
struct _cframe {
off_t current_frame;
off_t previous;
uint64_t current_frame;
uint64_t previous;
} cframe;

// Code object offset;
struct _code_object {
off_t filename;
off_t name;
off_t linetable;
off_t firstlineno;
off_t argcount;
off_t localsplusnames;
off_t localspluskinds;
off_t co_code_adaptive;
uint64_t filename;
uint64_t name;
uint64_t linetable;
uint64_t firstlineno;
uint64_t argcount;
uint64_t localsplusnames;
uint64_t localspluskinds;
uint64_t co_code_adaptive;
} code_object;

// PyObject offset;
struct _pyobject {
off_t ob_type;
uint64_t ob_type;
} pyobject;

// PyTypeObject object offset;
struct _type_object {
off_t tp_name;
uint64_t tp_name;
} type_object;

// PyTuple object offset;
struct _tuple_object {
off_t ob_item;
uint64_t ob_item;
} tuple_object;

// Unicode object offset;
struct _unicode_object {
uint64_t state;
uint64_t length;
size_t asciiobject_size;
} unicode_object;
} _Py_DebugOffsets;

/* Full Python runtime state */
Expand Down
5 changes: 5 additions & 0 deletions Include/internal/pycore_runtime_init.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ extern PyTypeObject _PyExc_MemoryError;
.tuple_object = { \
.ob_item = offsetof(PyTupleObject, ob_item), \
}, \
.unicode_object = { \
.state = offsetof(PyUnicodeObject, _base._base.state), \
.length = offsetof(PyUnicodeObject, _base._base.length), \
.asciiobject_size = sizeof(PyASCIIObject), \
}, \
}, \
.allocators = { \
.standard = _pymem_allocators_standard_INIT(runtime), \
Expand Down
84 changes: 84 additions & 0 deletions Lib/test/test_external_inspection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import unittest
import os
import textwrap
import importlib
import sys
from test.support import os_helper, SHORT_TIMEOUT
from test.support.script_helper import make_script

import subprocess

PROCESS_VM_READV_SUPPORTED = False

try:
from _testexternalinspection import PROCESS_VM_READV_SUPPORTED
from _testexternalinspection import get_stack_trace
except ImportError:
unittest.skip("Test only runs when _testexternalinspection is available")

def _make_test_script(script_dir, script_basename, source):
to_return = make_script(script_dir, script_basename, source)
importlib.invalidate_caches()
return to_return

class TestGetStackTrace(unittest.TestCase):

@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux", "Test only runs on Linux and MacOS")
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support")
def test_remote_stack_trace(self):
# Spawn a process with some realistic Python code
script = textwrap.dedent("""\
import time, sys, os
def bar():
for x in range(100):
if x == 50:
baz()
def baz():
foo()
def foo():
fifo = sys.argv[1]
with open(sys.argv[1], "w") as fifo:
fifo.write("ready")
time.sleep(1000)
bar()
""")
stack_trace = None
with os_helper.temp_dir() as work_dir:
script_dir = os.path.join(work_dir, "script_pkg")
os.mkdir(script_dir)
fifo = f"{work_dir}/the_fifo"
os.mkfifo(fifo)
script_name = _make_test_script(script_dir, 'script', script)
try:
p = subprocess.Popen([sys.executable, script_name, str(fifo)])
with open(fifo, "r") as fifo_file:
response = fifo_file.read()
self.assertEqual(response, "ready")
stack_trace = get_stack_trace(p.pid)
except PermissionError:
self.skipTest("Insufficient permissions to read the stack trace")
finally:
os.remove(fifo)
p.kill()
p.terminate()
p.wait(timeout=SHORT_TIMEOUT)


expected_stack_trace = [
'foo',
'baz',
'bar',
'<module>'
]
self.assertEqual(stack_trace, expected_stack_trace)

@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux", "Test only runs on Linux and MacOS")
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support")
def test_self_trace(self):
stack_trace = get_stack_trace(os.getpid())
self.assertEqual(stack_trace[0], "test_self_trace")

if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions Modules/Setup
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ PYTHONPATH=$(COREPYTHONPATH)
#_testcapi _testcapimodule.c
#_testimportmultiple _testimportmultiple.c
#_testmultiphase _testmultiphase.c
#_testexternalinspection _testexternalinspection.c
#_testsinglephase _testsinglephase.c

# ---
Expand Down
1 change: 1 addition & 0 deletions Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
@MODULE__TESTIMPORTMULTIPLE_TRUE@_testimportmultiple _testimportmultiple.c
@MODULE__TESTMULTIPHASE_TRUE@_testmultiphase _testmultiphase.c
@MODULE__TESTMULTIPHASE_TRUE@_testsinglephase _testsinglephase.c
@MODULE__TESTEXTERNALINSPECTION_TRUE@_testexternalinspection _testexternalinspection.c
@MODULE__CTYPES_TEST_TRUE@_ctypes_test _ctypes/_ctypes_test.c

# Limited API template modules; must be built as shared modules.
Expand Down
Loading

0 comments on commit 1752b51

Please sign in to comment.