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