Skip to main content

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
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: Cursor<&'map [u8]>,
18}
19
20impl Debug for MappedFileReader<'_> {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 = 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: 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
62cfg_select! {
63    windows => {
64        #[allow(non_camel_case_types, clippy::upper_case_acronyms)]
65        // Memory mapping on Windows using `CreateFileMappingA` / `MapViewOfFile`.
66        mod windows {
67            use std::fs::File;
68            use std::os::windows::prelude::AsRawHandle;
69            use std::os::windows::raw::HANDLE;
70            use std::{io, ptr, slice};
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: &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                assert!(size <= isize::MAX.try_into().unwrap(), "slice is too large");
165
166                // Create the slice over the mapping.
167                // SAFETY: This is safe because:
168                //   - It is a byte slice, so we don't need to care about the alignment.
169                //   - The base is not NULL as we've verified that it is the case above.
170                //   - The underlying is owned by the type and the lifetime.
171                //   - We asked the OS to map `size` bytes, so we have a guarantee that there's
172                //     `size` consecutive bytes.
173                //   - We never give a mutable reference to this slice, so it can't get mutated.
174                //   - The total len of the slice is guaranteed to be smaller than
175                //     [`isize::MAX`].
176                //   - The underlying mapping, the type and the slice have the same lifetime
177                //     which guarantees that we can't access the underlying once it has been
178                //     unmapped (use-after-unmap).
179                Ok(unsafe { slice::from_raw_parts(base, size) })
180            }
181
182            /// Unmap the memory mapped file.
183            pub fn unmap_memory_mapped_file(data: &[u8]) -> Result<(), io::Error> {
184                match unsafe { UnmapViewOfFile(data.as_ptr()) } {
185                    0 => Err(io::Error::last_os_error()),
186                    _ => Ok(()),
187                }
188            }
189        }
190
191        use windows::memory_map_file;
192        use windows::unmap_memory_mapped_file;
193    }
194    unix => {
195        mod unix {
196            // Memory mapping on Unix using the mmap syscall.
197            use std::os::fd::AsRawFd;
198            use std::fs::File;
199            use std::{io, ptr, slice};
200
201            const PROT_READ: i32 = 1;
202            const MAP_SHARED: i32 = 1;
203            const MAP_FAILED: *const u8 = usize::MAX as _;
204
205            unsafe extern "system" {
206                fn mmap(
207                    addr: *const u8,
208                    length: usize,
209                    prot: i32,
210                    flags: i32,
211                    fd: i32,
212                    offset: i32,
213                ) -> *const u8;
214
215                fn munmap(addr: *const u8, length: usize) -> i32;
216            }
217
218            pub fn memory_map_file<'map>(file: &File) -> Result<&'map [u8], io::Error> {
219                // Grab the underlying file descriptor.
220                let file_fd = file.as_raw_fd();
221
222                // Grab the size of the underlying file. This will be the size of the
223                // memory mapped region.
224                let size = file.metadata()?.len().try_into().unwrap();
225
226                // Mmap the file.
227                let ret = unsafe { mmap(ptr::null_mut(), size, PROT_READ, MAP_SHARED, file_fd, 0) };
228
229                // If the system call failed, bail.
230                if ret == MAP_FAILED {
231                    return Err(io::Error::last_os_error());
232                }
233
234                // Make sure the size is not bigger than what [`slice::from_raw_parts`] wants.
235                assert!(size <= isize::MAX.try_into().unwrap(), "slice is too large");
236
237                // Create the slice over the mapping.
238                // SAFETY: This is safe because:
239                //   - It is a byte slice, so we don't need to care about the alignment.
240                //   - The base is not NULL as we've verified that it is the case above.
241                //   - The underlying is owned by the type and the lifetime.
242                //   - We asked the OS to map `size` bytes, so we have a guarantee that there's
243                //     `size` consecutive bytes.
244                //   - We never give a mutable reference to this slice, so it can't get mutated.
245                //   - The total len of the slice is guaranteed to be smaller than
246                //     [`isize::MAX`].
247                //   - The underlying mapping, the type and the slice have the same lifetime
248                //     which guarantees that we can't access the underlying once it has been
249                //     unmapped (use-after-unmap).
250                Ok(unsafe { slice::from_raw_parts(ret, size) })
251            }
252
253            // Unmap a memory mapped file.
254            pub fn unmap_memory_mapped_file(data: &[u8]) -> Result<(), io::Error> {
255                match unsafe { munmap(data.as_ptr(), data.len()) } {
256                    0 => Ok(()),
257                    _ => Err(io::Error::last_os_error()),
258                }
259            }
260        }
261
262        use unix::memory_map_file;
263        use unix::unmap_memory_mapped_file;
264    }
265    _ => { panic!("Your system hasn't been implemented; if you do it, send a PR!"); }
266}