process_memory/
macos.rs

1use libc::{c_int, pid_t};
2use mach::kern_return::KERN_SUCCESS;
3use mach::port::{mach_port_name_t, MACH_PORT_NULL};
4use std::process::Child;
5
6use super::{Architecture, CopyAddress, ProcessHandleExt, PutAddress, TryIntoProcessHandle};
7
8/// On OS X a `Pid` is just a `libc::pid_t`.
9pub type Pid = pid_t;
10/// On OS X a `ProcessHandle` is a mach port.
11pub type ProcessHandle = (mach_port_name_t, Architecture);
12
13impl ProcessHandleExt for ProcessHandle {
14    #[must_use]
15    fn check_handle(&self) -> bool {
16        self.0 != 0
17    }
18    #[must_use]
19    fn null_type() -> ProcessHandle {
20        (0, Architecture::from_native())
21    }
22    #[must_use]
23    fn set_arch(self, arch: Architecture) -> Self {
24        (self.0, arch)
25    }
26}
27
28/// A small wrapper around `task_for_pid`, which taskes a pid returns the mach port representing its task.
29fn task_for_pid(pid: Pid) -> std::io::Result<mach_port_name_t> {
30    let mut task: mach_port_name_t = MACH_PORT_NULL;
31
32    unsafe {
33        let result =
34            mach::traps::task_for_pid(mach::traps::mach_task_self(), pid as c_int, &mut task);
35        if result != KERN_SUCCESS {
36            return Err(std::io::Error::last_os_error());
37        }
38    }
39
40    Ok(task)
41}
42
43/// `Pid` can be turned into a `ProcessHandle` with `task_for_pid`.
44impl TryIntoProcessHandle for Pid {
45    fn try_into_process_handle(&self) -> std::io::Result<ProcessHandle> {
46        Ok((task_for_pid(*self)?, Architecture::from_native()))
47    }
48}
49
50/// This `TryIntoProcessHandle` impl simply calls the `TryIntoProcessHandle` impl for `Pid`.
51impl TryIntoProcessHandle for Child {
52    fn try_into_process_handle(&self) -> std::io::Result<ProcessHandle> {
53        #[allow(clippy::cast_possible_wrap)]
54        Pid::try_into_process_handle(&(self.id() as _))
55    }
56}
57
58/// Here we use `mach_vm_write` to write a buffer to some arbitrary address on a process.
59impl PutAddress for ProcessHandle {
60    fn put_address(&self, addr: usize, buf: &[u8]) -> std::io::Result<()> {
61        #[allow(clippy::cast_possible_truncation)]
62        let result = unsafe {
63            mach::vm::mach_vm_write(self.0, addr as _, buf.as_ptr() as _, buf.len() as _)
64        };
65        if result != KERN_SUCCESS {
66            return Err(std::io::Error::last_os_error());
67        }
68        Ok(())
69    }
70}
71
72/// Use `vm_read_overwrite` to read memory from another process on OS X.
73///
74/// We use `vm_read_overwrite` instead of `vm_read` because it can handle non-aligned reads and
75/// won't read an entire page.
76impl CopyAddress for ProcessHandle {
77    #[allow(clippy::inline_always)]
78    #[inline(always)]
79    fn get_pointer_width(&self) -> Architecture {
80        self.1
81    }
82
83    fn copy_address(&self, addr: usize, buf: &mut [u8]) -> std::io::Result<()> {
84        let mut read_len: u64 = 0;
85        let result = unsafe {
86            mach::vm::mach_vm_read_overwrite(
87                self.0,
88                addr as _,
89                buf.len() as _,
90                buf.as_mut_ptr() as _,
91                &mut read_len,
92            )
93        };
94
95        if result != KERN_SUCCESS {
96            return Err(std::io::Error::last_os_error());
97        }
98
99        if read_len == buf.len() as _ {
100            Ok(())
101        } else {
102            Err(std::io::Error::new(
103                std::io::ErrorKind::BrokenPipe,
104                format!(
105                    "Mismatched read sizes for `vm_read_overwrite` (expected {}, got {})",
106                    buf.len(),
107                    read_len
108                ),
109            ))
110        }
111    }
112}