kdmp_parser/map.rs
1// Axel '0vercl0k' Souchet - July 18 2023
2//! This implements logic that allows to memory map a file on both
3//! Unix and Windows (cf [`memory_map_file`] / [`unmap_memory_mapped_file`]).
4use std::fmt::Debug;
5use std::io::{Read, Seek};
6use std::path::Path;
7use std::{fs, io, ptr, slice};
8
9pub trait Reader: Read + Seek {}
10
11impl<T> Reader for T where T: Read + Seek {}
12
13/// A memory mapped file reader is basically a slice of bytes over the memory
14/// mapping and a cursor to be able to access the region.
15pub struct MappedFileReader<'map> {
16 mapped_file: &'map [u8],
17 cursor: io::Cursor<&'map [u8]>,
18}
19
20impl Debug for MappedFileReader<'_> {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 f.debug_struct("MappedFileReader").finish()
23 }
24}
25
26impl MappedFileReader<'_> {
27 /// Create a new [`MappedFileReader`] from a path using a memory map.
28 pub fn new(path: impl AsRef<Path>) -> io::Result<Self> {
29 // Open the file..
30 let file = fs::File::open(path)?;
31
32 // ..and memory map it using the underlying OS-provided APIs.
33 let mapped_file = memory_map_file(file)?;
34
35 Ok(Self {
36 mapped_file,
37 cursor: io::Cursor::new(mapped_file),
38 })
39 }
40}
41
42impl Read for MappedFileReader<'_> {
43 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
44 self.cursor.read(buf)
45 }
46}
47
48impl Seek for MappedFileReader<'_> {
49 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
50 self.cursor.seek(pos)
51 }
52}
53
54/// Drop the [`MappedFileReader`]. In the case we memory mapped the file, we
55/// need to drop the mapping using OS-provided APIs.
56impl Drop for MappedFileReader<'_> {
57 fn drop(&mut self) {
58 unmap_memory_mapped_file(self.mapped_file).expect("failed to unmap")
59 }
60}
61
62#[cfg(windows)]
63#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
64/// Module that implements memory mapping on Windows using CreateFileMappingA /
65/// MapViewOfFile.
66mod windows {
67 use std::os::windows::prelude::AsRawHandle;
68 use std::os::windows::raw::HANDLE;
69
70 use super::*;
71
72 const PAGE_READONLY: DWORD = 2;
73 const FILE_MAP_READ: DWORD = 4;
74
75 type DWORD = u32;
76 type BOOL = u32;
77 type SIZE_T = usize;
78 type LPCSTR = *mut u8;
79 type LPVOID = *const u8;
80
81 unsafe extern "system" {
82 /// Creates or opens a named or unnamed file mapping object for a
83 /// specified file.
84 ///
85 /// <https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga>
86 fn CreateFileMappingA(
87 h: HANDLE,
88 file_mapping_attrs: *const u8,
89 protect: DWORD,
90 max_size_high: DWORD,
91 max_size_low: DWORD,
92 name: LPCSTR,
93 ) -> HANDLE;
94
95 /// Maps a view of a file mapping into the address space of a calling
96 /// process.
97 ///
98 /// <https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile>
99 fn MapViewOfFile(
100 file_mapping_object: HANDLE,
101 desired_access: DWORD,
102 file_offset_high: DWORD,
103 file_offset_low: DWORD,
104 number_of_bytes_to_map: SIZE_T,
105 ) -> LPVOID;
106
107 /// Closes an open object handle.
108 ///
109 /// <https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle>
110 fn CloseHandle(h: HANDLE) -> BOOL;
111
112 /// Unmaps a mapped view of a file from the calling process's address
113 /// space.
114 ///
115 /// <https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile>
116 fn UnmapViewOfFile(base_address: LPVOID) -> BOOL;
117 }
118
119 /// Memory map a file into memory.
120 pub fn memory_map_file<'map>(file: fs::File) -> Result<&'map [u8], io::Error> {
121 // Grab the underlying HANDLE.
122 let file_handle = file.as_raw_handle();
123
124 // Create the mapping.
125 let mapping_handle = unsafe {
126 CreateFileMappingA(
127 file_handle,
128 ptr::null_mut(),
129 PAGE_READONLY,
130 0,
131 0,
132 ptr::null_mut(),
133 )
134 };
135
136 // If the mapping is NULL, it failed so let's bail.
137 if mapping_handle.is_null() {
138 return Err(io::Error::last_os_error());
139 }
140
141 // Grab the size of the underlying file, this will be the size of the
142 // view.
143 let size = file.metadata()?.len().try_into().unwrap();
144
145 // Map the view in the address space.
146 let base = unsafe { MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, size) };
147
148 // If the base address is NULL, it failed so let's bail.
149 if base.is_null() {
150 // Don't forget to close the HANDLE we created for the mapping.
151 unsafe {
152 CloseHandle(mapping_handle);
153 }
154 return Err(io::Error::last_os_error());
155 }
156
157 // Now we materialized a view in the address space, we can get rid of
158 // the mapping handle.
159 unsafe {
160 CloseHandle(mapping_handle);
161 }
162
163 // Make sure the size is not bigger than what [`slice::from_raw_parts`] wants.
164 if size > isize::MAX.try_into().unwrap() {
165 panic!("slice is too large");
166 }
167
168 // Create the slice over the mapping.
169 // SAFETY: This is safe because:
170 // - It is a byte slice, so we don't need to care about the alignment.
171 // - The base is not NULL as we've verified that it is the case above.
172 // - The underlying is owned by the type and the lifetime.
173 // - We asked the OS to map `size` bytes, so we have a guarantee that there's
174 // `size` consecutive bytes.
175 // - We never give a mutable reference to this slice, so it can't get mutated.
176 // - The total len of the slice is guaranteed to be smaller than
177 // [`isize::MAX`].
178 // - The underlying mapping, the type and the slice have the same lifetime
179 // which guarantees that we can't access the underlying once it has been
180 // unmapped (use-after-unmap).
181 Ok(unsafe { slice::from_raw_parts(base, size) })
182 }
183
184 /// Unmap the memory mapped file.
185 pub fn unmap_memory_mapped_file(data: &[u8]) -> Result<(), io::Error> {
186 match unsafe { UnmapViewOfFile(data.as_ptr()) } {
187 0 => Err(io::Error::last_os_error()),
188 _ => Ok(()),
189 }
190 }
191}
192
193#[cfg(windows)]
194use windows::*;
195
196#[cfg(unix)]
197/// Module that implements memory mapping on Unix using the mmap syscall.
198mod unix {
199 use std::os::fd::AsRawFd;
200
201 use super::*;
202
203 const PROT_READ: i32 = 1;
204 const MAP_SHARED: i32 = 1;
205 const MAP_FAILED: *const u8 = usize::MAX as _;
206
207 unsafe extern "system" {
208 fn mmap(
209 addr: *const u8,
210 length: usize,
211 prot: i32,
212 flags: i32,
213 fd: i32,
214 offset: i32,
215 ) -> *const u8;
216
217 fn munmap(addr: *const u8, length: usize) -> i32;
218 }
219
220 pub fn memory_map_file<'map>(file: fs::File) -> Result<&'map [u8], io::Error> {
221 // Grab the underlying file descriptor.
222 let file_fd = file.as_raw_fd();
223
224 // Grab the size of the underlying file. This will be the size of the
225 // memory mapped region.
226 let size = file.metadata()?.len().try_into().unwrap();
227
228 // Mmap the file.
229 let ret = unsafe { mmap(ptr::null_mut(), size, PROT_READ, MAP_SHARED, file_fd, 0) };
230
231 // If the system call failed, bail.
232 if ret == MAP_FAILED {
233 return Err(io::Error::last_os_error());
234 }
235
236 // Make sure the size is not bigger than what [`slice::from_raw_parts`] wants.
237 if size > isize::MAX.try_into().unwrap() {
238 panic!("slice is too large");
239 }
240
241 // Create the slice over the mapping.
242 // SAFETY: This is safe because:
243 // - It is a byte slice, so we don't need to care about the alignment.
244 // - The base is not NULL as we've verified that it is the case above.
245 // - The underlying is owned by the type and the lifetime.
246 // - We asked the OS to map `size` bytes, so we have a guarantee that there's
247 // `size` consecutive bytes.
248 // - We never give a mutable reference to this slice, so it can't get mutated.
249 // - The total len of the slice is guaranteed to be smaller than
250 // [`isize::MAX`].
251 // - The underlying mapping, the type and the slice have the same lifetime
252 // which guarantees that we can't access the underlying once it has been
253 // unmapped (use-after-unmap).
254 Ok(unsafe { slice::from_raw_parts(ret, size) })
255 }
256
257 // Unmap a memory mapped file.
258 pub fn unmap_memory_mapped_file(data: &[u8]) -> Result<(), io::Error> {
259 match unsafe { munmap(data.as_ptr(), data.len()) } {
260 0 => Ok(()),
261 _ => Err(io::Error::last_os_error()),
262 }
263 }
264}
265
266#[cfg(unix)]
267use unix::*;
268
269#[cfg(not(any(windows, unix)))]
270/// Your system hasn't been implemented; if you do it, send a PR!
271fn unimplemented() -> u32 {}