aboutsummaryrefslogtreecommitdiff
path: root/std/macho.zig
blob: ddc4d334e4edb91e3366fde98b3c6595c1e10f11 (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
const builtin = @import("builtin");
const std = @import("index.zig");
const io = std.io;
const mem = std.mem;

const MH_MAGIC_64 = 0xFEEDFACF;
const MH_PIE = 0x200000;
const LC_SYMTAB = 2;

const MachHeader64 = packed struct {
    magic: u32,
    cputype: u32,
    cpusubtype: u32,
    filetype: u32,
    ncmds: u32,
    sizeofcmds: u32,
    flags: u32,
    reserved: u32,
};

const LoadCommand = packed struct {
    cmd: u32,
    cmdsize: u32,
};

const SymtabCommand = packed struct {
    symoff: u32,
    nsyms: u32,
    stroff: u32,
    strsize: u32,
};

const Nlist64 = packed struct {
    n_strx: u32,
    n_type: u8,
    n_sect: u8,
    n_desc: u16,
    n_value: u64,
};

pub const Symbol = struct {
    name: []const u8,
    address: u64,

    fn addressLessThan(lhs: Symbol, rhs: Symbol) bool {
        return lhs.address < rhs.address;
    }
};

pub const SymbolTable = struct {
    allocator: *mem.Allocator,
    symbols: []const Symbol,
    strings: []const u8,

    // Doubles as an eyecatcher to calculate the PIE slide, see loadSymbols().
    // Ideally we'd use _mh_execute_header because it's always at 0x100000000
    // in the image but as it's located in a different section than executable
    // code, its displacement is different.
    pub fn deinit(self: *SymbolTable) void {
        self.allocator.free(self.symbols);
        self.symbols = []const Symbol{};

        self.allocator.free(self.strings);
        self.strings = []const u8{};
    }

    pub fn search(self: *const SymbolTable, address: usize) ?*const Symbol {
        var min: usize = 0;
        var max: usize = self.symbols.len - 1; // Exclude sentinel.
        while (min < max) {
            const mid = min + (max - min) / 2;
            const curr = &self.symbols[mid];
            const next = &self.symbols[mid + 1];
            if (address >= next.address) {
                min = mid + 1;
            } else if (address < curr.address) {
                max = mid;
            } else {
                return curr;
            }
        }
        return null;
    }
};

pub fn loadSymbols(allocator: *mem.Allocator, in: *io.FileInStream) !SymbolTable {
    var file = in.file;
    try file.seekTo(0);

    var hdr: MachHeader64 = undefined;
    try readOneNoEof(in, MachHeader64, &hdr);
    if (hdr.magic != MH_MAGIC_64) return error.MissingDebugInfo;
    const is_pie = MH_PIE == (hdr.flags & MH_PIE);

    var pos: usize = @sizeOf(@typeOf(hdr));
    var ncmd: u32 = hdr.ncmds;
    while (ncmd != 0) : (ncmd -= 1) {
        try file.seekTo(pos);
        var lc: LoadCommand = undefined;
        try readOneNoEof(in, LoadCommand, &lc);
        if (lc.cmd == LC_SYMTAB) break;
        pos += lc.cmdsize;
    } else {
        return error.MissingDebugInfo;
    }

    var cmd: SymtabCommand = undefined;
    try readOneNoEof(in, SymtabCommand, &cmd);

    try file.seekTo(cmd.symoff);
    var syms = try allocator.alloc(Nlist64, cmd.nsyms);
    defer allocator.free(syms);
    try readNoEof(in, Nlist64, syms);

    try file.seekTo(cmd.stroff);
    var strings = try allocator.alloc(u8, cmd.strsize);
    errdefer allocator.free(strings);
    try in.stream.readNoEof(strings);

    var nsyms: usize = 0;
    for (syms) |sym|
        if (isSymbol(sym)) nsyms += 1;
    if (nsyms == 0) return error.MissingDebugInfo;

    var symbols = try allocator.alloc(Symbol, nsyms + 1); // Room for sentinel.
    errdefer allocator.free(symbols);

    var pie_slide: usize = 0;
    var nsym: usize = 0;
    for (syms) |sym| {
        if (!isSymbol(sym)) continue;
        const start = sym.n_strx;
        const end = mem.indexOfScalarPos(u8, strings, start, 0).?;
        const name = strings[start..end];
        const address = sym.n_value;
        symbols[nsym] = Symbol{ .name = name, .address = address };
        nsym += 1;
        if (is_pie and mem.eql(u8, name, "_SymbolTable_deinit")) {
            pie_slide = @ptrToInt(SymbolTable.deinit) - address;
        }
    }

    // Effectively a no-op, lld emits symbols in ascending order.
    std.sort.sort(Symbol, symbols[0..nsyms], Symbol.addressLessThan);

    // Insert the sentinel.  Since we don't know where the last function ends,
    // we arbitrarily limit it to the start address + 4 KB.
    const top = symbols[nsyms - 1].address + 4096;
    symbols[nsyms] = Symbol{ .name = "", .address = top };

    if (pie_slide != 0) {
        for (symbols) |*symbol|
            symbol.address += pie_slide;
    }

    return SymbolTable{
        .allocator = allocator,
        .symbols = symbols,
        .strings = strings,
    };
}

fn readNoEof(in: *io.FileInStream, comptime T: type, result: []T) !void {
    return in.stream.readNoEof(@sliceToBytes(result));
}
fn readOneNoEof(in: *io.FileInStream, comptime T: type, result: *T) !void {
    return readNoEof(in, T, (*[1]T)(result)[0..]);
}

fn isSymbol(sym: *const Nlist64) bool {
    return sym.n_value != 0 and sym.n_desc == 0;
}