aboutsummaryrefslogtreecommitdiff
path: root/src/link
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2023-09-14 01:45:23 +0200
committerGitHub <noreply@github.com>2023-09-14 01:45:23 +0200
commit8fb4a4efbabbe3fe98521201eac41feee5a9a50a (patch)
tree398abef45faa8065836c6a5f8cad167ae8adb4ed /src/link
parent223f62acbd32c04db3169906f33869f43e5258f7 (diff)
parent59a586a8785b8aea417824e5b6b8d6b8a1b2b695 (diff)
downloadzig-8fb4a4efbabbe3fe98521201eac41feee5a9a50a.tar.gz
zig-8fb4a4efbabbe3fe98521201eac41feee5a9a50a.zip
Merge pull request #17146 from ziglang/elf-linker
elf: upstream zld/ELF functionality, part 2
Diffstat (limited to 'src/link')
-rw-r--r--src/link/Elf.zig217
-rw-r--r--src/link/Elf/Archive.zig153
-rw-r--r--src/link/Elf/Atom.zig102
-rw-r--r--src/link/Elf/Object.zig6
-rw-r--r--src/link/Elf/Symbol.zig8
-rw-r--r--src/link/Elf/ZigModule.zig9
-rw-r--r--src/link/Elf/file.zig15
7 files changed, 470 insertions, 40 deletions
diff --git a/src/link/Elf.zig b/src/link/Elf.zig
index 16b7706f7a..b12d24745b 100644
--- a/src/link/Elf.zig
+++ b/src/link/Elf.zig
@@ -40,6 +40,8 @@ phdr_got_index: ?u16 = null,
phdr_load_ro_index: ?u16 = null,
/// The index into the program headers of a PT_LOAD program header with Write flag
phdr_load_rw_index: ?u16 = null,
+/// The index into the program headers of a PT_LOAD program header with zerofill data.
+phdr_load_zerofill_index: ?u16 = null,
entry_addr: ?u64 = null,
page_size: u32,
@@ -56,6 +58,7 @@ got: GotSection = .{},
text_section_index: ?u16 = null,
rodata_section_index: ?u16 = null,
data_section_index: ?u16 = null,
+bss_section_index: ?u16 = null,
eh_frame_section_index: ?u16 = null,
eh_frame_hdr_section_index: ?u16 = null,
dynamic_section_index: ?u16 = null,
@@ -532,6 +535,26 @@ pub fn populateMissingMetadata(self: *Elf) !void {
self.phdr_table_dirty = true;
}
+ if (self.phdr_load_zerofill_index == null) {
+ self.phdr_load_zerofill_index = @as(u16, @intCast(self.phdrs.items.len));
+ const p_align = if (self.base.options.target.os.tag == .linux) self.page_size else @as(u16, ptr_size);
+ const off = self.phdrs.items[self.phdr_load_rw_index.?].p_offset;
+ log.debug("found PT_LOAD zerofill free space 0x{x} to 0x{x}", .{ off, off });
+ // TODO Same as for GOT
+ const addr: u32 = if (self.base.options.target.ptrBitWidth() >= 32) 0x14000000 else 0xf000;
+ try self.phdrs.append(gpa, .{
+ .p_type = elf.PT_LOAD,
+ .p_offset = off,
+ .p_filesz = 0,
+ .p_vaddr = addr,
+ .p_paddr = addr,
+ .p_memsz = 0,
+ .p_align = p_align,
+ .p_flags = elf.PF_R | elf.PF_W,
+ });
+ self.phdr_table_dirty = true;
+ }
+
if (self.shstrtab_section_index == null) {
self.shstrtab_section_index = @as(u16, @intCast(self.shdrs.items.len));
assert(self.shstrtab.buffer.items.len == 0);
@@ -655,6 +678,26 @@ pub fn populateMissingMetadata(self: *Elf) !void {
self.shdr_table_dirty = true;
}
+ if (self.bss_section_index == null) {
+ self.bss_section_index = @as(u16, @intCast(self.shdrs.items.len));
+ const phdr = &self.phdrs.items[self.phdr_load_zerofill_index.?];
+ try self.shdrs.append(gpa, .{
+ .sh_name = try self.shstrtab.insert(gpa, ".bss"),
+ .sh_type = elf.SHT_NOBITS,
+ .sh_flags = elf.SHF_WRITE | elf.SHF_ALLOC,
+ .sh_addr = phdr.p_vaddr,
+ .sh_offset = phdr.p_offset,
+ .sh_size = phdr.p_filesz,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = @as(u16, ptr_size),
+ .sh_entsize = 0,
+ });
+ try self.phdr_to_shdr_table.putNoClobber(gpa, self.bss_section_index.?, self.phdr_load_zerofill_index.?);
+ try self.last_atom_and_free_list_table.putNoClobber(gpa, self.bss_section_index.?, .{});
+ self.shdr_table_dirty = true;
+ }
+
if (self.symtab_section_index == null) {
self.symtab_section_index = @as(u16, @intCast(self.shdrs.items.len));
const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym);
@@ -868,8 +911,9 @@ pub fn growAllocSection(self: *Elf, shdr_index: u16, needed_size: u64) !void {
const shdr = &self.shdrs.items[shdr_index];
const phdr_index = self.phdr_to_shdr_table.get(shdr_index).?;
const phdr = &self.phdrs.items[phdr_index];
+ const is_zerofill = shdr.sh_type == elf.SHT_NOBITS;
- if (needed_size > self.allocatedSize(shdr.sh_offset)) {
+ if (needed_size > self.allocatedSize(shdr.sh_offset) and !is_zerofill) {
// Must move the entire section.
const new_offset = self.findFreeSpace(needed_size, self.page_size);
const existing_size = if (self.last_atom_and_free_list_table.get(shdr_index)) |meta| blk: {
@@ -893,7 +937,10 @@ pub fn growAllocSection(self: *Elf, shdr_index: u16, needed_size: u64) !void {
shdr.sh_size = needed_size;
phdr.p_memsz = needed_size;
- phdr.p_filesz = needed_size;
+
+ if (!is_zerofill) {
+ phdr.p_filesz = needed_size;
+ }
self.markDirty(shdr_index, phdr_index);
}
@@ -1005,13 +1052,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type.
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
- const compiler_rt_path: ?[]const u8 = blk: {
- if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
- if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
- break :blk null;
- };
- _ = compiler_rt_path;
-
// Here we will parse input positional and library files (if referenced).
// This will roughly match in any linker backend we support.
var positionals = std.ArrayList(Compilation.LinkObject).init(arena);
@@ -1037,6 +1077,15 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
try positionals.append(.{ .path = key.status.success.object_path });
}
+ const compiler_rt_path: ?[]const u8 = blk: {
+ if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
+ if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
+ break :blk null;
+ };
+ if (compiler_rt_path) |path| {
+ try positionals.append(.{ .path = path });
+ }
+
for (positionals.items) |obj| {
const in_file = try std.fs.cwd().openFile(obj.path, .{});
defer in_file.close();
@@ -1093,7 +1142,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
// input Object files.
// Any qualifing unresolved symbol will be upgraded to an absolute, weak
// symbol for potential resolution at load-time.
- self.resolveSymbols();
+ try self.resolveSymbols();
self.markImportsExports();
self.claimUnresolved();
@@ -1356,6 +1405,7 @@ const ParseError = error{
EndOfStream,
FileSystem,
NotSupported,
+ InvalidCharacter,
} || std.os.SeekError || std.fs.File.OpenError || std.fs.File.ReadError;
fn parsePositional(
@@ -1367,10 +1417,32 @@ fn parsePositional(
) ParseError!void {
const tracy = trace(@src());
defer tracy.end();
- _ = must_link;
if (Object.isObject(in_file)) {
try self.parseObject(in_file, path, ctx);
+ } else {
+ try self.parseLibrary(in_file, path, .{
+ .path = null,
+ .needed = false,
+ .weak = false,
+ }, must_link, ctx);
+ }
+}
+
+fn parseLibrary(
+ self: *Elf,
+ in_file: std.fs.File,
+ path: []const u8,
+ lib: link.SystemLib,
+ must_link: bool,
+ ctx: *ParseErrorCtx,
+) ParseError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+ _ = lib;
+
+ if (Archive.isArchive(in_file)) {
+ try self.parseArchive(in_file, path, must_link, ctx);
} else return error.UnknownFileType;
}
@@ -1395,15 +1467,109 @@ fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseEr
if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch;
}
-fn resolveSymbols(self: *Elf) void {
- if (self.zig_module_index) |index| {
- const zig_module = self.file(index).?.zig_module;
- zig_module.resolveSymbols(self);
+fn parseArchive(
+ self: *Elf,
+ in_file: std.fs.File,
+ path: []const u8,
+ must_link: bool,
+ ctx: *ParseErrorCtx,
+) ParseError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = self.base.allocator;
+ const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32));
+ var archive = Archive{ .path = path, .data = data };
+ defer archive.deinit(gpa);
+ try archive.parse(self);
+
+ for (archive.objects.items) |extracted| {
+ const index = @as(File.Index, @intCast(try self.files.addOne(gpa)));
+ self.files.set(index, .{ .object = extracted });
+ const object = &self.files.items(.data)[index].object;
+ object.index = index;
+ object.alive = must_link;
+ try object.parse(self);
+ try self.objects.append(gpa, index);
+
+ ctx.detected_cpu_arch = object.header.?.e_machine.toTargetCpuArch().?;
+ if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch;
+ }
+}
+
+/// When resolving symbols, we approach the problem similarly to `mold`.
+/// 1. Resolve symbols across all objects (including those preemptively extracted archives).
+/// 2. Resolve symbols across all shared objects.
+/// 3. Mark live objects (see `Elf.markLive`)
+/// 4. Reset state of all resolved globals since we will redo this bit on the pruned set.
+/// 5. Remove references to dead objects/shared objects
+/// 6. Re-run symbol resolution on pruned objects and shared objects sets.
+fn resolveSymbols(self: *Elf) error{Overflow}!void {
+ // Resolve symbols in the ZigModule. For now, we assume that it's always live.
+ if (self.zig_module_index) |index| self.file(index).?.resolveSymbols(self);
+ // Resolve symbols on the set of all objects and shared objects (even if some are unneeded).
+ for (self.objects.items) |index| self.file(index).?.resolveSymbols(self);
+
+ // Mark live objects.
+ self.markLive();
+
+ // Reset state of all globals after marking live objects.
+ if (self.zig_module_index) |index| self.file(index).?.resetGlobals(self);
+ for (self.objects.items) |index| self.file(index).?.resetGlobals(self);
+
+ // Prune dead objects and shared objects.
+ var i: usize = 0;
+ while (i < self.objects.items.len) {
+ const index = self.objects.items[i];
+ if (!self.file(index).?.isAlive()) {
+ _ = self.objects.orderedRemove(i);
+ } else i += 1;
+ }
+
+ // Dedup comdat groups.
+ for (self.objects.items) |index| {
+ const object = self.file(index).?.object;
+ for (object.comdat_groups.items) |cg_index| {
+ const cg = self.comdatGroup(cg_index);
+ const cg_owner = self.comdatGroupOwner(cg.owner);
+ const owner_file_index = if (self.file(cg_owner.file)) |file_ptr|
+ file_ptr.object.index
+ else
+ std.math.maxInt(File.Index);
+ cg_owner.file = @min(owner_file_index, index);
+ }
}
for (self.objects.items) |index| {
const object = self.file(index).?.object;
- object.resolveSymbols(self);
+ for (object.comdat_groups.items) |cg_index| {
+ const cg = self.comdatGroup(cg_index);
+ const cg_owner = self.comdatGroupOwner(cg.owner);
+ if (cg_owner.file != index) {
+ for (try object.comdatGroupMembers(cg.shndx)) |shndx| {
+ const atom_index = object.atoms.items[shndx];
+ if (self.atom(atom_index)) |atom_ptr| {
+ atom_ptr.alive = false;
+ // atom_ptr.markFdesDead(self);
+ }
+ }
+ }
+ }
+ }
+
+ // Re-resolve the symbols.
+ if (self.zig_module_index) |index| self.file(index).?.resolveSymbols(self);
+ for (self.objects.items) |index| self.file(index).?.resolveSymbols(self);
+}
+
+/// Traverses all objects and shared objects marking any object referenced by
+/// a live object/shared object as alive itself.
+/// This routine will prune unneeded objects extracted from archives and
+/// unneeded shared objects.
+fn markLive(self: *Elf) void {
+ for (self.objects.items) |index| {
+ const file_ptr = self.file(index).?;
+ if (file_ptr.isAlive()) file_ptr.markLive(self);
}
}
@@ -1477,11 +1643,11 @@ fn scanRelocs(self: *Elf) !void {
try self.reportUndefined(&undefs);
- for (self.symbols.items) |*sym| {
+ for (self.symbols.items, 0..) |*sym, sym_index| {
if (sym.flags.needs_got) {
log.debug("'{s}' needs GOT", .{sym.name(self)});
// TODO how can we tell we need to write it again, aka the entry is dirty?
- const gop = try sym.getOrCreateGotEntry(self);
+ const gop = try sym.getOrCreateGotEntry(@intCast(sym_index), self);
try self.got.writeEntry(self, gop.index);
}
}
@@ -1500,7 +1666,7 @@ fn allocateObjects(self: *Elf) !void {
const local = self.symbol(local_index);
const atom_ptr = local.atom(self) orelse continue;
if (!atom_ptr.alive) continue;
- local.value = atom_ptr.value;
+ local.value += atom_ptr.value;
}
for (object.globals()) |global_index| {
@@ -1508,7 +1674,7 @@ fn allocateObjects(self: *Elf) !void {
const atom_ptr = global.atom(self) orelse continue;
if (!atom_ptr.alive) continue;
if (global.file_index == index) {
- global.value = atom_ptr.value;
+ global.value += atom_ptr.value;
}
}
}
@@ -1524,7 +1690,11 @@ fn writeObjects(self: *Elf) !void {
if (!atom_ptr.alive) continue;
const shdr = &self.shdrs.items[atom_ptr.output_section_index];
+ if (shdr.sh_type == elf.SHT_NOBITS) continue;
+ if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue; // TODO we don't yet know how to handle non-alloc sections
+
const file_offset = shdr.sh_offset + atom_ptr.value - shdr.sh_addr;
+ log.debug("writing atom({d}) at 0x{x}", .{ atom_ptr.atom_index, file_offset });
const code = try atom_ptr.codeInObjectUncompressAlloc(self);
defer gpa.free(code);
@@ -2529,7 +2699,7 @@ fn updateDeclCode(
esym.st_value = atom_ptr.value;
sym.flags.needs_got = true;
- const gop = try sym.getOrCreateGotEntry(self);
+ const gop = try sym.getOrCreateGotEntry(sym_index, self);
try self.got.writeEntry(self, gop.index);
}
@@ -2764,7 +2934,7 @@ fn updateLazySymbol(self: *Elf, sym: link.File.LazySymbol, symbol_index: Symbol.
local_esym.st_value = atom_ptr.value;
local_sym.flags.needs_got = true;
- const gop = try local_sym.getOrCreateGotEntry(self);
+ const gop = try local_sym.getOrCreateGotEntry(symbol_index, self);
try self.got.writeEntry(self, gop.index);
const section_offset = atom_ptr.value - self.phdrs.items[phdr_index].p_vaddr;
@@ -3635,7 +3805,7 @@ pub fn addSymbol(self: *Elf) !Symbol.Index {
break :blk index;
}
};
- self.symbols.items[index] = .{ .index = index };
+ self.symbols.items[index] = .{};
return index;
}
@@ -4012,6 +4182,7 @@ const synthetic_sections = @import("Elf/synthetic_sections.zig");
const Air = @import("../Air.zig");
const Allocator = std.mem.Allocator;
+const Archive = @import("Elf/Archive.zig");
pub const Atom = @import("Elf/Atom.zig");
const Cache = std.Build.Cache;
const Compilation = @import("../Compilation.zig");
diff --git a/src/link/Elf/Archive.zig b/src/link/Elf/Archive.zig
new file mode 100644
index 0000000000..f34b323206
--- /dev/null
+++ b/src/link/Elf/Archive.zig
@@ -0,0 +1,153 @@
+path: []const u8,
+data: []const u8,
+
+objects: std.ArrayListUnmanaged(Object) = .{},
+strtab: []const u8 = &[0]u8{},
+
+// Archive files start with the ARMAG identifying string. Then follows a
+// `struct ar_hdr', and as many bytes of member file data as its `ar_size'
+// member indicates, for each member file.
+/// String that begins an archive file.
+pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n";
+/// Size of that string.
+pub const SARMAG: u4 = 8;
+
+/// String in ar_fmag at the end of each header.
+const ARFMAG: *const [2:0]u8 = "`\n";
+
+const SYM64NAME: *const [7:0]u8 = "/SYM64/";
+
+const ar_hdr = extern struct {
+ /// Member file name, sometimes / terminated.
+ ar_name: [16]u8,
+
+ /// File date, decimal seconds since Epoch.
+ ar_date: [12]u8,
+
+ /// User ID, in ASCII format.
+ ar_uid: [6]u8,
+
+ /// Group ID, in ASCII format.
+ ar_gid: [6]u8,
+
+ /// File mode, in ASCII octal.
+ ar_mode: [8]u8,
+
+ /// File size, in ASCII decimal.
+ ar_size: [10]u8,
+
+ /// Always contains ARFMAG.
+ ar_fmag: [2]u8,
+
+ fn date(self: ar_hdr) !u64 {
+ const value = getValue(&self.ar_date);
+ return std.fmt.parseInt(u64, value, 10);
+ }
+
+ fn size(self: ar_hdr) !u32 {
+ const value = getValue(&self.ar_size);
+ return std.fmt.parseInt(u32, value, 10);
+ }
+
+ fn getValue(raw: []const u8) []const u8 {
+ return mem.trimRight(u8, raw, &[_]u8{@as(u8, 0x20)});
+ }
+
+ fn isStrtab(self: ar_hdr) bool {
+ return mem.eql(u8, getValue(&self.ar_name), "//");
+ }
+
+ fn isSymtab(self: ar_hdr) bool {
+ return mem.eql(u8, getValue(&self.ar_name), "/");
+ }
+};
+
+pub fn isArchive(file: std.fs.File) bool {
+ const reader = file.reader();
+ const magic = reader.readBytesNoEof(Archive.SARMAG) catch return false;
+ defer file.seekTo(0) catch {};
+ if (!mem.eql(u8, &magic, ARMAG)) return false;
+ return true;
+}
+
+pub fn deinit(self: *Archive, allocator: Allocator) void {
+ allocator.free(self.data);
+ self.objects.deinit(allocator);
+}
+
+pub fn parse(self: *Archive, elf_file: *Elf) !void {
+ const gpa = elf_file.base.allocator;
+
+ var stream = std.io.fixedBufferStream(self.data);
+ const reader = stream.reader();
+ _ = try reader.readBytesNoEof(SARMAG);
+
+ while (true) {
+ if (stream.pos % 2 != 0) {
+ stream.pos += 1;
+ }
+
+ const hdr = reader.readStruct(ar_hdr) catch break;
+
+ if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) {
+ // TODO convert into an error
+ log.debug(
+ "{s}: invalid header delimiter: expected '{s}', found '{s}'",
+ .{ self.path, std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag) },
+ );
+ return;
+ }
+
+ const size = try hdr.size();
+ defer {
+ _ = stream.seekBy(size) catch {};
+ }
+
+ if (hdr.isSymtab()) continue;
+ if (hdr.isStrtab()) {
+ self.strtab = self.data[stream.pos..][0..size];
+ continue;
+ }
+
+ const name = ar_hdr.getValue(&hdr.ar_name);
+
+ if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue;
+
+ const object_name = blk: {
+ if (name[0] == '/') {
+ const off = try std.fmt.parseInt(u32, name[1..], 10);
+ break :blk self.getString(off);
+ }
+ break :blk name;
+ };
+
+ const object = Object{
+ .archive = self.path,
+ .path = try gpa.dupe(u8, object_name[0 .. object_name.len - 1]), // To account for trailing '/'
+ .data = try gpa.dupe(u8, self.data[stream.pos..][0..size]),
+ .index = undefined,
+ .alive = false,
+ };
+
+ log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, self.path });
+
+ try self.objects.append(gpa, object);
+ }
+}
+
+fn getString(self: Archive, off: u32) []const u8 {
+ assert(off < self.strtab.len);
+ return mem.sliceTo(@as([*:'\n']const u8, @ptrCast(self.strtab.ptr + off)), 0);
+}
+
+const std = @import("std");
+const assert = std.debug.assert;
+const elf = std.elf;
+const fs = std.fs;
+const log = std.log.scoped(.link);
+const mem = std.mem;
+
+const Allocator = mem.Allocator;
+const Archive = @This();
+const Elf = @import("../Elf.zig");
+const Object = @import("Object.zig");
diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig
index 82c2b46d1d..073536fbaa 100644
--- a/src/link/Elf/Atom.zig
+++ b/src/link/Elf/Atom.zig
@@ -322,11 +322,12 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void {
if (rel.r_type() == elf.R_X86_64_NONE) continue;
- const symbol = switch (file_ptr) {
- .zig_module => |x| elf_file.symbol(x.symbol(rel.r_sym())),
- .object => |x| elf_file.symbol(x.symbols.items[rel.r_sym()]),
+ const symbol_index = switch (file_ptr) {
+ .zig_module => |x| x.symbol(rel.r_sym()),
+ .object => |x| x.symbols.items[rel.r_sym()],
else => unreachable,
};
+ const symbol = elf_file.symbol(symbol_index);
// Check for violation of One Definition Rule for COMDATs.
if (symbol.file(elf_file) == null) {
@@ -340,7 +341,7 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void {
}
// Report an undefined symbol.
- try self.reportUndefined(elf_file, symbol, rel, undefs);
+ try self.reportUndefined(elf_file, symbol, symbol_index, rel, undefs);
// While traversing relocations, mark symbols that require special handling such as
// pointer indirection via GOT, or a stub trampoline via PLT.
@@ -379,7 +380,14 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void {
}
// This function will report any undefined non-weak symbols that are not imports.
-fn reportUndefined(self: Atom, elf_file: *Elf, sym: *const Symbol, rel: elf.Elf64_Rela, undefs: anytype) !void {
+fn reportUndefined(
+ self: Atom,
+ elf_file: *Elf,
+ sym: *const Symbol,
+ sym_index: Symbol.Index,
+ rel: elf.Elf64_Rela,
+ undefs: anytype,
+) !void {
const rel_esym = switch (elf_file.file(self.file_index).?) {
.zig_module => |x| x.elfSym(rel.r_sym()).*,
.object => |x| x.symtab[rel.r_sym()],
@@ -392,7 +400,7 @@ fn reportUndefined(self: Atom, elf_file: *Elf, sym: *const Symbol, rel: elf.Elf6
!sym.flags.import and
esym.st_shndx == elf.SHN_UNDEF)
{
- const gop = try undefs.getOrPut(sym.index);
+ const gop = try undefs.getOrPut(sym_index);
if (!gop.found_existing) {
gop.value_ptr.* = std.ArrayList(Atom.Index).init(elf_file.base.allocator);
}
@@ -417,6 +425,7 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
.object => |x| elf_file.symbol(x.symbols.items[rel.r_sym()]),
else => unreachable,
};
+ const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
// We will use equation format to resolve relocations:
// https://intezer.com/blog/malware-analysis/executable-and-linkable-format-101-part-3-relocations/
@@ -446,24 +455,49 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
relocs_log.debug(" {s}: {x}: [{x} => {x}] G({x}) ({s})", .{
fmtRelocType(r_type),
- rel.r_offset,
+ r_offset,
P,
S + A,
G + GOT + A,
target.name(elf_file),
});
- try stream.seekTo(rel.r_offset);
+ try stream.seekTo(r_offset);
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_PLT32,
elf.R_X86_64_PC32,
=> try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A - P))),
+ 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),
+
+ elf.R_X86_64_GOTPCRELX => {
+ if (!target.flags.import and !target.isIFunc(elf_file) and !target.isAbs(elf_file)) blk: {
+ x86_64.relaxGotpcrelx(code[r_offset - 2 ..]) catch break :blk;
+ try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A - P)));
+ continue;
+ }
+ try cwriter.writeIntLittle(i32, @as(i32, @intCast(G + GOT + A - P)));
+ },
+
+ elf.R_X86_64_REX_GOTPCRELX => {
+ if (!target.flags.import and !target.isIFunc(elf_file) and !target.isAbs(elf_file)) blk: {
+ x86_64.relaxRexGotpcrelx(code[r_offset - 3 ..]) catch break :blk;
+ try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A - P)));
+ continue;
+ }
+ try cwriter.writeIntLittle(i32, @as(i32, @intCast(G + GOT + A - P)));
+ },
+
else => {
log.err("TODO: unhandled relocation type {}", .{fmtRelocType(rel.r_type())});
@panic("TODO unhandled relocation type");
@@ -591,6 +625,58 @@ fn format2(
// future.
pub const Index = u16;
+const x86_64 = struct {
+ pub fn relaxGotpcrelx(code: []u8) !void {
+ const old_inst = disassemble(code) orelse return error.RelaxFail;
+ const inst = switch (old_inst.encoding.mnemonic) {
+ .call => try Instruction.new(old_inst.prefix, .call, &.{
+ // TODO: hack to force imm32s in the assembler
+ .{ .imm = Immediate.s(-129) },
+ }),
+ .jmp => try Instruction.new(old_inst.prefix, .jmp, &.{
+ // TODO: hack to force imm32s in the assembler
+ .{ .imm = Immediate.s(-129) },
+ }),
+ else => return error.RelaxFail,
+ };
+ relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
+ const nop = try Instruction.new(.none, .nop, &.{});
+ encode(&.{ nop, inst }, code) catch return error.RelaxFail;
+ }
+
+ pub fn relaxRexGotpcrelx(code: []u8) !void {
+ const old_inst = disassemble(code) orelse return error.RelaxFail;
+ switch (old_inst.encoding.mnemonic) {
+ .mov => {
+ const inst = try Instruction.new(old_inst.prefix, .lea, &old_inst.ops);
+ relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding });
+ encode(&.{inst}, code) catch return error.RelaxFail;
+ },
+ else => return error.RelaxFail,
+ }
+ }
+
+ fn disassemble(code: []const u8) ?Instruction {
+ var disas = Disassembler.init(code);
+ const inst = disas.next() catch return null;
+ return inst;
+ }
+
+ fn encode(insts: []const Instruction, code: []u8) !void {
+ var stream = std.io.fixedBufferStream(code);
+ const writer = stream.writer();
+ for (insts) |inst| {
+ try inst.encode(writer, .{});
+ }
+ }
+
+ const bits = @import("../../arch/x86_64/bits.zig");
+ const encoder = @import("../../arch/x86_64/encoder.zig");
+ const Disassembler = @import("../../arch/x86_64/Disassembler.zig");
+ const Immediate = bits.Immediate;
+ const Instruction = encoder.Instruction;
+};
+
const std = @import("std");
const assert = std.debug.assert;
const elf = std.elf;
diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig
index b0a6ef2a1c..32c96b8d95 100644
--- a/src/link/Elf/Object.zig
+++ b/src/link/Elf/Object.zig
@@ -485,9 +485,9 @@ pub fn claimUnresolved(self: *Object, elf_file: *Elf) void {
pub fn resetGlobals(self: *Object, elf_file: *Elf) void {
for (self.globals()) |index| {
const global = elf_file.symbol(index);
- const name = global.name;
+ const off = global.name_offset;
global.* = .{};
- global.name = name;
+ global.name_offset = off;
}
}
@@ -499,7 +499,7 @@ pub fn markLive(self: *Object, elf_file: *Elf) void {
if (sym.st_bind() == elf.STB_WEAK) continue;
const global = elf_file.symbol(index);
- const file = global.getFile(elf_file) orelse continue;
+ const file = global.file(elf_file) orelse continue;
const should_keep = sym.st_shndx == elf.SHN_UNDEF or
(sym.st_shndx == elf.SHN_COMMON and global.elfSym(elf_file).st_shndx != elf.SHN_COMMON);
if (should_keep and !file.isAlive()) {
diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig
index 42b9b81ef9..c70d0b8229 100644
--- a/src/link/Elf/Symbol.zig
+++ b/src/link/Elf/Symbol.zig
@@ -1,7 +1,5 @@
//! Represents a defined symbol.
-index: Index = 0,
-
/// Allocated address value of this symbol.
value: u64 = 0,
@@ -117,10 +115,10 @@ const GetOrCreateGotEntryResult = struct {
index: GotSection.Index,
};
-pub fn getOrCreateGotEntry(symbol: *Symbol, elf_file: *Elf) !GetOrCreateGotEntryResult {
+pub fn getOrCreateGotEntry(symbol: *Symbol, symbol_index: Index, elf_file: *Elf) !GetOrCreateGotEntryResult {
assert(symbol.flags.needs_got);
if (symbol.flags.has_got) return .{ .found_existing = true, .index = symbol.extra(elf_file).?.got };
- const index = try elf_file.got.addGotSymbol(symbol.index, elf_file);
+ const index = try elf_file.got.addGotSymbol(symbol_index, elf_file);
symbol.flags.has_got = true;
return .{ .found_existing = false, .index = index };
}
@@ -270,7 +268,7 @@ fn format2(
_ = options;
_ = unused_fmt_string;
const symbol = ctx.symbol;
- try writer.print("%{d} : {s} : @{x}", .{ symbol.index, symbol.fmtName(ctx.elf_file), symbol.value });
+ try writer.print("%{d} : {s} : @{x}", .{ symbol.esym_index, symbol.fmtName(ctx.elf_file), symbol.value });
if (symbol.file(ctx.elf_file)) |file_ptr| {
if (symbol.isAbs(ctx.elf_file)) {
if (symbol.elfSym(ctx.elf_file).st_shndx == elf.SHN_UNDEF) {
diff --git a/src/link/Elf/ZigModule.zig b/src/link/Elf/ZigModule.zig
index 46a382abf9..98496a2c38 100644
--- a/src/link/Elf/ZigModule.zig
+++ b/src/link/Elf/ZigModule.zig
@@ -148,6 +148,15 @@ pub fn scanRelocs(self: *ZigModule, elf_file: *Elf, undefs: anytype) !void {
}
}
+pub fn resetGlobals(self: *ZigModule, elf_file: *Elf) void {
+ for (self.globals()) |index| {
+ const global = elf_file.symbol(index);
+ const off = global.name_offset;
+ global.* = .{};
+ global.name_offset = off;
+ }
+}
+
pub fn updateSymtabSize(self: *ZigModule, elf_file: *Elf) void {
for (self.locals()) |local_index| {
const local = elf_file.symbol(local_index);
diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig
index 2b49f43bf1..3b9790dffa 100644
--- a/src/link/Elf/file.zig
+++ b/src/link/Elf/file.zig
@@ -62,6 +62,19 @@ pub const File = union(enum) {
return (@as(u32, base) << 24) + file.index();
}
+ pub fn resolveSymbols(file: File, elf_file: *Elf) void {
+ switch (file) {
+ inline else => |x| x.resolveSymbols(elf_file),
+ }
+ }
+
+ pub fn resetGlobals(file: File, elf_file: *Elf) void {
+ switch (file) {
+ .linker_defined => unreachable,
+ inline else => |x| x.resetGlobals(elf_file),
+ }
+ }
+
pub fn setAlive(file: File) void {
switch (file) {
.zig_module, .linker_defined => {},
@@ -71,7 +84,7 @@ pub const File = union(enum) {
pub fn markLive(file: File, elf_file: *Elf) void {
switch (file) {
- .zig_module, .linker_defined => {},
+ .zig_module, .linker_defined => unreachable,
inline else => |x| x.markLive(elf_file),
}
}