samply_symbols/
external_file.rs

1use std::collections::HashMap;
2use std::sync::Mutex;
3
4use object::read::archive::ArchiveFile;
5use object::{File, FileKind, ReadRef};
6use yoke::Yoke;
7use yoke_derive::Yokeable;
8
9use crate::dwarf::{get_frames, Addr2lineContextData};
10use crate::error::Error;
11use crate::path_mapper::PathMapper;
12use crate::shared::{
13    ExternalFileAddressInFileRef, FileAndPathHelper, FileContents, FileContentsWrapper,
14    FrameDebugInfo,
15};
16
17pub async fn load_external_file<H>(
18    helper: &H,
19    external_file_location: H::FL,
20    external_file_path: &str,
21) -> Result<ExternalFileSymbolMap<H::F>, Error>
22where
23    H: FileAndPathHelper,
24{
25    let file = helper
26        .load_file(external_file_location)
27        .await
28        .map_err(|e| Error::HelperErrorDuringOpenFile(external_file_path.to_string(), e))?;
29    let symbol_map = ExternalFileSymbolMap::new(external_file_path, file)?;
30    Ok(symbol_map)
31}
32
33struct ExternalFileOuter<F: FileContents> {
34    file_path: String,
35    file_contents: FileContentsWrapper<F>,
36    addr2line_context_data: Addr2lineContextData,
37}
38
39impl<F: FileContents> ExternalFileOuter<F> {
40    pub fn new(file_path: &str, file: F) -> Self {
41        let file_contents = FileContentsWrapper::new(file);
42        Self {
43            file_path: file_path.to_owned(),
44            file_contents,
45            addr2line_context_data: Addr2lineContextData::new(),
46        }
47    }
48
49    pub fn file_path(&self) -> &str {
50        &self.file_path
51    }
52
53    fn make_member_context(
54        &self,
55        offset_and_size: (u64, u64),
56    ) -> Result<ExternalFileMemberContext<'_>, Error> {
57        let (start, size) = offset_and_size;
58        let data = self.file_contents.range(start, size);
59        let object_file = File::parse(data).map_err(Error::MachOHeaderParseError)?;
60        self.make_single_context(data, object_file)
61    }
62
63    fn make_single_context<'s, R: ReadRef<'s>>(
64        &'s self,
65        data: R,
66        object_file: object::read::File<'s, R>,
67    ) -> Result<ExternalFileMemberContext<'s>, Error> {
68        use object::{Object, ObjectSymbol};
69        let context = self
70            .addr2line_context_data
71            .make_context(data, &object_file, None, None);
72        let symbol_addresses = object_file
73            .symbols()
74            .filter_map(|symbol| {
75                let file_path = symbol.name_bytes().ok()?;
76                let address = symbol.address();
77                Some((file_path, address))
78            })
79            .collect();
80        let member_context = ExternalFileMemberContext {
81            context: context.ok(),
82            symbol_addresses,
83        };
84        Ok(member_context)
85    }
86
87    pub fn make_inner(&self) -> Result<ExternalFileInner<'_, F>, Error> {
88        let file_kind = FileKind::parse(&self.file_contents)
89            .map_err(|_| Error::CouldNotDetermineExternalFileFileKind)?;
90        let member_contexts = match file_kind {
91            FileKind::MachO32 | FileKind::MachO64 => {
92                let data = self.file_contents.full_range();
93                let object_file = File::parse(data).map_err(Error::MachOHeaderParseError)?;
94                let context = self.make_single_context(data, object_file)?;
95                ExternalFileMemberContexts::SingleObject(context)
96            }
97            FileKind::Archive => {
98                let archive = ArchiveFile::parse(&self.file_contents)
99                    .map_err(Error::ParseErrorInExternalArchive)?;
100                let mut member_ranges = HashMap::new();
101                for member in archive.members() {
102                    let member = member.map_err(Error::ParseErrorInExternalArchive)?;
103                    let file_path = member.name().to_owned();
104                    member_ranges.insert(file_path, member.file_range());
105                }
106                ExternalFileMemberContexts::Archive {
107                    member_ranges,
108                    contexts: Mutex::new(HashMap::new()),
109                }
110            }
111            FileKind::MachOFat32 | FileKind::MachOFat64 => {
112                return Err(Error::UnexpectedExternalFileFileKind(file_kind));
113            }
114            _ => {
115                return Err(Error::UnexpectedExternalFileFileKind(file_kind));
116            }
117        };
118        Ok(ExternalFileInner {
119            external_file: self,
120            member_contexts,
121            path_mapper: Mutex::new(PathMapper::new()),
122        })
123    }
124}
125
126enum ExternalFileMemberContexts<'a> {
127    SingleObject(ExternalFileMemberContext<'a>),
128    /// member file_path -> context
129    Archive {
130        member_ranges: HashMap<Vec<u8>, (u64, u64)>,
131        contexts: Mutex<HashMap<String, ExternalFileMemberContext<'a>>>,
132    },
133}
134
135#[derive(Yokeable)]
136struct ExternalFileInnerWrapper<'a>(Box<dyn ExternalFileInnerTrait + Send + 'a>);
137
138trait ExternalFileInnerTrait {
139    fn lookup(
140        &self,
141        external_file_address: &ExternalFileAddressInFileRef,
142    ) -> Option<Vec<FrameDebugInfo>>;
143}
144
145struct ExternalFileInner<'a, T: FileContents> {
146    external_file: &'a ExternalFileOuter<T>,
147    member_contexts: ExternalFileMemberContexts<'a>,
148    path_mapper: Mutex<PathMapper<()>>,
149}
150
151impl<F: FileContents> ExternalFileInnerTrait for ExternalFileInner<'_, F> {
152    fn lookup(
153        &self,
154        external_file_address: &ExternalFileAddressInFileRef,
155    ) -> Option<Vec<FrameDebugInfo>> {
156        let mut path_mapper = self.path_mapper.lock().unwrap();
157        match (&self.member_contexts, external_file_address) {
158            (
159                ExternalFileMemberContexts::SingleObject(context),
160                ExternalFileAddressInFileRef::MachoOsoObject {
161                    symbol_name,
162                    offset_from_symbol,
163                },
164            ) => context.lookup(symbol_name, *offset_from_symbol, &mut path_mapper),
165            (
166                ExternalFileMemberContexts::Archive {
167                    member_ranges,
168                    contexts,
169                },
170                ExternalFileAddressInFileRef::MachoOsoArchive {
171                    name_in_archive,
172                    symbol_name,
173                    offset_from_symbol,
174                },
175            ) => {
176                let mut member_contexts = contexts.lock().unwrap();
177                match member_contexts.get(name_in_archive) {
178                    Some(member_context) => {
179                        member_context.lookup(symbol_name, *offset_from_symbol, &mut path_mapper)
180                    }
181                    None => {
182                        let range = *member_ranges.get(name_in_archive.as_bytes())?;
183                        // .ok_or_else(|| Error::FileNotInArchive(name_in_archive.to_owned()))?;
184                        let member_context = self.external_file.make_member_context(range).ok()?;
185                        let res = member_context.lookup(
186                            symbol_name,
187                            *offset_from_symbol,
188                            &mut path_mapper,
189                        );
190                        member_contexts.insert(name_in_archive.to_string(), member_context);
191                        res
192                    }
193                }
194            }
195            (
196                ExternalFileMemberContexts::SingleObject(_),
197                ExternalFileAddressInFileRef::MachoOsoArchive { .. },
198            )
199            | (
200                ExternalFileMemberContexts::Archive { .. },
201                ExternalFileAddressInFileRef::MachoOsoObject { .. },
202            )
203            | (_, ExternalFileAddressInFileRef::ElfDwo { .. }) => None,
204        }
205    }
206}
207
208struct ExternalFileMemberContext<'a> {
209    context: Option<addr2line::Context<gimli::EndianSlice<'a, gimli::RunTimeEndian>>>,
210    symbol_addresses: HashMap<&'a [u8], u64>,
211}
212
213impl ExternalFileMemberContext<'_> {
214    pub fn lookup(
215        &self,
216        symbol_name: &[u8],
217        offset_from_symbol: u32,
218        path_mapper: &mut PathMapper<()>,
219    ) -> Option<Vec<FrameDebugInfo>> {
220        let symbol_address = self.symbol_addresses.get(symbol_name)?;
221        let address = symbol_address + offset_from_symbol as u64;
222        get_frames(address, self.context.as_ref(), path_mapper)
223    }
224}
225
226pub struct ExternalFileSymbolMap<F: FileContents + 'static>(
227    Yoke<ExternalFileInnerWrapper<'static>, Box<ExternalFileOuter<F>>>,
228);
229
230impl<F: FileContents + 'static> ExternalFileSymbolMap<F> {
231    pub fn new(file_path: &str, file: F) -> Result<Self, Error> {
232        let outer = ExternalFileOuter::new(file_path, file);
233        let inner = Yoke::try_attach_to_cart(
234            Box::new(outer),
235            |outer| -> Result<ExternalFileInnerWrapper<'_>, Error> {
236                let inner = outer.make_inner()?;
237                Ok(ExternalFileInnerWrapper(Box::new(inner)))
238            },
239        )?;
240        Ok(Self(inner))
241    }
242
243    /// The string which identifies this external file. This is usually an absolute
244    /// path.
245    pub fn file_path(&self) -> &str {
246        self.0.backing_cart().file_path()
247    }
248
249    /// Look up the debug info for the given [`ExternalFileAddressInFileRef`].
250    pub fn lookup(
251        &self,
252        external_file_address: &ExternalFileAddressInFileRef,
253    ) -> Option<Vec<FrameDebugInfo>> {
254        self.0.get().0.lookup(external_file_address)
255    }
256}