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 {
123 Ok(())
124 }
125 }
126 }
127}
128
129#[cfg(target_os = "macos")]
130mod platform {
131 use libc::{c_int, pid_t};
132 use mach::kern_return::{kern_return_t, KERN_SUCCESS};
133 use mach::port::{mach_port_name_t, mach_port_t, MACH_PORT_NULL};
134 use mach::vm_types::{mach_vm_address_t, mach_vm_size_t};
135
136 use std::convert::TryFrom;
137 use std::io;
138 use std::process::Child;
139
140 use super::CopyAddress;
141
142 #[allow(non_camel_case_types)]
143 type vm_map_t = mach_port_t;
144 #[allow(non_camel_case_types)]
145 type vm_address_t = mach_vm_address_t;
146 #[allow(non_camel_case_types)]
147 type vm_size_t = mach_vm_size_t;
148
149 pub type Pid = pid_t;
151 #[derive(Clone)]
153 pub struct ProcessHandle(mach_port_name_t);
154
155 extern "C" {
156 fn vm_read_overwrite(
157 target_task: vm_map_t,
158 address: vm_address_t,
159 size: vm_size_t,
160 data: vm_address_t,
161 out_size: *mut vm_size_t,
162 ) -> kern_return_t;
163 }
164
165 fn task_for_pid(pid: Pid) -> io::Result<mach_port_name_t> {
168 if pid == unsafe { libc::getpid() } as Pid {
169 return Ok(unsafe { mach::traps::mach_task_self() });
170 }
171
172 let mut task: mach_port_name_t = MACH_PORT_NULL;
173
174 unsafe {
175 let result =
176 mach::traps::task_for_pid(mach::traps::mach_task_self(), pid as c_int, &mut task);
177 if result != KERN_SUCCESS {
178 return Err(io::Error::last_os_error());
179 }
180 }
181
182 Ok(task)
183 }
184
185 impl TryFrom<Pid> for ProcessHandle {
187 type Error = io::Error;
188
189 fn try_from(pid: Pid) -> io::Result<Self> {
190 Ok(Self(task_for_pid(pid)?))
191 }
192 }
193
194 impl TryFrom<mach_port_name_t> for ProcessHandle {
196 type Error = io::Error;
197
198 fn try_from(mach_port_name: mach_port_name_t) -> io::Result<Self> {
199 Ok(Self(mach_port_name))
200 }
201 }
202
203 impl TryFrom<&Child> for ProcessHandle {
215 type Error = io::Error;
216
217 fn try_from(child: &Child) -> io::Result<Self> {
218 Self::try_from(child.id() as Pid)
219 }
220 }
221
222 impl CopyAddress for ProcessHandle {
224 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
225 let mut read_len = buf.len() as vm_size_t;
226 let result = unsafe {
227 vm_read_overwrite(
228 self.0,
229 addr as vm_address_t,
230 buf.len() as vm_size_t,
231 buf.as_mut_ptr() as vm_address_t,
232 &mut read_len,
233 )
234 };
235
236 if read_len != buf.len() as vm_size_t {
237 return Err(io::Error::new(
238 io::ErrorKind::Other,
239 format!(
240 "Mismatched read sizes for `vm_read` (expected {}, got {})",
241 buf.len(),
242 read_len
243 ),
244 ));
245 }
246
247 if result != KERN_SUCCESS {
248 return Err(io::Error::last_os_error());
249 }
250 Ok(())
251 }
252 }
253}
254
255#[cfg(target_os = "freebsd")]
256mod platform {
257 use libc::{c_int, c_void, pid_t};
258 use libc::{waitpid, EBUSY, PIOD_READ_D, PT_ATTACH, PT_DETACH, PT_IO, WIFSTOPPED};
259 use std::convert::TryFrom;
260 use std::process::Child;
261 use std::{io, ptr};
262
263 use super::CopyAddress;
264
265 pub type Pid = pid_t;
267 #[derive(Clone)]
269 pub struct ProcessHandle(Pid);
270
271 #[repr(C)]
272 struct PtraceIoDesc {
273 piod_op: c_int,
274 piod_offs: *mut c_void,
275 piod_addr: *mut c_void,
276 piod_len: usize,
277 }
278
279 #[derive(PartialEq)]
285 enum PtraceLockState {
286 Release,
287 NoRelease,
288 }
289
290 extern "C" {
291 fn ptrace(request: c_int, pid: pid_t, io_desc: *const PtraceIoDesc, data: c_int) -> c_int;
294 }
295
296 impl TryFrom<Pid> for ProcessHandle {
298 type Error = io::Error;
299
300 fn try_from(pid: Pid) -> io::Result<Self> {
301 Ok(Self(pid))
302 }
303 }
304
305 impl TryFrom<&Child> for ProcessHandle {
307 type Error = io::Error;
308
309 fn try_from(child: &Child) -> io::Result<Self> {
310 Self::try_from(child.id() as Pid)
311 }
312 }
313
314 fn ptrace_attach(pid: Pid) -> io::Result<PtraceLockState> {
316 let attach_status = unsafe { ptrace(PT_ATTACH, pid, ptr::null_mut(), 0) };
317
318 let last_error = io::Error::last_os_error();
319
320 if let Some(error) = last_error.raw_os_error() {
321 if attach_status == -1 {
322 return match error {
323 EBUSY => Ok(PtraceLockState::NoRelease),
324 _ => Err(last_error),
325 };
326 }
327 }
328
329 let mut wait_status = 0;
330
331 let stopped = unsafe {
332 waitpid(pid, &mut wait_status as *mut _, 0);
333 WIFSTOPPED(wait_status)
334 };
335
336 if !stopped {
337 Err(io::Error::last_os_error())
338 } else {
339 Ok(PtraceLockState::Release)
340 }
341 }
342
343 fn ptrace_io(pid: Pid, addr: usize, buf: &mut [u8]) -> io::Result<()> {
345 let ptrace_io_desc = PtraceIoDesc {
346 piod_op: PIOD_READ_D,
347 piod_offs: addr as *mut c_void,
348 piod_addr: buf.as_mut_ptr() as *mut c_void,
349 piod_len: buf.len(),
350 };
351
352 let result = unsafe { ptrace(PT_IO, pid, &ptrace_io_desc as *const _, 0) };
353
354 if result == -1 {
355 Err(io::Error::last_os_error())
356 } else {
357 Ok(())
358 }
359 }
360
361 fn ptrace_detach(pid: Pid) -> io::Result<()> {
363 let detach_status = unsafe { ptrace(PT_DETACH, pid, ptr::null_mut(), 0) };
364
365 if detach_status == -1 {
366 Err(io::Error::last_os_error())
367 } else {
368 Ok(())
369 }
370 }
371
372 impl CopyAddress for ProcessHandle {
373 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
374 let should_detach = ptrace_attach(self.0)? == PtraceLockState::Release;
375
376 let result = ptrace_io(self.0, addr, buf);
377 if should_detach {
378 ptrace_detach(self.0)?
379 }
380 result
381 }
382 }
383}
384
385#[cfg(windows)]
386mod platform {
387 use std::convert::TryFrom;
388 use std::io;
389 use std::mem;
390 use std::ops::Deref;
391 use std::os::windows::io::{AsRawHandle, RawHandle};
392 use std::process::Child;
393 use std::ptr;
394 use std::sync::Arc;
395 use winapi::{
396 shared::{basetsd, minwindef},
397 um::{handleapi, memoryapi, processthreadsapi, winnt},
398 };
399
400 use super::CopyAddress;
401
402 pub type Pid = minwindef::DWORD;
404 #[derive(Eq, PartialEq, Hash)]
405 struct ProcessHandleInner(RawHandle);
406 #[derive(Clone, Eq, PartialEq, Hash)]
408 pub struct ProcessHandle(Arc<ProcessHandleInner>);
409
410 impl Deref for ProcessHandle {
411 type Target = RawHandle;
412
413 fn deref(&self) -> &Self::Target {
414 &self.0 .0
415 }
416 }
417
418 impl Drop for ProcessHandleInner {
419 fn drop(&mut self) {
420 unsafe { handleapi::CloseHandle(self.0) };
421 }
422 }
423
424 impl TryFrom<Pid> for ProcessHandle {
426 type Error = io::Error;
427
428 fn try_from(pid: Pid) -> io::Result<Self> {
429 let handle = unsafe { processthreadsapi::OpenProcess(winnt::PROCESS_VM_READ, 0, pid) };
430 if handle == (0 as RawHandle) {
431 Err(io::Error::last_os_error())
432 } else {
433 Ok(Self(Arc::new(ProcessHandleInner(handle))))
434 }
435 }
436 }
437
438 impl TryFrom<&Child> for ProcessHandle {
440 type Error = io::Error;
441
442 fn try_from(child: &Child) -> io::Result<Self> {
443 Ok(Self(Arc::new(ProcessHandleInner(child.as_raw_handle()))))
444 }
445 }
446
447 impl From<RawHandle> for ProcessHandle {
448 fn from(handle: RawHandle) -> Self {
449 return Self(Arc::new(ProcessHandleInner(handle)));
450 }
451 }
452
453 impl CopyAddress for ProcessHandle {
455 fn copy_address(&self, addr: usize, buf: &mut [u8]) -> io::Result<()> {
456 if buf.len() == 0 {
457 return Ok(());
458 }
459
460 if unsafe {
461 memoryapi::ReadProcessMemory(
462 self.0 .0,
463 addr as minwindef::LPVOID,
464 buf.as_mut_ptr() as minwindef::LPVOID,
465 mem::size_of_val(buf) as basetsd::SIZE_T,
466 ptr::null_mut(),
467 )
468 } == 0
469 {
470 Err(io::Error::last_os_error())
471 } else {
472 Ok(())
473 }
474 }
475 }
476}
477
478pub fn copy_address<T>(addr: usize, length: usize, source: &T) -> io::Result<Vec<u8>>
483where
484 T: CopyAddress,
485{
486 log::debug!("copy_address: addr: {:x}", addr);
487
488 let mut copy = vec![0; length];
489
490 source
491 .copy_address(addr, &mut copy)
492 .map_err(|e| {
493 log::warn!("copy_address failed for {:x}: {:?}", addr, e);
494 e
495 })
496 .and(Ok(copy))
497}
498
499#[cfg(test)]
500mod test {
501 use super::*;
502 use std::convert::TryFrom;
503 use std::env;
504 use std::io::{self, BufRead, BufReader};
505 use std::path::PathBuf;
506 use std::process::{Child, Command, Stdio};
507
508 fn test_process_path() -> Option<PathBuf> {
509 env::current_exe().ok().and_then(|p| {
510 p.parent().map(|p| {
511 p.with_file_name("test")
512 .with_extension(env::consts::EXE_EXTENSION)
513 })
514 })
515 }
516
517 fn spawn_with_handle(cmd: &mut Command) -> io::Result<(Child, ProcessHandle)> {
518 let child = cmd.spawn()?;
519 let handle = ProcessHandle::try_from(child.id() as Pid)?;
520 Ok((child, handle))
521 }
522
523 fn read_test_process(args: Option<&[&str]>) -> io::Result<Vec<u8>> {
524 let path = test_process_path().unwrap();
526 let mut cmd = Command::new(&path);
527 {
528 cmd.stdin(Stdio::piped()).stdout(Stdio::piped());
529 }
530 if let Some(a) = args {
531 cmd.args(a);
532 }
533 let (mut child, handle) = spawn_with_handle(&mut cmd)?;
534 let reader = BufReader::new(child.stdout.take().unwrap());
537 let line = reader.lines().next().unwrap().unwrap();
538 let bits = line.split(' ').collect::<Vec<_>>();
539 let addr = usize::from_str_radix(&bits[0][2..], 16).unwrap();
540 let size = bits[1].parse::<usize>().unwrap();
541 let mem = copy_address(addr, size, &handle)?;
542 child.wait()?;
543 Ok(mem)
544 }
545
546 #[test]
547 fn test_read_small() {
548 let mem = read_test_process(None).unwrap();
549 assert_eq!(mem, (0..32u8).collect::<Vec<u8>>());
550 }
551
552 #[test]
553 fn test_read_large() {
554 const SIZE: usize = 20_000;
557 let arg = format!("{}", SIZE);
558 let mem = read_test_process(Some(&[&arg])).unwrap();
559 let expected = (0..SIZE)
560 .map(|v| (v % (u8::max_value() as usize + 1)) as u8)
561 .collect::<Vec<u8>>();
562 assert_eq!(mem, expected);
563 }
564}