Skip to content

Commit c65bb50

Browse files
committed
Parse fat libraries on macOS
This commit updates the object parsing code for macOS to support fat libraries. This enables gimli to symbolize addresses coming from system libraries which are currently installed frequently as fat libraries. Closes #319
1 parent 4fe5808 commit c65bb50

1 file changed

Lines changed: 127 additions & 54 deletions

File tree

‎src/symbolize/gimli.rs‎

Lines changed: 127 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ cfg_if::cfg_if! {
176176
use std::os::unix::prelude::*;
177177
use std::ffi::{OsStr, CStr};
178178
use object::{Bytes, NativeEndian};
179+
use object::macho;
179180
use object::read::macho::{MachHeader, Section, Segment as _, Nlist};
180181

181182
#[cfg(target_pointer_width = "32")]
@@ -248,71 +249,145 @@ cfg_if::cfg_if! {
248249
}
249250
}
250251

252+
fn find_header(mut data: Bytes<'_>) -> Option<(&'_ Mach, Bytes<'_>)> {
253+
use object::endian::BigEndian;
254+
255+
let desired_cpu = || {
256+
if cfg!(target_arch = "x86") {
257+
Some(macho::CPU_TYPE_X86)
258+
} else if cfg!(target_arch = "x86_64") {
259+
Some(macho::CPU_TYPE_X86_64)
260+
} else if cfg!(target_arch = "arm") {
261+
Some(macho::CPU_TYPE_ARM)
262+
} else if cfg!(target_arch = "aarch64") {
263+
Some(macho::CPU_TYPE_ARM64)
264+
} else {
265+
None
266+
}
267+
};
268+
269+
match data.clone().read::<object::endian::U32<NativeEndian>>().ok()?.get(NativeEndian) {
270+
macho::MH_MAGIC_64
271+
| macho::MH_CIGAM_64
272+
| macho::MH_MAGIC
273+
| macho::MH_CIGAM => {}
274+
275+
macho::FAT_MAGIC | macho::FAT_CIGAM => {
276+
let mut header_data = data;
277+
let endian = BigEndian;
278+
let header = header_data.read::<macho::FatHeader>().ok()?;
279+
let nfat = header.nfat_arch.get(endian);
280+
let arch = (0..nfat)
281+
.filter_map(|_| header_data.read::<macho::FatArch32>().ok())
282+
.find(|arch| desired_cpu() == Some(arch.cputype.get(endian)))?;
283+
let offset = arch.offset.get(endian);
284+
let size = arch.size.get(endian);
285+
data = data.read_bytes_at(
286+
offset.try_into().ok()?,
287+
size.try_into().ok()?,
288+
).ok()?;
289+
}
290+
291+
macho::FAT_MAGIC_64 | macho::FAT_CIGAM_64 => {
292+
let mut header_data = data;
293+
let endian = BigEndian;
294+
let header = header_data.read::<macho::FatHeader>().ok()?;
295+
let nfat = header.nfat_arch.get(endian);
296+
let arch = (0..nfat)
297+
.filter_map(|_| header_data.read::<macho::FatArch64>().ok())
298+
.find(|arch| desired_cpu() == Some(arch.cputype.get(endian)))?;
299+
let offset = arch.offset.get(endian);
300+
let size = arch.size.get(endian);
301+
data = data.read_bytes_at(
302+
offset.try_into().ok()?,
303+
size.try_into().ok()?,
304+
).ok()?;
305+
}
306+
307+
_ => return None,
308+
}
309+
310+
Mach::parse(data).ok().map(|h| (h, data))
311+
}
312+
251313
#[allow(deprecated)]
252314
fn native_libraries() -> Vec<Library> {
253315
let mut ret = Vec::new();
254-
unsafe {
255-
for i in 0..libc::_dyld_image_count() {
256-
ret.extend(native_library(i));
257-
}
316+
let images = unsafe { libc::_dyld_image_count() };
317+
for i in 0..images {
318+
ret.extend(native_library(i));
258319
}
259320
return ret;
260321
}
261322

262323
#[allow(deprecated)]
263-
unsafe fn native_library(i: u32) -> Option<Library> {
264-
let name = libc::_dyld_get_image_name(i);
265-
if name.is_null() {
266-
return None;
267-
}
268-
let name = CStr::from_ptr(name);
269-
let header = libc::_dyld_get_image_header(i);
270-
if header.is_null() {
271-
return None;
272-
}
273-
let mut segments = Vec::new();
274-
match (*header).magic {
275-
libc::MH_MAGIC => {
276-
let mut next_cmd = header.offset(1) as *const libc::load_command;
277-
for _ in 0..(*header).ncmds {
278-
segments.extend(segment(next_cmd));
279-
next_cmd = (next_cmd as usize + (*next_cmd).cmdsize as usize) as *const _;
280-
}
324+
fn native_library(i: u32) -> Option<Library> {
325+
// Fetch the name of this library which corresponds to the path of
326+
// where to load it as well.
327+
let name = unsafe {
328+
let name = libc::_dyld_get_image_name(i);
329+
if name.is_null() {
330+
return None;
281331
}
282-
libc::MH_MAGIC_64 => {
283-
let header = header as *const libc::mach_header_64;
284-
let mut next_cmd = header.offset(1) as *const libc::load_command;
285-
for _ in 0..(*header).ncmds {
286-
segments.extend(segment(next_cmd));
287-
next_cmd = (next_cmd as usize + (*next_cmd).cmdsize as usize) as *const _;
332+
CStr::from_ptr(name)
333+
};
334+
335+
// Load the image header of this library and delegate to `object` to
336+
// parse all the load commands so we can figure out all the segments
337+
// involved here.
338+
let (mut load_commands, endian) = unsafe {
339+
let header = libc::_dyld_get_image_header(i);
340+
if header.is_null() {
341+
return None;
342+
}
343+
match (*header).magic {
344+
macho::MH_MAGIC => {
345+
let endian = NativeEndian;
346+
let header = &*(header as *const macho::MachHeader32<NativeEndian>);
347+
let data = core::slice::from_raw_parts(
348+
header as *const _ as *const u8,
349+
mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize
350+
);
351+
(header.load_commands(endian, Bytes(data)).ok()?, endian)
352+
}
353+
macho::MH_MAGIC_64 => {
354+
let endian = NativeEndian;
355+
let header = &*(header as *const macho::MachHeader64<NativeEndian>);
356+
let data = core::slice::from_raw_parts(
357+
header as *const _ as *const u8,
358+
mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize
359+
);
360+
(header.load_commands(endian, Bytes(data)).ok()?, endian)
288361
}
362+
_ => return None,
363+
}
364+
};
365+
366+
// Iterate over the segments and register known regions for segments
367+
// that we find. Additionally record information bout text segments
368+
// for processing later, see comments below.
369+
let mut segments = Vec::new();
370+
while let Some(cmd) = load_commands.next().ok()? {
371+
if let Some((seg, _)) = cmd.segment_32().ok()? {
372+
segments.push(LibrarySegment {
373+
len: seg.vmsize(endian).try_into().ok()?,
374+
stated_virtual_memory_address: seg.vmaddr(endian) as *const u8,
375+
});
376+
}
377+
if let Some((seg, _)) = cmd.segment_64().ok()? {
378+
segments.push(LibrarySegment {
379+
len: seg.vmsize(endian).try_into().ok()?,
380+
stated_virtual_memory_address: seg.vmaddr(endian) as *const u8,
381+
});
289382
}
290-
_ => return None,
291383
}
384+
let slide = unsafe {
385+
libc::_dyld_get_image_vmaddr_slide(i)
386+
};
292387
Some(Library {
293388
name: OsStr::from_bytes(name.to_bytes()).to_owned(),
294389
segments,
295-
bias: libc::_dyld_get_image_vmaddr_slide(i) as *const u8,
296-
})
297-
}
298-
299-
unsafe fn segment(cmd: *const libc::load_command) -> Option<LibrarySegment> {
300-
Some(match (*cmd).cmd {
301-
libc::LC_SEGMENT => {
302-
let cmd = cmd as *const libc::segment_command;
303-
LibrarySegment {
304-
len: (*cmd).vmsize as usize,
305-
stated_virtual_memory_address: (*cmd).vmaddr as *const u8,
306-
}
307-
}
308-
libc::LC_SEGMENT_64 => {
309-
let cmd = cmd as *const libc::segment_command_64;
310-
LibrarySegment {
311-
len: (*cmd).vmsize as usize,
312-
stated_virtual_memory_address: (*cmd).vmaddr as *const u8,
313-
}
314-
}
315-
_ => return None,
390+
bias: slide as *const u8,
316391
})
317392
}
318393
} else {
@@ -476,8 +551,7 @@ impl Mapping {
476551
// First up we need to load the unique UUID which is stored in the macho
477552
// header of the file we're reading, specified at `path`.
478553
let map = mmap(path)?;
479-
let data = Bytes(&map);
480-
let macho = Mach::parse(data).ok()?;
554+
let (macho, data) = find_header(Bytes(&map))?;
481555
let endian = macho.endian().ok()?;
482556
let uuid = macho.uuid(endian, data).ok()??;
483557

@@ -513,8 +587,7 @@ impl Mapping {
513587
for entry in dir.read_dir().ok()? {
514588
let entry = entry.ok()?;
515589
let map = mmap(&entry.path())?;
516-
let data = Bytes(&map);
517-
let macho = Mach::parse(data).ok()?;
590+
let (macho, data) = find_header(Bytes(&map))?;
518591
let endian = macho.endian().ok()?;
519592
let entry_uuid = macho.uuid(endian, data).ok()??;
520593
if entry_uuid != uuid {

0 commit comments

Comments
 (0)