Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit be520c8

Browse filesBrowse files
committed
Add macOS support
1 parent 8b7f010 commit be520c8
Copy full SHA for be520c8

File tree

Expand file treeCollapse file tree

5 files changed

+232
-32
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+232
-32
lines changed

‎Lib/test/test_external_inspection.py

Copy file name to clipboardExpand all lines: Lib/test/test_external_inspection.py
+1-2Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ def _make_test_script(script_dir, script_basename, source):
2323

2424
class TestGetStackTrace(unittest.TestCase):
2525

26-
@unittest.skipIf(sys.platform != "linux", "Test only runs on Linux")
27-
@unittest.skipIf(not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support")
26+
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support")
2827
def test_stack_trace(self):
2928
# Spawn a process with some realistic Python code
3029
script = textwrap.dedent("""\

‎Modules/_testexternalinspection.c

Copy file name to clipboardExpand all lines: Modules/_testexternalinspection.c
+228-28Lines changed: 228 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
#ifdef __linux__
44
# include <elf.h>
5-
# include <sys/mman.h>
65
# include <sys/uio.h>
76
#if INTPTR_MAX == INT64_MAX
87
# define Elf_Ehdr Elf64_Ehdr
@@ -13,27 +12,229 @@
1312
#endif
1413
#endif
1514

15+
#ifdef __APPLE__
16+
#include <mach-o/fat.h>
17+
#include <mach-o/loader.h>
18+
#include <mach-o/nlist.h>
19+
#include <mach/mach.h>
20+
#include <mach/mach_vm.h>
21+
#include <mach/machine.h>
22+
#include <libproc.h>
23+
#endif
24+
1625
#include <errno.h>
1726
#include <fcntl.h>
27+
#include <stddef.h>
28+
#include <stdint.h>
1829
#include <stdio.h>
1930
#include <stdlib.h>
2031
#include <string.h>
32+
#include <sys/mman.h>
33+
#include <sys/param.h>
34+
#include <sys/proc.h>
2135
#include <sys/stat.h>
36+
#include <sys/sysctl.h>
2237
#include <sys/types.h>
2338
#include <unistd.h>
24-
#include <stdint.h>
2539

2640
#ifndef Py_BUILD_CORE_BUILTIN
2741
# define Py_BUILD_CORE_MODULE 1
2842
#endif
2943
#include "Python.h"
3044
#include <internal/pycore_runtime.h>
3145

46+
#ifndef HAVE_PROCESS_VM_READV
47+
#define HAVE_PROCESS_VM_READV 0
48+
#endif
49+
50+
#ifdef __APPLE__
51+
52+
static void* analyze_macho64(mach_port_t proc_ref, void *base, void *map) {
53+
struct mach_header_64 *hdr = (struct mach_header_64 *)map;
54+
int ncmds = hdr->ncmds;
55+
56+
int cmd_cnt = 0;
57+
struct segment_command_64 *cmd = map + sizeof(struct mach_header_64);
58+
59+
mach_vm_size_t size = 0;
60+
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
61+
mach_vm_address_t address = (mach_vm_address_t)base;
62+
vm_region_basic_info_data_64_t region_info;
63+
mach_port_t object_name;
64+
65+
for (register int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
66+
if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__DATA") == 0) {
67+
while (cmd->filesize != size) {
68+
address += size;
69+
if (mach_vm_region(
70+
proc_ref, &address, &size, VM_REGION_BASIC_INFO_64,
71+
(vm_region_info_t)&region_info, // cppcheck-suppress [uninitvar]
72+
&count, &object_name) != KERN_SUCCESS) {
73+
printf("Cannot get any more VM maps.\n");
74+
return 0;
75+
}
76+
}
77+
base = (void *)address - cmd->vmaddr;
78+
79+
int nsects = cmd->nsects;
80+
struct section_64 *sec =
81+
(struct section_64 *)((void *)cmd +
82+
sizeof(struct segment_command_64));
83+
for (register int j = 0; j < nsects; j++) {
84+
if (strcmp(sec[j].sectname, "PyRuntime") == 0) {
85+
return base + sec[j].addr;
86+
}
87+
}
88+
cmd_cnt++;
89+
}
90+
91+
cmd = (struct segment_command_64 *)((void *)cmd + cmd->cmdsize);
92+
}
93+
return 0;
94+
}
95+
96+
typedef struct {
97+
void *addr;
98+
size_t size;
99+
} map_t;
100+
101+
static inline map_t *map_new(int fd, size_t size, int flags) {
102+
void *addr = mmap(0, size, PROT_READ, flags, fd, 0);
103+
if (!(addr))
104+
return NULL;
105+
106+
map_t *map = malloc(sizeof(map_t));
107+
if (map == MAP_FAILED) {
108+
munmap(map, size);
109+
return NULL;
110+
}
111+
112+
map->size = size;
113+
map->addr = addr;
114+
115+
return map;
116+
}
117+
118+
static void* analyze_macho(char *path, void *base, mach_vm_size_t size,
119+
mach_port_t proc_ref) {
120+
int fd = open(path, O_RDONLY);
121+
if (fd == -1) {
122+
printf("Cannot open binary %s\n", path);
123+
return 0;
124+
}
125+
126+
// This would cause problem if allocated in the stack frame
127+
void *fs_buffer = malloc(sizeof(struct stat));
128+
struct stat *fs = (struct stat *)fs_buffer;
129+
map_t *map = NULL;
130+
if (fstat(fd, fs) == -1) {
131+
printf("Cannot get size of binary %s\n", path);
132+
return 0;
133+
}
134+
135+
map = map_new(fd, fs->st_size, MAP_SHARED);
136+
if (!map) {
137+
printf("Cannot map binary %s\n", path);
138+
return 0;
139+
}
140+
141+
void *map_addr = map->addr;
142+
143+
struct mach_header_64 *hdr = (struct mach_header_64 *)map_addr;
144+
switch (hdr->magic) {
145+
case MH_MAGIC:
146+
case MH_CIGAM:
147+
case FAT_MAGIC:
148+
case FAT_CIGAM:
149+
printf("Mach-O 32 or FAT binaries are not supported\n");
150+
return 0;
151+
case MH_MAGIC_64:
152+
case MH_CIGAM_64:
153+
return analyze_macho64(proc_ref, base, map_addr);
154+
default:
155+
printf("Unknown Mach-O magic\n");
156+
return 0;
157+
}
158+
159+
return 0;
160+
}
161+
162+
static mach_port_t pid_to_task(pid_t pid) {
163+
mach_port_t task;
164+
kern_return_t result;
165+
166+
result = task_for_pid(mach_task_self(), pid, &task);
167+
if (result != KERN_SUCCESS) {
168+
printf("Call to task_for_pid failed on PID %d: %s", pid, mach_error_string(result));
169+
return 0;
170+
}
171+
return task;
172+
}
173+
174+
static void* get_py_runtime(pid_t pid) {
175+
mach_vm_address_t address = 0;
176+
mach_vm_size_t size = 0;
177+
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
178+
vm_region_basic_info_data_64_t region_info;
179+
mach_port_t object_name;
180+
181+
mach_port_t proc_ref = pid_to_task(pid);
182+
if (proc_ref == 0) {
183+
printf("Cannot get task for PID\n");
184+
return NULL;
185+
}
186+
187+
int match_found = 0;
188+
char map_filename[MAXPATHLEN + 1];
189+
void* result_address = NULL;
190+
while (mach_vm_region(
191+
proc_ref, &address, &size, VM_REGION_BASIC_INFO_64,
192+
(vm_region_info_t)&region_info, // cppcheck-suppress [uninitvar]
193+
&count, &object_name) == KERN_SUCCESS) {
194+
195+
int path_len = proc_regionfilename(pid, address, map_filename, MAXPATHLEN);
196+
if (path_len == 0) {
197+
address += size;
198+
continue;
199+
}
200+
201+
char *filename = strrchr(map_filename, '/');
202+
if (filename != NULL) {
203+
filename++; // Move past the '/'
204+
} else {
205+
filename = map_filename; // No path, use the whole string
206+
}
207+
208+
// Check if the filename starts with "python" or "libpython"
209+
if (!match_found && strncmp(filename, "python", 6) == 0) {
210+
match_found = 1;
211+
result_address = analyze_macho(map_filename, (void *)address, size, proc_ref);
212+
}
213+
if (strncmp(filename, "libpython", 9) == 0) {
214+
match_found = 1;
215+
result_address = analyze_macho(map_filename, (void *)address, size, proc_ref);
216+
break;
217+
}
218+
219+
address += size;
220+
}
221+
return result_address;
222+
}
223+
#endif
32224

33225

34-
unsigned long
35-
get_py_runtime(char* elf_file) {
36226
#ifdef __linux__
227+
void*
228+
get_py_runtime(pid_t pid) {
229+
230+
char elf_file[256];
231+
unsigned long start_address = find_python_map_start_address(pid, elf_file);
232+
233+
if (start_address == 0) {
234+
PyErr_SetString(PyExc_RuntimeError, "No memory map associated with python or libpython found");
235+
return NULL;
236+
}
237+
37238
unsigned long result = 0;
38239

39240
int fd = open(elf_file, O_RDONLY);
@@ -70,15 +271,12 @@ get_py_runtime(char* elf_file) {
70271
}
71272

72273
if (py_runtime_section != NULL) {
73-
result = (unsigned long)py_runtime_section->sh_addr;
274+
result = start_address + (unsigned long)py_runtime_section->sh_addr;
74275
}
75276

76277
close(fd);
77278
munmap(file_memory, file_stats.st_size);
78279
return result;
79-
#else
80-
return 0;
81-
#endif
82280
}
83281

84282
unsigned long
@@ -129,15 +327,13 @@ find_python_map_start_address(pid_t pid, char* result_filename) {
129327

130328
return result_address;
131329
}
330+
#endif
132331

133332
ssize_t
134333
read_memory(pid_t pid, void* remote_address, ssize_t size, void* local_address) {
135-
#ifndef HAVE_PROCESS_VM_READV
136-
return -1
137-
#else
138334
ssize_t total_bytes_read = 0;
335+
#ifdef __linux__
139336
ssize_t bytes_read;
140-
141337
while (total_bytes_read < size) {
142338
struct iovec local_iov = {(char*)local_address + total_bytes_read, size - total_bytes_read};
143339
struct iovec remote_iov = {(char*)remote_address + total_bytes_read, size - total_bytes_read};
@@ -154,9 +350,23 @@ read_memory(pid_t pid, void* remote_address, ssize_t size, void* local_address)
154350
break;
155351
}
156352
}
157-
158-
return total_bytes_read;
353+
#elif defined(__APPLE__)
354+
ssize_t result = -1;
355+
kern_return_t kr = mach_vm_read_overwrite(
356+
pid_to_task(pid),
357+
(mach_vm_address_t)remote_address,
358+
size,
359+
(mach_vm_address_t)local_address,
360+
(mach_vm_size_t *)&result);
361+
362+
if (kr != KERN_SUCCESS) {
363+
return -1;
364+
}
365+
total_bytes_read = size;
366+
#else
367+
return -1;
159368
#endif
369+
return total_bytes_read;
160370
}
161371

162372
int
@@ -191,26 +401,16 @@ get_stack_trace(PyObject* self, PyObject* args) {
191401
return NULL;
192402
}
193403

194-
char map_filename[256];
195-
unsigned long start_address = find_python_map_start_address(pid, map_filename);
196-
197-
if (start_address == 0) {
198-
PyErr_SetString(PyExc_RuntimeError, "No memory map associated with python or libpython found");
199-
return NULL;
200-
}
201-
202-
unsigned long py_runtime_address = get_py_runtime(map_filename);
203-
if (py_runtime_address == 0) {
404+
void* runtime_start_address = get_py_runtime(pid);
405+
if (runtime_start_address == NULL) {
204406
PyErr_SetString(PyExc_RuntimeError, "Failed to get .PyRuntime address");
205407
return NULL;
206408
}
207409

208-
void* runtime_state_address = (void*)start_address + py_runtime_address;
209-
210410
size_t size = sizeof(struct _Py_DebugOffsets);
211411
struct _Py_DebugOffsets local_debug_offsets;
212412

213-
ssize_t bytes_read = read_memory(pid, runtime_state_address, size, &local_debug_offsets);
413+
ssize_t bytes_read = read_memory(pid, runtime_start_address, size, &local_debug_offsets);
214414
if (bytes_read == -1) {
215415
return NULL;
216416
}
@@ -220,7 +420,7 @@ get_stack_trace(PyObject* self, PyObject* args) {
220420
void* address_of_interpreter_state;
221421
bytes_read = read_memory(
222422
pid,
223-
(void*)(runtime_state_address + thread_state_list_head),
423+
(void*)(runtime_start_address + thread_state_list_head),
224424
sizeof(void*),
225425
&address_of_interpreter_state);
226426
if (bytes_read == -1) {

‎Tools/build/generate_stdlib_module_names.py

Copy file name to clipboardExpand all lines: Tools/build/generate_stdlib_module_names.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
'_testinternalcapi',
3535
'_testmultiphase',
3636
'_testsinglephase',
37+
'_testexternalinspection',
3738
'_xxsubinterpreters',
3839
'_xxinterpchannels',
3940
'_xxinterpqueues',

‎configure

Copy file name to clipboardExpand all lines: configure
+1-1Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎configure.ac

Copy file name to clipboardExpand all lines: configure.ac
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7457,7 +7457,7 @@ PY_STDLIB_MOD([_testinternalcapi], [test "$TEST_MODULES" = yes])
74577457
PY_STDLIB_MOD([_testbuffer], [test "$TEST_MODULES" = yes])
74587458
PY_STDLIB_MOD([_testimportmultiple], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
74597459
PY_STDLIB_MOD([_testmultiphase], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_dlopen" = yes])
7460-
PY_STDLIB_MOD([_testexternalinspection], [test "$TEST_MODULES" = yes], [test "$ac_cv_func_process_vm_readv" = yes])
7460+
PY_STDLIB_MOD([_testexternalinspection], [test "$TEST_MODULES" = yes])
74617461
PY_STDLIB_MOD([xxsubtype], [test "$TEST_MODULES" = yes])
74627462
PY_STDLIB_MOD([_xxtestfuzz], [test "$TEST_MODULES" = yes])
74637463
PY_STDLIB_MOD([_ctypes_test],

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.