procmem_linux/
library.rs

1use std::{num::ParseIntError, str::FromStr};
2
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum ParsePermError {
7    #[error("permissions string must be 4 characters long, got {0}")]
8    InvalidLength(String),
9    #[error("invalid character \"{0}\" at position {1}")]
10    InvalidChar(char, usize),
11}
12
13#[derive(Debug, Clone)]
14pub struct Permissions {
15    pub read: bool,
16    pub write: bool,
17    pub execute: bool,
18    pub private: bool,
19}
20
21impl FromStr for Permissions {
22    type Err = ParsePermError;
23
24    fn from_str(s: &str) -> Result<Self, Self::Err> {
25        if s.len() != 4 {
26            return Err(ParsePermError::InvalidLength(s.to_string()));
27        }
28
29        let bytes = s.as_bytes();
30
31        let read = match bytes[0] {
32            b'r' => true,
33            b'-' => false,
34            other => return Err(ParsePermError::InvalidChar(other as char, 0)),
35        };
36        let write = match bytes[1] {
37            b'w' => true,
38            b'-' => false,
39            other => return Err(ParsePermError::InvalidChar(other as char, 1)),
40        };
41        let execute = match bytes[2] {
42            b'x' => true,
43            b'-' => false,
44            other => return Err(ParsePermError::InvalidChar(other as char, 2)),
45        };
46        let private = match bytes[3] {
47            b'p' => true,
48            b's' => false,
49            other => return Err(ParsePermError::InvalidChar(other as char, 3)),
50        };
51
52        Ok(Permissions {
53            read,
54            write,
55            execute,
56            private,
57        })
58    }
59}
60
61#[derive(Error, Debug)]
62pub enum ParseLibraryError {
63    #[error("expected at least 6 whitespace-separated fields, got {0}")]
64    FieldCount(usize),
65    #[error("address field was invalid")]
66    InvalidAddress,
67    #[error("permissions field was invalid")]
68    InvalidPermissions(#[from] ParsePermError),
69    #[error("offset field was invalid")]
70    InvalidOffset(ParseIntError),
71    #[error("inode field was invalid")]
72    InvalidInode(ParseIntError),
73}
74
75#[derive(Debug, Clone)]
76pub struct LibraryInfo {
77    /// memory region start address
78    start: usize,
79    /// memory region end address
80    end: usize,
81    /// permissions of memory region
82    permissions: Permissions,
83    /// offset into mapped file
84    offset: usize,
85    /// device, in format `major:minor`
86    device: String,
87    inode: u64,
88    /// can be a file path, special name like `[heap]`, or anonymous
89    ///
90    /// special names could be:
91    ///
92    /// - `[heap]`: process heap region
93    /// - `[stack]`: the main threads' stack
94    /// - `[vdso]`: virtual dynamic shared object
95    /// - `[vsyscall]`: legacy syscall page
96    /// - `[vvar]`: variable data page for vdso
97    pathname: Option<String>,
98}
99
100impl LibraryInfo {
101    pub fn start(&self) -> usize {
102        self.start
103    }
104
105    pub fn end(&self) -> usize {
106        self.end
107    }
108
109    pub fn permissions(&self) -> &Permissions {
110        &self.permissions
111    }
112
113    pub fn offset(&self) -> usize {
114        self.offset
115    }
116
117    pub fn device(&self) -> &str {
118        &self.device
119    }
120
121    pub fn inode(&self) -> u64 {
122        self.inode
123    }
124
125    pub fn path(&self) -> Option<&str> {
126        self.pathname.as_deref()
127    }
128}
129
130impl FromStr for LibraryInfo {
131    type Err = ParseLibraryError;
132
133    fn from_str(s: &str) -> Result<Self, Self::Err> {
134        // split into 6 fields: address range,
135        // permissions, offset, device, inode and pathname
136        let mut parts = s.splitn(6, char::is_whitespace).filter(|s| !s.is_empty());
137
138        let address = parts.next().ok_or(ParseLibraryError::FieldCount(0))?;
139        let permissions = parts.next().ok_or(ParseLibraryError::FieldCount(1))?;
140        let offset = parts.next().ok_or(ParseLibraryError::FieldCount(2))?;
141        let device = parts.next().ok_or(ParseLibraryError::FieldCount(3))?;
142        let inode = parts.next().ok_or(ParseLibraryError::FieldCount(4))?;
143        let path = parts.next();
144
145        let Some((start, end)) = address.split_once('-') else {
146            return Err(ParseLibraryError::InvalidAddress);
147        };
148
149        let start =
150            usize::from_str_radix(start, 16).map_err(|_| ParseLibraryError::InvalidAddress)?;
151        let end = usize::from_str_radix(end, 16).map_err(|_| ParseLibraryError::InvalidAddress)?;
152
153        let permissions = permissions.parse()?;
154
155        let offset = usize::from_str_radix(offset, 16).map_err(ParseLibraryError::InvalidOffset)?;
156        let device = device.to_string();
157        let inode = inode.parse().map_err(ParseLibraryError::InvalidInode)?;
158        let pathname = path
159            .map(str::trim)
160            .filter(|s| !s.is_empty())
161            .map(str::to_string);
162
163        Ok(Self {
164            start,
165            end,
166            permissions,
167            offset,
168            device,
169            inode,
170            pathname,
171        })
172    }
173}