summaryrefslogtreecommitdiffstats
path: root/vendor/backtrace/src/symbolize/gimli/libs_macos.rs
blob: 438bbff6fad0a672e4549cf78b2130600866ff7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#![allow(deprecated)]

use super::mystd::ffi::{CStr, OsStr};
use super::mystd::os::unix::prelude::*;
use super::mystd::prelude::v1::*;
use super::{Library, LibrarySegment};
use core::convert::TryInto;
use core::mem;

pub(super) fn native_libraries() -> Vec<Library> {
    let mut ret = Vec::new();
    let images = unsafe { libc::_dyld_image_count() };
    for i in 0..images {
        ret.extend(native_library(i));
    }
    return ret;
}

fn native_library(i: u32) -> Option<Library> {
    use object::macho;
    use object::read::macho::{MachHeader, Segment};
    use object::NativeEndian;

    // Fetch the name of this library which corresponds to the path of
    // where to load it as well.
    let name = unsafe {
        let name = libc::_dyld_get_image_name(i);
        if name.is_null() {
            return None;
        }
        CStr::from_ptr(name)
    };

    // Load the image header of this library and delegate to `object` to
    // parse all the load commands so we can figure out all the segments
    // involved here.
    let (mut load_commands, endian) = unsafe {
        let header = libc::_dyld_get_image_header(i);
        if header.is_null() {
            return None;
        }
        match (*header).magic {
            macho::MH_MAGIC => {
                let endian = NativeEndian;
                let header = &*(header as *const macho::MachHeader32<NativeEndian>);
                let data = core::slice::from_raw_parts(
                    header as *const _ as *const u8,
                    mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize,
                );
                (header.load_commands(endian, data, 0).ok()?, endian)
            }
            macho::MH_MAGIC_64 => {
                let endian = NativeEndian;
                let header = &*(header as *const macho::MachHeader64<NativeEndian>);
                let data = core::slice::from_raw_parts(
                    header as *const _ as *const u8,
                    mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize,
                );
                (header.load_commands(endian, data, 0).ok()?, endian)
            }
            _ => return None,
        }
    };

    // Iterate over the segments and register known regions for segments
    // that we find. Additionally record information bout text segments
    // for processing later, see comments below.
    let mut segments = Vec::new();
    let mut first_text = 0;
    let mut text_fileoff_zero = false;
    while let Some(cmd) = load_commands.next().ok()? {
        if let Some((seg, _)) = cmd.segment_32().ok()? {
            if seg.name() == b"__TEXT" {
                first_text = segments.len();
                if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 {
                    text_fileoff_zero = true;
                }
            }
            segments.push(LibrarySegment {
                len: seg.vmsize(endian).try_into().ok()?,
                stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?,
            });
        }
        if let Some((seg, _)) = cmd.segment_64().ok()? {
            if seg.name() == b"__TEXT" {
                first_text = segments.len();
                if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 {
                    text_fileoff_zero = true;
                }
            }
            segments.push(LibrarySegment {
                len: seg.vmsize(endian).try_into().ok()?,
                stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?,
            });
        }
    }

    // Determine the "slide" for this library which ends up being the
    // bias we use to figure out where in memory objects are loaded.
    // This is a bit of a weird computation though and is the result of
    // trying a few things in the wild and seeing what sticks.
    //
    // The general idea is that the `bias` plus a segment's
    // `stated_virtual_memory_address` is going to be where in the
    // actual address space the segment resides. The other thing we rely
    // on though is that a real address minus the `bias` is the index to
    // look up in the symbol table and debuginfo.
    //
    // It turns out, though, that for system loaded libraries these
    // calculations are incorrect. For native executables, however, it
    // appears correct. Lifting some logic from LLDB's source it has
    // some special-casing for the first `__TEXT` section loaded from
    // file offset 0 with a nonzero size. For whatever reason when this
    // is present it appears to mean that the symbol table is relative
    // to just the vmaddr slide for the library. If it's *not* present
    // then the symbol table is relative to the vmaddr slide plus the
    // segment's stated address.
    //
    // To handle this situation if we *don't* find a text section at
    // file offset zero then we increase the bias by the first text
    // sections's stated address and decrease all stated addresses by
    // that amount as well. That way the symbol table is always appears
    // relative to the library's bias amount. This appears to have the
    // right results for symbolizing via the symbol table.
    //
    // Honestly I'm not entirely sure whether this is right or if
    // there's something else that should indicate how to do this. For
    // now though this seems to work well enough (?) and we should
    // always be able to tweak this over time if necessary.
    //
    // For some more information see #318
    let mut slide = unsafe { libc::_dyld_get_image_vmaddr_slide(i) as usize };
    if !text_fileoff_zero {
        let adjust = segments[first_text].stated_virtual_memory_address;
        for segment in segments.iter_mut() {
            segment.stated_virtual_memory_address -= adjust;
        }
        slide += adjust;
    }

    Some(Library {
        name: OsStr::from_bytes(name.to_bytes()).to_owned(),
        segments,
        bias: slide,
    })
}