diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2023-01-20 18:26:21 +0100 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2023-01-20 18:43:16 +0100 |
| commit | 835a60a34fe2bc3d35e4524caee455a4743a5022 (patch) | |
| tree | 41e1c40e393397096209ee670304d2e7a510ec19 /src/link/MachO/UnwindInfo.zig | |
| parent | 74b72a766de96c7c70fc8a02d3e2ee3cd353f225 (diff) | |
| download | zig-835a60a34fe2bc3d35e4524caee455a4743a5022.tar.gz zig-835a60a34fe2bc3d35e4524caee455a4743a5022.zip | |
zld: parse, synthesise and emit unwind records
Diffstat (limited to 'src/link/MachO/UnwindInfo.zig')
| -rw-r--r-- | src/link/MachO/UnwindInfo.zig | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/src/link/MachO/UnwindInfo.zig b/src/link/MachO/UnwindInfo.zig new file mode 100644 index 0000000000..4f3860a72b --- /dev/null +++ b/src/link/MachO/UnwindInfo.zig @@ -0,0 +1,831 @@ +const UnwindInfo = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const eh_frame = @import("eh_frame.zig"); +const fs = std.fs; +const leb = std.leb; +const log = std.log.scoped(.unwind_info); +const macho = std.macho; +const math = std.math; +const mem = std.mem; +const trace = @import("../../tracy.zig").trace; + +const Allocator = mem.Allocator; +const Atom = @import("ZldAtom.zig"); +const AtomIndex = @import("zld.zig").AtomIndex; +const EhFrameRecord = eh_frame.EhFrameRecord; +const Object = @import("Object.zig"); +const SymbolWithLoc = @import("zld.zig").SymbolWithLoc; +const Zld = @import("zld.zig").Zld; + +const N_DEAD = @import("zld.zig").N_DEAD; + +gpa: Allocator, + +/// List of all unwind records gathered from all objects and sorted +/// by source function address. +records: std.ArrayListUnmanaged(macho.compact_unwind_entry) = .{}, +records_lookup: std.AutoHashMapUnmanaged(AtomIndex, RecordIndex) = .{}, + +/// List of all personalities referenced by either unwind info entries +/// or __eh_frame entries. +personalities: [max_personalities]SymbolWithLoc = undefined, +personalities_count: u2 = 0, + +/// List of common encodings sorted in descending order with the most common first. +common_encodings: [max_common_encodings]macho.compact_unwind_encoding_t = undefined, +common_encodings_count: u7 = 0, + +/// List of record indexes containing an LSDA pointer. +lsdas: std.ArrayListUnmanaged(RecordIndex) = .{}, +lsdas_lookup: std.AutoHashMapUnmanaged(RecordIndex, u32) = .{}, + +/// List of second level pages. +pages: std.ArrayListUnmanaged(Page) = .{}, + +const RecordIndex = u32; + +const max_personalities = 3; +const max_common_encodings = 127; +const max_compact_encodings = 256; + +const second_level_page_bytes = 0x1000; +const second_level_page_words = second_level_page_bytes / @sizeOf(u32); + +const max_regular_second_level_entries = + (second_level_page_bytes - @sizeOf(macho.unwind_info_regular_second_level_page_header)) / + @sizeOf(macho.unwind_info_regular_second_level_entry); + +const max_compressed_second_level_entries = + (second_level_page_bytes - @sizeOf(macho.unwind_info_compressed_second_level_page_header)) / + @sizeOf(u32); + +const compressed_entry_func_offset_mask = ~@as(u24, 0); + +const Page = struct { + kind: enum { regular, compressed }, + start: RecordIndex, + count: u16, + page_encodings: [max_compact_encodings]RecordIndex = undefined, + page_encodings_count: u8 = 0, + + fn appendPageEncoding(page: *Page, record_id: RecordIndex) void { + assert(page.page_encodings_count <= max_compact_encodings); + page.page_encodings[page.page_encodings_count] = record_id; + page.page_encodings_count += 1; + } + + fn getPageEncoding( + page: *const Page, + info: *const UnwindInfo, + enc: macho.compact_unwind_encoding_t, + ) ?u8 { + comptime var index: u8 = 0; + inline while (index < max_compact_encodings) : (index += 1) { + if (index >= page.page_encodings_count) return null; + const record_id = page.page_encodings[index]; + const record = info.records.items[record_id]; + if (record.compactUnwindEncoding == enc) { + return index; + } + } + return null; + } + + fn format( + page: *const Page, + comptime unused_format_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = page; + _ = unused_format_string; + _ = options; + _ = writer; + @compileError("do not format Page directly; use page.fmtDebug()"); + } + + const DumpCtx = struct { + page: *const Page, + info: *const UnwindInfo, + }; + + fn dump( + ctx: DumpCtx, + comptime unused_format_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + _ = options; + comptime assert(unused_format_string.len == 0); + try writer.writeAll("Page:\n"); + try writer.print(" kind: {s}\n", .{@tagName(ctx.page.kind)}); + try writer.print(" entries: {d} - {d}\n", .{ + ctx.page.start, + ctx.page.start + ctx.page.count, + }); + try writer.print(" encodings (count = {d})\n", .{ctx.page.page_encodings_count}); + for (ctx.page.page_encodings[0..ctx.page.page_encodings_count]) |record_id, i| { + const record = ctx.info.records.items[record_id]; + const enc = record.compactUnwindEncoding; + try writer.print(" {d}: 0x{x:0>8}\n", .{ ctx.info.common_encodings_count + i, enc }); + } + } + + fn fmtDebug(page: *const Page, info: *const UnwindInfo) std.fmt.Formatter(dump) { + return .{ .data = .{ + .page = page, + .info = info, + } }; + } + + fn write(page: *const Page, info: *const UnwindInfo, writer: anytype) !void { + switch (page.kind) { + .regular => { + try writer.writeStruct(macho.unwind_info_regular_second_level_page_header{ + .entryPageOffset = @sizeOf(macho.unwind_info_regular_second_level_page_header), + .entryCount = page.count, + }); + + for (info.records.items[page.start..][0..page.count]) |record| { + try writer.writeStruct(macho.unwind_info_regular_second_level_entry{ + .functionOffset = @intCast(u32, record.rangeStart), + .encoding = record.compactUnwindEncoding, + }); + } + }, + .compressed => { + const entry_offset = @sizeOf(macho.unwind_info_compressed_second_level_page_header) + + @intCast(u16, page.page_encodings_count) * @sizeOf(u32); + try writer.writeStruct(macho.unwind_info_compressed_second_level_page_header{ + .entryPageOffset = entry_offset, + .entryCount = page.count, + .encodingsPageOffset = @sizeOf( + macho.unwind_info_compressed_second_level_page_header, + ), + .encodingsCount = page.page_encodings_count, + }); + + for (page.page_encodings[0..page.page_encodings_count]) |record_id| { + const enc = info.records.items[record_id].compactUnwindEncoding; + try writer.writeIntLittle(u32, enc); + } + + assert(page.count > 0); + const first_entry = info.records.items[page.start]; + for (info.records.items[page.start..][0..page.count]) |record| { + const enc_index = blk: { + if (info.getCommonEncoding(record.compactUnwindEncoding)) |id| { + break :blk id; + } + const ncommon = info.common_encodings_count; + break :blk ncommon + page.getPageEncoding(info, record.compactUnwindEncoding).?; + }; + const compressed = macho.UnwindInfoCompressedEntry{ + .funcOffset = @intCast(u24, record.rangeStart - first_entry.rangeStart), + .encodingIndex = @intCast(u8, enc_index), + }; + try writer.writeStruct(compressed); + } + }, + } + } +}; + +pub fn deinit(info: *UnwindInfo) void { + info.records.deinit(info.gpa); + info.records_lookup.deinit(info.gpa); + info.pages.deinit(info.gpa); + info.lsdas.deinit(info.gpa); + info.lsdas_lookup.deinit(info.gpa); +} + +pub fn scanRelocs(zld: *Zld) !void { + if (zld.getSectionByName("__TEXT", "__unwind_info") == null) return; + + const cpu_arch = zld.options.target.cpu.arch; + for (zld.objects.items) |*object, object_id| { + const unwind_records = object.getUnwindRecords(); + for (object.exec_atoms.items) |atom_index| { + const record_id = object.unwind_records_lookup.get(atom_index) orelse continue; + if (object.unwind_relocs_lookup[record_id].dead) continue; + const record = unwind_records[record_id]; + if (!UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) { + if (getPersonalityFunctionReloc( + zld, + @intCast(u32, object_id), + record_id, + )) |rel| { + // Personality function; add GOT pointer. + const target = parseRelocTarget( + zld, + @intCast(u32, object_id), + rel, + mem.asBytes(&record), + @intCast(i32, record_id * @sizeOf(macho.compact_unwind_entry)), + ); + try Atom.addGotEntry(zld, target); + } + } + } + } +} + +pub fn collect(info: *UnwindInfo, zld: *Zld) !void { + if (zld.getSectionByName("__TEXT", "__unwind_info") == null) return; + + const cpu_arch = zld.options.target.cpu.arch; + + var records = std.ArrayList(macho.compact_unwind_entry).init(info.gpa); + defer records.deinit(); + + var atom_indexes = std.ArrayList(AtomIndex).init(info.gpa); + defer atom_indexes.deinit(); + + // TODO handle dead stripping + for (zld.objects.items) |*object, object_id| { + log.debug("collecting unwind records in {s} ({d})", .{ object.name, object_id }); + const unwind_records = object.getUnwindRecords(); + + // Contents of unwind records does not have to cover all symbol in executable section + // so we need insert them ourselves. + try records.ensureUnusedCapacity(object.exec_atoms.items.len); + try atom_indexes.ensureUnusedCapacity(object.exec_atoms.items.len); + + var it = object.getEhFrameRecordsIterator(); + + for (object.exec_atoms.items) |atom_index| { + var record = if (object.unwind_records_lookup.get(atom_index)) |record_id| blk: { + if (object.unwind_relocs_lookup[record_id].dead) continue; + var record = unwind_records[record_id]; + + if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) { + const fde_offset = object.eh_frame_records_lookup.get(atom_index).?; + it.seekTo(fde_offset); + const fde = (try it.next()).?; + const cie_ptr = fde.getCiePointer(); + const cie_offset = fde_offset + 4 - cie_ptr; + it.seekTo(cie_offset); + const cie = (try it.next()).?; + + if (cie.getPersonalityPointerReloc( + zld, + @intCast(u32, object_id), + cie_offset, + )) |target| { + const personality_index = info.getPersonalityFunction(target) orelse inner: { + const personality_index = info.personalities_count; + info.personalities[personality_index] = target; + info.personalities_count += 1; + break :inner personality_index; + }; + + record.personalityFunction = personality_index + 1; + UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1); + } + } else { + if (getPersonalityFunctionReloc( + zld, + @intCast(u32, object_id), + record_id, + )) |rel| { + const target = parseRelocTarget( + zld, + @intCast(u32, object_id), + rel, + mem.asBytes(&record), + @intCast(i32, record_id * @sizeOf(macho.compact_unwind_entry)), + ); + const personality_index = info.getPersonalityFunction(target) orelse inner: { + const personality_index = info.personalities_count; + info.personalities[personality_index] = target; + info.personalities_count += 1; + break :inner personality_index; + }; + + record.personalityFunction = personality_index + 1; + UnwindEncoding.setPersonalityIndex(&record.compactUnwindEncoding, personality_index + 1); + } + + if (getLsdaReloc(zld, @intCast(u32, object_id), record_id)) |rel| { + const target = parseRelocTarget( + zld, + @intCast(u32, object_id), + rel, + mem.asBytes(&record), + @intCast(i32, record_id * @sizeOf(macho.compact_unwind_entry)), + ); + record.lsda = @bitCast(u64, target); + } + } + break :blk record; + } else blk: { + const atom = zld.getAtom(atom_index); + const sym = zld.getSymbol(atom.getSymbolWithLoc()); + if (sym.n_desc == N_DEAD) continue; + break :blk nullRecord(); + }; + + const atom = zld.getAtom(atom_index); + const sym_loc = atom.getSymbolWithLoc(); + const sym = zld.getSymbol(sym_loc); + assert(sym.n_desc != N_DEAD); + record.rangeStart = sym.n_value; + record.rangeLength = @intCast(u32, atom.size); + + records.appendAssumeCapacity(record); + atom_indexes.appendAssumeCapacity(atom_index); + } + } + + // Fold records + try info.records.ensureTotalCapacity(info.gpa, records.items.len); + try info.records_lookup.ensureTotalCapacity(info.gpa, @intCast(u32, atom_indexes.items.len)); + + var maybe_prev: ?macho.compact_unwind_entry = null; + for (records.items) |record, i| { + const record_id = blk: { + if (maybe_prev) |prev| { + const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch); + if (is_dwarf or + (prev.compactUnwindEncoding != record.compactUnwindEncoding) or + (prev.personalityFunction != record.personalityFunction) or + record.lsda > 0) + { + const record_id = @intCast(RecordIndex, info.records.items.len); + info.records.appendAssumeCapacity(record); + maybe_prev = record; + break :blk record_id; + } else { + break :blk @intCast(RecordIndex, info.records.items.len - 1); + } + } else { + const record_id = @intCast(RecordIndex, info.records.items.len); + info.records.appendAssumeCapacity(record); + maybe_prev = record; + break :blk record_id; + } + }; + info.records_lookup.putAssumeCapacityNoClobber(atom_indexes.items[i], record_id); + } + + // Calculate common encodings + { + const CommonEncWithCount = struct { + enc: macho.compact_unwind_encoding_t, + count: u32, + + fn greaterThan(ctx: void, lhs: @This(), rhs: @This()) bool { + _ = ctx; + return lhs.count > rhs.count; + } + }; + + const Context = struct { + pub fn hash(ctx: @This(), key: macho.compact_unwind_encoding_t) u32 { + _ = ctx; + return key; + } + + pub fn eql( + ctx: @This(), + key1: macho.compact_unwind_encoding_t, + key2: macho.compact_unwind_encoding_t, + b_index: usize, + ) bool { + _ = ctx; + _ = b_index; + return key1 == key2; + } + }; + + var common_encodings_counts = std.ArrayHashMap( + macho.compact_unwind_encoding_t, + CommonEncWithCount, + Context, + false, + ).init(info.gpa); + defer common_encodings_counts.deinit(); + + for (info.records.items) |record| { + assert(!isNull(record)); + if (UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch)) continue; + const enc = record.compactUnwindEncoding; + const gop = try common_encodings_counts.getOrPut(enc); + if (!gop.found_existing) { + gop.value_ptr.* = .{ + .enc = enc, + .count = 0, + }; + } + gop.value_ptr.count += 1; + } + + var slice = common_encodings_counts.values(); + std.sort.sort(CommonEncWithCount, slice, {}, CommonEncWithCount.greaterThan); + + var i: u7 = 0; + while (i < slice.len) : (i += 1) { + if (i >= max_common_encodings) break; + if (slice[i].count < 2) continue; + info.appendCommonEncoding(slice[i].enc); + log.debug("adding common encoding: {d} => 0x{x:0>8}", .{ i, slice[i].enc }); + } + } + + // Compute page allocations + { + var i: u32 = 0; + while (i < info.records.items.len) { + const range_start_max: u64 = + info.records.items[i].rangeStart + compressed_entry_func_offset_mask; + var encoding_count: u9 = info.common_encodings_count; + var space_left: u32 = second_level_page_words - + @sizeOf(macho.unwind_info_compressed_second_level_page_header) / @sizeOf(u32); + var page = Page{ + .kind = undefined, + .start = i, + .count = 0, + }; + + while (space_left >= 1 and i < info.records.items.len) { + const record = info.records.items[i]; + const enc = record.compactUnwindEncoding; + const is_dwarf = UnwindEncoding.isDwarf(record.compactUnwindEncoding, cpu_arch); + + if (record.rangeStart >= range_start_max) { + break; + } else if (info.getCommonEncoding(enc) != null or + page.getPageEncoding(info, enc) != null and !is_dwarf) + { + i += 1; + space_left -= 1; + } else if (space_left >= 2 and encoding_count < max_compact_encodings) { + page.appendPageEncoding(i); + i += 1; + space_left -= 2; + encoding_count += 1; + } else { + break; + } + } + + page.count = @intCast(u16, i - page.start); + + if (i < info.records.items.len and page.count < max_regular_second_level_entries) { + page.kind = .regular; + page.count = @intCast(u16, @min( + max_regular_second_level_entries, + info.records.items.len - page.start, + )); + i = page.start + page.count; + } else { + page.kind = .compressed; + } + + log.debug("{}", .{page.fmtDebug(info)}); + + try info.pages.append(info.gpa, page); + } + } + + // Save indices of records requiring LSDA relocation + try info.lsdas_lookup.ensureTotalCapacity(info.gpa, @intCast(u32, info.records.items.len)); + for (info.records.items) |rec, i| { + info.lsdas_lookup.putAssumeCapacityNoClobber(@intCast(RecordIndex, i), @intCast(u32, info.lsdas.items.len)); + if (rec.lsda == 0) continue; + try info.lsdas.append(info.gpa, @intCast(RecordIndex, i)); + } +} + +pub fn calcSectionSize(info: UnwindInfo, zld: *Zld) !void { + const sect_id = zld.getSectionByName("__TEXT", "__unwind_info") orelse return; + const sect = &zld.sections.items(.header)[sect_id]; + sect.@"align" = 2; + sect.size = info.calcRequiredSize(); +} + +fn calcRequiredSize(info: UnwindInfo) usize { + var total_size: usize = 0; + total_size += @sizeOf(macho.unwind_info_section_header); + total_size += + @intCast(usize, info.common_encodings_count) * @sizeOf(macho.compact_unwind_encoding_t); + total_size += @intCast(usize, info.personalities_count) * @sizeOf(u32); + total_size += (info.pages.items.len + 1) * @sizeOf(macho.unwind_info_section_header_index_entry); + total_size += info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry); + total_size += info.pages.items.len * second_level_page_bytes; + return total_size; +} + +pub fn write(info: *UnwindInfo, zld: *Zld) !void { + const sect_id = zld.getSectionByName("__TEXT", "__unwind_info") orelse return; + const sect = &zld.sections.items(.header)[sect_id]; + const seg_id = zld.sections.items(.segment_index)[sect_id]; + const seg = zld.segments.items[seg_id]; + + const text_sect_id = zld.getSectionByName("__TEXT", "__text").?; + const text_sect = zld.sections.items(.header)[text_sect_id]; + + var personalities: [max_personalities]u32 = undefined; + const cpu_arch = zld.options.target.cpu.arch; + + log.debug("Personalities:", .{}); + for (info.personalities[0..info.personalities_count]) |target, i| { + const atom_index = zld.getGotAtomIndexForSymbol(target).?; + const atom = zld.getAtom(atom_index); + const sym = zld.getSymbol(atom.getSymbolWithLoc()); + personalities[i] = @intCast(u32, sym.n_value - seg.vmaddr); + log.debug(" {d}: 0x{x} ({s})", .{ i, personalities[i], zld.getSymbolName(target) }); + } + + for (info.records.items) |*rec| { + // Finalize missing address values + rec.rangeStart += text_sect.addr - seg.vmaddr; + if (rec.personalityFunction > 0) { + rec.personalityFunction = personalities[rec.personalityFunction - 1]; + } + + if (rec.compactUnwindEncoding > 0 and !UnwindEncoding.isDwarf(rec.compactUnwindEncoding, cpu_arch)) { + const lsda_target = @bitCast(SymbolWithLoc, rec.lsda); + if (lsda_target.getFile()) |_| { + const sym = zld.getSymbol(lsda_target); + rec.lsda = sym.n_value - seg.vmaddr; + } + } + } + + for (info.records.items) |record, i| { + log.debug("Unwind record at offset 0x{x}", .{i * @sizeOf(macho.compact_unwind_entry)}); + log.debug(" start: 0x{x}", .{record.rangeStart}); + log.debug(" length: 0x{x}", .{record.rangeLength}); + log.debug(" compact encoding: 0x{x:0>8}", .{record.compactUnwindEncoding}); + log.debug(" personality: 0x{x}", .{record.personalityFunction}); + log.debug(" LSDA: 0x{x}", .{record.lsda}); + } + + var buffer = std.ArrayList(u8).init(info.gpa); + defer buffer.deinit(); + + const size = info.calcRequiredSize(); + try buffer.ensureTotalCapacityPrecise(size); + + var cwriter = std.io.countingWriter(buffer.writer()); + const writer = cwriter.writer(); + + const common_encodings_offset: u32 = @sizeOf(macho.unwind_info_section_header); + const common_encodings_count: u32 = info.common_encodings_count; + const personalities_offset: u32 = common_encodings_offset + common_encodings_count * @sizeOf(u32); + const personalities_count: u32 = info.personalities_count; + const indexes_offset: u32 = personalities_offset + personalities_count * @sizeOf(u32); + const indexes_count: u32 = @intCast(u32, info.pages.items.len + 1); + + try writer.writeStruct(macho.unwind_info_section_header{ + .commonEncodingsArraySectionOffset = common_encodings_offset, + .commonEncodingsArrayCount = common_encodings_count, + .personalityArraySectionOffset = personalities_offset, + .personalityArrayCount = personalities_count, + .indexSectionOffset = indexes_offset, + .indexCount = indexes_count, + }); + + try writer.writeAll(mem.sliceAsBytes(info.common_encodings[0..info.common_encodings_count])); + try writer.writeAll(mem.sliceAsBytes(personalities[0..info.personalities_count])); + + const pages_base_offset = @intCast(u32, size - (info.pages.items.len * second_level_page_bytes)); + const lsda_base_offset = @intCast(u32, pages_base_offset - + (info.lsdas.items.len * @sizeOf(macho.unwind_info_section_header_lsda_index_entry))); + for (info.pages.items) |page, i| { + assert(page.count > 0); + const first_entry = info.records.items[page.start]; + try writer.writeStruct(macho.unwind_info_section_header_index_entry{ + .functionOffset = @intCast(u32, first_entry.rangeStart), + .secondLevelPagesSectionOffset = @intCast(u32, pages_base_offset + i * second_level_page_bytes), + .lsdaIndexArraySectionOffset = lsda_base_offset + + info.lsdas_lookup.get(page.start).? * @sizeOf(macho.unwind_info_section_header_lsda_index_entry), + }); + } + + const last_entry = info.records.items[info.records.items.len - 1]; + const sentinel_address = @intCast(u32, last_entry.rangeStart + last_entry.rangeLength); + try writer.writeStruct(macho.unwind_info_section_header_index_entry{ + .functionOffset = sentinel_address, + .secondLevelPagesSectionOffset = 0, + .lsdaIndexArraySectionOffset = lsda_base_offset + + @intCast(u32, info.lsdas.items.len) * @sizeOf(macho.unwind_info_section_header_lsda_index_entry), + }); + + for (info.lsdas.items) |record_id| { + const record = info.records.items[record_id]; + try writer.writeStruct(macho.unwind_info_section_header_lsda_index_entry{ + .functionOffset = @intCast(u32, record.rangeStart), + .lsdaOffset = @intCast(u32, record.lsda), + }); + } + + for (info.pages.items) |page| { + const start = cwriter.bytes_written; + try page.write(info, writer); + const nwritten = cwriter.bytes_written - start; + if (nwritten < second_level_page_bytes) { + try writer.writeByteNTimes(0, second_level_page_bytes - nwritten); + } + } + + const padding = buffer.items.len - cwriter.bytes_written; + if (padding > 0) { + mem.set(u8, buffer.items[cwriter.bytes_written..], 0); + } + + try zld.file.pwriteAll(buffer.items, sect.offset); +} + +pub fn parseRelocTarget( + zld: *Zld, + object_id: u32, + rel: macho.relocation_info, + code: []const u8, + base_offset: i32, +) SymbolWithLoc { + const tracy = trace(@src()); + defer tracy.end(); + + const object = &zld.objects.items[object_id]; + + const sym_index = if (rel.r_extern == 0) blk: { + const sect_id = @intCast(u8, rel.r_symbolnum - 1); + const rel_offset = @intCast(u32, rel.r_address - base_offset); + assert(rel.r_pcrel == 0 and rel.r_length == 3); + const address_in_section = mem.readIntLittle(u64, code[rel_offset..][0..8]); + const sym_index = object.getSymbolByAddress(address_in_section, sect_id); + break :blk sym_index; + } else object.reverse_symtab_lookup[rel.r_symbolnum]; + + const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = object_id + 1 }; + const sym = zld.getSymbol(sym_loc); + + if (sym.sect() and !sym.ext()) { + // Make sure we are not dealing with a local alias. + const atom_index = object.getAtomIndexForSymbol(sym_index) orelse + return sym_loc; + const atom = zld.getAtom(atom_index); + return atom.getSymbolWithLoc(); + } else if (object.getGlobal(sym_index)) |global_index| { + return zld.globals.items[global_index]; + } else return sym_loc; +} + +fn getRelocs( + zld: *Zld, + object_id: u32, + record_id: usize, +) []align(1) const macho.relocation_info { + const object = &zld.objects.items[object_id]; + assert(object.hasUnwindRecords()); + const rel_pos = object.unwind_relocs_lookup[record_id].reloc; + const relocs = object.getRelocs(object.unwind_info_sect.?); + return relocs[rel_pos.start..][0..rel_pos.len]; +} + +fn isPersonalityFunction(record_id: usize, rel: macho.relocation_info) bool { + const base_offset = @intCast(i32, record_id * @sizeOf(macho.compact_unwind_entry)); + const rel_offset = rel.r_address - base_offset; + return rel_offset == 16; +} + +pub fn getPersonalityFunctionReloc( + zld: *Zld, + object_id: u32, + record_id: usize, +) ?macho.relocation_info { + const relocs = getRelocs(zld, object_id, record_id); + for (relocs) |rel| { + if (isPersonalityFunction(record_id, rel)) return rel; + } + return null; +} + +fn getPersonalityFunction(info: UnwindInfo, global_index: SymbolWithLoc) ?u2 { + comptime var index: u2 = 0; + inline while (index < max_personalities) : (index += 1) { + if (index >= info.personalities_count) return null; + if (info.personalities[index].eql(global_index)) { + return index; + } + } + return null; +} + +fn isLsda(record_id: usize, rel: macho.relocation_info) bool { + const base_offset = @intCast(i32, record_id * @sizeOf(macho.compact_unwind_entry)); + const rel_offset = rel.r_address - base_offset; + return rel_offset == 24; +} + +pub fn getLsdaReloc(zld: *Zld, object_id: u32, record_id: usize) ?macho.relocation_info { + const relocs = getRelocs(zld, object_id, record_id); + for (relocs) |rel| { + if (isLsda(record_id, rel)) return rel; + } + return null; +} + +pub fn isNull(rec: macho.compact_unwind_entry) bool { + return rec.rangeStart == 0 and + rec.rangeLength == 0 and + rec.compactUnwindEncoding == 0 and + rec.lsda == 0 and + rec.personalityFunction == 0; +} + +inline fn nullRecord() macho.compact_unwind_entry { + return .{ + .rangeStart = 0, + .rangeLength = 0, + .compactUnwindEncoding = 0, + .personalityFunction = 0, + .lsda = 0, + }; +} + +fn appendCommonEncoding(info: *UnwindInfo, enc: macho.compact_unwind_encoding_t) void { + assert(info.common_encodings_count <= max_common_encodings); + info.common_encodings[info.common_encodings_count] = enc; + info.common_encodings_count += 1; +} + +fn getCommonEncoding(info: UnwindInfo, enc: macho.compact_unwind_encoding_t) ?u7 { + comptime var index: u7 = 0; + inline while (index < max_common_encodings) : (index += 1) { + if (index >= info.common_encodings_count) return null; + if (info.common_encodings[index] == enc) { + return index; + } + } + return null; +} + +pub const UnwindEncoding = struct { + pub const UNWIND_X86_64_MODE = enum(u4) { + none = 0, + ebp_frame = 1, + stack_immd = 2, + stack_ind = 3, + dwarf = 4, + }; + + pub const UNWIND_ARM64_MODE = enum(u4) { + none = 0, + frameless = 2, + dwarf = 3, + frame = 4, + }; + + pub const UNWIND_MODE_MASK: u32 = 0x0F000000; + pub const UNWIND_PERSONALITY_INDEX_MASK: u32 = 0x30000000; + pub const UNWIND_HAS_LSDA_MASK: u32 = 0x40000000; + + pub fn getMode(enc: macho.compact_unwind_encoding_t) u4 { + const mode = @truncate(u4, (enc & UNWIND_MODE_MASK) >> 24); + return mode; + } + + pub fn isDwarf(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) bool { + switch (cpu_arch) { + .aarch64 => return @intToEnum(UNWIND_ARM64_MODE, getMode(enc)) == .dwarf, + .x86_64 => return @intToEnum(UNWIND_X86_64_MODE, getMode(enc)) == .dwarf, + else => unreachable, + } + } + + pub fn hasLsda(enc: macho.compact_unwind_encoding_t) bool { + const has_lsda = @truncate(u1, (enc & UNWIND_HAS_LSDA_MASK) >> 31); + return has_lsda == 1; + } + + pub fn setHasLsda(enc: *macho.compact_unwind_encoding_t, has_lsda: bool) void { + const mask = @intCast(u32, @boolToInt(has_lsda)) << 31; + enc.* |= mask; + } + + pub fn getPersonalityIndex(enc: macho.compact_unwind_encoding_t) u2 { + const index = @truncate(u2, (enc & UNWIND_PERSONALITY_INDEX_MASK) >> 28); + return index; + } + + pub fn setPersonalityIndex(enc: *macho.compact_unwind_encoding_t, index: u2) void { + const mask = @intCast(u32, index) << 28; + enc.* |= mask; + } + + pub fn getDwarfSectionOffset(enc: macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch) u24 { + assert(isDwarf(enc, cpu_arch)); + const offset = @truncate(u24, enc); + return offset; + } + + pub fn setDwarfSectionOffset(enc: *macho.compact_unwind_encoding_t, cpu_arch: std.Target.Cpu.Arch, offset: u24) void { + assert(isDwarf(enc.*, cpu_arch)); + enc.* |= offset; + } +}; |
