diff --git a/CMakeLists.txt b/CMakeLists.txt index 700cc20cae..6cdeffe504 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -591,6 +591,7 @@ set(ZIG_STAGE2_SOURCES "${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/SharedObject.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" diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 804be920f7..d54ec0a12e 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -14,6 +14,7 @@ files: std.MultiArrayList(File.Entry) = .{}, zig_module_index: ?File.Index = null, linker_defined_index: ?File.Index = null, objects: std.ArrayListUnmanaged(File.Index) = .{}, +shared_objects: std.ArrayListUnmanaged(File.Index) = .{}, /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. @@ -61,10 +62,34 @@ default_sym_version: elf.Elf64_Versym, shstrtab: StringTable(.strtab) = .{}, /// .strtab buffer strtab: StringTable(.strtab) = .{}, - -/// Representation of the GOT table as committed to the file. +/// Dynamic symbol table. Only populated and emitted when linking dynamically. +dynsym: DynsymSection = .{}, +/// .dynstrtab buffer +dynstrtab: StringTable(.dynstrtab) = .{}, +/// Version symbol table. Only populated and emitted when linking dynamically. +versym: std.ArrayListUnmanaged(elf.Elf64_Versym) = .{}, +/// .verneed section +verneed: VerneedSection = .{}, +/// .got section got: GotSection = .{}, +/// .rela.dyn section rela_dyn: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{}, +/// .dynamic section +dynamic: DynamicSection = .{}, +/// .hash section +hash: HashSection = .{}, +/// .gnu.hash section +gnu_hash: GnuHashSection = .{}, +/// .plt section +plt: PltSection = .{}, +/// .got.plt section +got_plt: GotPltSection = .{}, +/// .plt.got section +plt_got: PltGotSection = .{}, +/// .copyrel section +copy_rel: CopyRelSection = .{}, +/// .rela.plt section +rela_plt: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{}, /// Tracked section headers text_section_index: ?u16 = null, @@ -73,18 +98,30 @@ data_section_index: ?u16 = null, bss_section_index: ?u16 = null, tdata_section_index: ?u16 = null, tbss_section_index: ?u16 = null, -eh_frame_section_index: ?u16 = null, -eh_frame_hdr_section_index: ?u16 = null, -dynamic_section_index: ?u16 = null, -got_section_index: ?u16 = null, -got_plt_section_index: ?u16 = null, -plt_section_index: ?u16 = null, -rela_dyn_section_index: ?u16 = null, debug_info_section_index: ?u16 = null, debug_abbrev_section_index: ?u16 = null, debug_str_section_index: ?u16 = null, debug_aranges_section_index: ?u16 = null, debug_line_section_index: ?u16 = null, + +copy_rel_section_index: ?u16 = null, +dynamic_section_index: ?u16 = null, +dynstrtab_section_index: ?u16 = null, +dynsymtab_section_index: ?u16 = null, +eh_frame_section_index: ?u16 = null, +eh_frame_hdr_section_index: ?u16 = null, +hash_section_index: ?u16 = null, +gnu_hash_section_index: ?u16 = null, +got_section_index: ?u16 = null, +got_plt_section_index: ?u16 = null, +interp_section_index: ?u16 = null, +plt_section_index: ?u16 = null, +plt_got_section_index: ?u16 = null, +rela_dyn_section_index: ?u16 = null, +rela_plt_section_index: ?u16 = null, +versym_section_index: ?u16 = null, +verneed_section_index: ?u16 = null, + shstrtab_section_index: ?u16 = null, strtab_section_index: ?u16 = null, symtab_section_index: ?u16 = null, @@ -316,10 +353,11 @@ pub fn deinit(self: *Elf) void { .zig_module => data.zig_module.deinit(gpa), .linker_defined => data.linker_defined.deinit(gpa), .object => data.object.deinit(gpa), - // .shared_object => data.shared_object.deinit(gpa), + .shared_object => data.shared_object.deinit(gpa), }; self.files.deinit(gpa); self.objects.deinit(gpa); + self.shared_objects.deinit(gpa); self.shdrs.deinit(gpa); self.phdr_to_shdr_table.deinit(gpa); @@ -333,7 +371,6 @@ pub fn deinit(self: *Elf) void { self.symbols.deinit(gpa); self.symbols_extra.deinit(gpa); self.symbols_free_list.deinit(gpa); - self.got.deinit(gpa); self.resolver.deinit(gpa); self.start_stop_indexes.deinit(gpa); @@ -369,7 +406,19 @@ pub fn deinit(self: *Elf) void { self.comdat_groups.deinit(gpa); self.comdat_groups_owners.deinit(gpa); self.comdat_groups_table.deinit(gpa); + + self.got.deinit(gpa); + self.plt.deinit(gpa); + self.plt_got.deinit(gpa); + self.dynsym.deinit(gpa); + self.dynstrtab.deinit(gpa); + self.dynamic.deinit(gpa); + self.hash.deinit(gpa); + self.versym.deinit(gpa); + self.verneed.deinit(gpa); + self.copy_rel.deinit(gpa); self.rela_dyn.deinit(gpa); + self.rela_plt.deinit(gpa); } pub fn getDeclVAddr(self: *Elf, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo) !u64 { @@ -1268,6 +1317,24 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node try dw.flushModule(self.base.options.module.?); } + // Dedup shared objects + { + var seen_dsos = std.StringHashMap(void).init(gpa); + defer seen_dsos.deinit(); + try seen_dsos.ensureTotalCapacity(@as(u32, @intCast(self.shared_objects.items.len))); + + var i: usize = 0; + while (i < self.shared_objects.items.len) { + const index = self.shared_objects.items[i]; + const shared_object = self.file(index).?.shared_object; + const soname = shared_object.soname(); + const gop = seen_dsos.getOrPutAssumeCapacity(soname); + if (gop.found_existing) { + _ = self.shared_objects.orderedRemove(i); + } else i += 1; + } + } + // If we haven't already, create a linker-generated input file comprising of // linker-defined synthetic symbols only such as `_DYNAMIC`, etc. if (self.linker_defined_index == null) { @@ -1677,6 +1744,7 @@ fn resolveSymbols(self: *Elf) void { if (self.zig_module_index) |index| self.file(index).?.resolveSymbols(self); // Resolve symbols on the set of all objects and shared objects (even if some are unneeded). for (self.objects.items) |index| self.file(index).?.resolveSymbols(self); + for (self.shared_objects.items) |index| self.file(index).?.resolveSymbols(self); // Mark live objects. self.markLive(); @@ -1684,6 +1752,7 @@ fn resolveSymbols(self: *Elf) void { // Reset state of all globals after marking live objects. if (self.zig_module_index) |index| self.file(index).?.resetGlobals(self); for (self.objects.items) |index| self.file(index).?.resetGlobals(self); + for (self.shared_objects.items) |index| self.file(index).?.resetGlobals(self); // Prune dead objects and shared objects. var i: usize = 0; @@ -1693,6 +1762,13 @@ fn resolveSymbols(self: *Elf) void { _ = self.objects.orderedRemove(i); } else i += 1; } + i = 0; + while (i < self.shared_objects.items.len) { + const index = self.shared_objects.items[i]; + if (!self.file(index).?.isAlive()) { + _ = self.shared_objects.orderedRemove(i); + } else i += 1; + } // Dedup comdat groups. for (self.objects.items) |index| { @@ -1728,6 +1804,7 @@ fn resolveSymbols(self: *Elf) void { // Re-resolve the symbols. if (self.zig_module_index) |index| self.file(index).?.resolveSymbols(self); for (self.objects.items) |index| self.file(index).?.resolveSymbols(self); + for (self.shared_objects.items) |index| self.file(index).?.resolveSymbols(self); } /// Traverses all objects and shared objects marking any object referenced by @@ -1740,6 +1817,10 @@ fn markLive(self: *Elf) void { const file_ptr = self.file(index).?; if (file_ptr.isAlive()) file_ptr.markLive(self); } + for (self.shared_objects.items) |index| { + const file_ptr = self.file(index).?; + if (file_ptr.isAlive()) file_ptr.markLive(self); + } } fn markEhFrameAtomsDead(self: *Elf) void { @@ -1759,10 +1840,10 @@ fn markImportsExports(self: *Elf) void { const file_ptr = global.file(elf_file) orelse continue; const vis = @as(elf.STV, @enumFromInt(global.elfSym(elf_file).st_other)); if (vis == .HIDDEN) continue; - // if (file == .shared and !global.isAbs(self)) { - // global.flags.import = true; - // continue; - // } + if (file_ptr == .shared_object and !global.isAbs(elf_file)) { + global.flags.import = true; + continue; + } if (file_ptr.index() == file_index) { global.flags.@"export" = true; if (elf_file.isDynLib() and vis != .PROTECTED) { @@ -1773,6 +1854,17 @@ fn markImportsExports(self: *Elf) void { } }.mark; + if (!self.isDynLib()) { + for (self.shared_objects.items) |index| { + for (self.file(index).?.globals()) |global_index| { + const global = self.symbol(global_index); + const file_ptr = global.file(self) orelse continue; + const vis = @as(elf.STV, @enumFromInt(global.elfSym(self).st_other)); + if (file_ptr != .shared_object and vis != .HIDDEN) global.flags.@"export" = true; + } + } + } + if (self.zig_module_index) |index| { mark(self, index); } @@ -1820,12 +1912,51 @@ fn scanRelocs(self: *Elf) !void { try self.reportUndefined(&undefs); - for (self.symbols.items, 0..) |*sym, sym_index| { + for (self.symbols.items, 0..) |*sym, i| { + const index = @as(u32, @intCast(i)); + if (!sym.isLocal() and !sym.flags.has_dynamic) { + log.debug("'{s}' is non-local", .{sym.name(self)}); + try self.dynsym.addSymbol(index, self); + } if (sym.flags.needs_got) { log.debug("'{s}' needs GOT", .{sym.name(self)}); - _ = try self.got.addGotSymbol(@intCast(sym_index), self); - sym.flags.has_got = true; + _ = try self.got.addGotSymbol(index, self); } + if (sym.flags.needs_plt) { + if (sym.flags.is_canonical) { + log.debug("'{s}' needs CPLT", .{sym.name(self)}); + sym.flags.@"export" = true; + try self.plt.addSymbol(index, self); + } else if (sym.flags.needs_got) { + log.debug("'{s}' needs PLTGOT", .{sym.name(self)}); + try self.plt_got.addSymbol(index, self); + } else { + log.debug("'{s}' needs PLT", .{sym.name(self)}); + try self.plt.addSymbol(index, self); + } + } + if (sym.flags.needs_copy_rel and !sym.flags.has_copy_rel) { + log.debug("'{s}' needs COPYREL", .{sym.name(self)}); + try self.copy_rel.addSymbol(index, self); + } + if (sym.flags.needs_tlsgd) { + log.debug("'{s}' needs TLSGD", .{sym.name(self)}); + try self.got.addTlsGdSymbol(index, self); + } + if (sym.flags.needs_gottp) { + log.debug("'{s}' needs GOTTP", .{sym.name(self)}); + try self.got.addGotTpSymbol(index, self); + } + if (sym.flags.needs_tlsdesc) { + log.debug("'{s}' needs TLSDESC", .{sym.name(self)}); + try self.dynsym.addSymbol(index, self); + try self.got.addTlsDescSymbol(index, self); + } + } + + if (self.got.flags.needs_tlsld) { + log.debug("program needs TLSLD", .{}); + try self.got.addTlsLdSymbol(self); } } @@ -2567,12 +2698,12 @@ fn writeHeader(self: *Elf) !void { assert(index == 16); - const elf_type = switch (self.base.options.effectiveOutputMode()) { - .Exe => elf.ET.EXEC, - .Obj => elf.ET.REL, + const elf_type: elf.ET = switch (self.base.options.effectiveOutputMode()) { + .Exe => if (self.base.options.pic) .DYN else .EXEC, + .Obj => .REL, .Lib => switch (self.base.options.link_mode) { - .Static => elf.ET.REL, - .Dynamic => elf.ET.DYN, + .Static => @as(elf.ET, .REL), + .Dynamic => .DYN, }, }; mem.writeInt(u16, hdr_buf[index..][0..2], @intFromEnum(elf_type), endian); @@ -2819,8 +2950,8 @@ fn updateDeclCode( esym.st_value = atom_ptr.value; log.debug(" (writing new offset table entry)", .{}); - const extra = sym.extra(self).?; - try self.got.writeEntry(self, extra.got); + // const extra = sym.extra(self).?; + // try self.got.writeEntry(self, extra.got); } } else if (code.len < old_size) { atom_ptr.shrink(self); @@ -2834,7 +2965,8 @@ fn updateDeclCode( sym.flags.needs_got = true; const gop = try sym.getOrCreateGotEntry(sym_index, self); - try self.got.writeEntry(self, gop.index); + _ = gop; + // try self.got.writeEntry(self, gop.index); } if (self.base.child_pid) |pid| { @@ -3070,7 +3202,8 @@ fn updateLazySymbol(self: *Elf, sym: link.File.LazySymbol, symbol_index: Symbol. local_sym.flags.needs_got = true; const gop = try local_sym.getOrCreateGotEntry(symbol_index, self); - try self.got.writeEntry(self, gop.index); + _ = gop; + // try self.got.writeEntry(self, gop.index); const section_offset = atom_ptr.value - self.phdrs.items[phdr_index].p_vaddr; const file_offset = self.shdrs.items[output_section_index].sh_offset + section_offset; @@ -3480,13 +3613,145 @@ fn initSections(self: *Elf) !void { } } - if (self.got.entries.items.len > 0 and self.got_section_index == null) { + if (self.got.entries.items.len > 0) { self.got_section_index = try self.addSection(.{ .name = ".got", .type = elf.SHT_PROGBITS, .flags = elf.SHF_ALLOC | elf.SHF_WRITE, .addralign = ptr_size, }); + self.got_plt_section_index = try self.addSection(.{ + .name = ".got.plt", + .type = elf.SHT_PROGBITS, + .flags = elf.SHF_ALLOC | elf.SHF_WRITE, + .addralign = @alignOf(u64), + }); + } + + const needs_rela_dyn = blk: { + if (self.got.flags.needs_rela or self.got.flags.needs_tlsld or + self.copy_rel.symbols.items.len > 0) break :blk true; + for (self.objects.items) |index| { + if (self.file(index).?.object.num_dynrelocs > 0) break :blk true; + } + break :blk false; + }; + if (needs_rela_dyn) { + self.rela_dyn_section_index = try self.addSection(.{ + .name = ".rela.dyn", + .type = elf.SHT_RELA, + .flags = elf.SHF_ALLOC, + .addralign = @alignOf(elf.Elf64_Rela), + .entsize = @sizeOf(elf.Elf64_Rela), + }); + } + + if (self.plt.symbols.items.len > 0) { + self.plt_section_index = try self.addSection(.{ + .name = ".plt", + .type = elf.SHT_PROGBITS, + .flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, + .addralign = 16, + }); + self.rela_plt_section_index = try self.addSection(.{ + .name = ".rela.plt", + .type = elf.SHT_RELA, + .flags = elf.SHF_ALLOC, + .addralign = @alignOf(elf.Elf64_Rela), + .entsize = @sizeOf(elf.Elf64_Rela), + }); + } + + if (self.plt_got.symbols.items.len > 0) { + self.plt_got_section_index = try self.addSection(.{ + .name = ".plt.got", + .type = elf.SHT_PROGBITS, + .flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, + .addralign = 16, + }); + } + + if (self.copy_rel.symbols.items.len > 0) { + self.copy_rel_section_index = try self.addSection(.{ + .name = ".copyrel", + .type = elf.SHT_NOBITS, + .flags = elf.SHF_ALLOC | elf.SHF_WRITE, + }); + } + + const needs_interp = blk: { + // On Ubuntu with musl-gcc, we get a weird combo of options looking like this: + // -dynamic-linker= -static + // In this case, if we do generate .interp section and segment, we will get + // a segfault in the dynamic linker trying to load a binary that is static + // and doesn't contain .dynamic section. + if (self.isStatic() and !self.base.options.pie) break :blk false; + break :blk self.base.options.dynamic_linker != null; + }; + if (needs_interp) { + self.interp_section_index = try self.addSection(.{ + .name = ".interp", + .type = elf.SHT_PROGBITS, + .flags = elf.SHF_ALLOC, + .addralign = 1, + }); + } + + if (self.isDynLib() or self.shared_objects.items.len > 0 or self.base.options.pie) { + self.dynstrtab_section_index = try self.addSection(.{ + .name = ".dynstr", + .flags = elf.SHF_ALLOC, + .type = elf.SHT_STRTAB, + .entsize = 1, + .addralign = 1, + }); + self.dynamic_section_index = try self.addSection(.{ + .name = ".dynamic", + .flags = elf.SHF_ALLOC | elf.SHF_WRITE, + .type = elf.SHT_DYNAMIC, + .entsize = @sizeOf(elf.Elf64_Dyn), + .addralign = @alignOf(elf.Elf64_Dyn), + }); + self.dynsymtab_section_index = try self.addSection(.{ + .name = ".dynsym", + .flags = elf.SHF_ALLOC, + .type = elf.SHT_DYNSYM, + .addralign = @alignOf(elf.Elf64_Sym), + .entsize = @sizeOf(elf.Elf64_Sym), + }); + self.hash_section_index = try self.addSection(.{ + .name = ".hash", + .flags = elf.SHF_ALLOC, + .type = elf.SHT_HASH, + .addralign = 4, + .entsize = 4, + }); + self.gnu_hash_section_index = try self.addSection(.{ + .name = ".gnu.hash", + .flags = elf.SHF_ALLOC, + .type = elf.SHT_GNU_HASH, + .addralign = 8, + }); + + const needs_versions = for (self.dynsym.entries.items) |entry| { + const sym = self.symbol(entry.symbol_index); + if (sym.flags.import and sym.version_index & elf.VERSYM_VERSION > elf.VER_NDX_GLOBAL) break true; + } else false; + if (needs_versions) { + self.versym_section_index = try self.addSection(.{ + .name = ".gnu.version", + .flags = elf.SHF_ALLOC, + .type = elf.SHT_GNU_VERSYM, + .addralign = @alignOf(elf.Elf64_Versym), + .entsize = @sizeOf(elf.Elf64_Versym), + }); + self.verneed_section_index = try self.addSection(.{ + .name = ".gnu.version_r", + .flags = elf.SHF_ALLOC, + .type = elf.SHT_GNU_VERNEED, + .addralign = @alignOf(elf.Elf64_Verneed), + }); + } } if (self.symtab_section_index == null) { @@ -3665,6 +3930,20 @@ fn sortSections(self: *Elf) !void { &self.symtab_section_index, &self.strtab_section_index, &self.shstrtab_section_index, + &self.interp_section_index, + &self.dynamic_section_index, + &self.dynsymtab_section_index, + &self.dynstrtab_section_index, + &self.hash_section_index, + &self.gnu_hash_section_index, + &self.plt_section_index, + &self.got_plt_section_index, + &self.plt_got_section_index, + &self.rela_dyn_section_index, + &self.rela_plt_section_index, + &self.copy_rel_section_index, + &self.versym_section_index, + &self.verneed_section_index, }) |maybe_index| { if (maybe_index.*) |*index| { index.* = backlinks[index.*]; @@ -3675,6 +3954,47 @@ fn sortSections(self: *Elf) !void { const shdr = &self.shdrs.items[index]; shdr.sh_link = self.strtab_section_index.?; } + + if (self.dynamic_section_index) |index| { + const shdr = &self.shdrs.items[index]; + shdr.sh_link = self.dynstrtab_section_index.?; + } + + if (self.dynsymtab_section_index) |index| { + const shdr = &self.shdrs.items[index]; + shdr.sh_link = self.dynstrtab_section_index.?; + } + + if (self.hash_section_index) |index| { + const shdr = &self.shdrs.items[index]; + shdr.sh_link = self.dynsymtab_section_index.?; + } + + if (self.gnu_hash_section_index) |index| { + const shdr = &self.shdrs.items[index]; + shdr.sh_link = self.dynsymtab_section_index.?; + } + + if (self.versym_section_index) |index| { + const shdr = &self.shdrs.items[index]; + shdr.sh_link = self.dynsymtab_section_index.?; + } + + if (self.verneed_section_index) |index| { + const shdr = &self.shdrs.items[index]; + shdr.sh_link = self.dynstrtab_section_index.?; + } + + if (self.rela_dyn_section_index) |index| { + const shdr = &self.shdrs.items[index]; + shdr.sh_link = self.dynsymtab_section_index orelse 0; + } + + if (self.rela_plt_section_index) |index| { + const shdr = &self.shdrs.items[index]; + shdr.sh_link = self.dynsymtab_section_index.?; + shdr.sh_info = self.plt_section_index.?; + } } fn updateSectionSizes(self: *Elf) !void { @@ -3683,21 +4003,77 @@ fn updateSectionSizes(self: *Elf) !void { } if (self.eh_frame_section_index) |index| { - const shdr = &self.shdrs.items[index]; - shdr.sh_size = try eh_frame.calcEhFrameSize(self); - shdr.sh_addralign = @alignOf(u64); + self.shdrs.items[index].sh_size = try eh_frame.calcEhFrameSize(self); } if (self.eh_frame_hdr_section_index) |index| { - const shdr = &self.shdrs.items[index]; - shdr.sh_size = eh_frame.calcEhFrameHdrSize(self); - shdr.sh_addralign = @alignOf(u32); + self.shdrs.items[index].sh_size = eh_frame.calcEhFrameHdrSize(self); } if (self.got_section_index) |index| { self.shdrs.items[index].sh_size = self.got.size(self); } + if (self.plt_section_index) |index| { + self.shdrs.items[index].sh_size = self.plt.size(); + } + + if (self.got_plt_section_index) |index| { + self.shdrs.items[index].sh_size = self.got_plt.size(self); + } + + if (self.plt_got_section_index) |index| { + self.shdrs.items[index].sh_size = self.plt_got.size(); + } + + if (self.rela_dyn_section_index) |shndx| { + var num = self.got.numRela(self) + self.copy_rel.numRela(); + for (self.objects.items) |index| { + num += self.file(index).?.object.num_dynrelocs; + } + self.shdrs.items[shndx].sh_size = num * @sizeOf(elf.Elf64_Rela); + } + + if (self.rela_plt_section_index) |index| { + self.shdrs.items[index].sh_size = self.plt.numRela() * @sizeOf(elf.Elf64_Rela); + } + + if (self.copy_rel_section_index) |index| { + try self.copy_rel.updateSectionSize(index, self); + } + + if (self.interp_section_index) |index| { + self.shdrs.items[index].sh_size = self.base.options.dynamic_linker.?.len + 1; + } + + if (self.hash_section_index) |index| { + self.shdrs.items[index].sh_size = self.hash.size(); + } + + if (self.gnu_hash_section_index) |index| { + self.shdrs.items[index].sh_size = self.gnu_hash.size(); + } + + if (self.dynamic_section_index) |index| { + self.shdrs.items[index].sh_size = self.dynamic.size(self); + } + + if (self.dynsymtab_section_index) |index| { + self.shdrs.items[index].sh_size = self.dynsym.size(); + } + + if (self.dynstrtab_section_index) |index| { + self.shdrs.items[index].sh_size = self.dynstrtab.buffer.items.len; + } + + if (self.versym_section_index) |index| { + self.shdrs.items[index].sh_size = self.versym.items.len * @sizeOf(elf.Elf64_Versym); + } + + if (self.verneed_section_index) |index| { + self.shdrs.items[index].sh_size = self.verneed.size(); + } + if (self.symtab_section_index != null) { try self.updateSymtabSize(); } @@ -3708,13 +4084,17 @@ fn updateSectionSizes(self: *Elf) !void { if (self.got_section_index) |_| { try self.got.updateStrtab(self); } + if (self.plt_section_index) |_| { + try self.plt.updateStrtab(self); + } + if (self.plt_got_section_index) |_| { + try self.plt_got.updateStrtab(self); + } self.shdrs.items[index].sh_size = self.strtab.buffer.items.len; - // try self.growNonAllocSection(index, self.strtab.buffer.items.len, 1, false); } if (self.shstrtab_section_index) |index| { self.shdrs.items[index].sh_size = self.shstrtab.buffer.items.len; - // try self.growNonAllocSection(index, self.shstrtab.buffer.items.len, 1, false); } } @@ -3729,18 +4109,18 @@ fn initPhdrs(self: *Elf) !void { }); // Add INTERP phdr if required - // if (self.interp_sect_index) |index| { - // const shdr = self.sections.items(.shdr)[index]; - // _ = try self.addPhdr(.{ - // .type = elf.PT_INTERP, - // .flags = elf.PF_R, - // .@"align" = 1, - // .offset = shdr.sh_offset, - // .addr = shdr.sh_addr, - // .filesz = shdr.sh_size, - // .memsz = shdr.sh_size, - // }); - // } + if (self.interp_section_index) |index| { + const shdr = self.shdrs.items[index]; + _ = try self.addPhdr(.{ + .type = elf.PT_INTERP, + .flags = elf.PF_R, + .@"align" = 1, + .offset = shdr.sh_offset, + .addr = shdr.sh_addr, + .filesz = shdr.sh_size, + .memsz = shdr.sh_size, + }); + } // Add LOAD phdrs const slice = self.shdrs.items; @@ -3806,18 +4186,18 @@ fn initPhdrs(self: *Elf) !void { } // Add DYNAMIC phdr - // if (self.dynamic_sect_index) |index| { - // const shdr = self.sections.items(.shdr)[index]; - // _ = try self.addPhdr(.{ - // .type = elf.PT_DYNAMIC, - // .flags = elf.PF_R | elf.PF_W, - // .@"align" = shdr.sh_addralign, - // .offset = shdr.sh_offset, - // .addr = shdr.sh_addr, - // .memsz = shdr.sh_size, - // .filesz = shdr.sh_size, - // }); - // } + if (self.dynamic_section_index) |index| { + const shdr = self.shdrs.items[index]; + _ = try self.addPhdr(.{ + .type = elf.PT_DYNAMIC, + .flags = elf.PF_R | elf.PF_W, + .@"align" = shdr.sh_addralign, + .offset = shdr.sh_offset, + .addr = shdr.sh_addr, + .memsz = shdr.sh_size, + .filesz = shdr.sh_size, + }); + } // Add PT_GNU_EH_FRAME phdr if required. if (self.eh_frame_hdr_section_index) |index| { @@ -4113,6 +4493,16 @@ fn updateSymtabSize(self: *Elf) !void { sizes.nlocals += self.got.output_symtab_size.nlocals; } + if (self.plt_section_index) |_| { + self.plt.updateSymtabSize(self); + sizes.nlocals += self.plt.output_symtab_size.nlocals; + } + + if (self.plt_got_section_index) |_| { + self.plt_got.updateSymtabSize(self); + sizes.nlocals += self.plt_got.output_symtab_size.nlocals; + } + if (self.linker_defined_index) |index| { const linker_defined = self.file(index).?.linker_defined; linker_defined.updateSymtabSize(self); @@ -4127,18 +4517,70 @@ fn updateSymtabSize(self: *Elf) !void { .p32 => @sizeOf(elf.Elf32_Sym), .p64 => @sizeOf(elf.Elf64_Sym), }; - // const sym_align: u16 = switch (self.ptr_width) { - // .p32 => @alignOf(elf.Elf32_Sym), - // .p64 => @alignOf(elf.Elf64_Sym), - // }; const needed_size = (sizes.nlocals + sizes.nglobals + 1) * sym_size; shdr.sh_size = needed_size; - // try self.growNonAllocSection(self.symtab_section_index.?, needed_size, sym_align, false); } fn writeSyntheticSections(self: *Elf) !void { const gpa = self.base.allocator; + if (self.interp_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + var buffer = try gpa.alloc(u8, shdr.sh_size); + defer gpa.free(buffer); + const dylinker = self.base.options.dynamic_linker.?; + @memcpy(buffer[0..dylinker.len], dylinker); + buffer[dylinker.len] = 0; + try self.base.file.?.pwriteAll(buffer, shdr.sh_offset); + } + + if (self.hash_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + try self.base.file.?.pwriteAll(self.hash.buffer.items, shdr.sh_offset); + } + + if (self.gnu_hash_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, self.gnu_hash.size()); + defer buffer.deinit(); + try self.gnu_hash.write(self, buffer.writer()); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } + + if (self.versym_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.versym.items), shdr.sh_offset); + } + + if (self.verneed_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, self.verneed.size()); + defer buffer.deinit(); + try self.verneed.write(buffer.writer()); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } + + if (self.dynamic_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, self.dynamic.size(self)); + defer buffer.deinit(); + try self.dynamic.write(self, buffer.writer()); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } + + if (self.dynsymtab_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, self.dynsym.size()); + defer buffer.deinit(); + try self.dynsym.write(self, buffer.writer()); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } + + if (self.dynstrtab_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + try self.base.file.?.pwriteAll(self.dynstrtab.buffer.items, shdr.sh_offset); + } + if (self.eh_frame_section_index) |shndx| { const shdr = self.shdrs.items[shndx]; var buffer = try std.ArrayList(u8).initCapacity(gpa, shdr.sh_size); @@ -4163,6 +4605,44 @@ fn writeSyntheticSections(self: *Elf) !void { try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); } + if (self.rela_dyn_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + try self.got.addRela(self); + try self.copy_rel.addRela(self); + self.sortRelaDyn(); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.rela_dyn.items), shdr.sh_offset); + } + + if (self.plt_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, self.plt.size()); + defer buffer.deinit(); + try self.plt.write(self, buffer.writer()); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } + + if (self.got_plt_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, self.got_plt.size(self)); + defer buffer.deinit(); + try self.got_plt.write(self, buffer.writer()); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } + + if (self.plt_got_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + var buffer = try std.ArrayList(u8).initCapacity(gpa, self.plt_got.size()); + defer buffer.deinit(); + try self.plt_got.write(self, buffer.writer()); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } + + if (self.rela_plt_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + try self.plt.addRela(self); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.rela_plt.items), shdr.sh_offset); + } + if (self.shstrtab_section_index) |index| { const shdr = self.shdrs.items[index]; try self.base.file.?.pwriteAll(self.shstrtab.buffer.items, shdr.sh_offset); @@ -4213,11 +4693,27 @@ fn writeSymtab(self: *Elf) !void { ctx.iglobal += object.output_symtab_size.nglobals; } + for (self.shared_objects.items) |index| { + const shared_object = self.file(index).?.shared_object; + shared_object.writeSymtab(self, ctx); + ctx.iglobal += shared_object.output_symtab_size.nglobals; + } + if (self.got_section_index) |_| { try self.got.writeSymtab(self, ctx); ctx.ilocal += self.got.output_symtab_size.nlocals; } + if (self.plt_section_index) |_| { + try self.plt.writeSymtab(self, ctx); + ctx.ilocal += self.plt.output_symtab_size.nlocals; + } + + if (self.plt_got_section_index) |_| { + try self.plt_got.writeSymtab(self, ctx); + ctx.ilocal += self.plt_got.output_symtab_size.nlocals; + } + if (self.linker_defined_index) |index| { const linker_defined = self.file(index).?.linker_defined; linker_defined.writeSymtab(self, ctx); @@ -4576,7 +5072,7 @@ pub fn isStatic(self: Elf) bool { } pub fn isDynLib(self: Elf) bool { - return self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic; + return self.base.options.effectiveOutputMode() == .Lib and self.base.options.link_mode == .Dynamic; } fn addPhdr(self: *Elf, opts: struct { @@ -4723,6 +5219,7 @@ pub fn file(self: *Elf, index: File.Index) ?File { .linker_defined => .{ .linker_defined = &self.files.items(.data)[index].linker_defined }, .zig_module => .{ .zig_module = &self.files.items(.data)[index].zig_module }, .object => .{ .object = &self.files.items(.data)[index].object }, + .shared_object => .{ .shared_object = &self.files.items(.data)[index].shared_object }, }; } @@ -5077,6 +5574,16 @@ fn fmtDumpState( }); } + for (self.shared_objects.items) |index| { + const shared_object = self.file(index).?.shared_object; + try writer.print("shared_object({d}) : ", .{index}); + try writer.print("{s}", .{shared_object.path}); + try writer.print(" : needed({})", .{shared_object.needed}); + if (!shared_object.alive) try writer.writeAll(" : [*]"); + try writer.writeByte('\n'); + try writer.print("{}\n", .{shared_object.fmtSymtab(self)}); + } + if (self.linker_defined_index) |index| { const linker_defined = self.file(index).?.linker_defined; try writer.print("linker_defined({d}) : (linker defined)\n", .{index}); @@ -5227,10 +5734,16 @@ const Archive = @import("Elf/Archive.zig"); pub const Atom = @import("Elf/Atom.zig"); const Cache = std.Build.Cache; const Compilation = @import("../Compilation.zig"); +const CopyRelSection = synthetic_sections.CopyRelSection; +const DynamicSection = synthetic_sections.DynamicSection; +const DynsymSection = synthetic_sections.DynsymSection; const Dwarf = @import("Dwarf.zig"); const Elf = @This(); const File = @import("Elf/file.zig").File; +const GnuHashSection = synthetic_sections.GnuHashSection; const GotSection = synthetic_sections.GotSection; +const GotPltSection = synthetic_sections.GotPltSection; +const HashSection = synthetic_sections.HashSection; const LinkerDefined = @import("Elf/LinkerDefined.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; @@ -5238,10 +5751,14 @@ const Module = @import("../Module.zig"); const Object = @import("Elf/Object.zig"); const InternPool = @import("../InternPool.zig"); const Package = @import("../Package.zig"); +const PltSection = synthetic_sections.PltSection; +const PltGotSection = synthetic_sections.PltGotSection; +const SharedObject = @import("Elf/SharedObject.zig"); const Symbol = @import("Elf/Symbol.zig"); const StringTable = @import("strtab.zig").StringTable; const TableSection = @import("table_section.zig").TableSection; const Type = @import("../type.zig").Type; const TypedValue = @import("../TypedValue.zig"); const Value = @import("../value.zig").Value; +const VerneedSection = synthetic_sections.VerneedSection; const ZigModule = @import("Elf/ZigModule.zig"); diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig new file mode 100644 index 0000000000..d4e3c6f505 --- /dev/null +++ b/src/link/Elf/SharedObject.zig @@ -0,0 +1,361 @@ +path: []const u8, +data: []const u8, +index: File.Index, + +header: ?elf.Elf64_Ehdr = null, +shdrs: std.ArrayListUnmanaged(ElfShdr) = .{}, +symtab: []align(1) const elf.Elf64_Sym = &[0]elf.Elf64_Sym{}, +strtab: []const u8 = &[0]u8{}, +/// Version symtab contains version strings of the symbols if present. +versyms: std.ArrayListUnmanaged(elf.Elf64_Versym) = .{}, +verstrings: std.ArrayListUnmanaged(u32) = .{}, + +dynamic_sect_index: ?u16 = null, +versym_sect_index: ?u16 = null, +verdef_sect_index: ?u16 = null, + +symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, +aliases: ?std.ArrayListUnmanaged(u32) = null, + +needed: bool, +alive: bool, + +output_symtab_size: Elf.SymtabSize = .{}, + +pub fn isSharedObject(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.DYN) return false; + return true; +} + +pub fn deinit(self: *SharedObject, allocator: Allocator) void { + self.versyms.deinit(allocator); + self.verstrings.deinit(allocator); + self.symbols.deinit(allocator); + if (self.aliases) |*aliases| aliases.deinit(allocator); + self.shdrs.deinit(allocator); +} + +pub fn parse(self: *SharedObject, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + var stream = std.io.fixedBufferStream(self.data); + const reader = stream.reader(); + + self.header = try reader.readStruct(elf.Elf64_Ehdr); + + var dynsym_index: ?u16 = null; + const shdrs = @as( + [*]align(1) const elf.Elf64_Shdr, + @ptrCast(self.data.ptr + self.header.?.e_shoff), + )[0..self.header.?.e_shnum]; + try self.shdrs.ensureTotalCapacityPrecise(gpa, shdrs.len); + + for (shdrs, 0..) |shdr, i| { + self.shdrs.appendAssumeCapacity(try ElfShdr.fromElf64Shdr(shdr)); + switch (shdr.sh_type) { + elf.SHT_DYNSYM => dynsym_index = @as(u16, @intCast(i)), + elf.SHT_DYNAMIC => self.dynamic_sect_index = @as(u16, @intCast(i)), + elf.SHT_GNU_VERSYM => self.versym_sect_index = @as(u16, @intCast(i)), + elf.SHT_GNU_VERDEF => self.verdef_sect_index = @as(u16, @intCast(i)), + else => {}, + } + } + + if (dynsym_index) |index| { + const shdr = self.shdrs.items[index]; + const symtab = 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 = self.shdrContents(@as(u16, @intCast(shdr.sh_link))); + } + + try self.parseVersions(elf_file); + try self.initSymtab(elf_file); +} + +fn parseVersions(self: *SharedObject, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + + try self.verstrings.resize(gpa, 2); + self.verstrings.items[elf.VER_NDX_LOCAL] = 0; + self.verstrings.items[elf.VER_NDX_GLOBAL] = 0; + + if (self.verdef_sect_index) |shndx| { + const verdefs = self.shdrContents(shndx); + const nverdefs = self.verdefNum(); + try self.verstrings.resize(gpa, self.verstrings.items.len + nverdefs); + + var i: u32 = 0; + var offset: u32 = 0; + while (i < nverdefs) : (i += 1) { + const verdef = @as(*align(1) const elf.Elf64_Verdef, @ptrCast(verdefs.ptr + offset)).*; + defer offset += verdef.vd_next; + if (verdef.vd_flags == elf.VER_FLG_BASE) continue; // Skip BASE entry + const vda_name = if (verdef.vd_cnt > 0) + @as(*align(1) const elf.Elf64_Verdaux, @ptrCast(verdefs.ptr + offset + verdef.vd_aux)).vda_name + else + 0; + self.verstrings.items[verdef.vd_ndx] = vda_name; + } + } + + try self.versyms.ensureTotalCapacityPrecise(gpa, self.symtab.len); + + if (self.versym_sect_index) |shndx| { + const versyms_raw = self.shdrContents(shndx); + const nversyms = @divExact(versyms_raw.len, @sizeOf(elf.Elf64_Versym)); + const versyms = @as([*]align(1) const elf.Elf64_Versym, @ptrCast(versyms_raw.ptr))[0..nversyms]; + for (versyms) |ver| { + const normalized_ver = if (ver & elf.VERSYM_VERSION >= self.verstrings.items.len - 1) + elf.VER_NDX_GLOBAL + else + ver; + self.versyms.appendAssumeCapacity(normalized_ver); + } + } else for (0..self.symtab.len) |_| { + self.versyms.appendAssumeCapacity(elf.VER_NDX_GLOBAL); + } +} + +fn initSymtab(self: *SharedObject, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + + try self.symbols.ensureTotalCapacityPrecise(gpa, self.symtab.len); + + for (self.symtab, 0..) |sym, i| { + const hidden = self.versyms.items[i] & elf.VERSYM_HIDDEN != 0; + const name = self.getString(sym.st_name); + // We need to garble up the name so that we don't pick this symbol + // during symbol resolution. Thank you GNU! + const off = if (hidden) blk: { + const full_name = try std.fmt.allocPrint(gpa, "{s}@{s}", .{ + name, + self.versionString(self.versyms.items[i]), + }); + defer gpa.free(full_name); + break :blk try elf_file.strtab.insert(gpa, full_name); + } else try elf_file.strtab.insert(gpa, name); + const gop = try elf_file.getOrCreateGlobal(off); + self.symbols.addOneAssumeCapacity().* = gop.index; + } +} + +pub fn resolveSymbols(self: *SharedObject, elf_file: *Elf) void { + for (self.globals(), 0..) |index, i| { + const esym_index = @as(u32, @intCast(i)); + const this_sym = self.symtab[esym_index]; + + 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 = this_sym.st_value; + global.atom_index = 0; + global.esym_index = esym_index; + global.version_index = self.versyms.items[esym_index]; + global.file_index = self.index; + } + } +} + +pub fn resetGlobals(self: *SharedObject, elf_file: *Elf) void { + for (self.globals()) |index| { + const global = elf_file.symbol(index); + const off = global.name_offset; + global.* = .{}; + global.name_offset = off; + } +} + +pub fn markLive(self: *SharedObject, elf_file: *Elf) void { + for (self.globals(), 0..) |index, i| { + const sym = self.symtab[i]; + if (sym.st_shndx != elf.SHN_UNDEF) continue; + + const global = elf_file.symbol(index); + const file = global.file(elf_file) orelse continue; + const should_drop = switch (file) { + .shared_object => |sh| !sh.needed and sym.st_bind() == elf.STB_WEAK, + else => false, + }; + if (!should_drop and !file.isAlive()) { + file.setAlive(); + file.markLive(elf_file); + } + } +} + +pub fn updateSymtabSize(self: *SharedObject, 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; + if (global.isLocal()) continue; + global.flags.output_symtab = true; + self.output_symtab_size.nglobals += 1; + } +} + +pub fn writeSymtab(self: *SharedObject, elf_file: *Elf, ctx: anytype) void { + 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; + global.setOutputSym(elf_file, &ctx.symtab[iglobal]); + iglobal += 1; + } +} + +pub fn globals(self: SharedObject) []const Symbol.Index { + return self.symbols.items; +} + +pub fn shdrContents(self: SharedObject, index: u16) []const u8 { + const shdr = self.shdrs.items[index]; + return self.data[shdr.sh_offset..][0..shdr.sh_size]; +} + +pub fn getString(self: SharedObject, 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 versionString(self: SharedObject, index: elf.Elf64_Versym) [:0]const u8 { + const off = self.verstrings.items[index & elf.VERSYM_VERSION]; + return self.getString(off); +} + +pub fn asFile(self: *SharedObject) File { + return .{ .shared_object = self }; +} + +fn dynamicTable(self: *SharedObject) []align(1) const elf.Elf64_Dyn { + const shndx = self.dynamic_sect_index orelse return &[0]elf.Elf64_Dyn{}; + const raw = self.shdrContents(shndx); + const num = @divExact(raw.len, @sizeOf(elf.Elf64_Dyn)); + return @as([*]align(1) const elf.Elf64_Dyn, @ptrCast(raw.ptr))[0..num]; +} + +fn verdefNum(self: *SharedObject) u32 { + const entries = self.dynamicTable(); + for (entries) |entry| switch (entry.d_tag) { + elf.DT_VERDEFNUM => return @as(u32, @intCast(entry.d_val)), + else => {}, + }; + return 0; +} + +pub fn soname(self: *SharedObject) []const u8 { + const entries = self.dynamicTable(); + for (entries) |entry| switch (entry.d_tag) { + elf.DT_SONAME => return self.getString(@as(u32, @intCast(entry.d_val))), + else => {}, + }; + return std.fs.path.basename(self.path); +} + +pub fn initSymbolAliases(self: *SharedObject, elf_file: *Elf) !void { + assert(self.aliases == null); + + const SortAlias = struct { + pub fn lessThan(ctx: *Elf, lhs: Symbol.Index, rhs: Symbol.Index) bool { + const lhs_sym = ctx.symbol(lhs).elfSym(ctx); + const rhs_sym = ctx.symbol(rhs).elfSym(ctx); + return lhs_sym.st_value < rhs_sym.st_value; + } + }; + + const gpa = elf_file.base.allocator; + var aliases = std.ArrayList(Symbol.Index).init(gpa); + defer aliases.deinit(); + try aliases.ensureTotalCapacityPrecise(self.globals().len); + + for (self.globals()) |index| { + const global = elf_file.symbol(index); + const global_file = global.file(elf_file) orelse continue; + if (global_file.index() != self.index) continue; + aliases.appendAssumeCapacity(index); + } + + std.mem.sort(u32, aliases.items, elf_file, SortAlias.lessThan); + + self.aliases = aliases.moveToUnmanaged(); +} + +pub fn symbolAliases(self: *SharedObject, index: u32, elf_file: *Elf) []const u32 { + assert(self.aliases != null); + + const symbol = elf_file.symbol(index).elfSym(elf_file); + const aliases = self.aliases.?; + + const start = for (aliases.items, 0..) |alias, i| { + const alias_sym = elf_file.symbol(alias).elfSym(elf_file); + if (symbol.st_value == alias_sym.st_value) break i; + } else aliases.items.len; + + const end = for (aliases.items[start..], 0..) |alias, i| { + const alias_sym = elf_file.symbol(alias).elfSym(elf_file); + if (symbol.st_value < alias_sym.st_value) break i + start; + } else aliases.items.len; + + return aliases.items[start..end]; +} + +pub fn format( + self: SharedObject, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = self; + _ = unused_fmt_string; + _ = options; + _ = writer; + @compileError("do not format shared objects directly"); +} + +pub fn fmtSymtab(self: SharedObject, elf_file: *Elf) std.fmt.Formatter(formatSymtab) { + return .{ .data = .{ + .shared = self, + .elf_file = elf_file, + } }; +} + +const FormatContext = struct { + shared: SharedObject, + 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 shared = ctx.shared; + try writer.writeAll(" globals\n"); + for (shared.symbols.items) |index| { + const global = ctx.elf_file.symbol(index); + try writer.print(" {}\n", .{global.fmt(ctx.elf_file)}); + } +} + +const SharedObject = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const elf = std.elf; +const log = std.log.scoped(.elf); +const mem = std.mem; + +const Allocator = mem.Allocator; +const Elf = @import("../Elf.zig"); +const ElfShdr = @import("Object.zig").ElfShdr; +const File = @import("file.zig").File; +const Symbol = @import("Symbol.zig"); diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index 022f8fbde1..63d37c3448 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -32,7 +32,7 @@ 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; + if (file_ptr == .shared_object) return symbol.elfSym(elf_file).st_shndx == elf.SHN_ABS; return !symbol.flags.import and symbol.atom(elf_file) == null and symbol.outputShndx() == null and file_ptr != .linker_defined; } @@ -52,8 +52,8 @@ pub fn isIFunc(symbol: Symbol, elf_file: *Elf) bool { 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; + const file_ptr = symbol.file(elf_file).?; + if (s_sym.st_type() == elf.STT_GNU_IFUNC and file_ptr == .shared_object) return elf.STT_FUNC; return s_sym.st_type(); } @@ -74,7 +74,7 @@ pub fn elfSym(symbol: Symbol, elf_file: *Elf) elf.Elf64_Sym { 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], + inline else => |x| return x.symtab[symbol.esym_index], } } @@ -88,23 +88,18 @@ pub fn symbolRank(symbol: Symbol, elf_file: *Elf) u32 { 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); - // } +pub fn address(symbol: Symbol, opts: struct { plt: bool = true }, elf_file: *Elf) u64 { + if (symbol.flags.has_copy_rel) { + return symbol.copyRelAddress(elf_file); + } + if (symbol.flags.has_plt and opts.plt) { + if (!symbol.flags.is_canonical and symbol.flags.has_got) { + // We have a non-lazy bound function pointer, use that! + return symbol.pltGotAddress(elf_file); + } + // Lazy-bound function it is! + return symbol.pltAddress(elf_file); + } return symbol.value; } @@ -115,6 +110,33 @@ pub fn gotAddress(symbol: Symbol, elf_file: *Elf) u64 { return entry.address(elf_file); } +pub fn pltGotAddress(symbol: Symbol, elf_file: *Elf) u64 { + if (!(symbol.flags.has_plt and symbol.flags.has_got and !symbol.flags.is_canonical)) return 0; + const extras = symbol.extra(elf_file).?; + const shdr = elf_file.shdrs.items[elf_file.plt_got_section_index.?]; + return shdr.sh_addr + extras.plt_got * 16; +} + +pub fn pltAddress(symbol: Symbol, elf_file: *Elf) u64 { + if (!symbol.flags.has_plt) return 0; + const extras = symbol.extra(elf_file).?; + const shdr = elf_file.shdrs.items[elf_file.plt_section_index.?]; + return shdr.sh_addr + extras.plt * 16 + PltSection.preamble_size; +} + +pub fn gotPltAddress(symbol: Symbol, elf_file: *Elf) u64 { + if (!symbol.flags.has_plt) return 0; + const extras = symbol.extra(elf_file).?; + const shdr = elf_file.shdrs.items[elf_file.got_plt_section_index.?]; + return shdr.sh_addr + extras.plt * 8 + GotPltSection.preamble_size; +} + +pub fn copyRelAddress(symbol: Symbol, elf_file: *Elf) u64 { + if (!symbol.flags.has_copy_rel) return 0; + const shdr = elf_file.shdrs.items[elf_file.copy_rel_section_index.?]; + return shdr.sh_addr + symbol.value; +} + const GetOrCreateGotEntryResult = struct { found_existing: bool, index: GotSection.Index, @@ -149,17 +171,18 @@ pub fn tlsDescAddress(symbol: Symbol, elf_file: *Elf) u64 { return entry.address(elf_file); } -// 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 dsoAlignment(symbol: Symbol, elf_file: *Elf) !u64 { + const file_ptr = symbol.file(elf_file) orelse return 0; + assert(file_ptr == .shared_object); + const shared_object = file_ptr.shared_object; + const esym = symbol.elfSym(elf_file); + const shdr = shared_object.shdrs.items[esym.st_shndx]; + const alignment = @max(1, shdr.sh_addralign); + return if (esym.st_value == 0) + alignment + else + @min(alignment, try std.math.powi(u64, 2, @ctz(esym.st_value))); +} pub fn addExtra(symbol: *Symbol, extras: Extra, elf_file: *Elf) !void { symbol.extra_index = try elf_file.addSymbolExtra(extras); @@ -183,22 +206,22 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void { 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; + if (file_ptr == .shared_object) 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.flags.has_copy_rel) break :blk elf_file.copy_rel_section_index.?; + if (file_ptr == .shared_object or esym.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.outputShndx() orelse elf.SHN_UNDEF; }; 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 (symbol.flags.has_copy_rel) break :blk symbol.address(.{}, elf_file); + if (file_ptr == .shared_object or esym.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.shdrs.items[st_shndx]; if (shdr.sh_flags & elf.SHF_TLS != 0 and file_ptr != .linker_defined) @@ -254,9 +277,10 @@ fn formatName( 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)}); + const file_ptr = symbol.file(elf_file).?; + assert(file_ptr == .shared_object); + const shared_object = file_ptr.shared_object; + try writer.print("@{s}", .{shared_object.versionString(symbol.version_index)}); }, } } @@ -312,9 +336,12 @@ pub const Flags = packed struct { /// Whether this symbol is weak. weak: bool = false, - /// Whether the symbol makes into the output symtab or not. + /// Whether the symbol makes into the output symtab. output_symtab: bool = false, + /// Whether the symbol has entry in dynamic symbol table. + has_dynamic: bool = false, + /// Whether the symbol contains GOT indirection. needs_got: bool = false, has_got: bool = false, @@ -328,7 +355,6 @@ pub const Flags = packed struct { /// Whether the symbol contains COPYREL directive. needs_copy_rel: bool = false, has_copy_rel: bool = false, - has_dynamic: bool = false, /// Whether the symbol contains TLSGD indirection. needs_tlsgd: bool = false, @@ -365,8 +391,10 @@ const Atom = @import("Atom.zig"); const Elf = @import("../Elf.zig"); const File = @import("file.zig").File; const GotSection = synthetic_sections.GotSection; +const GotPltSection = synthetic_sections.GotPltSection; const LinkerDefined = @import("LinkerDefined.zig"); -// const Object = @import("Object.zig"); -// const SharedObject = @import("SharedObject.zig"); +const Object = @import("Object.zig"); +const PltSection = synthetic_sections.PltSection; +const SharedObject = @import("SharedObject.zig"); const Symbol = @This(); const ZigModule = @import("ZigModule.zig"); diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index 937ca6696f..f8258bb884 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -2,7 +2,7 @@ pub const File = union(enum) { zig_module: *ZigModule, linker_defined: *LinkerDefined, object: *Object, - // shared_object: *SharedObject, + shared_object: *SharedObject, pub fn index(file: File) Index { return switch (file) { @@ -26,7 +26,7 @@ pub const File = union(enum) { .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), + .shared_object => |x| try writer.writeAll(x.path), } } @@ -49,8 +49,7 @@ pub const File = union(enum) { 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()) { + if (file == .shared_object or in_archive) break :blk switch (sym.st_bind()) { elf.STB_GLOBAL => 3, else => 4, }; @@ -92,6 +91,7 @@ pub const File = union(enum) { pub fn atoms(file: File) []const Atom.Index { return switch (file) { .linker_defined => unreachable, + .shared_object => unreachable, .zig_module => |x| x.atoms.items, .object => |x| x.atoms.items, }; @@ -100,6 +100,7 @@ pub const File = union(enum) { pub fn locals(file: File) []const Symbol.Index { return switch (file) { .linker_defined => unreachable, + .shared_object => unreachable, inline else => |x| x.locals(), }; } @@ -117,7 +118,7 @@ pub const File = union(enum) { zig_module: ZigModule, linker_defined: LinkerDefined, object: Object, - // shared_object: SharedObject, + shared_object: SharedObject, }; }; @@ -129,6 +130,6 @@ const Atom = @import("Atom.zig"); const Elf = @import("../Elf.zig"); const LinkerDefined = @import("LinkerDefined.zig"); const Object = @import("Object.zig"); -// const SharedObject = @import("SharedObject.zig"); +const SharedObject = @import("SharedObject.zig"); const Symbol = @import("Symbol.zig"); const ZigModule = @import("ZigModule.zig"); diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index 2b4191f3c1..de87dd5743 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -1,3 +1,225 @@ +pub const DynamicSection = struct { + soname: ?u32 = null, + needed: std.ArrayListUnmanaged(u32) = .{}, + rpath: u32 = 0, + + pub fn deinit(dt: *DynamicSection, allocator: Allocator) void { + dt.needed.deinit(allocator); + } + + pub fn addNeeded(dt: *DynamicSection, shared: *SharedObject, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + const off = try elf_file.dynstrtab.insert(gpa, shared.getSoname()); + try dt.needed.append(gpa, off); + } + + pub fn setRpath(dt: *DynamicSection, rpath_list: []const []const u8, elf_file: *Elf) !void { + if (rpath_list.len == 0) return; + const gpa = elf_file.base.allocator; + var rpath = std.ArrayList(u8).init(gpa); + defer rpath.deinit(); + for (rpath_list, 0..) |path, i| { + if (i > 0) try rpath.append(':'); + try rpath.appendSlice(path); + } + dt.rpath = try elf_file.dynstrtab.insert(gpa, rpath.items); + } + + pub fn setSoname(dt: *DynamicSection, soname: []const u8, elf_file: *Elf) !void { + dt.soname = try elf_file.dynstrtab.insert(elf_file.base.allocator, soname); + } + + fn getFlags(dt: DynamicSection, elf_file: *Elf) ?u64 { + _ = dt; + var flags: u64 = 0; + if (elf_file.base.options.z_now) { + flags |= elf.DF_BIND_NOW; + } + for (elf_file.got.entries.items) |entry| switch (entry.tag) { + .gottp => { + flags |= elf.DF_STATIC_TLS; + break; + }, + else => {}, + }; + if (elf_file.has_text_reloc) { + flags |= elf.DF_TEXTREL; + } + return if (flags > 0) flags else null; + } + + fn getFlags1(dt: DynamicSection, elf_file: *Elf) ?u64 { + _ = dt; + var flags_1: u64 = 0; + if (elf_file.base.options.z_now) { + flags_1 |= elf.DF_1_NOW; + } + if (elf_file.base.options.pie) { + flags_1 |= elf.DF_1_PIE; + } + // if (elf_file.base.options.z_nodlopen) { + // flags_1 |= elf.DF_1_NOOPEN; + // } + return if (flags_1 > 0) flags_1 else null; + } + + pub fn size(dt: DynamicSection, elf_file: *Elf) usize { + var nentries: usize = 0; + nentries += dt.needed.items.len; // NEEDED + if (dt.soname != null) nentries += 1; // SONAME + if (dt.rpath > 0) nentries += 1; // RUNPATH + if (elf_file.sectionByName(".init") != null) nentries += 1; // INIT + if (elf_file.sectionByName(".fini") != null) nentries += 1; // FINI + if (elf_file.sectionByName(".init_array") != null) nentries += 2; // INIT_ARRAY + if (elf_file.sectionByName(".fini_array") != null) nentries += 2; // FINI_ARRAY + if (elf_file.rela_dyn_section_index != null) nentries += 3; // RELA + if (elf_file.rela_plt_section_index != null) nentries += 3; // JMPREL + if (elf_file.got_plt_section_index != null) nentries += 1; // PLTGOT + nentries += 1; // HASH + if (elf_file.gnu_hash_section_index != null) nentries += 1; // GNU_HASH + if (elf_file.has_text_reloc) nentries += 1; // TEXTREL + nentries += 1; // SYMTAB + nentries += 1; // SYMENT + nentries += 1; // STRTAB + nentries += 1; // STRSZ + if (elf_file.versym_section_index != null) nentries += 1; // VERSYM + if (elf_file.verneed_section_index != null) nentries += 2; // VERNEED + if (dt.getFlags(elf_file) != null) nentries += 1; // FLAGS + if (dt.getFlags1(elf_file) != null) nentries += 1; // FLAGS_1 + if (!elf_file.isDynLib()) nentries += 1; // DEBUG + nentries += 1; // NULL + return nentries * @sizeOf(elf.Elf64_Dyn); + } + + pub fn write(dt: DynamicSection, elf_file: *Elf, writer: anytype) !void { + // NEEDED + for (dt.needed.items) |off| { + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_NEEDED, .d_val = off }); + } + + if (dt.soname) |off| { + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_SONAME, .d_val = off }); + } + + // RUNPATH + // TODO add option in Options to revert to old RPATH tag + if (dt.rpath > 0) { + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RUNPATH, .d_val = dt.rpath }); + } + + // INIT + if (elf_file.sectionByName(".init")) |shndx| { + const addr = elf_file.shdrs.items[shndx].sh_addr; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_INIT, .d_val = addr }); + } + + // FINI + if (elf_file.sectionByName(".fini")) |shndx| { + const addr = elf_file.shdrs.items[shndx].sh_addr; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FINI, .d_val = addr }); + } + + // INIT_ARRAY + if (elf_file.sectionByName(".init_array")) |shndx| { + const shdr = elf_file.shdrs.items[shndx]; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_INIT_ARRAY, .d_val = shdr.sh_addr }); + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_INIT_ARRAYSZ, .d_val = shdr.sh_size }); + } + + // FINI_ARRAY + if (elf_file.sectionByName(".fini_array")) |shndx| { + const shdr = elf_file.shdrs.items[shndx]; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FINI_ARRAY, .d_val = shdr.sh_addr }); + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FINI_ARRAYSZ, .d_val = shdr.sh_size }); + } + + // RELA + if (elf_file.rela_dyn_section_index) |shndx| { + const shdr = elf_file.shdrs.items[shndx]; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RELA, .d_val = shdr.sh_addr }); + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RELASZ, .d_val = shdr.sh_size }); + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_RELAENT, .d_val = shdr.sh_entsize }); + } + + // JMPREL + if (elf_file.rela_plt_section_index) |shndx| { + const shdr = elf_file.shdrs.items[shndx]; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_JMPREL, .d_val = shdr.sh_addr }); + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_PLTRELSZ, .d_val = shdr.sh_size }); + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_PLTREL, .d_val = elf.DT_RELA }); + } + + // PLTGOT + if (elf_file.got_plt_section_index) |shndx| { + const addr = elf_file.shdrs.items[shndx].sh_addr; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_PLTGOT, .d_val = addr }); + } + + { + assert(elf_file.hash_section_index != null); + const addr = elf_file.shdrs.items[elf_file.hash_section_index.?].sh_addr; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_HASH, .d_val = addr }); + } + + if (elf_file.gnu_hash_section_index) |shndx| { + const addr = elf_file.shdrs.items[shndx].sh_addr; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_GNU_HASH, .d_val = addr }); + } + + // TEXTREL + if (elf_file.has_text_reloc) { + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_TEXTREL, .d_val = 0 }); + } + + // SYMTAB + SYMENT + { + assert(elf_file.dynsymtab_section_index != null); + const shdr = elf_file.shdrs.items[elf_file.dynsymtab_section_index.?]; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_SYMTAB, .d_val = shdr.sh_addr }); + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_SYMENT, .d_val = shdr.sh_entsize }); + } + + // STRTAB + STRSZ + { + assert(elf_file.dynstrtab_section_index != null); + const shdr = elf_file.shdrs.items[elf_file.dynstrtab_section_index.?]; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_STRTAB, .d_val = shdr.sh_addr }); + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_STRSZ, .d_val = shdr.sh_size }); + } + + // VERSYM + if (elf_file.versym_section_index) |shndx| { + const addr = elf_file.shdrs.items[shndx].sh_addr; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_VERSYM, .d_val = addr }); + } + + // VERNEED + VERNEEDNUM + if (elf_file.verneed_section_index) |shndx| { + const addr = elf_file.shdrs.items[shndx].sh_addr; + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_VERNEED, .d_val = addr }); + try writer.writeStruct(elf.Elf64_Dyn{ + .d_tag = elf.DT_VERNEEDNUM, + .d_val = elf_file.verneed.verneed.items.len, + }); + } + + // FLAGS + if (dt.getFlags(elf_file)) |flags| { + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FLAGS, .d_val = flags }); + } + // FLAGS_1 + if (dt.getFlags1(elf_file)) |flags_1| { + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_FLAGS_1, .d_val = flags_1 }); + } + + // DEBUG + if (!elf_file.isDynLib()) try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_DEBUG, .d_val = 0 }); + + // NULL + try writer.writeStruct(elf.Elf64_Dyn{ .d_tag = elf.DT_NULL, .d_val = 0 }); + } +}; + pub const GotSection = struct { entries: std.ArrayListUnmanaged(Entry) = .{}, output_symtab_size: Elf.SymtabSize = .{}, @@ -72,44 +294,57 @@ pub const GotSection = struct { 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 addTlsLdSymbol(got: *GotSection, elf_file: *Elf) !void { + assert(got.flags.needs_tlsld); + const index = try got.allocateEntry(elf_file.base.allocator); + const entry = &got.entries.items[index]; + entry.tag = .tlsld; + entry.symbol_index = undefined; // unused + got.flags.needs_rela = true; + got.tlsld_index = index; + } - // 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 addTlsGdSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void { + const index = try got.allocateEntry(elf_file.base.allocator); + const entry = &got.entries.items[index]; + entry.tag = .tlsgd; + entry.symbol_index = sym_index; + const symbol = elf_file.symbol(sym_index); + if (symbol.flags.import or elf_file.isDynLib()) got.flags.needs_rela = true; + if (symbol.extra(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); + } - // 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 addGotTpSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void { + const index = try got.allocateEntry(elf_file.base.allocator); + const entry = &got.entries.items[index]; + entry.tag = .gottp; + entry.symbol_index = sym_index; + const symbol = elf_file.symbol(sym_index); + if (symbol.flags.import or elf_file.isDynLib()) got.flags.needs_rela = true; + if (symbol.extra(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); + } + + pub fn addTlsDescSymbol(got: *GotSection, sym_index: Symbol.Index, elf_file: *Elf) !void { + const index = try got.allocateEntry(elf_file.base.allocator); + const entry = &got.entries.items[index]; + entry.tag = .tlsdesc; + entry.symbol_index = sym_index; + const symbol = elf_file.symbol(sym_index); + got.flags.needs_rela = true; + if (symbol.extra(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); + } pub fn size(got: GotSection, elf_file: *Elf) usize { var s: usize = 0; @@ -119,260 +354,207 @@ pub const GotSection = struct { 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 entry_size: u16 = elf_file.archPtrWidthBytes(); - const endian = elf_file.base.options.target.cpu.arch.endian(); + const is_dyn_lib = elf_file.isDynLib(); + const apply_relocs = true; // TODO add user option for this + for (got.entries.items) |entry| { - const value = elf_file.symbol(entry.symbol_index).value; - switch (entry_size) { - 2 => try writer.writeInt(u16, @intCast(value), endian), - 4 => try writer.writeInt(u32, @intCast(value), endian), - 8 => try writer.writeInt(u64, @intCast(value), endian), - else => unreachable, + const symbol = elf_file.symbol(entry.symbol_index); + switch (entry.tag) { + .got => { + const value = blk: { + const value = symbol.address(.{ .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.base.options.pic and !symbol.isAbs(elf_file)) + break :blk if (apply_relocs) value else 0; + break :blk value; + }; + try writeInt(value, elf_file, writer); + }, + .tlsld => { + try writeInt(if (is_dyn_lib) @as(u64, 0) else 1, elf_file, writer); + try writeInt(0, elf_file, writer); + }, + .tlsgd => { + if (symbol.flags.import) { + try writeInt(0, elf_file, writer); + try writeInt(0, elf_file, writer); + } else { + try writeInt(if (is_dyn_lib) @as(u64, 0) else 1, elf_file, writer); + const offset = symbol.address(.{}, elf_file) - elf_file.dtpAddress(); + try writeInt(offset, elf_file, writer); + } + }, + .gottp => { + if (symbol.flags.import) { + try writeInt(0, elf_file, writer); + } else if (is_dyn_lib) { + const offset = if (apply_relocs) + symbol.address(.{}, elf_file) - elf_file.tlsAddress() + else + 0; + try writeInt(offset, elf_file, writer); + } else { + const offset = @as(i64, @intCast(symbol.address(.{}, elf_file))) - + @as(i64, @intCast(elf_file.tpAddress())); + try writeInt(offset, elf_file, writer); + } + }, + .tlsdesc => { + try writeInt(0, elf_file, writer); + try writeInt(0, elf_file, writer); + }, } } } - // 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; + fn writeInt(value: anytype, elf_file: *Elf, writer: anytype) !void { + const entry_size = elf_file.archPtrWidthBytes(); + const endian = elf_file.base.options.target.cpu.arch.endian(); + switch (entry_size) { + 2 => try writer.writeInt(u16, @intCast(value), endian), + 4 => try writer.writeInt(u32, @intCast(value), endian), + 8 => try writer.writeInt(u64, @intCast(value), endian), + else => unreachable, + } + } - // 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); - // }, + pub fn addRela(got: GotSection, elf_file: *Elf) !void { + const is_dyn_lib = elf_file.isDynLib(); + try elf_file.rela_dyn.ensureUnusedCapacity(elf_file.base.allocator, got.numRela(elf_file)); - // .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); - // } - // }, + for (got.entries.items) |entry| { + const symbol = switch (entry.tag) { + .tlsld => null, + inline else => elf_file.symbol(entry.symbol_index), + }; + const extra = if (symbol) |s| s.extra(elf_file).? else null; - // .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))); - // } - // }, + switch (entry.tag) { + .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.?.address(.{ .plt = false }, elf_file)), + }); + continue; + } + if (elf_file.base.options.pic and !symbol.?.isAbs(elf_file)) { + elf_file.addRelaDynAssumeCapacity(.{ + .offset = offset, + .type = elf.R_X86_64_RELATIVE, + .addend = @intCast(symbol.?.address(.{ .plt = false }, elf_file)), + }); + } + }, - // .tlsdesc => { - // try writer.writeIntLittle(u64, 0); - // try writer.writeIntLittle(u64, 0); - // }, - // } - // } + .tlsld => { + if (is_dyn_lib) { + const offset = entry.address(elf_file); + elf_file.addRelaDynAssumeCapacity(.{ + .offset = offset, + .type = elf.R_X86_64_DTPMOD64, + }); + } + }, - // if (got.emit_tlsld) { - // try writer.writeIntLittle(u64, if (is_shared) @as(u64, 0) else 1); - // try writer.writeIntLittle(u64, 0); - // } - // } + .tlsgd => { + const offset = symbol.?.tlsGdAddress(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_dyn_lib) { + elf_file.addRelaDynAssumeCapacity(.{ + .offset = offset, + .sym = extra.?.dynamic, + .type = elf.R_X86_64_DTPMOD64, + }); + } + }, - // 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)); + .gottp => { + const offset = symbol.?.gotTpAddress(elf_file); + if (symbol.?.flags.import) { + elf_file.addRelaDynAssumeCapacity(.{ + .offset = offset, + .sym = extra.?.dynamic, + .type = elf.R_X86_64_TPOFF64, + }); + } else if (is_dyn_lib) { + elf_file.addRelaDynAssumeCapacity(.{ + .offset = offset, + .type = elf.R_X86_64_TPOFF64, + .addend = @intCast(symbol.?.address(.{}, elf_file) - elf_file.tlsAddress()), + }); + } + }, - // for (got.symbols.items) |sym| { - // const symbol = elf_file.getSymbol(sym.getIndex()); - // const extra = symbol.getExtra(elf_file).?; + .tlsdesc => { + const offset = symbol.?.tlsDescAddress(elf_file); + elf_file.addRelaDynAssumeCapacity(.{ + .offset = offset, + .sym = extra.?.dynamic, + .type = elf.R_X86_64_TLSDESC, + }); + }, + } + } + } - // switch (sym) { - // .got => { - // const offset = symbol.gotAddress(elf_file); + pub fn numRela(got: GotSection, elf_file: *Elf) usize { + const is_dyn_lib = elf_file.isDynLib(); + var num: usize = 0; + for (got.entries.items) |entry| { + const symbol = switch (entry.tag) { + .tlsld => null, + inline else => elf_file.symbol(entry.symbol_index), + }; + switch (entry.tag) { + .got => if (symbol.?.flags.import or + symbol.?.isIFunc(elf_file) or (elf_file.base.options.pic and !symbol.?.isAbs(elf_file))) + { + num += 1; + }, - // if (symbol.flags.import) { - // elf_file.addRelaDynAssumeCapacity(.{ - // .offset = offset, - // .sym = extra.dynamic, - // .type = elf.R_X86_64_GLOB_DAT, - // }); - // continue; - // } + .tlsld => if (is_dyn_lib) { + num += 1; + }, - // 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; - // } + .tlsgd => if (symbol.?.flags.import) { + num += 2; + } else if (is_dyn_lib) { + num += 1; + }, - // 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)), - // }); - // } - // }, + .gottp => if (symbol.?.flags.import or is_dyn_lib) { + num += 1; + }, - // .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; - // } + .tlsdesc => num += 1, + } + } + return num; + } pub fn updateSymtabSize(got: *GotSection, elf_file: *Elf) void { _ = elf_file; @@ -382,8 +564,11 @@ pub const GotSection = struct { pub fn updateStrtab(got: GotSection, elf_file: *Elf) !void { const gpa = elf_file.base.allocator; for (got.entries.items) |entry| { - const symbol = elf_file.symbol(entry.symbol_index); - const name = try std.fmt.allocPrint(gpa, "{s}${s}", .{ symbol.name(elf_file), @tagName(entry.tag) }); + const symbol_name = switch (entry.tag) { + .tlsld => "", + inline else => elf_file.symbol(entry.symbol_index).name(elf_file), + }; + const name = try std.fmt.allocPrint(gpa, "{s}${s}", .{ symbol_name, @tagName(entry.tag) }); defer gpa.free(name); _ = try elf_file.strtab.insert(gpa, name); } @@ -392,14 +577,18 @@ pub const GotSection = struct { 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 symbol = elf_file.symbol(entry.symbol_index); - const name = try std.fmt.allocPrint(gpa, "{s}${s}", .{ symbol.name(elf_file), @tagName(entry.tag) }); + const symbol = switch (entry.tag) { + .tlsld => null, + inline else => elf_file.symbol(entry.symbol_index), + }; + const symbol_name = switch (entry.tag) { + .tlsld => "", + inline else => symbol.?.name(elf_file), + }; + const name = try std.fmt.allocPrint(gpa, "{s}${s}", .{ symbol_name, @tagName(entry.tag) }); 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_value = entry.address(elf_file); const st_size: u64 = entry.len() * elf_file.archPtrWidthBytes(); ctx.symtab[ilocal] = .{ .st_name = st_name, @@ -443,12 +632,697 @@ pub const GotSection = struct { } }; +pub const PltSection = struct { + symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, + output_symtab_size: Elf.SymtabSize = .{}, + + pub const preamble_size = 32; + + pub fn deinit(plt: *PltSection, allocator: Allocator) void { + plt.symbols.deinit(allocator); + } + + pub fn addSymbol(plt: *PltSection, sym_index: Symbol.Index, elf_file: *Elf) !void { + const index = @as(u32, @intCast(plt.symbols.items.len)); + const symbol = elf_file.symbol(sym_index); + if (symbol.extra(elf_file)) |extra| { + var new_extra = extra; + new_extra.plt = index; + symbol.setExtra(new_extra, elf_file); + } else try symbol.addExtra(.{ .plt = index }, elf_file); + try plt.symbols.append(elf_file.base.allocator, sym_index); + } + + pub fn size(plt: PltSection) usize { + return preamble_size + plt.symbols.items.len * 16; + } + + pub fn write(plt: PltSection, elf_file: *Elf, writer: anytype) !void { + const plt_addr = elf_file.shdrs.items[elf_file.plt_section_index.?].sh_addr; + const got_plt_addr = elf_file.shdrs.items[elf_file.got_plt_section_index.?].sh_addr; + var preamble = [_]u8{ + 0xf3, 0x0f, 0x1e, 0xfa, // endbr64 + 0x41, 0x53, // push r11 + 0xff, 0x35, 0x00, 0x00, 0x00, 0x00, // push qword ptr [rip] -> .got.plt[1] + 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip] -> .got.plt[2] + }; + var disp = @as(i64, @intCast(got_plt_addr + 8)) - @as(i64, @intCast(plt_addr + 8)) - 4; + mem.writeIntLittle(i32, preamble[8..][0..4], @as(i32, @intCast(disp))); + disp = @as(i64, @intCast(got_plt_addr + 16)) - @as(i64, @intCast(plt_addr + 14)) - 4; + mem.writeIntLittle(i32, preamble[14..][0..4], @as(i32, @intCast(disp))); + try writer.writeAll(&preamble); + try writer.writeByteNTimes(0xcc, preamble_size - preamble.len); + + for (plt.symbols.items, 0..) |sym_index, i| { + const sym = elf_file.symbol(sym_index); + const target_addr = sym.gotPltAddress(elf_file); + const source_addr = sym.pltAddress(elf_file); + disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 12)) - 4; + var entry = [_]u8{ + 0xf3, 0x0f, 0x1e, 0xfa, // endbr64 + 0x41, 0xbb, 0x00, 0x00, 0x00, 0x00, // mov r11d, N + 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip] -> .got.plt[N] + }; + mem.writeIntLittle(i32, entry[6..][0..4], @as(i32, @intCast(i))); + mem.writeIntLittle(i32, entry[12..][0..4], @as(i32, @intCast(disp))); + try writer.writeAll(&entry); + } + } + + pub fn addRela(plt: PltSection, elf_file: *Elf) !void { + try elf_file.rela_plt.ensureUnusedCapacity(elf_file.base.allocator, plt.numRela()); + for (plt.symbols.items) |sym_index| { + const sym = elf_file.symbol(sym_index); + assert(sym.flags.import); + const extra = sym.extra(elf_file).?; + const r_offset = sym.gotPltAddress(elf_file); + const r_sym: u64 = extra.dynamic; + const r_type: u32 = elf.R_X86_64_JUMP_SLOT; + elf_file.rela_plt.appendAssumeCapacity(.{ + .r_offset = r_offset, + .r_info = (r_sym << 32) | r_type, + .r_addend = 0, + }); + } + } + + pub fn numRela(plt: PltSection) usize { + return plt.symbols.items.len; + } + + pub fn updateSymtabSize(plt: *PltSection, elf_file: *Elf) void { + _ = elf_file; + plt.output_symtab_size.nlocals = @as(u32, @intCast(plt.symbols.items.len)); + } + + pub fn updateStrtab(plt: PltSection, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + for (plt.symbols.items) |sym_index| { + const sym = elf_file.symbol(sym_index); + const name = try std.fmt.allocPrint(gpa, "{s}$plt", .{sym.name(elf_file)}); + defer gpa.free(name); + _ = try elf_file.strtab.insert(gpa, name); + } + } + + pub fn writeSymtab(plt: PltSection, elf_file: *Elf, ctx: anytype) !void { + const gpa = elf_file.base.allocator; + + var ilocal = ctx.ilocal; + for (plt.symbols.items) |sym_index| { + const sym = elf_file.symbol(sym_index); + const name = try std.fmt.allocPrint(gpa, "{s}$plt", .{sym.name(elf_file)}); + defer gpa.free(name); + const st_name = try elf_file.strtab.insert(gpa, name); + ctx.symtab[ilocal] = .{ + .st_name = st_name, + .st_info = elf.STT_FUNC, + .st_other = 0, + .st_shndx = elf_file.plt_section_index.?, + .st_value = sym.pltAddress(elf_file), + .st_size = 16, + }; + ilocal += 1; + } + } +}; + +pub const GotPltSection = struct { + pub const preamble_size = 24; + + pub fn size(got_plt: GotPltSection, elf_file: *Elf) usize { + _ = got_plt; + return preamble_size + elf_file.plt.symbols.items.len * 8; + } + + pub fn write(got_plt: GotPltSection, elf_file: *Elf, writer: anytype) !void { + _ = got_plt; + { + // [0]: _DYNAMIC + const symbol = elf_file.symbol(elf_file.dynamic_index.?); + try writer.writeIntLittle(u64, symbol.value); + } + // [1]: 0x0 + // [2]: 0x0 + try writer.writeIntLittle(u64, 0x0); + try writer.writeIntLittle(u64, 0x0); + if (elf_file.plt_section_index) |shndx| { + const plt_addr = elf_file.shdrs.items[shndx].sh_addr; + for (0..elf_file.plt.symbols.items.len) |_| { + // [N]: .plt + try writer.writeIntLittle(u64, plt_addr); + } + } + } +}; + +pub const PltGotSection = struct { + symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, + output_symtab_size: Elf.SymtabSize = .{}, + + pub fn deinit(plt_got: *PltGotSection, allocator: Allocator) void { + plt_got.symbols.deinit(allocator); + } + + pub fn addSymbol(plt_got: *PltGotSection, sym_index: Symbol.Index, elf_file: *Elf) !void { + const index = @as(u32, @intCast(plt_got.symbols.items.len)); + const symbol = elf_file.symbol(sym_index); + if (symbol.extra(elf_file)) |extra| { + var new_extra = extra; + new_extra.plt_got = index; + symbol.setExtra(new_extra, elf_file); + } else try symbol.addExtra(.{ .plt_got = index }, elf_file); + try plt_got.symbols.append(elf_file.base.allocator, sym_index); + } + + pub fn size(plt_got: PltGotSection) usize { + return plt_got.symbols.items.len * 16; + } + + pub fn write(plt_got: PltGotSection, elf_file: *Elf, writer: anytype) !void { + for (plt_got.symbols.items) |sym_index| { + const sym = elf_file.symbol(sym_index); + const target_addr = sym.gotAddress(elf_file); + const source_addr = sym.pltGotAddress(elf_file); + const disp = @as(i64, @intCast(target_addr)) - @as(i64, @intCast(source_addr + 6)) - 4; + var entry = [_]u8{ + 0xf3, 0x0f, 0x1e, 0xfa, // endbr64 + 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip] -> .got[N] + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + }; + mem.writeIntLittle(i32, entry[6..][0..4], @as(i32, @intCast(disp))); + try writer.writeAll(&entry); + } + } + + pub fn updateSymtabSize(plt_got: *PltGotSection, elf_file: *Elf) void { + _ = elf_file; + plt_got.output_symtab_size.nlocals = @as(u32, @intCast(plt_got.symbols.items.len)); + } + + pub fn updateStrtab(plt_got: PltGotSection, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + for (plt_got.symbols.items) |sym_index| { + const sym = elf_file.symbol(sym_index); + const name = try std.fmt.allocPrint(gpa, "{s}$pltgot", .{sym.name(elf_file)}); + defer gpa.free(name); + _ = try elf_file.strtab.insert(gpa, name); + } + } + + pub fn writeSymtab(plt_got: PltGotSection, elf_file: *Elf, ctx: anytype) !void { + const gpa = elf_file.base.allocator; + var ilocal = ctx.ilocal; + for (plt_got.symbols.items) |sym_index| { + const sym = elf_file.symbol(sym_index); + const name = try std.fmt.allocPrint(gpa, "{s}$pltgot", .{sym.name(elf_file)}); + defer gpa.free(name); + const st_name = try elf_file.strtab.insert(gpa, name); + ctx.symtab[ilocal] = .{ + .st_name = st_name, + .st_info = elf.STT_FUNC, + .st_other = 0, + .st_shndx = elf_file.plt_got_section_index.?, + .st_value = sym.pltGotAddress(elf_file), + .st_size = 16, + }; + ilocal += 1; + } + } +}; + +pub const CopyRelSection = struct { + symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, + + pub fn deinit(copy_rel: *CopyRelSection, allocator: Allocator) void { + copy_rel.symbols.deinit(allocator); + } + + pub fn addSymbol(copy_rel: *CopyRelSection, sym_index: Symbol.Index, elf_file: *Elf) !void { + const index = @as(u32, @intCast(copy_rel.symbols.items.len)); + const symbol = elf_file.symbol(sym_index); + symbol.flags.import = true; + symbol.flags.@"export" = true; + symbol.flags.has_copy_rel = true; + symbol.flags.weak = false; + + if (symbol.extra(elf_file)) |extra| { + var new_extra = extra; + new_extra.copy_rel = index; + symbol.setExtra(new_extra, elf_file); + } else try symbol.addExtra(.{ .copy_rel = index }, elf_file); + try copy_rel.symbols.append(elf_file.base.allocator, sym_index); + + const shared_object = symbol.file(elf_file).?.shared_object; + if (shared_object.aliases == null) { + try shared_object.initSymbolAliases(elf_file); + } + + const aliases = shared_object.symbolAliases(sym_index, elf_file); + for (aliases) |alias| { + if (alias == sym_index) continue; + const alias_sym = elf_file.symbol(alias); + alias_sym.flags.import = true; + alias_sym.flags.@"export" = true; + alias_sym.flags.has_copy_rel = true; + alias_sym.flags.needs_copy_rel = true; + alias_sym.flags.weak = false; + try elf_file.dynsym.addSymbol(alias, elf_file); + } + } + + pub fn updateSectionSize(copy_rel: CopyRelSection, shndx: u16, elf_file: *Elf) !void { + const shdr = &elf_file.shdrs.items[shndx]; + for (copy_rel.symbols.items) |sym_index| { + const symbol = elf_file.symbol(sym_index); + const shared_object = symbol.file(elf_file).?.shared_object; + const alignment = try symbol.dsoAlignment(elf_file); + symbol.value = mem.alignForward(u64, shdr.sh_size, alignment); + shdr.sh_addralign = @max(shdr.sh_addralign, alignment); + shdr.sh_size = symbol.value + symbol.elfSym(elf_file).st_size; + + const aliases = shared_object.symbolAliases(sym_index, elf_file); + for (aliases) |alias| { + if (alias == sym_index) continue; + const alias_sym = elf_file.symbol(alias); + alias_sym.value = symbol.value; + } + } + } + + pub fn addRela(copy_rel: CopyRelSection, elf_file: *Elf) !void { + try elf_file.rela_dyn.ensureUnusedCapacity(elf_file.base.allocator, copy_rel.numRela()); + for (copy_rel.symbols.items) |sym_index| { + const sym = elf_file.symbol(sym_index); + assert(sym.flags.import and sym.flags.has_copy_rel); + const extra = sym.extra(elf_file).?; + elf_file.addRelaDynAssumeCapacity(.{ + .offset = sym.address(.{}, elf_file), + .sym = extra.dynamic, + .type = elf.R_X86_64_COPY, + }); + } + } + + pub fn numRela(copy_rel: CopyRelSection) usize { + return copy_rel.symbols.items.len; + } +}; + +pub const DynsymSection = struct { + entries: std.ArrayListUnmanaged(Entry) = .{}, + + pub const Entry = struct { + /// Index of the symbol which gets privilege of getting a dynamic treatment + symbol_index: Symbol.Index, + /// Offset into .dynstrtab + off: u32, + }; + + pub fn deinit(dynsym: *DynsymSection, allocator: Allocator) void { + dynsym.entries.deinit(allocator); + } + + pub fn addSymbol(dynsym: *DynsymSection, sym_index: Symbol.Index, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + const index = @as(u32, @intCast(dynsym.entries.items.len + 1)); + const sym = elf_file.symbol(sym_index); + sym.flags.has_dynamic = true; + if (sym.extra(elf_file)) |extra| { + var new_extra = extra; + new_extra.dynamic = index; + sym.setExtra(new_extra, elf_file); + } else try sym.addExtra(.{ .dynamic = index }, elf_file); + const off = try elf_file.dynstrtab.insert(gpa, sym.name(elf_file)); + try dynsym.entries.append(gpa, .{ .symbol_index = sym_index, .off = off }); + } + + pub fn sort(dynsym: *DynsymSection, elf_file: *Elf) void { + const Sort = struct { + pub fn lessThan(ctx: *Elf, lhs: Entry, rhs: Entry) bool { + const lhs_sym = ctx.symbol(lhs.symbol_index); + const rhs_sym = ctx.symbol(rhs.symbol_index); + + if (lhs_sym.flags.@"export" != rhs_sym.flags.@"export") { + return rhs_sym.flags.@"export"; + } + + // TODO cache hash values + const nbuckets = ctx.gnu_hash.num_buckets; + const lhs_hash = GnuHashSection.hasher(lhs_sym.name(ctx)) % nbuckets; + const rhs_hash = GnuHashSection.hasher(rhs_sym.name(ctx)) % nbuckets; + + if (lhs_hash == rhs_hash) + return lhs_sym.extra(ctx).?.dynamic < rhs_sym.extra(ctx).?.dynamic; + return lhs_hash < rhs_hash; + } + }; + + var num_exports: u32 = 0; + for (dynsym.entries.items) |entry| { + const sym = elf_file.symbol(entry.symbol_index); + if (sym.flags.@"export") num_exports += 1; + } + + elf_file.gnu_hash.num_buckets = @divTrunc(num_exports, GnuHashSection.load_factor) + 1; + + std.mem.sort(Entry, dynsym.entries.items, elf_file, Sort.lessThan); + + for (dynsym.entries.items, 1..) |entry, index| { + const sym = elf_file.symbol(entry.symbol_index); + var extra = sym.extra(elf_file).?; + extra.dynamic = @as(u32, @intCast(index)); + sym.setExtra(extra, elf_file); + } + } + + pub fn size(dynsym: DynsymSection) usize { + return dynsym.count() * @sizeOf(elf.Elf64_Sym); + } + + pub fn count(dynsym: DynsymSection) u32 { + return @as(u32, @intCast(dynsym.entries.items.len + 1)); + } + + pub fn write(dynsym: DynsymSection, elf_file: *Elf, writer: anytype) !void { + try writer.writeStruct(Elf.null_sym); + for (dynsym.entries.items) |entry| { + const sym = elf_file.symbol(entry.symbol_index); + var out_sym: elf.Elf64_Sym = Elf.null_sym; + sym.setOutputSym(elf_file, &out_sym); + out_sym.st_name = entry.off; + try writer.writeStruct(out_sym); + } + } +}; + +pub const HashSection = struct { + buffer: std.ArrayListUnmanaged(u8) = .{}, + + pub fn deinit(hs: *HashSection, allocator: Allocator) void { + hs.buffer.deinit(allocator); + } + + pub fn generate(hs: *HashSection, elf_file: *Elf) !void { + if (elf_file.dynsym.count() == 1) return; + + const gpa = elf_file.base.allocator; + const nsyms = elf_file.dynsym.count(); + + var buckets = try gpa.alloc(u32, nsyms); + defer gpa.free(buckets); + @memset(buckets, 0); + + var chains = try gpa.alloc(u32, nsyms); + defer gpa.free(chains); + @memset(chains, 0); + + for (elf_file.dynsym.entries.items, 1..) |entry, i| { + const name = elf_file.dynstrtab.getAssumeExists(entry.off); + const hash = hasher(name) % buckets.len; + chains[@as(u32, @intCast(i))] = buckets[hash]; + buckets[hash] = @as(u32, @intCast(i)); + } + + try hs.buffer.ensureTotalCapacityPrecise(gpa, (2 + nsyms * 2) * 4); + hs.buffer.writer(gpa).writeIntLittle(u32, @as(u32, @intCast(nsyms))) catch unreachable; + hs.buffer.writer(gpa).writeIntLittle(u32, @as(u32, @intCast(nsyms))) catch unreachable; + hs.buffer.writer(gpa).writeAll(mem.sliceAsBytes(buckets)) catch unreachable; + hs.buffer.writer(gpa).writeAll(mem.sliceAsBytes(chains)) catch unreachable; + } + + pub inline fn size(hs: HashSection) usize { + return hs.buffer.items.len; + } + + pub fn hasher(name: [:0]const u8) u32 { + var h: u32 = 0; + var g: u32 = 0; + for (name) |c| { + h = (h << 4) + c; + g = h & 0xf0000000; + if (g > 0) h ^= g >> 24; + h &= ~g; + } + return h; + } +}; + +pub const GnuHashSection = struct { + num_buckets: u32 = 0, + num_bloom: u32 = 1, + num_exports: u32 = 0, + + pub const load_factor = 8; + pub const header_size = 16; + pub const bloom_shift = 26; + + fn getExports(elf_file: *Elf) []const DynsymSection.Entry { + const start = for (elf_file.dynsym.entries.items, 0..) |entry, i| { + const sym = elf_file.symbol(entry.symbol_index); + if (sym.flags.@"export") break i; + } else elf_file.dynsym.entries.items.len; + return elf_file.dynsym.entries.items[start..]; + } + + inline fn bitCeil(x: u64) u64 { + if (@popCount(x) == 1) return x; + return @as(u64, @intCast(@as(u128, 1) << (64 - @clz(x)))); + } + + pub fn calcSize(hash: *GnuHashSection, elf_file: *Elf) !void { + hash.num_exports = @as(u32, @intCast(getExports(elf_file).len)); + if (hash.num_exports > 0) { + const num_bits = hash.num_exports * 12; + hash.num_bloom = @as(u32, @intCast(bitCeil(@divTrunc(num_bits, 64)))); + } + } + + pub fn size(hash: GnuHashSection) usize { + return header_size + hash.num_bloom * 8 + hash.num_buckets * 4 + hash.num_exports * 4; + } + + pub fn write(hash: GnuHashSection, elf_file: *Elf, writer: anytype) !void { + const exports = getExports(elf_file); + const export_off = elf_file.dynsym.count() - hash.num_exports; + + var counting = std.io.countingWriter(writer); + const cwriter = counting.writer(); + + try cwriter.writeIntLittle(u32, hash.num_buckets); + try cwriter.writeIntLittle(u32, export_off); + try cwriter.writeIntLittle(u32, hash.num_bloom); + try cwriter.writeIntLittle(u32, bloom_shift); + + const gpa = elf_file.base.allocator; + const hashes = try gpa.alloc(u32, exports.len); + defer gpa.free(hashes); + const indices = try gpa.alloc(u32, exports.len); + defer gpa.free(indices); + + // Compose and write the bloom filter + const bloom = try gpa.alloc(u64, hash.num_bloom); + defer gpa.free(bloom); + @memset(bloom, 0); + + for (exports, 0..) |entry, i| { + const sym = elf_file.symbol(entry.symbol_index); + const h = hasher(sym.name(elf_file)); + hashes[i] = h; + indices[i] = h % hash.num_buckets; + const idx = @divTrunc(h, 64) % hash.num_bloom; + bloom[idx] |= @as(u64, 1) << @as(u6, @intCast(h % 64)); + bloom[idx] |= @as(u64, 1) << @as(u6, @intCast((h >> bloom_shift) % 64)); + } + + try cwriter.writeAll(mem.sliceAsBytes(bloom)); + + // Fill in the hash bucket indices + const buckets = try gpa.alloc(u32, hash.num_buckets); + defer gpa.free(buckets); + @memset(buckets, 0); + + for (0..hash.num_exports) |i| { + if (buckets[indices[i]] == 0) { + buckets[indices[i]] = @as(u32, @intCast(i + export_off)); + } + } + + try cwriter.writeAll(mem.sliceAsBytes(buckets)); + + // Finally, write the hash table + const table = try gpa.alloc(u32, hash.num_exports); + defer gpa.free(table); + @memset(table, 0); + + for (0..hash.num_exports) |i| { + const h = hashes[i]; + if (i == exports.len - 1 or indices[i] != indices[i + 1]) { + table[i] = h | 1; + } else { + table[i] = h & ~@as(u32, 1); + } + } + + try cwriter.writeAll(mem.sliceAsBytes(table)); + + assert(counting.bytes_written == hash.size()); + } + + pub fn hasher(name: [:0]const u8) u32 { + var h: u32 = 5381; + for (name) |c| { + h = (h << 5) +% h +% c; + } + return h; + } +}; + +pub const VerneedSection = struct { + verneed: std.ArrayListUnmanaged(elf.Elf64_Verneed) = .{}, + vernaux: std.ArrayListUnmanaged(elf.Elf64_Vernaux) = .{}, + index: elf.Elf64_Versym = elf.VER_NDX_GLOBAL + 1, + + pub fn deinit(vern: *VerneedSection, allocator: Allocator) void { + vern.verneed.deinit(allocator); + vern.vernaux.deinit(allocator); + } + + pub fn generate(vern: *VerneedSection, elf_file: *Elf) !void { + const dynsyms = elf_file.dynsym.entries.items; + var versyms = elf_file.versym.items; + + const VersionedSymbol = struct { + /// Index in the output version table + index: usize, + /// Index of the defining this symbol version shared object file + shared_object: File.Index, + /// Version index + version_index: elf.Elf64_Versym, + + fn soname(this: @This(), ctx: *Elf) []const u8 { + const shared_object = ctx.file(this.shared_object).?.shared_object; + return shared_object.soname(); + } + + fn versionString(this: @This(), ctx: *Elf) [:0]const u8 { + const shared_object = ctx.file(this.shared_object).?.shared_object; + return shared_object.versionString(this.version_index); + } + + pub fn lessThan(ctx: *Elf, lhs: @This(), rhs: @This()) bool { + if (lhs.shared_object == rhs.shared_object) return lhs.version_index < rhs.version_index; + return mem.lessThan(u8, lhs.soname(ctx), rhs.soname(ctx)); + } + }; + + const gpa = elf_file.base.allocator; + var verneed = std.ArrayList(VersionedSymbol).init(gpa); + defer verneed.deinit(); + try verneed.ensureTotalCapacity(dynsyms.len); + + for (dynsyms, 1..) |entry, i| { + const symbol = elf_file.symbol(entry.symbol_index); + if (symbol.flags.import and symbol.version_index & elf.VERSYM_VERSION > elf.VER_NDX_GLOBAL) { + const shared_object = symbol.file(elf_file).?.shared_object; + verneed.appendAssumeCapacity(.{ + .index = i, + .shared_object = shared_object.index, + .version_index = symbol.version_index, + }); + } + } + + mem.sort(VersionedSymbol, verneed.items, elf_file, VersionedSymbol.lessThan); + + var last = verneed.items[0]; + var last_verneed = try vern.addVerneed(last.soname(elf_file), elf_file); + var last_vernaux = try vern.addVernaux(last_verneed, last.versionString(elf_file), elf_file); + versyms[last.index] = last_vernaux.vna_other; + + for (verneed.items[1..]) |ver| { + if (ver.shared_object == last.shared_object) { + if (ver.version_index != last.version_index) { + last_vernaux = try vern.addVernaux(last_verneed, ver.versionString(elf_file), elf_file); + } + } else { + last_verneed = try vern.addVerneed(ver.soname(elf_file), elf_file); + last_vernaux = try vern.addVernaux(last_verneed, ver.versionString(elf_file), elf_file); + } + last = ver; + versyms[ver.index] = last_vernaux.vna_other; + } + + // Fixup offsets + var count: usize = 0; + var verneed_off: u32 = 0; + var vernaux_off: u32 = @as(u32, @intCast(vern.verneed.items.len)) * @sizeOf(elf.Elf64_Verneed); + for (vern.verneed.items, 0..) |*vsym, vsym_i| { + if (vsym_i < vern.verneed.items.len - 1) vsym.vn_next = @sizeOf(elf.Elf64_Verneed); + vsym.vn_aux = vernaux_off - verneed_off; + var inner_off: u32 = 0; + for (vern.vernaux.items[count..][0..vsym.vn_cnt], 0..) |*vaux, vaux_i| { + if (vaux_i < vsym.vn_cnt - 1) vaux.vna_next = @sizeOf(elf.Elf64_Vernaux); + inner_off += @sizeOf(elf.Elf64_Vernaux); + } + vernaux_off += inner_off; + verneed_off += @sizeOf(elf.Elf64_Verneed); + count += vsym.vn_cnt; + } + } + + fn addVerneed(vern: *VerneedSection, soname: []const u8, elf_file: *Elf) !*elf.Elf64_Verneed { + const gpa = elf_file.base.allocator; + const sym = try vern.verneed.addOne(gpa); + sym.* = .{ + .vn_version = 1, + .vn_cnt = 0, + .vn_file = try elf_file.dynstrtab.insert(gpa, soname), + .vn_aux = 0, + .vn_next = 0, + }; + return sym; + } + + fn addVernaux( + vern: *VerneedSection, + verneed_sym: *elf.Elf64_Verneed, + version: [:0]const u8, + elf_file: *Elf, + ) !elf.Elf64_Vernaux { + const gpa = elf_file.base.allocator; + const sym = try vern.vernaux.addOne(gpa); + sym.* = .{ + .vna_hash = HashSection.hasher(version), + .vna_flags = 0, + .vna_other = vern.index, + .vna_name = try elf_file.dynstrtab.insert(gpa, version), + .vna_next = 0, + }; + verneed_sym.vn_cnt += 1; + vern.index += 1; + return sym.*; + } + + pub fn size(vern: VerneedSection) usize { + return vern.verneed.items.len * @sizeOf(elf.Elf64_Verneed) + vern.vernaux.items.len * @sizeOf(elf.Elf64_Vernaux); + } + + pub fn write(vern: VerneedSection, writer: anytype) !void { + try writer.writeAll(mem.sliceAsBytes(vern.verneed.items)); + try writer.writeAll(mem.sliceAsBytes(vern.vernaux.items)); + } +}; + const assert = std.debug.assert; const builtin = @import("builtin"); const elf = std.elf; +const mem = std.mem; const log = std.log.scoped(.link); const std = @import("std"); const Allocator = std.mem.Allocator; const Elf = @import("../Elf.zig"); +const File = @import("file.zig").File; +const SharedObject = @import("SharedObject.zig"); const Symbol = @import("Symbol.zig");