diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2023-09-13 10:07:07 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-09-13 10:07:07 +0200 |
| commit | 4d29b3967873d6ff7e7426b2ab99c00c9e0fa284 (patch) | |
| tree | 90bfc83c9ef5aafbdbd626d7e1767db18730dbeb /src/link/Elf/Object.zig | |
| parent | 89ea67aee2aeb4c041f55625f22fb9a1c56cf9ea (diff) | |
| parent | 8142349d699140ff71801d31f1d1958599f5adda (diff) | |
| download | zig-4d29b3967873d6ff7e7426b2ab99c00c9e0fa284.tar.gz zig-4d29b3967873d6ff7e7426b2ab99c00c9e0fa284.zip | |
Merge pull request #17113 from ziglang/elf-linker
elf: upstream zld/ELF functionality, part 1
Diffstat (limited to 'src/link/Elf/Object.zig')
| -rw-r--r-- | src/link/Elf/Object.zig | 872 |
1 files changed, 872 insertions, 0 deletions
diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig new file mode 100644 index 0000000000..b0a6ef2a1c --- /dev/null +++ b/src/link/Elf/Object.zig @@ -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"); |
