gsym/mapped.rs
1use std::fs::File;
2use std::path::Path;
3
4use memmap2::{Mmap, MmapOptions};
5
6use crate::{Error, Gsym, Result};
7
8/// Opaque read-only mapping used by [`MappedGsym`].
9///
10/// Obtain one through [`MappedGsym::into_inner`] when direct access to the
11/// mapped bytes is needed.
12pub struct MappedBytes(Mmap);
13
14impl std::fmt::Debug for MappedBytes {
15 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 formatter
17 .debug_struct("MappedBytes")
18 .field("len", &self.0.len())
19 .finish_non_exhaustive()
20 }
21}
22
23impl AsRef<[u8]> for MappedBytes {
24 fn as_ref(&self) -> &[u8] {
25 self.0.as_ref()
26 }
27}
28
29/// GSYM reader backed directly by a read-only memory map.
30///
31/// This is the same reader as [`Gsym`] with opaque mapped-byte storage, so it
32/// has the same query API. Mapping suits a large file with sparse lookups, where
33/// reading the whole file into memory would cost more than the lookups do.
34///
35/// Both constructors are `unsafe` because a memory map is not a snapshot. If
36/// any process truncates or rewrites the file while it is mapped, results
37/// borrowed from it can observe changed bytes or become invalid.
38/// [`Gsym::open`] reads an owned snapshot instead and carries no such
39/// requirement.
40///
41/// ```no_run
42/// use gsym::MappedGsym;
43///
44/// // SAFETY: this process controls the file and keeps it immutable while mapped.
45/// let gsym = unsafe { MappedGsym::map("app.gsym")? };
46/// if let Some(symbol) = gsym.lookup(0x401000)? {
47/// println!("{}", String::from_utf8_lossy(symbol.frames()[0].name));
48/// }
49/// # Ok::<(), gsym::Error>(())
50/// ```
51///
52/// A mapped reader and an owned one parse the same file identically:
53///
54/// ```no_run
55/// use gsym::{Gsym, MappedGsym};
56///
57/// let snapshot = Gsym::open("app.gsym")?;
58///
59/// // SAFETY: this application owns the file and keeps it immutable while mapped.
60/// let mapped = unsafe { MappedGsym::map("app.gsym")? };
61/// let file = std::fs::File::open("app.gsym")?;
62/// // SAFETY: the same file-stability guarantee applies to this mapping.
63/// let mapped_file = unsafe { MappedGsym::map_file(&file)? };
64///
65/// assert_eq!(snapshot.header(), mapped.header());
66/// assert_eq!(mapped.header(), mapped_file.header());
67/// # Ok::<(), gsym::Error>(())
68/// ```
69pub type MappedGsym = Gsym<MappedBytes>;
70
71impl Gsym<MappedBytes> {
72 /// Opens and validates a GSYM file through a read-only memory map.
73 ///
74 /// # Safety
75 ///
76 /// The mapped file must not be modified or truncated by any process for
77 /// the lifetime of the returned mapping. Use [`Gsym::open`] to read owned
78 /// bytes when that cannot be guaranteed.
79 ///
80 /// # Errors
81 ///
82 /// Returns an I/O error when the file cannot be opened or mapped, or a
83 /// format error when its GSYM metadata is invalid.
84 pub unsafe fn map(path: impl AsRef<Path>) -> Result<Self> {
85 let path = path.as_ref();
86 let file = File::open(path).map_err(|source| Error::IoAtPath {
87 operation: "open GSYM file for mapping",
88 path: path.to_path_buf(),
89 source,
90 })?;
91 // SAFETY: the caller accepted the file-stability requirement.
92 let mapping =
93 unsafe { MmapOptions::new().map(&file) }.map_err(|source| Error::IoAtPath {
94 operation: "memory-map GSYM file",
95 path: path.to_path_buf(),
96 source,
97 })?;
98 Self::parse(MappedBytes(mapping))
99 }
100
101 /// Maps and validates an already-open file.
102 ///
103 /// Use this when the file was opened elsewhere, for instance through a
104 /// descriptor passed in or a handle kept for locking. The mapping does not
105 /// keep the `File` alive, so it may be closed once this returns.
106 ///
107 /// # Safety
108 ///
109 /// `file` must not be modified or truncated by any process for the
110 /// lifetime of the returned mapping.
111 ///
112 /// # Errors
113 ///
114 /// Returns an I/O error when mapping fails or a format error for invalid
115 /// GSYM metadata.
116 pub unsafe fn map_file(file: &File) -> Result<Self> {
117 // SAFETY: guaranteed by this function's caller.
118 let mapping = unsafe { MmapOptions::new().map(file)? };
119 Self::parse(MappedBytes(mapping))
120 }
121}