remoteprocess/
lib.rs

1//! This crate provides a cross platform way of querying information about other processes running
2//! on the system. This let's you build profiling and debugging tools.
3//!
4//! Features:
5//!
6//! * Getting the process executable name and current working directory
7//! * Listing all the threads in the process
8//! * Suspending the execution of a process or thread
9//! * Returning if a thread is running or not
10//! * Getting a stack trace for a thread in the target process
11//! * Resolve symbols for an address in the other process
12//! * Copy memory from the other process (using the read_process_memory crate)
13//!
14//! This crate provides implementations for Linux, OSX and Windows. However this crate is still
15//! very much in alpha stage, and the following caveats apply:
16//!
17//! * Stack unwinding only works on x86_64 processors right now, and is disabled for arm/x86
18//! * the OSX stack unwinding code is very unstable and shouldn't be relied on
19//! * Getting the cwd on windows returns incorrect results
20//!
21//! # Example
22//!
23//! ```rust,no_run
24//! #[cfg(feature="unwind")]
25//! fn get_backtrace(pid: remoteprocess::Pid) -> Result<(), remoteprocess::Error> {
26//!     // Create a new handle to the process
27//!     let process = remoteprocess::Process::new(pid)?;
28//!     // Create a stack unwind object, and use it to get the stack for each thread
29//!     let unwinder = process.unwinder()?;
30//!     let symbolicator = process.symbolicator()?;
31//!     for thread in process.threads()?.iter() {
32//!         println!("Thread {} - {}", thread.id()?, if thread.active()? { "running" } else { "idle" });
33//!
34//!         // lock the thread to get a consistent snapshot (unwinding will fail otherwise)
35//!         // Note: the thread will appear idle when locked, so we are calling
36//!         // thread.active() before this
37//!         let _lock = thread.lock()?;
38//!
39//!         // Iterate over the callstack for the current thread
40//!         for ip in unwinder.cursor(&thread)? {
41//!             let ip = ip?;
42//!
43//!             // Lookup the current stack frame containing a filename/function/linenumber etc
44//!             // for the current address
45//!             symbolicator.symbolicate(ip, true, &mut |sf| {
46//!                 println!("\t{}", sf);
47//!             })?;
48//!         }
49//!     }
50//!     Ok(())
51//! }
52//! ```
53
54#[cfg(target_os = "macos")]
55mod osx;
56#[cfg(target_os = "macos")]
57pub use osx::*;
58
59#[cfg(target_os = "linux")]
60mod linux;
61#[cfg(target_os = "linux")]
62pub use linux::*;
63
64#[cfg(target_os = "freebsd")]
65mod freebsd;
66#[cfg(target_os = "freebsd")]
67pub use freebsd::*;
68
69#[cfg(target_os = "windows")]
70mod windows;
71#[cfg(target_os = "windows")]
72pub use windows::*;
73
74#[derive(Debug)]
75pub enum Error {
76    NoBinaryForAddress(u64),
77    GoblinError(::goblin::error::Error),
78    IOError(std::io::Error),
79    Other(String),
80    #[cfg(use_libunwind)]
81    LibunwindError(linux::libunwind::Error),
82    #[cfg(target_os = "linux")]
83    NixError(nix::Error),
84}
85
86impl std::fmt::Display for Error {
87    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
88        match *self {
89            Error::NoBinaryForAddress(addr) => {
90                write!(
91                    f,
92                    "No binary found for address 0x{:016x}. Try reloading.",
93                    addr
94                )
95            }
96            Error::GoblinError(ref e) => e.fmt(f),
97            Error::IOError(ref e) => e.fmt(f),
98            Error::Other(ref e) => write!(f, "{}", e),
99            #[cfg(use_libunwind)]
100            Error::LibunwindError(ref e) => e.fmt(f),
101            #[cfg(target_os = "linux")]
102            Error::NixError(ref e) => e.fmt(f),
103        }
104    }
105}
106
107impl std::error::Error for Error {
108    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
109        match *self {
110            Error::GoblinError(ref e) => Some(e),
111            Error::IOError(ref e) => Some(e),
112            #[cfg(use_libunwind)]
113            Error::LibunwindError(ref e) => Some(e),
114            #[cfg(target_os = "linux")]
115            Error::NixError(ref e) => Some(e),
116            _ => None,
117        }
118    }
119}
120
121impl From<goblin::error::Error> for Error {
122    fn from(err: goblin::error::Error) -> Error {
123        Error::GoblinError(err)
124    }
125}
126
127impl From<std::io::Error> for Error {
128    fn from(err: std::io::Error) -> Error {
129        Error::IOError(err)
130    }
131}
132
133#[cfg(target_os = "linux")]
134impl From<nix::Error> for Error {
135    fn from(err: nix::Error) -> Error {
136        Error::NixError(err)
137    }
138}
139
140#[cfg(use_libunwind)]
141impl From<linux::libunwind::Error> for Error {
142    fn from(err: linux::libunwind::Error) -> Error {
143        Error::LibunwindError(err)
144    }
145}
146
147#[derive(Debug, Clone)]
148pub struct StackFrame {
149    pub line: Option<u64>,
150    pub filename: Option<String>,
151    pub function: Option<String>,
152    pub module: String,
153    pub addr: u64,
154}
155
156impl std::fmt::Display for StackFrame {
157    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
158        let function = self.function.as_ref().map(String::as_str).unwrap_or("?");
159        if let Some(filename) = self.filename.as_ref() {
160            write!(
161                f,
162                "0x{:016x} {} ({}:{})",
163                self.addr,
164                function,
165                filename,
166                self.line.unwrap_or(0)
167            )
168        } else {
169            write!(f, "0x{:016x} {} ({})", self.addr, function, self.module)
170        }
171    }
172}
173
174pub trait ProcessMemory {
175    /// Copies memory from another process into an already allocated
176    /// byte buffer
177    fn read(&self, addr: usize, buf: &mut [u8]) -> Result<(), Error>;
178
179    /// Copies a series of bytes from another process. Main difference
180    /// with 'read' is that this will allocate memory for you
181    fn copy(&self, addr: usize, length: usize) -> Result<Vec<u8>, Error> {
182        let mut data = vec![0; length];
183        self.read(addr, &mut data)?;
184        Ok(data)
185    }
186
187    /// Copies a structure from another process
188    fn copy_struct<T: Copy>(&self, addr: usize) -> Result<T, Error> {
189        let mut data = vec![0; std::mem::size_of::<T>()];
190        self.read(addr, &mut data)?;
191        Ok(unsafe { std::ptr::read(data.as_ptr() as *const _) })
192    }
193
194    /// Given a pointer that points to a struct in another process, returns the struct
195    fn copy_pointer<T: Copy>(&self, ptr: *const T) -> Result<T, Error> {
196        self.copy_struct(ptr as usize)
197    }
198
199    /// Copies a series of bytes from another process into a vector of
200    /// structures of type T.
201    fn copy_vec<T: Copy>(&self, addr: usize, length: usize) -> Result<Vec<T>, Error> {
202        let mut vec = self.copy(addr, length * std::mem::size_of::<T>())?;
203        let capacity = vec.capacity() as usize / std::mem::size_of::<T>() as usize;
204        let ptr = vec.as_mut_ptr() as *mut T;
205        std::mem::forget(vec);
206        unsafe { Ok(Vec::from_raw_parts(ptr, capacity, capacity)) }
207    }
208}
209
210#[doc(hidden)]
211/// Mock for using ProcessMemory on the local process.
212pub struct LocalProcess;
213impl ProcessMemory for LocalProcess {
214    fn read(&self, addr: usize, buf: &mut [u8]) -> Result<(), Error> {
215        unsafe {
216            std::ptr::copy_nonoverlapping(addr as *mut u8, buf.as_mut_ptr(), buf.len());
217        }
218        Ok(())
219    }
220}
221
222#[cfg(any(target_os = "linux", target_os = "windows", target_os = "freebsd"))]
223#[doc(hidden)]
224/// Filters pids to own include descendations of target_pid
225fn filter_child_pids(
226    target_pid: Pid,
227    processes: &std::collections::HashMap<Pid, Pid>,
228) -> Vec<(Pid, Pid)> {
229    let mut ret = Vec::new();
230    for (child, parent) in processes.iter() {
231        let mut current = *parent;
232        loop {
233            if current == target_pid {
234                ret.push((*child, *parent));
235                break;
236            }
237            current = match processes.get(&current) {
238                Some(pid) => {
239                    if current == *pid {
240                        break;
241                    }
242                    *pid
243                }
244                None => break,
245            };
246        }
247    }
248    ret
249}
250
251#[cfg(test)]
252pub mod tests {
253    use super::*;
254
255    #[derive(Copy, Clone)]
256    struct Point {
257        x: i32,
258        y: i64,
259    }
260
261    #[test]
262    fn test_copy_pointer() {
263        let original = Point { x: 15, y: 25 };
264        let copy = LocalProcess.copy_pointer(&original).unwrap();
265        assert_eq!(original.x, copy.x);
266        assert_eq!(original.y, copy.y);
267    }
268
269    #[test]
270    fn test_copy_struct() {
271        let original = Point { x: 10, y: 20 };
272        let copy: Point = LocalProcess
273            .copy_struct(&original as *const Point as usize)
274            .unwrap();
275        assert_eq!(original.x, copy.x);
276        assert_eq!(original.y, copy.y);
277    }
278}