diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2023-10-04 12:06:52 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2023-10-16 19:33:04 +0200 |
| commit | d6cec5a586c55f471d3f830f34d7a5f668337fb9 (patch) | |
| tree | 65080226790b92497314415963eb2f132c2c3f13 /src | |
| parent | 66f34b15e8573d669ef99d47337162f2aa91d602 (diff) | |
| download | zig-d6cec5a586c55f471d3f830f34d7a5f668337fb9.tar.gz zig-d6cec5a586c55f471d3f830f34d7a5f668337fb9.zip | |
elf: add more prepwork for linking c++ objects
Diffstat (limited to 'src')
| -rw-r--r-- | src/link/Elf.zig | 169 | ||||
| -rw-r--r-- | src/link/Elf/Atom.zig | 569 | ||||
| -rw-r--r-- | src/link/Elf/Object.zig | 29 | ||||
| -rw-r--r-- | src/link/Elf/Symbol.zig | 40 | ||||
| -rw-r--r-- | src/link/Elf/synthetic_sections.zig | 10 |
5 files changed, 710 insertions, 107 deletions
diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 2266bad251..cd9c2e3cba 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -22,6 +22,9 @@ shdrs: std.ArrayListUnmanaged(elf.Elf64_Shdr) = .{}, phdr_to_shdr_table: std.AutoHashMapUnmanaged(u16, u16) = .{}, /// File offset into the shdr table. shdr_table_offset: ?u64 = null, +/// Table of lists of atoms per output section. +/// This table is not used to track incrementally generated atoms. +output_sections: std.AutoArrayHashMapUnmanaged(u16, std.ArrayListUnmanaged(Atom.Index)) = .{}, /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. @@ -61,6 +64,7 @@ strtab: StringTable(.strtab) = .{}, /// Representation of the GOT table as committed to the file. got: GotSection = .{}, +rela_dyn: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{}, /// Tracked section headers text_section_index: ?u16 = null, @@ -109,6 +113,9 @@ symbols_extra: std.ArrayListUnmanaged(u32) = .{}, resolver: std.AutoArrayHashMapUnmanaged(u32, Symbol.Index) = .{}, symbols_free_list: std.ArrayListUnmanaged(Symbol.Index) = .{}, +has_text_reloc: bool = false, +num_ifunc_dynrelocs: usize = 0, + phdr_table_dirty: bool = false, shdr_table_dirty: bool = false, @@ -317,6 +324,10 @@ pub fn deinit(self: *Elf) void { self.shdrs.deinit(gpa); self.phdr_to_shdr_table.deinit(gpa); self.phdrs.deinit(gpa); + for (self.output_sections.values()) |*list| { + list.deinit(gpa); + } + self.output_sections.deinit(gpa); self.shstrtab.deinit(gpa); self.strtab.deinit(gpa); self.symbols.deinit(gpa); @@ -358,6 +369,7 @@ pub fn deinit(self: *Elf) void { self.comdat_groups.deinit(gpa); self.comdat_groups_owners.deinit(gpa); self.comdat_groups_table.deinit(gpa); + self.rela_dyn.deinit(gpa); } pub fn getDeclVAddr(self: *Elf, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo) !u64 { @@ -1249,6 +1261,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node for (self.objects.items) |index| { try self.file(index).?.object.addAtomsToOutputSections(self); } + try self.sortInitFini(); try self.updateSectionSizes(); try self.allocateSections(); @@ -1316,7 +1329,13 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node const code = try zig_module.codeAlloc(self, atom_index); defer gpa.free(code); const file_offset = shdr.sh_offset + atom_ptr.value - shdr.sh_addr; - try atom_ptr.resolveRelocsAlloc(self, code); + atom_ptr.resolveRelocsAlloc(self, code) catch |err| switch (err) { + // TODO + error.RelaxFail, error.InvalidInstruction, error.CannotEncode => { + log.err("relaxing intructions failed; TODO this should be a fatal linker error", .{}); + }, + else => |e| return e, + }; try self.base.file.?.pwriteAll(code, file_offset); } @@ -3460,6 +3479,69 @@ fn initSections(self: *Elf) !void { } } +fn sortInitFini(self: *Elf) !void { + const gpa = self.base.allocator; + + const Entry = struct { + priority: i32, + atom_index: Atom.Index, + + pub fn lessThan(ctx: void, lhs: @This(), rhs: @This()) bool { + _ = ctx; + return lhs.priority < rhs.priority; + } + }; + + for (self.shdrs.items, 0..) |*shdr, shndx| { + if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue; + + var is_init_fini = false; + var is_ctor_dtor = false; + switch (shdr.sh_type) { + elf.SHT_PREINIT_ARRAY, + elf.SHT_INIT_ARRAY, + elf.SHT_FINI_ARRAY, + => is_init_fini = true, + else => { + const name = self.shstrtab.getAssumeExists(shdr.sh_name); + is_ctor_dtor = mem.indexOf(u8, name, ".ctors") != null or mem.indexOf(u8, name, ".dtors") != null; + }, + } + + if (!is_init_fini and !is_ctor_dtor) continue; + + const atom_list = self.output_sections.getPtr(@intCast(shndx)) orelse continue; + + var entries = std.ArrayList(Entry).init(gpa); + try entries.ensureTotalCapacityPrecise(atom_list.items.len); + defer entries.deinit(); + + for (atom_list.items) |atom_index| { + const atom_ptr = self.atom(atom_index).?; + const object = atom_ptr.file(self).?.object; + const priority = blk: { + if (is_ctor_dtor) { + if (mem.indexOf(u8, object.path, "crtbegin") != null) break :blk std.math.minInt(i32); + if (mem.indexOf(u8, object.path, "crtend") != null) break :blk std.math.maxInt(i32); + } + const default: i32 = if (is_ctor_dtor) -1 else std.math.maxInt(i32); + const name = atom_ptr.name(self); + var it = mem.splitBackwards(u8, name, "."); + const priority = std.fmt.parseUnsigned(u16, it.first(), 10) catch default; + break :blk priority; + }; + entries.appendAssumeCapacity(.{ .priority = priority, .atom_index = atom_index }); + } + + mem.sort(Entry, entries.items, {}, Entry.lessThan); + + atom_list.clearRetainingCapacity(); + for (entries.items) |entry| { + atom_list.appendAssumeCapacity(entry.atom_index); + } + } +} + fn sectionRank(self: *Elf, shdr: elf.Elf64_Shdr) u8 { const name = self.shstrtab.getAssumeExists(shdr.sh_name); const flags = shdr.sh_flags; @@ -3926,6 +4008,8 @@ fn writeAtoms(self: *Elf) !void { if (shdr.sh_type == elf.SHT_NULL) continue; if (shdr.sh_type == elf.SHT_NOBITS) continue; + const atom_list = self.output_sections.get(@intCast(shndx)) orelse continue; + log.debug("writing atoms in '{s}' section", .{self.shstrtab.getAssumeExists(shdr.sh_name)}); const buffer = try gpa.alloc(u8, shdr.sh_size); @@ -3937,8 +4021,32 @@ fn writeAtoms(self: *Elf) !void { 0; @memset(buffer, padding_byte); - for (self.objects.items) |index| { - try self.file(index).?.object.writeAtoms(self, @intCast(shndx), buffer, &undefs); + for (atom_list.items) |atom_index| { + const atom_ptr = self.atom(atom_index).?; + assert(atom_ptr.flags.alive); + + const object = atom_ptr.file(self).?.object; + const offset = atom_ptr.value - shdr.sh_addr; + + log.debug("writing atom({d}) at 0x{x}", .{ atom_index, shdr.sh_offset + offset }); + + // TODO decompress directly into provided buffer + const out_code = buffer[offset..][0..atom_ptr.size]; + const in_code = try object.codeDecompressAlloc(self, atom_index); + defer gpa.free(in_code); + @memcpy(out_code, in_code); + + if (shdr.sh_flags & elf.SHF_ALLOC == 0) { + try atom_ptr.resolveRelocsNonAlloc(self, out_code, &undefs); + } else { + atom_ptr.resolveRelocsAlloc(self, out_code) catch |err| switch (err) { + // TODO + error.RelaxFail, error.InvalidInstruction, error.CannotEncode => { + log.err("relaxing intructions failed; TODO this should be a fatal linker error", .{}); + }, + else => |e| return e, + }; + } } try self.base.file.?.pwriteAll(buffer, shdr.sh_offset); @@ -4495,9 +4603,58 @@ pub fn sectionByName(self: *Elf, name: [:0]const u8) ?u16 { } else return null; } -pub fn calcNumIRelativeRelocs(self: *Elf) u64 { - _ = self; - unreachable; // TODO +const RelaDyn = struct { + offset: u64, + sym: u64 = 0, + type: u32, + addend: i64 = 0, +}; + +pub fn addRelaDyn(self: *Elf, opts: RelaDyn) !void { + try self.rela_dyn.ensureUnusedCapacity(self.base.alloctor, 1); + self.addRelaDynAssumeCapacity(opts); +} + +pub fn addRelaDynAssumeCapacity(self: *Elf, opts: RelaDyn) void { + self.rela_dyn.appendAssumeCapacity(.{ + .r_offset = opts.offset, + .r_info = (opts.sym << 32) | opts.type, + .r_addend = opts.addend, + }); +} + +fn sortRelaDyn(self: *Elf) void { + const Sort = struct { + fn rank(rel: elf.Elf64_Rela) u2 { + return switch (rel.r_type()) { + elf.R_X86_64_RELATIVE => 0, + elf.R_X86_64_IRELATIVE => 2, + else => 1, + }; + } + + pub fn lessThan(ctx: void, lhs: elf.Elf64_Rela, rhs: elf.Elf64_Rela) bool { + _ = ctx; + if (rank(lhs) == rank(rhs)) { + if (lhs.r_sym() == rhs.r_sym()) return lhs.r_offset < rhs.r_offset; + return lhs.r_sym() < rhs.r_sym(); + } + return rank(lhs) < rank(rhs); + } + }; + mem.sort(elf.Elf64_Rela, self.rela_dyn.items, {}, Sort.lessThan); +} + +fn calcNumIRelativeRelocs(self: *Elf) usize { + var count: usize = self.num_ifunc_dynrelocs; + + for (self.got.entries.items) |entry| { + if (entry.tag != .got) continue; + const sym = self.symbol(entry.symbol_index); + if (sym.isIFunc(self)) count += 1; + } + + return count; } pub fn atom(self: *Elf, atom_index: Atom.Index) ?*Atom { diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 802b1dc1b7..3420833530 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -316,6 +316,7 @@ pub fn scanRelocsRequiresCode(self: Atom, elf_file: *Elf) bool { } pub fn scanRelocs(self: Atom, elf_file: *Elf, code: ?[]const u8, undefs: anytype) !void { + const is_static = elf_file.isStatic(); const is_dyn_lib = elf_file.isDynLib(); const file_ptr = self.file(elf_file).?; const rels = self.relocs(elf_file); @@ -348,14 +349,23 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, code: ?[]const u8, undefs: anytype // Report an undefined symbol. try self.reportUndefined(elf_file, symbol, symbol_index, rel, undefs); + if (symbol.isIFunc(elf_file)) { + symbol.flags.needs_got = true; + symbol.flags.needs_plt = true; + } + // While traversing relocations, mark symbols that require special handling such as // pointer indirection via GOT, or a stub trampoline via PLT. switch (rel.r_type()) { - elf.R_X86_64_64 => {}, + elf.R_X86_64_64 => { + try self.scanReloc(symbol, rel, dynAbsRelocAction(symbol, elf_file), elf_file); + }, elf.R_X86_64_32, elf.R_X86_64_32S, - => {}, + => { + try self.scanReloc(symbol, rel, dynAbsRelocAction(symbol, elf_file), elf_file); + }, elf.R_X86_64_GOT32, elf.R_X86_64_GOT64, @@ -377,23 +387,14 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, code: ?[]const u8, undefs: anytype } }, - elf.R_X86_64_PC32 => {}, - - elf.R_X86_64_TPOFF32, - elf.R_X86_64_TPOFF64, - => { - if (is_dyn_lib) { - // TODO - // self.picError(symbol, rel, elf_file); - } + elf.R_X86_64_PC32 => { + try self.scanReloc(symbol, rel, pcRelocAction(symbol, elf_file), elf_file); }, elf.R_X86_64_TLSGD => { // TODO verify followed by appropriate relocation such as PLT32 __tls_get_addr - if (elf_file.isStatic() or - (!symbol.flags.import and !is_dyn_lib)) - { + if (is_static or (!symbol.flags.import and !is_dyn_lib)) { // Relax if building with -static flag as __tls_get_addr() will not be present in libc.a // We skip the next relocation. i += 1; @@ -405,9 +406,21 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, code: ?[]const u8, undefs: anytype } }, + elf.R_X86_64_TLSLD => { + // TODO verify followed by appropriate relocation such as PLT32 __tls_get_addr + + if (is_static or !is_dyn_lib) { + // Relax if building with -static flag as __tls_get_addr() will not be present in libc.a + // We skip the next relocation. + i += 1; + } else { + elf_file.got.flags.needs_tlsld = true; + } + }, + elf.R_X86_64_GOTTPOFF => { const should_relax = blk: { - // if (!elf_file.options.relax or is_shared or symbol.flags.import) break :blk false; + if (is_dyn_lib or symbol.flags.import) break :blk false; if (!x86_64.canRelaxGotTpOff(code.?[r_offset - 3 ..])) break :blk false; break :blk true; }; @@ -416,21 +429,245 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, code: ?[]const u8, undefs: anytype } }, - else => { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "fatal linker error: unhandled relocation type {}", .{ - fmtRelocType(rel.r_type()), - }); - try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ - self.file(elf_file).?.fmtPath(), - self.name(elf_file), - r_offset, - }); + elf.R_X86_64_GOTPC32_TLSDESC => { + const should_relax = is_static or (!is_dyn_lib and !symbol.flags.import); + if (!should_relax) { + symbol.flags.needs_tlsdesc = true; + } }, + + elf.R_X86_64_TPOFF32, + elf.R_X86_64_TPOFF64, + => { + if (is_dyn_lib) try self.reportPicError(symbol, rel, elf_file); + }, + + elf.R_X86_64_GOTOFF64, + elf.R_X86_64_DTPOFF32, + elf.R_X86_64_DTPOFF64, + elf.R_X86_64_SIZE32, + elf.R_X86_64_SIZE64, + elf.R_X86_64_TLSDESC_CALL, + => {}, + + else => try self.reportUnhandledRelocError(rel, elf_file), } } } +fn scanReloc( + self: Atom, + symbol: *Symbol, + rel: elf.Elf64_Rela, + action: RelocAction, + elf_file: *Elf, +) error{OutOfMemory}!void { + const is_writeable = self.inputShdr(elf_file).sh_flags & elf.SHF_WRITE != 0; + const object = self.file(elf_file).?.object; + + switch (action) { + .none => {}, + + .@"error" => if (symbol.isAbs(elf_file)) + try self.reportNoPicError(symbol, rel, elf_file) + else + try self.reportPicError(symbol, rel, elf_file), + + .copyrel => { + if (elf_file.base.options.z_nocopyreloc) { + if (symbol.isAbs(elf_file)) + try self.reportNoPicError(symbol, rel, elf_file) + else + try self.reportPicError(symbol, rel, elf_file); + } else { + symbol.flags.needs_copy_rel = true; + } + }, + + .dyn_copyrel => { + if (is_writeable or elf_file.base.options.z_nocopyreloc) { + if (!is_writeable) { + if (elf_file.base.options.z_notext) { + elf_file.has_text_reloc = true; + } else { + try self.reportTextRelocError(symbol, rel, elf_file); + } + } + object.num_dynrelocs += 1; + } else { + symbol.flags.needs_copy_rel = true; + } + }, + + .plt => { + symbol.flags.needs_plt = true; + }, + + .cplt => { + symbol.flags.needs_plt = true; + symbol.flags.is_canonical = true; + }, + + .dyn_cplt => { + if (is_writeable) { + object.num_dynrelocs += 1; + } else { + symbol.flags.needs_plt = true; + symbol.flags.is_canonical = true; + } + }, + + .dynrel, .baserel, .ifunc => { + if (!is_writeable) { + if (elf_file.base.options.z_notext) { + elf_file.has_text_reloc = true; + } else { + try self.reportTextRelocError(symbol, rel, elf_file); + } + } + object.num_dynrelocs += 1; + + if (action == .ifunc) elf_file.num_ifunc_dynrelocs += 1; + }, + } +} + +const RelocAction = enum { + none, + @"error", + copyrel, + dyn_copyrel, + plt, + dyn_cplt, + cplt, + dynrel, + baserel, + ifunc, +}; + +fn pcRelocAction(symbol: *const Symbol, elf_file: *Elf) RelocAction { + // zig fmt: off + const table: [3][4]RelocAction = .{ + // Abs Local Import data Import func + .{ .@"error", .none, .@"error", .plt }, // Shared object + .{ .@"error", .none, .copyrel, .plt }, // PIE + .{ .none, .none, .copyrel, .cplt }, // Non-PIE + }; + // zig fmt: on + const output = outputType(elf_file); + const data = dataType(symbol, elf_file); + return table[output][data]; +} + +fn absRelocAction(symbol: *const Symbol, elf_file: *Elf) RelocAction { + // zig fmt: off + const table: [3][4]RelocAction = .{ + // Abs Local Import data Import func + .{ .none, .@"error", .@"error", .@"error" }, // Shared object + .{ .none, .@"error", .@"error", .@"error" }, // PIE + .{ .none, .none, .copyrel, .cplt }, // Non-PIE + }; + // zig fmt: on + const output = outputType(elf_file); + const data = dataType(symbol, elf_file); + return table[output][data]; +} + +fn dynAbsRelocAction(symbol: *const Symbol, elf_file: *Elf) RelocAction { + if (symbol.isIFunc(elf_file)) return .ifunc; + // zig fmt: off + const table: [3][4]RelocAction = .{ + // Abs Local Import data Import func + .{ .none, .baserel, .dynrel, .dynrel }, // Shared object + .{ .none, .baserel, .dynrel, .dynrel }, // PIE + .{ .none, .none, .dyn_copyrel, .dyn_cplt }, // Non-PIE + }; + // zig fmt: on + const output = outputType(elf_file); + const data = dataType(symbol, elf_file); + return table[output][data]; +} + +fn outputType(elf_file: *Elf) u2 { + return switch (elf_file.base.options.output_mode) { + .Obj => unreachable, + .Lib => 0, + .Exe => if (elf_file.base.options.pie) 1 else 2, + }; +} + +fn dataType(symbol: *const Symbol, elf_file: *Elf) u2 { + if (symbol.isAbs(elf_file)) return 0; + if (!symbol.flags.import) return 1; + if (symbol.type(elf_file) != elf.STT_FUNC) return 2; + return 3; +} + +fn reportUnhandledRelocError(self: Atom, rel: elf.Elf64_Rela, elf_file: *Elf) error{OutOfMemory}!void { + var err = try elf_file.addErrorWithNotes(1); + try err.addMsg(elf_file, "fatal linker error: unhandled relocation type {} at offset 0x{x}", .{ + fmtRelocType(rel.r_type()), + rel.r_offset, + }); + try err.addNote(elf_file, "in {}:{s}", .{ + self.file(elf_file).?.fmtPath(), + self.name(elf_file), + }); +} + +fn reportTextRelocError( + self: Atom, + symbol: *const Symbol, + rel: elf.Elf64_Rela, + elf_file: *Elf, +) error{OutOfMemory}!void { + var err = try elf_file.addErrorWithNotes(1); + try err.addMsg(elf_file, "relocation at offset 0x{x} against symbol '{s}' cannot be used", .{ + rel.r_offset, + symbol.name(elf_file), + }); + try err.addNote(elf_file, "in {}:{s}", .{ + self.file(elf_file).?.fmtPath(), + self.name(elf_file), + }); +} + +fn reportPicError( + self: Atom, + symbol: *const Symbol, + rel: elf.Elf64_Rela, + elf_file: *Elf, +) error{OutOfMemory}!void { + var err = try elf_file.addErrorWithNotes(2); + try err.addMsg(elf_file, "relocation at offset 0x{x} against symbol '{s}' cannot be used", .{ + rel.r_offset, + symbol.name(elf_file), + }); + try err.addNote(elf_file, "in {}:{s}", .{ + self.file(elf_file).?.fmtPath(), + self.name(elf_file), + }); + try err.addNote(elf_file, "recompile with -fPIC", .{}); +} + +fn reportNoPicError( + self: Atom, + symbol: *const Symbol, + rel: elf.Elf64_Rela, + elf_file: *Elf, +) error{OutOfMemory}!void { + var err = try elf_file.addErrorWithNotes(2); + try err.addMsg(elf_file, "relocation at offset 0x{x} against symbol '{s}' cannot be used", .{ + rel.r_offset, + symbol.name(elf_file), + }); + try err.addNote(elf_file, "in {}:{s}", .{ + self.file(elf_file).?.fmtPath(), + self.name(elf_file), + }); + try err.addNote(elf_file, "recompile with -fno-PIC", .{}); +} + // This function will report any undefined non-weak symbols that are not imports. fn reportUndefined( self: Atom, @@ -504,7 +741,6 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) !void { const TP = @as(i64, @intCast(elf_file.tpAddress())); // Address of the dynamic thread pointer. const DTP = @as(i64, @intCast(elf_file.dtpAddress())); - _ = DTP; relocs_log.debug(" {s}: {x}: [{x} => {x}] G({x}) ({s})", .{ fmtRelocType(r_type), @@ -520,18 +756,20 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) !void { switch (rel.r_type()) { elf.R_X86_64_NONE => unreachable, - elf.R_X86_64_64 => try cwriter.writeIntLittle(i64, S + A), - - elf.R_X86_64_32 => try cwriter.writeIntLittle(u32, @as(u32, @truncate(@as(u64, @intCast(S + A))))), - elf.R_X86_64_32S => try cwriter.writeIntLittle(i32, @as(i32, @truncate(S + A))), + elf.R_X86_64_64 => { + try self.resolveDynAbsReloc( + target, + rel, + dynAbsRelocAction(target, elf_file), + elf_file, + cwriter, + ); + }, elf.R_X86_64_PLT32, elf.R_X86_64_PC32, => try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A - P))), - elf.R_X86_64_GOT32 => try cwriter.writeIntLittle(u32, @as(u32, @intCast(G + GOT + A))), - elf.R_X86_64_GOT64 => try cwriter.writeIntLittle(u64, @as(u64, @intCast(G + GOT + A))), - elf.R_X86_64_GOTPCREL => try cwriter.writeIntLittle(i32, @as(i32, @intCast(G + GOT + A - P))), elf.R_X86_64_GOTPC32 => try cwriter.writeIntLittle(i32, @as(i32, @intCast(GOT + A - P))), elf.R_X86_64_GOTPC64 => try cwriter.writeIntLittle(i64, GOT + A - P), @@ -554,18 +792,25 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) !void { try cwriter.writeIntLittle(i32, @as(i32, @intCast(G + GOT + A - P))); }, + elf.R_X86_64_32 => try cwriter.writeIntLittle(u32, @as(u32, @truncate(@as(u64, @intCast(S + A))))), + elf.R_X86_64_32S => try cwriter.writeIntLittle(i32, @as(i32, @truncate(S + A))), + + elf.R_X86_64_GOT32 => try cwriter.writeIntLittle(u32, @as(u32, @intCast(G + GOT + A))), + elf.R_X86_64_GOT64 => try cwriter.writeIntLittle(u64, @as(u64, @intCast(G + GOT + A))), + elf.R_X86_64_TPOFF32 => try cwriter.writeIntLittle(i32, @as(i32, @truncate(S + A - TP))), elf.R_X86_64_TPOFF64 => try cwriter.writeIntLittle(i64, S + A - TP), + elf.R_X86_64_DTPOFF32 => try cwriter.writeIntLittle(i32, @as(i32, @truncate(S + A - DTP))), + elf.R_X86_64_DTPOFF64 => try cwriter.writeIntLittle(i64, S + A - DTP), + elf.R_X86_64_TLSGD => { if (target.flags.has_tlsgd) { - // TODO - // const S_ = @as(i64, @intCast(target.tlsGdAddress(elf_file))); - // try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P))); + const S_ = @as(i64, @intCast(target.tlsGdAddress(elf_file))); + try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P))); } else if (target.flags.has_gottp) { - // TODO - // const S_ = @as(i64, @intCast(target.getGotTpAddress(elf_file))); - // try relaxTlsGdToIe(relocs[i .. i + 2], @intCast(S_ - P), elf_file, &stream); + const S_ = @as(i64, @intCast(target.gotTpAddress(elf_file))); + try x86_64.relaxTlsGdToIe(self, rels[i .. i + 2], @intCast(S_ - P), elf_file, &stream); i += 1; } else { try x86_64.relaxTlsGdToLe( @@ -579,11 +824,42 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) !void { } }, + elf.R_X86_64_TLSLD => { + if (elf_file.got.tlsld_index) |entry_index| { + const tlsld_entry = elf_file.got.entries.items[entry_index]; + const S_ = @as(i64, @intCast(tlsld_entry.address(elf_file))); + try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P))); + } else { + try x86_64.relaxTlsLdToLe( + self, + rels[i .. i + 2], + @as(i32, @intCast(TP - @as(i64, @intCast(elf_file.tlsAddress())))), + elf_file, + &stream, + ); + i += 1; + } + }, + + elf.R_X86_64_GOTPC32_TLSDESC => { + if (target.flags.has_tlsdesc) { + const S_ = @as(i64, @intCast(target.tlsDescAddress(elf_file))); + try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P))); + } else { + try x86_64.relaxGotPcTlsDesc(code[rel.r_offset - 3 ..]); + try cwriter.writeIntLittle(i32, @as(i32, @intCast(S - TP))); + } + }, + + elf.R_X86_64_TLSDESC_CALL => if (!target.flags.has_tlsdesc) { + // call -> nop + try cwriter.writeAll(&.{ 0x66, 0x90 }); + }, + elf.R_X86_64_GOTTPOFF => { if (target.flags.has_gottp) { - // TODO - // const S_ = @as(i64, @intCast(target.gotTpAddress(elf_file))); - // try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P))); + const S_ = @as(i64, @intCast(target.gotTpAddress(elf_file))); + try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P))); } else { x86_64.relaxGotTpOff(code[r_offset - 3 ..]) catch unreachable; try cwriter.writeIntLittle(i32, @as(i32, @intCast(S - TP))); @@ -595,6 +871,98 @@ pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) !void { } } +fn resolveDynAbsReloc( + self: Atom, + target: *const Symbol, + rel: elf.Elf64_Rela, + action: RelocAction, + elf_file: *Elf, + writer: anytype, +) !void { + const P = self.value + rel.r_offset; + const A = rel.r_addend; + const S = @as(i64, @intCast(target.address(.{}, elf_file))); + const is_writeable = self.inputShdr(elf_file).sh_flags & elf.SHF_WRITE != 0; + const object = self.file(elf_file).?.object; + + try elf_file.rela_dyn.ensureUnusedCapacity(elf_file.base.allocator, object.num_dynrelocs); + + switch (action) { + .@"error", + .plt, + => unreachable, + + .copyrel, + .cplt, + .none, + => try writer.writeIntLittle(i32, @as(i32, @truncate(S + A))), + + .dyn_copyrel => { + if (is_writeable or elf_file.base.options.z_nocopyreloc) { + elf_file.addRelaDynAssumeCapacity(.{ + .offset = P, + .sym = target.extra(elf_file).?.dynamic, + .type = elf.R_X86_64_64, + .addend = A, + }); + try applyDynamicReloc(A, elf_file, writer); + } else { + try writer.writeIntLittle(i32, @as(i32, @truncate(S + A))); + } + }, + + .dyn_cplt => { + if (is_writeable) { + elf_file.addRelaDynAssumeCapacity(.{ + .offset = P, + .sym = target.extra(elf_file).?.dynamic, + .type = elf.R_X86_64_64, + .addend = A, + }); + try applyDynamicReloc(A, elf_file, writer); + } else { + try writer.writeIntLittle(i32, @as(i32, @truncate(S + A))); + } + }, + + .dynrel => { + elf_file.addRelaDynAssumeCapacity(.{ + .offset = P, + .sym = target.extra(elf_file).?.dynamic, + .type = elf.R_X86_64_64, + .addend = A, + }); + try applyDynamicReloc(A, elf_file, writer); + }, + + .baserel => { + elf_file.addRelaDynAssumeCapacity(.{ + .offset = P, + .type = elf.R_X86_64_RELATIVE, + .addend = S + A, + }); + try applyDynamicReloc(S + A, elf_file, writer); + }, + + .ifunc => { + const S_ = @as(i64, @intCast(target.address(.{ .plt = false }, elf_file))); + elf_file.addRelaDynAssumeCapacity(.{ + .offset = P, + .type = elf.R_X86_64_IRELATIVE, + .addend = S_ + A, + }); + try applyDynamicReloc(S_ + A, elf_file, writer); + }, + } +} + +fn applyDynamicReloc(value: i64, elf_file: *Elf, writer: anytype) !void { + _ = elf_file; + // if (elf_file.options.apply_dynamic_relocs) { + try writer.writeIntLittle(i64, value); + // } +} + pub fn resolveRelocsNonAlloc(self: Atom, elf_file: *Elf, code: []u8, undefs: anytype) !void { relocs_log.debug("0x{x}: {s}", .{ self.value, self.name(elf_file) }); @@ -682,17 +1050,7 @@ pub fn resolveRelocsNonAlloc(self: Atom, elf_file: *Elf, code: []u8, undefs: any const size = @as(i64, @intCast(target.elfSym(elf_file).st_size)); try cwriter.writeIntLittle(i64, @as(i64, @intCast(size + A))); }, - else => { - var err = try elf_file.addErrorWithNotes(1); - try err.addMsg(elf_file, "fatal linker error: unhandled relocation type {}", .{ - fmtRelocType(r_type), - }); - try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ - self.file(elf_file).?.fmtPath(), - self.name(elf_file), - r_offset, - }); - }, + else => try self.reportUnhandledRelocError(rel, elf_file), } } } @@ -854,6 +1212,95 @@ const x86_64 = struct { } } + pub fn relaxTlsGdToIe( + self: Atom, + rels: []align(1) const elf.Elf64_Rela, + value: i32, + elf_file: *Elf, + stream: anytype, + ) !void { + assert(rels.len == 2); + const writer = stream.writer(); + switch (rels[1].r_type()) { + elf.R_X86_64_PC32, + elf.R_X86_64_PLT32, + => { + var insts = [_]u8{ + 0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // movq %fs:0,%rax + 0x48, 0x03, 0x05, 0, 0, 0, 0, // add foo@gottpoff(%rip), %rax + }; + std.mem.writeIntLittle(i32, insts[12..][0..4], value - 12); + try stream.seekBy(-4); + try writer.writeAll(&insts); + }, + + else => { + var err = try elf_file.addErrorWithNotes(1); + try err.addMsg(elf_file, "fatal linker error: rewrite {} when followed by {}", .{ + fmtRelocType(rels[0].r_type()), + fmtRelocType(rels[1].r_type()), + }); + try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ + self.file(elf_file).?.fmtPath(), + self.name(elf_file), + rels[0].r_offset, + }); + }, + } + } + + pub fn relaxTlsLdToLe( + self: Atom, + rels: []align(1) const elf.Elf64_Rela, + value: i32, + elf_file: *Elf, + stream: anytype, + ) !void { + assert(rels.len == 2); + const writer = stream.writer(); + switch (rels[1].r_type()) { + elf.R_X86_64_PC32, + elf.R_X86_64_PLT32, + => { + var insts = [_]u8{ + 0x31, 0xc0, // xor %eax, %eax + 0x64, 0x48, 0x8b, 0, // mov %fs:(%rax), %rax + 0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax + }; + std.mem.writeIntLittle(i32, insts[8..][0..4], value); + try stream.seekBy(-3); + try writer.writeAll(&insts); + }, + + elf.R_X86_64_GOTPCREL, + elf.R_X86_64_GOTPCRELX, + => { + var insts = [_]u8{ + 0x31, 0xc0, // xor %eax, %eax + 0x64, 0x48, 0x8b, 0, // mov %fs:(%rax), %rax + 0x48, 0x2d, 0, 0, 0, 0, // sub $tls_size, %rax + 0x90, // nop + }; + std.mem.writeIntLittle(i32, insts[8..][0..4], value); + try stream.seekBy(-3); + try writer.writeAll(&insts); + }, + + else => { + var err = try elf_file.addErrorWithNotes(1); + try err.addMsg(elf_file, "fatal linker error: rewrite {} when followed by {}", .{ + fmtRelocType(rels[0].r_type()), + fmtRelocType(rels[1].r_type()), + }); + try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ + self.file(elf_file).?.fmtPath(), + self.name(elf_file), + rels[0].r_offset, + }); + }, + } + } + pub fn canRelaxGotTpOff(code: []const u8) bool { const old_inst = disassemble(code) orelse return false; switch (old_inst.encoding.mnemonic) { @@ -885,6 +1332,22 @@ const x86_64 = struct { } } + pub fn relaxGotPcTlsDesc(code: []u8) !void { + const old_inst = disassemble(code) orelse return error.RelaxFail; + switch (old_inst.encoding.mnemonic) { + .lea => { + const inst = try Instruction.new(old_inst.prefix, .mov, &.{ + old_inst.ops[0], + // TODO: hack to force imm32s in the assembler + .{ .imm = Immediate.s(-129) }, + }); + relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding }); + encode(&.{inst}, code) catch return error.RelaxFail; + }, + else => return error.RelaxFail, + } + } + pub fn relaxTlsGdToLe( self: Atom, rels: []align(1) const elf.Elf64_Rela, diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 723b76469f..9baa5c07ed 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -20,7 +20,6 @@ cies: std.ArrayListUnmanaged(Cie) = .{}, alive: bool = true, num_dynrelocs: u32 = 0, -output_sections: std.AutoArrayHashMapUnmanaged(u16, std.ArrayListUnmanaged(Atom.Index)) = .{}, output_symtab_size: Elf.SymtabSize = .{}, pub fn isObject(file: std.fs.File) bool { @@ -43,10 +42,6 @@ pub fn deinit(self: *Object, allocator: Allocator) void { self.comdat_groups.deinit(allocator); self.fdes.deinit(allocator); self.cies.deinit(allocator); - for (self.output_sections.values()) |*list| { - list.deinit(allocator); - } - self.output_sections.deinit(allocator); } pub fn parse(self: *Object, elf_file: *Elf) !void { @@ -635,7 +630,7 @@ pub fn addAtomsToOutputSections(self: *Object, elf_file: *Elf) !void { if (shdr.sh_type == elf.SHT_NOBITS) continue; const gpa = elf_file.base.allocator; - const gop = try self.output_sections.getOrPut(gpa, atom.output_section_index); + const gop = try elf_file.output_sections.getOrPut(gpa, atom.output_section_index); if (!gop.found_existing) gop.value_ptr.* = .{}; try gop.value_ptr.append(gpa, atom_index); } @@ -680,28 +675,6 @@ pub fn allocateAtoms(self: Object, elf_file: *Elf) void { } } -pub fn writeAtoms(self: Object, elf_file: *Elf, output_section_index: u16, buffer: []u8, undefs: anytype) !void { - const gpa = elf_file.base.allocator; - const atom_list = self.output_sections.get(output_section_index) orelse return; - const shdr = elf_file.shdrs.items[output_section_index]; - for (atom_list.items) |atom_index| { - const atom = elf_file.atom(atom_index).?; - assert(atom.flags.alive); - const offset = atom.value - shdr.sh_addr; - log.debug("writing atom({d}) at 0x{x}", .{ atom_index, shdr.sh_offset + offset }); - // TODO decompress directly into provided buffer - const out_code = buffer[offset..][0..atom.size]; - const in_code = try self.codeDecompressAlloc(elf_file, atom_index); - defer gpa.free(in_code); - @memcpy(out_code, in_code); - - if (shdr.sh_flags & elf.SHF_ALLOC == 0) - try atom.resolveRelocsNonAlloc(elf_file, out_code, undefs) - else - try atom.resolveRelocsAlloc(elf_file, out_code); - } -} - pub fn updateSymtabSize(self: *Object, elf_file: *Elf) void { for (self.locals()) |local_index| { const local = elf_file.symbol(local_index); diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index a0236f4c87..022f8fbde1 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -128,23 +128,26 @@ pub fn getOrCreateGotEntry(symbol: *Symbol, symbol_index: Index, elf_file: *Elf) return .{ .found_existing = false, .index = index }; } -// pub fn tlsGdAddress(symbol: Symbol, elf_file: *Elf) u64 { -// if (!symbol.flags.tlsgd) return 0; -// const extra = symbol.getExtra(elf_file).?; -// return elf_file.getGotEntryAddress(extra.tlsgd); -// } +pub fn tlsGdAddress(symbol: Symbol, elf_file: *Elf) u64 { + if (!symbol.flags.has_tlsgd) return 0; + const extras = symbol.extra(elf_file).?; + const entry = elf_file.got.entries.items[extras.tlsgd]; + return entry.address(elf_file); +} -// pub fn gotTpAddress(symbol: Symbol, elf_file: *Elf) u64 { -// if (!symbol.flags.gottp) return 0; -// const extra = symbol.getExtra(elf_file).?; -// return elf_file.getGotEntryAddress(extra.gottp); -// } +pub fn gotTpAddress(symbol: Symbol, elf_file: *Elf) u64 { + if (!symbol.flags.has_gottp) return 0; + const extras = symbol.extra(elf_file).?; + const entry = elf_file.got.entries.items[extras.gottp]; + return entry.address(elf_file); +} -// pub fn tlsDescAddress(symbol: Symbol, elf_file: *Elf) u64 { -// if (!symbol.flags.tlsdesc) return 0; -// const extra = symbol.getExtra(elf_file).?; -// return elf_file.getGotEntryAddress(extra.tlsdesc); -// } +pub fn tlsDescAddress(symbol: Symbol, elf_file: *Elf) u64 { + if (!symbol.flags.has_tlsdesc) return 0; + const extras = symbol.extra(elf_file).?; + const entry = elf_file.got.entries.items[extras.tlsdesc]; + return entry.address(elf_file); +} // pub fn alignment(symbol: Symbol, elf_file: *Elf) !u64 { // const file = symbol.getFile(elf_file) orelse return 0; @@ -318,12 +321,12 @@ pub const Flags = packed struct { /// Whether the symbol contains PLT indirection. needs_plt: bool = false, - plt: bool = false, + has_plt: bool = false, /// Whether the PLT entry is canonical. is_canonical: bool = false, /// Whether the symbol contains COPYREL directive. - copy_rel: bool = false, + needs_copy_rel: bool = false, has_copy_rel: bool = false, has_dynamic: bool = false, @@ -336,7 +339,8 @@ pub const Flags = packed struct { has_gottp: bool = false, /// Whether the symbol contains TLSDESC indirection. - tlsdesc: bool = false, + needs_tlsdesc: bool = false, + has_tlsdesc: bool = false, }; pub const Extra = struct { diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index 2e719a7ced..2b4191f3c1 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -1,10 +1,16 @@ pub const GotSection = struct { entries: std.ArrayListUnmanaged(Entry) = .{}, - needs_rela: bool = false, output_symtab_size: Elf.SymtabSize = .{}, + tlsld_index: ?u32 = null, + flags: Flags = .{}, pub const Index = u32; + const Flags = packed struct { + needs_rela: bool = false, + needs_tlsld: bool = false, + }; + const Tag = enum { got, tlsld, @@ -57,7 +63,7 @@ pub const GotSection = struct { entry.symbol_index = sym_index; const symbol = elf_file.symbol(sym_index); if (symbol.flags.import or symbol.isIFunc(elf_file) or (elf_file.base.options.pic and !symbol.isAbs(elf_file))) - got.needs_rela = true; + got.flags.needs_rela = true; if (symbol.extra(elf_file)) |extra| { var new_extra = extra; new_extra.got = index; |
