Skip to content

Commit 4ced7ae

Browse files
committed
Add macOS support
1 parent be520c8 commit 4ced7ae

File tree

2 files changed

+60
-54
lines changed

2 files changed

+60
-54
lines changed

Lib/test/test_external_inspection.py

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def _make_test_script(script_dir, script_basename, source):
2323

2424
class TestGetStackTrace(unittest.TestCase):
2525

26+
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux", "Test only runs on Linux and MacOS")
2627
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support")
2728
def test_stack_trace(self):
2829
# Spawn a process with some realistic Python code

Modules/_testexternalinspection.c

+59-54
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
#include <mach/mach_vm.h>
2121
#include <mach/machine.h>
2222
#include <libproc.h>
23-
#endif
23+
#include <sys/proc.h>
24+
#include <sys/sysctl.h>
25+
#endif
2426

2527
#include <errno.h>
2628
#include <fcntl.h>
@@ -31,9 +33,7 @@
3133
#include <string.h>
3234
#include <sys/mman.h>
3335
#include <sys/param.h>
34-
#include <sys/proc.h>
3536
#include <sys/stat.h>
36-
#include <sys/sysctl.h>
3737
#include <sys/types.h>
3838
#include <unistd.h>
3939

@@ -166,6 +166,7 @@ static mach_port_t pid_to_task(pid_t pid) {
166166
result = task_for_pid(mach_task_self(), pid, &task);
167167
if (result != KERN_SUCCESS) {
168168
printf("Call to task_for_pid failed on PID %d: %s", pid, mach_error_string(result));
169+
PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID");
169170
return 0;
170171
}
171172
return task;
@@ -224,6 +225,55 @@ static void* get_py_runtime(pid_t pid) {
224225

225226

226227
#ifdef __linux__
228+
unsigned long
229+
find_python_map_start_address(pid_t pid, char* result_filename) {
230+
char maps_file_path[64];
231+
sprintf(maps_file_path, "/proc/%d/maps", pid);
232+
233+
FILE* maps_file = fopen(maps_file_path, "r");
234+
if (maps_file == NULL) {
235+
PyErr_SetFromErrno(PyExc_OSError);
236+
return 0;
237+
}
238+
239+
int match_found = 0;
240+
241+
char line[256];
242+
char map_filename[256];
243+
unsigned long result_address = 0;
244+
while (fgets(line, sizeof(line), maps_file) != NULL) {
245+
unsigned long start_address = 0;
246+
sscanf(line, "%lx-%*x %*s %*s %*s %*s %s", &start_address, map_filename);
247+
char* filename = strrchr(map_filename, '/');
248+
if (filename != NULL) {
249+
filename++; // Move past the '/'
250+
} else {
251+
filename = map_filename; // No path, use the whole string
252+
}
253+
254+
// Check if the filename starts with "python" or "libpython"
255+
if (!match_found && strncmp(filename, "python", 6) == 0) {
256+
match_found = 1;
257+
result_address = start_address;
258+
strcpy(result_filename, map_filename);
259+
}
260+
if (strncmp(filename, "libpython", 9) == 0) {
261+
match_found = 1;
262+
result_address = start_address;
263+
strcpy(result_filename, map_filename);
264+
break;
265+
}
266+
}
267+
268+
fclose(maps_file);
269+
270+
if (!match_found) {
271+
map_filename[0] = '\0';
272+
}
273+
274+
return result_address;
275+
}
276+
227277
void*
228278
get_py_runtime(pid_t pid) {
229279

@@ -279,54 +329,7 @@ get_py_runtime(pid_t pid) {
279329
return result;
280330
}
281331

282-
unsigned long
283-
find_python_map_start_address(pid_t pid, char* result_filename) {
284-
char maps_file_path[64];
285-
sprintf(maps_file_path, "/proc/%d/maps", pid);
286-
287-
FILE* maps_file = fopen(maps_file_path, "r");
288-
if (maps_file == NULL) {
289-
PyErr_SetFromErrno(PyExc_OSError);
290-
return 0;
291-
}
292-
293-
int match_found = 0;
294-
295-
char line[256];
296-
char map_filename[256];
297-
unsigned long result_address = 0;
298-
while (fgets(line, sizeof(line), maps_file) != NULL) {
299-
unsigned long start_address = 0;
300-
sscanf(line, "%lx-%*x %*s %*s %*s %*s %s", &start_address, map_filename);
301-
char* filename = strrchr(map_filename, '/');
302-
if (filename != NULL) {
303-
filename++; // Move past the '/'
304-
} else {
305-
filename = map_filename; // No path, use the whole string
306-
}
307332

308-
// Check if the filename starts with "python" or "libpython"
309-
if (!match_found && strncmp(filename, "python", 6) == 0) {
310-
match_found = 1;
311-
result_address = start_address;
312-
strcpy(result_filename, map_filename);
313-
}
314-
if (strncmp(filename, "libpython", 9) == 0) {
315-
match_found = 1;
316-
result_address = start_address;
317-
strcpy(result_filename, map_filename);
318-
break;
319-
}
320-
}
321-
322-
fclose(maps_file);
323-
324-
if (!match_found) {
325-
map_filename[0] = '\0';
326-
}
327-
328-
return result_address;
329-
}
330333
#endif
331334

332335
ssize_t
@@ -363,7 +366,7 @@ read_memory(pid_t pid, void* remote_address, ssize_t size, void* local_address)
363366
return -1;
364367
}
365368
total_bytes_read = size;
366-
#else
369+
#else
367370
return -1;
368371
#endif
369372
return total_bytes_read;
@@ -391,8 +394,8 @@ read_string(pid_t pid, _Py_DebugOffsets *debug_offsets, void* address, char* buf
391394

392395
static PyObject*
393396
get_stack_trace(PyObject* self, PyObject* args) {
394-
#ifndef HAVE_PROCESS_VM_READV
395-
PyErr_SetString(PyExc_RuntimeError, "process_vm_readv not available on this platform");
397+
#if (!defined(__linux__) && !defined(__APPLE__)) || (defined(__linux__) && !HAVE_PROCESS_VM_READV)
398+
PyErr_SetString(PyExc_RuntimeError, "get_strac_trace is not supported on this platform");
396399
return NULL;
397400
#endif
398401
int pid;
@@ -403,7 +406,9 @@ get_stack_trace(PyObject* self, PyObject* args) {
403406

404407
void* runtime_start_address = get_py_runtime(pid);
405408
if (runtime_start_address == NULL) {
406-
PyErr_SetString(PyExc_RuntimeError, "Failed to get .PyRuntime address");
409+
if(!PyErr_Occurred()) {
410+
PyErr_SetString(PyExc_RuntimeError, "Failed to get .PyRuntime address");
411+
}
407412
return NULL;
408413
}
409414

0 commit comments

Comments
 (0)