diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 4c53898df8..a72c96c51e 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -1664,6 +1664,8 @@ pub const EM = enum(u16) { } }; +pub const GRP_COMDAT = 1; + /// Section data should be writable during execution. pub const SHF_WRITE = 0x1; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index f4b9b5ff33..d210292bf9 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -18,12 +18,13 @@ shared_objects: std.ArrayListUnmanaged(File.Index) = .{}, /// Same order as in the file. shdrs: std.ArrayListUnmanaged(elf.Elf64_Shdr) = .{}, /// Given index to a section, pulls index of containing phdr if any. -phdr_to_shdr_table: std.AutoHashMapUnmanaged(u16, u16) = .{}, +phdr_to_shdr_table: std.AutoHashMapUnmanaged(u32, u32) = .{}, /// File offset into the shdr table. shdr_table_offset: ?u64 = null, /// Table of lists of atoms per output section. /// This table is not used to track incrementally generated atoms. -output_sections: std.AutoArrayHashMapUnmanaged(u16, std.ArrayListUnmanaged(Atom.Index)) = .{}, +output_sections: std.AutoArrayHashMapUnmanaged(u32, std.ArrayListUnmanaged(Atom.Index)) = .{}, +output_rela_sections: std.AutoArrayHashMapUnmanaged(u32, RelaSection) = .{}, /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. @@ -101,15 +102,15 @@ copy_rel: CopyRelSection = .{}, rela_plt: std.ArrayListUnmanaged(elf.Elf64_Rela) = .{}, /// .got.zig section zig_got: ZigGotSection = .{}, +/// SHT_GROUP sections +/// Applies only to a relocatable. +comdat_group_sections: std.ArrayListUnmanaged(ComdatGroupSection) = .{}, /// Tracked section headers with incremental updates to Zig object. /// .rela.* sections are only used when emitting a relocatable object file. zig_text_section_index: ?u16 = null, -zig_text_rela_section_index: ?u16 = null, zig_data_rel_ro_section_index: ?u16 = null, -zig_data_rel_ro_rela_section_index: ?u16 = null, zig_data_section_index: ?u16 = null, -zig_data_rela_section_index: ?u16 = null, zig_bss_section_index: ?u16 = null, zig_got_section_index: ?u16 = null, @@ -124,6 +125,7 @@ dynamic_section_index: ?u16 = null, dynstrtab_section_index: ?u16 = null, dynsymtab_section_index: ?u16 = null, eh_frame_section_index: ?u16 = null, +eh_frame_rela_section_index: ?u16 = null, eh_frame_hdr_section_index: ?u16 = null, hash_section_index: ?u16 = null, gnu_hash_section_index: ?u16 = null, @@ -216,10 +218,6 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), }); } - if (is_obj) { - // TODO until we implement -r option, we don't want to open a file at this stage. - return self; - } } errdefer if (self.base.intermediary_basename) |path| allocator.free(path); @@ -361,6 +359,10 @@ pub fn deinit(self: *Elf) void { list.deinit(gpa); } self.output_sections.deinit(gpa); + for (self.output_rela_sections.values()) |*sec| { + sec.atom_list.deinit(gpa); + } + self.output_rela_sections.deinit(gpa); self.shstrtab.deinit(gpa); self.symtab.deinit(gpa); self.strtab.deinit(gpa); @@ -395,6 +397,7 @@ pub fn deinit(self: *Elf) void { self.rela_dyn.deinit(gpa); self.rela_plt.deinit(gpa); self.zig_got.deinit(gpa); + self.comdat_group_sections.deinit(gpa); } pub fn getDeclVAddr(self: *Elf, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo) !u64 { @@ -601,11 +604,10 @@ pub fn initMetadata(self: *Elf) !void { const shdr = &self.shdrs.items[self.zig_text_section_index.?]; fillSection(self, shdr, self.base.options.program_code_size_hint, self.phdr_zig_load_re_index); if (self.isRelocatable()) { - try zig_object.addSectionSymbol(self.zig_text_section_index.?, self); - self.zig_text_rela_section_index = try self.addRelaShdr( - ".rela.text.zig", - self.zig_text_section_index.?, - ); + const rela_shndx = try self.addRelaShdr(".rela.text.zig", self.zig_text_section_index.?); + try self.output_rela_sections.putNoClobber(gpa, self.zig_text_section_index.?, .{ + .shndx = rela_shndx, + }); } else { try self.phdr_to_shdr_table.putNoClobber( gpa, @@ -613,6 +615,7 @@ pub fn initMetadata(self: *Elf) !void { self.phdr_zig_load_re_index.?, ); } + try self.output_sections.putNoClobber(gpa, self.zig_text_section_index.?, .{}); try self.last_atom_and_free_list_table.putNoClobber(gpa, self.zig_text_section_index.?, .{}); } @@ -642,17 +645,19 @@ pub fn initMetadata(self: *Elf) !void { .name = ".data.rel.ro.zig", .type = elf.SHT_PROGBITS, .addralign = 1, - .flags = elf.SHF_ALLOC | elf.SHF_WRITE, // TODO rename this section to .data.rel.ro + .flags = elf.SHF_ALLOC | elf.SHF_WRITE, .offset = std.math.maxInt(u64), }); const shdr = &self.shdrs.items[self.zig_data_rel_ro_section_index.?]; fillSection(self, shdr, 1024, self.phdr_zig_load_ro_index); if (self.isRelocatable()) { - try zig_object.addSectionSymbol(self.zig_data_rel_ro_section_index.?, self); - self.zig_data_rel_ro_rela_section_index = try self.addRelaShdr( + const rela_shndx = try self.addRelaShdr( ".rela.data.rel.ro.zig", self.zig_data_rel_ro_section_index.?, ); + try self.output_rela_sections.putNoClobber(gpa, self.zig_data_rel_ro_section_index.?, .{ + .shndx = rela_shndx, + }); } else { try self.phdr_to_shdr_table.putNoClobber( gpa, @@ -660,6 +665,7 @@ pub fn initMetadata(self: *Elf) !void { self.phdr_zig_load_ro_index.?, ); } + try self.output_sections.putNoClobber(gpa, self.zig_data_rel_ro_section_index.?, .{}); try self.last_atom_and_free_list_table.putNoClobber(gpa, self.zig_data_rel_ro_section_index.?, .{}); } @@ -674,11 +680,13 @@ pub fn initMetadata(self: *Elf) !void { const shdr = &self.shdrs.items[self.zig_data_section_index.?]; fillSection(self, shdr, 1024, self.phdr_zig_load_rw_index); if (self.isRelocatable()) { - try zig_object.addSectionSymbol(self.zig_data_section_index.?, self); - self.zig_data_rela_section_index = try self.addRelaShdr( + const rela_shndx = try self.addRelaShdr( ".rela.data.zig", self.zig_data_section_index.?, ); + try self.output_rela_sections.putNoClobber(gpa, self.zig_data_section_index.?, .{ + .shndx = rela_shndx, + }); } else { try self.phdr_to_shdr_table.putNoClobber( gpa, @@ -686,6 +694,7 @@ pub fn initMetadata(self: *Elf) !void { self.phdr_zig_load_rw_index.?, ); } + try self.output_sections.putNoClobber(gpa, self.zig_data_section_index.?, .{}); try self.last_atom_and_free_list_table.putNoClobber(gpa, self.zig_data_section_index.?, .{}); } @@ -704,9 +713,9 @@ pub fn initMetadata(self: *Elf) !void { shdr.sh_size = phdr.p_memsz; try self.phdr_to_shdr_table.putNoClobber(gpa, self.zig_bss_section_index.?, phndx); } else { - try zig_object.addSectionSymbol(self.zig_bss_section_index.?, self); shdr.sh_size = 1024; } + try self.output_sections.putNoClobber(gpa, self.zig_bss_section_index.?, .{}); try self.last_atom_and_free_list_table.putNoClobber(gpa, self.zig_bss_section_index.?, .{}); } @@ -728,6 +737,7 @@ pub fn initMetadata(self: *Elf) !void { shdr.sh_offset = off; shdr.sh_size = size; zig_object.debug_strtab_dirty = true; + try self.output_sections.putNoClobber(gpa, self.debug_str_section_index.?, .{}); } if (self.debug_info_section_index == null) { @@ -743,6 +753,7 @@ pub fn initMetadata(self: *Elf) !void { shdr.sh_offset = off; shdr.sh_size = size; zig_object.debug_info_header_dirty = true; + try self.output_sections.putNoClobber(gpa, self.debug_info_section_index.?, .{}); } if (self.debug_abbrev_section_index == null) { @@ -758,6 +769,7 @@ pub fn initMetadata(self: *Elf) !void { shdr.sh_offset = off; shdr.sh_size = size; zig_object.debug_abbrev_section_dirty = true; + try self.output_sections.putNoClobber(gpa, self.debug_abbrev_section_index.?, .{}); } if (self.debug_aranges_section_index == null) { @@ -773,6 +785,7 @@ pub fn initMetadata(self: *Elf) !void { shdr.sh_offset = off; shdr.sh_size = size; zig_object.debug_aranges_section_dirty = true; + try self.output_sections.putNoClobber(gpa, self.debug_aranges_section_index.?, .{}); } if (self.debug_line_section_index == null) { @@ -788,8 +801,17 @@ pub fn initMetadata(self: *Elf) !void { shdr.sh_offset = off; shdr.sh_size = size; zig_object.debug_line_header_dirty = true; + try self.output_sections.putNoClobber(gpa, self.debug_line_section_index.?, .{}); } } + + // We need to find current max assumed file offset, and actually write to file to make it a reality. + var end_pos: u64 = 0; + for (self.shdrs.items) |shdr| { + if (shdr.sh_offset == std.math.maxInt(u64)) continue; + end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size); + } + try self.base.file.?.pwriteAll(&[1]u8{0}, end_pos); } pub fn growAllocSection(self: *Elf, shdr_index: u16, needed_size: u64) !void { @@ -942,30 +964,8 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node } else null; const gc_sections = self.base.options.gc_sections orelse false; - if (self.isObject() and self.zig_object_index == null) { - // TODO this will become -r route I guess. For now, just copy the object file. - const the_object_path = blk: { - if (self.base.options.objects.len != 0) { - break :blk self.base.options.objects[0].path; - } - - if (comp.c_object_table.count() != 0) - break :blk comp.c_object_table.keys()[0].status.success.object_path; - - if (module_obj_path) |p| - break :blk p; - - // TODO I think this is unreachable. Audit this situation when solving the above TODO - // regarding eliding redundant object -> object transformations. - return error.NoObjectsToLink; - }; - // This can happen when using --enable-cache and using the stage1 backend. In this case - // we can skip the file copy. - if (!mem.eql(u8, the_object_path, full_out_path)) { - try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{}); - } - return; - } + // --verbose-link + if (self.base.options.verbose_link) try self.dumpArgv(comp); var csu = try CsuObjects.init(arena, self.base.options, comp); const compiler_rt_path: ?[]const u8 = blk: { @@ -974,247 +974,9 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node break :blk null; }; - // --verbose-link - if (self.base.options.verbose_link) { - var argv = std.ArrayList([]const u8).init(arena); - - try argv.append("zig"); - try argv.append("ld"); - - try argv.append("-o"); - try argv.append(full_out_path); - - if (self.base.options.entry) |entry| { - try argv.append("--entry"); - try argv.append(entry); - } - - if (self.base.options.dynamic_linker) |path| { - try argv.append("-dynamic-linker"); - try argv.append(path); - } - - if (self.base.options.soname) |name| { - try argv.append("-soname"); - try argv.append(name); - } - - for (self.base.options.rpath_list) |rpath| { - try argv.append("-rpath"); - try argv.append(rpath); - } - - if (self.base.options.each_lib_rpath) { - for (self.base.options.lib_dirs) |lib_dir_path| { - try argv.append("-rpath"); - try argv.append(lib_dir_path); - } - for (self.base.options.objects) |obj| { - if (Compilation.classifyFileExt(obj.path) == .shared_library) { - const lib_dir_path = std.fs.path.dirname(obj.path) orelse continue; - if (obj.loption) continue; - - try argv.append("-rpath"); - try argv.append(lib_dir_path); - } - } - } - - if (self.base.options.stack_size_override) |ss| { - try argv.append("-z"); - try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{ss})); - } - - if (self.base.options.image_base_override) |image_base| { - try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{image_base})); - } - - if (gc_sections) { - try argv.append("--gc-sections"); - } - - if (self.base.options.print_gc_sections) { - try argv.append("--print-gc-sections"); - } - - if (self.base.options.eh_frame_hdr) { - try argv.append("--eh-frame-hdr"); - } - - if (self.base.options.rdynamic) { - try argv.append("--export-dynamic"); - } - - if (self.base.options.strip) { - try argv.append("-s"); - } - - if (self.base.options.z_notext) { - try argv.append("-z"); - try argv.append("notext"); - } - - if (self.base.options.z_nocopyreloc) { - try argv.append("-z"); - try argv.append("nocopyreloc"); - } - - if (self.base.options.z_now) { - try argv.append("-z"); - try argv.append("now"); - } - - if (self.isStatic()) { - try argv.append("-static"); - } else if (self.isDynLib()) { - try argv.append("-shared"); - } - - if (self.base.options.pie and self.isExe()) { - try argv.append("-pie"); - } - - // csu prelude - if (csu.crt0) |v| try argv.append(v); - if (csu.crti) |v| try argv.append(v); - if (csu.crtbegin) |v| try argv.append(v); - - for (self.base.options.lib_dirs) |lib_dir| { - try argv.append("-L"); - try argv.append(lib_dir); - } - - if (self.base.options.link_libc) { - if (self.base.options.libc_installation) |libc_installation| { - try argv.append("-L"); - try argv.append(libc_installation.crt_dir.?); - } - } - - var whole_archive = false; - for (self.base.options.objects) |obj| { - if (obj.must_link and !whole_archive) { - try argv.append("-whole-archive"); - whole_archive = true; - } else if (!obj.must_link and whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - - if (obj.loption) { - assert(obj.path[0] == ':'); - try argv.append("-l"); - } - try argv.append(obj.path); - } - if (whole_archive) { - try argv.append("-no-whole-archive"); - whole_archive = false; - } - - for (comp.c_object_table.keys()) |key| { - try argv.append(key.status.success.object_path); - } - - if (module_obj_path) |p| { - try argv.append(p); - } - - // TSAN - if (self.base.options.tsan) { - try argv.append(comp.tsan_static_lib.?.full_object_path); - } - - // libc - if (!self.base.options.skip_linker_dependencies and - !self.base.options.link_libc) - { - if (comp.libc_static_lib) |lib| { - try argv.append(lib.full_object_path); - } - } - - // stack-protector. - // Related: https://github.com/ziglang/zig/issues/7265 - if (comp.libssp_static_lib) |ssp| { - try argv.append(ssp.full_object_path); - } - - // Shared libraries. - // Worst-case, we need an --as-needed argument for every lib, as well - // as one before and one after. - try argv.ensureUnusedCapacity(self.base.options.system_libs.keys().len * 2 + 2); - argv.appendAssumeCapacity("--as-needed"); - var as_needed = true; - - for (self.base.options.system_libs.values()) |lib_info| { - const lib_as_needed = !lib_info.needed; - switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { - 0b00, 0b11 => {}, - 0b01 => { - argv.appendAssumeCapacity("--no-as-needed"); - as_needed = false; - }, - 0b10 => { - argv.appendAssumeCapacity("--as-needed"); - as_needed = true; - }, - } - argv.appendAssumeCapacity(lib_info.path.?); - } - - if (!as_needed) { - argv.appendAssumeCapacity("--as-needed"); - as_needed = true; - } - - // libc++ dep - if (self.base.options.link_libcpp) { - try argv.append(comp.libcxxabi_static_lib.?.full_object_path); - try argv.append(comp.libcxx_static_lib.?.full_object_path); - } - - // libunwind dep - if (self.base.options.link_libunwind) { - try argv.append(comp.libunwind_static_lib.?.full_object_path); - } - - // libc dep - if (self.base.options.link_libc) { - if (self.base.options.libc_installation != null) { - const needs_grouping = self.base.options.link_mode == .Static; - if (needs_grouping) try argv.append("--start-group"); - try argv.appendSlice(target_util.libcFullLinkFlags(target)); - if (needs_grouping) try argv.append("--end-group"); - } else if (target.isGnuLibC()) { - for (glibc.libs) |lib| { - const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{ - comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, - }); - try argv.append(lib_path); - } - try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a")); - } else if (target.isMusl()) { - try argv.append(try comp.get_libc_crt_file(arena, switch (self.base.options.link_mode) { - .Static => "libc.a", - .Dynamic => "libc.so", - })); - } - } - - // compiler-rt - if (compiler_rt_path) |p| { - try argv.append(p); - } - - // crt postlude - if (csu.crtend) |v| try argv.append(v); - if (csu.crtn) |v| try argv.append(v); - - Compilation.dump_argv(argv.items); - } - if (self.zigObjectPtr()) |zig_object| try zig_object.flushModule(self); + if (self.isStaticLib()) return self.flushStaticLib(comp, module_obj_path); + if (self.isObject()) return self.flushObject(comp, module_obj_path); // Here we will parse input positional and library files (if referenced). // This will roughly match in any linker backend we support. @@ -1240,6 +1002,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node // rpaths var rpath_table = std.StringArrayHashMap(void).init(self.base.allocator); defer rpath_table.deinit(); + for (self.base.options.rpath_list) |rpath| { _ = try rpath_table.put(rpath, {}); } @@ -1388,8 +1151,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node try self.handleAndReportParseError(obj.path, err, &parse_ctx); } - if (self.isStaticLib()) return self.flushStaticLib(comp); - // Init all objects for (self.objects.items) |index| { try self.file(index).?.object.init(self); @@ -1418,7 +1179,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node // 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 and !self.isRelocatable()) { + if (self.linker_defined_index == null) { const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .linker_defined = .{ .index = index } }); self.linker_defined_index = index; @@ -1432,8 +1193,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node self.resolveSymbols(); self.markEhFrameAtomsDead(); - if (self.isObject()) return self.flushObject(comp); - try self.convertCommonSymbols(); self.markImportsExports(); @@ -1527,10 +1286,30 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node } } -pub fn flushStaticLib(self: *Elf, comp: *Compilation) link.File.FlushError!void { - _ = comp; +pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { const gpa = self.base.allocator; + var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); + defer positionals.deinit(); + + try positionals.ensureUnusedCapacity(self.base.options.objects.len); + positionals.appendSliceAssumeCapacity(self.base.options.objects); + + // This is a set of object files emitted by clang in a single `build-exe` invocation. + // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up + // in this set. + for (comp.c_object_table.keys()) |key| { + try positionals.append(.{ .path = key.status.success.object_path }); + } + + if (module_obj_path) |path| try positionals.append(.{ .path = path }); + + for (positionals.items) |obj| { + var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined }; + self.parsePositional(obj.path, obj.must_link, &parse_ctx) catch |err| + try self.handleAndReportParseError(obj.path, err, &parse_ctx); + } + // First, we flush relocatable object file generated with our backends. if (self.zigObjectPtr()) |zig_object| { zig_object.resolveSymbols(self); @@ -1539,16 +1318,17 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation) link.File.FlushError!void try self.initSymtab(); try self.initShStrtab(); try self.sortShdrs(); - zig_object.updateRelaSectionSizes(self); - self.updateSymtabSizeObject(zig_object); - self.updateShStrtabSize(); + try zig_object.addAtomsToRelaSections(self); + try self.updateSectionSizesObject(); try self.allocateNonAllocSections(); + if (build_options.enable_logging) { + state_log.debug("{}", .{self.dumpState()}); + } + + try self.writeSyntheticSectionsObject(); try self.writeShdrTable(); - try zig_object.writeRelaSections(self); - try self.writeSymtabObject(zig_object); - try self.writeShStrtab(); try self.writeElfHeader(); } @@ -1639,33 +1419,357 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation) link.File.FlushError!void try self.base.file.?.pwriteAll(buffer.items, 0); } -pub fn flushObject(self: *Elf, comp: *Compilation) link.File.FlushError!void { - _ = comp; +pub fn flushObject(self: *Elf, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { + const gpa = self.base.allocator; - if (self.objects.items.len > 0) { - var err = try self.addErrorWithNotes(1); - try err.addMsg(self, "fatal linker error: too many input positionals", .{}); - try err.addNote(self, "TODO implement '-r' option", .{}); - return; + var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); + defer positionals.deinit(); + try positionals.ensureUnusedCapacity(self.base.options.objects.len); + positionals.appendSliceAssumeCapacity(self.base.options.objects); + + // This is a set of object files emitted by clang in a single `build-exe` invocation. + // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up + // in this set. + for (comp.c_object_table.keys()) |key| { + try positionals.append(.{ .path = key.status.success.object_path }); } + if (module_obj_path) |path| try positionals.append(.{ .path = path }); + + for (positionals.items) |obj| { + var parse_ctx: ParseErrorCtx = .{ .detected_cpu_arch = undefined }; + self.parsePositional(obj.path, obj.must_link, &parse_ctx) catch |err| + try self.handleAndReportParseError(obj.path, err, &parse_ctx); + } + + // Init all objects + for (self.objects.items) |index| { + try self.file(index).?.object.init(self); + } + + // Now, we are ready to resolve the symbols across all input files. + // We will first resolve the files in the ZigObject, next in the parsed + // input Object files. + self.resolveSymbols(); + self.markEhFrameAtomsDead(); self.claimUnresolvedObject(); - try self.initSections(); + try self.initSectionsObject(); try self.sortShdrs(); - try self.updateSectionSizes(); + if (self.zigObjectPtr()) |zig_object| { + try zig_object.addAtomsToRelaSections(self); + } + for (self.objects.items) |index| { + const object = self.file(index).?.object; + try object.addAtomsToOutputSections(self); + try object.addAtomsToRelaSections(self); + } + try self.updateSectionSizesObject(); + try self.allocateAllocSectionsObject(); try self.allocateNonAllocSections(); + self.allocateAtoms(); if (build_options.enable_logging) { state_log.debug("{}", .{self.dumpState()}); } + try self.writeAtomsObject(); + try self.writeSyntheticSectionsObject(); try self.writeShdrTable(); - try self.writeSyntheticSections(); try self.writeElfHeader(); } +/// --verbose-link output +fn dumpArgv(self: *Elf, comp: *Compilation) !void { + var arena_allocator = std.heap.ArenaAllocator.init(self.base.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const target = self.base.options.target; + const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type. + const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); + const module_obj_path: ?[]const u8 = if (self.base.intermediary_basename) |path| blk: { + if (fs.path.dirname(full_out_path)) |dirname| { + break :blk try fs.path.join(arena, &.{ dirname, path }); + } else { + break :blk path; + } + } else null; + const gc_sections = self.base.options.gc_sections orelse false; + + var csu = try CsuObjects.init(arena, self.base.options, comp); + const compiler_rt_path: ?[]const u8 = blk: { + if (comp.compiler_rt_lib) |x| break :blk x.full_object_path; + if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; + + var argv = std.ArrayList([]const u8).init(arena); + + try argv.append("zig"); + + if (self.isStaticLib()) { + try argv.append("ar"); + } else { + try argv.append("ld"); + } + + if (self.isObject()) { + try argv.append("-r"); + } + + try argv.append("-o"); + try argv.append(full_out_path); + + if (self.isRelocatable()) { + for (self.base.options.objects) |obj| { + try argv.append(obj.path); + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(key.status.success.object_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + } else { + if (!self.isStatic()) { + if (self.base.options.dynamic_linker) |path| { + try argv.append("-dynamic-linker"); + try argv.append(path); + } + } + + if (self.isDynLib()) { + if (self.base.options.soname) |name| { + try argv.append("-soname"); + try argv.append(name); + } + } + + if (self.base.options.entry) |entry| { + try argv.append("--entry"); + try argv.append(entry); + } + + for (self.base.options.rpath_list) |rpath| { + try argv.append("-rpath"); + try argv.append(rpath); + } + + if (self.base.options.each_lib_rpath) { + for (self.base.options.lib_dirs) |lib_dir_path| { + try argv.append("-rpath"); + try argv.append(lib_dir_path); + } + for (self.base.options.objects) |obj| { + if (Compilation.classifyFileExt(obj.path) == .shared_library) { + const lib_dir_path = std.fs.path.dirname(obj.path) orelse continue; + if (obj.loption) continue; + + try argv.append("-rpath"); + try argv.append(lib_dir_path); + } + } + } + + if (self.base.options.stack_size_override) |ss| { + try argv.append("-z"); + try argv.append(try std.fmt.allocPrint(arena, "stack-size={d}", .{ss})); + } + + if (self.base.options.image_base_override) |image_base| { + try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{image_base})); + } + + if (gc_sections) { + try argv.append("--gc-sections"); + } + + if (self.base.options.print_gc_sections) { + try argv.append("--print-gc-sections"); + } + + if (self.base.options.eh_frame_hdr) { + try argv.append("--eh-frame-hdr"); + } + + if (self.base.options.rdynamic) { + try argv.append("--export-dynamic"); + } + + if (self.base.options.z_notext) { + try argv.append("-z"); + try argv.append("notext"); + } + + if (self.base.options.z_nocopyreloc) { + try argv.append("-z"); + try argv.append("nocopyreloc"); + } + + if (self.base.options.z_now) { + try argv.append("-z"); + try argv.append("now"); + } + + if (self.isStatic()) { + try argv.append("-static"); + } else if (self.isDynLib()) { + try argv.append("-shared"); + } + + if (self.base.options.pie and self.isExe()) { + try argv.append("-pie"); + } + + if (self.base.options.strip) { + try argv.append("-s"); + } + + // csu prelude + if (csu.crt0) |v| try argv.append(v); + if (csu.crti) |v| try argv.append(v); + if (csu.crtbegin) |v| try argv.append(v); + + for (self.base.options.lib_dirs) |lib_dir| { + try argv.append("-L"); + try argv.append(lib_dir); + } + + if (self.base.options.link_libc) { + if (self.base.options.libc_installation) |libc_installation| { + try argv.append("-L"); + try argv.append(libc_installation.crt_dir.?); + } + } + + var whole_archive = false; + for (self.base.options.objects) |obj| { + if (obj.must_link and !whole_archive) { + try argv.append("-whole-archive"); + whole_archive = true; + } else if (!obj.must_link and whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + + if (obj.loption) { + assert(obj.path[0] == ':'); + try argv.append("-l"); + } + try argv.append(obj.path); + } + if (whole_archive) { + try argv.append("-no-whole-archive"); + whole_archive = false; + } + + for (comp.c_object_table.keys()) |key| { + try argv.append(key.status.success.object_path); + } + + if (module_obj_path) |p| { + try argv.append(p); + } + + // TSAN + if (self.base.options.tsan) { + try argv.append(comp.tsan_static_lib.?.full_object_path); + } + + // libc + if (!self.base.options.skip_linker_dependencies and + !self.base.options.link_libc) + { + if (comp.libc_static_lib) |lib| { + try argv.append(lib.full_object_path); + } + } + + // stack-protector. + // Related: https://github.com/ziglang/zig/issues/7265 + if (comp.libssp_static_lib) |ssp| { + try argv.append(ssp.full_object_path); + } + + // Shared libraries. + // Worst-case, we need an --as-needed argument for every lib, as well + // as one before and one after. + try argv.ensureUnusedCapacity(self.base.options.system_libs.keys().len * 2 + 2); + argv.appendAssumeCapacity("--as-needed"); + var as_needed = true; + + for (self.base.options.system_libs.values()) |lib_info| { + const lib_as_needed = !lib_info.needed; + switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { + 0b00, 0b11 => {}, + 0b01 => { + argv.appendAssumeCapacity("--no-as-needed"); + as_needed = false; + }, + 0b10 => { + argv.appendAssumeCapacity("--as-needed"); + as_needed = true; + }, + } + argv.appendAssumeCapacity(lib_info.path.?); + } + + if (!as_needed) { + argv.appendAssumeCapacity("--as-needed"); + as_needed = true; + } + + // libc++ dep + if (self.base.options.link_libcpp) { + try argv.append(comp.libcxxabi_static_lib.?.full_object_path); + try argv.append(comp.libcxx_static_lib.?.full_object_path); + } + + // libunwind dep + if (self.base.options.link_libunwind) { + try argv.append(comp.libunwind_static_lib.?.full_object_path); + } + + // libc dep + if (self.base.options.link_libc) { + if (self.base.options.libc_installation != null) { + const needs_grouping = self.base.options.link_mode == .Static; + if (needs_grouping) try argv.append("--start-group"); + try argv.appendSlice(target_util.libcFullLinkFlags(target)); + if (needs_grouping) try argv.append("--end-group"); + } else if (target.isGnuLibC()) { + for (glibc.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{s}{c}lib{s}.so.{d}", .{ + comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover, + }); + try argv.append(lib_path); + } + try argv.append(try comp.get_libc_crt_file(arena, "libc_nonshared.a")); + } else if (target.isMusl()) { + try argv.append(try comp.get_libc_crt_file(arena, switch (self.base.options.link_mode) { + .Static => "libc.a", + .Dynamic => "libc.so", + })); + } + } + + // compiler-rt + if (compiler_rt_path) |p| { + try argv.append(p); + } + + // crt postlude + if (csu.crtend) |v| try argv.append(v); + if (csu.crtn) |v| try argv.append(v); + } + + Compilation.dump_argv(argv.items); +} + const ParseError = error{ UnknownFileType, InvalidCpuArch, @@ -2047,8 +2151,7 @@ fn claimUnresolved(self: *Elf) void { zig_object.claimUnresolved(self); } for (self.objects.items) |index| { - const object = self.file(index).?.object; - object.claimUnresolved(self); + self.file(index).?.object.claimUnresolved(self); } } @@ -2056,6 +2159,9 @@ fn claimUnresolvedObject(self: *Elf) void { if (self.zigObjectPtr()) |zig_object| { zig_object.claimUnresolvedObject(self); } + for (self.objects.items) |index| { + self.file(index).?.object.claimUnresolvedObject(self); + } } /// In scanRelocs we will go over all live atoms and scan their relocs. @@ -3460,6 +3566,60 @@ fn initSections(self: *Elf) !void { try self.initShStrtab(); } +fn initSectionsObject(self: *Elf) !void { + const ptr_size = self.ptrWidthBytes(); + + for (self.objects.items) |index| { + const object = self.file(index).?.object; + try object.initOutputSections(self); + try object.initRelaSections(self); + } + + const needs_eh_frame = for (self.objects.items) |index| { + if (self.file(index).?.object.cies.items.len > 0) break true; + } else false; + if (needs_eh_frame) { + self.eh_frame_section_index = try self.addSection(.{ + .name = ".eh_frame", + .type = elf.SHT_PROGBITS, + .flags = elf.SHF_ALLOC, + .addralign = ptr_size, + .offset = std.math.maxInt(u64), + }); + self.eh_frame_rela_section_index = try self.addRelaShdr(".rela.eh_frame", self.eh_frame_section_index.?); + } + + try self.initComdatGroups(); + try self.initSymtab(); + try self.initShStrtab(); +} + +fn initComdatGroups(self: *Elf) !void { + const gpa = self.base.allocator; + + for (self.objects.items) |index| { + const object = self.file(index).?.object; + + for (object.comdat_groups.items) |cg_index| { + const cg = self.comdatGroup(cg_index); + const cg_owner = self.comdatGroupOwner(cg.owner); + if (cg_owner.file != index) continue; + + const cg_sec = try self.comdat_group_sections.addOne(gpa); + cg_sec.* = .{ + .shndx = try self.addSection(.{ + .name = ".group", + .type = elf.SHT_GROUP, + .entsize = @sizeOf(u32), + .addralign = @alignOf(u32), + .offset = std.math.maxInt(u64), + }), + .cg_index = cg_index, + }; + } + } +} + fn initSymtab(self: *Elf) !void { const small_ptr = switch (self.ptr_width) { .p32 => true, @@ -3586,7 +3746,8 @@ fn sortInitFini(self: *Elf) !void { if (!is_init_fini and !is_ctor_dtor) continue; - const atom_list = self.output_sections.getPtr(@intCast(shndx)) orelse continue; + const atom_list = self.output_sections.getPtr(@intCast(shndx)).?; + if (atom_list.items.len == 0) continue; var entries = std.ArrayList(Entry).init(gpa); try entries.ensureTotalCapacityPrecise(atom_list.items.len); @@ -3627,8 +3788,10 @@ fn setDynamicSection(self: *Elf, rpaths: []const []const u8) !void { try self.dynamic.addNeeded(shared_object, self); } - if (self.base.options.soname) |soname| { - try self.dynamic.setSoname(soname, self); + if (self.isDynLib()) { + if (self.base.options.soname) |soname| { + try self.dynamic.setSoname(soname, self); + } } try self.dynamic.setRpath(rpaths, self); @@ -3760,7 +3923,7 @@ fn shdrRank(self: *Elf, shndx: u16) u8 { elf.SHT_DYNAMIC => return 0xf3, - elf.SHT_RELA => return 0xf, + elf.SHT_RELA, elf.SHT_GROUP => return 0xf, elf.SHT_PROGBITS => if (flags & elf.SHF_ALLOC != 0) { if (flags & elf.SHF_EXECINSTR != 0) { @@ -3819,8 +3982,15 @@ fn sortShdrs(self: *Elf) !void { self.shdrs.appendAssumeCapacity(slice[sorted.shndx]); } + try self.resetShdrIndexes(backlinks); +} + +fn resetShdrIndexes(self: *Elf, backlinks: []const u16) !void { + const gpa = self.base.allocator; + for (&[_]*?u16{ &self.eh_frame_section_index, + &self.eh_frame_rela_section_index, &self.eh_frame_hdr_section_index, &self.got_section_index, &self.symtab_section_index, @@ -3841,12 +4011,9 @@ fn sortShdrs(self: *Elf) !void { &self.versym_section_index, &self.verneed_section_index, &self.zig_text_section_index, - &self.zig_text_rela_section_index, &self.zig_got_section_index, &self.zig_data_rel_ro_section_index, - &self.zig_data_rel_ro_rela_section_index, &self.zig_data_section_index, - &self.zig_data_rela_section_index, &self.zig_bss_section_index, &self.debug_str_section_index, &self.debug_info_section_index, @@ -3905,15 +4072,39 @@ fn sortShdrs(self: *Elf) !void { shdr.sh_info = self.plt_section_index.?; } - for (&[_]?u16{ - self.zig_text_rela_section_index, - self.zig_data_rel_ro_rela_section_index, - self.zig_data_rela_section_index, - }) |maybe_index| { - const index = maybe_index orelse continue; + if (self.eh_frame_rela_section_index) |index| { const shdr = &self.shdrs.items[index]; shdr.sh_link = self.symtab_section_index.?; - shdr.sh_info = backlinks[shdr.sh_info]; + shdr.sh_info = self.eh_frame_section_index.?; + } + + { + var output_sections = try self.output_sections.clone(gpa); + defer output_sections.deinit(gpa); + + self.output_sections.clearRetainingCapacity(); + + var it = output_sections.iterator(); + while (it.next()) |entry| { + const shndx = entry.key_ptr.*; + const meta = entry.value_ptr.*; + self.output_sections.putAssumeCapacityNoClobber(backlinks[shndx], meta); + } + } + + { + var output_rela_sections = try self.output_rela_sections.clone(gpa); + defer output_rela_sections.deinit(gpa); + + self.output_rela_sections.clearRetainingCapacity(); + + var it = output_rela_sections.iterator(); + while (it.next()) |entry| { + const shndx = entry.key_ptr.*; + var meta = entry.value_ptr.*; + meta.shndx = backlinks[meta.shndx]; + self.output_rela_sections.putAssumeCapacityNoClobber(backlinks[shndx], meta); + } } { @@ -3965,11 +4156,20 @@ fn sortShdrs(self: *Elf) !void { global.output_section_index = backlinks[out_shndx]; } } + + for (self.output_rela_sections.keys(), self.output_rela_sections.values()) |shndx, sec| { + const shdr = &self.shdrs.items[sec.shndx]; + shdr.sh_link = self.symtab_section_index.?; + shdr.sh_info = shndx; + } + + for (self.comdat_group_sections.items) |*cg| { + cg.shndx = backlinks[cg.shndx]; + } } fn updateSectionSizes(self: *Elf) !void { for (self.output_sections.keys(), self.output_sections.values()) |shndx, atom_list| { - if (atom_list.items.len == 0) continue; const shdr = &self.shdrs.items[shndx]; for (atom_list.items) |atom_index| { const atom_ptr = self.atom(atom_index) orelse continue; @@ -3982,10 +4182,6 @@ fn updateSectionSizes(self: *Elf) !void { } } - if (self.zigObjectPtr()) |zig_object| { - zig_object.updateRelaSectionSizes(self); - } - if (self.eh_frame_section_index) |index| { self.shdrs.items[index].sh_size = try eh_frame.calcEhFrameSize(self); } @@ -4061,10 +4257,61 @@ fn updateSectionSizes(self: *Elf) !void { self.shdrs.items[index].sh_size = self.verneed.size(); } - self.updateSymtabSize(); + try self.updateSymtabSize(); self.updateShStrtabSize(); } +fn updateSectionSizesObject(self: *Elf) !void { + for (self.output_sections.keys(), self.output_sections.values()) |shndx, atom_list| { + const shdr = &self.shdrs.items[shndx]; + for (atom_list.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; + const offset = atom_ptr.alignment.forward(shdr.sh_size); + const padding = offset - shdr.sh_size; + atom_ptr.value = offset; + shdr.sh_size += padding + atom_ptr.size; + shdr.sh_addralign = @max(shdr.sh_addralign, atom_ptr.alignment.toByteUnits(1)); + } + } + + for (self.output_rela_sections.values()) |sec| { + const shdr = &self.shdrs.items[sec.shndx]; + for (sec.atom_list.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; + const relocs = atom_ptr.relocs(self); + shdr.sh_size += shdr.sh_entsize * relocs.len; + } + + if (shdr.sh_size == 0) shdr.sh_offset = 0; + } + + if (self.eh_frame_section_index) |index| { + self.shdrs.items[index].sh_size = try eh_frame.calcEhFrameSize(self); + } + if (self.eh_frame_rela_section_index) |index| { + const shdr = &self.shdrs.items[index]; + shdr.sh_size = eh_frame.calcEhFrameRelocs(self) * shdr.sh_entsize; + } + + try self.updateSymtabSize(); + self.updateComdatGroupsSizes(); + self.updateShStrtabSize(); +} + +fn updateComdatGroupsSizes(self: *Elf) void { + for (self.comdat_group_sections.items) |cg| { + const shdr = &self.shdrs.items[cg.shndx]; + shdr.sh_size = cg.size(self); + shdr.sh_link = self.symtab_section_index.?; + + const sym = self.symbol(cg.symbol(self)); + shdr.sh_info = sym.outputSymtabIndex(self) orelse + self.sectionSymbolOutputSymtabIndex(sym.outputShndx().?); + } +} + fn updateShStrtabSize(self: *Elf) void { if (self.shstrtab_section_index) |index| { self.shdrs.items[index].sh_size = self.shstrtab.items.len; @@ -4294,6 +4541,22 @@ fn allocateAllocSections(self: *Elf) error{OutOfMemory}!void { } } +/// Allocates alloc sections when merging relocatable objects files together. +fn allocateAllocSectionsObject(self: *Elf) !void { + for (self.shdrs.items) |*shdr| { + if (shdr.sh_type == elf.SHT_NULL) continue; + if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue; + if (shdr.sh_type == elf.SHT_NOBITS) continue; + const needed_size = shdr.sh_size; + if (needed_size > self.allocatedSize(shdr.sh_offset)) { + shdr.sh_size = 0; + const new_offset = self.findFreeSpace(needed_size, shdr.sh_addralign); + shdr.sh_offset = new_offset; + shdr.sh_size = needed_size; + } + } +} + /// Allocates non-alloc sections (debug info, symtabs, etc.). fn allocateNonAllocSections(self: *Elf) !void { for (self.shdrs.items, 0..) |*shdr, shndx| { @@ -4418,6 +4681,7 @@ fn writeAtoms(self: *Elf) !void { if (shdr.sh_type == elf.SHT_NOBITS) continue; const atom_list = self.output_sections.get(@intCast(shndx)) orelse continue; + if (atom_list.items.len == 0) continue; log.debug("writing atoms in '{s}' section", .{self.getShString(shdr.sh_name)}); @@ -4484,93 +4748,165 @@ fn writeAtoms(self: *Elf) !void { try self.reportUndefined(&undefs); } -fn updateSymtabSize(self: *Elf) void { - var sizes = SymtabSize{}; +fn writeAtomsObject(self: *Elf) !void { + const gpa = self.base.allocator; - if (self.zigObjectPtr()) |zig_object| { - zig_object.asFile().updateSymtabSize(self); - sizes.add(zig_object.output_symtab_size); + // TODO iterate over `output_sections` directly + for (self.shdrs.items, 0..) |shdr, shndx| { + if (shdr.sh_type == elf.SHT_NULL) continue; + if (shdr.sh_type == elf.SHT_NOBITS) continue; + + const atom_list = self.output_sections.get(@intCast(shndx)) orelse continue; + if (atom_list.items.len == 0) continue; + + log.debug("writing atoms in '{s}' section", .{self.getShString(shdr.sh_name)}); + + // TODO really, really handle debug section separately + const base_offset = if (self.isDebugSection(@intCast(shndx))) blk: { + const zig_object = self.zigObjectPtr().?; + if (shndx == self.debug_info_section_index.?) + break :blk zig_object.debug_info_section_zig_size; + if (shndx == self.debug_abbrev_section_index.?) + break :blk zig_object.debug_abbrev_section_zig_size; + if (shndx == self.debug_str_section_index.?) + break :blk zig_object.debug_str_section_zig_size; + if (shndx == self.debug_aranges_section_index.?) + break :blk zig_object.debug_aranges_section_zig_size; + if (shndx == self.debug_line_section_index.?) + break :blk zig_object.debug_line_section_zig_size; + unreachable; + } else 0; + const sh_offset = shdr.sh_offset + base_offset; + const sh_size = math.cast(usize, shdr.sh_size - base_offset) orelse return error.Overflow; + + const buffer = try gpa.alloc(u8, sh_size); + defer gpa.free(buffer); + const padding_byte: u8 = if (shdr.sh_type == elf.SHT_PROGBITS and + shdr.sh_flags & elf.SHF_EXECINSTR != 0) + 0xcc // int3 + else + 0; + @memset(buffer, padding_byte); + + for (atom_list.items) |atom_index| { + const atom_ptr = self.atom(atom_index).?; + assert(atom_ptr.flags.alive); + + const object = atom_ptr.file(self).?.object; + const offset = math.cast(usize, atom_ptr.value - shdr.sh_addr - base_offset) orelse + return error.Overflow; + const size = math.cast(usize, atom_ptr.size) orelse return error.Overflow; + + log.debug("writing atom({d}) from 0x{x} to 0x{x}", .{ + atom_index, + sh_offset + offset, + sh_offset + offset + size, + }); + + // TODO decompress directly into provided buffer + const out_code = buffer[offset..][0..size]; + const in_code = try object.codeDecompressAlloc(self, atom_index); + defer gpa.free(in_code); + @memcpy(out_code, in_code); + } + + try self.base.file.?.pwriteAll(buffer, sh_offset); } +} +fn updateSymtabSize(self: *Elf) !void { + var nlocals: u32 = 0; + var nglobals: u32 = 0; + var strsize: u32 = 0; + + const gpa = self.base.allocator; + var files = std.ArrayList(File.Index).init(gpa); + defer files.deinit(); + try files.ensureTotalCapacityPrecise(self.objects.items.len + self.shared_objects.items.len + 1); + + if (self.zig_object_index) |index| files.appendAssumeCapacity(index); for (self.objects.items) |index| { - const file_ptr = self.file(index).?; - file_ptr.updateSymtabSize(self); - sizes.add(file_ptr.object.output_symtab_size); + files.appendAssumeCapacity(index); + } + for (self.shared_objects.items) |index| { + files.appendAssumeCapacity(index); } - for (self.shared_objects.items) |index| { + // Section symbols + for (self.output_sections.keys()) |_| { + nlocals += 1; + } + if (self.eh_frame_section_index) |_| { + nlocals += 1; + } + + for (files.items) |index| { const file_ptr = self.file(index).?; - file_ptr.updateSymtabSize(self); - sizes.add(file_ptr.shared_object.output_symtab_size); + const ctx = switch (file_ptr) { + inline else => |x| &x.output_symtab_ctx, + }; + ctx.ilocal = nlocals + 1; + ctx.iglobal = nglobals + 1; + try file_ptr.updateSymtabSize(self); + nlocals += ctx.nlocals; + nglobals += ctx.nglobals; + strsize += ctx.strsize; } if (self.zig_got_section_index) |_| { + self.zig_got.output_symtab_ctx.ilocal = nlocals + 1; self.zig_got.updateSymtabSize(self); - sizes.add(self.zig_got.output_symtab_size); + nlocals += self.zig_got.output_symtab_ctx.nlocals; + strsize += self.zig_got.output_symtab_ctx.strsize; } if (self.got_section_index) |_| { + self.got.output_symtab_ctx.ilocal = nlocals + 1; self.got.updateSymtabSize(self); - sizes.add(self.got.output_symtab_size); + nlocals += self.got.output_symtab_ctx.nlocals; + strsize += self.got.output_symtab_ctx.strsize; } if (self.plt_section_index) |_| { + self.plt.output_symtab_ctx.ilocal = nlocals + 1; self.plt.updateSymtabSize(self); - sizes.add(self.plt.output_symtab_size); + nlocals += self.plt.output_symtab_ctx.nlocals; + strsize += self.plt.output_symtab_ctx.strsize; } if (self.plt_got_section_index) |_| { + self.plt_got.output_symtab_ctx.ilocal = nlocals + 1; self.plt_got.updateSymtabSize(self); - sizes.add(self.plt_got.output_symtab_size); + nlocals += self.plt_got.output_symtab_ctx.nlocals; + strsize += self.plt_got.output_symtab_ctx.strsize; } - if (self.linker_defined_index) |index| { + for (files.items) |index| { const file_ptr = self.file(index).?; - file_ptr.updateSymtabSize(self); - sizes.add(file_ptr.linker_defined.output_symtab_size); + const ctx = switch (file_ptr) { + inline else => |x| &x.output_symtab_ctx, + }; + ctx.iglobal += nlocals; } const symtab_shdr = &self.shdrs.items[self.symtab_section_index.?]; - symtab_shdr.sh_info = sizes.nlocals + 1; + symtab_shdr.sh_info = nlocals + 1; symtab_shdr.sh_link = self.strtab_section_index.?; const sym_size: u64 = switch (self.ptr_width) { .p32 => @sizeOf(elf.Elf32_Sym), .p64 => @sizeOf(elf.Elf64_Sym), }; - const needed_size = (sizes.nlocals + sizes.nglobals + 1) * sym_size; + const needed_size = (nlocals + nglobals + 1) * sym_size; symtab_shdr.sh_size = needed_size; const strtab = &self.shdrs.items[self.strtab_section_index.?]; - strtab.sh_size = sizes.strsize + 1; -} - -fn updateSymtabSizeObject(self: *Elf, zig_object: *ZigObject) void { - zig_object.asFile().updateSymtabSize(self); - const sizes = zig_object.output_symtab_size; - - const symtab_shdr = &self.shdrs.items[self.symtab_section_index.?]; - symtab_shdr.sh_info = sizes.nlocals + 1; - symtab_shdr.sh_link = self.strtab_section_index.?; - - const sym_size: u64 = switch (self.ptr_width) { - .p32 => @sizeOf(elf.Elf32_Sym), - .p64 => @sizeOf(elf.Elf64_Sym), - }; - const needed_size = (sizes.nlocals + sizes.nglobals + 1) * sym_size; - symtab_shdr.sh_size = needed_size; - - const strtab = &self.shdrs.items[self.strtab_section_index.?]; - strtab.sh_size = sizes.strsize + 1; + strtab.sh_size = strsize + 1; } fn writeSyntheticSections(self: *Elf) !void { const gpa = self.base.allocator; - if (self.zigObjectPtr()) |zig_object| { - try zig_object.writeRelaSections(self); - } - if (self.interp_section_index) |shndx| { const shdr = self.shdrs.items[shndx]; const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow; @@ -4698,9 +5034,97 @@ fn writeSyntheticSections(self: *Elf) !void { try self.writeShStrtab(); } +fn writeSyntheticSectionsObject(self: *Elf) !void { + const gpa = self.base.allocator; + + for (self.output_rela_sections.values()) |sec| { + if (sec.atom_list.items.len == 0) continue; + + const shdr = self.shdrs.items[sec.shndx]; + + const num_relocs = math.cast(usize, @divExact(shdr.sh_size, shdr.sh_entsize)) orelse + return error.Overflow; + var relocs = try std.ArrayList(elf.Elf64_Rela).initCapacity(gpa, num_relocs); + defer relocs.deinit(); + + for (sec.atom_list.items) |atom_index| { + const atom_ptr = self.atom(atom_index) orelse continue; + if (!atom_ptr.flags.alive) continue; + try atom_ptr.writeRelocs(self, &relocs); + } + assert(relocs.items.len == num_relocs); + + const SortRelocs = struct { + pub fn lessThan(ctx: void, lhs: elf.Elf64_Rela, rhs: elf.Elf64_Rela) bool { + _ = ctx; + return lhs.r_offset < rhs.r_offset; + } + }; + + mem.sort(elf.Elf64_Rela, relocs.items, {}, SortRelocs.lessThan); + + log.debug("writing {s} from 0x{x} to 0x{x}", .{ + self.getShString(shdr.sh_name), + shdr.sh_offset, + shdr.sh_offset + shdr.sh_size, + }); + + try self.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), shdr.sh_offset); + } + + if (self.eh_frame_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow; + var buffer = try std.ArrayList(u8).initCapacity(gpa, sh_size); + defer buffer.deinit(); + try eh_frame.writeEhFrameObject(self, buffer.writer()); + log.debug("writing .eh_frame from 0x{x} to 0x{x}", .{ + shdr.sh_offset, + shdr.sh_offset + shdr.sh_size, + }); + assert(buffer.items.len == sh_size); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } + if (self.eh_frame_rela_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow; + var buffer = try std.ArrayList(u8).initCapacity(gpa, sh_size); + defer buffer.deinit(); + try eh_frame.writeEhFrameRelocs(self, buffer.writer()); + assert(buffer.items.len == sh_size); + log.debug("writing .rela.eh_frame from 0x{x} to 0x{x}", .{ + shdr.sh_offset, + shdr.sh_offset + shdr.sh_size, + }); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } + + try self.writeComdatGroups(); + try self.writeSymtab(); + try self.writeShStrtab(); +} + +fn writeComdatGroups(self: *Elf) !void { + const gpa = self.base.allocator; + for (self.comdat_group_sections.items) |cgs| { + const shdr = self.shdrs.items[cgs.shndx]; + const sh_size = math.cast(usize, shdr.sh_size) orelse return error.Overflow; + var buffer = try std.ArrayList(u8).initCapacity(gpa, sh_size); + defer buffer.deinit(); + try cgs.write(self, buffer.writer()); + assert(buffer.items.len == sh_size); + log.debug("writing COMDAT group from 0x{x} to 0x{x}", .{ + shdr.sh_offset, + shdr.sh_offset + shdr.sh_size, + }); + try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset); + } +} + fn writeShStrtab(self: *Elf) !void { if (self.shstrtab_section_index) |index| { const shdr = self.shdrs.items[index]; + log.debug("writing .shstrtab from 0x{x} to 0x{x}", .{ shdr.sh_offset, shdr.sh_offset + shdr.sh_size }); try self.base.file.?.pwriteAll(self.shstrtab.items, shdr.sh_offset); } } @@ -4715,67 +5139,55 @@ fn writeSymtab(self: *Elf) !void { }; const nsyms = math.cast(usize, @divExact(symtab_shdr.sh_size, sym_size)) orelse return error.Overflow; - log.debug("writing {d} symbols at 0x{x}", .{ nsyms, symtab_shdr.sh_offset }); + log.debug("writing {d} symbols in .symtab from 0x{x} to 0x{x}", .{ + nsyms, + symtab_shdr.sh_offset, + symtab_shdr.sh_offset + symtab_shdr.sh_size, + }); + log.debug("writing .strtab from 0x{x} to 0x{x}", .{ + strtab_shdr.sh_offset, + strtab_shdr.sh_offset + strtab_shdr.sh_size, + }); try self.symtab.resize(gpa, nsyms); const needed_strtab_size = math.cast(usize, strtab_shdr.sh_size - 1) orelse return error.Overflow; try self.strtab.ensureUnusedCapacity(gpa, needed_strtab_size); - const Ctx = struct { - ilocal: usize, - iglobal: usize, - - fn incr(this: *@This(), ss: SymtabSize) void { - this.ilocal += ss.nlocals; - this.iglobal += ss.nglobals; - } - }; - var ctx: Ctx = .{ - .ilocal = 1, - .iglobal = symtab_shdr.sh_info, - }; + self.writeSectionSymbols(); if (self.zigObjectPtr()) |zig_object| { - zig_object.asFile().writeSymtab(self, ctx); - ctx.incr(zig_object.output_symtab_size); + zig_object.asFile().writeSymtab(self); } for (self.objects.items) |index| { const file_ptr = self.file(index).?; - file_ptr.writeSymtab(self, ctx); - ctx.incr(file_ptr.object.output_symtab_size); + file_ptr.writeSymtab(self); } for (self.shared_objects.items) |index| { const file_ptr = self.file(index).?; - file_ptr.writeSymtab(self, ctx); - ctx.incr(file_ptr.shared_object.output_symtab_size); + file_ptr.writeSymtab(self); } if (self.zig_got_section_index) |_| { - self.zig_got.writeSymtab(self, ctx); - ctx.incr(self.zig_got.output_symtab_size); + self.zig_got.writeSymtab(self); } if (self.got_section_index) |_| { - self.got.writeSymtab(self, ctx); - ctx.incr(self.got.output_symtab_size); + self.got.writeSymtab(self); } if (self.plt_section_index) |_| { - self.plt.writeSymtab(self, ctx); - ctx.incr(self.plt.output_symtab_size); + self.plt.writeSymtab(self); } if (self.plt_got_section_index) |_| { - self.plt_got.writeSymtab(self, ctx); - ctx.incr(self.plt_got.output_symtab_size); + self.plt_got.writeSymtab(self); } if (self.linker_defined_index) |index| { const file_ptr = self.file(index).?; - file_ptr.writeSymtab(self, ctx); - ctx.incr(file_ptr.linker_defined.output_symtab_size); + file_ptr.writeSymtab(self); } const foreign_endian = self.base.options.target.cpu.arch.endian() != builtin.cpu.arch.endian(); @@ -4808,52 +5220,42 @@ fn writeSymtab(self: *Elf) !void { try self.base.file.?.pwriteAll(self.strtab.items, strtab_shdr.sh_offset); } -fn writeSymtabObject(self: *Elf, zig_object: *ZigObject) !void { - const gpa = self.base.allocator; - const symtab_shdr = self.shdrs.items[self.symtab_section_index.?]; - const strtab_shdr = self.shdrs.items[self.strtab_section_index.?]; - const sym_size: u64 = switch (self.ptr_width) { - .p32 => @sizeOf(elf.Elf32_Sym), - .p64 => @sizeOf(elf.Elf64_Sym), - }; - const nsyms = math.cast(usize, @divExact(symtab_shdr.sh_size, sym_size)) orelse return error.Overflow; - - log.debug("writing {d} symbols at 0x{x}", .{ nsyms, symtab_shdr.sh_offset }); - - try self.symtab.resize(gpa, nsyms); - const needed_strtab_size = math.cast(usize, strtab_shdr.sh_size - 1) orelse return error.Overflow; - try self.strtab.ensureUnusedCapacity(gpa, needed_strtab_size); - - zig_object.asFile().writeSymtab(self, .{ .ilocal = 1, .iglobal = symtab_shdr.sh_info }); - - const foreign_endian = self.base.options.target.cpu.arch.endian() != builtin.cpu.arch.endian(); - switch (self.ptr_width) { - .p32 => { - const buf = try gpa.alloc(elf.Elf32_Sym, self.symtab.items.len); - defer gpa.free(buf); - - for (buf, self.symtab.items) |*out, sym| { - out.* = .{ - .st_name = sym.st_name, - .st_info = sym.st_info, - .st_other = sym.st_other, - .st_shndx = sym.st_shndx, - .st_value = @as(u32, @intCast(sym.st_value)), - .st_size = @as(u32, @intCast(sym.st_size)), - }; - if (foreign_endian) mem.byteSwapAllFields(elf.Elf32_Sym, out); - } - try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), symtab_shdr.sh_offset); - }, - .p64 => { - if (foreign_endian) { - for (self.symtab.items) |*sym| mem.byteSwapAllFields(elf.Elf64_Sym, sym); - } - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.symtab.items), symtab_shdr.sh_offset); - }, +fn writeSectionSymbols(self: *Elf) void { + var ilocal: u32 = 1; + for (self.output_sections.keys()) |shndx| { + const shdr = self.shdrs.items[shndx]; + const out_sym = &self.symtab.items[ilocal]; + out_sym.* = .{ + .st_name = 0, + .st_value = shdr.sh_addr, + .st_info = elf.STT_SECTION, + .st_shndx = @intCast(shndx), + .st_size = 0, + .st_other = 0, + }; + ilocal += 1; } - try self.base.file.?.pwriteAll(self.strtab.items, strtab_shdr.sh_offset); + if (self.eh_frame_section_index) |shndx| { + const shdr = self.shdrs.items[shndx]; + const out_sym = &self.symtab.items[ilocal]; + out_sym.* = .{ + .st_name = 0, + .st_value = shdr.sh_addr, + .st_info = elf.STT_SECTION, + .st_shndx = @intCast(shndx), + .st_size = 0, + .st_other = 0, + }; + ilocal += 1; + } +} + +pub fn sectionSymbolOutputSymtabIndex(self: Elf, shndx: u32) u32 { + if (self.eh_frame_section_index) |index| { + if (index == shndx) return @intCast(self.output_sections.keys().len + 1); + } + return @intCast(self.output_sections.getIndex(shndx).? + 1); } /// Always 4 or 8 depending on whether this is 32-bit ELF or 64-bit ELF. @@ -5707,13 +6109,69 @@ fn formatShdr( _ = options; _ = unused_fmt_string; const shdr = ctx.shdr; - try writer.print("{s} : @{x} ({x}) : align({x}) : size({x})", .{ + try writer.print("{s} : @{x} ({x}) : align({x}) : size({x}) : flags({})", .{ ctx.elf_file.getShString(shdr.sh_name), shdr.sh_offset, shdr.sh_addr, shdr.sh_addralign, - shdr.sh_size, + shdr.sh_size, fmtShdrFlags(shdr.sh_flags), }); } +pub fn fmtShdrFlags(sh_flags: u64) std.fmt.Formatter(formatShdrFlags) { + return .{ .data = sh_flags }; +} + +fn formatShdrFlags( + sh_flags: u64, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = unused_fmt_string; + _ = options; + if (elf.SHF_WRITE & sh_flags != 0) { + try writer.writeAll("W"); + } + if (elf.SHF_ALLOC & sh_flags != 0) { + try writer.writeAll("A"); + } + if (elf.SHF_EXECINSTR & sh_flags != 0) { + try writer.writeAll("X"); + } + if (elf.SHF_MERGE & sh_flags != 0) { + try writer.writeAll("M"); + } + if (elf.SHF_STRINGS & sh_flags != 0) { + try writer.writeAll("S"); + } + if (elf.SHF_INFO_LINK & sh_flags != 0) { + try writer.writeAll("I"); + } + if (elf.SHF_LINK_ORDER & sh_flags != 0) { + try writer.writeAll("L"); + } + if (elf.SHF_EXCLUDE & sh_flags != 0) { + try writer.writeAll("E"); + } + if (elf.SHF_COMPRESSED & sh_flags != 0) { + try writer.writeAll("C"); + } + if (elf.SHF_GROUP & sh_flags != 0) { + try writer.writeAll("G"); + } + if (elf.SHF_OS_NONCONFORMING & sh_flags != 0) { + try writer.writeAll("O"); + } + if (elf.SHF_TLS & sh_flags != 0) { + try writer.writeAll("T"); + } + if (elf.SHF_X86_64_LARGE & sh_flags != 0) { + try writer.writeAll("l"); + } + if (elf.SHF_MIPS_ADDR & sh_flags != 0 or elf.SHF_ARM_PURECODE & sh_flags != 0) { + try writer.writeAll("p"); + } +} + const FormatPhdrCtx = struct { elf_file: *Elf, phdr: elf.Elf64_Phdr, @@ -5813,9 +6271,14 @@ fn fmtDumpState( try writer.print("{}\n", .{self.got.fmt(self)}); try writer.print("{}\n", .{self.zig_got.fmt(self)}); - try writer.writeAll("Output shdrs\n"); + try writer.writeAll("Output COMDAT groups\n"); + for (self.comdat_group_sections.items) |cg| { + try writer.print(" shdr({d}) : COMDAT({d})\n", .{ cg.shndx, cg.cg_index }); + } + + try writer.writeAll("\nOutput shdrs\n"); for (self.shdrs.items, 0..) |shdr, shndx| { - try writer.print("shdr({d}) : phdr({?d}) : {}\n", .{ + try writer.print(" shdr({d}) : phdr({?d}) : {}\n", .{ shndx, self.phdr_to_shdr_table.get(@intCast(shndx)), self.fmtShdr(shdr), @@ -5823,7 +6286,7 @@ fn fmtDumpState( } try writer.writeAll("\nOutput phdrs\n"); for (self.phdrs.items, 0..) |phdr, phndx| { - try writer.print("phdr{d} : {}\n", .{ phndx, self.fmtPhdr(phdr) }); + try writer.print(" phdr{d} : {}\n", .{ phndx, self.fmtPhdr(phdr) }); } } @@ -5882,16 +6345,12 @@ pub const ComdatGroup = struct { pub const Index = u32; }; -pub const SymtabSize = struct { +pub const SymtabCtx = struct { + ilocal: u32 = 0, + iglobal: u32 = 0, nlocals: u32 = 0, nglobals: u32 = 0, strsize: u32 = 0, - - fn add(ss: *SymtabSize, other: SymtabSize) void { - ss.nlocals += other.nlocals; - ss.nglobals += other.nglobals; - ss.strsize += other.strsize; - } }; pub const null_sym = elf.Elf64_Sym{ @@ -5942,8 +6401,13 @@ const LastAtomAndFreeList = struct { /// by 1 byte. It will then have -1 overcapacity. free_list: std.ArrayListUnmanaged(Atom.Index) = .{}, }; +const LastAtomAndFreeListTable = std.AutoArrayHashMapUnmanaged(u32, LastAtomAndFreeList); -const LastAtomAndFreeListTable = std.AutoArrayHashMapUnmanaged(u16, LastAtomAndFreeList); +const RelaSection = struct { + shndx: u32, + atom_list: std.ArrayListUnmanaged(Atom.Index) = .{}, +}; +const RelaSectionTable = std.AutoArrayHashMapUnmanaged(u32, RelaSection); pub const R_X86_64_ZIG_GOT32 = elf.R_X86_64_NUM + 1; pub const R_X86_64_ZIG_GOTPCREL = elf.R_X86_64_NUM + 2; @@ -5976,6 +6440,7 @@ const Archive = @import("Elf/Archive.zig"); pub const Atom = @import("Elf/Atom.zig"); const Cache = std.Build.Cache; const Compilation = @import("../Compilation.zig"); +const ComdatGroupSection = synthetic_sections.ComdatGroupSection; const CopyRelSection = synthetic_sections.CopyRelSection; const DynamicSection = synthetic_sections.DynamicSection; const DynsymSection = synthetic_sections.DynsymSection; diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 1f6f77cc4b..ec12f17721 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -60,6 +60,11 @@ pub fn inputShdr(self: Atom, elf_file: *Elf) Object.ElfShdr { }; } +pub fn relocsShndx(self: Atom) ?u32 { + if (self.relocs_section_index == 0) return null; + return self.relocs_section_index; +} + pub fn outputShndx(self: Atom) ?u16 { if (self.output_section_index == 0) return null; return self.output_section_index; @@ -280,13 +285,60 @@ pub fn free(self: *Atom, elf_file: *Elf) void { } pub fn relocs(self: Atom, elf_file: *Elf) []align(1) const elf.Elf64_Rela { + const shndx = self.relocsShndx() orelse return &[0]elf.Elf64_Rela{}; return switch (self.file(elf_file).?) { - .zig_object => |x| x.relocs.items[self.relocs_section_index].items, - .object => |x| x.getRelocs(self.relocs_section_index), + .zig_object => |x| x.relocs.items[shndx].items, + .object => |x| x.getRelocs(shndx), else => unreachable, }; } +pub fn writeRelocs(self: Atom, elf_file: *Elf, out_relocs: *std.ArrayList(elf.Elf64_Rela)) !void { + relocs_log.debug("0x{x}: {s}", .{ self.value, self.name(elf_file) }); + + const file_ptr = self.file(elf_file).?; + for (self.relocs(elf_file)) |rel| { + const target_index = switch (file_ptr) { + .zig_object => |x| x.symbol(rel.r_sym()), + .object => |x| x.symbols.items[rel.r_sym()], + else => unreachable, + }; + const target = elf_file.symbol(target_index); + const r_type = switch (rel.r_type()) { + Elf.R_X86_64_ZIG_GOT32, + Elf.R_X86_64_ZIG_GOTPCREL, + => unreachable, // Sanity check if we accidentally emitted those. + else => |r_type| r_type, + }; + const r_offset = self.value + rel.r_offset; + var r_addend = rel.r_addend; + var r_sym: u32 = 0; + switch (target.type(elf_file)) { + elf.STT_SECTION => { + r_addend += @intCast(target.value); + r_sym = elf_file.sectionSymbolOutputSymtabIndex(target.outputShndx().?); + }, + else => { + r_sym = target.outputSymtabIndex(elf_file) orelse 0; + }, + } + + relocs_log.debug(" {s}: [{x} => {d}({s})] + {x}", .{ + fmtRelocType(r_type), + r_offset, + r_sym, + target.name(elf_file), + r_addend, + }); + + out_relocs.appendAssumeCapacity(.{ + .r_offset = r_offset, + .r_addend = r_addend, + .r_info = (@as(u64, @intCast(r_sym)) << 32) | r_type, + }); + } +} + pub fn fdes(self: Atom, elf_file: *Elf) []Fde { if (self.fde_start == self.fde_end) return &[0]Fde{}; const object = self.file(elf_file).?.object; diff --git a/src/link/Elf/LinkerDefined.zig b/src/link/Elf/LinkerDefined.zig index 49e0e8f71d..938f22985f 100644 --- a/src/link/Elf/LinkerDefined.zig +++ b/src/link/Elf/LinkerDefined.zig @@ -3,7 +3,7 @@ symtab: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{}, strtab: std.ArrayListUnmanaged(u8) = .{}, symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, -output_symtab_size: Elf.SymtabSize = .{}, +output_symtab_ctx: Elf.SymtabCtx = .{}, pub fn deinit(self: *LinkerDefined, allocator: Allocator) void { self.symtab.deinit(allocator); diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index e710a81ad3..f595988e82 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -19,7 +19,7 @@ cies: std.ArrayListUnmanaged(Cie) = .{}, alive: bool = true, num_dynrelocs: u32 = 0, -output_symtab_size: Elf.SymtabSize = .{}, +output_symtab_ctx: Elf.SymtabCtx = .{}, output_ar_state: Archive.ArState = .{}, pub fn isObject(path: []const u8) !bool { @@ -142,7 +142,7 @@ fn initAtoms(self: *Object, elf_file: *Elf) !void { const group_nmembers = @divExact(group_raw_data.len, @sizeOf(u32)); const group_members = @as([*]align(1) const u32, @ptrCast(group_raw_data.ptr))[0..group_nmembers]; - if (group_members[0] != 0x1) { // GRP_COMDAT + if (group_members[0] != elf.GRP_COMDAT) { // TODO convert into an error log.debug("{}: unknown SHT_GROUP format", .{self.fmtPath()}); continue; @@ -211,6 +211,7 @@ fn addAtom(self: *Object, shdr: ElfShdr, shndx: u16, elf_file: *Elf) error{OutOf fn initOutputSection(self: Object, elf_file: *Elf, shdr: ElfShdr) error{OutOfMemory}!u16 { const name = blk: { const name = self.getString(shdr.sh_name); + if (elf_file.isRelocatable()) break :blk name; if (shdr.sh_flags & elf.SHF_MERGE != 0) break :blk name; const sh_name_prefixes: []const [:0]const u8 = &.{ ".text", ".data.rel.ro", ".data", ".rodata", ".bss.rel.ro", ".bss", @@ -237,7 +238,10 @@ fn initOutputSection(self: Object, elf_file: *Elf, shdr: ElfShdr) error{OutOfMem else => shdr.sh_type, }; const flags = blk: { - const flags = shdr.sh_flags & ~@as(u64, elf.SHF_COMPRESSED | elf.SHF_GROUP | elf.SHF_GNU_RETAIN); + var flags = shdr.sh_flags; + if (!elf_file.isRelocatable()) { + flags &= ~@as(u64, elf.SHF_COMPRESSED | elf.SHF_GROUP | elf.SHF_GNU_RETAIN); + } break :blk switch (@"type") { elf.SHT_INIT_ARRAY, elf.SHT_FINI_ARRAY => flags | elf.SHF_WRITE, else => flags, @@ -487,6 +491,25 @@ pub fn claimUnresolved(self: *Object, elf_file: *Elf) void { } } +pub fn claimUnresolvedObject(self: *Object, elf_file: *Elf) void { + const first_global = self.first_global orelse return; + for (self.globals(), 0..) |index, i| { + const esym_index = @as(u32, @intCast(first_global + i)); + const esym = self.symtab.items[esym_index]; + if (esym.st_shndx != elf.SHN_UNDEF) continue; + + const global = elf_file.symbol(index); + if (global.file(elf_file)) |file| { + if (global.elfSym(elf_file).st_shndx != elf.SHN_UNDEF or file.index() <= self.index) continue; + } + + global.value = 0; + global.atom_index = 0; + global.esym_index = esym_index; + global.file_index = self.index; + } +} + pub fn markLive(self: *Object, elf_file: *Elf) void { const first_global = self.first_global orelse return; for (self.globals(), 0..) |index, i| { @@ -654,6 +677,40 @@ pub fn allocateAtoms(self: Object, elf_file: *Elf) void { } } +pub fn initRelaSections(self: Object, elf_file: *Elf) !void { + for (self.atoms.items) |atom_index| { + const atom = elf_file.atom(atom_index) orelse continue; + if (!atom.flags.alive) continue; + const shndx = atom.relocsShndx() orelse continue; + const shdr = self.shdrs.items[shndx]; + const out_shndx = try self.initOutputSection(elf_file, shdr); + const out_shdr = &elf_file.shdrs.items[out_shndx]; + out_shdr.sh_addralign = @alignOf(elf.Elf64_Rela); + out_shdr.sh_entsize = @sizeOf(elf.Elf64_Rela); + out_shdr.sh_flags |= elf.SHF_INFO_LINK; + } +} + +pub fn addAtomsToRelaSections(self: Object, elf_file: *Elf) !void { + for (self.atoms.items) |atom_index| { + const atom = elf_file.atom(atom_index) orelse continue; + if (!atom.flags.alive) continue; + const shndx = blk: { + const shndx = atom.relocsShndx() orelse continue; + const shdr = self.shdrs.items[shndx]; + break :blk self.initOutputSection(elf_file, shdr) catch unreachable; + }; + const shdr = &elf_file.shdrs.items[shndx]; + shdr.sh_info = atom.outputShndx().?; + shdr.sh_link = elf_file.symtab_section_index.?; + + const gpa = elf_file.base.allocator; + const gop = try elf_file.output_rela_sections.getOrPut(gpa, atom.outputShndx().?); + if (!gop.found_existing) gop.value_ptr.* = .{ .shndx = shndx }; + try gop.value_ptr.atom_list.append(gpa, atom_index); + } +} + pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, elf_file: *Elf) !void { const gpa = elf_file.base.allocator; const start = self.first_global orelse self.symtab.items.len; @@ -685,11 +742,13 @@ pub fn writeAr(self: Object, writer: anytype) !void { } pub fn locals(self: Object) []const Symbol.Index { + if (self.symbols.items.len == 0) return &[0]Symbol.Index{}; const end = self.first_global orelse self.symbols.items.len; return self.symbols.items[0..end]; } pub fn globals(self: Object) []const Symbol.Index { + if (self.symbols.items.len == 0) return &[0]Symbol.Index{}; const start = self.first_global orelse self.symbols.items.len; return self.symbols.items[start..]; } @@ -881,16 +940,17 @@ fn formatComdatGroups( _ = options; const object = ctx.object; const elf_file = ctx.elf_file; - try writer.writeAll(" comdat groups\n"); + try writer.writeAll(" COMDAT groups\n"); for (object.comdat_groups.items) |cg_index| { const cg = elf_file.comdatGroup(cg_index); const cg_owner = elf_file.comdatGroupOwner(cg.owner); if (cg_owner.file != object.index) continue; + try writer.print(" COMDAT({d})\n", .{cg_index}); const cg_members = object.comdatGroupMembers(cg.shndx); for (cg_members) |shndx| { const atom_index = object.atoms.items[shndx]; const atom = elf_file.atom(atom_index) orelse continue; - try writer.print(" atom({d}) : {s}\n", .{ atom_index, atom.name(elf_file) }); + try writer.print(" atom({d}) : {s}\n", .{ atom_index, atom.name(elf_file) }); } } } diff --git a/src/link/Elf/SharedObject.zig b/src/link/Elf/SharedObject.zig index 1785232625..2317719012 100644 --- a/src/link/Elf/SharedObject.zig +++ b/src/link/Elf/SharedObject.zig @@ -21,7 +21,7 @@ verdef_sect_index: ?u16 = null, needed: bool, alive: bool, -output_symtab_size: Elf.SymtabSize = .{}, +output_symtab_ctx: Elf.SymtabCtx = .{}, pub fn isSharedObject(path: []const u8) !bool { const file = try std.fs.cwd().openFile(path, .{}); diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index 13d9041813..1040515de5 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -107,6 +107,24 @@ pub fn address(symbol: Symbol, opts: struct { plt: bool = true }, elf_file: *Elf return symbol.value; } +pub fn outputSymtabIndex(symbol: Symbol, elf_file: *Elf) ?u32 { + if (!symbol.flags.output_symtab) return null; + const file_ptr = symbol.file(elf_file).?; + const symtab_ctx = switch (file_ptr) { + inline else => |x| x.output_symtab_ctx, + }; + const idx = symbol.extra(elf_file).?.symtab; + return if (symbol.isLocal(elf_file)) idx + symtab_ctx.ilocal else idx + symtab_ctx.iglobal; +} + +pub fn setOutputSymtabIndex(symbol: *Symbol, index: u32, elf_file: *Elf) !void { + if (symbol.extra(elf_file)) |extras| { + var new_extras = extras; + new_extras.symtab = index; + symbol.setExtra(new_extras, elf_file); + } else try symbol.addExtra(.{ .symtab = index }, elf_file); +} + pub fn gotAddress(symbol: Symbol, elf_file: *Elf) u64 { if (!symbol.flags.has_got) return 0; const extras = symbol.extra(elf_file).?; @@ -219,10 +237,8 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void { const st_shndx = blk: { 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; - // TODO I think this is wrong and obsolete - if (elf_file.isRelocatable() and st_type == elf.STT_SECTION) break :blk symbol.outputShndx().?; - if (symbol.atom(elf_file) == null and file_ptr != .linker_defined) - break :blk elf.SHN_ABS; + if (elf_file.isRelocatable() and esym.st_shndx == elf.SHN_COMMON) break :blk elf.SHN_COMMON; + 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: { @@ -231,7 +247,7 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void { if (symbol.flags.is_canonical) break :blk symbol.address(.{}, elf_file); break :blk 0; } - if (st_shndx == elf.SHN_ABS) break :blk symbol.value; + if (st_shndx == elf.SHN_ABS or st_shndx == elf.SHN_COMMON) 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) break :blk symbol.value - elf_file.tlsAddress(); @@ -390,6 +406,7 @@ pub const Extra = struct { plt: u32 = 0, plt_got: u32 = 0, dynamic: u32 = 0, + symtab: u32 = 0, copy_rel: u32 = 0, tlsgd: u32 = 0, gottp: u32 = 0, diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index dd2126a99e..f08e5bacbb 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -18,7 +18,7 @@ relocs: std.ArrayListUnmanaged(std.ArrayListUnmanaged(elf.Elf64_Rela)) = .{}, num_dynrelocs: u32 = 0, -output_symtab_size: Elf.SymtabSize = .{}, +output_symtab_ctx: Elf.SymtabCtx = .{}, output_ar_state: Archive.ArState = .{}, dwarf: ?Dwarf = null, @@ -75,6 +75,7 @@ pub fn init(self: *ZigObject, elf_file: *Elf) !void { const gpa = elf_file.base.allocator; try self.atoms.append(gpa, 0); // null input section + try self.relocs.append(gpa, .{}); // null relocs section try self.strtab.buffer.append(gpa, 0); const name_off = try self.strtab.insert(gpa, self.path); @@ -287,22 +288,6 @@ pub fn addAtom(self: *ZigObject, elf_file: *Elf) !Symbol.Index { return symbol_index; } -pub fn addSectionSymbol(self: *ZigObject, shndx: u16, elf_file: *Elf) !void { - assert(elf_file.isRelocatable()); - const gpa = elf_file.base.allocator; - const symbol_index = try elf_file.addSymbol(); - try self.local_symbols.append(gpa, symbol_index); - const symbol_ptr = elf_file.symbol(symbol_index); - symbol_ptr.file_index = self.index; - symbol_ptr.output_section_index = shndx; - - const esym_index = try self.addLocalEsym(gpa); - const esym = &self.local_esyms.items(.elf_sym)[esym_index]; - esym.st_info = elf.STT_SECTION; - esym.st_shndx = shndx; - symbol_ptr.esym_index = esym_index; -} - /// TODO actually create fake input shdrs and return that instead. pub fn inputShdr(self: ZigObject, atom_index: Atom.Index, elf_file: *Elf) Object.ElfShdr { _ = self; @@ -393,8 +378,7 @@ pub fn claimUnresolvedObject(self: ZigObject, elf_file: *Elf) void { const global = elf_file.symbol(index); if (global.file(elf_file)) |file| { - if (global.elfSym(elf_file).st_shndx != elf.SHN_UNDEF or - file.index() <= self.index) continue; + if (global.elfSym(elf_file).st_shndx != elf.SHN_UNDEF or file.index() <= self.index) continue; } global.value = 0; @@ -549,94 +533,18 @@ pub fn writeAr(self: ZigObject, elf_file: *Elf, writer: anytype) !void { try writer.writeAll(contents); } -pub fn updateRelaSectionSizes(self: ZigObject, elf_file: *Elf) void { - _ = self; +pub fn addAtomsToRelaSections(self: ZigObject, elf_file: *Elf) !void { + for (self.atoms.items) |atom_index| { + const atom = elf_file.atom(atom_index) orelse continue; + if (!atom.flags.alive) continue; + _ = atom.relocsShndx() orelse continue; + const out_shndx = atom.outputShndx().?; + const out_shdr = elf_file.shdrs.items[out_shndx]; + if (out_shdr.sh_type == elf.SHT_NOBITS) continue; - for (&[_]?u16{ - elf_file.zig_text_rela_section_index, - elf_file.zig_data_rel_ro_rela_section_index, - elf_file.zig_data_rela_section_index, - }) |maybe_index| { - const index = maybe_index orelse continue; - const shdr = &elf_file.shdrs.items[index]; - const meta = elf_file.last_atom_and_free_list_table.get(@intCast(shdr.sh_info)).?; - const last_atom_index = meta.last_atom_index; - - var atom = elf_file.atom(last_atom_index) orelse continue; - while (true) { - const relocs = atom.relocs(elf_file); - shdr.sh_size += relocs.len * shdr.sh_entsize; - if (elf_file.atom(atom.prev_index)) |prev| { - atom = prev; - } else break; - } - } - - for (&[_]?u16{ - elf_file.zig_text_rela_section_index, - elf_file.zig_data_rel_ro_rela_section_index, - elf_file.zig_data_rela_section_index, - }) |maybe_index| { - const index = maybe_index orelse continue; - const shdr = &elf_file.shdrs.items[index]; - if (shdr.sh_size == 0) shdr.sh_offset = 0; - } -} - -pub fn writeRelaSections(self: ZigObject, elf_file: *Elf) !void { - const gpa = elf_file.base.allocator; - - for (&[_]?u16{ - elf_file.zig_text_rela_section_index, - elf_file.zig_data_rel_ro_rela_section_index, - elf_file.zig_data_rela_section_index, - }) |maybe_index| { - const index = maybe_index orelse continue; - const shdr = elf_file.shdrs.items[index]; - const meta = elf_file.last_atom_and_free_list_table.get(@intCast(shdr.sh_info)).?; - const last_atom_index = meta.last_atom_index; - - var atom = elf_file.atom(last_atom_index) orelse continue; - - var relocs = std.ArrayList(elf.Elf64_Rela).init(gpa); - defer relocs.deinit(); - try relocs.ensureTotalCapacityPrecise(@intCast(@divExact(shdr.sh_size, shdr.sh_entsize))); - - while (true) { - for (atom.relocs(elf_file)) |rel| { - const target = elf_file.symbol(self.symbol(rel.r_sym())); - const r_offset = atom.value + rel.r_offset; - const r_sym: u32 = if (target.flags.global) - (target.esym_index & symbol_mask) + @as(u32, @intCast(self.local_esyms.slice().len)) - else - target.esym_index; - const r_type = switch (rel.r_type()) { - Elf.R_X86_64_ZIG_GOT32, - Elf.R_X86_64_ZIG_GOTPCREL, - => unreachable, // Sanity check if we accidentally emitted those. - else => |r_type| r_type, - }; - relocs.appendAssumeCapacity(.{ - .r_offset = r_offset, - .r_addend = rel.r_addend, - .r_info = (@as(u64, @intCast(r_sym + 1)) << 32) | r_type, - }); - } - if (elf_file.atom(atom.prev_index)) |prev| { - atom = prev; - } else break; - } - - const SortRelocs = struct { - pub fn lessThan(ctx: void, lhs: elf.Elf64_Rela, rhs: elf.Elf64_Rela) bool { - _ = ctx; - return lhs.r_offset < rhs.r_offset; - } - }; - - mem.sort(elf.Elf64_Rela, relocs.items, {}, SortRelocs.lessThan); - - try elf_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), shdr.sh_offset); + const gpa = elf_file.base.allocator; + const sec = elf_file.output_rela_sections.getPtr(out_shndx).?; + try sec.atom_list.append(gpa, atom_index); } } diff --git a/src/link/Elf/eh_frame.zig b/src/link/Elf/eh_frame.zig index 8c0fa0f769..73857d03d2 100644 --- a/src/link/Elf/eh_frame.zig +++ b/src/link/Elf/eh_frame.zig @@ -268,7 +268,11 @@ pub fn calcEhFrameSize(elf_file: *Elf) !usize { } } - return offset + 4; // NULL terminator + if (!elf_file.isRelocatable()) { + offset += 4; // NULL terminator + } + + return offset; } pub fn calcEhFrameHdrSize(elf_file: *Elf) usize { @@ -282,6 +286,22 @@ pub fn calcEhFrameHdrSize(elf_file: *Elf) usize { return eh_frame_hdr_header_size + count * 8; } +pub fn calcEhFrameRelocs(elf_file: *Elf) usize { + var count: usize = 0; + for (elf_file.objects.items) |index| { + const object = elf_file.file(index).?.object; + for (object.cies.items) |cie| { + if (!cie.alive) continue; + count += cie.relocs(elf_file).len; + } + for (object.fdes.items) |fde| { + if (!fde.alive) continue; + count += fde.relocs(elf_file).len; + } + } + return count; +} + fn resolveReloc(rec: anytype, sym: *const Symbol, rel: elf.Elf64_Rela, elf_file: *Elf, contents: []u8) !void { const offset = std.math.cast(usize, rel.r_offset - rec.offset) orelse return error.Overflow; const P = @as(i64, @intCast(rec.address(elf_file) + offset)); @@ -357,6 +377,95 @@ pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void { try writer.writeInt(u32, 0, .little); } +pub fn writeEhFrameObject(elf_file: *Elf, writer: anytype) !void { + const gpa = elf_file.base.allocator; + + for (elf_file.objects.items) |index| { + const object = elf_file.file(index).?.object; + + for (object.cies.items) |cie| { + if (!cie.alive) continue; + try writer.writeAll(cie.data(elf_file)); + } + } + + for (elf_file.objects.items) |index| { + const object = elf_file.file(index).?.object; + + for (object.fdes.items) |fde| { + if (!fde.alive) continue; + + const contents = try gpa.dupe(u8, fde.data(elf_file)); + defer gpa.free(contents); + + std.mem.writeInt( + i32, + contents[4..8], + @truncate(@as(i64, @intCast(fde.out_offset + 4)) - @as(i64, @intCast(fde.cie(elf_file).out_offset))), + .little, + ); + + try writer.writeAll(contents); + } + } +} + +fn emitReloc(elf_file: *Elf, rec: anytype, sym: *const Symbol, rel: elf.Elf64_Rela) elf.Elf64_Rela { + const r_offset = rec.address(elf_file) + rel.r_offset - rec.offset; + const r_type = rel.r_type(); + var r_addend = rel.r_addend; + var r_sym: u32 = 0; + switch (sym.type(elf_file)) { + elf.STT_SECTION => { + r_addend += @intCast(sym.value); + r_sym = elf_file.sectionSymbolOutputSymtabIndex(sym.outputShndx().?); + }, + else => { + r_sym = sym.outputSymtabIndex(elf_file) orelse 0; + }, + } + + relocs_log.debug(" {s}: [{x} => {d}({s})] + {x}", .{ + Atom.fmtRelocType(r_type), + r_offset, + r_sym, + sym.name(elf_file), + r_addend, + }); + + return .{ + .r_offset = r_offset, + .r_addend = r_addend, + .r_info = (@as(u64, @intCast(r_sym)) << 32) | r_type, + }; +} + +pub fn writeEhFrameRelocs(elf_file: *Elf, writer: anytype) !void { + relocs_log.debug("{x}: .eh_frame", .{elf_file.shdrs.items[elf_file.eh_frame_section_index.?].sh_addr}); + + for (elf_file.objects.items) |index| { + const object = elf_file.file(index).?.object; + + for (object.cies.items) |cie| { + if (!cie.alive) continue; + for (cie.relocs(elf_file)) |rel| { + const sym = elf_file.symbol(object.symbols.items[rel.r_sym()]); + const out_rel = emitReloc(elf_file, cie, sym, rel); + try writer.writeStruct(out_rel); + } + } + + for (object.fdes.items) |fde| { + if (!fde.alive) continue; + for (fde.relocs(elf_file)) |rel| { + const sym = elf_file.symbol(object.symbols.items[rel.r_sym()]); + const out_rel = emitReloc(elf_file, fde, sym, rel); + try writer.writeStruct(out_rel); + } + } + } +} + pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void { try writer.writeByte(1); // version try writer.writeByte(EH_PE.pcrel | EH_PE.sdata4); diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index cc0b486692..29a76daad9 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -127,22 +127,22 @@ pub const File = union(enum) { }; } - pub fn updateSymtabSize(file: File, elf_file: *Elf) void { - const output_symtab_size = switch (file) { - inline else => |x| &x.output_symtab_size, + pub fn updateSymtabSize(file: File, elf_file: *Elf) !void { + const output_symtab_ctx = switch (file) { + inline else => |x| &x.output_symtab_ctx, }; for (file.locals()) |local_index| { const local = elf_file.symbol(local_index); if (local.atom(elf_file)) |atom| if (!atom.flags.alive) continue; const esym = local.elfSym(elf_file); switch (esym.st_type()) { - elf.STT_SECTION => if (!elf_file.isRelocatable()) continue, - elf.STT_NOTYPE => continue, + elf.STT_SECTION, elf.STT_NOTYPE => continue, else => {}, } local.flags.output_symtab = true; - output_symtab_size.nlocals += 1; - output_symtab_size.strsize += @as(u32, @intCast(local.name(elf_file).len)) + 1; + try local.setOutputSymtabIndex(output_symtab_ctx.nlocals, elf_file); + output_symtab_ctx.nlocals += 1; + output_symtab_ctx.strsize += @as(u32, @intCast(local.name(elf_file).len)) + 1; } for (file.globals()) |global_index| { @@ -152,47 +152,38 @@ pub const File = union(enum) { if (global.atom(elf_file)) |atom| if (!atom.flags.alive) continue; global.flags.output_symtab = true; if (global.isLocal(elf_file)) { - output_symtab_size.nlocals += 1; + try global.setOutputSymtabIndex(output_symtab_ctx.nlocals, elf_file); + output_symtab_ctx.nlocals += 1; } else { - output_symtab_size.nglobals += 1; + try global.setOutputSymtabIndex(output_symtab_ctx.nglobals, elf_file); + output_symtab_ctx.nglobals += 1; } - output_symtab_size.strsize += @as(u32, @intCast(global.name(elf_file).len)) + 1; + output_symtab_ctx.strsize += @as(u32, @intCast(global.name(elf_file).len)) + 1; } } - pub fn writeSymtab(file: File, elf_file: *Elf, ctx: anytype) void { - var ilocal: usize = ctx.ilocal; + pub fn writeSymtab(file: File, elf_file: *Elf) void { for (file.locals()) |local_index| { const local = elf_file.symbol(local_index); - if (!local.flags.output_symtab) continue; - const out_sym = &elf_file.symtab.items[ilocal]; + const idx = local.outputSymtabIndex(elf_file) orelse continue; + const out_sym = &elf_file.symtab.items[idx]; out_sym.st_name = @intCast(elf_file.strtab.items.len); elf_file.strtab.appendSliceAssumeCapacity(local.name(elf_file)); elf_file.strtab.appendAssumeCapacity(0); local.setOutputSym(elf_file, out_sym); - ilocal += 1; } - var iglobal: usize = ctx.iglobal; for (file.globals()) |global_index| { const global = elf_file.symbol(global_index); const file_ptr = global.file(elf_file) orelse continue; if (file_ptr.index() != file.index()) continue; - if (!global.flags.output_symtab) continue; + const idx = global.outputSymtabIndex(elf_file) orelse continue; const st_name = @as(u32, @intCast(elf_file.strtab.items.len)); elf_file.strtab.appendSliceAssumeCapacity(global.name(elf_file)); elf_file.strtab.appendAssumeCapacity(0); - if (global.isLocal(elf_file)) { - const out_sym = &elf_file.symtab.items[ilocal]; - out_sym.st_name = st_name; - global.setOutputSym(elf_file, out_sym); - ilocal += 1; - } else { - const out_sym = &elf_file.symtab.items[iglobal]; - out_sym.st_name = st_name; - global.setOutputSym(elf_file, out_sym); - iglobal += 1; - } + const out_sym = &elf_file.symtab.items[idx]; + out_sym.st_name = st_name; + global.setOutputSym(elf_file, out_sym); } } diff --git a/src/link/Elf/synthetic_sections.zig b/src/link/Elf/synthetic_sections.zig index 2602940d41..7419709afc 100644 --- a/src/link/Elf/synthetic_sections.zig +++ b/src/link/Elf/synthetic_sections.zig @@ -222,7 +222,7 @@ pub const DynamicSection = struct { pub const ZigGotSection = struct { entries: std.ArrayListUnmanaged(Symbol.Index) = .{}, - output_symtab_size: Elf.SymtabSize = .{}, + output_symtab_ctx: Elf.SymtabCtx = .{}, flags: Flags = .{}, const Flags = packed struct { @@ -359,15 +359,15 @@ pub const ZigGotSection = struct { } pub fn updateSymtabSize(zig_got: *ZigGotSection, elf_file: *Elf) void { - zig_got.output_symtab_size.nlocals = @as(u32, @intCast(zig_got.entries.items.len)); + zig_got.output_symtab_ctx.nlocals = @as(u32, @intCast(zig_got.entries.items.len)); for (zig_got.entries.items) |entry| { const name = elf_file.symbol(entry).name(elf_file); - zig_got.output_symtab_size.strsize += @as(u32, @intCast(name.len + "$ziggot".len)) + 1; + zig_got.output_symtab_ctx.strsize += @as(u32, @intCast(name.len + "$ziggot".len)) + 1; } } - pub fn writeSymtab(zig_got: ZigGotSection, elf_file: *Elf, ctx: anytype) void { - for (zig_got.entries.items, ctx.ilocal.., 0..) |entry, ilocal, index| { + pub fn writeSymtab(zig_got: ZigGotSection, elf_file: *Elf) void { + for (zig_got.entries.items, zig_got.output_symtab_ctx.ilocal.., 0..) |entry, ilocal, index| { const symbol = elf_file.symbol(entry); const symbol_name = symbol.name(elf_file); const st_name = @as(u32, @intCast(elf_file.strtab.items.len)); @@ -420,7 +420,7 @@ pub const ZigGotSection = struct { pub const GotSection = struct { entries: std.ArrayListUnmanaged(Entry) = .{}, - output_symtab_size: Elf.SymtabSize = .{}, + output_symtab_ctx: Elf.SymtabCtx = .{}, tlsld_index: ?u32 = null, flags: Flags = .{}, @@ -760,18 +760,18 @@ pub const GotSection = struct { } pub fn updateSymtabSize(got: *GotSection, elf_file: *Elf) void { - got.output_symtab_size.nlocals = @as(u32, @intCast(got.entries.items.len)); + got.output_symtab_ctx.nlocals = @as(u32, @intCast(got.entries.items.len)); for (got.entries.items) |entry| { const symbol_name = switch (entry.tag) { .tlsld => "", inline else => elf_file.symbol(entry.symbol_index).name(elf_file), }; - got.output_symtab_size.strsize += @as(u32, @intCast(symbol_name.len + @tagName(entry.tag).len)) + 1 + 1; + got.output_symtab_ctx.strsize += @as(u32, @intCast(symbol_name.len + @tagName(entry.tag).len)) + 1 + 1; } } - pub fn writeSymtab(got: GotSection, elf_file: *Elf, ctx: anytype) void { - for (got.entries.items, ctx.ilocal..) |entry, ilocal| { + pub fn writeSymtab(got: GotSection, elf_file: *Elf) void { + for (got.entries.items, got.output_symtab_ctx.ilocal..) |entry, ilocal| { const symbol = switch (entry.tag) { .tlsld => null, inline else => elf_file.symbol(entry.symbol_index), @@ -831,7 +831,7 @@ pub const GotSection = struct { pub const PltSection = struct { symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, - output_symtab_size: Elf.SymtabSize = .{}, + output_symtab_ctx: Elf.SymtabCtx = .{}, pub const preamble_size = 32; @@ -909,15 +909,15 @@ pub const PltSection = struct { } pub fn updateSymtabSize(plt: *PltSection, elf_file: *Elf) void { - plt.output_symtab_size.nlocals = @as(u32, @intCast(plt.symbols.items.len)); + plt.output_symtab_ctx.nlocals = @as(u32, @intCast(plt.symbols.items.len)); for (plt.symbols.items) |sym_index| { const name = elf_file.symbol(sym_index).name(elf_file); - plt.output_symtab_size.strsize += @as(u32, @intCast(name.len + "$plt".len)) + 1; + plt.output_symtab_ctx.strsize += @as(u32, @intCast(name.len + "$plt".len)) + 1; } } - pub fn writeSymtab(plt: PltSection, elf_file: *Elf, ctx: anytype) void { - var ilocal = ctx.ilocal; + pub fn writeSymtab(plt: PltSection, elf_file: *Elf) void { + var ilocal = plt.output_symtab_ctx.ilocal; for (plt.symbols.items) |sym_index| { const sym = elf_file.symbol(sym_index); const st_name = @as(u32, @intCast(elf_file.strtab.items.len)); @@ -968,7 +968,7 @@ pub const GotPltSection = struct { pub const PltGotSection = struct { symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, - output_symtab_size: Elf.SymtabSize = .{}, + output_symtab_ctx: Elf.SymtabCtx = .{}, pub fn deinit(plt_got: *PltGotSection, allocator: Allocator) void { plt_got.symbols.deinit(allocator); @@ -1008,15 +1008,15 @@ pub const PltGotSection = struct { } pub fn updateSymtabSize(plt_got: *PltGotSection, elf_file: *Elf) void { - plt_got.output_symtab_size.nlocals = @as(u32, @intCast(plt_got.symbols.items.len)); + plt_got.output_symtab_ctx.nlocals = @as(u32, @intCast(plt_got.symbols.items.len)); for (plt_got.symbols.items) |sym_index| { const name = elf_file.symbol(sym_index).name(elf_file); - plt_got.output_symtab_size.strsize += @as(u32, @intCast(name.len + "$pltgot".len)) + 1; + plt_got.output_symtab_ctx.strsize += @as(u32, @intCast(name.len + "$pltgot".len)) + 1; } } - pub fn writeSymtab(plt_got: PltGotSection, elf_file: *Elf, ctx: anytype) void { - var ilocal = ctx.ilocal; + pub fn writeSymtab(plt_got: PltGotSection, elf_file: *Elf) void { + var ilocal = plt_got.output_symtab_ctx.ilocal; for (plt_got.symbols.items) |sym_index| { const sym = elf_file.symbol(sym_index); const st_name = @as(u32, @intCast(elf_file.strtab.items.len)); @@ -1499,6 +1499,54 @@ pub const VerneedSection = struct { } }; +pub const ComdatGroupSection = struct { + shndx: u32, + cg_index: u32, + + fn file(cgs: ComdatGroupSection, elf_file: *Elf) ?File { + const cg = elf_file.comdatGroup(cgs.cg_index); + const cg_owner = elf_file.comdatGroupOwner(cg.owner); + return elf_file.file(cg_owner.file); + } + + pub fn symbol(cgs: ComdatGroupSection, elf_file: *Elf) Symbol.Index { + const cg = elf_file.comdatGroup(cgs.cg_index); + const object = cgs.file(elf_file).?.object; + const shdr = object.shdrs.items[cg.shndx]; + return object.symbols.items[shdr.sh_info]; + } + + pub fn size(cgs: ComdatGroupSection, elf_file: *Elf) usize { + const cg = elf_file.comdatGroup(cgs.cg_index); + const object = cgs.file(elf_file).?.object; + const members = object.comdatGroupMembers(cg.shndx); + return (members.len + 1) * @sizeOf(u32); + } + + pub fn write(cgs: ComdatGroupSection, elf_file: *Elf, writer: anytype) !void { + const cg = elf_file.comdatGroup(cgs.cg_index); + const object = cgs.file(elf_file).?.object; + const members = object.comdatGroupMembers(cg.shndx); + try writer.writeInt(u32, elf.GRP_COMDAT, .little); + for (members) |shndx| { + const shdr = object.shdrs.items[shndx]; + switch (shdr.sh_type) { + elf.SHT_RELA => { + const atom_index = object.atoms.items[shdr.sh_info]; + const atom = elf_file.atom(atom_index).?; + const rela = elf_file.output_rela_sections.get(atom.outputShndx().?).?; + try writer.writeInt(u32, rela.shndx, .little); + }, + else => { + const atom_index = object.atoms.items[shndx]; + const atom = elf_file.atom(atom_index).?; + try writer.writeInt(u32, atom.outputShndx().?, .little); + }, + } + } + } +}; + 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(); diff --git a/test/link/elf.zig b/test/link/elf.zig index 02eae58a06..09847e382c 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -21,6 +21,11 @@ pub fn build(b: *Build) void { .abi = .gnu, }; + // Exercise linker in -r mode + elf_step.dependOn(testEmitRelocatable(b, .{ .use_llvm = false, .target = musl_target })); + elf_step.dependOn(testEmitRelocatable(b, .{ .target = musl_target })); + elf_step.dependOn(testRelocatableEhFrame(b, .{ .target = musl_target })); + // Exercise linker in ar mode elf_step.dependOn(testEmitStaticLib(b, .{ .target = musl_target })); @@ -629,6 +634,53 @@ fn testDsoUndef(b: *Build, opts: Options) *Step { return test_step; } +fn testEmitRelocatable(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "emit-relocatable", opts); + + const obj1 = addObject(b, "obj1", opts); + addZigSourceBytes(obj1, + \\const std = @import("std"); + \\extern var bar: i32; + \\export fn foo() i32 { + \\ return bar; + \\} + \\export fn printFoo() void { + \\ std.debug.print("foo={d}\n", .{foo()}); + \\} + ); + addCSourceBytes(obj1, + \\#include + \\int bar = 42; + \\void printBar() { + \\ fprintf(stderr, "bar=%d\n", bar); + \\} + , &.{}); + obj1.linkLibC(); + + const exe = addExecutable(b, "test", opts); + addZigSourceBytes(exe, + \\const std = @import("std"); + \\extern fn printFoo() void; + \\extern fn printBar() void; + \\pub fn main() void { + \\ printFoo(); + \\ printBar(); + \\} + ); + exe.addObject(obj1); + exe.linkLibC(); + + const run = addRunArtifact(exe); + run.expectStdErrEqual( + \\foo=42 + \\bar=42 + \\ + ); + test_step.dependOn(&run.step); + + return test_step; +} + fn testEmitStaticLib(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "emit-static-lib", opts); @@ -951,7 +1003,6 @@ fn testGcSectionsZig(b: *Build, opts: Options) *Step { const obj = addObject(b, "obj", .{ .target = opts.target, .use_llvm = true, - .use_lld = true, }); addCSourceBytes(obj, \\int live_var1 = 1; @@ -2089,6 +2140,89 @@ fn testPreinitArray(b: *Build, opts: Options) *Step { return test_step; } +fn testRelocatableEhFrame(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "relocatable-eh-frame", opts); + + { + const obj = addObject(b, "obj1", opts); + addCppSourceBytes(obj, + \\#include + \\int try_me() { + \\ throw std::runtime_error("Oh no!"); + \\} + , &.{}); + addCppSourceBytes(obj, + \\extern int try_me(); + \\int try_again() { + \\ return try_me(); + \\} + , &.{}); + obj.linkLibCpp(); + + const exe = addExecutable(b, "test1", opts); + addCppSourceBytes(exe, + \\#include + \\#include + \\extern int try_again(); + \\int main() { + \\ try { + \\ try_again(); + \\ } catch (const std::exception &e) { + \\ std::cout << "exception=" << e.what(); + \\ } + \\ return 0; + \\} + , &.{}); + exe.addObject(obj); + exe.linkLibCpp(); + + const run = addRunArtifact(exe); + run.expectStdOutEqual("exception=Oh no!"); + test_step.dependOn(&run.step); + } + + { + // Let's make the object file COMDAT group heavy! + const obj = addObject(b, "obj2", opts); + addCppSourceBytes(obj, + \\#include + \\int try_me() { + \\ throw std::runtime_error("Oh no!"); + \\} + , &.{}); + addCppSourceBytes(obj, + \\extern int try_me(); + \\int try_again() { + \\ return try_me(); + \\} + , &.{}); + addCppSourceBytes(obj, + \\#include + \\#include + \\extern int try_again(); + \\int main() { + \\ try { + \\ try_again(); + \\ } catch (const std::exception &e) { + \\ std::cout << "exception=" << e.what(); + \\ } + \\ return 0; + \\} + , &.{}); + obj.linkLibCpp(); + + const exe = addExecutable(b, "test2", opts); + exe.addObject(obj); + exe.linkLibCpp(); + + const run = addRunArtifact(exe); + run.expectStdOutEqual("exception=Oh no!"); + test_step.dependOn(&run.step); + } + + return test_step; +} + fn testSharedAbsSymbol(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "shared-abs-symbol", opts);