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 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 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 pub fn file_path(&self) -> &str {
246 self.0.backing_cart().file_path()
247 }
248
249 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}