Merge pull request #17113 from ziglang/elf-linker

elf: upstream zld/ELF functionality, part 1
This commit is contained in:
Jakub Konka 2023-09-13 10:07:07 +02:00 committed by GitHub
commit 4d29b39678
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 4872 additions and 1223 deletions

View File

@ -584,6 +584,14 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/src/link/Coff/Object.zig"
"${CMAKE_SOURCE_DIR}/src/link/Coff/lld.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf/Atom.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf/LinkerDefined.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf/Object.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf/Symbol.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf/ZigModule.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf/eh_frame.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf/file.zig"
"${CMAKE_SOURCE_DIR}/src/link/Elf/synthetic_sections.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig"
"${CMAKE_SOURCE_DIR}/src/link/MachO/Atom.zig"

View File

@ -4314,10 +4314,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
if (try self.air.value(callee, mod)) |func_value| {
if (func_value.getFunction(mod)) |func| {
if (self.bin_file.cast(link.File.Elf)) |elf_file| {
const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl);
const atom = elf_file.getAtom(atom_index);
_ = try atom.getOrCreateOffsetTableEntry(elf_file);
const got_addr = @as(u32, @intCast(atom.getOffsetTableAddress(elf_file)));
const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl);
const sym = elf_file.symbol(sym_index);
_ = try sym.getOrCreateGotEntry(elf_file);
const got_addr = @as(u32, @intCast(sym.gotAddress(elf_file)));
try self.genSetReg(Type.usize, .x30, .{ .memory = got_addr });
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl);

View File

