Skip to main content

libprocmem/
lib.rs

1//! A memory reading library for reading data from `/proc/<pid>/mem` with
2//! memory layout awareness from `/proc/<pid>/maps`
3
4#![feature(new_uninit)]
5
6#[cfg(not(target_os = "linux"))]
7compile_error!("libprocmem only supported for Linux targets");
8
9use std::fs::File;
10use std::io::{Seek, SeekFrom, Read};
11use std::mem::MaybeUninit;
12
13/// Wrapper type around `Result`
14pub type Result<T> = std::result::Result<T, Error>;
15
16/// Errors for this module
17pub enum Error {
18    /// Failed to open `/proc/<pid>/mem`
19    OpenMem(usize, std::io::Error),
20
21    /// Failed to read `/proc/<pid>/mem`
22    ReadMem(usize, std::io::Error),
23
24    /// Failed to read `/proc/<pid>/maps`
25    ReadMaps(usize, std::io::Error),
26
27    /// Failed to convert read string to UTF-8
28    InvalidString(usize, std::string::FromUtf8Error),
29
30    /// Failed to parse `/proc/<pid>/maps`
31    ParseMaps(usize),
32}
33
34impl std::fmt::Debug for Error {
35    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
36        match self {
37            Error::OpenMem(pid, e) =>
38                write!(f, "Failed to open memory for pid {pid}: {e}")?,
39            Error::ReadMem(pid, e) =>
40                write!(f, "Failed to read memory for pid {pid}: {e}")?,
41            Error::ReadMaps(pid, e) =>
42                write!(f, "Failed to read maps for pid {pid}: {e}")?,
43            Error::InvalidString(pid, e) =>
44                write!(f, "Failed to convert bytes to UTF-8 data \
45                          for pid {pid}: {e}")?,
46            Error::ParseMaps(pid) => 
47                write!(f, "Failed to parse maps for pid {pid}")?,
48        }
49
50        Ok(())
51    }
52}
53
54/// An instance of the memory reader
55pub struct Memory {
56    /// PID we attached to
57    pid: usize,
58
59    /// Wrapper around the readable memory for the process
60    mem: File,
61}
62
63/// Info about a memory mapping
64pub struct Mapping {
65    /// Start address of the mapping
66    pub base: u64,
67
68    /// End address of the mapping, such that `end - base` is the number of
69    /// bytes in the mapping
70    pub end: u64,
71
72    /// Memory is readable
73    pub r: bool,
74
75    /// Memory is writable
76    pub w: bool,
77
78    /// Memory is executable
79    pub x: bool,
80}
81
82impl Memory {
83    /// Create a [`Memory`] instance from a process with a running PID
84    pub fn pid(pid: usize) -> Result<Self> {
85        // Open the memory mapping
86        let mem = File::open(format!("/proc/{}/mem", pid))
87            .map_err(|x| Error::OpenMem(pid, x))?;
88
89        Ok(Self {
90            pid,
91            mem,
92        })
93    }
94
95    /// Read a null terminated string at a given address, doesn't include the
96    /// null-terminator in the returned string as it's converted to a Rust
97    /// string
98    pub fn read_nul_string(&mut self, addr: u64) -> Result<String> {
99        // Create storage for the resut
100        let mut bytes = Vec::new();
101
102        // Read one byte at a time. Slow, but you're working with strings, you
103        // don't care about perf anyways
104        for addr in addr.. {
105            let byte = self.read::<u8>(addr)?;
106            if byte == 0 {
107                break;
108            }
109
110            bytes.push(byte);
111        }
112
113        // Convert the string
114        Ok(String::from_utf8(bytes).map_err(|x| {
115            Error::InvalidString(self.pid, x)
116        })?)
117    }
118
119    /// Query the address space information from `/proc/<pid>/maps`
120    pub fn query_address_space(&mut self) -> Result<Vec<Mapping>> {
121        // Output
122        let mut ret = Vec::new();
123
124        // Read the memory map
125        let maps = std::fs::read_to_string(
126            format!("/proc/{}/maps", self.pid))
127            .map_err(|x| Error::ReadMaps(self.pid, x))?;
128
129        for line in maps.lines() {
130            // Split the main line by spaces
131            let mut spl = line.split(" ");
132            let addr_range = spl.next()
133                .ok_or(Error::ParseMaps(self.pid))?;
134            let perms = spl.next()
135                .ok_or(Error::ParseMaps(self.pid))?;
136
137            // Parse addresses
138            let mut spl = addr_range.split("-");
139            let base = u64::from_str_radix(spl.next()
140                .ok_or(Error::ParseMaps(self.pid))?, 16)
141                .map_err(|_| Error::ParseMaps(self.pid))?;
142            let end = u64::from_str_radix(spl.next()
143                .ok_or(Error::ParseMaps(self.pid))?, 16)
144                .map_err(|_| Error::ParseMaps(self.pid))?;
145
146            // Parse permissions
147            let r = perms.get(0..1) == Some("r");
148            let w = perms.get(1..2) == Some("w");
149            let x = perms.get(2..3) == Some("x");
150
151            // Log the memory
152            ret.push(Mapping { base, end, r, w, x });
153        }
154
155        Ok(ret)
156    }
157
158    /// Read a `T` from memory at `addr`
159    pub fn read<T: Pod>(&mut self, addr: u64) -> Result<T> {
160        // Create return value
161        let mut ret: MaybeUninit<T> = MaybeUninit::uninit();
162
163        // Seek to the location to read
164        self.mem.seek(SeekFrom::Start(addr))
165            .map_err(|x| Error::ReadMem(self.pid, x))?;
166
167        // Attempt to read the memory
168        let ptr = unsafe {
169            core::slice::from_raw_parts_mut(
170                ret.as_mut_ptr() as *mut u8, core::mem::size_of_val(&ret))
171        };
172        self.mem.read_exact(ptr)
173            .map_err(|x| Error::ReadMem(self.pid, x))?;
174
175        Ok(unsafe { ret.assume_init() })
176    }
177
178    /// Read a `[T]` from memory at `addr` containing `elements`
179    pub fn read_slice<T: Pod>(&mut self, addr: u64, elements: usize)
180            -> Result<Box<[T]>> {
181        // Create return value
182        let mut ret: Box<[MaybeUninit<T>]> = Box::new_uninit_slice(elements);
183
184        // Seek to the location to read
185        self.mem.seek(SeekFrom::Start(addr))
186            .map_err(|x| Error::ReadMem(self.pid, x))?;
187
188        // Attempt to read the memory
189        let ptr = unsafe {
190            core::slice::from_raw_parts_mut(
191                ret.as_mut_ptr() as *mut u8, core::mem::size_of_val(&*ret))
192        };
193        self.mem.read_exact(ptr)
194            .map_err(|x| Error::ReadMem(self.pid, x))?;
195
196        Ok(unsafe { ret.assume_init() })
197    }
198}
199
200/// Marker trait for plain-old-data which can be copied directly between other
201/// Pod types by copying the underlying bytes
202///
203/// This is only safe to implement on repr-C structures with no padding bytes
204/// and consisting only of fields which are also `Pod`
205pub unsafe trait Pod: Copy + Sync + Send + 'static {}
206
207unsafe impl Pod for i8    {}
208unsafe impl Pod for i16   {}
209unsafe impl Pod for i32   {}
210unsafe impl Pod for i64   {}
211unsafe impl Pod for i128  {}
212unsafe impl Pod for isize {}
213unsafe impl Pod for u8    {}
214unsafe impl Pod for u16   {}
215unsafe impl Pod for u32   {}
216unsafe impl Pod for u64   {}
217unsafe impl Pod for u128  {}
218unsafe impl Pod for usize {}
219unsafe impl Pod for f32   {}
220unsafe impl Pod for f64   {}
221
222unsafe impl<const N: usize> Pod for [i8;    N] {}
223unsafe impl<const N: usize> Pod for [i16;   N] {}
224unsafe impl<const N: usize> Pod for [i32;   N] {}
225unsafe impl<const N: usize> Pod for [i64;   N] {}
226unsafe impl<const N: usize> Pod for [i128;  N] {}
227unsafe impl<const N: usize> Pod for [isize; N] {}
228unsafe impl<const N: usize> Pod for [u8;    N] {}
229unsafe impl<const N: usize> Pod for [u16;   N] {}
230unsafe impl<const N: usize> Pod for [u32;   N] {}
231unsafe impl<const N: usize> Pod for [u64;   N] {}
232unsafe impl<const N: usize> Pod for [u128;  N] {}
233unsafe impl<const N: usize> Pod for [usize; N] {}
234unsafe impl<const N: usize> Pod for [f32;   N] {}
235unsafe impl<const N: usize> Pod for [f64;   N] {}
236