]>
Commit | Line | Data |
---|---|---|
e1599b0c XL |
1 | use core::fmt::{self, Write}; |
2 | use core::mem::{size_of, transmute}; | |
3 | use core::slice::from_raw_parts; | |
4 | use libc::c_char; | |
5 | ||
6 | extern "C" { | |
7 | // dl_iterate_phdr takes a callback that will receive a dl_phdr_info pointer | |
8 | // for every DSO that has been linked into the process. dl_iterate_phdr also | |
9 | // ensures that the dynamic linker is locked from start to finish of the | |
10 | // iteration. If the callback returns a non-zero value the iteration is | |
11 | // terminated early. 'data' will be passed as the third argument to the | |
12 | // callback on each call. 'size' gives the size of the dl_phdr_info. | |
13 | #[allow(improper_ctypes)] | |
14 | fn dl_iterate_phdr( | |
3dfed10e XL |
15 | f: extern "C" fn(info: &dl_phdr_info, size: usize, data: &mut DsoPrinter<'_, '_>) -> i32, |
16 | data: &mut DsoPrinter<'_, '_>, | |
e1599b0c XL |
17 | ) -> i32; |
18 | } | |
19 | ||
20 | // We need to parse out the build ID and some basic program header data | |
21 | // which means that we need a bit of stuff from the ELF spec as well. | |
22 | ||
23 | const PT_LOAD: u32 = 1; | |
24 | const PT_NOTE: u32 = 4; | |
25 | ||
26 | // Now we have to replicate, bit for bit, the structure of the dl_phdr_info | |
27 | // type used by fuchsia's current dynamic linker. Chromium also has this ABI | |
49aad941 | 28 | // boundary as well as crashpad. Eventually we'd like to move these cases to |
e1599b0c XL |
29 | // use elf-search but we'd need to provide that in the SDK and that has not |
30 | // yet been done. Thus we (and they) are stuck having to use this method | |
31 | // which incurs a tight coupling with the fuchsia libc. | |
32 | ||
33 | #[allow(non_camel_case_types)] | |
34 | #[repr(C)] | |
35 | struct dl_phdr_info { | |
36 | addr: *const u8, | |
37 | name: *const c_char, | |
38 | phdr: *const Elf_Phdr, | |
39 | phnum: u16, | |
40 | adds: u64, | |
41 | subs: u64, | |
42 | tls_modid: usize, | |
43 | tls_data: *const u8, | |
44 | } | |
45 | ||
46 | impl dl_phdr_info { | |
47 | fn program_headers(&self) -> PhdrIter<'_> { | |
48 | PhdrIter { | |
49 | phdrs: self.phdr_slice(), | |
50 | base: self.addr, | |
51 | } | |
52 | } | |
53 | // We have no way of knowing of checking if e_phoff and e_phnum are valid. | |
54 | // libc should ensure this for us however so it's safe to form a slice here. | |
55 | fn phdr_slice(&self) -> &[Elf_Phdr] { | |
56 | unsafe { from_raw_parts(self.phdr, self.phnum as usize) } | |
57 | } | |
58 | } | |
59 | ||
60 | struct PhdrIter<'a> { | |
61 | phdrs: &'a [Elf_Phdr], | |
62 | base: *const u8, | |
63 | } | |
64 | ||
65 | impl<'a> Iterator for PhdrIter<'a> { | |
66 | type Item = Phdr<'a>; | |
67 | fn next(&mut self) -> Option<Self::Item> { | |
68 | self.phdrs.split_first().map(|(phdr, new_phdrs)| { | |
69 | self.phdrs = new_phdrs; | |
70 | Phdr { | |
71 | phdr, | |
72 | base: self.base, | |
73 | } | |
74 | }) | |
75 | } | |
76 | } | |
77 | ||
78 | // Elf_Phdr represents a 64-bit ELF program header in the endianness of the target | |
79 | // architecture. | |
80 | #[allow(non_camel_case_types)] | |
81 | #[derive(Clone, Debug)] | |
82 | #[repr(C)] | |
83 | struct Elf_Phdr { | |
84 | p_type: u32, | |
85 | p_flags: u32, | |
86 | p_offset: u64, | |
87 | p_vaddr: u64, | |
88 | p_paddr: u64, | |
89 | p_filesz: u64, | |
90 | p_memsz: u64, | |
91 | p_align: u64, | |
92 | } | |
93 | ||
94 | // Phdr represents a valid ELF program header and its contents. | |
95 | struct Phdr<'a> { | |
96 | phdr: &'a Elf_Phdr, | |
97 | base: *const u8, | |
98 | } | |
99 | ||
100 | impl<'a> Phdr<'a> { | |
101 | // We have no way of checking if p_addr or p_memsz are valid. Fuchsia's libc | |
102 | // parses the notes first however so by virtue of being here these headers | |
103 | // must be valid. NoteIter does not require the underlying data to be valid | |
104 | // but it does require the bounds to be valid. We trust that libc has ensured | |
105 | // that this is the case for us here. | |
106 | fn notes(&self) -> NoteIter<'a> { | |
107 | unsafe { | |
108 | NoteIter::new( | |
109 | self.base.add(self.phdr.p_offset as usize), | |
110 | self.phdr.p_memsz as usize, | |
111 | ) | |
112 | } | |
113 | } | |
114 | } | |
115 | ||
116 | // The note type for build IDs. | |
117 | const NT_GNU_BUILD_ID: u32 = 3; | |
118 | ||
119 | // Elf_Nhdr represents an ELF note header in the endianness of the target. | |
120 | #[allow(non_camel_case_types)] | |
121 | #[repr(C)] | |
122 | struct Elf_Nhdr { | |
123 | n_namesz: u32, | |
124 | n_descsz: u32, | |
125 | n_type: u32, | |
126 | } | |
127 | ||
128 | // Note represents an ELF note (header + contents). The name is left as a u8 | |
129 | // slice because it is not always null terminated and rust makes it easy enough | |
130 | // to check that the bytes match eitherway. | |
131 | struct Note<'a> { | |
132 | name: &'a [u8], | |
133 | desc: &'a [u8], | |
134 | tipe: u32, | |
135 | } | |
136 | ||
137 | // NoteIter lets you safely iterate over a note segment. It terminates as soon | |
138 | // as an error occurs or there are no more notes. If you iterate over invalid | |
139 | // data it will function as though no notes were found. | |
140 | struct NoteIter<'a> { | |
141 | base: &'a [u8], | |
142 | error: bool, | |
143 | } | |
144 | ||
145 | impl<'a> NoteIter<'a> { | |
146 | // It is an invariant of function that the pointer and size given denote a | |
147 | // valid range of bytes that can all be read. The contents of these bytes | |
148 | // can be anything but the range must be valid for this to be safe. | |
149 | unsafe fn new(base: *const u8, size: usize) -> Self { | |
150 | NoteIter { | |
151 | base: from_raw_parts(base, size), | |
152 | error: false, | |
153 | } | |
154 | } | |
155 | } | |
156 | ||
157 | // align_to aligns 'x' to 'to'-byte alignment assuming 'to' is a power of 2. | |
158 | // This follows a standard pattern in C/C++ ELF parsing code where | |
159 | // (x + to - 1) & -to is used. Rust does not let you negate usize so I use | |
160 | // 2's-complement conversion to recreate that. | |
161 | fn align_to(x: usize, to: usize) -> usize { | |
162 | (x + to - 1) & (!to + 1) | |
163 | } | |
164 | ||
165 | // take_bytes_align4 consumes num bytes from the slice (if present) and | |
166 | // additionally ensures that the final slice is properlly aligned. If an | |
167 | // either the number of bytes requested is too large or the slice can't be | |
168 | // realigned afterwards due to not enough remaining bytes existing, None is | |
169 | // returned and the slice is not modified. | |
170 | fn take_bytes_align4<'a>(num: usize, bytes: &mut &'a [u8]) -> Option<&'a [u8]> { | |
171 | if bytes.len() < align_to(num, 4) { | |
172 | return None; | |
173 | } | |
174 | let (out, bytes_new) = bytes.split_at(num); | |
175 | *bytes = &bytes_new[align_to(num, 4) - num..]; | |
176 | Some(out) | |
177 | } | |
178 | ||
179 | // This function has no real invariants the caller must uphold other than | |
180 | // perhaps that 'bytes' should be aligned for performance (and on some | |
181 | // architectures correctness). The values in the Elf_Nhdr fields might | |
182 | // be nonsense but this function ensures no such thing. | |
183 | fn take_nhdr<'a>(bytes: &mut &'a [u8]) -> Option<&'a Elf_Nhdr> { | |
184 | if size_of::<Elf_Nhdr>() > bytes.len() { | |
185 | return None; | |
186 | } | |
187 | // This is safe as long as there is enough space and we just confirmed that | |
188 | // in the if statement above so this should not be unsafe. | |
189 | let out = unsafe { transmute::<*const u8, &'a Elf_Nhdr>(bytes.as_ptr()) }; | |
190 | // Note that sice_of::<Elf_Nhdr>() is always 4-byte aligned. | |
191 | *bytes = &bytes[size_of::<Elf_Nhdr>()..]; | |
192 | Some(out) | |
193 | } | |
194 | ||
195 | impl<'a> Iterator for NoteIter<'a> { | |
196 | type Item = Note<'a>; | |
197 | fn next(&mut self) -> Option<Self::Item> { | |
198 | // Check if we've reached the end. | |
199 | if self.base.len() == 0 || self.error { | |
200 | return None; | |
201 | } | |
202 | // We transmute out an nhdr but we carefully consider the resulting | |
203 | // struct. We don't trust the namesz or descsz and we make no unsafe | |
204 | // decisions based on the type. So even if we get out complete garbage | |
205 | // we should still be safe. | |
206 | let nhdr = take_nhdr(&mut self.base)?; | |
207 | let name = take_bytes_align4(nhdr.n_namesz as usize, &mut self.base)?; | |
208 | let desc = take_bytes_align4(nhdr.n_descsz as usize, &mut self.base)?; | |
209 | Some(Note { | |
210 | name: name, | |
211 | desc: desc, | |
212 | tipe: nhdr.n_type, | |
213 | }) | |
214 | } | |
215 | } | |
216 | ||
217 | struct Perm(u32); | |
218 | ||
219 | /// Indicates that a segment is executable. | |
220 | const PERM_X: u32 = 0b00000001; | |
221 | /// Indicates that a segment is writable. | |
222 | const PERM_W: u32 = 0b00000010; | |
223 | /// Indicates that a segment is readable. | |
224 | const PERM_R: u32 = 0b00000100; | |
225 | ||
226 | impl core::fmt::Display for Perm { | |
3dfed10e | 227 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
e1599b0c XL |
228 | let v = self.0; |
229 | if v & PERM_R != 0 { | |
230 | f.write_char('r')? | |
231 | } | |
232 | if v & PERM_W != 0 { | |
233 | f.write_char('w')? | |
234 | } | |
235 | if v & PERM_X != 0 { | |
236 | f.write_char('x')? | |
237 | } | |
238 | Ok(()) | |
239 | } | |
240 | } | |
241 | ||
242 | /// Represents an ELF segment at runtime. | |
243 | struct Segment { | |
244 | /// Gives the runtime virtual address of this segment's contents. | |
245 | addr: usize, | |
246 | /// Gives the memory size of this segment's contents. | |
247 | size: usize, | |
248 | /// Gives the module virtual address of this segment with the ELF file. | |
249 | mod_rel_addr: usize, | |
250 | /// Gives the permissions found in the ELF file. These permissions are not | |
251 | /// necessarily the permissions present at runtime however. | |
252 | flags: Perm, | |
253 | } | |
254 | ||
255 | /// Lets one iterate over Segments from a DSO. | |
256 | struct SegmentIter<'a> { | |
257 | phdrs: &'a [Elf_Phdr], | |
258 | base: usize, | |
259 | } | |
260 | ||
261 | impl Iterator for SegmentIter<'_> { | |
262 | type Item = Segment; | |
263 | ||
264 | fn next(&mut self) -> Option<Self::Item> { | |
265 | self.phdrs.split_first().and_then(|(phdr, new_phdrs)| { | |
266 | self.phdrs = new_phdrs; | |
267 | if phdr.p_type != PT_LOAD { | |
268 | self.next() | |
269 | } else { | |
270 | Some(Segment { | |
271 | addr: phdr.p_vaddr as usize + self.base, | |
272 | size: phdr.p_memsz as usize, | |
273 | mod_rel_addr: phdr.p_vaddr as usize, | |
274 | flags: Perm(phdr.p_flags), | |
275 | }) | |
276 | } | |
277 | }) | |
278 | } | |
279 | } | |
280 | ||
281 | /// Represents an ELF DSO (Dynamic Shared Object). This type references | |
282 | /// the data stored in the actual DSO rather than making its own copy. | |
283 | struct Dso<'a> { | |
284 | /// The dynamic linker always gives us a name, even if the name is empty. | |
285 | /// In the case of the main executable this name will be empty. In the case | |
286 | /// of a shared object it will be the soname (see DT_SONAME). | |
287 | name: &'a str, | |
288 | /// On Fuchsia virtually all binaries have build IDs but this is not a strict | |
923072b8 | 289 | /// requirement. There's no way to match up DSO information with a real ELF |
e1599b0c XL |
290 | /// file afterwards if there is no build_id so we require that every DSO |
291 | /// have one here. DSO's without a build_id are ignored. | |
292 | build_id: &'a [u8], | |
293 | ||
294 | base: usize, | |
295 | phdrs: &'a [Elf_Phdr], | |
296 | } | |
297 | ||
298 | impl Dso<'_> { | |
299 | /// Returns an iterator over Segments in this DSO. | |
300 | fn segments(&self) -> SegmentIter<'_> { | |
301 | SegmentIter { | |
302 | phdrs: self.phdrs.as_ref(), | |
303 | base: self.base, | |
304 | } | |
305 | } | |
306 | } | |
307 | ||
308 | struct HexSlice<'a> { | |
309 | bytes: &'a [u8], | |
310 | } | |
311 | ||
312 | impl fmt::Display for HexSlice<'_> { | |
3dfed10e | 313 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
e1599b0c | 314 | for byte in self.bytes { |
e74abb32 | 315 | write!(f, "{:02x}", byte)?; |
e1599b0c XL |
316 | } |
317 | Ok(()) | |
318 | } | |
319 | } | |
320 | ||
321 | fn get_build_id<'a>(info: &'a dl_phdr_info) -> Option<&'a [u8]> { | |
322 | for phdr in info.program_headers() { | |
323 | if phdr.phdr.p_type == PT_NOTE { | |
324 | for note in phdr.notes() { | |
325 | if note.tipe == NT_GNU_BUILD_ID && (note.name == b"GNU\0" || note.name == b"GNU") { | |
326 | return Some(note.desc); | |
327 | } | |
328 | } | |
329 | } | |
330 | } | |
331 | None | |
332 | } | |
333 | ||
334 | /// These errors encode issues that arise while parsing information about | |
335 | /// each DSO. | |
336 | enum Error { | |
337 | /// NameError means that an error occurred while converting a C style string | |
338 | /// into a rust string. | |
339 | NameError(core::str::Utf8Error), | |
340 | /// BuildIDError means that we didn't find a build ID. This could either be | |
341 | /// because the DSO had no build ID or because the segment containing the | |
342 | /// build ID was malformed. | |
343 | BuildIDError, | |
344 | } | |
345 | ||
346 | /// Calls either 'dso' or 'error' for each DSO linked into the process by the | |
347 | /// dynamic linker. | |
348 | /// | |
349 | /// # Arguments | |
350 | /// | |
351 | /// * `visitor` - A DsoPrinter that will have one of eats methods called foreach DSO. | |
3dfed10e XL |
352 | fn for_each_dso(mut visitor: &mut DsoPrinter<'_, '_>) { |
353 | extern "C" fn callback( | |
354 | info: &dl_phdr_info, | |
355 | _size: usize, | |
356 | visitor: &mut DsoPrinter<'_, '_>, | |
357 | ) -> i32 { | |
e1599b0c XL |
358 | // dl_iterate_phdr ensures that info.name will point to a valid |
359 | // location. | |
360 | let name_len = unsafe { libc::strlen(info.name) }; | |
361 | let name_slice: &[u8] = | |
362 | unsafe { core::slice::from_raw_parts(info.name as *const u8, name_len) }; | |
363 | let name = match core::str::from_utf8(name_slice) { | |
364 | Ok(name) => name, | |
365 | Err(err) => { | |
366 | return visitor.error(Error::NameError(err)) as i32; | |
367 | } | |
368 | }; | |
369 | let build_id = match get_build_id(info) { | |
370 | Some(build_id) => build_id, | |
371 | None => { | |
372 | return visitor.error(Error::BuildIDError) as i32; | |
373 | } | |
374 | }; | |
375 | visitor.dso(Dso { | |
376 | name: name, | |
377 | build_id: build_id, | |
378 | phdrs: info.phdr_slice(), | |
379 | base: info.addr as usize, | |
380 | }) as i32 | |
381 | } | |
382 | unsafe { dl_iterate_phdr(callback, &mut visitor) }; | |
383 | } | |
384 | ||
385 | struct DsoPrinter<'a, 'b> { | |
386 | writer: &'a mut core::fmt::Formatter<'b>, | |
387 | module_count: usize, | |
388 | error: core::fmt::Result, | |
389 | } | |
390 | ||
391 | impl DsoPrinter<'_, '_> { | |
392 | fn dso(&mut self, dso: Dso<'_>) -> bool { | |
393 | let mut write = || { | |
394 | write!( | |
395 | self.writer, | |
396 | "{{{{{{module:{:#x}:{}:elf:{}}}}}}}\n", | |
397 | self.module_count, | |
398 | dso.name, | |
399 | HexSlice { | |
400 | bytes: dso.build_id.as_ref() | |
401 | } | |
402 | )?; | |
403 | for seg in dso.segments() { | |
404 | write!( | |
405 | self.writer, | |
406 | "{{{{{{mmap:{:#x}:{:#x}:load:{:#x}:{}:{:#x}}}}}}}\n", | |
407 | seg.addr, seg.size, self.module_count, seg.flags, seg.mod_rel_addr | |
408 | )?; | |
409 | } | |
410 | self.module_count += 1; | |
411 | Ok(()) | |
412 | }; | |
413 | match write() { | |
414 | Ok(()) => false, | |
415 | Err(err) => { | |
416 | self.error = Err(err); | |
417 | true | |
418 | } | |
419 | } | |
420 | } | |
421 | fn error(&mut self, _error: Error) -> bool { | |
422 | false | |
423 | } | |
424 | } | |
425 | ||
426 | /// This function prints the Fuchsia symbolizer markup for all information contained in a DSO. | |
427 | pub fn print_dso_context(out: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | |
781aab86 | 428 | out.write_str("{{{reset:begin}}}\n")?; |
e1599b0c XL |
429 | let mut visitor = DsoPrinter { |
430 | writer: out, | |
431 | module_count: 0, | |
432 | error: Ok(()), | |
433 | }; | |
434 | for_each_dso(&mut visitor); | |
435 | visitor.error | |
436 | } | |
781aab86 FG |
437 | |
438 | /// This function prints the Fuchsia symbolizer markup to end the backtrace. | |
439 | pub fn finish_context(out: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | |
440 | out.write_str("{{{reset:end}}}\n") | |
441 | } |