pub const Fde = struct { /// Includes 4byte size cell. offset: usize, size: usize, cie_index: u32, rel_index: u32 = 0, rel_num: u32 = 0, input_section_index: u32 = 0, file_index: u32 = 0, alive: bool = true, /// Includes 4byte size cell. out_offset: u64 = 0, pub fn address(fde: Fde, elf_file: *Elf) u64 { const base: u64 = if (elf_file.section_indexes.eh_frame) |shndx| elf_file.sections.items(.shdr)[shndx].sh_addr else 0; return base + fde.out_offset; } pub fn data(fde: Fde, object: *Object) []u8 { return object.eh_frame_data.items[fde.offset..][0..fde.calcSize()]; } pub fn cie(fde: Fde, object: *Object) Cie { return object.cies.items[fde.cie_index]; } pub fn ciePointer(fde: Fde, object: *Object) u32 { const fde_data = fde.data(object); return std.mem.readInt(u32, fde_data[4..8], .little); } pub fn calcSize(fde: Fde) usize { return fde.size + 4; } pub fn atom(fde: Fde, object: *Object) *Atom { const rel = fde.relocs(object)[0]; const sym = object.symtab.items[rel.r_sym()]; const atom_index = object.atoms_indexes.items[sym.st_shndx]; return object.atom(atom_index).?; } pub fn relocs(fde: Fde, object: *Object) []const elf.Elf64_Rela { return object.relocs.items[fde.rel_index..][0..fde.rel_num]; } pub fn fmt(fde: Fde, elf_file: *Elf) std.fmt.Alt(Format, Format.default) { return .{ .data = .{ .fde = fde, .elf_file = elf_file, } }; } const Format = struct { fde: Fde, elf_file: *Elf, fn default(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void { const fde = f.fde; const elf_file = f.elf_file; const base_addr = fde.address(elf_file); const object = elf_file.file(fde.file_index).?.object; const atom_name = fde.atom(object).name(elf_file); try writer.print("@{x} : size({x}) : cie({d}) : {s}", .{ base_addr + fde.out_offset, fde.calcSize(), fde.cie_index, atom_name, }); if (!fde.alive) try writer.writeAll(" : [*]"); } }; }; pub const Cie = struct { /// Includes 4byte size cell. offset: usize, size: usize, rel_index: u32 = 0, rel_num: u32 = 0, input_section_index: u32 = 0, file_index: u32 = 0, /// Includes 4byte size cell. out_offset: u64 = 0, alive: bool = false, pub fn address(cie: Cie, elf_file: *Elf) u64 { const base: u64 = if (elf_file.section_indexes.eh_frame) |shndx| elf_file.sections.items(.shdr)[shndx].sh_addr else 0; return base + cie.out_offset; } pub fn data(cie: Cie, elf_file: *Elf) []u8 { const object = elf_file.file(cie.file_index).?.object; return object.eh_frame_data.items[cie.offset..][0..cie.calcSize()]; } pub fn calcSize(cie: Cie) usize { return cie.size + 4; } pub fn relocs(cie: Cie, elf_file: *Elf) []align(1) const elf.Elf64_Rela { const object = elf_file.file(cie.file_index).?.object; return object.relocs.items[cie.rel_index..][0..cie.rel_num]; } pub fn eql(cie: Cie, other: Cie, elf_file: *Elf) bool { if (!std.mem.eql(u8, cie.data(elf_file), other.data(elf_file))) return false; const cie_relocs = cie.relocs(elf_file); const other_relocs = other.relocs(elf_file); if (cie_relocs.len != other_relocs.len) return false; for (cie_relocs, other_relocs) |cie_rel, other_rel| { if (cie_rel.r_offset - cie.offset != other_rel.r_offset - other.offset) return false; if (cie_rel.r_type() != other_rel.r_type()) return false; if (cie_rel.r_addend != other_rel.r_addend) return false; const cie_object = elf_file.file(cie.file_index).?.object; const cie_ref = cie_object.resolveSymbol(cie_rel.r_sym(), elf_file); const other_object = elf_file.file(other.file_index).?.object; const other_ref = other_object.resolveSymbol(other_rel.r_sym(), elf_file); if (!cie_ref.eql(other_ref)) return false; } return true; } pub fn fmt(cie: Cie, elf_file: *Elf) std.fmt.Alt(Format, Format.default) { return .{ .data = .{ .cie = cie, .elf_file = elf_file, } }; } const Format = struct { cie: Cie, elf_file: *Elf, fn default(f: Format, writer: *std.Io.Writer) std.Io.Writer.Error!void { const cie = f.cie; const elf_file = f.elf_file; const base_addr = cie.address(elf_file); try writer.print("@{x} : size({x})", .{ base_addr + cie.out_offset, cie.calcSize(), }); if (!cie.alive) try writer.writeAll(" : [*]"); } }; }; pub const Iterator = struct { data: []const u8, pos: usize = 0, pub const Record = struct { tag: enum { fde, cie }, offset: usize, size: usize, }; pub fn next(it: *Iterator) !?Record { if (it.pos >= it.data.len) return null; var reader: std.Io.Reader = .fixed(it.data[it.pos..]); const size = try reader.takeInt(u32, .little); if (size == 0) return null; if (size == 0xFFFFFFFF) @panic("TODO"); const id = try reader.takeInt(u32, .little); const record: Record = .{ .tag = if (id == 0) .cie else .fde, .offset = it.pos, .size = size, }; it.pos += size + 4; return record; } }; pub fn calcEhFrameSize(elf_file: *Elf) !usize { const comp = elf_file.base.comp; const gpa = comp.gpa; var offset: usize = if (elf_file.zigObjectPtr()) |zo| blk: { const sym = zo.symbol(zo.eh_frame_index orelse break :blk 0); break :blk math.cast(usize, sym.atom(elf_file).?.size) orelse return error.Overflow; } else 0; var cies = std.array_list.Managed(Cie).init(gpa); defer cies.deinit(); for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; outer: for (object.cies.items) |*cie| { for (cies.items) |other| { if (other.eql(cie.*, elf_file)) { // We already have a CIE record that has the exact same contents, so instead of // duplicating them, we mark this one dead and set its output offset to be // equal to that of the alive record. This way, we won't have to rewrite // Fde.cie_index field when committing the records to file. cie.out_offset = other.out_offset; continue :outer; } } cie.alive = true; cie.out_offset = offset; offset += cie.calcSize(); try cies.append(cie.*); } } for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; for (object.fdes.items) |*fde| { if (!fde.alive) continue; fde.out_offset = offset; offset += fde.calcSize(); } } if (!elf_file.base.isRelocatable()) { offset += 4; // NULL terminator } return offset; } fn haveEhFrameHdrSearchTable(elf_file: *Elf) bool { // Seach table generation is not implemented for the ZigObject. Also, it would be wasteful to // re-do this work on every single incremental update. return elf_file.zigObjectPtr() == null; } pub fn calcEhFrameHdrSize(elf_file: *Elf) usize { if (!haveEhFrameHdrSearchTable(elf_file)) return 8; var count: usize = 0; for (elf_file.objects.items) |index| { for (elf_file.file(index).?.object.fdes.items) |fde| { if (!fde.alive) continue; count += 1; } } return 12 + count * 8; } pub fn calcEhFrameRelocs(elf_file: *Elf) usize { var count: usize = 0; if (elf_file.zigObjectPtr()) |zo| zo: { const sym_index = zo.eh_frame_index orelse break :zo; const sym = zo.symbol(sym_index); const atom_ptr = zo.atom(sym.ref.index).?; if (!atom_ptr.alive) break :zo; count += atom_ptr.relocs(elf_file).len; } for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; for (object.cies.items) |cie| { if (!cie.alive) continue; count += cie.relocs(elf_file).len; } for (object.fdes.items) |fde| { if (!fde.alive) continue; count += fde.relocs(object).len; } } return count; } fn resolveReloc(rec: anytype, sym: *const Symbol, rel: elf.Elf64_Rela, elf_file: *Elf, contents: []u8) !void { const cpu_arch = elf_file.getTarget().cpu.arch; const offset = std.math.cast(usize, rel.r_offset - rec.offset) orelse return error.Overflow; const P = math.cast(i64, rec.address(elf_file) + offset) orelse return error.Overflow; const S = math.cast(i64, sym.address(.{}, elf_file)) orelse return error.Overflow; const A = rel.r_addend; relocs_log.debug(" {f}: {x}: [{x} => {x}] ({s})", .{ relocation.fmtRelocType(rel.r_type(), cpu_arch), offset, P, S + A, sym.name(elf_file), }); switch (cpu_arch) { .x86_64 => try x86_64.resolveReloc(rec, elf_file, rel, P, S + A, contents[offset..]), .aarch64, .aarch64_be => try aarch64.resolveReloc(rec, elf_file, rel, P, S + A, contents[offset..]), .riscv64, .riscv64be => try riscv.resolveReloc(rec, elf_file, rel, P, S + A, contents[offset..]), else => return error.UnsupportedCpuArch, } } pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void { relocs_log.debug("{x}: .eh_frame", .{ elf_file.sections.items(.shdr)[elf_file.section_indexes.eh_frame.?].sh_addr, }); var has_reloc_errors = false; for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; for (object.cies.items) |cie| { if (!cie.alive) continue; const contents = cie.data(elf_file); for (cie.relocs(elf_file)) |rel| { const ref = object.resolveSymbol(rel.r_sym(), elf_file); const sym = elf_file.symbol(ref).?; resolveReloc(cie, sym, rel, elf_file, contents) catch |err| switch (err) { error.RelocFailure => has_reloc_errors = true, else => |e| return e, }; } try writer.writeAll(contents); } } for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; for (object.fdes.items) |fde| { if (!fde.alive) continue; const contents = fde.data(object); std.mem.writeInt( i32, contents[4..8], @truncate(@as(i64, @intCast(fde.out_offset + 4)) - @as(i64, @intCast(fde.cie(object).out_offset))), .little, ); for (fde.relocs(object)) |rel| { const ref = object.resolveSymbol(rel.r_sym(), elf_file); const sym = elf_file.symbol(ref).?; resolveReloc(fde, sym, rel, elf_file, contents) catch |err| switch (err) { error.RelocFailure => has_reloc_errors = true, else => |e| return e, }; } try writer.writeAll(contents); } } try writer.writeInt(u32, 0, .little); if (has_reloc_errors) return error.RelocFailure; } pub fn writeEhFrameRelocatable(elf_file: *Elf, writer: anytype) !void { for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; for (object.cies.items) |cie| { if (!cie.alive) continue; try writer.writeAll(cie.data(elf_file)); } } for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; for (object.fdes.items) |fde| { if (!fde.alive) continue; const contents = fde.data(object); std.mem.writeInt( i32, contents[4..8], @truncate(@as(i64, @intCast(fde.out_offset + 4)) - @as(i64, @intCast(fde.cie(object).out_offset))), .little, ); try writer.writeAll(contents); } } } fn emitReloc(elf_file: *Elf, r_offset: u64, sym: *const Symbol, rel: elf.Elf64_Rela) elf.Elf64_Rela { const cpu_arch = elf_file.getTarget().cpu.arch; const r_type = rel.r_type(); var r_addend = rel.r_addend; var r_sym: u32 = 0; switch (sym.type(elf_file)) { elf.STT_SECTION => { r_addend += @intCast(sym.address(.{}, elf_file)); r_sym = sym.outputShndx(elf_file).?; }, else => { r_sym = sym.outputSymtabIndex(elf_file) orelse 0; }, } relocs_log.debug(" {f}: [{x} => {d}({s})] + {x}", .{ relocation.fmtRelocType(r_type, cpu_arch), r_offset, r_sym, sym.name(elf_file), r_addend, }); return .{ .r_offset = r_offset, .r_addend = r_addend, .r_info = (@as(u64, @intCast(r_sym)) << 32) | r_type, }; } pub fn writeEhFrameRelocs(elf_file: *Elf, relocs: *std.array_list.Managed(elf.Elf64_Rela)) !void { relocs_log.debug("{x}: .eh_frame", .{ elf_file.sections.items(.shdr)[elf_file.section_indexes.eh_frame.?].sh_addr, }); if (elf_file.zigObjectPtr()) |zo| zo: { const sym_index = zo.eh_frame_index orelse break :zo; const sym = zo.symbol(sym_index); const atom_ptr = zo.atom(sym.ref.index).?; if (!atom_ptr.alive) break :zo; for (atom_ptr.relocs(elf_file)) |rel| { const ref = zo.resolveSymbol(rel.r_sym(), elf_file); const target = elf_file.symbol(ref).?; try relocs.append(emitReloc(elf_file, rel.r_offset, target, rel)); } } for (elf_file.objects.items) |index| { const object = elf_file.file(index).?.object; for (object.cies.items) |cie| { if (!cie.alive) continue; for (cie.relocs(elf_file)) |rel| { const ref = object.resolveSymbol(rel.r_sym(), elf_file); const sym = elf_file.symbol(ref).?; const r_offset = cie.address(elf_file) + rel.r_offset - cie.offset; try relocs.append(emitReloc(elf_file, r_offset, sym, rel)); } } for (object.fdes.items) |fde| { if (!fde.alive) continue; for (fde.relocs(object)) |rel| { const ref = object.resolveSymbol(rel.r_sym(), elf_file); const sym = elf_file.symbol(ref).?; const r_offset = fde.address(elf_file) + rel.r_offset - fde.offset; try relocs.append(emitReloc(elf_file, r_offset, sym, rel)); } } } } pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void { const endian = elf_file.getTarget().cpu.arch.endian(); const have_table = haveEhFrameHdrSearchTable(elf_file); try writer.writeByte(1); // version try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .sdata4, .rel = .pcrel }))); // eh_frame_ptr_enc if (have_table) { try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .udata4, .rel = .abs }))); // fde_count_enc try writer.writeByte(@bitCast(@as(DW_EH_PE, .{ .type = .sdata4, .rel = .datarel }))); // table_enc } else { try writer.writeByte(@bitCast(DW_EH_PE.omit)); // fde_count_enc try writer.writeByte(@bitCast(DW_EH_PE.omit)); // table_enc } const shdrs = elf_file.sections.items(.shdr); const eh_frame_shdr = shdrs[elf_file.section_indexes.eh_frame.?]; const eh_frame_hdr_shdr = shdrs[elf_file.section_indexes.eh_frame_hdr.?]; // eh_frame_ptr try writer.writeInt( u32, @as(u32, @bitCast(@as( i32, @truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4), ))), .little, ); if (!have_table) return; const gpa = elf_file.base.comp.gpa; // This must be an `extern struct` because we will write the bytes directly to the file. const Entry = extern struct { first_pc_rel: i32, fde_addr_rel: i32, fn lessThan(_: void, lhs: @This(), rhs: @This()) bool { return lhs.first_pc_rel < rhs.first_pc_rel; } }; // The number of entries was already computed by `calcEhFrameHdrSize`. const num_fdes: u32 = @intCast(@divExact(eh_frame_hdr_shdr.sh_size - 12, 8)); try writer.writeInt(u32, num_fdes, endian); var entries: std.ArrayList(Entry) = try .initCapacity(gpa, num_fdes); defer entries.deinit(gpa); for (elf_file.objects.items) |file_index| { const object = elf_file.file(file_index).?.object; for (object.fdes.items) |fde| { if (!fde.alive) continue; const relocs = fde.relocs(object); // Should `relocs.len == 0` be an error? Things are completely broken anyhow in that case... const rel = relocs[0]; const ref = object.resolveSymbol(rel.r_sym(), elf_file); const sym = elf_file.symbol(ref).?; const fde_addr_abs: i64 = @intCast(fde.address(elf_file)); const fde_addr_rel: i64 = fde_addr_abs - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)); const first_pc_abs: i64 = @as(i64, @intCast(sym.address(.{}, elf_file))) + rel.r_addend; const first_pc_rel: i64 = first_pc_abs - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)); entries.appendAssumeCapacity(.{ .first_pc_rel = @truncate(first_pc_rel), .fde_addr_rel = @truncate(fde_addr_rel), }); } } assert(entries.items.len == num_fdes); std.mem.sort(Entry, entries.items, {}, Entry.lessThan); if (endian != builtin.cpu.arch.endian()) { std.mem.byteSwapAllElements(Entry, entries.items); } try writer.writeAll(@ptrCast(entries.items)); } const x86_64 = struct { fn resolveReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela, source: i64, target: i64, data: []u8) !void { const r_type: elf.R_X86_64 = @enumFromInt(rel.r_type()); switch (r_type) { .NONE => {}, .@"32" => std.mem.writeInt(i32, data[0..4], @as(i32, @truncate(target)), .little), .@"64" => std.mem.writeInt(i64, data[0..8], target, .little), .PC32 => std.mem.writeInt(i32, data[0..4], @as(i32, @intCast(target - source)), .little), .PC64 => std.mem.writeInt(i64, data[0..8], target - source, .little), else => try reportInvalidReloc(rec, elf_file, rel), } } }; const aarch64 = struct { fn resolveReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela, source: i64, target: i64, data: []u8) !void { const r_type: elf.R_AARCH64 = @enumFromInt(rel.r_type()); switch (r_type) { .NONE => {}, .ABS64 => std.mem.writeInt(i64, data[0..8], target, .little), .PREL32 => std.mem.writeInt(i32, data[0..4], @as(i32, @intCast(target - source)), .little), .PREL64 => std.mem.writeInt(i64, data[0..8], target - source, .little), else => try reportInvalidReloc(rec, elf_file, rel), } } }; const riscv = struct { fn resolveReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela, source: i64, target: i64, data: []u8) !void { const r_type: elf.R_RISCV = @enumFromInt(rel.r_type()); switch (r_type) { .NONE => {}, .@"32_PCREL" => std.mem.writeInt(i32, data[0..4], @as(i32, @intCast(target - source)), .little), else => try reportInvalidReloc(rec, elf_file, rel), } } }; fn reportInvalidReloc(rec: anytype, elf_file: *Elf, rel: elf.Elf64_Rela) !void { const diags = &elf_file.base.comp.link_diags; var err = try diags.addErrorWithNotes(1); try err.addMsg("invalid relocation type {f} at offset 0x{x}", .{ relocation.fmtRelocType(rel.r_type(), elf_file.getTarget().cpu.arch), rel.r_offset, }); err.addNote("in {f}:.eh_frame", .{elf_file.file(rec.file_index).?.fmtPath()}); return error.RelocFailure; } const std = @import("std"); const assert = std.debug.assert; const elf = std.elf; const math = std.math; const relocs_log = std.log.scoped(.link_relocs); const relocation = @import("relocation.zig"); const Allocator = std.mem.Allocator; const Atom = @import("Atom.zig"); const DW_EH_PE = std.dwarf.EH.PE; const Elf = @import("../Elf.zig"); const Object = @import("Object.zig"); const Symbol = @import("Symbol.zig"); const builtin = @import("builtin");