Commit d6cec5a586
Changed files (5)
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,
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);
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 {
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;
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 {