1#[doc(hidden)]
27#[doc = include_str!("../README.md")]
28mod readme {}
29
30use std::io;
31
32pub trait CopyAddress {
34 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()>;
37}
38
39pub use crate::platform::Pid;
41pub use crate::platform::ProcessHandle;
63
64#[cfg(target_os = "linux")]
65mod platform {
66 use libc::{c_void, iovec, pid_t, process_vm_readv};
67 use std::convert::TryFrom;
68 use std::fs;
69 use std::io;
70 use std::io::Read;
71 use std::io::Seek;
72 use std::process::Child;
73
74 use super::CopyAddress;
75
76 pub type Pid = pid_t;
78 #[derive(Clone)]
80 pub struct ProcessHandle(Pid);
81
82 impl TryFrom<Pid> for ProcessHandle {
84 type Error = io::Error;
85
86 fn try_from(pid: Pid) -> io::Result<Self> {
87 Ok(Self(pid))
88 }
89 }
90
91 impl TryFrom<&Child> for ProcessHandle {
93 type Error = io::Error;
94
95 fn try_from(child: &Child) -> io::Result<Self> {
96 Self::try_from(child.id() as Pid)
97 }
98 }
99
100 impl CopyAddress for ProcessHandle {
101 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
102 let local_iov = iovec {
103 iov_base: buf.as_mut_ptr() as *mut c_void,
104 iov_len: buf.len(),
105 };
106 let remote_iov = iovec {
107 iov_base: addr as *mut c_void,
108 iov_len: buf.len(),
109 };
110 let result = unsafe { process_vm_readv(self.0, &local_iov, 1, &remote_iov, 1, 0) };
111 if result == -1 {
112 match io::Error::last_os_error().raw_os_error() {
113 Some(libc::ENOSYS) | Some(libc::EPERM) => {
114 let mut procmem = fs::File::open(format!("/proc/{}/mem", self.0))?;
117 procmem.seek(io::SeekFrom::Start(addr as u64))?;
118 procmem.read_exact(buf)
119 }
120 _ => Err(io::Error::last_os_error()),
121 }
122 } else if result as usize != buf.len() {
123 Err(io::Error::new(
124 io::ErrorKind::UnexpectedEof,
125 format!(
126 "Mismatched read sizes for `process_vm_readv` (expected {}, got {})",
127 buf.len(),
128 result
129 ),
130 ))
131 } else {
132 Ok(())
133 }
134 }
135 }
136}
137
138#[cfg(target_os = "macos")]
139mod platform {
140 use libc::{c_int, pid_t};
141 use mach2::kern_return::{kern_return_t, KERN_SUCCESS};
142 use mach2::port::{mach_port_name_t, mach_port_t, MACH_PORT_NULL};
143 use mach2::vm_types::{mach_vm_address_t, mach_vm_size_t};
144
145 use std::convert::TryFrom;
146 use std::io;
147 use std::process::Child;
148
149 use super::CopyAddress;
150
151 #[allow(non_camel_case_types)]
152 type vm_map_t = mach_port_t;
153 #[allow(non_camel_case_types)]
154 type vm_address_t = mach_vm_address_t;
155 #[allow(non_camel_case_types)]
156 type vm_size_t = mach_vm_size_t;
157
158 pub type Pid = pid_t;
160 #[derive(Clone)]
162 pub struct ProcessHandle(mach_port_name_t);
163
164 extern "C" {
165 fn vm_read_overwrite(
166 target_task: vm_map_t,
167 address: vm_address_t,
168 size: vm_size_t,
169 data: vm_address_t,
170 out_size: *mut vm_size_t,
171 ) -> kern_return_t;
172 }
173
174 fn task_for_pid(pid: Pid) -> io::Result<mach_port_name_t> {
177 if pid == unsafe { libc::getpid() } as Pid {
178 return Ok(unsafe { mach2::traps::mach_task_self() });
179 }
180
181 let mut task: mach_port_name_t = MACH_PORT_NULL;
182
183 unsafe {
184 let result =
185 mach2::traps::task_for_pid(mach2::traps::mach_task_self(), pid as c_int, &mut task);
186 if result != KERN_SUCCESS {
187 return Err(io::Error::last_os_error());
188 }
189 }
190
191 Ok(task)
192 }
193
194 impl TryFrom<Pid> for ProcessHandle {
196 type Error = io::Error;
197
198 fn try_from(pid: Pid) -> io::Result<Self> {
199 Ok(Self(task_for_pid(pid)?))
200 }
201 }
202
203 impl TryFrom<mach_port_name_t> for ProcessHandle {
205 type Error = io::Error;
206
207 fn try_from(mach_port_name: mach_port_name_t) -> io::Result<Self> {
208 Ok(Self(mach_port_name))
209 }
210 }
211
212 impl TryFrom<&Child> for ProcessHandle {
224 type Error = io::Error;
225
226 fn try_from(child: &Child) -> io::Result<Self> {
227 Self::try_from(child.id() as Pid)
228 }
229 }
230
231 impl CopyAddress for ProcessHandle {
233 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
234 let mut read_len = buf.len() as vm_size_t;
235 let result = unsafe {
236 vm_read_overwrite(
237 self.0,
238 addr as vm_address_t,
239 buf.len() as vm_size_t,
240 buf.as_mut_ptr() as vm_address_t,
241 &mut read_len,
242 )
243 };
244
245 if read_len != buf.len() as vm_size_t {
246 return Err(io::Error::new(
247 io::ErrorKind::UnexpectedEof,
248 format!(
249 "Mismatched read sizes for `vm_read` (expected {}, got {})",
250 buf.len(),
251 read_len
252 ),
253 ));
254 }
255
256 if result != KERN_SUCCESS {
257 return Err(io::Error::last_os_error());
258 }
259 Ok(())
260 }
261 }
262}
263
264#[cfg(target_os = "freebsd")]
265mod platform {
266 use libc::{c_int, c_void, pid_t};
267 use libc::{waitpid, EBUSY, PIOD_READ_D, PT_ATTACH, PT_DETACH, PT_IO, WIFSTOPPED};
268 use std::convert::TryFrom;
269 use std::process::Child;
270 use std::{io, ptr};
271
272 use super::CopyAddress;
273
274 pub type Pid = pid_t;
276 #[derive(Clone)]
278 pub struct ProcessHandle(Pid);
279
280 #[repr(C)]
281 struct PtraceIoDesc {
282 piod_op: c_int,
283 piod_offs: *mut c_void,
284 piod_addr: *mut c_void,
285 piod_len: usize,
286 }
287
288 #[derive(PartialEq)]
294 enum PtraceLockState {
295 Release,
296 NoRelease,
297 }
298
299 extern "C" {
300 fn ptrace(request: c_int, pid: pid_t, io_desc: *mut PtraceIoDesc, data: c_int) -> c_int;
303 }
304
305 impl TryFrom<Pid> for ProcessHandle {
307 type Error = io::Error;
308
309 fn try_from(pid: Pid) -> io::Result<Self> {
310 Ok(Self(pid))
311 }
312 }
313
314 impl TryFrom<&Child> for ProcessHandle {
316 type Error = io::Error;
317
318 fn try_from(child: &Child) -> io::Result<Self> {
319 Self::try_from(child.id() as Pid)
320 }
321 }
322
323 fn ptrace_attach(pid: Pid) -> io::Result<PtraceLockState> {
325 let attach_status = unsafe { ptrace(PT_ATTACH, pid, ptr::null_mut(), 0) };
326
327 let last_error = io::Error::last_os_error();
328
329 if let Some(error) = last_error.raw_os_error() {
330 if attach_status == -1 {
331 return match error {
332 EBUSY => Ok(PtraceLockState::NoRelease),
333 _ => Err(last_error),
334 };
335 }
336 }
337
338 let mut wait_status = 0;
339
340 let stopped = unsafe {
341 waitpid(pid, &mut wait_status as *mut _, 0);
342 WIFSTOPPED(wait_status)
343 };
344
345 if !stopped {
346 Err(io::Error::last_os_error())
347 } else {
348 Ok(PtraceLockState::Release)
349 }
350 }
351
352 fn ptrace_io(pid: Pid, addr: usize, buf: &mut [u8]) -> io::Result<()> {
354 let mut ptrace_io_desc = PtraceIoDesc {
355 piod_op: PIOD_READ_D,
356 piod_offs: addr as *mut c_void,
357 piod_addr: buf.as_mut_ptr() as *mut c_void,
358 piod_len: buf.len(),
359 };
360
361 let result = unsafe { ptrace(PT_IO, pid, &mut ptrace_io_desc as *mut _, 0) };
362
363 if result == -1 {
364 Err(io::Error::last_os_error())
365 } else if ptrace_io_desc.piod_len != buf.len() {
366 Err(io::Error::new(
367 io::ErrorKind::UnexpectedEof,
368 format!(
369 "Mismatched read sizes for `PT_IO` (expected {}, got {})",
370 buf.len(),
371 ptrace_io_desc.piod_len
372 ),
373 ))
374 } else {
375 Ok(())
376 }
377 }
378
379 fn ptrace_detach(pid: Pid) -> io::Result<()> {
381 let detach_status = unsafe { ptrace(PT_DETACH, pid, ptr::null_mut(), 0) };
382
383 if detach_status == -1 {
384 Err(io::Error::last_os_error())
385 } else {
386 Ok(())
387 }
388 }
389
390 impl CopyAddress for ProcessHandle {
391 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
392 let should_detach = ptrace_attach(self.0)? == PtraceLockState::Release;
393
394 let result = ptrace_io(self.0, addr, buf);
395 if should_detach {
396 ptrace_detach(self.0)?
397 }
398 result
399 }
400 }
401}
402
403#[cfg(windows)]
404mod platform {
405 use std::convert::TryFrom;
406 use std::io;
407 use std::mem;
408 use std::ops::Deref;
409 use std::os::raw::c_void;
410 use std::os::windows::io::RawHandle;
411 use std::process::Child;
412 use std::ptr;
413 use std::sync::Arc;
414 use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
415 use windows_sys::Win32::System::Diagnostics::Debug::ReadProcessMemory;
416 use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_VM_READ};
417
418 use super::CopyAddress;
419
420 pub type Pid = u32;
422 #[derive(Eq, PartialEq, Hash)]
423 struct ProcessHandleInner(HANDLE);
424
425 unsafe impl Send for ProcessHandleInner {}
428 unsafe impl Sync for ProcessHandleInner {}
429
430 #[derive(Clone, Eq, PartialEq, Hash)]
432 pub struct ProcessHandle(Arc<ProcessHandleInner>);
433
434 impl Deref for ProcessHandle {
435 type Target = HANDLE;
436
437 fn deref(&self) -> &Self::Target {
438 &self.0 .0
439 }
440 }
441
442 impl Drop for ProcessHandleInner {
443 fn drop(&mut self) {
444 unsafe { CloseHandle(self.0) };
445 }
446 }
447
448 impl TryFrom<Pid> for ProcessHandle {
450 type Error = io::Error;
451
452 fn try_from(pid: Pid) -> io::Result<Self> {
453 let handle = unsafe { OpenProcess(PROCESS_VM_READ, 0, pid) };
454 if handle.is_null() {
455 Err(io::Error::last_os_error())
456 } else {
457 Ok(Self(Arc::new(ProcessHandleInner(handle))))
458 }
459 }
460 }
461
462 impl TryFrom<&Child> for ProcessHandle {
465 type Error = io::Error;
466
467 fn try_from(child: &Child) -> io::Result<Self> {
468 Self::try_from(child.id() as Pid)
469 }
470 }
471
472 impl From<RawHandle> for ProcessHandle {
479 fn from(handle: RawHandle) -> Self {
480 Self(Arc::new(ProcessHandleInner(handle as HANDLE)))
481 }
482 }
483
484 impl CopyAddress for ProcessHandle {
486 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
487 if buf.is_empty() {
488 return Ok(());
489 }
490
491 if unsafe {
492 ReadProcessMemory(
493 self.0 .0,
494 addr as *const c_void,
495 buf.as_mut_ptr().cast(),
496 mem::size_of_val(buf),
497 ptr::null_mut(),
498 )
499 } == 0
500 {
501 Err(io::Error::last_os_error())
502 } else {
503 Ok(())
504 }
505 }
506 }
507}
508
509pub fn copy_address<T>(addr: usize, length: usize, source: &T) -> io::Result<Vec<u8>>
514where
515 T: CopyAddress,
516{
517 log::debug!("copy_address: addr: {:x}", addr);
518
519 let mut copy = vec![0; length];
520
521 source
522 .copy_address(addr, &mut copy)
523 .map_err(|e| {
524 log::warn!("copy_address failed for {:x}: {:?}", addr, e);
525 e
526 })
527 .and(Ok(copy))
528}
529
530#[cfg(test)]
531mod test {
532 use super::*;
533 use std::convert::TryFrom;
534 use std::env;
535 use std::io::{self, BufRead, BufReader};
536 use std::path::PathBuf;
537 use std::process::{Child, Command, Stdio};
538
539 #[allow(unused)]
540 const fn assert_send_sync<T: Send + Sync>() {}
541 const _: () = assert_send_sync::<ProcessHandle>();
542 const _: () = assert_send_sync::<Pid>();
543
544 fn test_process_path() -> Option<PathBuf> {
545 env::current_exe().ok().and_then(|p| {
546 p.parent().map(|p| {
547 p.with_file_name("test")
548 .with_extension(env::consts::EXE_EXTENSION)
549 })
550 })
551 }
552
553 fn read_test_process(args: Option<&[&str]>) -> io::Result<Vec<u8>> {
554 read_test_process_with(args, |child| ProcessHandle::try_from(child.id() as Pid))
555 }
556
557 fn read_test_process_with<F>(args: Option<&[&str]>, make_handle: F) -> io::Result<Vec<u8>>
565 where
566 F: FnOnce(&Child) -> io::Result<ProcessHandle>,
567 {
568 let path = test_process_path().unwrap();
569 let mut cmd = Command::new(&path);
570 {
571 cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
572 }
573 if let Some(a) = args {
574 cmd.args(a);
575 }
576 let mut child = cmd.spawn()?;
577 let handle = make_handle(&child)?;
578 let reader = BufReader::new(child.stdout.take().unwrap());
581 let line = reader.lines().next().unwrap().unwrap();
582 let bits = line.split(' ').collect::<Vec<_>>();
583 let addr = usize::from_str_radix(&bits[0][2..], 16).unwrap();
584 let size = bits[1].parse::<usize>().unwrap();
585 let mem = copy_address(addr, size, &handle)?;
586 drop(handle);
587 child.wait()?;
588 Ok(mem)
589 }
590
591 #[test]
592 fn test_read_small() {
593 let mem = read_test_process(None).unwrap();
594 assert_eq!(mem, (0..32u8).collect::<Vec<u8>>());
595 }
596
597 #[test]
598 fn test_read_large() {
599 const SIZE: usize = 20_000;
602 let arg = format!("{}", SIZE);
603 let mem = read_test_process(Some(&[&arg])).unwrap();
604 let expected = (0..SIZE)
605 .map(|v| (v % (u8::max_value() as usize + 1)) as u8)
606 .collect::<Vec<u8>>();
607 assert_eq!(mem, expected);
608 }
609
610 #[cfg(target_os = "linux")]
611 #[test]
612 fn test_read_fails_on_short_read() {
613 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
614 let mem = unsafe {
615 libc::mmap(
616 std::ptr::null_mut(),
617 2 * page,
618 libc::PROT_READ | libc::PROT_WRITE,
619 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
620 -1,
621 0,
622 )
623 };
624 assert_ne!(mem, libc::MAP_FAILED);
625 assert_eq!(
626 unsafe { libc::mprotect((mem as usize + page) as *mut _, page, libc::PROT_NONE) },
627 0
628 );
629
630 let handle = ProcessHandle::try_from(std::process::id() as Pid).unwrap();
631 let end = mem as usize + page;
632 let mut buf = [0u8; 8];
633 assert!(handle.copy_address(end - 8, &mut buf).is_ok());
634 let err = handle.copy_address(end - 4, &mut buf).unwrap_err();
635 assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof);
636 unsafe { libc::munmap(mem, 2 * page) };
637 }
638
639 #[test]
640 fn test_read_from_child_handle() {
641 let mem = read_test_process_with(None, |child| ProcessHandle::try_from(child)).unwrap();
642 assert_eq!(mem, (0..32u8).collect::<Vec<u8>>());
643 }
644}