mirror of https://github.com/python/cpython
gh-115773: Add tests to exercise the _Py_DebugOffsets structure (#115774)
This commit is contained in:
parent
d53560deb2
commit
1752b51012
|
@ -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 */
|
||||
|
|
|
@ -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), \
|
||||
|
|
|
@ -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()
|
|
@ -285,6 +285,7 @@ PYTHONPATH=$(COREPYTHONPATH)
|
|||
#_testcapi _testcapimodule.c
|
||||
#_testimportmultiple _testimportmultiple.c
|
||||
#_testmultiphase _testmultiphase.c
|
||||
#_testexternalinspection _testexternalinspection.c
|
||||
#_testsinglephase _testsinglephase.c
|
||||
|
||||
# ---
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -0,0 +1,629 @@
|
|||
#define _GNU_SOURCE
|
||||
|
||||
#ifdef __linux__
|
||||
# include <elf.h>
|
||||
# include <sys/uio.h>
|
||||
# if INTPTR_MAX == INT64_MAX
|
||||
# define Elf_Ehdr Elf64_Ehdr
|
||||
# define Elf_Shdr Elf64_Shdr
|
||||
# define Elf_Phdr Elf64_Phdr
|
||||
# else
|
||||
# define Elf_Ehdr Elf32_Ehdr
|
||||
# define Elf_Shdr Elf32_Shdr
|
||||
# define Elf_Phdr Elf32_Phdr
|
||||
# endif
|
||||
# include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <libproc.h>
|
||||
# include <mach-o/fat.h>
|
||||
# include <mach-o/loader.h>
|
||||
# include <mach-o/nlist.h>
|
||||
# include <mach/mach.h>
|
||||
# include <mach/mach_vm.h>
|
||||
# include <mach/machine.h>
|
||||
# include <sys/mman.h>
|
||||
# include <sys/proc.h>
|
||||
# include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef Py_BUILD_CORE_BUILTIN
|
||||
# define Py_BUILD_CORE_MODULE 1
|
||||
#endif
|
||||
#include "Python.h"
|
||||
#include <internal/pycore_runtime.h>
|
||||
|
||||
#ifndef HAVE_PROCESS_VM_READV
|
||||
# define HAVE_PROCESS_VM_READV 0
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
static void*
|
||||
analyze_macho64(mach_port_t proc_ref, void* base, void* map)
|
||||
{
|
||||
struct mach_header_64* hdr = (struct mach_header_64*)map;
|
||||
int ncmds = hdr->ncmds;
|
||||
|
||||
int cmd_cnt = 0;
|
||||
struct segment_command_64* cmd = map + sizeof(struct mach_header_64);
|
||||
|
||||
mach_vm_size_t size = 0;
|
||||
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
|
||||
mach_vm_address_t address = (mach_vm_address_t)base;
|
||||
vm_region_basic_info_data_64_t region_info;
|
||||
mach_port_t object_name;
|
||||
|
||||
for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
|
||||
if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__DATA") == 0) {
|
||||
while (cmd->filesize != size) {
|
||||
address += size;
|
||||
if (mach_vm_region(
|
||||
proc_ref,
|
||||
&address,
|
||||
&size,
|
||||
VM_REGION_BASIC_INFO_64,
|
||||
(vm_region_info_t)®ion_info, // cppcheck-suppress [uninitvar]
|
||||
&count,
|
||||
&object_name)
|
||||
!= KERN_SUCCESS)
|
||||
{
|
||||
PyErr_SetString(PyExc_RuntimeError, "Cannot get any more VM maps.\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
base = (void*)address - cmd->vmaddr;
|
||||
|
||||
int nsects = cmd->nsects;
|
||||
struct section_64* sec =
|
||||
(struct section_64*)((void*)cmd + sizeof(struct segment_command_64));
|
||||
for (int j = 0; j < nsects; j++) {
|
||||
if (strcmp(sec[j].sectname, "PyRuntime") == 0) {
|
||||
return base + sec[j].addr;
|
||||
}
|
||||
}
|
||||
cmd_cnt++;
|
||||
}
|
||||
|
||||
cmd = (struct segment_command_64*)((void*)cmd + cmd->cmdsize);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void*
|
||||
analyze_macho(char* path, void* base, mach_vm_size_t size, mach_port_t proc_ref)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Cannot open binary %s\n", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct stat fs;
|
||||
if (fstat(fd, &fs) == -1) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Cannot get size of binary %s\n", path);
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (map == MAP_FAILED) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Cannot map binary %s\n", path);
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* result = NULL;
|
||||
|
||||
struct mach_header_64* hdr = (struct mach_header_64*)map;
|
||||
switch (hdr->magic) {
|
||||
case MH_MAGIC:
|
||||
case MH_CIGAM:
|
||||
case FAT_MAGIC:
|
||||
case FAT_CIGAM:
|
||||
PyErr_SetString(PyExc_RuntimeError, "32-bit Mach-O binaries are not supported");
|
||||
break;
|
||||
case MH_MAGIC_64:
|
||||
case MH_CIGAM_64:
|
||||
result = analyze_macho64(proc_ref, base, map);
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic");
|
||||
break;
|
||||
}
|
||||
|
||||
munmap(map, fs.st_size);
|
||||
if (close(fd) != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static mach_port_t
|
||||
pid_to_task(pid_t pid)
|
||||
{
|
||||
mach_port_t task;
|
||||
kern_return_t result;
|
||||
|
||||
result = task_for_pid(mach_task_self(), pid, &task);
|
||||
if (result != KERN_SUCCESS) {
|
||||
PyErr_Format(PyExc_PermissionError, "Cannot get task for PID %d", pid);
|
||||
return 0;
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
static void*
|
||||
get_py_runtime_macos(pid_t pid)
|
||||
{
|
||||
mach_vm_address_t address = 0;
|
||||
mach_vm_size_t size = 0;
|
||||
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
|
||||
vm_region_basic_info_data_64_t region_info;
|
||||
mach_port_t object_name;
|
||||
|
||||
mach_port_t proc_ref = pid_to_task(pid);
|
||||
if (proc_ref == 0) {
|
||||
PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int match_found = 0;
|
||||
char map_filename[MAXPATHLEN + 1];
|
||||
void* result_address = NULL;
|
||||
while (mach_vm_region(
|
||||
proc_ref,
|
||||
&address,
|
||||
&size,
|
||||
VM_REGION_BASIC_INFO_64,
|
||||
(vm_region_info_t)®ion_info,
|
||||
&count,
|
||||
&object_name)
|
||||
== KERN_SUCCESS)
|
||||
{
|
||||
int path_len = proc_regionfilename(pid, address, map_filename, MAXPATHLEN);
|
||||
if (path_len == 0) {
|
||||
address += size;
|
||||
continue;
|
||||
}
|
||||
|
||||
char* filename = strrchr(map_filename, '/');
|
||||
if (filename != NULL) {
|
||||
filename++; // Move past the '/'
|
||||
} else {
|
||||
filename = map_filename; // No path, use the whole string
|
||||
}
|
||||
|
||||
// Check if the filename starts with "python" or "libpython"
|
||||
if (!match_found && strncmp(filename, "python", 6) == 0) {
|
||||
match_found = 1;
|
||||
result_address = analyze_macho(map_filename, (void*)address, size, proc_ref);
|
||||
}
|
||||
if (strncmp(filename, "libpython", 9) == 0) {
|
||||
match_found = 1;
|
||||
result_address = analyze_macho(map_filename, (void*)address, size, proc_ref);
|
||||
break;
|
||||
}
|
||||
|
||||
address += size;
|
||||
}
|
||||
return result_address;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
void*
|
||||
find_python_map_start_address(pid_t pid, char* result_filename)
|
||||
{
|
||||
char maps_file_path[64];
|
||||
sprintf(maps_file_path, "/proc/%d/maps", pid);
|
||||
|
||||
FILE* maps_file = fopen(maps_file_path, "r");
|
||||
if (maps_file == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int match_found = 0;
|
||||
|
||||
char line[256];
|
||||
char map_filename[PATH_MAX];
|
||||
void* result_address = 0;
|
||||
while (fgets(line, sizeof(line), maps_file) != NULL) {
|
||||
unsigned long start_address = 0;
|
||||
sscanf(line, "%lx-%*x %*s %*s %*s %*s %s", &start_address, map_filename);
|
||||
char* filename = strrchr(map_filename, '/');
|
||||
if (filename != NULL) {
|
||||
filename++; // Move past the '/'
|
||||
} else {
|
||||
filename = map_filename; // No path, use the whole string
|
||||
}
|
||||
|
||||
// Check if the filename starts with "python" or "libpython"
|
||||
if (!match_found && strncmp(filename, "python", 6) == 0) {
|
||||
match_found = 1;
|
||||
result_address = (void*)start_address;
|
||||
strcpy(result_filename, map_filename);
|
||||
}
|
||||
if (strncmp(filename, "libpython", 9) == 0) {
|
||||
match_found = 1;
|
||||
result_address = (void*)start_address;
|
||||
strcpy(result_filename, map_filename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(maps_file);
|
||||
|
||||
if (!match_found) {
|
||||
map_filename[0] = '\0';
|
||||
}
|
||||
|
||||
return result_address;
|
||||
}
|
||||
|
||||
void*
|
||||
get_py_runtime_linux(pid_t pid)
|
||||
{
|
||||
char elf_file[256];
|
||||
void* start_address = (void*)find_python_map_start_address(pid, elf_file);
|
||||
|
||||
if (start_address == 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "No memory map associated with python or libpython found");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* result = NULL;
|
||||
void* file_memory = NULL;
|
||||
|
||||
int fd = open(elf_file, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
struct stat file_stats;
|
||||
if (fstat(fd, &file_stats) != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
file_memory = mmap(NULL, file_stats.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (file_memory == MAP_FAILED) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
Elf_Ehdr* elf_header = (Elf_Ehdr*)file_memory;
|
||||
|
||||
Elf_Shdr* section_header_table = (Elf_Shdr*)(file_memory + elf_header->e_shoff);
|
||||
|
||||
Elf_Shdr* shstrtab_section = §ion_header_table[elf_header->e_shstrndx];
|
||||
char* shstrtab = (char*)(file_memory + shstrtab_section->sh_offset);
|
||||
|
||||
Elf_Shdr* py_runtime_section = NULL;
|
||||
for (int i = 0; i < elf_header->e_shnum; i++) {
|
||||
if (strcmp(".PyRuntime", shstrtab + section_header_table[i].sh_name) == 0) {
|
||||
py_runtime_section = §ion_header_table[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Elf_Phdr* program_header_table = (Elf_Phdr*)(file_memory + elf_header->e_phoff);
|
||||
// Find the first PT_LOAD segment
|
||||
Elf_Phdr* first_load_segment = NULL;
|
||||
for (int i = 0; i < elf_header->e_phnum; i++) {
|
||||
if (program_header_table[i].p_type == PT_LOAD) {
|
||||
first_load_segment = &program_header_table[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (py_runtime_section != NULL && first_load_segment != NULL) {
|
||||
uintptr_t elf_load_addr = first_load_segment->p_vaddr
|
||||
- (first_load_segment->p_vaddr % first_load_segment->p_align);
|
||||
result = start_address + py_runtime_section->sh_addr - elf_load_addr;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (close(fd) != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
if (file_memory != NULL) {
|
||||
munmap(file_memory, file_stats.st_size);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
ssize_t
|
||||
read_memory(pid_t pid, void* remote_address, size_t len, void* dst)
|
||||
{
|
||||
ssize_t total_bytes_read = 0;
|
||||
#ifdef __linux__
|
||||
struct iovec local[1];
|
||||
struct iovec remote[1];
|
||||
ssize_t result = 0;
|
||||
ssize_t read = 0;
|
||||
|
||||
do {
|
||||
local[0].iov_base = dst + result;
|
||||
local[0].iov_len = len - result;
|
||||
remote[0].iov_base = (void*)(remote_address + result);
|
||||
remote[0].iov_len = len - result;
|
||||
|
||||
read = process_vm_readv(pid, local, 1, remote, 1, 0);
|
||||
if (read < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return -1;
|
||||
}
|
||||
|
||||
result += read;
|
||||
} while ((size_t)read != local[0].iov_len);
|
||||
total_bytes_read = result;
|
||||
#elif defined(__APPLE__)
|
||||
ssize_t result = -1;
|
||||
kern_return_t kr = mach_vm_read_overwrite(
|
||||
pid_to_task(pid),
|
||||
(mach_vm_address_t)remote_address,
|
||||
len,
|
||||
(mach_vm_address_t)dst,
|
||||
(mach_vm_size_t*)&result);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
switch (kr) {
|
||||
case KERN_PROTECTION_FAILURE:
|
||||
PyErr_SetString(PyExc_PermissionError, "Not enough permissions to read memory");
|
||||
break;
|
||||
case KERN_INVALID_ARGUMENT:
|
||||
PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_read_overwrite");
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unknown error reading memory");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
total_bytes_read = len;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
return total_bytes_read;
|
||||
}
|
||||
|
||||
int
|
||||
read_string(pid_t pid, _Py_DebugOffsets* debug_offsets, void* address, char* buffer, Py_ssize_t size)
|
||||
{
|
||||
Py_ssize_t len;
|
||||
ssize_t bytes_read =
|
||||
read_memory(pid, address + debug_offsets->unicode_object.length, sizeof(Py_ssize_t), &len);
|
||||
if (bytes_read == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (len >= size) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Buffer too small");
|
||||
return -1;
|
||||
}
|
||||
size_t offset = debug_offsets->unicode_object.asciiobject_size;
|
||||
bytes_read = read_memory(pid, address + offset, len, buffer);
|
||||
if (bytes_read == -1) {
|
||||
return -1;
|
||||
}
|
||||
buffer[len] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
void*
|
||||
get_py_runtime(pid_t pid)
|
||||
{
|
||||
#if defined(__linux__)
|
||||
return get_py_runtime_linux(pid);
|
||||
#elif defined(__APPLE__)
|
||||
return get_py_runtime_macos(pid);
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
parse_code_object(
|
||||
int pid,
|
||||
PyObject* result,
|
||||
struct _Py_DebugOffsets* offsets,
|
||||
void* address,
|
||||
void** previous_frame)
|
||||
{
|
||||
void* address_of_function_name;
|
||||
read_memory(
|
||||
pid,
|
||||
(void*)(address + offsets->code_object.name),
|
||||
sizeof(void*),
|
||||
&address_of_function_name);
|
||||
|
||||
if (address_of_function_name == NULL) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "No function name found");
|
||||
return -1;
|
||||
}
|
||||
|
||||
char function_name[256];
|
||||
if (read_string(pid, offsets, address_of_function_name, function_name, sizeof(function_name)) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyObject* py_function_name = PyUnicode_FromString(function_name);
|
||||
if (py_function_name == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (PyList_Append(result, py_function_name) == -1) {
|
||||
Py_DECREF(py_function_name);
|
||||
return -1;
|
||||
}
|
||||
Py_DECREF(py_function_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_frame_object(
|
||||
int pid,
|
||||
PyObject* result,
|
||||
struct _Py_DebugOffsets* offsets,
|
||||
void* address,
|
||||
void** previous_frame)
|
||||
{
|
||||
ssize_t bytes_read = read_memory(
|
||||
pid,
|
||||
(void*)(address + offsets->interpreter_frame.previous),
|
||||
sizeof(void*),
|
||||
previous_frame);
|
||||
if (bytes_read == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
char owner;
|
||||
bytes_read =
|
||||
read_memory(pid, (void*)(address + offsets->interpreter_frame.owner), sizeof(char), &owner);
|
||||
if (bytes_read < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (owner == FRAME_OWNED_BY_CSTACK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* address_of_code_object;
|
||||
bytes_read = read_memory(
|
||||
pid,
|
||||
(void*)(address + offsets->interpreter_frame.executable),
|
||||
sizeof(void*),
|
||||
&address_of_code_object);
|
||||
if (bytes_read == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (address_of_code_object == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return parse_code_object(pid, result, offsets, address_of_code_object, previous_frame);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
get_stack_trace(PyObject* self, PyObject* args)
|
||||
{
|
||||
#if (!defined(__linux__) && !defined(__APPLE__)) || (defined(__linux__) && !HAVE_PROCESS_VM_READV)
|
||||
PyErr_SetString(PyExc_RuntimeError, "get_stack_trace is not supported on this platform");
|
||||
return NULL;
|
||||
#endif
|
||||
int pid;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "i", &pid)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* runtime_start_address = get_py_runtime(pid);
|
||||
if (runtime_start_address == NULL) {
|
||||
if (!PyErr_Occurred()) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to get .PyRuntime address");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
size_t size = sizeof(struct _Py_DebugOffsets);
|
||||
struct _Py_DebugOffsets local_debug_offsets;
|
||||
|
||||
ssize_t bytes_read = read_memory(pid, runtime_start_address, size, &local_debug_offsets);
|
||||
if (bytes_read == -1) {
|
||||
return NULL;
|
||||
}
|
||||
off_t thread_state_list_head = local_debug_offsets.runtime_state.interpreters_head;
|
||||
|
||||
void* address_of_interpreter_state;
|
||||
bytes_read = read_memory(
|
||||
pid,
|
||||
(void*)(runtime_start_address + thread_state_list_head),
|
||||
sizeof(void*),
|
||||
&address_of_interpreter_state);
|
||||
if (bytes_read == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (address_of_interpreter_state == NULL) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "No interpreter state found");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* address_of_thread;
|
||||
bytes_read = read_memory(
|
||||
pid,
|
||||
(void*)(address_of_interpreter_state + local_debug_offsets.interpreter_state.threads_head),
|
||||
sizeof(void*),
|
||||
&address_of_thread);
|
||||
if (bytes_read == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* result = PyList_New(0);
|
||||
if (result == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// No Python frames are available for us (can happen at tear-down).
|
||||
if (address_of_thread != NULL) {
|
||||
void* address_of_current_frame;
|
||||
(void)read_memory(
|
||||
pid,
|
||||
(void*)(address_of_thread + local_debug_offsets.thread_state.current_frame),
|
||||
sizeof(void*),
|
||||
&address_of_current_frame);
|
||||
while (address_of_current_frame != NULL) {
|
||||
if (parse_frame_object(
|
||||
pid,
|
||||
result,
|
||||
&local_debug_offsets,
|
||||
address_of_current_frame,
|
||||
&address_of_current_frame)
|
||||
< 0)
|
||||
{
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{"get_stack_trace", get_stack_trace, METH_VARARGS, "Get the Python stack from a given PID"},
|
||||
{NULL, NULL, 0, NULL},
|
||||
};
|
||||
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "_testexternalinspection",
|
||||
.m_size = -1,
|
||||
.m_methods = methods,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__testexternalinspection(void)
|
||||
{
|
||||
PyObject* mod = PyModule_Create(&module);
|
||||
int rc = PyModule_AddIntConstant(mod, "PROCESS_VM_READV_SUPPORTED", HAVE_PROCESS_VM_READV);
|
||||
if (rc < 0) {
|
||||
Py_DECREF(mod);
|
||||
return NULL;
|
||||
}
|
||||
return mod;
|
||||
}
|
|
@ -34,6 +34,7 @@ IGNORE = {
|
|||
'_testinternalcapi',
|
||||
'_testmultiphase',
|
||||
'_testsinglephase',
|
||||
'_testexternalinspection',
|
||||
'_xxsubinterpreters',
|
||||
'_xxinterpchannels',
|
||||
'_xxinterpqueues',
|
||||
|
|
|
@ -661,6 +661,8 @@ MODULE__XXTESTFUZZ_FALSE
|
|||
MODULE__XXTESTFUZZ_TRUE
|
||||
MODULE_XXSUBTYPE_FALSE
|
||||
MODULE_XXSUBTYPE_TRUE
|
||||
MODULE__TESTEXTERNALINSPECTION_FALSE
|
||||
MODULE__TESTEXTERNALINSPECTION_TRUE
|
||||
MODULE__TESTMULTIPHASE_FALSE
|
||||
MODULE__TESTMULTIPHASE_TRUE
|
||||
MODULE__TESTIMPORTMULTIPLE_FALSE
|
||||
|
@ -17997,6 +17999,12 @@ if test "x$ac_cv_func_preadv2" = xyes
|
|||
then :
|
||||
printf "%s\n" "#define HAVE_PREADV2 1" >>confdefs.h
|
||||
|
||||
fi
|
||||
ac_fn_c_check_func "$LINENO" "process_vm_readv" "ac_cv_func_process_vm_readv"
|
||||
if test "x$ac_cv_func_process_vm_readv" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_PROCESS_VM_READV 1" >>confdefs.h
|
||||
|
||||
fi
|
||||
ac_fn_c_check_func "$LINENO" "pthread_cond_timedwait_relative_np" "ac_cv_func_pthread_cond_timedwait_relative_np"
|
||||
if test "x$ac_cv_func_pthread_cond_timedwait_relative_np" = xyes
|
||||
|
@ -30688,6 +30696,44 @@ fi
|
|||
printf "%s\n" "$py_cv_module__testmultiphase" >&6; }
|
||||
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module _testexternalinspection" >&5
|
||||
printf %s "checking for stdlib extension module _testexternalinspection... " >&6; }
|
||||
if test "$py_cv_module__testexternalinspection" != "n/a"
|
||||
then :
|
||||
|
||||
if test "$TEST_MODULES" = yes
|
||||
then :
|
||||
if true
|
||||
then :
|
||||
py_cv_module__testexternalinspection=yes
|
||||
else $as_nop
|
||||
py_cv_module__testexternalinspection=missing
|
||||
fi
|
||||
else $as_nop
|
||||
py_cv_module__testexternalinspection=disabled
|
||||
fi
|
||||
|
||||
fi
|
||||
as_fn_append MODULE_BLOCK "MODULE__TESTEXTERNALINSPECTION_STATE=$py_cv_module__testexternalinspection$as_nl"
|
||||
if test "x$py_cv_module__testexternalinspection" = xyes
|
||||
then :
|
||||
|
||||
|
||||
|
||||
|
||||
fi
|
||||
if test "$py_cv_module__testexternalinspection" = yes; then
|
||||
MODULE__TESTEXTERNALINSPECTION_TRUE=
|
||||
MODULE__TESTEXTERNALINSPECTION_FALSE='#'
|
||||
else
|
||||
MODULE__TESTEXTERNALINSPECTION_TRUE='#'
|
||||
MODULE__TESTEXTERNALINSPECTION_FALSE=
|
||||
fi
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $py_cv_module__testexternalinspection" >&5
|
||||
printf "%s\n" "$py_cv_module__testexternalinspection" >&6; }
|
||||
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for stdlib extension module xxsubtype" >&5
|
||||
printf %s "checking for stdlib extension module xxsubtype... " >&6; }
|
||||
if test "$py_cv_module_xxsubtype" != "n/a"
|
||||
|
@ -31300,6 +31346,10 @@ if test -z "${MODULE__TESTMULTIPHASE_TRUE}" && test -z "${MODULE__TESTMULTIPHASE
|
|||
as_fn_error $? "conditional \"MODULE__TESTMULTIPHASE\" was never defined.
|
||||
Usually this means the macro was only invoked conditionally." "$LINENO" 5
|
||||
fi
|
||||
if test -z "${MODULE__TESTEXTERNALINSPECTION_TRUE}" && test -z "${MODULE__TESTEXTERNALINSPECTION_FALSE}"; then
|
||||
as_fn_error $? "conditional \"MODULE__TESTEXTERNALINSPECTION\" was never defined.
|
||||
Usually this means the macro was only invoked conditionally." "$LINENO" 5
|
||||
fi
|
||||
if test -z "${MODULE_XXSUBTYPE_TRUE}" && test -z "${MODULE_XXSUBTYPE_FALSE}"; then
|
||||
as_fn_error $? "conditional \"MODULE_XXSUBTYPE\" was never defined.
|
||||
Usually this means the macro was only invoked conditionally." "$LINENO" 5
|
||||
|
|
|
@ -4920,7 +4920,7 @@ AC_CHECK_FUNCS([ \
|
|||
mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
|
||||
pipe2 plock poll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \
|
||||
posix_spawn_file_actions_addclosefrom_np \
|
||||
pread preadv preadv2 pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \
|
||||
pread preadv preadv2 process_vm_readv pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \
|
||||
pthread_kill ptsname ptsname_r pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
|
||||
rtpSpawn sched_get_priority_max sched_rr_get_interval sched_setaffinity \
|
||||
sched_setparam sched_setscheduler sem_clockwait sem_getvalue sem_open \
|
||||
|
@ -7581,6 +7581,7 @@ PY_STDLIB_MOD([_testinternalcapi], [test "$TEST_MODULES" = yes])
|
|||
PY_STDLIB_MOD([_testbuffer], [test "$TEST_MODULES" = yes])
|
||||
PY_STDLIB_MOD([_testimportmultiple], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
|
||||
PY_STDLIB_MOD([_testmultiphase], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
|
||||
PY_STDLIB_MOD([_testexternalinspection], [test "$TEST_MODULES" = yes])
|
||||
PY_STDLIB_MOD([xxsubtype], [test "$TEST_MODULES" = yes])
|
||||
PY_STDLIB_MOD([_xxtestfuzz], [test "$TEST_MODULES" = yes])
|
||||
PY_STDLIB_MOD([_ctypes_test],
|
||||
|
|
|
@ -933,6 +933,9 @@
|
|||
/* Define to 1 if you have the <process.h> header file. */
|
||||
#undef HAVE_PROCESS_H
|
||||
|
||||
/* Define to 1 if you have the `process_vm_readv' function. */
|
||||
#undef HAVE_PROCESS_VM_READV
|
||||
|
||||
/* Define if your compiler supports function prototype */
|
||||
#undef HAVE_PROTOTYPES
|
||||
|
||||
|
|
Loading…
Reference in New Issue