Skip to content

Commit e67e1fb

Browse files
committed
Improve ReadRemoteMemory error handling on macOS
Improve the return value check to be able to raise a ProcessLookupError when the remote process is not available. Mach uses composite error values where higher error values indicate specific subsystems. We can use the err_get_code function to mask the higher bits to make our error checking more robust in case the subsystem bits are set. For example, in some situations if the process is in zombie state, we can get KERN_NO_SPACE (0x3) but the actual return value is 0x10000003 which indicates a specific subsystem, thus we need to use err_get_code to extract the error value. This also improves how KERN_INVALID_ARGUMENT is handled to check whether we got a generic invalid argument error, or if the process is no longer accessible.
1 parent dc9aad3 commit e67e1fb

File tree

2 files changed

+27
-8
lines changed

2 files changed

+27
-8
lines changed

Lib/test/test_sample_profiler.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,12 +1723,11 @@ def test_is_process_running(self):
17231723
self.assertIsNotNone(profiler.unwinder.get_stack_trace())
17241724
proc.kill()
17251725
proc.wait()
1726-
# ValueError on MacOS (yeah I know), ProcessLookupError on Linux and Windows
1727-
self.assertRaises((ValueError, ProcessLookupError), profiler.unwinder.get_stack_trace)
1726+
self.assertRaises(ProcessLookupError, profiler.unwinder.get_stack_trace)
17281727

17291728
# Exit the context manager to ensure the process is terminated
17301729
self.assertFalse(profiler._is_process_running())
1731-
self.assertRaises((ValueError, ProcessLookupError), profiler.unwinder.get_stack_trace)
1730+
self.assertRaises(ProcessLookupError, profiler.unwinder.get_stack_trace)
17321731

17331732
@unittest.skipUnless(sys.platform == "linux", "Only valid on Linux")
17341733
def test_esrch_signal_handling(self):

Python/remote_debug.h

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ extern "C" {
5050
# include <mach-o/fat.h>
5151
# include <mach-o/loader.h>
5252
# include <mach-o/nlist.h>
53+
# include <mach/error.h>
5354
# include <mach/mach.h>
5455
# include <mach/mach_vm.h>
5556
# include <mach/machine.h>
@@ -1053,18 +1054,37 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address
10531054
(mach_vm_size_t*)&result);
10541055

10551056
if (kr != KERN_SUCCESS) {
1056-
switch (kr) {
1057+
switch (err_get_code(kr)) {
10571058
case KERN_PROTECTION_FAILURE:
10581059
PyErr_Format(PyExc_PermissionError,
10591060
"Memory protection failure reading from PID %d at address "
10601061
"0x%lx (size %zu): insufficient permissions",
10611062
handle->pid, remote_address, len);
10621063
break;
10631064
case KERN_INVALID_ARGUMENT:
1064-
PyErr_Format(PyExc_ValueError,
1065-
"Invalid argument to mach_vm_read_overwrite for PID %d at "
1066-
"address 0x%lx (size %zu)",
1067-
handle->pid, remote_address, len);
1065+
// Perform a task_info check to see if the invalid argument is due
1066+
// to the process being terminated
1067+
task_basic_info_data_t task_basic_info;
1068+
mach_msg_type_number_t task_info_count = TASK_BASIC_INFO_COUNT;
1069+
kern_return_t task_valid_check = task_info(handle->task, TASK_BASIC_INFO,
1070+
(task_info_t)&task_basic_info,
1071+
&task_info_count);
1072+
if (task_valid_check == KERN_INVALID_ARGUMENT) {
1073+
PyErr_Format(PyExc_ProcessLookupError,
1074+
"Process %d is no longer accessible (process terminated)",
1075+
handle->pid);
1076+
} else {
1077+
PyErr_Format(PyExc_PermissionError,
1078+
"Invalid argument to mach_vm_read_overwrite for PID %d at "
1079+
"address 0x%lx (size %zu) - check memory permissions",
1080+
handle->pid, remote_address, len);
1081+
}
1082+
break;
1083+
case KERN_NO_SPACE:
1084+
case KERN_MEMORY_ERROR:
1085+
PyErr_Format(PyExc_ProcessLookupError,
1086+
"Process %d memory space no longer available (process terminated)",
1087+
handle->pid);
10681088
break;
10691089
default:
10701090
PyErr_Format(PyExc_RuntimeError,

0 commit comments

Comments
 (0)