master
  1pub const Cie = struct {
  2    /// Includes 4byte size cell.
  3    offset: u32,
  4    out_offset: u32 = 0,
  5    size: u32,
  6    lsda_size: ?enum { p32, p64 } = null,
  7    personality: ?Personality = null,
  8    file: File.Index = 0,
  9    alive: bool = false,
 10
 11    pub fn parse(cie: *Cie, macho_file: *MachO) !void {
 12        const tracy = trace(@src());
 13        defer tracy.end();
 14
 15        const data = cie.getData(macho_file);
 16        const aug = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(data.ptr + 9)), 0);
 17
 18        if (aug[0] != 'z') return; // TODO should we error out?
 19
 20        var reader: std.Io.Reader = .fixed(data[9 + aug.len + 1 ..]);
 21
 22        _ = try reader.takeLeb128(u64); // code alignment factor
 23        _ = try reader.takeLeb128(u64); // data alignment factor
 24        _ = try reader.takeLeb128(u64); // return address register
 25        _ = try reader.takeLeb128(u64); // augmentation data length
 26
 27        for (aug[1..]) |ch| switch (ch) {
 28            'R' => {
 29                const enc: DW.EH.PE = @bitCast(try reader.takeByte());
 30                if (enc != @as(DW.EH.PE, .{ .type = .absptr, .rel = .pcrel })) {
 31                    @panic("unexpected pointer encoding"); // TODO error
 32                }
 33            },
 34            'P' => {
 35                const enc: DW.EH.PE = @bitCast(try reader.takeByte());
 36                if (enc != @as(DW.EH.PE, .{ .type = .sdata4, .rel = .pcrel, .indirect = true })) {
 37                    @panic("unexpected personality pointer encoding"); // TODO error
 38                }
 39                _ = try reader.takeInt(u32, .little); // personality pointer
 40            },
 41            'L' => {
 42                const enc: DW.EH.PE = @bitCast(try reader.takeByte());
 43                switch (enc.type) {
 44                    .sdata4 => cie.lsda_size = .p32,
 45                    .absptr => cie.lsda_size = .p64,
 46                    else => @panic("unexpected lsda encoding"), // TODO error
 47                }
 48            },
 49            else => @panic("unexpected augmentation string"), // TODO error
 50        };
 51    }
 52
 53    pub inline fn getSize(cie: Cie) u32 {
 54        return cie.size + 4;
 55    }
 56
 57    pub fn getObject(cie: Cie, macho_file: *MachO) *Object {
 58        const file = macho_file.getFile(cie.file).?;
 59        return file.object;
 60    }
 61
 62    pub fn getData(cie: Cie, macho_file: *MachO) []const u8 {
 63        const object = cie.getObject(macho_file);
 64        return object.eh_frame_data.items[cie.offset..][0..cie.getSize()];
 65    }
 66
 67    pub fn getPersonality(cie: Cie, macho_file: *MachO) ?*Symbol {
 68        const personality = cie.personality orelse return null;
 69        const object = cie.getObject(macho_file);
 70        return object.getSymbolRef(personality.index, macho_file).getSymbol(macho_file);
 71    }
 72
 73    pub fn eql(cie: Cie, other: Cie, macho_file: *MachO) bool {
 74        if (!std.mem.eql(u8, cie.getData(macho_file), other.getData(macho_file))) return false;
 75        if (cie.personality != null and other.personality != null) {
 76            if (cie.personality.?.index != other.personality.?.index) return false;
 77        }
 78        if (cie.personality != null or other.personality != null) return false;
 79        return true;
 80    }
 81
 82    pub fn fmt(cie: Cie, macho_file: *MachO) std.fmt.Alt(Format, Format.default) {
 83        return .{ .data = .{
 84            .cie = cie,
 85            .macho_file = macho_file,
 86        } };
 87    }
 88
 89    const Format = struct {
 90        cie: Cie,
 91        macho_file: *MachO,
 92
 93        fn default(f: Format, w: *Writer) Writer.Error!void {
 94            const cie = f.cie;
 95            try w.print("@{x} : size({x})", .{
 96                cie.offset,
 97                cie.getSize(),
 98            });
 99            if (!cie.alive) try w.writeAll(" : [*]");
100        }
101    };
102
103    pub const Index = u32;
104
105    pub const Personality = struct {
106        index: Symbol.Index = 0,
107        offset: u32 = 0,
108    };
109};
110
111pub const Fde = struct {
112    /// Includes 4byte size cell.
113    offset: u32,
114    out_offset: u32 = 0,
115    size: u32,
116    cie: Cie.Index,
117    atom: Atom.Index = 0,
118    atom_offset: u32 = 0,
119    lsda: Atom.Index = 0,
120    lsda_offset: u32 = 0,
121    lsda_ptr_offset: u32 = 0,
122    file: File.Index = 0,
123    alive: bool = true,
124
125    pub fn parse(fde: *Fde, macho_file: *MachO) !void {
126        const tracy = trace(@src());
127        defer tracy.end();
128
129        const data = fde.getData(macho_file);
130        const object = fde.getObject(macho_file);
131        const sect = object.sections.items(.header)[object.eh_frame_sect_index.?];
132
133        // Parse target atom index
134        const pc_begin = std.mem.readInt(i64, data[8..][0..8], .little);
135        const taddr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + 8)) + pc_begin);
136        fde.atom = object.findAtom(taddr) orelse {
137            try macho_file.reportParseError2(object.index, "{s},{s}: 0x{x}: invalid function reference in FDE", .{
138                sect.segName(), sect.sectName(), fde.offset + 8,
139            });
140            return error.MalformedObject;
141        };
142        const atom = fde.getAtom(macho_file);
143        fde.atom_offset = @intCast(taddr - atom.getInputAddress(macho_file));
144
145        // Associate with a CIE
146        const cie_ptr = std.mem.readInt(u32, data[4..8], .little);
147        const cie_offset = fde.offset + 4 - cie_ptr;
148        const cie_index = for (object.cies.items, 0..) |cie, cie_index| {
149            if (cie.offset == cie_offset) break @as(Cie.Index, @intCast(cie_index));
150        } else null;
151        if (cie_index) |cie| {
152            fde.cie = cie;
153        } else {
154            try macho_file.reportParseError2(object.index, "no matching CIE found for FDE at offset {x}", .{
155                fde.offset,
156            });
157            return error.MalformedObject;
158        }
159
160        const cie = fde.getCie(macho_file);
161
162        // Parse LSDA atom index if any
163        if (cie.lsda_size) |lsda_size| {
164            var reader: std.Io.Reader = .fixed(data);
165            reader.seek = 24;
166            _ = try reader.takeLeb128(u64); // augmentation length
167            fde.lsda_ptr_offset = @intCast(reader.seek);
168            const lsda_ptr = switch (lsda_size) {
169                .p32 => try reader.takeInt(i32, .little),
170                .p64 => try reader.takeInt(i64, .little),
171            };
172            const lsda_addr: u64 = @intCast(@as(i64, @intCast(sect.addr + fde.offset + fde.lsda_ptr_offset)) + lsda_ptr);
173            fde.lsda = object.findAtom(lsda_addr) orelse {
174                try macho_file.reportParseError2(object.index, "{s},{s}: 0x{x}: invalid LSDA reference in FDE", .{
175                    sect.segName(), sect.sectName(), fde.offset + fde.lsda_ptr_offset,
176                });
177                return error.MalformedObject;
178            };
179            const lsda_atom = fde.getLsdaAtom(macho_file).?;
180            fde.lsda_offset = @intCast(lsda_addr - lsda_atom.getInputAddress(macho_file));
181        }
182    }
183
184    pub inline fn getSize(fde: Fde) u32 {
185        return fde.size + 4;
186    }
187
188    pub fn getObject(fde: Fde, macho_file: *MachO) *Object {
189        const file = macho_file.getFile(fde.file).?;
190        return file.object;
191    }
192
193    pub fn getData(fde: Fde, macho_file: *MachO) []const u8 {
194        const object = fde.getObject(macho_file);
195        return object.eh_frame_data.items[fde.offset..][0..fde.getSize()];
196    }
197
198    pub fn getCie(fde: Fde, macho_file: *MachO) *const Cie {
199        const object = fde.getObject(macho_file);
200        return &object.cies.items[fde.cie];
201    }
202
203    pub fn getAtom(fde: Fde, macho_file: *MachO) *Atom {
204        return fde.getObject(macho_file).getAtom(fde.atom).?;
205    }
206
207    pub fn getLsdaAtom(fde: Fde, macho_file: *MachO) ?*Atom {
208        return fde.getObject(macho_file).getAtom(fde.lsda);
209    }
210
211    pub fn fmt(fde: Fde, macho_file: *MachO) std.fmt.Alt(Format, Format.default) {
212        return .{ .data = .{
213            .fde = fde,
214            .macho_file = macho_file,
215        } };
216    }
217
218    const Format = struct {
219        fde: Fde,
220        macho_file: *MachO,
221
222        fn default(f: Format, writer: *Writer) Writer.Error!void {
223            const fde = f.fde;
224            const macho_file = f.macho_file;
225            try writer.print("@{x} : size({x}) : cie({d}) : {s}", .{
226                fde.offset,
227                fde.getSize(),
228                fde.cie,
229                fde.getAtom(macho_file).getName(macho_file),
230            });
231            if (!fde.alive) try writer.writeAll(" : [*]");
232        }
233    };
234
235    pub const Index = u32;
236};
237
238pub const Iterator = struct {
239    data: []const u8,
240    pos: u32 = 0,
241
242    pub const Record = struct {
243        tag: enum { fde, cie },
244        offset: u32,
245        size: u32,
246    };
247
248    pub fn next(it: *Iterator) !?Record {
249        if (it.pos >= it.data.len) return null;
250
251        var reader: std.Io.Reader = .fixed(it.data[it.pos..]);
252
253        const size = try reader.takeInt(u32, .little);
254        if (size == 0xFFFFFFFF) @panic("DWARF CFI is 32bit on macOS");
255
256        const id = try reader.takeInt(u32, .little);
257        const record = Record{
258            .tag = if (id == 0) .cie else .fde,
259            .offset = it.pos,
260            .size = size,
261        };
262        it.pos += size + 4;
263
264        return record;
265    }
266};
267
268pub fn calcSize(macho_file: *MachO) !u32 {
269    const tracy = trace(@src());
270    defer tracy.end();
271
272    var offset: u32 = 0;
273
274    var cies = std.array_list.Managed(Cie).init(macho_file.base.comp.gpa);
275    defer cies.deinit();
276
277    for (macho_file.objects.items) |index| {
278        const object = macho_file.getFile(index).?.object;
279
280        outer: for (object.cies.items) |*cie| {
281            for (cies.items) |other| {
282                if (other.eql(cie.*, macho_file)) {
283                    // We already have a CIE record that has the exact same contents, so instead of
284                    // duplicating them, we mark this one dead and set its output offset to be
285                    // equal to that of the alive record. This way, we won't have to rewrite
286                    // Fde.cie_index field when committing the records to file.
287                    cie.out_offset = other.out_offset;
288                    continue :outer;
289                }
290            }
291            cie.alive = true;
292            cie.out_offset = offset;
293            offset += cie.getSize();
294            try cies.append(cie.*);
295        }
296    }
297
298    for (macho_file.objects.items) |index| {
299        const object = macho_file.getFile(index).?.object;
300        for (object.fdes.items) |*fde| {
301            if (!fde.alive) continue;
302            fde.out_offset = offset;
303            offset += fde.getSize();
304        }
305    }
306
307    return offset;
308}
309
310pub fn calcNumRelocs(macho_file: *MachO) u32 {
311    const tracy = trace(@src());
312    defer tracy.end();
313
314    var nreloc: u32 = 0;
315
316    for (macho_file.objects.items) |index| {
317        const object = macho_file.getFile(index).?.object;
318        for (object.cies.items) |cie| {
319            if (!cie.alive) continue;
320            if (cie.getPersonality(macho_file)) |_| {
321                nreloc += 1; // personality
322            }
323        }
324    }
325
326    return nreloc;
327}
328
329pub fn write(macho_file: *MachO, buffer: []u8) void {
330    const tracy = trace(@src());
331    defer tracy.end();
332
333    const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
334    const addend: i64 = switch (macho_file.getTarget().cpu.arch) {
335        .x86_64 => 4,
336        else => 0,
337    };
338
339    for (macho_file.objects.items) |index| {
340        const object = macho_file.getFile(index).?.object;
341        for (object.cies.items) |cie| {
342            if (!cie.alive) continue;
343
344            @memcpy(buffer[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file));
345
346            if (cie.getPersonality(macho_file)) |sym| {
347                const offset = cie.out_offset + cie.personality.?.offset;
348                const saddr = sect.addr + offset;
349                const taddr = sym.getGotAddress(macho_file);
350                std.mem.writeInt(
351                    i32,
352                    buffer[offset..][0..4],
353                    @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
354                    .little,
355                );
356            }
357        }
358    }
359
360    for (macho_file.objects.items) |index| {
361        const object = macho_file.getFile(index).?.object;
362        for (object.fdes.items) |fde| {
363            if (!fde.alive) continue;
364
365            @memcpy(buffer[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
366
367            {
368                const offset = fde.out_offset + 4;
369                const value = offset - fde.getCie(macho_file).out_offset;
370                std.mem.writeInt(u32, buffer[offset..][0..4], value, .little);
371            }
372
373            {
374                const offset = fde.out_offset + 8;
375                const saddr = sect.addr + offset;
376                const taddr = fde.getAtom(macho_file).getAddress(macho_file);
377                std.mem.writeInt(
378                    i64,
379                    buffer[offset..][0..8],
380                    @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
381                    .little,
382                );
383            }
384
385            if (fde.getLsdaAtom(macho_file)) |atom| {
386                const offset = fde.out_offset + fde.lsda_ptr_offset;
387                const saddr = sect.addr + offset;
388                const taddr = atom.getAddress(macho_file) + fde.lsda_offset;
389                switch (fde.getCie(macho_file).lsda_size.?) {
390                    .p32 => std.mem.writeInt(
391                        i32,
392                        buffer[offset..][0..4],
393                        @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
394                        .little,
395                    ),
396                    .p64 => std.mem.writeInt(
397                        i64,
398                        buffer[offset..][0..8],
399                        @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
400                        .little,
401                    ),
402                }
403            }
404        }
405    }
406}
407
408pub fn writeRelocs(macho_file: *MachO, code: []u8, relocs: []macho.relocation_info) error{Overflow}!void {
409    const tracy = trace(@src());
410    defer tracy.end();
411
412    const cpu_arch = macho_file.getTarget().cpu.arch;
413    const sect = macho_file.sections.items(.header)[macho_file.eh_frame_sect_index.?];
414    const addend: i64 = switch (cpu_arch) {
415        .x86_64 => 4,
416        else => 0,
417    };
418
419    var i: usize = 0;
420    for (macho_file.objects.items) |index| {
421        const object = macho_file.getFile(index).?.object;
422        for (object.cies.items) |cie| {
423            if (!cie.alive) continue;
424
425            @memcpy(code[cie.out_offset..][0..cie.getSize()], cie.getData(macho_file));
426
427            if (cie.getPersonality(macho_file)) |sym| {
428                const r_address = math.cast(i32, cie.out_offset + cie.personality.?.offset) orelse return error.Overflow;
429                const r_symbolnum = math.cast(u24, sym.getOutputSymtabIndex(macho_file).?) orelse return error.Overflow;
430                relocs[i] = .{
431                    .r_address = r_address,
432                    .r_symbolnum = r_symbolnum,
433                    .r_length = 2,
434                    .r_extern = 1,
435                    .r_pcrel = 1,
436                    .r_type = switch (cpu_arch) {
437                        .aarch64 => @intFromEnum(macho.reloc_type_arm64.ARM64_RELOC_POINTER_TO_GOT),
438                        .x86_64 => @intFromEnum(macho.reloc_type_x86_64.X86_64_RELOC_GOT),
439                        else => unreachable,
440                    },
441                };
442                i += 1;
443            }
444        }
445    }
446
447    for (macho_file.objects.items) |index| {
448        const object = macho_file.getFile(index).?.object;
449        for (object.fdes.items) |fde| {
450            if (!fde.alive) continue;
451
452            @memcpy(code[fde.out_offset..][0..fde.getSize()], fde.getData(macho_file));
453
454            {
455                const offset = fde.out_offset + 4;
456                const value = offset - fde.getCie(macho_file).out_offset;
457                std.mem.writeInt(u32, code[offset..][0..4], value, .little);
458            }
459
460            {
461                const offset = fde.out_offset + 8;
462                const saddr = sect.addr + offset;
463                const taddr = fde.getAtom(macho_file).getAddress(macho_file);
464                std.mem.writeInt(
465                    i64,
466                    code[offset..][0..8],
467                    @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
468                    .little,
469                );
470            }
471
472            if (fde.getLsdaAtom(macho_file)) |atom| {
473                const offset = fde.out_offset + fde.lsda_ptr_offset;
474                const saddr = sect.addr + offset;
475                const taddr = atom.getAddress(macho_file) + fde.lsda_offset;
476                switch (fde.getCie(macho_file).lsda_size.?) {
477                    .p32 => std.mem.writeInt(
478                        i32,
479                        code[offset..][0..4],
480                        @intCast(@as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)) + addend),
481                        .little,
482                    ),
483                    .p64 => std.mem.writeInt(
484                        i64,
485                        code[offset..][0..8],
486                        @as(i64, @intCast(taddr)) - @as(i64, @intCast(saddr)),
487                        .little,
488                    ),
489                }
490            }
491        }
492    }
493
494    assert(relocs.len == i);
495}
496
497const assert = std.debug.assert;
498const leb = std.leb;
499const macho = std.macho;
500const math = std.math;
501const mem = std.mem;
502const std = @import("std");
503const trace = @import("../../tracy.zig").trace;
504const Writer = std.Io.Writer;
505
506const Allocator = std.mem.Allocator;
507const Atom = @import("Atom.zig");
508const DW = std.dwarf;
509const File = @import("file.zig").File;
510const MachO = @import("../MachO.zig");
511const Object = @import("Object.zig");
512const Symbol = @import("Symbol.zig");