@ -4294,10 +4294,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
if (try self.air.value(callee, mod)) |func_value| {
if (func_value.getFunction(mod)) |func| {
if (self.bin_file.cast(link.File.Elf)) |elf_file| {
const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl);
const atom = elf_file.getAtom(atom_index);
_ = try atom.getOrCreateOffsetTableEntry(elf_file);
const got_addr = @as(u32, @intCast(atom.getOffsetTableAddress(elf_file)));
const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl);
const sym = elf_file.symbol(sym_index);
_ = try sym.getOrCreateGotEntry(elf_file);
const got_addr = @as(u32, @intCast(sym.gotAddress(elf_file)));
try self.genSetReg(Type.usize, .lr, .{ .memory = got_addr });
} else if (self.bin_file.cast(link.File.MachO)) |_| {
unreachable; // unsupported architecture for MachO

View File

@ -1747,10 +1747,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
if (try self.air.value(callee, mod)) |func_value| {
switch (mod.intern_pool.indexToKey(func_value.ip_index)) {
.func => |func| {
const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl);
const atom = elf_file.getAtom(atom_index);
_ = try atom.getOrCreateOffsetTableEntry(elf_file);
const got_addr = @as(u32, @intCast(atom.getOffsetTableAddress(elf_file)));
const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl);
const sym = elf_file.symbol(sym_index);
_ = try sym.getOrCreateGotEntry(elf_file);
const got_addr = @as(u32, @intCast(sym.gotAddress(elf_file)));
try self.genSetReg(Type.usize, .ra, .{ .memory = got_addr });
_ = try self.addInst(.{
.tag = .jalr,

View File

@ -1349,10 +1349,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
switch (mod.intern_pool.indexToKey(func_value.ip_index)) {
.func => |func| {
const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl);
const atom = elf_file.getAtom(atom_index);
_ = try atom.getOrCreateOffsetTableEntry(elf_file);
break :blk @as(u32, @intCast(atom.getOffsetTableAddress(elf_file)));
const sym_index = try elf_file.getOrCreateMetadataForDecl(func.owner_decl);
const sym = elf_file.symbol(sym_index);
_ = try sym.getOrCreateGotEntry(elf_file);
break :blk @as(u32, @intCast(sym.gotAddress(elf_file)));
} else unreachable;
try self.genSetReg(Type.usize, .o7, .{ .memory = got_addr });

View File

@ -125,7 +125,9 @@ const Owner = union(enum) {
.func_index => |func_index| {
const mod = ctx.bin_file.options.module.?;
const decl_index = mod.funcOwnerDeclIndex(func_index);
if (ctx.bin_file.cast(link.File.MachO)) |macho_file| {
if (ctx.bin_file.cast(link.File.Elf)) |elf_file| {
return elf_file.getOrCreateMetadataForDecl(decl_index);
} else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| {
const atom = try macho_file.getOrCreateAtomForDecl(decl_index);
return macho_file.getAtom(atom).getSymbolIndex().?;
} else if (ctx.bin_file.cast(link.File.Coff)) |coff_file| {
@ -136,7 +138,10 @@ const Owner = union(enum) {
} else unreachable;
},
.lazy_sym => |lazy_sym| {
if (ctx.bin_file.cast(link.File.MachO)) |macho_file| {
if (ctx.bin_file.cast(link.File.Elf)) |elf_file| {
return elf_file.getOrCreateMetadataForLazySymbol(lazy_sym) catch |err|
ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
} else if (ctx.bin_file.cast(link.File.MachO)) |macho_file| {
const atom = macho_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
return ctx.fail("{s} creating lazy symbol", .{@errorName(err)});
return macho_file.getAtom(atom).getSymbolIndex().?;
@ -8149,10 +8154,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
else => null,
}) |owner_decl| {
if (self.bin_file.cast(link.File.Elf)) |elf_file| {
const atom_index = try elf_file.getOrCreateAtomForDecl(owner_decl);
const atom = elf_file.getAtom(atom_index);
_ = try atom.getOrCreateOffsetTableEntry(elf_file);
const got_addr = atom.getOffsetTableAddress(elf_file);
const sym_index = try elf_file.getOrCreateMetadataForDecl(owner_decl);
const sym = elf_file.symbol(sym_index);
sym.flags.needs_got = true;
_ = try sym.getOrCreateGotEntry(elf_file);
const got_addr = sym.gotAddress(elf_file);
try self.asmMemory(.{ ._, .call }, Memory.sib(.qword, .{
.base = .{ .reg = .ds },
.disp = @intCast(got_addr),
@ -8178,7 +8184,18 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
} else if (func_value.getExternFunc(mod)) |extern_func| {
const decl_name = mod.intern_pool.stringToSlice(mod.declPtr(extern_func.decl).name);
const lib_name = mod.intern_pool.stringToSliceUnwrap(extern_func.lib_name);
if (self.bin_file.cast(link.File.Coff)) |coff_file| {
if (self.bin_file.cast(link.File.Elf)) |elf_file| {
const atom_index = try self.owner.getSymbolIndex(self);
const sym_index = try elf_file.getGlobalSymbol(decl_name, lib_name);
_ = try self.addInst(.{
.tag = .call,
.ops = .extern_fn_reloc,
.data = .{ .reloc = .{
.atom_index = atom_index,
.sym_index = sym_index,
} },
});
} else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
const atom_index = try self.owner.getSymbolIndex(self);
const sym_index = try coff_file.getGlobalSymbol(decl_name, lib_name);
_ = try self.addInst(.{
@ -10215,11 +10232,12 @@ fn genLazySymbolRef(
lazy_sym: link.File.LazySymbol,
) InnerError!void {
if (self.bin_file.cast(link.File.Elf)) |elf_file| {
const atom_index = elf_file.getOrCreateAtomForLazySymbol(lazy_sym) catch |err|
const sym_index = elf_file.getOrCreateMetadataForLazySymbol(lazy_sym) catch |err|
return self.fail("{s} creating lazy symbol", .{@errorName(err)});
const atom = elf_file.getAtom(atom_index);
_ = try atom.getOrCreateOffsetTableEntry(elf_file);
const got_addr = atom.getOffsetTableAddress(elf_file);
const sym = elf_file.symbol(sym_index);
sym.flags.needs_got = true;
_ = try sym.getOrCreateGotEntry(elf_file);
const got_addr = sym.gotAddress(elf_file);
const got_mem =
Memory.sib(.qword, .{ .base = .{ .reg = .ds }, .disp = @intCast(got_addr) });
switch (tag) {

View File

@ -41,7 +41,15 @@ pub fn emitMir(emit: *Emit) Error!void {
.offset = end_offset - 4,
.length = @as(u5, @intCast(end_offset - start_offset)),
}),
.linker_extern_fn => |symbol| if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
.linker_extern_fn => |symbol| if (emit.bin_file.cast(link.File.Elf)) |elf_file| {
// Add relocation to the decl.
const atom_ptr = elf_file.symbol(symbol.atom_index).atom(elf_file).?;
try atom_ptr.addReloc(elf_file, .{
.r_offset = end_offset - 4,
.r_info = (@as(u64, @intCast(symbol.sym_index)) << 32) | std.elf.R_X86_64_PLT32,
.r_addend = -4,
});
} else if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
// Add relocation to the decl.
const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = symbol.atom_index }).?;
const target = macho_file.getGlobalByIndex(symbol.sym_index);

View File

@ -858,10 +858,11 @@ fn genDeclRef(
const is_threadlocal = tv.val.isPtrToThreadLocal(mod) and !bin_file.options.single_threaded;
if (bin_file.cast(link.File.Elf)) |elf_file| {
const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index);
const atom = elf_file.getAtom(atom_index);
_ = try atom.getOrCreateOffsetTableEntry(elf_file);
return GenResult.mcv(.{ .memory = atom.getOffsetTableAddress(elf_file) });
const sym_index = try elf_file.getOrCreateMetadataForDecl(decl_index);
const sym = elf_file.symbol(sym_index);
sym.flags.needs_got = true;
_ = try sym.getOrCreateGotEntry(elf_file);
return GenResult.mcv(.{ .memory = sym.gotAddress(elf_file) });
} else if (bin_file.cast(link.File.MachO)) |macho_file| {
const atom_index = try macho_file.getOrCreateAtomForDecl(decl_index);
const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
@ -896,7 +897,7 @@ fn genUnnamedConst(
return GenResult.fail(bin_file.allocator, src_loc, "lowering unnamed constant failed: {s}", .{@errorName(err)});
};
if (bin_file.cast(link.File.Elf)) |elf_file| {
return GenResult.mcv(.{ .memory = elf_file.getSymbol(local_sym_index).st_value });
return GenResult.mcv(.{ .memory = elf_file.symbol(local_sym_index).value });
} else if (bin_file.cast(link.File.MachO)) |_| {
return GenResult.mcv(.{ .load_direct = local_sym_index });
} else if (bin_file.cast(link.File.Coff)) |_| {

View File

@ -549,7 +549,7 @@ pub const File = struct {
switch (base.tag) {
// zig fmt: off
.coff => return @fieldParentPtr(Coff, "base", base).getGlobalSymbol(name, lib_name),
.elf => unreachable,
.elf => return @fieldParentPtr(Elf, "base", base).getGlobalSymbol(name, lib_name),
.macho => return @fieldParentPtr(MachO, "base", base).getGlobalSymbol(name, lib_name),
.plan9 => unreachable,
.spirv => unreachable,
@ -849,6 +849,7 @@ pub const File = struct {
pub fn miscErrors(base: *File) []const ErrorMsg {
switch (base.tag) {
.elf => return @fieldParentPtr(Elf, "base", base).misc_errors.items,
.macho => return @fieldParentPtr(MachO, "base", base).misc_errors.items,
else => return &.{},
}

View File

@ -1108,7 +1108,7 @@ pub fn commitDeclState(
switch (self.bin_file.tag) {
.elf => {
const elf_file = self.bin_file.cast(File.Elf).?;
const debug_line_sect = &elf_file.sections.items(.shdr)[elf_file.debug_line_section_index.?];
const debug_line_sect = &elf_file.shdrs.items[elf_file.debug_line_section_index.?];
const file_pos = debug_line_sect.sh_offset + src_fn.off;
try pwriteDbgLineNops(elf_file.base.file.?, file_pos, 0, &[0]u8{}, src_fn.len);
},
@ -1170,7 +1170,7 @@ pub fn commitDeclState(
const elf_file = self.bin_file.cast(File.Elf).?;
const shdr_index = elf_file.debug_line_section_index.?;
try elf_file.growNonAllocSection(shdr_index, needed_size, 1, true);
const debug_line_sect = elf_file.sections.items(.shdr)[shdr_index];
const debug_line_sect = elf_file.shdrs.items[shdr_index];
const file_pos = debug_line_sect.sh_offset + src_fn.off;
try pwriteDbgLineNops(
elf_file.base.file.?,
@ -1337,7 +1337,7 @@ fn updateDeclDebugInfoAllocation(self: *Dwarf, atom_index: Atom.Index, len: u32)
switch (self.bin_file.tag) {
.elf => {
const elf_file = self.bin_file.cast(File.Elf).?;
const debug_info_sect = &elf_file.sections.items(.shdr)[elf_file.debug_info_section_index.?];
const debug_info_sect = &elf_file.shdrs.items[elf_file.debug_info_section_index.?];
const file_pos = debug_info_sect.sh_offset + atom.off;
try pwriteDbgInfoNops(elf_file.base.file.?, file_pos, 0, &[0]u8{}, atom.len, false);
},
@ -1415,7 +1415,7 @@ fn writeDeclDebugInfo(self: *Dwarf, atom_index: Atom.Index, dbg_info_buf: []cons
const elf_file = self.bin_file.cast(File.Elf).?;
const shdr_index = elf_file.debug_info_section_index.?;
try elf_file.growNonAllocSection(shdr_index, needed_size, 1, true);
const debug_info_sect = elf_file.sections.items(.shdr)[shdr_index];
const debug_info_sect = &elf_file.shdrs.items[shdr_index];
const file_pos = debug_info_sect.sh_offset + atom.off;
try pwriteDbgInfoNops(
elf_file.base.file.?,
@ -1496,7 +1496,7 @@ pub fn updateDeclLineNumber(self: *Dwarf, mod: *Module, decl_index: Module.Decl.
switch (self.bin_file.tag) {
.elf => {
const elf_file = self.bin_file.cast(File.Elf).?;
const shdr = elf_file.sections.items(.shdr)[elf_file.debug_line_section_index.?];
const shdr = elf_file.shdrs.items[elf_file.debug_line_section_index.?];
const file_pos = shdr.sh_offset + atom.off + self.getRelocDbgLineOff();
try elf_file.base.file.?.pwriteAll(&data, file_pos);
},
@ -1713,7 +1713,7 @@ pub fn writeDbgAbbrev(self: *Dwarf) !void {
const elf_file = self.bin_file.cast(File.Elf).?;
const shdr_index = elf_file.debug_abbrev_section_index.?;
try elf_file.growNonAllocSection(shdr_index, needed_size, 1, false);
const debug_abbrev_sect = elf_file.sections.items(.shdr)[shdr_index];
const debug_abbrev_sect = &elf_file.shdrs.items[shdr_index];
const file_pos = debug_abbrev_sect.sh_offset + abbrev_offset;
try elf_file.base.file.?.pwriteAll(&abbrev_buf, file_pos);
},
@ -1828,7 +1828,7 @@ pub fn writeDbgInfoHeader(self: *Dwarf, module: *Module, low_pc: u64, high_pc: u
switch (self.bin_file.tag) {
.elf => {
const elf_file = self.bin_file.cast(File.Elf).?;
const debug_info_sect = elf_file.sections.items(.shdr)[elf_file.debug_info_section_index.?];
const debug_info_sect = &elf_file.shdrs.items[elf_file.debug_info_section_index.?];
const file_pos = debug_info_sect.sh_offset;
try pwriteDbgInfoNops(elf_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt, false);
},
@ -2147,7 +2147,7 @@ pub fn writeDbgAranges(self: *Dwarf, addr: u64, size: u64) !void {
const elf_file = self.bin_file.cast(File.Elf).?;
const shdr_index = elf_file.debug_aranges_section_index.?;
try elf_file.growNonAllocSection(shdr_index, needed_size, 16, false);
const debug_aranges_sect = elf_file.sections.items(.shdr)[shdr_index];
const debug_aranges_sect = &elf_file.shdrs.items[shdr_index];
const file_pos = debug_aranges_sect.sh_offset;
try elf_file.base.file.?.pwriteAll(di_buf.items, file_pos);
},
@ -2312,9 +2312,9 @@ pub fn writeDbgLineHeader(self: *Dwarf) !void {
.elf => {
const elf_file = self.bin_file.cast(File.Elf).?;
const shdr_index = elf_file.debug_line_section_index.?;
const needed_size = elf_file.sections.items(.shdr)[shdr_index].sh_size + delta;
const needed_size = elf_file.shdrs.items[shdr_index].sh_size + delta;
try elf_file.growNonAllocSection(shdr_index, needed_size, 1, true);
const file_pos = elf_file.sections.items(.shdr)[shdr_index].sh_offset + first_fn.off;
const file_pos = elf_file.shdrs.items[shdr_index].sh_offset + first_fn.off;
const amt = try elf_file.base.file.?.preadAll(buffer, file_pos);
if (amt != buffer.len) return error.InputOutput;
@ -2377,7 +2377,7 @@ pub fn writeDbgLineHeader(self: *Dwarf) !void {
switch (self.bin_file.tag) {
.elf => {
const elf_file = self.bin_file.cast(File.Elf).?;
const debug_line_sect = elf_file.sections.items(.shdr)[elf_file.debug_line_section_index.?];
const debug_line_sect = &elf_file.shdrs.items[elf_file.debug_line_section_index.?];
const file_pos = debug_line_sect.sh_offset;
try pwriteDbgLineNops(elf_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt);
},
@ -2500,7 +2500,7 @@ pub fn flushModule(self: *Dwarf, module: *Module) !void {
switch (self.bin_file.tag) {
.elf => {
const elf_file = self.bin_file.cast(File.Elf).?;
const debug_info_sect = &elf_file.sections.items(.shdr)[elf_file.debug_info_section_index.?];
const debug_info_sect = &elf_file.shdrs.items[elf_file.debug_info_section_index.?];
break :blk debug_info_sect.sh_offset;
},
.macho => {

File diff suppressed because it is too large Load Diff

View File

@ -1,108 +1,604 @@
const Atom = @This();
/// Address allocated for this Atom.
value: u64 = 0,
const std = @import("std");
const assert = std.debug.assert;
const elf = std.elf;
/// Name of this Atom.
name_offset: u32 = 0,
const Elf = @import("../Elf.zig");
/// Index into linker's input file table.
file_index: File.Index = 0,
/// Each decl always gets a local symbol with the fully qualified name.
/// The vaddr and size are found here directly.
/// The file offset is found by computing the vaddr offset from the section vaddr
/// the symbol references, and adding that to the file offset of the section.
/// If this field is 0, it means the codegen size = 0 and there is no symbol or
/// offset table entry.
local_sym_index: u32,
/// Size of this atom
size: u64 = 0,
/// Alignment of this atom as a power of two.
alignment: u8 = 0,
/// Index of the input section.
input_section_index: Index = 0,
/// Index of the output section.
output_section_index: Index = 0,
/// Index of the input section containing this atom's relocs.
relocs_section_index: Index = 0,
/// Index of this atom in the linker's atoms table.
atom_index: Index = 0,
/// Specifies whether this atom is alive or has been garbage collected.
alive: bool = false,
/// Specifies if the atom has been visited during garbage collection.
visited: bool = false,
/// Start index of FDEs referencing this atom.
fde_start: u32 = 0,
/// End index of FDEs referencing this atom.
fde_end: u32 = 0,
/// Points to the previous and next neighbors, based on the `text_offset`.
/// This can be used to find, for example, the capacity of this `TextBlock`.
prev_index: ?Index,
next_index: ?Index,
prev_index: Index = 0,
next_index: Index = 0,
pub const Index = u32;
pub const Reloc = struct {
target: u32,
offset: u64,
addend: u32,
prev_vaddr: u64,
};
pub fn getSymbolIndex(self: Atom) ?u32 {
if (self.local_sym_index == 0) return null;
return self.local_sym_index;
pub fn name(self: Atom, elf_file: *Elf) []const u8 {
return elf_file.strtab.getAssumeExists(self.name_offset);
}
pub fn getSymbol(self: Atom, elf_file: *const Elf) elf.Elf64_Sym {
return elf_file.getSymbol(self.getSymbolIndex().?);
pub fn inputShdr(self: Atom, elf_file: *Elf) elf.Elf64_Shdr {
const object = elf_file.file(self.file_index).?.object;
return object.shdrs.items[self.input_section_index];
}
pub fn getSymbolPtr(self: Atom, elf_file: *Elf) *elf.Elf64_Sym {
return elf_file.getSymbolPtr(self.getSymbolIndex().?);
pub fn codeInObject(self: Atom, elf_file: *Elf) error{Overflow}![]const u8 {
const object = elf_file.file(self.file_index).?.object;
return object.shdrContents(self.input_section_index);
}
pub fn getName(self: Atom, elf_file: *const Elf) []const u8 {
return elf_file.getSymbolName(self.getSymbolIndex().?);
/// Returns atom's code and optionally uncompresses data if required (for compressed sections).
/// Caller owns the memory.
pub fn codeInObjectUncompressAlloc(self: Atom, elf_file: *Elf) ![]u8 {
const gpa = elf_file.base.allocator;
const data = try self.codeInObject(elf_file);
const shdr = self.inputShdr(elf_file);
if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {
const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
switch (chdr.ch_type) {
.ZLIB => {
var stream = std.io.fixedBufferStream(data[@sizeOf(elf.Elf64_Chdr)..]);
var zlib_stream = std.compress.zlib.decompressStream(gpa, stream.reader()) catch
return error.InputOutput;
defer zlib_stream.deinit();
const size = std.math.cast(usize, chdr.ch_size) orelse return error.Overflow;
const decomp = try gpa.alloc(u8, size);
const nread = zlib_stream.reader().readAll(decomp) catch return error.InputOutput;
if (nread != decomp.len) {
return error.InputOutput;
}
return decomp;
},
else => @panic("TODO unhandled compression scheme"),
}
} else return gpa.dupe(u8, data);
}
/// If entry already exists, returns index to it.
/// Otherwise, creates a new entry in the Global Offset Table for this Atom.
pub fn getOrCreateOffsetTableEntry(self: Atom, elf_file: *Elf) !u32 {
const sym_index = self.getSymbolIndex().?;
if (elf_file.got_table.lookup.get(sym_index)) |index| return index;
const index = try elf_file.got_table.allocateEntry(elf_file.base.allocator, sym_index);
elf_file.got_table_count_dirty = true;
return index;
}
pub fn getOffsetTableAddress(self: Atom, elf_file: *Elf) u64 {
const sym_index = self.getSymbolIndex().?;
const got_entry_index = elf_file.got_table.lookup.get(sym_index).?;
const target = elf_file.base.options.target;
const ptr_bits = target.ptrBitWidth();
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
const got = elf_file.program_headers.items[elf_file.phdr_got_index.?];
return got.p_vaddr + got_entry_index * ptr_bytes;
pub fn priority(self: Atom, elf_file: *Elf) u64 {
const index = elf_file.file(self.file_index).?.index();
return (@as(u64, @intCast(index)) << 32) | @as(u64, @intCast(self.input_section_index));
}
/// Returns how much room there is to grow in virtual address space.
/// File offset relocation happens transparently, so it is not included in
/// this calculation.
pub fn capacity(self: Atom, elf_file: *const Elf) u64 {
const self_sym = self.getSymbol(elf_file);
if (self.next_index) |next_index| {
const next = elf_file.getAtom(next_index);
const next_sym = next.getSymbol(elf_file);
return next_sym.st_value - self_sym.st_value;
} else {
// We are the last block. The capacity is limited only by virtual address space.
return std.math.maxInt(u32) - self_sym.st_value;
}
pub fn capacity(self: Atom, elf_file: *Elf) u64 {
const next_value = if (elf_file.atom(self.next_index)) |next| next.value else std.math.maxInt(u32);
return next_value - self.value;
}
pub fn freeListEligible(self: Atom, elf_file: *const Elf) bool {
pub fn freeListEligible(self: Atom, elf_file: *Elf) bool {
// No need to keep a free list node for the last block.
const next_index = self.next_index orelse return false;
const next = elf_file.getAtom(next_index);
const self_sym = self.getSymbol(elf_file);
const next_sym = next.getSymbol(elf_file);
const cap = next_sym.st_value - self_sym.st_value;
const ideal_cap = Elf.padToIdeal(self_sym.st_size);
const next = elf_file.atom(self.next_index) orelse return false;
const cap = next.value - self.value;
const ideal_cap = Elf.padToIdeal(self.size);
if (cap <= ideal_cap) return false;
const surplus = cap - ideal_cap;
return surplus >= Elf.min_text_capacity;
}
pub fn addRelocation(elf_file: *Elf, atom_index: Index, reloc: Reloc) !void {
const gpa = elf_file.base.allocator;
const gop = try elf_file.relocs.getOrPut(gpa, atom_index);
if (!gop.found_existing) {
gop.value_ptr.* = .{};
pub fn allocate(self: *Atom, elf_file: *Elf) !void {
const shdr = &elf_file.shdrs.items[self.output_section_index];
const meta = elf_file.last_atom_and_free_list_table.getPtr(self.output_section_index).?;
const free_list = &meta.free_list;
const last_atom_index = &meta.last_atom_index;
const new_atom_ideal_capacity = Elf.padToIdeal(self.size);
const alignment = try std.math.powi(u64, 2, self.alignment);
// We use these to indicate our intention to update metadata, placing the new atom,
// and possibly removing a free list node.
// It would be simpler to do it inside the for loop below, but that would cause a
// problem if an error was returned later in the function. So this action
// is actually carried out at the end of the function, when errors are no longer possible.
var atom_placement: ?Atom.Index = null;
var free_list_removal: ?usize = null;
// First we look for an appropriately sized free list node.
// The list is unordered. We'll just take the first thing that works.
self.value = blk: {
var i: usize = if (elf_file.base.child_pid == null) 0 else free_list.items.len;
while (i < free_list.items.len) {
const big_atom_index = free_list.items[i];
const big_atom = elf_file.atom(big_atom_index).?;
// We now have a pointer to a live atom that has too much capacity.
// Is it enough that we could fit this new atom?
const cap = big_atom.capacity(elf_file);
const ideal_capacity = Elf.padToIdeal(cap);
const ideal_capacity_end_vaddr = std.math.add(u64, big_atom.value, ideal_capacity) catch ideal_capacity;
const capacity_end_vaddr = big_atom.value + cap;
const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
const new_start_vaddr = std.mem.alignBackward(u64, new_start_vaddr_unaligned, alignment);
if (new_start_vaddr < ideal_capacity_end_vaddr) {
// Additional bookkeeping here to notice if this free list node
// should be deleted because the block that it points to has grown to take up
// more of the extra capacity.
if (!big_atom.freeListEligible(elf_file)) {
_ = free_list.swapRemove(i);
} else {
i += 1;
}
continue;
}
// At this point we know that we will place the new block here. But the
// remaining question is whether there is still yet enough capacity left
// over for there to still be a free list node.
const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
const keep_free_list_node = remaining_capacity >= Elf.min_text_capacity;
// Set up the metadata to be updated, after errors are no longer possible.
atom_placement = big_atom_index;
if (!keep_free_list_node) {
free_list_removal = i;
}
break :blk new_start_vaddr;
} else if (elf_file.atom(last_atom_index.*)) |last| {
const ideal_capacity = Elf.padToIdeal(last.size);
const ideal_capacity_end_vaddr = last.value + ideal_capacity;
const new_start_vaddr = std.mem.alignForward(u64, ideal_capacity_end_vaddr, alignment);
// Set up the metadata to be updated, after errors are no longer possible.
atom_placement = last.atom_index;
break :blk new_start_vaddr;
} else {
break :blk shdr.sh_addr;
}
};
const expand_section = if (atom_placement) |placement_index|
elf_file.atom(placement_index).?.next_index == 0
else
true;
if (expand_section) {
const needed_size = (self.value + self.size) - shdr.sh_addr;
try elf_file.growAllocSection(self.output_section_index, needed_size);
last_atom_index.* = self.atom_index;
if (elf_file.dwarf) |_| {
// The .debug_info section has `low_pc` and `high_pc` values which is the virtual address
// range of the compilation unit. When we expand the text section, this range changes,
// so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty.
elf_file.debug_info_header_dirty = true;
// This becomes dirty for the same reason. We could potentially make this more
// fine-grained with the addition of support for more compilation units. It is planned to
// model each package as a different compilation unit.
elf_file.debug_aranges_section_dirty = true;
}
}
shdr.sh_addralign = @max(shdr.sh_addralign, alignment);
// This function can also reallocate an atom.
// In this case we need to "unplug" it from its previous location before
// plugging it in to its new location.
if (elf_file.atom(self.prev_index)) |prev| {
prev.next_index = self.next_index;
}
if (elf_file.atom(self.next_index)) |next| {
next.prev_index = self.prev_index;
}
if (atom_placement) |big_atom_index| {
const big_atom = elf_file.atom(big_atom_index).?;
self.prev_index = big_atom_index;
self.next_index = big_atom.next_index;
big_atom.next_index = self.atom_index;
} else {
self.prev_index = 0;
self.next_index = 0;
}
if (free_list_removal) |i| {
_ = free_list.swapRemove(i);
}
try gop.value_ptr.append(gpa, reloc);
}
pub fn freeRelocations(elf_file: *Elf, atom_index: Index) void {
var removed_relocs = elf_file.relocs.fetchRemove(atom_index);
if (removed_relocs) |*relocs| relocs.value.deinit(elf_file.base.allocator);
pub fn shrink(self: *Atom, elf_file: *Elf) void {
_ = self;
_ = elf_file;
}
pub fn grow(self: *Atom, elf_file: *Elf) !void {
const alignment = try std.math.powi(u64, 2, self.alignment);
const align_ok = std.mem.alignBackward(u64, self.value, alignment) == self.value;
const need_realloc = !align_ok or self.size > self.capacity(elf_file);
if (need_realloc) try self.allocate(elf_file);
}
pub fn free(self: *Atom, elf_file: *Elf) void {
log.debug("freeAtom {d} ({s})", .{ self.atom_index, self.name(elf_file) });
const gpa = elf_file.base.allocator;
const zig_module = elf_file.file(self.file_index).?.zig_module;
const shndx = self.output_section_index;
const meta = elf_file.last_atom_and_free_list_table.getPtr(shndx).?;
const free_list = &meta.free_list;
const last_atom_index = &meta.last_atom_index;
var already_have_free_list_node = false;
{
var i: usize = 0;
// TODO turn free_list into a hash map
while (i < free_list.items.len) {
if (free_list.items[i] == self.atom_index) {
_ = free_list.swapRemove(i);
continue;
}
if (free_list.items[i] == self.prev_index) {
already_have_free_list_node = true;
}
i += 1;
}
}
if (elf_file.atom(last_atom_index.*)) |last_atom| {
if (last_atom.atom_index == self.atom_index) {
if (elf_file.atom(self.prev_index)) |_| {
// TODO shrink the section size here
last_atom_index.* = self.prev_index;
} else {
last_atom_index.* = 0;
}
}
}
if (elf_file.atom(self.prev_index)) |prev| {
prev.next_index = self.next_index;
if (!already_have_free_list_node and prev.*.freeListEligible(elf_file)) {
// The free list is heuristics, it doesn't have to be perfect, so we can
// ignore the OOM here.
free_list.append(gpa, prev.atom_index) catch {};
}
} else {
self.prev_index = 0;
}
if (elf_file.atom(self.next_index)) |next| {
next.prev_index = self.prev_index;
} else {
self.next_index = 0;
}
// TODO create relocs free list
self.freeRelocs(elf_file);
assert(zig_module.atoms.swapRemove(self.atom_index));
self.* = .{};
}
pub fn relocs(self: Atom, elf_file: *Elf) error{Overflow}![]align(1) const elf.Elf64_Rela {
return switch (elf_file.file(self.file_index).?) {
.zig_module => |x| x.relocs.items[self.relocs_section_index].items,
.object => |x| x.getRelocs(self.relocs_section_index),
else => unreachable,
};
}
pub fn addReloc(self: Atom, elf_file: *Elf, reloc: elf.Elf64_Rela) !void {
const gpa = elf_file.base.allocator;
const file_ptr = elf_file.file(self.file_index).?;
assert(file_ptr == .zig_module);
const zig_module = file_ptr.zig_module;
const rels = &zig_module.relocs.items[self.relocs_section_index];
try rels.append(gpa, reloc);
}
pub fn freeRelocs(self: Atom, elf_file: *Elf) void {
const file_ptr = elf_file.file(self.file_index).?;
assert(file_ptr == .zig_module);
const zig_module = file_ptr.zig_module;
zig_module.relocs.items[self.relocs_section_index].clearRetainingCapacity();
}
pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void {
const file_ptr = elf_file.file(self.file_index).?;
const rels = try self.relocs(elf_file);
var i: usize = 0;
while (i < rels.len) : (i += 1) {
const rel = rels[i];
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()]),
else => unreachable,
};
// Check for violation of One Definition Rule for COMDATs.
if (symbol.file(elf_file) == null) {
// TODO convert into an error
log.debug("{}: {s}: {s} refers to a discarded COMDAT section", .{
file_ptr.fmtPath(),
self.name(elf_file),
symbol.name(elf_file),
});
continue;
}
// Report an undefined symbol.
try self.reportUndefined(elf_file, symbol, rel, undefs);
// 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_32,
elf.R_X86_64_32S,
=> {},
elf.R_X86_64_GOT32,
elf.R_X86_64_GOT64,
elf.R_X86_64_GOTPC32,
elf.R_X86_64_GOTPC64,
elf.R_X86_64_GOTPCREL,
elf.R_X86_64_GOTPCREL64,
elf.R_X86_64_GOTPCRELX,
elf.R_X86_64_REX_GOTPCRELX,
=> {
symbol.flags.needs_got = true;
},
elf.R_X86_64_PLT32,
elf.R_X86_64_PLTOFF64,
=> {
if (symbol.flags.import) {
symbol.flags.needs_plt = true;
}
},
elf.R_X86_64_PC32 => {},
else => @panic("TODO"),
}
}
}
// 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 {
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()],
else => unreachable,
};
const esym = sym.elfSym(elf_file);
if (rel_esym.st_shndx == elf.SHN_UNDEF and
rel_esym.st_bind() == elf.STB_GLOBAL and
sym.esym_index > 0 and
!sym.flags.import and
esym.st_shndx == elf.SHN_UNDEF)
{
const gop = try undefs.getOrPut(sym.index);
if (!gop.found_existing) {
gop.value_ptr.* = std.ArrayList(Atom.Index).init(elf_file.base.allocator);
}
try gop.value_ptr.append(self.atom_index);
}
}
/// TODO mark relocs dirty
pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
relocs_log.debug("0x{x}: {s}", .{ self.value, self.name(elf_file) });
const file_ptr = elf_file.file(self.file_index).?;
var stream = std.io.fixedBufferStream(code);
const cwriter = stream.writer();
for (try self.relocs(elf_file)) |rel| {
const r_type = rel.r_type();
if (r_type == elf.R_X86_64_NONE) continue;
const target = 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()]),
else => unreachable,
};
// We will use equation format to resolve relocations:
// https://intezer.com/blog/malware-analysis/executable-and-linkable-format-101-part-3-relocations/
//
// Address of the source atom.
const P = @as(i64, @intCast(self.value + rel.r_offset));
// Addend from the relocation.
const A = rel.r_addend;
// Address of the target symbol - can be address of the symbol within an atom or address of PLT stub.
const S = @as(i64, @intCast(target.address(.{}, elf_file)));
// Address of the global offset table.
const GOT = blk: {
const shndx = if (elf_file.got_plt_section_index) |shndx|
shndx
else if (elf_file.got_section_index) |shndx|
shndx
else
null;
break :blk if (shndx) |index| @as(i64, @intCast(elf_file.shdrs.items[index].sh_addr)) else 0;
};
// Relative offset to the start of the global offset table.
const G = @as(i64, @intCast(target.gotAddress(elf_file))) - GOT;
// // Address of the thread pointer.
// const TP = @as(i64, @intCast(elf_file.getTpAddress()));
// // Address of the dynamic thread pointer.
// const DTP = @as(i64, @intCast(elf_file.getDtpAddress()));
relocs_log.debug(" {s}: {x}: [{x} => {x}] G({x}) ({s})", .{
fmtRelocType(r_type),
rel.r_offset,
P,
S + A,
G + GOT + A,
target.name(elf_file),
});
try stream.seekTo(rel.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_PLT32,
elf.R_X86_64_PC32,
=> try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A - P))),
else => {
log.err("TODO: unhandled relocation type {}", .{fmtRelocType(rel.r_type())});
@panic("TODO unhandled relocation type");
},
}
}
}
pub fn fmtRelocType(r_type: u32) std.fmt.Formatter(formatRelocType) {
return .{ .data = r_type };
}
fn formatRelocType(
r_type: u32,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
const str = switch (r_type) {
elf.R_X86_64_NONE => "R_X86_64_NONE",
elf.R_X86_64_64 => "R_X86_64_64",
elf.R_X86_64_PC32 => "R_X86_64_PC32",
elf.R_X86_64_GOT32 => "R_X86_64_GOT32",
elf.R_X86_64_PLT32 => "R_X86_64_PLT32",
elf.R_X86_64_COPY => "R_X86_64_COPY",
elf.R_X86_64_GLOB_DAT => "R_X86_64_GLOB_DAT",
elf.R_X86_64_JUMP_SLOT => "R_X86_64_JUMP_SLOT",
elf.R_X86_64_RELATIVE => "R_X86_64_RELATIVE",
elf.R_X86_64_GOTPCREL => "R_X86_64_GOTPCREL",
elf.R_X86_64_32 => "R_X86_64_32",
elf.R_X86_64_32S => "R_X86_64_32S",
elf.R_X86_64_16 => "R_X86_64_16",
elf.R_X86_64_PC16 => "R_X86_64_PC16",
elf.R_X86_64_8 => "R_X86_64_8",
elf.R_X86_64_PC8 => "R_X86_64_PC8",
elf.R_X86_64_DTPMOD64 => "R_X86_64_DTPMOD64",
elf.R_X86_64_DTPOFF64 => "R_X86_64_DTPOFF64",
elf.R_X86_64_TPOFF64 => "R_X86_64_TPOFF64",
elf.R_X86_64_TLSGD => "R_X86_64_TLSGD",
elf.R_X86_64_TLSLD => "R_X86_64_TLSLD",
elf.R_X86_64_DTPOFF32 => "R_X86_64_DTPOFF32",
elf.R_X86_64_GOTTPOFF => "R_X86_64_GOTTPOFF",
elf.R_X86_64_TPOFF32 => "R_X86_64_TPOFF32",
elf.R_X86_64_PC64 => "R_X86_64_PC64",
elf.R_X86_64_GOTOFF64 => "R_X86_64_GOTOFF64",
elf.R_X86_64_GOTPC32 => "R_X86_64_GOTPC32",
elf.R_X86_64_GOT64 => "R_X86_64_GOT64",
elf.R_X86_64_GOTPCREL64 => "R_X86_64_GOTPCREL64",
elf.R_X86_64_GOTPC64 => "R_X86_64_GOTPC64",
elf.R_X86_64_GOTPLT64 => "R_X86_64_GOTPLT64",
elf.R_X86_64_PLTOFF64 => "R_X86_64_PLTOFF64",
elf.R_X86_64_SIZE32 => "R_X86_64_SIZE32",
elf.R_X86_64_SIZE64 => "R_X86_64_SIZE64",
elf.R_X86_64_GOTPC32_TLSDESC => "R_X86_64_GOTPC32_TLSDESC",
elf.R_X86_64_TLSDESC_CALL => "R_X86_64_TLSDESC_CALL",
elf.R_X86_64_TLSDESC => "R_X86_64_TLSDESC",
elf.R_X86_64_IRELATIVE => "R_X86_64_IRELATIVE",
elf.R_X86_64_RELATIVE64 => "R_X86_64_RELATIVE64",
elf.R_X86_64_GOTPCRELX => "R_X86_64_GOTPCRELX",
elf.R_X86_64_REX_GOTPCRELX => "R_X86_64_REX_GOTPCRELX",
elf.R_X86_64_NUM => "R_X86_64_NUM",
else => "R_X86_64_UNKNOWN",
};
try writer.print("{s}", .{str});
}
pub fn format(
atom: Atom,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = atom;
_ = unused_fmt_string;
_ = options;
_ = writer;
@compileError("do not format symbols directly");
}
pub fn fmt(atom: Atom, elf_file: *Elf) std.fmt.Formatter(format2) {
return .{ .data = .{
.atom = atom,
.elf_file = elf_file,
} };
}
const FormatContext = struct {
atom: Atom,
elf_file: *Elf,
};
fn format2(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
const atom = ctx.atom;
const elf_file = ctx.elf_file;
try writer.print("atom({d}) : {s} : @{x} : sect({d}) : align({x}) : size({x})", .{
atom.atom_index, atom.name(elf_file), atom.value,
atom.output_section_index, atom.alignment, atom.size,
});
// if (atom.fde_start != atom.fde_end) {
// try writer.writeAll(" : fdes{ ");
// for (atom.getFdes(elf_file), atom.fde_start..) |fde, i| {
// try writer.print("{d}", .{i});
// if (!fde.alive) try writer.writeAll("([*])");
// if (i < atom.fde_end - 1) try writer.writeAll(", ");
// }
// try writer.writeAll(" }");
// }
const gc_sections = if (elf_file.base.options.gc_sections) |gc_sections| gc_sections else false;
if (gc_sections and !atom.alive) {
try writer.writeAll(" : [*]");
}
}
// TODO this has to be u32 but for now, to avoid redesigning elfSym machinery for
// ZigModule, keep it at u16 with the intention of bumping it to u32 in the near
// future.
pub const Index = u16;
const std = @import("std");
const assert = std.debug.assert;
const elf = std.elf;
const log = std.log.scoped(.link);
const relocs_log = std.log.scoped(.link_relocs);
const Allocator = std.mem.Allocator;
const Atom = @This();
const Elf = @import("../Elf.zig");
const File = @import("file.zig").File;
const Symbol = @import("Symbol.zig");

View File

@ -0,0 +1,112 @@
index: File.Index,
symtab: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{},
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
output_symtab_size: Elf.SymtabSize = .{},
pub fn deinit(self: *LinkerDefined, allocator: Allocator) void {
self.symtab.deinit(allocator);
self.symbols.deinit(allocator);
}
pub fn addGlobal(self: *LinkerDefined, name: [:0]const u8, elf_file: *Elf) !u32 {
const gpa = elf_file.base.allocator;
try self.symtab.ensureUnusedCapacity(gpa, 1);
try self.symbols.ensureUnusedCapacity(gpa, 1);
self.symtab.appendAssumeCapacity(.{
.st_name = try elf_file.strtab.insert(gpa, name),
.st_info = elf.STB_GLOBAL << 4,
.st_other = @intFromEnum(elf.STV.HIDDEN),
.st_shndx = elf.SHN_ABS,
.st_value = 0,
.st_size = 0,
});
const off = try elf_file.strtab.insert(gpa, name);
const gop = try elf_file.getOrPutGlobal(off);
self.symbols.addOneAssumeCapacity().* = gop.index;
return gop.index;
}
pub fn resolveSymbols(self: *LinkerDefined, elf_file: *Elf) void {
for (self.symbols.items, 0..) |index, i| {
const sym_idx = @as(Symbol.Index, @intCast(i));
const this_sym = self.symtab.items[sym_idx];
if (this_sym.st_shndx == elf.SHN_UNDEF) continue;
const global = elf_file.symbol(index);
if (self.asFile().symbolRank(this_sym, false) < global.symbolRank(elf_file)) {
global.value = 0;
global.name_offset = global.name_offset;
global.atom_index = 0;
global.file_index = self.index;
global.esym_index = sym_idx;
global.version_index = elf_file.default_sym_version;
}
}
}
pub fn updateSymtabSize(self: *LinkerDefined, elf_file: *Elf) void {
for (self.globals()) |global_index| {
const global = elf_file.symbol(global_index);
if (global.file(elf_file)) |file| if (file.index() != self.index) continue;
global.flags.output_symtab = true;
self.output_symtab_size.nlocals += 1;
}
}
pub fn writeSymtab(self: *LinkerDefined, elf_file: *Elf, ctx: anytype) void {
var ilocal = ctx.ilocal;
for (self.globals()) |global_index| {
const global = elf_file.symbol(global_index);
if (global.file(elf_file)) |file| if (file.index() != self.index) continue;
if (!global.flags.output_symtab) continue;
global.setOutputSym(elf_file, &ctx.symtab[ilocal]);
ilocal += 1;
}
}
pub fn globals(self: *LinkerDefined) []const Symbol.Index {
return self.symbols.items;
}
pub fn asFile(self: *LinkerDefined) File {
return .{ .linker_defined = self };
}
pub fn fmtSymtab(self: *LinkerDefined, elf_file: *Elf) std.fmt.Formatter(formatSymtab) {
return .{ .data = .{
.self = self,
.elf_file = elf_file,
} };
}
const FormatContext = struct {
self: *LinkerDefined,
elf_file: *Elf,
};
fn formatSymtab(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
try writer.writeAll(" globals\n");
for (ctx.self.globals()) |index| {
const global = ctx.elf_file.symbol(index);
try writer.print(" {}\n", .{global.fmt(ctx.elf_file)});
}
}
const std = @import("std");
const elf = std.elf;
const Allocator = std.mem.Allocator;
const Elf = @import("../Elf.zig");
const File = @import("file.zig").File;
const LinkerDefined = @This();
// const Object = @import("Object.zig");
const Symbol = @import("Symbol.zig");

872
src/link/Elf/Object.zig Normal file
View File

@ -0,0 +1,872 @@
archive: ?[]const u8 = null,
path: []const u8,
data: []const u8,
index: File.Index,
header: ?elf.Elf64_Ehdr = null,
shdrs: std.ArrayListUnmanaged(elf.Elf64_Shdr) = .{},
strings: StringTable(.object_strings) = .{},
symtab: []align(1) const elf.Elf64_Sym = &[0]elf.Elf64_Sym{},
strtab: []const u8 = &[0]u8{},
first_global: ?Symbol.Index = null,
symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
comdat_groups: std.ArrayListUnmanaged(Elf.ComdatGroup.Index) = .{},
fdes: std.ArrayListUnmanaged(Fde) = .{},
cies: std.ArrayListUnmanaged(Cie) = .{},
alive: bool = true,
num_dynrelocs: u32 = 0,
output_symtab_size: Elf.SymtabSize = .{},
pub fn isObject(file: std.fs.File) bool {
const reader = file.reader();
const header = reader.readStruct(elf.Elf64_Ehdr) catch return false;
defer file.seekTo(0) catch {};
if (!mem.eql(u8, header.e_ident[0..4], "\x7fELF")) return false;
if (header.e_ident[elf.EI_VERSION] != 1) return false;
if (header.e_type != elf.ET.REL) return false;
if (header.e_version != 1) return false;
return true;
}
pub fn deinit(self: *Object, allocator: Allocator) void {
allocator.free(self.data);
self.shdrs.deinit(allocator);
self.strings.deinit(allocator);
self.symbols.deinit(allocator);
self.atoms.deinit(allocator);
self.comdat_groups.deinit(allocator);
self.fdes.deinit(allocator);
self.cies.deinit(allocator);
}
pub fn parse(self: *Object, elf_file: *Elf) !void {
var stream = std.io.fixedBufferStream(self.data);
const reader = stream.reader();
self.header = try reader.readStruct(elf.Elf64_Ehdr);
if (self.header.?.e_shnum == 0) return;
const gpa = elf_file.base.allocator;
const shoff = math.cast(usize, self.header.?.e_shoff) orelse return error.Overflow;
const shdrs = @as(
[*]align(1) const elf.Elf64_Shdr,
@ptrCast(self.data.ptr + shoff),
)[0..self.header.?.e_shnum];
try self.shdrs.appendUnalignedSlice(gpa, shdrs);
try self.strings.buffer.appendSlice(gpa, try self.shdrContents(self.header.?.e_shstrndx));
const symtab_index = for (self.shdrs.items, 0..) |shdr, i| switch (shdr.sh_type) {
elf.SHT_SYMTAB => break @as(u16, @intCast(i)),
else => {},
} else null;
if (symtab_index) |index| {
const shdr = shdrs[index];
self.first_global = shdr.sh_info;
const symtab = try self.shdrContents(index);
const nsyms = @divExact(symtab.len, @sizeOf(elf.Elf64_Sym));
self.symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(symtab.ptr))[0..nsyms];
self.strtab = try self.shdrContents(@as(u16, @intCast(shdr.sh_link)));
}
try self.initAtoms(elf_file);
try self.initSymtab(elf_file);
// for (self.shdrs.items, 0..) |shdr, i| {
// const atom = elf_file.atom(self.atoms.items[i]) orelse continue;
// if (!atom.alive) continue;
// if (shdr.sh_type == elf.SHT_X86_64_UNWIND or mem.eql(u8, atom.name(elf_file), ".eh_frame"))
// try self.parseEhFrame(@as(u16, @intCast(i)), elf_file);
// }
}
fn initAtoms(self: *Object, elf_file: *Elf) !void {
const shdrs = self.shdrs.items;
try self.atoms.resize(elf_file.base.allocator, shdrs.len);
@memset(self.atoms.items, 0);
for (shdrs, 0..) |shdr, i| {
if (shdr.sh_flags & elf.SHF_EXCLUDE != 0 and
shdr.sh_flags & elf.SHF_ALLOC == 0 and
shdr.sh_type != elf.SHT_LLVM_ADDRSIG) continue;
switch (shdr.sh_type) {
elf.SHT_GROUP => {
if (shdr.sh_info >= self.symtab.len) {
// TODO convert into an error
log.debug("{}: invalid symbol index in sh_info", .{self.fmtPath()});
continue;
}
const group_info_sym = self.symtab[shdr.sh_info];
const group_signature = blk: {
if (group_info_sym.st_name == 0 and group_info_sym.st_type() == elf.STT_SECTION) {
const sym_shdr = shdrs[group_info_sym.st_shndx];
break :blk self.strings.getAssumeExists(sym_shdr.sh_name);
}
break :blk self.getString(group_info_sym.st_name);
};
const shndx = @as(u16, @intCast(i));
const group_raw_data = try self.shdrContents(shndx);
const group_nmembers = @divExact(group_raw_data.len, @sizeOf(u32));
const group_members = @as([*]align(1) const u32, @ptrCast(group_raw_data.ptr))[0..group_nmembers];
if (group_members[0] != 0x1) { // GRP_COMDAT
// TODO convert into an error
log.debug("{}: unknown SHT_GROUP format", .{self.fmtPath()});
continue;
}
const group_signature_off = try self.strings.insert(elf_file.base.allocator, group_signature);
const gop = try elf_file.getOrCreateComdatGroupOwner(group_signature_off);
const comdat_group_index = try elf_file.addComdatGroup();
const comdat_group = elf_file.comdatGroup(comdat_group_index);
comdat_group.* = .{
.owner = gop.index,
.shndx = shndx,
};
try self.comdat_groups.append(elf_file.base.allocator, comdat_group_index);
},
elf.SHT_SYMTAB_SHNDX => @panic("TODO"),
elf.SHT_NULL,
elf.SHT_REL,
elf.SHT_RELA,
elf.SHT_SYMTAB,
elf.SHT_STRTAB,
=> {},
else => {
const name = self.strings.getAssumeExists(shdr.sh_name);
const shndx = @as(u16, @intCast(i));
if (self.skipShdr(shndx, elf_file)) continue;
try self.addAtom(shdr, shndx, name, elf_file);
},
}
}
// Parse relocs sections if any.
for (shdrs, 0..) |shdr, i| switch (shdr.sh_type) {
elf.SHT_REL, elf.SHT_RELA => {
const atom_index = self.atoms.items[shdr.sh_info];
if (elf_file.atom(atom_index)) |atom| {
atom.relocs_section_index = @as(u16, @intCast(i));
}
},
else => {},
};
}
fn addAtom(self: *Object, shdr: elf.Elf64_Shdr, shndx: u16, name: [:0]const u8, elf_file: *Elf) !void {
const atom_index = try elf_file.addAtom();
const atom = elf_file.atom(atom_index).?;
atom.atom_index = atom_index;
atom.name_offset = try elf_file.strtab.insert(elf_file.base.allocator, name);
atom.file_index = self.index;
atom.input_section_index = shndx;
atom.output_section_index = self.getOutputSectionIndex(elf_file, shdr);
atom.alive = true;
self.atoms.items[shndx] = atom_index;
if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) {
const data = try self.shdrContents(shndx);
const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*;
atom.size = chdr.ch_size;
atom.alignment = math.log2_int(u64, chdr.ch_addralign);
} else {
atom.size = shdr.sh_size;
atom.alignment = math.log2_int(u64, shdr.sh_addralign);
}
}
fn getOutputSectionIndex(self: *Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) u16 {
const name = blk: {
const name = self.strings.getAssumeExists(shdr.sh_name);
// if (shdr.sh_flags & elf.SHF_MERGE != 0) break :blk name;
const sh_name_prefixes: []const [:0]const u8 = &.{
".text", ".data.rel.ro", ".data", ".rodata", ".bss.rel.ro", ".bss",
".init_array", ".fini_array", ".tbss", ".tdata", ".gcc_except_table", ".ctors",
".dtors", ".gnu.warning",
};
inline for (sh_name_prefixes) |prefix| {
if (std.mem.eql(u8, name, prefix) or std.mem.startsWith(u8, name, prefix ++ ".")) {
break :blk prefix;
}
}
break :blk name;
};
const @"type" = switch (shdr.sh_type) {
elf.SHT_NULL => unreachable,
elf.SHT_PROGBITS => blk: {
if (std.mem.eql(u8, name, ".init_array") or std.mem.startsWith(u8, name, ".init_array."))
break :blk elf.SHT_INIT_ARRAY;
if (std.mem.eql(u8, name, ".fini_array") or std.mem.startsWith(u8, name, ".fini_array."))
break :blk elf.SHT_FINI_ARRAY;
break :blk shdr.sh_type;
},
elf.SHT_X86_64_UNWIND => elf.SHT_PROGBITS,
else => shdr.sh_type,
};
const flags = blk: {
const flags = shdr.sh_flags & ~@as(u64, elf.SHF_COMPRESSED | elf.SHF_GROUP | elf.SHF_GNU_RETAIN);
break :blk switch (@"type") {
elf.SHT_INIT_ARRAY, elf.SHT_FINI_ARRAY => flags | elf.SHF_WRITE,
else => flags,
};
};
_ = flags;
const out_shndx = elf_file.sectionByName(name) orelse {
log.err("{}: output section {s} not found", .{ self.fmtPath(), name });
@panic("TODO: missing output section!");
};
return out_shndx;
}
fn skipShdr(self: *Object, index: u16, elf_file: *Elf) bool {
_ = elf_file;
const shdr = self.shdrs.items[index];
const name = self.strings.getAssumeExists(shdr.sh_name);
const ignore = blk: {
if (mem.startsWith(u8, name, ".note")) break :blk true;
if (mem.startsWith(u8, name, ".comment")) break :blk true;
if (mem.startsWith(u8, name, ".llvm_addrsig")) break :blk true;
if (mem.startsWith(u8, name, ".eh_frame")) break :blk true;
// if (elf_file.base.options.strip and shdr.sh_flags & elf.SHF_ALLOC == 0 and
// mem.startsWith(u8, name, ".debug")) break :blk true;
if (shdr.sh_flags & elf.SHF_ALLOC == 0 and mem.startsWith(u8, name, ".debug")) break :blk true;
break :blk false;
};
return ignore;
}
fn initSymtab(self: *Object, elf_file: *Elf) !void {
const gpa = elf_file.base.allocator;
const first_global = self.first_global orelse self.symtab.len;
const shdrs = self.shdrs.items;
try self.symbols.ensureTotalCapacityPrecise(gpa, self.symtab.len);
for (self.symtab[0..first_global], 0..) |sym, i| {
const index = try elf_file.addSymbol();
self.symbols.appendAssumeCapacity(index);
const sym_ptr = elf_file.symbol(index);
const name = blk: {
if (sym.st_name == 0 and sym.st_type() == elf.STT_SECTION) {
const shdr = shdrs[sym.st_shndx];
break :blk self.strings.getAssumeExists(shdr.sh_name);
}
break :blk self.getString(sym.st_name);
};
sym_ptr.value = sym.st_value;
sym_ptr.name_offset = try elf_file.strtab.insert(gpa, name);
sym_ptr.esym_index = @as(u32, @intCast(i));
sym_ptr.atom_index = if (sym.st_shndx == elf.SHN_ABS) 0 else self.atoms.items[sym.st_shndx];
sym_ptr.file_index = self.index;
sym_ptr.output_section_index = if (sym_ptr.atom(elf_file)) |atom_ptr|
atom_ptr.output_section_index
else
0;
}
for (self.symtab[first_global..]) |sym| {
const name = self.getString(sym.st_name);
const off = try elf_file.strtab.insert(gpa, name);
const gop = try elf_file.getOrPutGlobal(off);
self.symbols.addOneAssumeCapacity().* = gop.index;
}
}
fn parseEhFrame(self: *Object, shndx: u16, elf_file: *Elf) !void {
const relocs_shndx = for (self.shdrs.items, 0..) |shdr, i| switch (shdr.sh_type) {
elf.SHT_RELA => if (shdr.sh_info == shndx) break @as(u16, @intCast(i)),
else => {},
} else {
log.debug("{s}: missing reloc section for unwind info section", .{self.fmtPath()});
return;
};
const gpa = elf_file.base.allocator;
const raw = try self.shdrContents(shndx);
const relocs = try self.getRelocs(relocs_shndx);
const fdes_start = self.fdes.items.len;
const cies_start = self.cies.items.len;
var it = eh_frame.Iterator{ .data = raw };
while (try it.next()) |rec| {
const rel_range = filterRelocs(relocs, rec.offset, rec.size + 4);
switch (rec.tag) {
.cie => try self.cies.append(gpa, .{
.offset = rec.offset,
.size = rec.size,
.rel_index = @as(u32, @intCast(rel_range.start)),
.rel_num = @as(u32, @intCast(rel_range.len)),
.rel_section_index = relocs_shndx,
.input_section_index = shndx,
.file_index = self.index,
}),
.fde => try self.fdes.append(gpa, .{
.offset = rec.offset,
.size = rec.size,
.cie_index = undefined,
.rel_index = @as(u32, @intCast(rel_range.start)),
.rel_num = @as(u32, @intCast(rel_range.len)),
.rel_section_index = relocs_shndx,
.input_section_index = shndx,
.file_index = self.index,
}),
}
}
// Tie each FDE to its CIE
for (self.fdes.items[fdes_start..]) |*fde| {
const cie_ptr = fde.offset + 4 - fde.ciePointer(elf_file);
const cie_index = for (self.cies.items[cies_start..], cies_start..) |cie, cie_index| {
if (cie.offset == cie_ptr) break @as(u32, @intCast(cie_index));
} else {
// TODO convert into an error
log.debug("{s}: no matching CIE found for FDE at offset {x}", .{
self.fmtPath(),
fde.offset,
});
continue;
};
fde.cie_index = cie_index;
}
// Tie each FDE record to its matching atom
const SortFdes = struct {
pub fn lessThan(ctx: *Elf, lhs: Fde, rhs: Fde) bool {
const lhs_atom = lhs.atom(ctx);
const rhs_atom = rhs.atom(ctx);
return lhs_atom.priority(ctx) < rhs_atom.priority(ctx);
}
};
mem.sort(Fde, self.fdes.items[fdes_start..], elf_file, SortFdes.lessThan);
// Create a back-link from atom to FDEs
var i: u32 = @as(u32, @intCast(fdes_start));
while (i < self.fdes.items.len) {
const fde = self.fdes.items[i];
const atom = fde.atom(elf_file);
atom.fde_start = i;
i += 1;
while (i < self.fdes.items.len) : (i += 1) {
const next_fde = self.fdes.items[i];
if (atom.atom_index != next_fde.atom(elf_file).atom_index) break;
}
atom.fde_end = i;
}
}
fn filterRelocs(
relocs: []align(1) const elf.Elf64_Rela,
start: u64,
len: u64,
) struct { start: u64, len: u64 } {
const Predicate = struct {
value: u64,
pub fn predicate(self: @This(), rel: elf.Elf64_Rela) bool {
return rel.r_offset < self.value;
}
};
const LPredicate = struct {
value: u64,
pub fn predicate(self: @This(), rel: elf.Elf64_Rela) bool {
return rel.r_offset >= self.value;
}
};
const f_start = Elf.bsearch(elf.Elf64_Rela, relocs, Predicate{ .value = start });
const f_len = Elf.lsearch(elf.Elf64_Rela, relocs[f_start..], LPredicate{ .value = start + len });
return .{ .start = f_start, .len = f_len };
}
pub fn scanRelocs(self: *Object, elf_file: *Elf, undefs: anytype) !void {
for (self.atoms.items) |atom_index| {
const atom = elf_file.atom(atom_index) orelse continue;
if (!atom.alive) continue;
const shdr = atom.inputShdr(elf_file);
if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue;
if (shdr.sh_type == elf.SHT_NOBITS) continue;
try atom.scanRelocs(elf_file, undefs);
}
for (self.cies.items) |cie| {
for (try cie.relocs(elf_file)) |rel| {
const sym = elf_file.symbol(self.symbols.items[rel.r_sym()]);
if (sym.flags.import) {
if (sym.type(elf_file) != elf.STT_FUNC)
// TODO convert into an error
log.debug("{s}: {s}: CIE referencing external data reference", .{
self.fmtPath(),
sym.name(elf_file),
});
sym.flags.needs_plt = true;
}
}
}
}
pub fn resolveSymbols(self: *Object, elf_file: *Elf) void {
const first_global = self.first_global orelse return;
for (self.globals(), 0..) |index, i| {
const esym_index = @as(Symbol.Index, @intCast(first_global + i));
const esym = self.symtab[esym_index];
if (esym.st_shndx == elf.SHN_UNDEF) continue;
if (esym.st_shndx != elf.SHN_ABS and esym.st_shndx != elf.SHN_COMMON) {
const atom_index = self.atoms.items[esym.st_shndx];
const atom = elf_file.atom(atom_index) orelse continue;
if (!atom.alive) continue;
}
const global = elf_file.symbol(index);
if (self.asFile().symbolRank(esym, !self.alive) < global.symbolRank(elf_file)) {
const atom_index = switch (esym.st_shndx) {
elf.SHN_ABS, elf.SHN_COMMON => 0,
else => self.atoms.items[esym.st_shndx],
};
const output_section_index = if (elf_file.atom(atom_index)) |atom|
atom.output_section_index
else
0;
global.value = esym.st_value;
global.atom_index = atom_index;
global.esym_index = esym_index;
global.file_index = self.index;
global.output_section_index = output_section_index;
global.version_index = elf_file.default_sym_version;
if (esym.st_bind() == elf.STB_WEAK) global.flags.weak = true;
}
}
}
pub fn claimUnresolved(self: *Object, elf_file: *Elf) void {
const first_global = self.first_global orelse return;
for (self.globals(), 0..) |index, i| {
const esym_index = @as(u32, @intCast(first_global + i));
const esym = self.symtab[esym_index];
if (esym.st_shndx != elf.SHN_UNDEF) continue;
const global = elf_file.symbol(index);
if (global.file(elf_file)) |_| {
if (global.elfSym(elf_file).st_shndx != elf.SHN_UNDEF) continue;
}
const is_import = blk: {
if (!elf_file.isDynLib()) break :blk false;
const vis = @as(elf.STV, @enumFromInt(esym.st_other));
if (vis == .HIDDEN) break :blk false;
break :blk true;
};
global.value = 0;
global.atom_index = 0;
global.esym_index = esym_index;
global.file_index = self.index;
global.version_index = if (is_import) elf.VER_NDX_LOCAL else elf_file.default_sym_version;
global.flags.import = is_import;
}
}
pub fn resetGlobals(self: *Object, elf_file: *Elf) void {
for (self.globals()) |index| {
const global = elf_file.symbol(index);
const name = global.name;
global.* = .{};
global.name = name;
}
}
pub fn markLive(self: *Object, elf_file: *Elf) void {
const first_global = self.first_global orelse return;
for (self.globals(), 0..) |index, i| {
const sym_idx = first_global + i;
const sym = self.symtab[sym_idx];
if (sym.st_bind() == elf.STB_WEAK) continue;
const global = elf_file.symbol(index);
const file = global.getFile(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()) {
file.setAlive();
file.markLive(elf_file);
}
}
}
pub fn checkDuplicates(self: *Object, elf_file: *Elf) void {
const first_global = self.first_global orelse return;
for (self.globals(), 0..) |index, i| {
const sym_idx = @as(u32, @intCast(first_global + i));
const this_sym = self.symtab[sym_idx];
const global = elf_file.symbol(index);
const global_file = global.getFile(elf_file) orelse continue;
if (self.index == global_file.getIndex() or
this_sym.st_shndx == elf.SHN_UNDEF or
this_sym.st_bind() == elf.STB_WEAK or
this_sym.st_shndx == elf.SHN_COMMON) continue;
if (this_sym.st_shndx != elf.SHN_ABS) {
const atom_index = self.atoms.items[this_sym.st_shndx];
const atom = elf_file.atom(atom_index) orelse continue;
if (!atom.alive) continue;
}
elf_file.base.fatal("multiple definition: {}: {}: {s}", .{
self.fmtPath(),
global_file.fmtPath(),
global.getName(elf_file),
});
}
}
/// We will create dummy shdrs per each resolved common symbols to make it
/// play nicely with the rest of the system.
pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void {
const first_global = self.first_global orelse return;
for (self.globals(), 0..) |index, i| {
const sym_idx = @as(u32, @intCast(first_global + i));
const this_sym = self.symtab[sym_idx];
if (this_sym.st_shndx != elf.SHN_COMMON) continue;
const global = elf_file.symbol(index);
const global_file = global.getFile(elf_file).?;
if (global_file.getIndex() != self.index) {
if (elf_file.options.warn_common) {
elf_file.base.warn("{}: multiple common symbols: {s}", .{
self.fmtPath(),
global.getName(elf_file),
});
}
continue;
}
const gpa = elf_file.base.allocator;
const atom_index = try elf_file.addAtom();
try self.atoms.append(gpa, atom_index);
const is_tls = global.getType(elf_file) == elf.STT_TLS;
const name = if (is_tls) ".tls_common" else ".common";
const atom = elf_file.atom(atom_index).?;
atom.atom_index = atom_index;
atom.name = try elf_file.strtab.insert(gpa, name);
atom.file = self.index;
atom.size = this_sym.st_size;
const alignment = this_sym.st_value;
atom.alignment = math.log2_int(u64, alignment);
var sh_flags: u32 = elf.SHF_ALLOC | elf.SHF_WRITE;
if (is_tls) sh_flags |= elf.SHF_TLS;
const shndx = @as(u16, @intCast(self.shdrs.items.len));
const shdr = try self.shdrs.addOne(gpa);
shdr.* = .{
.sh_name = try self.strings.insert(gpa, name),
.sh_type = elf.SHT_NOBITS,
.sh_flags = sh_flags,
.sh_addr = 0,
.sh_offset = 0,
.sh_size = this_sym.st_size,
.sh_link = 0,
.sh_info = 0,
.sh_addralign = alignment,
.sh_entsize = 0,
};
atom.shndx = shndx;
global.value = 0;
global.atom = atom_index;
global.flags.weak = false;
}
}
pub fn updateSymtabSize(self: *Object, elf_file: *Elf) void {
for (self.locals()) |local_index| {
const local = elf_file.symbol(local_index);
if (local.atom(elf_file)) |atom| if (!atom.alive) continue;
const esym = local.elfSym(elf_file);
switch (esym.st_type()) {
elf.STT_SECTION, elf.STT_NOTYPE => continue,
else => {},
}
local.flags.output_symtab = true;
self.output_symtab_size.nlocals += 1;
}
for (self.globals()) |global_index| {
const global = elf_file.symbol(global_index);
if (global.file(elf_file)) |file| if (file.index() != self.index) continue;
if (global.atom(elf_file)) |atom| if (!atom.alive) continue;
global.flags.output_symtab = true;
if (global.isLocal()) {
self.output_symtab_size.nlocals += 1;
} else {
self.output_symtab_size.nglobals += 1;
}
}
}
pub fn writeSymtab(self: *Object, elf_file: *Elf, ctx: anytype) void {
var ilocal = ctx.ilocal;
for (self.locals()) |local_index| {
const local = elf_file.symbol(local_index);
if (!local.flags.output_symtab) continue;
local.setOutputSym(elf_file, &ctx.symtab[ilocal]);
ilocal += 1;
}
var iglobal = ctx.iglobal;
for (self.globals()) |global_index| {
const global = elf_file.symbol(global_index);
if (global.file(elf_file)) |file| if (file.index() != self.index) continue;
if (!global.flags.output_symtab) continue;
if (global.isLocal()) {
global.setOutputSym(elf_file, &ctx.symtab[ilocal]);
ilocal += 1;
} else {
global.setOutputSym(elf_file, &ctx.symtab[iglobal]);
iglobal += 1;
}
}
}
pub fn locals(self: *Object) []const Symbol.Index {
const end = self.first_global orelse self.symbols.items.len;
return self.symbols.items[0..end];
}
pub fn globals(self: *Object) []const Symbol.Index {
const start = self.first_global orelse self.symbols.items.len;
return self.symbols.items[start..];
}
pub fn shdrContents(self: *Object, index: u32) error{Overflow}![]const u8 {
assert(index < self.shdrs.items.len);
const shdr = self.shdrs.items[index];
const offset = math.cast(usize, shdr.sh_offset) orelse return error.Overflow;
const size = math.cast(usize, shdr.sh_size) orelse return error.Overflow;
return self.data[offset..][0..size];
}
fn getString(self: *Object, off: u32) [:0]const u8 {
assert(off < self.strtab.len);
return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0);
}
pub fn comdatGroupMembers(self: *Object, index: u16) error{Overflow}![]align(1) const u32 {
const raw = try self.shdrContents(index);
const nmembers = @divExact(raw.len, @sizeOf(u32));
const members = @as([*]align(1) const u32, @ptrCast(raw.ptr))[1..nmembers];
return members;
}
pub fn asFile(self: *Object) File {
return .{ .object = self };
}
pub fn getRelocs(self: *Object, shndx: u32) error{Overflow}![]align(1) const elf.Elf64_Rela {
const raw = try self.shdrContents(shndx);
const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela));
return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num];
}
pub fn format(
self: *Object,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = self;
_ = unused_fmt_string;
_ = options;
_ = writer;
@compileError("do not format objects directly");
}
pub fn fmtSymtab(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatSymtab) {
return .{ .data = .{
.object = self,
.elf_file = elf_file,
} };
}
const FormatContext = struct {
object: *Object,
elf_file: *Elf,
};
fn formatSymtab(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
const object = ctx.object;
try writer.writeAll(" locals\n");
for (object.locals()) |index| {
const local = ctx.elf_file.symbol(index);
try writer.print(" {}\n", .{local.fmt(ctx.elf_file)});
}
try writer.writeAll(" globals\n");
for (object.globals()) |index| {
const global = ctx.elf_file.symbol(index);
try writer.print(" {}\n", .{global.fmt(ctx.elf_file)});
}
}
pub fn fmtAtoms(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatAtoms) {
return .{ .data = .{
.object = self,
.elf_file = elf_file,
} };
}
fn formatAtoms(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
const object = ctx.object;
try writer.writeAll(" atoms\n");
for (object.atoms.items) |atom_index| {
const atom = ctx.elf_file.atom(atom_index) orelse continue;
try writer.print(" {}\n", .{atom.fmt(ctx.elf_file)});
}
}
pub fn fmtCies(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatCies) {
return .{ .data = .{
.object = self,
.elf_file = elf_file,
} };
}
fn formatCies(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
const object = ctx.object;
try writer.writeAll(" cies\n");
for (object.cies.items, 0..) |cie, i| {
try writer.print(" cie({d}) : {}\n", .{ i, cie.fmt(ctx.elf_file) });
}
}
pub fn fmtFdes(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatFdes) {
return .{ .data = .{
.object = self,
.elf_file = elf_file,
} };
}
fn formatFdes(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
const object = ctx.object;
try writer.writeAll(" fdes\n");
for (object.fdes.items, 0..) |fde, i| {
try writer.print(" fde({d}) : {}\n", .{ i, fde.fmt(ctx.elf_file) });
}
}
pub fn fmtComdatGroups(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatComdatGroups) {
return .{ .data = .{
.object = self,
.elf_file = elf_file,
} };
}
fn formatComdatGroups(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
const object = ctx.object;
const elf_file = ctx.elf_file;
try writer.writeAll(" comdat groups\n");
for (object.comdat_groups.items) |cg_index| {
const cg = elf_file.comdatGroup(cg_index);
const cg_owner = elf_file.comdatGroupOwner(cg.owner);
if (cg_owner.file != object.index) continue;
const cg_members = object.comdatGroupMembers(cg.shndx) catch continue;
for (cg_members) |shndx| {
const atom_index = object.atoms.items[shndx];
const atom = elf_file.atom(atom_index) orelse continue;
try writer.print(" atom({d}) : {s}\n", .{ atom_index, atom.name(elf_file) });
}
}
}
pub fn fmtPath(self: *Object) std.fmt.Formatter(formatPath) {
return .{ .data = self };
}
fn formatPath(
object: *Object,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
if (object.archive) |path| {
try writer.writeAll(path);
try writer.writeByte('(');
try writer.writeAll(object.path);
try writer.writeByte(')');
} else try writer.writeAll(object.path);
}
const Object = @This();
const std = @import("std");
const assert = std.debug.assert;
const eh_frame = @import("eh_frame.zig");
const elf = std.elf;
const fs = std.fs;
const log = std.log.scoped(.link);
const math = std.math;
const mem = std.mem;
const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
const Cie = eh_frame.Cie;
const Elf = @import("../Elf.zig");
const Fde = eh_frame.Fde;
const File = @import("file.zig").File;
const StringTable = @import("../strtab.zig").StringTable;
const Symbol = @import("Symbol.zig");

362
src/link/Elf/Symbol.zig Normal file
View File

@ -0,0 +1,362 @@
//! Represents a defined symbol.
index: Index = 0,
/// Allocated address value of this symbol.
value: u64 = 0,
/// Offset into the linker's string table.
name_offset: u32 = 0,
/// Index of file where this symbol is defined.
file_index: File.Index = 0,
/// Index of atom containing this symbol.
/// Index of 0 means there is no associated atom with this symbol.
/// Use `atom` to get the pointer to the atom.
atom_index: Atom.Index = 0,
/// Assigned output section index for this atom.
output_section_index: u16 = 0,
/// Index of the source symbol this symbol references.
/// Use `elfSym` to pull the source symbol from the relevant file.
esym_index: Index = 0,
/// Index of the source version symbol this symbol references if any.
/// If the symbol is unversioned it will have either VER_NDX_LOCAL or VER_NDX_GLOBAL.
version_index: elf.Elf64_Versym = elf.VER_NDX_LOCAL,
/// Misc flags for the symbol packaged as packed struct for compression.
flags: Flags = .{},
extra_index: u32 = 0,
pub fn isAbs(symbol: Symbol, elf_file: *Elf) bool {
const file_ptr = symbol.file(elf_file).?;
// if (file_ptr == .shared) return symbol.sourceSymbol(elf_file).st_shndx == elf.SHN_ABS;
return !symbol.flags.import and symbol.atom(elf_file) == null and symbol.output_section_index == 0 and
file_ptr != .linker_defined;
}
pub fn isLocal(symbol: Symbol) bool {
return !(symbol.flags.import or symbol.flags.@"export");
}
pub fn isIFunc(symbol: Symbol, elf_file: *Elf) bool {
return symbol.type(elf_file) == elf.STT_GNU_IFUNC;
}
pub fn @"type"(symbol: Symbol, elf_file: *Elf) u4 {
const s_sym = symbol.elfSym(elf_file);
// const file_ptr = symbol.file(elf_file).?;
// if (s_sym.st_type() == elf.STT_GNU_IFUNC and file_ptr == .shared) return elf.STT_FUNC;
return s_sym.st_type();
}
pub fn name(symbol: Symbol, elf_file: *Elf) [:0]const u8 {
return elf_file.strtab.getAssumeExists(symbol.name_offset);
}
pub fn atom(symbol: Symbol, elf_file: *Elf) ?*Atom {
return elf_file.atom(symbol.atom_index);
}
pub fn file(symbol: Symbol, elf_file: *Elf) ?File {
return elf_file.file(symbol.file_index);
}
pub fn elfSym(symbol: Symbol, elf_file: *Elf) elf.Elf64_Sym {
const file_ptr = symbol.file(elf_file).?;
switch (file_ptr) {
.zig_module => |x| return x.elfSym(symbol.esym_index).*,
.linker_defined => |x| return x.symtab.items[symbol.esym_index],
.object => |x| return x.symtab[symbol.esym_index],
}
}
pub fn symbolRank(symbol: Symbol, elf_file: *Elf) u32 {
const file_ptr = symbol.file(elf_file) orelse return std.math.maxInt(u32);
const sym = symbol.elfSym(elf_file);
const in_archive = switch (file_ptr) {
.object => |x| !x.alive,
else => false,
};
return file_ptr.symbolRank(sym, in_archive);
}
pub fn address(symbol: Symbol, opts: struct {
plt: bool = true,
}, elf_file: *Elf) u64 {
_ = elf_file;
_ = opts;
// if (symbol.flags.copy_rel) {
// return elf_file.sectionAddress(elf_file.copy_rel_sect_index.?) + symbol.value;
// }
// if (symbol.flags.plt and opts.plt) {
// const extra = symbol.getExtra(elf_file).?;
// if (!symbol.flags.is_canonical and symbol.flags.got) {
// // We have a non-lazy bound function pointer, use that!
// return elf_file.getPltGotEntryAddress(extra.plt_got);
// }
// // Lazy-bound function it is!
// return elf_file.getPltEntryAddress(extra.plt);
// }
return symbol.value;
}
pub fn gotAddress(symbol: Symbol, elf_file: *Elf) u64 {
if (!symbol.flags.has_got) return 0;
const extras = symbol.extra(elf_file).?;
const entry = elf_file.got.entries.items[extras.got];
return entry.address(elf_file);
}
const GetOrCreateGotEntryResult = struct {
found_existing: bool,
index: GotSection.Index,
};
pub fn getOrCreateGotEntry(symbol: *Symbol, 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);
symbol.flags.has_got = true;
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 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 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 alignment(symbol: Symbol, elf_file: *Elf) !u64 {
// const file = symbol.getFile(elf_file) orelse return 0;
// const shared = file.shared;
// const s_sym = symbol.getSourceSymbol(elf_file);
// const shdr = shared.getShdrs()[s_sym.st_shndx];
// const alignment = @max(1, shdr.sh_addralign);
// return if (s_sym.st_value == 0)
// alignment
// else
// @min(alignment, try std.math.powi(u64, 2, @ctz(s_sym.st_value)));
// }
pub fn addExtra(symbol: *Symbol, extras: Extra, elf_file: *Elf) !void {
symbol.extra_index = try elf_file.addSymbolExtra(extras);
}
pub fn extra(symbol: Symbol, elf_file: *Elf) ?Extra {
return elf_file.symbolExtra(symbol.extra_index);
}
pub fn setExtra(symbol: Symbol, extras: Extra, elf_file: *Elf) void {
elf_file.setSymbolExtra(symbol.extra_index, extras);
}
pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void {
const file_ptr = symbol.file(elf_file) orelse {
out.* = Elf.null_sym;
return;
};
const esym = symbol.elfSym(elf_file);
const st_type = symbol.type(elf_file);
const st_bind: u8 = blk: {
if (symbol.isLocal()) break :blk 0;
if (symbol.flags.weak) break :blk elf.STB_WEAK;
// if (file_ptr == .shared) break :blk elf.STB_GLOBAL;
break :blk esym.st_bind();
};
const st_shndx = blk: {
// if (symbol.flags.copy_rel) break :blk elf_file.copy_rel_sect_index.?;
// if (file_ptr == .shared or s_sym.st_shndx == elf.SHN_UNDEF) break :blk elf.SHN_UNDEF;
if (symbol.atom(elf_file) == null and file_ptr != .linker_defined)
break :blk elf.SHN_ABS;
break :blk symbol.output_section_index;
};
const st_value = blk: {
// if (symbol.flags.copy_rel) break :blk symbol.address(.{}, elf_file);
// if (file_ptr == .shared or s_sym.st_shndx == elf.SHN_UNDEF) {
// if (symbol.flags.is_canonical) break :blk symbol.address(.{}, elf_file);
// break :blk 0;
// }
// if (st_shndx == elf.SHN_ABS) break :blk symbol.value;
// const shdr = &elf_file.sections.items(.shdr)[st_shndx];
// if (Elf.shdrIsTls(shdr)) break :blk symbol.value - elf_file.getTlsAddress();
break :blk symbol.value;
};
out.* = .{
.st_name = symbol.name_offset,
.st_info = (st_bind << 4) | st_type,
.st_other = esym.st_other,
.st_shndx = st_shndx,
.st_value = st_value,
.st_size = esym.st_size,
};
}
pub fn format(
symbol: Symbol,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = symbol;
_ = unused_fmt_string;
_ = options;
_ = writer;
@compileError("do not format symbols directly");
}
const FormatContext = struct {
symbol: Symbol,
elf_file: *Elf,
};
pub fn fmtName(symbol: Symbol, elf_file: *Elf) std.fmt.Formatter(formatName) {
return .{ .data = .{
.symbol = symbol,
.elf_file = elf_file,
} };
}
fn formatName(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
const elf_file = ctx.elf_file;
const symbol = ctx.symbol;
try writer.writeAll(symbol.name(elf_file));
switch (symbol.version_index & elf.VERSYM_VERSION) {
elf.VER_NDX_LOCAL, elf.VER_NDX_GLOBAL => {},
else => {
unreachable;
// const shared = symbol.getFile(elf_file).?.shared;
// try writer.print("@{s}", .{shared.getVersionString(symbol.version_index)});
},
}
}
pub fn fmt(symbol: Symbol, elf_file: *Elf) std.fmt.Formatter(format2) {
return .{ .data = .{
.symbol = symbol,
.elf_file = elf_file,
} };
}
fn format2(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
const symbol = ctx.symbol;
try writer.print("%{d} : {s} : @{x}", .{ symbol.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) {
try writer.writeAll(" : undef");
} else {
try writer.writeAll(" : absolute");
}
} else if (symbol.output_section_index != 0) {
try writer.print(" : sect({d})", .{symbol.output_section_index});
}
if (symbol.atom(ctx.elf_file)) |atom_ptr| {
try writer.print(" : atom({d})", .{atom_ptr.atom_index});
}
var buf: [2]u8 = .{'_'} ** 2;
if (symbol.flags.@"export") buf[0] = 'E';
if (symbol.flags.import) buf[1] = 'I';
try writer.print(" : {s}", .{&buf});
if (symbol.flags.weak) try writer.writeAll(" : weak");
switch (file_ptr) {
inline else => |x| try writer.print(" : {s}({d})", .{ @tagName(file_ptr), x.index }),
}
} else try writer.writeAll(" : unresolved");
}
pub const Flags = packed struct {
/// Whether the symbol is imported at runtime.
import: bool = false,
/// Whether the symbol is exported at runtime.
@"export": bool = false,
/// Whether this symbol is weak.
weak: bool = false,
/// Whether the symbol makes into the output symtab or not.
output_symtab: bool = false,
/// Whether the symbol contains GOT indirection.
needs_got: bool = false,
has_got: bool = false,
/// Whether the symbol contains PLT indirection.
needs_plt: bool = false,
plt: bool = false,
/// Whether the PLT entry is canonical.
is_canonical: bool = false,
/// Whether the symbol contains COPYREL directive.
copy_rel: bool = false,
has_copy_rel: bool = false,
has_dynamic: bool = false,
/// Whether the symbol contains TLSGD indirection.
tlsgd: bool = false,
/// Whether the symbol contains GOTTP indirection.
gottp: bool = false,
/// Whether the symbol contains TLSDESC indirection.
tlsdesc: bool = false,
};
pub const Extra = struct {
got: u32 = 0,
plt: u32 = 0,
plt_got: u32 = 0,
dynamic: u32 = 0,
copy_rel: u32 = 0,
tlsgd: u32 = 0,
gottp: u32 = 0,
tlsdesc: u32 = 0,
};
pub const Index = u32;
const assert = std.debug.assert;
const elf = std.elf;
const std = @import("std");
const synthetic_sections = @import("synthetic_sections.zig");
const Atom = @import("Atom.zig");
const Elf = @import("../Elf.zig");
const File = @import("file.zig").File;
const GotSection = synthetic_sections.GotSection;
const LinkerDefined = @import("LinkerDefined.zig");
// const Object = @import("Object.zig");
// const SharedObject = @import("SharedObject.zig");
const Symbol = @This();
const ZigModule = @import("ZigModule.zig");

295
src/link/Elf/ZigModule.zig Normal file
View File

@ -0,0 +1,295 @@
//! ZigModule encapsulates the state of the incrementally compiled Zig module.
//! It stores the associated input local and global symbols, allocated atoms,
//! and any relocations that may have been emitted.
//! Think about this as fake in-memory Object file for the Zig module.
/// Path is owned by Module and lives as long as *Module.
path: []const u8,
index: File.Index,
local_esyms: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{},
global_esyms: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{},
local_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
global_symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
globals_lookup: std.AutoHashMapUnmanaged(u32, Symbol.Index) = .{},
atoms: std.AutoArrayHashMapUnmanaged(Atom.Index, void) = .{},
relocs: std.ArrayListUnmanaged(std.ArrayListUnmanaged(elf.Elf64_Rela)) = .{},
output_symtab_size: Elf.SymtabSize = .{},
pub fn deinit(self: *ZigModule, allocator: Allocator) void {
self.local_esyms.deinit(allocator);
self.global_esyms.deinit(allocator);
self.local_symbols.deinit(allocator);
self.global_symbols.deinit(allocator);
self.globals_lookup.deinit(allocator);
self.atoms.deinit(allocator);
for (self.relocs.items) |*list| {
list.deinit(allocator);
}
self.relocs.deinit(allocator);
}
pub fn addLocalEsym(self: *ZigModule, allocator: Allocator) !Symbol.Index {
try self.local_esyms.ensureUnusedCapacity(allocator, 1);
const index = @as(Symbol.Index, @intCast(self.local_esyms.items.len));
const esym = self.local_esyms.addOneAssumeCapacity();
esym.* = Elf.null_sym;
esym.st_info = elf.STB_LOCAL << 4;
return index;
}
pub fn addGlobalEsym(self: *ZigModule, allocator: Allocator) !Symbol.Index {
try self.global_esyms.ensureUnusedCapacity(allocator, 1);
const index = @as(Symbol.Index, @intCast(self.global_esyms.items.len));
const esym = self.global_esyms.addOneAssumeCapacity();
esym.* = Elf.null_sym;
esym.st_info = elf.STB_GLOBAL << 4;
return index | 0x10000000;
}
pub fn addAtom(self: *ZigModule, output_section_index: u16, elf_file: *Elf) !Symbol.Index {
const gpa = elf_file.base.allocator;
const atom_index = try elf_file.addAtom();
try self.atoms.putNoClobber(gpa, atom_index, {});
const atom_ptr = elf_file.atom(atom_index).?;
atom_ptr.file_index = self.index;
atom_ptr.output_section_index = output_section_index;
const symbol_index = try elf_file.addSymbol();
try self.local_symbols.append(gpa, symbol_index);
const symbol_ptr = elf_file.symbol(symbol_index);
symbol_ptr.file_index = self.index;
symbol_ptr.atom_index = atom_index;
symbol_ptr.output_section_index = output_section_index;
const esym_index = try self.addLocalEsym(gpa);
const esym = &self.local_esyms.items[esym_index];
esym.st_shndx = atom_index;
symbol_ptr.esym_index = esym_index;
const relocs_index = @as(Atom.Index, @intCast(self.relocs.items.len));
const relocs = try self.relocs.addOne(gpa);
relocs.* = .{};
atom_ptr.relocs_section_index = relocs_index;
return symbol_index;
}
pub fn resolveSymbols(self: *ZigModule, elf_file: *Elf) void {
for (self.globals(), 0..) |index, i| {
const esym_index = @as(Symbol.Index, @intCast(i)) | 0x10000000;
const esym = self.global_esyms.items[i];
if (esym.st_shndx == elf.SHN_UNDEF) continue;
if (esym.st_shndx != elf.SHN_ABS and esym.st_shndx != elf.SHN_COMMON) {
const atom_index = esym.st_shndx;
const atom = elf_file.atom(atom_index) orelse continue;
if (!atom.alive) continue;
}
const global = elf_file.symbol(index);
if (self.asFile().symbolRank(esym, false) < global.symbolRank(elf_file)) {
const atom_index = switch (esym.st_shndx) {
elf.SHN_ABS, elf.SHN_COMMON => 0,
else => esym.st_shndx,
};
const output_section_index = if (elf_file.atom(atom_index)) |atom|
atom.output_section_index
else
0;
global.value = esym.st_value;
global.atom_index = atom_index;
global.esym_index = esym_index;
global.file_index = self.index;
global.output_section_index = output_section_index;
global.version_index = elf_file.default_sym_version;
if (esym.st_bind() == elf.STB_WEAK) global.flags.weak = true;
}
}
}
pub fn claimUnresolved(self: *ZigModule, elf_file: *Elf) void {
for (self.globals(), 0..) |index, i| {
const esym_index = @as(Symbol.Index, @intCast(i)) | 0x10000000;
const esym = self.global_esyms.items[i];
if (esym.st_shndx != elf.SHN_UNDEF) continue;
const global = elf_file.symbol(index);
if (global.file(elf_file)) |_| {
if (global.elfSym(elf_file).st_shndx != elf.SHN_UNDEF) continue;
}
const is_import = blk: {
if (!elf_file.isDynLib()) break :blk false;
const vis = @as(elf.STV, @enumFromInt(esym.st_other));
if (vis == .HIDDEN) break :blk false;
break :blk true;
};
global.value = 0;
global.atom_index = 0;
global.esym_index = esym_index;
global.file_index = self.index;
global.version_index = if (is_import) elf.VER_NDX_LOCAL else elf_file.default_sym_version;
global.flags.import = is_import;
}
}
pub fn scanRelocs(self: *ZigModule, elf_file: *Elf, undefs: anytype) !void {
for (self.atoms.keys()) |atom_index| {
const atom = elf_file.atom(atom_index) orelse continue;
if (!atom.alive) continue;
try atom.scanRelocs(elf_file, undefs);
}
}
pub fn updateSymtabSize(self: *ZigModule, elf_file: *Elf) void {
for (self.locals()) |local_index| {
const local = elf_file.symbol(local_index);
const esym = local.elfSym(elf_file);
switch (esym.st_type()) {
elf.STT_SECTION, elf.STT_NOTYPE => {
local.flags.output_symtab = false;
continue;
},
else => {},
}
local.flags.output_symtab = true;
self.output_symtab_size.nlocals += 1;
}
for (self.globals()) |global_index| {
const global = elf_file.symbol(global_index);
if (global.file(elf_file)) |file| if (file.index() != self.index) {
global.flags.output_symtab = false;
continue;
};
global.flags.output_symtab = true;
if (global.isLocal()) {
self.output_symtab_size.nlocals += 1;
} else {
self.output_symtab_size.nglobals += 1;
}
}
}
pub fn writeSymtab(self: *ZigModule, elf_file: *Elf, ctx: anytype) void {
var ilocal = ctx.ilocal;
for (self.locals()) |local_index| {
const local = elf_file.symbol(local_index);
if (!local.flags.output_symtab) continue;
local.setOutputSym(elf_file, &ctx.symtab[ilocal]);
ilocal += 1;
}
var iglobal = ctx.iglobal;
for (self.globals()) |global_index| {
const global = elf_file.symbol(global_index);
if (global.file(elf_file)) |file| if (file.index() != self.index) continue;
if (!global.flags.output_symtab) continue;
if (global.isLocal()) {
global.setOutputSym(elf_file, &ctx.symtab[ilocal]);
ilocal += 1;
} else {
global.setOutputSym(elf_file, &ctx.symtab[iglobal]);
iglobal += 1;
}
}
}
pub fn symbol(self: *ZigModule, index: Symbol.Index) Symbol.Index {
const is_global = index & 0x10000000 != 0;
const actual_index = index & 0x0fffffff;
if (is_global) return self.global_symbols.items[actual_index];
return self.local_symbols.items[actual_index];
}
pub fn elfSym(self: *ZigModule, index: Symbol.Index) *elf.Elf64_Sym {
const is_global = index & 0x10000000 != 0;
const actual_index = index & 0x0fffffff;
if (is_global) return &self.global_esyms.items[actual_index];
return &self.local_esyms.items[actual_index];
}
pub fn locals(self: *ZigModule) []const Symbol.Index {
return self.local_symbols.items;
}
pub fn globals(self: *ZigModule) []const Symbol.Index {
return self.global_symbols.items;
}
pub fn asFile(self: *ZigModule) File {
return .{ .zig_module = self };
}
pub fn fmtSymtab(self: *ZigModule, elf_file: *Elf) std.fmt.Formatter(formatSymtab) {
return .{ .data = .{
.self = self,
.elf_file = elf_file,
} };
}
const FormatContext = struct {
self: *ZigModule,
elf_file: *Elf,
};
fn formatSymtab(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
try writer.writeAll(" locals\n");
for (ctx.self.locals()) |index| {
const local = ctx.elf_file.symbol(index);
try writer.print(" {}\n", .{local.fmt(ctx.elf_file)});
}
try writer.writeAll(" globals\n");
for (ctx.self.globals()) |index| {
const global = ctx.elf_file.symbol(index);
try writer.print(" {}\n", .{global.fmt(ctx.elf_file)});
}
}
pub fn fmtAtoms(self: *ZigModule, elf_file: *Elf) std.fmt.Formatter(formatAtoms) {
return .{ .data = .{
.self = self,
.elf_file = elf_file,
} };
}
fn formatAtoms(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
try writer.writeAll(" atoms\n");
for (ctx.self.atoms.keys()) |atom_index| {
const atom = ctx.elf_file.atom(atom_index) orelse continue;
try writer.print(" {}\n", .{atom.fmt(ctx.elf_file)});
}
}
const assert = std.debug.assert;
const std = @import("std");
const elf = std.elf;
const Allocator = std.mem.Allocator;
const Atom = @import("Atom.zig");
const Elf = @import("../Elf.zig");
const File = @import("file.zig").File;
const Module = @import("../../Module.zig");
const Symbol = @import("Symbol.zig");
const ZigModule = @This();

449
src/link/Elf/eh_frame.zig Normal file
View File

@ -0,0 +1,449 @@
pub const Fde = struct {
/// Includes 4byte size cell.
offset: u64,
size: u64,
cie_index: u32,
rel_index: u32 = 0,
rel_num: u32 = 0,
rel_section_index: u32 = 0,
input_section_index: u32 = 0,
file_index: u32 = 0,
alive: bool = true,
/// Includes 4byte size cell.
out_offset: u64 = 0,
pub fn address(fde: Fde, elf_file: *Elf) u64 {
const base: u64 = if (elf_file.eh_frame_section_index) |shndx|
elf_file.shdrs.items[shndx].sh_addr
else
0;
return base + fde.out_offset;
}
pub fn data(fde: Fde, elf_file: *Elf) error{Overflow}![]const u8 {
const object = elf_file.file(fde.file_index).?.object;
const contents = try object.shdrContents(fde.input_section_index);
return contents[fde.offset..][0..fde.calcSize()];
}
pub fn cie(fde: Fde, elf_file: *Elf) Cie {
const object = elf_file.file(fde.file_index).?.object;
return object.cies.items[fde.cie_index];
}
pub fn ciePointer(fde: Fde, elf_file: *Elf) u32 {
return std.mem.readIntLittle(u32, fde.data(elf_file)[4..8]);
}
pub fn calcSize(fde: Fde) u64 {
return fde.size + 4;
}
pub fn atom(fde: Fde, elf_file: *Elf) error{Overflow}!*Atom {
const object = elf_file.file(fde.file_index).?.object;
const rel = (try fde.relocs(elf_file))[0];
const sym = object.symtab[rel.r_sym()];
const atom_index = object.atoms.items[sym.st_shndx];
return elf_file.atom(atom_index).?;
}
pub fn relocs(fde: Fde, elf_file: *Elf) error{Overflow}![]align(1) const elf.Elf64_Rela {
const object = elf_file.file(fde.file_index).?.object;
return (try object.getRelocs(fde.rel_section_index))[fde.rel_index..][0..fde.rel_num];
}
pub fn format(
fde: Fde,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fde;
_ = unused_fmt_string;
_ = options;
_ = writer;
@compileError("do not format FDEs directly");
}
pub fn fmt(fde: Fde, elf_file: *Elf) std.fmt.Formatter(format2) {
return .{ .data = .{
.fde = fde,
.elf_file = elf_file,
} };
}
const FdeFormatContext = struct {
fde: Fde,
elf_file: *Elf,
};
fn format2(
ctx: FdeFormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
const fde = ctx.fde;
const elf_file = ctx.elf_file;
const base_addr = fde.address(elf_file);
const atom_name = if (fde.atom(elf_file)) |atom_ptr|
atom_ptr.name(elf_file)
else |_|
"";
try writer.print("@{x} : size({x}) : cie({d}) : {s}", .{
base_addr + fde.out_offset,
fde.calcSize(),
fde.cie_index,
atom_name,
});
if (!fde.alive) try writer.writeAll(" : [*]");
}
};
pub const Cie = struct {
/// Includes 4byte size cell.
offset: u64,
size: u64,
rel_index: u32 = 0,
rel_num: u32 = 0,
rel_section_index: u32 = 0,
input_section_index: u32 = 0,
file_index: u32 = 0,
/// Includes 4byte size cell.
out_offset: u64 = 0,
alive: bool = false,
pub fn address(cie: Cie, elf_file: *Elf) u64 {
const base: u64 = if (elf_file.eh_frame_section_index) |shndx|
elf_file.shdrs.items[shndx].sh_addr
else
0;
return base + cie.out_offset;
}
pub fn data(cie: Cie, elf_file: *Elf) error{Overflow}![]const u8 {
const object = elf_file.file(cie.file_index).?.object;
const contents = try object.shdrContents(cie.input_section_index);
return contents[cie.offset..][0..cie.calcSize()];
}
pub fn calcSize(cie: Cie) u64 {
return cie.size + 4;
}
pub fn relocs(cie: Cie, elf_file: *Elf) error{Overflow}![]align(1) const elf.Elf64_Rela {
const object = elf_file.file(cie.file_index).?.object;
return (try object.getRelocs(cie.rel_section_index))[cie.rel_index..][0..cie.rel_num];
}
pub fn eql(cie: Cie, other: Cie, elf_file: *Elf) error{Overflow}!bool {
if (!std.mem.eql(u8, try cie.data(elf_file), try other.data(elf_file))) return false;
const cie_relocs = try cie.relocs(elf_file);
const other_relocs = try other.relocs(elf_file);
if (cie_relocs.len != other_relocs.len) return false;
for (cie_relocs, other_relocs) |cie_rel, other_rel| {
if (cie_rel.r_offset - cie.offset != other_rel.r_offset - other.offset) return false;
if (cie_rel.r_type() != other_rel.r_type()) return false;
if (cie_rel.r_addend != other_rel.r_addend) return false;
const cie_object = elf_file.file(cie.file_index).?.object;
const other_object = elf_file.file(other.file_index).?.object;
const cie_sym = cie_object.symbol(cie_rel.r_sym(), elf_file);
const other_sym = other_object.symbol(other_rel.r_sym(), elf_file);
if (!std.mem.eql(u8, std.mem.asBytes(&cie_sym), std.mem.asBytes(&other_sym))) return false;
}
return true;
}
pub fn format(
cie: Cie,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = cie;
_ = unused_fmt_string;
_ = options;
_ = writer;
@compileError("do not format CIEs directly");
}
pub fn fmt(cie: Cie, elf_file: *Elf) std.fmt.Formatter(format2) {
return .{ .data = .{
.cie = cie,
.elf_file = elf_file,
} };
}
const CieFormatContext = struct {
cie: Cie,
elf_file: *Elf,
};
fn format2(
ctx: CieFormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
const cie = ctx.cie;
const elf_file = ctx.elf_file;
const base_addr = cie.address(elf_file);
try writer.print("@{x} : size({x})", .{
base_addr + cie.out_offset,
cie.calcSize(),
});
if (!cie.alive) try writer.writeAll(" : [*]");
}
};
pub const Iterator = struct {
data: []const u8,
pos: u64 = 0,
pub const Record = struct {
tag: enum { fde, cie },
offset: u64,
size: u64,
};
pub fn next(it: *Iterator) !?Record {
if (it.pos >= it.data.len) return null;
var stream = std.io.fixedBufferStream(it.data[it.pos..]);
const reader = stream.reader();
var size = try reader.readIntLittle(u32);
if (size == 0xFFFFFFFF) @panic("TODO");
const id = try reader.readIntLittle(u32);
const record = Record{
.tag = if (id == 0) .cie else .fde,
.offset = it.pos,
.size = size,
};
it.pos += size + 4;
return record;
}
};
pub fn calcEhFrameSize(elf_file: *Elf) !usize {
var offset: u64 = 0;
var cies = std.ArrayList(Cie).init(elf_file.base.allocator);
defer cies.deinit();
for (elf_file.objects.items) |index| {
const object = elf_file.file(index).?.object;
outer: for (object.cies.items) |*cie| {
for (cies.items) |other| {
if (other.eql(cie.*, elf_file)) {
// We already have a CIE record that has the exact same contents, so instead of
// duplicating them, we mark this one dead and set its output offset to be
// equal to that of the alive record. This way, we won't have to rewrite
// Fde.cie_index field when committing the records to file.
cie.out_offset = other.out_offset;
continue :outer;
}
}
cie.alive = true;
cie.out_offset = offset;
offset += cie.calcSize();
try cies.append(cie.*);
}
}
for (elf_file.objects.items) |index| {
const object = elf_file.file(index).?.object;
for (object.fdes.items) |*fde| {
if (!fde.alive) continue;
fde.out_offset = offset;
offset += fde.calcSize();
}
}
return offset + 4; // NULL terminator
}
pub fn calcEhFrameHdrSize(elf_file: *Elf) usize {
var count: usize = 0;
for (elf_file.objects.items) |index| {
for (elf_file.file(index).?.object.fdes.items) |fde| {
if (!fde.alive) continue;
count += 1;
}
}
return eh_frame_hdr_header_size + count * 8;
}
fn resolveReloc(rec: anytype, sym: *const Symbol, rel: elf.Elf64_Rela, elf_file: *Elf, contents: []u8) !void {
const offset = rel.r_offset - rec.offset;
const P = @as(i64, @intCast(rec.address(elf_file) + offset));
const S = @as(i64, @intCast(sym.address(.{}, elf_file)));
const A = rel.r_addend;
relocs_log.debug(" {s}: {x}: [{x} => {x}] ({s})", .{
Atom.fmtRelocType(rel.r_type()),
offset,
P,
S + A,
sym.name(elf_file),
});
var where = contents[offset..];
switch (rel.r_type()) {
elf.R_X86_64_32 => std.mem.writeIntLittle(i32, where[0..4], @as(i32, @truncate(S + A))),
elf.R_X86_64_64 => std.mem.writeIntLittle(i64, where[0..8], S + A),
elf.R_X86_64_PC32 => std.mem.writeIntLittle(i32, where[0..4], @as(i32, @intCast(S - P + A))),
elf.R_X86_64_PC64 => std.mem.writeIntLittle(i64, where[0..8], S - P + A),
else => unreachable,
}
}
pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void {
const gpa = elf_file.base.allocator;
relocs_log.debug("{x}: .eh_frame", .{elf_file.shdrs.items[elf_file.eh_frame_section_index.?].sh_addr});
for (elf_file.objects.items) |index| {
const object = elf_file.file(index).?.object;
for (object.cies.items) |cie| {
if (!cie.alive) continue;
const contents = try gpa.dupe(u8, try cie.data(elf_file));
defer gpa.free(contents);
for (try cie.relocs(elf_file)) |rel| {
const sym = object.symbol(rel.r_sym(), elf_file);
try resolveReloc(cie, sym, rel, elf_file, contents);
}
try writer.writeAll(contents);
}
}
for (elf_file.objects.items) |index| {
const object = elf_file.file(index).?.object;
for (object.fdes.items) |fde| {
if (!fde.alive) continue;
const contents = try gpa.dupe(u8, try fde.data(elf_file));
defer gpa.free(contents);
std.mem.writeIntLittle(
i32,
contents[4..8],
@as(i32, @truncate(@as(i64, @intCast(fde.out_offset + 4)) - @as(i64, @intCast(fde.cie(elf_file).out_offset)))),
);
for (try fde.relocs(elf_file)) |rel| {
const sym = object.symbol(rel.r_sym(), elf_file);
try resolveReloc(fde, sym, rel, elf_file, contents);
}
try writer.writeAll(contents);
}
}
try writer.writeIntLittle(u32, 0);
}
pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
try writer.writeByte(1); // version
try writer.writeByte(EH_PE.pcrel | EH_PE.sdata4);
try writer.writeByte(EH_PE.udata4);
try writer.writeByte(EH_PE.datarel | EH_PE.sdata4);
const eh_frame_shdr = elf_file.shdrs.items[elf_file.eh_frame_section_index.?];
const eh_frame_hdr_shdr = elf_file.shdrs.items[elf_file.eh_frame_hdr_section_index.?];
const num_fdes = @as(u32, @intCast(@divExact(eh_frame_hdr_shdr.sh_size - eh_frame_hdr_header_size, 8)));
try writer.writeIntLittle(
u32,
@as(u32, @bitCast(@as(
i32,
@truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4),
))),
);
try writer.writeIntLittle(u32, num_fdes);
const Entry = struct {
init_addr: u32,
fde_addr: u32,
pub fn lessThan(ctx: void, lhs: @This(), rhs: @This()) bool {
_ = ctx;
return lhs.init_addr < rhs.init_addr;
}
};
var entries = std.ArrayList(Entry).init(elf_file.base.allocator);
defer entries.deinit();
try entries.ensureTotalCapacityPrecise(num_fdes);
for (elf_file.objects.items) |index| {
const object = elf_file.file(index).?.object;
for (object.fdes.items) |fde| {
if (!fde.alive) continue;
const relocs = try fde.relocs(elf_file);
assert(relocs.len > 0); // Should this be an error? Things are completely broken anyhow if this trips...
const rel = relocs[0];
const sym = object.symbol(rel.r_sym(), elf_file);
const P = @as(i64, @intCast(fde.address(elf_file)));
const S = @as(i64, @intCast(sym.address(.{}, elf_file)));
const A = rel.r_addend;
entries.appendAssumeCapacity(.{
.init_addr = @as(u32, @bitCast(@as(i32, @truncate(S + A - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)))))),
.fde_addr = @as(
u32,
@bitCast(@as(i32, @truncate(P - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))),
),
});
}
}
std.mem.sort(Entry, entries.items, {}, Entry.lessThan);
try writer.writeAll(std.mem.sliceAsBytes(entries.items));
}
const eh_frame_hdr_header_size: u64 = 12;
const EH_PE = struct {
pub const absptr = 0x00;
pub const uleb128 = 0x01;
pub const udata2 = 0x02;
pub const udata4 = 0x03;
pub const udata8 = 0x04;
pub const sleb128 = 0x09;
pub const sdata2 = 0x0A;
pub const sdata4 = 0x0B;
pub const sdata8 = 0x0C;
pub const pcrel = 0x10;
pub const textrel = 0x20;
pub const datarel = 0x30;
pub const funcrel = 0x40;
pub const aligned = 0x50;
pub const indirect = 0x80;
pub const omit = 0xFF;
};
const std = @import("std");
const assert = std.debug.assert;
const elf = std.elf;
const relocs_log = std.log.scoped(.link_relocs);
const Allocator = std.mem.Allocator;
const Atom = @import("Atom.zig");
const Elf = @import("../Elf.zig");
const Object = @import("Object.zig");
const Symbol = @import("Symbol.zig");

105
src/link/Elf/file.zig Normal file
View File

@ -0,0 +1,105 @@
pub const File = union(enum) {
zig_module: *ZigModule,
linker_defined: *LinkerDefined,
object: *Object,
// shared_object: *SharedObject,
pub fn index(file: File) Index {
return switch (file) {
inline else => |x| x.index,
};
}
pub fn fmtPath(file: File) std.fmt.Formatter(formatPath) {
return .{ .data = file };
}
fn formatPath(
file: File,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = unused_fmt_string;
_ = options;
switch (file) {
.zig_module => |x| try writer.print("{s}", .{x.path}),
.linker_defined => try writer.writeAll("(linker defined)"),
.object => |x| try writer.print("{}", .{x.fmtPath()}),
// .shared_object => |x| try writer.writeAll(x.path),
}
}
pub fn isAlive(file: File) bool {
return switch (file) {
.zig_module => true,
.linker_defined => true,
inline else => |x| x.alive,
};
}
/// Encodes symbol rank so that the following ordering applies:
/// * strong defined
/// * weak defined
/// * strong in lib (dso/archive)
/// * weak in lib (dso/archive)
/// * common
/// * common in lib (archive)
/// * unclaimed
pub fn symbolRank(file: File, sym: elf.Elf64_Sym, in_archive: bool) u32 {
const base: u3 = blk: {
if (sym.st_shndx == elf.SHN_COMMON) break :blk if (in_archive) 6 else 5;
// if (file == .shared or in_archive) break :blk switch (sym.st_bind()) {
if (in_archive) break :blk switch (sym.st_bind()) {
elf.STB_GLOBAL => 3,
else => 4,
};
break :blk switch (sym.st_bind()) {
elf.STB_GLOBAL => 1,
else => 2,
};
};
return (@as(u32, base) << 24) + file.index();
}
pub fn setAlive(file: File) void {
switch (file) {
.zig_module, .linker_defined => {},
inline else => |x| x.alive = true,
}
}
pub fn markLive(file: File, elf_file: *Elf) void {
switch (file) {
.zig_module, .linker_defined => {},
inline else => |x| x.markLive(elf_file),
}
}
pub fn globals(file: File) []const Symbol.Index {
return switch (file) {
inline else => |x| x.globals(),
};
}
pub const Index = u32;
pub const Entry = union(enum) {
null: void,
zig_module: ZigModule,
linker_defined: LinkerDefined,
object: Object,
// shared_object: SharedObject,
};
};
const std = @import("std");
const elf = std.elf;
const Allocator = std.mem.Allocator;
const Elf = @import("../Elf.zig");
const LinkerDefined = @import("LinkerDefined.zig");
const Object = @import("Object.zig");
// const SharedObject = @import("SharedObject.zig");
const Symbol = @import("Symbol.zig");
const ZigModule = @import("ZigModule.zig");

View File

@ -0,0 +1,433 @@
pub const GotSection = struct {
entries: std.ArrayListUnmanaged(Entry) = .{},
needs_rela: bool = false,
dirty: bool = false,
output_symtab_size: Elf.SymtabSize = .{},
pub const Index = u32;
const Tag = enum {
got,
tlsld,
tlsgd,
gottp,
tlsdesc,
};
const Entry = struct {
tag: Tag,
symbol_index: Symbol.Index,
cell_index: Index,
/// Returns how many indexes in the GOT this entry uses.
pub inline fn len(entry: Entry) usize {
return switch (entry.tag) {
.got, .gottp => 1,
.tlsld, .tlsgd, .tlsdesc => 2,
};
}
pub fn address(entry: Entry, elf_file: *Elf) u64 {
const ptr_bytes = @as(u64, elf_file.archPtrWidthBytes());
const shdr = &elf_file.shdrs.items[elf_file.got_section_index.?];
return shdr.sh_addr + @as(u64, entry.cell_index) * ptr_bytes;
}
};
pub fn deinit(got: *GotSection, allocator: Allocator) void {
got.entries.deinit(allocator);
}
fn allocateEntry(got: *GotSection, allocator: Allocator) !Index {
try got.entries.ensureUnusedCapacity(allocator, 1);
// TODO add free list
const index = @as(Index, @intCast(got.entries.items.len));
const entry = got.entries.addOneAssumeCapacity();
const cell_index: Index = if (index > 0) blk: {
const last = got.entries.items[index - 1];
break :blk last.cell_index + @as(Index, @intCast(last.len()));
} else 0;
entry.* = .{ .tag = undefined, .symbol_index = undefined, .cell_index = cell_index };
got.dirty = true;
return index;
}
pub fn addGotSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !Index {
const index = try got.allocateEntry(elf_file.base.allocator);
const entry = &got.entries.items[index];
entry.tag = .got;
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;
if (symbol.extra(elf_file)) |extra| {
var new_extra = extra;
new_extra.got = index;
symbol.setExtra(new_extra, elf_file);
} else try symbol.addExtra(.{ .got = index }, elf_file);
return index;
}
// pub fn addTlsGdSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
// const index = got.next_index;
// const symbol = elf_file.getSymbol(sym_index);
// if (symbol.flags.import or elf_file.options.output_mode == .lib) got.needs_rela = true;
// if (symbol.getExtra(elf_file)) |extra| {
// var new_extra = extra;
// new_extra.tlsgd = index;
// symbol.setExtra(new_extra, elf_file);
// } else try symbol.addExtra(.{ .tlsgd = index }, elf_file);
// try got.symbols.append(elf_file.base.allocator, .{ .tlsgd = sym_index });
// got.next_index += 2;
// }
// pub fn addGotTpSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
// const index = got.next_index;
// const symbol = elf_file.getSymbol(sym_index);
// if (symbol.flags.import or elf_file.options.output_mode == .lib) got.needs_rela = true;
// if (symbol.getExtra(elf_file)) |extra| {
// var new_extra = extra;
// new_extra.gottp = index;
// symbol.setExtra(new_extra, elf_file);
// } else try symbol.addExtra(.{ .gottp = index }, elf_file);
// try got.symbols.append(elf_file.base.allocator, .{ .gottp = sym_index });
// got.next_index += 1;
// }
// pub fn addTlsDescSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void {
// const index = got.next_index;
// const symbol = elf_file.getSymbol(sym_index);
// got.needs_rela = true;
// if (symbol.getExtra(elf_file)) |extra| {
// var new_extra = extra;
// new_extra.tlsdesc = index;
// symbol.setExtra(new_extra, elf_file);
// } else try symbol.addExtra(.{ .tlsdesc = index }, elf_file);
// try got.symbols.append(elf_file.base.allocator, .{ .tlsdesc = sym_index });
// got.next_index += 2;
// }
pub fn size(got: GotSection, elf_file: *Elf) usize {
var s: usize = 0;
for (got.entries.items) |entry| {
s += elf_file.archPtrWidthBytes() * entry.len();
}
return s;
}
pub fn writeEntry(got: *GotSection, elf_file: *Elf, index: Index) !void {
const entry_size: u16 = elf_file.archPtrWidthBytes();
if (got.dirty) {
const needed_size = got.size(elf_file);
try elf_file.growAllocSection(elf_file.got_section_index.?, needed_size);
got.dirty = false;
}
const endian = elf_file.base.options.target.cpu.arch.endian();
const entry = got.entries.items[index];
const shdr = &elf_file.shdrs.items[elf_file.got_section_index.?];
const off = shdr.sh_offset + @as(u64, entry_size) * entry.cell_index;
const vaddr = shdr.sh_addr + @as(u64, entry_size) * entry.cell_index;
const value = elf_file.symbol(entry.symbol_index).value;
switch (entry_size) {
2 => {
var buf: [2]u8 = undefined;
std.mem.writeInt(u16, &buf, @as(u16, @intCast(value)), endian);
try elf_file.base.file.?.pwriteAll(&buf, off);
},
4 => {
var buf: [4]u8 = undefined;
std.mem.writeInt(u32, &buf, @as(u32, @intCast(value)), endian);
try elf_file.base.file.?.pwriteAll(&buf, off);
},
8 => {
var buf: [8]u8 = undefined;
std.mem.writeInt(u64, &buf, value, endian);
try elf_file.base.file.?.pwriteAll(&buf, off);
if (elf_file.base.child_pid) |pid| {
switch (builtin.os.tag) {
.linux => {
var local_vec: [1]std.os.iovec_const = .{.{
.iov_base = &buf,
.iov_len = buf.len,
}};
var remote_vec: [1]std.os.iovec_const = .{.{
.iov_base = @as([*]u8, @ptrFromInt(@as(usize, @intCast(vaddr)))),
.iov_len = buf.len,
}};
const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0);
switch (std.os.errno(rc)) {
.SUCCESS => assert(rc == buf.len),
else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}),
}
},
else => return error.HotSwapUnavailableOnHostOperatingSystem,
}
}
},
else => unreachable,
}
}
// pub fn write(got: GotSection, elf_file: *Elf, writer: anytype) !void {
// const is_shared = elf_file.options.output_mode == .lib;
// const apply_relocs = elf_file.options.apply_dynamic_relocs;
// for (got.symbols.items) |sym| {
// const symbol = elf_file.getSymbol(sym.getIndex());
// switch (sym) {
// .got => {
// const value: u64 = blk: {
// const value = symbol.getAddress(.{ .plt = false }, elf_file);
// if (symbol.flags.import) break :blk 0;
// if (symbol.isIFunc(elf_file))
// break :blk if (apply_relocs) value else 0;
// if (elf_file.options.pic and !symbol.isAbs(elf_file))
// break :blk if (apply_relocs) value else 0;
// break :blk value;
// };
// try writer.writeIntLittle(u64, value);
// },
// .tlsgd => {
// if (symbol.flags.import) {
// try writer.writeIntLittle(u64, 0);
// try writer.writeIntLittle(u64, 0);
// } else {
// try writer.writeIntLittle(u64, if (is_shared) @as(u64, 0) else 1);
// const offset = symbol.getAddress(.{}, elf_file) - elf_file.getDtpAddress();
// try writer.writeIntLittle(u64, offset);
// }
// },
// .gottp => {
// if (symbol.flags.import) {
// try writer.writeIntLittle(u64, 0);
// } else if (is_shared) {
// const offset = if (apply_relocs)
// symbol.getAddress(.{}, elf_file) - elf_file.getTlsAddress()
// else
// 0;
// try writer.writeIntLittle(u64, offset);
// } else {
// const offset = @as(i64, @intCast(symbol.getAddress(.{}, elf_file))) -
// @as(i64, @intCast(elf_file.getTpAddress()));
// try writer.writeIntLittle(u64, @as(u64, @bitCast(offset)));
// }
// },
// .tlsdesc => {
// try writer.writeIntLittle(u64, 0);
// try writer.writeIntLittle(u64, 0);
// },
// }
// }
// if (got.emit_tlsld) {
// try writer.writeIntLittle(u64, if (is_shared) @as(u64, 0) else 1);
// try writer.writeIntLittle(u64, 0);
// }
// }
// pub fn addRela(got: GotSection, elf_file: *Elf) !void {
// const is_shared = elf_file.options.output_mode == .lib;
// try elf_file.rela_dyn.ensureUnusedCapacity(elf_file.base.allocator, got.numRela(elf_file));
// for (got.symbols.items) |sym| {
// const symbol = elf_file.getSymbol(sym.getIndex());
// const extra = symbol.getExtra(elf_file).?;
// switch (sym) {
// .got => {
// const offset = symbol.gotAddress(elf_file);
// if (symbol.flags.import) {
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset,
// .sym = extra.dynamic,
// .type = elf.R_X86_64_GLOB_DAT,
// });
// continue;
// }
// if (symbol.isIFunc(elf_file)) {
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset,
// .type = elf.R_X86_64_IRELATIVE,
// .addend = @intCast(symbol.getAddress(.{ .plt = false }, elf_file)),
// });
// continue;
// }
// if (elf_file.options.pic and !symbol.isAbs(elf_file)) {
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset,
// .type = elf.R_X86_64_RELATIVE,
// .addend = @intCast(symbol.getAddress(.{ .plt = false }, elf_file)),
// });
// }
// },
// .tlsgd => {
// const offset = symbol.getTlsGdAddress(elf_file);
// if (symbol.flags.import) {
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset,
// .sym = extra.dynamic,
// .type = elf.R_X86_64_DTPMOD64,
// });
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset + 8,
// .sym = extra.dynamic,
// .type = elf.R_X86_64_DTPOFF64,
// });
// } else if (is_shared) {
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset,
// .sym = extra.dynamic,
// .type = elf.R_X86_64_DTPMOD64,
// });
// }
// },
// .gottp => {
// const offset = symbol.getGotTpAddress(elf_file);
// if (symbol.flags.import) {
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset,
// .sym = extra.dynamic,
// .type = elf.R_X86_64_TPOFF64,
// });
// } else if (is_shared) {
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset,
// .type = elf.R_X86_64_TPOFF64,
// .addend = @intCast(symbol.getAddress(.{}, elf_file) - elf_file.getTlsAddress()),
// });
// }
// },
// .tlsdesc => {
// const offset = symbol.getTlsDescAddress(elf_file);
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset,
// .sym = extra.dynamic,
// .type = elf.R_X86_64_TLSDESC,
// });
// },
// }
// }
// if (is_shared and got.emit_tlsld) {
// const offset = elf_file.getTlsLdAddress();
// elf_file.addRelaDynAssumeCapacity(.{
// .offset = offset,
// .type = elf.R_X86_64_DTPMOD64,
// });
// }
// }
// pub fn numRela(got: GotSection, elf_file: *Elf) usize {
// const is_shared = elf_file.options.output_mode == .lib;
// var num: usize = 0;
// for (got.symbols.items) |sym| {
// const symbol = elf_file.symbol(sym.index());
// switch (sym) {
// .got => if (symbol.flags.import or
// symbol.isIFunc(elf_file) or (elf_file.options.pic and !symbol.isAbs(elf_file)))
// {
// num += 1;
// },
// .tlsgd => if (symbol.flags.import) {
// num += 2;
// } else if (is_shared) {
// num += 1;
// },
// .gottp => if (symbol.flags.import or is_shared) {
// num += 1;
// },
// .tlsdesc => num += 1,
// }
// }
// if (is_shared and got.emit_tlsld) num += 1;
// return num;
// }
pub fn updateSymtabSize(got: *GotSection, elf_file: *Elf) void {
_ = elf_file;
got.output_symtab_size.nlocals = @as(u32, @intCast(got.entries.items.len));
}
pub fn writeSymtab(got: GotSection, elf_file: *Elf, ctx: anytype) !void {
const gpa = elf_file.base.allocator;
for (got.entries.items, ctx.ilocal..) |entry, ilocal| {
const suffix = switch (entry.tag) {
.tlsld => "$tlsld",
.tlsgd => "$tlsgd",
.got => "$got",
.gottp => "$gottp",
.tlsdesc => "$tlsdesc",
};
const symbol = elf_file.symbol(entry.symbol_index);
const name = try std.fmt.allocPrint(gpa, "{s}{s}", .{ symbol.name(elf_file), suffix });
defer gpa.free(name);
const st_name = try elf_file.strtab.insert(gpa, name);
const st_value = switch (entry.tag) {
.got => symbol.gotAddress(elf_file),
else => unreachable,
};
const st_size: u64 = entry.len() * elf_file.archPtrWidthBytes();
ctx.symtab[ilocal] = .{
.st_name = st_name,
.st_info = elf.STT_OBJECT,
.st_other = 0,
.st_shndx = elf_file.got_section_index.?,
.st_value = st_value,
.st_size = st_size,
};
}
}
const FormatCtx = struct {
got: GotSection,
elf_file: *Elf,
};
pub fn fmt(got: GotSection, elf_file: *Elf) std.fmt.Formatter(format2) {
return .{ .data = .{ .got = got, .elf_file = elf_file } };
}
pub fn format2(
ctx: FormatCtx,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
try writer.writeAll("GOT\n");
for (ctx.got.entries.items) |entry| {
const symbol = ctx.elf_file.symbol(entry.symbol_index);
try writer.print(" {d}@0x{x} => {d}@0x{x} ({s})\n", .{
entry.cell_index,
entry.address(ctx.elf_file),
entry.symbol_index,
symbol.address(.{}, ctx.elf_file),
symbol.name(ctx.elf_file),
});
}
}
};
const assert = std.debug.assert;
const builtin = @import("builtin");
const elf = std.elf;
const log = std.log.scoped(.link);
const std = @import("std");
const Allocator = std.mem.Allocator;
const Elf = @import("../Elf.zig");
const Symbol = @import("Symbol.zig");

View File

@ -100,13 +100,13 @@ pub fn StringTable(comptime log_scope: @Type(.EnumLiteral)) type {
});
}
pub fn get(self: Self, off: u32) ?[]const u8 {
pub fn get(self: Self, off: u32) ?[:0]const u8 {
log.debug("getting string at 0x{x}", .{off});
if (off >= self.buffer.items.len) return null;
return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.buffer.items.ptr + off)), 0);
}
pub fn getAssumeExists(self: Self, off: u32) []const u8 {
pub fn getAssumeExists(self: Self, off: u32) [:0]const u8 {
return self.get(off) orelse unreachable;
}