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 {}