proc_maps/
win_maps.rs

1use std;
2use std::ffi::{OsStr, OsString};
3use std::io;
4use std::os::windows::ffi::{OsStrExt, OsStringExt};
5use std::path::{Path, PathBuf};
6use std::ptr::null_mut;
7use winapi::shared::minwindef::{DWORD, FALSE};
8use winapi::um::dbghelp::{
9    SymCleanup, SymFromNameW, SymInitializeW, SymLoadModuleExW, SymUnloadModule64, SYMBOL_INFOW,
10};
11use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE};
12use winapi::um::memoryapi::VirtualQueryEx;
13use winapi::um::processthreadsapi::OpenProcess;
14use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO};
15use winapi::um::tlhelp32::{CreateToolhelp32Snapshot, TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32};
16use winapi::um::tlhelp32::{Module32FirstW, Module32NextW, MODULEENTRY32W};
17use winapi::um::winnt::{HANDLE, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
18use winapi::um::winnt::{MEMORY_BASIC_INFORMATION, MEM_COMMIT, MEM_IMAGE};
19use winapi::um::winnt::{PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE};
20use winapi::um::winnt::{PAGE_EXECUTE_WRITECOPY, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY};
21
22use MapRangeImpl;
23
24pub type Pid = u32;
25
26#[derive(Debug, Clone, PartialEq)]
27pub struct MapRange {
28    base_addr: usize,
29    base_size: usize,
30    pathname: Option<PathBuf>,
31    read: bool,
32    write: bool,
33    exec: bool,
34}
35
36impl MapRangeImpl for MapRange {
37    fn size(&self) -> usize {
38        self.base_size
39    }
40    fn start(&self) -> usize {
41        self.base_addr
42    }
43    fn filename(&self) -> Option<&Path> {
44        self.pathname.as_deref()
45    }
46    fn is_exec(&self) -> bool {
47        self.exec
48    }
49    fn is_write(&self) -> bool {
50        self.write
51    }
52    fn is_read(&self) -> bool {
53        self.read
54    }
55}
56
57pub fn get_process_maps(pid: Pid) -> io::Result<Vec<MapRange>> {
58    let mut modules = get_process_modules(pid)?;
59    modules.sort_by_key(|m| m.base_addr);
60    let page_ranges = get_process_page_ranges(pid)?;
61    let mut maps = Vec::<MapRange>::with_capacity(page_ranges.len());
62    for page_range in page_ranges {
63        maps.push(MapRange {
64            base_addr: page_range.base_addr,
65            base_size: page_range.base_size,
66            pathname: find_pathname(&modules, &page_range),
67            read: page_range.read,
68            write: page_range.write,
69            exec: page_range.exec,
70        });
71    }
72    Ok(maps)
73}
74
75/// Find pathname of module containing this page range, if any.
76/// Assumes that modules are sorted by base address and do not overlap.
77fn find_pathname(modules: &Vec<Module>, page_range: &PageRange) -> Option<PathBuf> {
78    if !page_range.image {
79        return None;
80    }
81
82    // Find module with the same base address or that could contain it.
83    let module: &Module = match modules.binary_search_by_key(&page_range.base_addr, |m| m.base_addr)
84    {
85        Ok(i) => &modules[i],
86        Err(0) => return None,
87        Err(i) => &modules[i - 1],
88    };
89
90    if module.contains(page_range) {
91        Some(module.pathname.clone())
92    } else {
93        None
94    }
95}
96
97/// The memory region where an executable or DLL was loaded.
98struct Module {
99    base_addr: usize,
100    base_size: usize,
101    pathname: PathBuf,
102}
103
104impl Module {
105    fn contains(&self, page_range: &PageRange) -> bool {
106        self.base_addr <= page_range.base_addr
107            && page_range.base_addr + page_range.base_size <= self.base_addr + self.base_size
108    }
109}
110
111/// Uses the Tool Help API to list all modules.
112fn get_process_modules(pid: Pid) -> io::Result<Vec<Module>> {
113    unsafe {
114        let handle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
115        if handle == INVALID_HANDLE_VALUE {
116            return Err(io::Error::last_os_error());
117        }
118
119        let mut module = MODULEENTRY32W {
120            ..Default::default()
121        };
122        module.dwSize = std::mem::size_of_val(&module) as u32;
123
124        let mut success = Module32FirstW(handle, &mut module);
125        if success == 0 {
126            CloseHandle(handle);
127            return Err(io::Error::last_os_error());
128        }
129
130        let mut vec = Vec::new();
131        while success != 0 {
132            vec.push(Module {
133                base_addr: module.modBaseAddr as usize,
134                base_size: module.modBaseSize as usize,
135                pathname: PathBuf::from(wstr_to_string(&module.szExePath)),
136            });
137
138            success = Module32NextW(handle, &mut module);
139        }
140        CloseHandle(handle);
141        Ok(vec)
142    }
143}
144
145/// A range of pages in the virtual address space of a process.
146struct PageRange {
147    base_addr: usize,
148    base_size: usize,
149    image: bool,
150    read: bool,
151    write: bool,
152    exec: bool,
153}
154
155/// Uses `VirtualQueryEx` to get info on *every* memory page range in the process.
156fn get_process_page_ranges(pid: Pid) -> io::Result<Vec<PageRange>> {
157    unsafe {
158        let mut sysinfo = SYSTEM_INFO {
159            ..Default::default()
160        };
161        GetSystemInfo(&mut sysinfo);
162
163        let mut vec = Vec::new();
164
165        let process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid as DWORD);
166        if process == INVALID_HANDLE_VALUE {
167            return Err(io::Error::last_os_error());
168        }
169
170        let mut meminfo = MEMORY_BASIC_INFORMATION {
171            ..Default::default()
172        };
173        let buffer_size = std::mem::size_of_val(&meminfo);
174
175        let mut address = sysinfo.lpMinimumApplicationAddress;
176        while address < sysinfo.lpMaximumApplicationAddress {
177            let bytes_returned = VirtualQueryEx(process, address, &mut meminfo, buffer_size);
178            if bytes_returned != buffer_size {
179                CloseHandle(process);
180                return Err(io::Error::last_os_error());
181            }
182
183            if meminfo.State & MEM_COMMIT != 0 {
184                // Skip free pages and pages that are reserved but not allocated.
185                vec.push(PageRange {
186                    base_addr: meminfo.BaseAddress as usize,
187                    base_size: meminfo.RegionSize as usize,
188                    image: meminfo.Type & MEM_IMAGE != 0,
189                    read: meminfo.Protect
190                        & (PAGE_EXECUTE_READ
191                            | PAGE_EXECUTE_READWRITE
192                            | PAGE_EXECUTE_WRITECOPY
193                            | PAGE_READONLY
194                            | PAGE_READWRITE
195                            | PAGE_WRITECOPY)
196                        != 0,
197                    write: meminfo.Protect & (PAGE_EXECUTE_READWRITE | PAGE_READWRITE) != 0,
198                    exec: meminfo.Protect
199                        & (PAGE_EXECUTE
200                            | PAGE_EXECUTE_READ
201                            | PAGE_EXECUTE_READWRITE
202                            | PAGE_EXECUTE_WRITECOPY)
203                        != 0,
204                });
205            }
206
207            address = (meminfo.BaseAddress as *mut u8)
208                .add(meminfo.RegionSize)
209                .cast(); // as winapi::um::winnt::PVOID;
210        }
211
212        CloseHandle(process);
213        Ok(vec)
214    }
215}
216
217// The rest of this code is utilities for loading windows symbols.
218// This uses the dbghelp win32 api to load the symbols for a process,
219// since just parsing the PE file with goblin isn't sufficient (symbols
220// can be stored in a separate PDB file on Windows)
221pub struct SymbolLoader {
222    pub process: HANDLE,
223}
224
225pub struct SymbolModule<'a> {
226    pub parent: &'a SymbolLoader,
227    pub filename: &'a Path,
228    pub base: u64,
229}
230
231impl SymbolLoader {
232    pub fn new(pid: Pid) -> io::Result<Self> {
233        unsafe {
234            let process = OpenProcess(PROCESS_VM_READ, FALSE, pid as DWORD);
235            if process == INVALID_HANDLE_VALUE {
236                return Err(io::Error::last_os_error());
237            }
238            if SymInitializeW(process, null_mut(), FALSE) == 0 {
239                return Err(io::Error::last_os_error());
240            }
241            Ok(Self { process })
242        }
243    }
244
245    pub fn address_from_name(&self, name: &str) -> io::Result<(u64, u64)> {
246        // Need to allocate extra space for the SYMBOL_INFO structure, otherwise segfaults
247        let size = std::mem::size_of::<SYMBOL_INFOW>() + 256;
248        let buffer = vec![0; size];
249        let info: *mut SYMBOL_INFOW = buffer.as_ptr() as *mut SYMBOL_INFOW;
250        unsafe {
251            (*info).SizeOfStruct = size as u32;
252            if SymFromNameW(self.process, string_to_wstr(name).as_ptr(), info) == 0 {
253                return Err(std::io::Error::last_os_error());
254            }
255            Ok(((*info).ModBase, (*info).Address))
256        }
257    }
258
259    /// Loads symbols for filename, returns a SymbolModule structure that must be kept alive
260    pub fn load_module<'a>(&'a self, filename: &'a Path) -> io::Result<SymbolModule<'a>> {
261        unsafe {
262            let base = SymLoadModuleExW(
263                self.process,
264                null_mut(),
265                path_to_wstr(filename).as_ptr(),
266                null_mut(),
267                0,
268                0,
269                null_mut(),
270                0,
271            );
272            if base == 0 {
273                return Err(std::io::Error::last_os_error());
274            }
275            Ok(SymbolModule {
276                parent: self,
277                filename,
278                base,
279            })
280        }
281    }
282}
283
284impl Drop for SymbolLoader {
285    fn drop(&mut self) {
286        unsafe {
287            SymCleanup(self.process);
288            CloseHandle(self.process);
289        }
290    }
291}
292
293impl<'a> Drop for SymbolModule<'a> {
294    fn drop(&mut self) {
295        unsafe {
296            SymUnloadModule64(self.parent.process, self.base);
297        }
298    }
299}
300
301fn wstr_to_string(full: &[u16]) -> OsString {
302    let len = full.iter().position(|&x| x == 0).unwrap_or(full.len());
303    OsString::from_wide(&full[..len])
304}
305
306fn string_to_wstr(val: &str) -> Vec<u16> {
307    OsStr::new(val)
308        .encode_wide()
309        .chain(std::iter::once(0))
310        .collect()
311}
312
313fn path_to_wstr(val: &Path) -> Vec<u16> {
314    OsStr::new(val)
315        .encode_wide()
316        .chain(std::iter::once(0))
317        .collect()
318}