diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 7ef0bd558c..b2646d11d8 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -9,6 +9,7 @@ llvm_object: ?*LlvmObject = null, files: std.MultiArrayList(File.Entry) = .{}, zig_module_index: ?File.Index = null, linker_defined_index: ?File.Index = null, +objects: std.ArrayListUnmanaged(File.Index) = .{}, /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. @@ -51,11 +52,12 @@ got: GotSection = .{}, text_section_index: ?u16 = null, rodata_section_index: ?u16 = null, data_section_index: ?u16 = null, +eh_frame_section_index: ?u16 = null, +eh_frame_hdr_section_index: ?u16 = null, dynamic_section_index: ?u16 = null, got_section_index: ?u16 = null, got_plt_section_index: ?u16 = null, plt_section_index: ?u16 = null, -eh_frame_hdr_section_index: ?u16 = null, rela_dyn_section_index: ?u16 = null, debug_info_section_index: ?u16 = null, debug_abbrev_section_index: ?u16 = null, @@ -136,6 +138,10 @@ last_atom_and_free_list_table: std.AutoArrayHashMapUnmanaged(u16, LastAtomAndFre /// with `Decl` `main`, and lives as long as that `Decl`. unnamed_consts: UnnamedConstTable = .{}, +comdat_groups: std.ArrayListUnmanaged(ComdatGroup) = .{}, +comdat_groups_owners: std.ArrayListUnmanaged(ComdatGroupOwner) = .{}, +comdat_groups_table: std.AutoHashMapUnmanaged(u32, ComdatGroupOwner.Index) = .{}, + const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(Symbol.Index)); const LazySymbolTable = std.AutoArrayHashMapUnmanaged(Module.Decl.OptionalIndex, LazySymbolMetadata); @@ -249,10 +255,11 @@ pub fn deinit(self: *Elf) void { .null => {}, .zig_module => data.zig_module.deinit(gpa), .linker_defined => data.linker_defined.deinit(gpa), - // .object => data.object.deinit(gpa), + .object => data.object.deinit(gpa), // .shared_object => data.shared_object.deinit(gpa), }; self.files.deinit(gpa); + self.objects.deinit(gpa); self.shdrs.deinit(gpa); self.phdr_to_shdr_table.deinit(gpa); @@ -295,6 +302,9 @@ pub fn deinit(self: *Elf) void { } self.misc_errors.deinit(gpa); + self.comdat_groups.deinit(gpa); + self.comdat_groups_owners.deinit(gpa); + self.comdat_groups_table.deinit(gpa); } pub fn getDeclVAddr(self: *Elf, decl_index: Module.Decl.Index, reloc_info: link.File.RelocInfo) !u64 { @@ -828,7 +838,7 @@ pub fn populateMissingMetadata(self: *Elf) !void { const zig_module = self.file(index).?.zig_module; const sym_index = try zig_module.addLocal(self); const sym = self.symbol(sym_index); - const esym = sym.sourceSymbol(self); + const esym = zig_module.sourceSymbol(sym_index, self); const name_off = try self.strtab.insert(gpa, std.fs.path.stem(module.main_pkg.root_src_path)); sym.name_offset = name_off; esym.st_name = name_off; @@ -1276,14 +1286,35 @@ fn parsePositional( ) ParseError!void { const tracy = trace(@src()); defer tracy.end(); - - _ = self; - _ = in_file; - _ = path; _ = must_link; - _ = ctx; - return error.UnknownFileType; + if (Object.isObject(in_file)) { + try self.parseObject(in_file, path, ctx); + } else return error.UnknownFileType; +} + +fn parseObject(self: *Elf, in_file: std.fs.File, path: []const u8, ctx: *ParseErrorCtx) ParseError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.base.allocator; + const data = try in_file.readToEndAlloc(gpa, std.math.maxInt(u32)); + const index = @as(File.Index, @intCast(self.files.slice().len)); + + var object = Object{ + .path = path, + .data = data, + .index = index, + }; + errdefer object.deinit(gpa); + try object.parse(self); + + ctx.detected_cpu_arch = object.header.?.e_machine.toTargetCpuArch().?; + if (ctx.detected_cpu_arch != self.base.options.target.cpu.arch) return error.InvalidCpuArch; + + _ = try self.files.addOne(gpa); + self.files.set(index, .{ .object = object }); + try self.objects.append(gpa, index); } fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -2226,6 +2257,7 @@ fn updateDeclCode( ) !void { const gpa = self.base.allocator; const mod = self.base.options.module.?; + const zig_module = self.file(self.zig_module_index.?).?.zig_module; const decl = mod.declPtr(decl_index); const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod)); @@ -2234,7 +2266,7 @@ fn updateDeclCode( const required_alignment = decl.getAlignment(mod); const sym = self.symbol(sym_index); - const esym = sym.sourceSymbol(self); + const esym = zig_module.sourceSymbol(sym_index, self); const atom_ptr = sym.atom(self).?; const shdr_index = sym.output_section_index; @@ -2447,6 +2479,7 @@ pub fn updateDecl( fn updateLazySymbol(self: *Elf, sym: link.File.LazySymbol, symbol_index: Symbol.Index) !void { const gpa = self.base.allocator; const mod = self.base.options.module.?; + const zig_module = self.file(self.zig_module_index.?).?.zig_module; var required_alignment: u32 = undefined; var code_buffer = std.ArrayList(u8).init(gpa); @@ -2490,7 +2523,7 @@ fn updateLazySymbol(self: *Elf, sym: link.File.LazySymbol, symbol_index: Symbol. const local_sym = self.symbol(symbol_index); const phdr_index = self.phdr_to_shdr_table.get(local_sym.output_section_index).?; local_sym.name_offset = name_str_index; - const local_esym = local_sym.sourceSymbol(self); + const local_esym = zig_module.sourceSymbol(symbol_index, self); local_esym.st_name = name_str_index; local_esym.st_info |= elf.STT_OBJECT; local_esym.st_size = code.len; @@ -2566,7 +2599,7 @@ pub fn lowerUnnamedConst(self: *Elf, typed_value: TypedValue, decl_index: Module const phdr_index = self.phdr_to_shdr_table.get(shdr_index).?; const local_sym = self.symbol(sym_index); local_sym.name_offset = name_str_index; - const local_esym = local_sym.sourceSymbol(self); + const local_esym = zig_module.sourceSymbol(sym_index, self); local_esym.st_name = name_str_index; local_esym.st_info |= elf.STT_OBJECT; local_esym.st_size = code.len; @@ -2650,8 +2683,8 @@ pub fn updateDeclExports( }; const stt_bits: u8 = @as(u4, @truncate(decl_esym.st_info)); + const zig_module = self.file(self.zig_module_index.?).?.zig_module; const sym_index = if (decl_metadata.@"export"(self, exp_name)) |exp_index| exp_index.* else blk: { - const zig_module = self.file(self.zig_module_index.?).?.zig_module; const sym_index = try zig_module.addGlobal(exp_name, self); try decl_metadata.exports.append(gpa, sym_index); break :blk sym_index; @@ -2660,8 +2693,8 @@ pub fn updateDeclExports( sym.value = decl_sym.value; sym.atom_index = decl_sym.atom_index; sym.output_section_index = decl_sym.output_section_index; - const esym = sym.sourceSymbol(self); - esym.* = decl_esym.*; + const esym = zig_module.sourceSymbol(sym_index, self); + esym.* = decl_esym; esym.st_info = (stb_bits << 4) | stt_bits; } } @@ -2691,11 +2724,12 @@ pub fn deleteDeclExport( const metadata = self.decls.getPtr(decl_index) orelse return; const gpa = self.base.allocator; const mod = self.base.options.module.?; + const zig_module = self.file(self.zig_module_index.?).?.zig_module; const exp_name = mod.intern_pool.stringToSlice(name); const sym_index = metadata.@"export"(self, exp_name) orelse return; log.debug("deleting export '{s}'", .{exp_name}); const sym = self.symbol(sym_index.*); - const esym = sym.sourceSymbol(self); + const esym = zig_module.sourceSymbol(sym_index.*, self); assert(self.resolver.fetchSwapRemove(sym.name_offset) != null); // TODO don't delete it if it's not dominant sym.* = .{}; // TODO free list for esym! @@ -3337,6 +3371,7 @@ pub fn file(self: *Elf, index: File.Index) ?File { .null => null, .linker_defined => .{ .linker_defined = &self.files.items(.data)[index].linker_defined }, .zig_module => .{ .zig_module = &self.files.items(.data)[index].zig_module }, + .object => .{ .object = &self.files.items(.data)[index].object }, }; } @@ -3442,6 +3477,42 @@ pub fn getGlobalSymbol(self: *Elf, name: []const u8, lib_name: ?[]const u8) !u32 return gop.index; } +const GetOrCreateComdatGroupOwnerResult = struct { + found_existing: bool, + index: ComdatGroupOwner.Index, +}; + +pub fn getOrCreateComdatGroupOwner(self: *Elf, off: u32) !GetOrCreateComdatGroupOwnerResult { + const gpa = self.base.allocator; + const gop = try self.comdat_groups_table.getOrPut(gpa, off); + if (!gop.found_existing) { + const index = @as(ComdatGroupOwner.Index, @intCast(self.comdat_groups_owners.items.len)); + const owner = try self.comdat_groups_owners.addOne(gpa); + owner.* = .{}; + gop.value_ptr.* = index; + } + return .{ + .found_existing = gop.found_existing, + .index = gop.value_ptr.*, + }; +} + +pub fn addComdatGroup(self: *Elf) !ComdatGroup.Index { + const index = @as(ComdatGroup.Index, @intCast(self.comdat_groups.items.len)); + _ = try self.comdat_groups.addOne(self.base.allocator); + return index; +} + +pub fn comdatGroup(self: *Elf, index: ComdatGroup.Index) *ComdatGroup { + assert(index < self.comdat_groups.items.len); + return &self.comdat_groups.items[index]; +} + +pub fn comdatGroupOwner(self: *Elf, index: ComdatGroupOwner.Index) *ComdatGroupOwner { + assert(index < self.comdat_groups_owners.items.len); + return &self.comdat_groups_owners.items[index]; +} + fn reportUndefined(self: *Elf) !void { const gpa = self.base.allocator; const max_notes = 4; @@ -3552,6 +3623,21 @@ fn fmtDumpState( try writer.print("zig_module({d}) : {s}\n", .{ index, zig_module.path }); try writer.print("{}\n", .{zig_module.fmtSymtab(self)}); } + + for (self.objects.items) |index| { + const object = self.file(index).?.object; + try writer.print("object({d}) : {}", .{ index, object.fmtPath() }); + if (!object.alive) try writer.writeAll(" : [*]"); + try writer.writeByte('\n'); + try writer.print("{}{}{}{}{}\n", .{ + object.fmtAtoms(self), + object.fmtCies(self), + object.fmtFdes(self), + object.fmtSymtab(self), + object.fmtComdatGroups(self), + }); + } + if (self.linker_defined_index) |index| { const linker_defined = self.file(index).?.linker_defined; try writer.print("linker_defined({d}) : (linker defined)\n", .{index}); @@ -3560,6 +3646,37 @@ fn fmtDumpState( try writer.print("{}\n", .{self.got.fmt(self)}); } +/// Binary search +pub fn bsearch(comptime T: type, haystack: []align(1) const T, predicate: anytype) usize { + if (!@hasDecl(@TypeOf(predicate), "predicate")) + @compileError("Predicate is required to define fn predicate(@This(), T) bool"); + + var min: usize = 0; + var max: usize = haystack.len; + while (min < max) { + const index = (min + max) / 2; + const curr = haystack[index]; + if (predicate.predicate(curr)) { + min = index + 1; + } else { + max = index; + } + } + return min; +} + +/// Linear search +pub fn lsearch(comptime T: type, haystack: []align(1) const T, predicate: anytype) usize { + if (!@hasDecl(@TypeOf(predicate), "predicate")) + @compileError("Predicate is required to define fn predicate(@This(), T) bool"); + + var i: usize = 0; + while (i < haystack.len) : (i += 1) { + if (predicate.predicate(haystack[i])) break; + } + return i; +} + const default_entry_addr = 0x8000000; pub const base_tag: link.File.Tag = .elf; @@ -3607,6 +3724,17 @@ const DeclMetadata = struct { } }; +const ComdatGroupOwner = struct { + file: File.Index = 0, + const Index = u32; +}; + +pub const ComdatGroup = struct { + owner: ComdatGroupOwner.Index, + shndx: u16, + pub const Index = u32; +}; + pub const SymtabSize = struct { nlocals: u32 = 0, nglobals: u32 = 0, @@ -3654,6 +3782,7 @@ const LinkerDefined = @import("Elf/LinkerDefined.zig"); const Liveness = @import("../Liveness.zig"); const LlvmObject = @import("../codegen/llvm.zig").Object; const Module = @import("../Module.zig"); +const Object = @import("Elf/Object.zig"); const InternPool = @import("../InternPool.zig"); const Package = @import("../Package.zig"); const Symbol = @import("Elf/Symbol.zig"); diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index dacbb369df..c163aa8ecc 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -46,6 +46,46 @@ pub fn name(self: Atom, elf_file: *Elf) []const u8 { return elf_file.strtab.getAssumeExists(self.name_offset); } +pub fn inputShdr(self: Atom, elf_file: *Elf) elf.Elf64_Shdr { + const object = elf_file.file(self.file_index).?.object; + return object.shdrs.items[self.input_section_index]; +} + +pub fn codeInObject(self: Atom, elf_file: *Elf) []const u8 { + const object = elf_file.file(self.file_index).?.object; + return object.shdrContents(self.input_section_index); +} + +/// Returns atom's code and optionally uncompresses data if required (for compressed sections). +/// Caller owns the memory. +pub fn codeInObjectUncompressAlloc(self: Atom, elf_file: *Elf) ![]u8 { + const gpa = elf_file.base.allocator; + const data = self.codeInObject(elf_file); + const shdr = self.inputShdr(elf_file); + if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) { + const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*; + switch (chdr.ch_type) { + .ZLIB => { + var stream = std.io.fixedBufferStream(data[@sizeOf(elf.Elf64_Chdr)..]); + var zlib_stream = try std.compress.zlib.decompressStream(gpa, stream.reader()); + defer zlib_stream.deinit(); + const decomp = try gpa.alloc(u8, chdr.ch_size); + const nread = try zlib_stream.reader().readAll(decomp); + if (nread != decomp.len) { + return error.Io; + } + return decomp; + }, + else => @panic("TODO unhandled compression scheme"), + } + } else return gpa.dupe(u8, data); +} + +pub fn priority(self: Atom, elf_file: *Elf) u64 { + const index = elf_file.file(self.file_index).?.index(); + return (@as(u64, @intCast(index)) << 32) | @as(u64, @intCast(self.input_section_index)); +} + /// Returns how much room there is to grow in virtual address space. /// File offset relocation happens transparently, so it is not included in /// this calculation. @@ -247,11 +287,12 @@ pub fn free(self: *Atom, elf_file: *Elf) void { self.* = .{}; } -pub fn relocs(self: Atom, elf_file: *Elf) []const elf.Elf64_Rela { - const file_ptr = elf_file.file(self.file_index).?; - if (file_ptr != .zig_module) @panic("TODO"); - const zig_module = file_ptr.zig_module; - return zig_module.relocs.items[self.relocs_section_index].items; +pub fn relocs(self: Atom, elf_file: *Elf) []align(1) const elf.Elf64_Rela { + return switch (elf_file.file(self.file_index).?) { + .zig_module => |x| x.relocs.items[self.relocs_section_index].items, + .object => |x| x.getRelocs(self.relocs_section_index), + else => unreachable, + }; } pub fn addReloc(self: Atom, elf_file: *Elf, reloc: elf.Elf64_Rela) !void { diff --git a/src/link/Elf/LinkerDefined.zig b/src/link/Elf/LinkerDefined.zig index 3f71e0a58e..98b5ff563e 100644 --- a/src/link/Elf/LinkerDefined.zig +++ b/src/link/Elf/LinkerDefined.zig @@ -1,7 +1,6 @@ index: File.Index, symtab: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{}, symbols: std.ArrayListUnmanaged(Symbol.Index) = .{}, -alive: bool = true, output_symtab_size: Elf.SymtabSize = .{}, @@ -76,10 +75,6 @@ pub fn writeSymtab(self: *LinkerDefined, elf_file: *Elf, ctx: anytype) void { } } -pub fn sourceSymbol(self: *LinkerDefined, symbol_index: Symbol.Index) *elf.Elf64_Sym { - return &self.symtab.items[symbol_index]; -} - pub fn globals(self: *LinkerDefined) []const Symbol.Index { return self.symbols.items; } diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig new file mode 100644 index 0000000000..e4a1f432b6 --- /dev/null +++ b/src/link/Elf/Object.zig @@ -0,0 +1,810 @@ +archive: ?[]const u8 = null, +path: []const u8, +data: []const u8, +index: File.Index, + +header: ?elf.Elf64_Ehdr = null, +shdrs: std.ArrayListUnmanaged(elf.Elf64_Shdr) = .{}, +strings: StringTable(.object_strings) = .{}, +symtab: []align(1) const elf.Elf64_Sym = &[0]elf.Elf64_Sym{}, +strtab: []const u8 = &[0]u8{}, +first_global: ?u32 = null, + +symbols: std.ArrayListUnmanaged(u32) = .{}, +atoms: std.ArrayListUnmanaged(Atom.Index) = .{}, +comdat_groups: std.ArrayListUnmanaged(Elf.ComdatGroup.Index) = .{}, + +fdes: std.ArrayListUnmanaged(Fde) = .{}, +cies: std.ArrayListUnmanaged(Cie) = .{}, + +needs_exec_stack: bool = false, +alive: bool = true, +num_dynrelocs: u32 = 0, + +output_symtab_size: Elf.SymtabSize = .{}, + +pub fn isObject(file: std.fs.File) bool { + const reader = file.reader(); + const header = reader.readStruct(elf.Elf64_Ehdr) catch return false; + defer file.seekTo(0) catch {}; + if (!mem.eql(u8, header.e_ident[0..4], "\x7fELF")) return false; + if (header.e_ident[elf.EI_VERSION] != 1) return false; + if (header.e_type != elf.ET.REL) return false; + if (header.e_version != 1) return false; + return true; +} + +pub fn deinit(self: *Object, allocator: Allocator) void { + allocator.free(self.data); + self.shdrs.deinit(allocator); + self.strings.deinit(allocator); + self.symbols.deinit(allocator); + self.atoms.deinit(allocator); + self.comdat_groups.deinit(allocator); + self.fdes.deinit(allocator); + self.cies.deinit(allocator); +} + +pub fn parse(self: *Object, elf_file: *Elf) !void { + var stream = std.io.fixedBufferStream(self.data); + const reader = stream.reader(); + + self.header = try reader.readStruct(elf.Elf64_Ehdr); + + if (self.header.?.e_shnum == 0) return; + + const gpa = elf_file.base.allocator; + + const shdrs = @as( + [*]align(1) const elf.Elf64_Shdr, + @ptrCast(self.data.ptr + self.header.?.e_shoff), + )[0..self.header.?.e_shnum]; + try self.shdrs.appendUnalignedSlice(gpa, shdrs); + try self.strings.buffer.appendSlice(gpa, self.shdrContents(self.header.?.e_shstrndx)); + + const symtab_index = for (self.shdrs.items, 0..) |shdr, i| switch (shdr.sh_type) { + elf.SHT_SYMTAB => break @as(u16, @intCast(i)), + else => {}, + } else null; + + if (symtab_index) |index| { + const shdr = shdrs[index]; + self.first_global = shdr.sh_info; + + const symtab = self.shdrContents(index); + const nsyms = @divExact(symtab.len, @sizeOf(elf.Elf64_Sym)); + self.symtab = @as([*]align(1) const elf.Elf64_Sym, @ptrCast(symtab.ptr))[0..nsyms]; + self.strtab = self.shdrContents(@as(u16, @intCast(shdr.sh_link))); + } + + try self.initAtoms(elf_file); + try self.initSymtab(elf_file); + + for (self.shdrs.items, 0..) |shdr, i| { + const atom = elf_file.atom(self.atoms.items[i]) orelse continue; + if (!atom.alive) continue; + if (shdr.sh_type == elf.SHT_X86_64_UNWIND or mem.eql(u8, atom.name(elf_file), ".eh_frame")) + try self.parseEhFrame(@as(u16, @intCast(i)), elf_file); + } +} + +fn initAtoms(self: *Object, elf_file: *Elf) !void { + const shdrs = self.shdrs.items; + try self.atoms.resize(elf_file.base.allocator, shdrs.len); + @memset(self.atoms.items, 0); + + for (shdrs, 0..) |shdr, i| { + if (shdr.sh_flags & elf.SHF_EXCLUDE != 0 and + shdr.sh_flags & elf.SHF_ALLOC == 0 and + shdr.sh_type != elf.SHT_LLVM_ADDRSIG) continue; + + switch (shdr.sh_type) { + elf.SHT_GROUP => { + if (shdr.sh_info >= self.symtab.len) { + // TODO convert into an error + log.debug("{}: invalid symbol index in sh_info", .{self.fmtPath()}); + continue; + } + const group_info_sym = self.symtab[shdr.sh_info]; + const group_signature = blk: { + if (group_info_sym.st_name == 0 and group_info_sym.st_type() == elf.STT_SECTION) { + const sym_shdr = shdrs[group_info_sym.st_shndx]; + break :blk self.strings.getAssumeExists(sym_shdr.sh_name); + } + break :blk self.getString(group_info_sym.st_name); + }; + + const shndx = @as(u16, @intCast(i)); + const group_raw_data = self.shdrContents(shndx); + const group_nmembers = @divExact(group_raw_data.len, @sizeOf(u32)); + const group_members = @as([*]align(1) const u32, @ptrCast(group_raw_data.ptr))[0..group_nmembers]; + + if (group_members[0] != 0x1) { // GRP_COMDAT + // TODO convert into an error + log.debug("{}: unknown SHT_GROUP format", .{self.fmtPath()}); + continue; + } + + const group_signature_off = try self.strings.insert(elf_file.base.allocator, group_signature); + const gop = try elf_file.getOrCreateComdatGroupOwner(group_signature_off); + const comdat_group_index = try elf_file.addComdatGroup(); + const comdat_group = elf_file.comdatGroup(comdat_group_index); + comdat_group.* = .{ + .owner = gop.index, + .shndx = shndx, + }; + try self.comdat_groups.append(elf_file.base.allocator, comdat_group_index); + }, + + elf.SHT_SYMTAB_SHNDX => @panic("TODO"), + + elf.SHT_NULL, + elf.SHT_REL, + elf.SHT_RELA, + elf.SHT_SYMTAB, + elf.SHT_STRTAB, + => {}, + + else => { + const name = self.strings.getAssumeExists(shdr.sh_name); + const shndx = @as(u16, @intCast(i)); + + // if (mem.eql(u8, ".note.GNU-stack", name)) { + // if (shdr.sh_flags & elf.SHF_EXECINSTR != 0) { + // if (!elf_file.options.z_execstack or !elf_file.options.z_execstack_if_needed) { + // elf_file.base.warn( + // "{}: may cause segmentation fault as this file requested executable stack", + // .{self.fmtPath()}, + // ); + // } + // self.needs_exec_stack = true; + // } + // continue; + // } + + if (self.skipShdr(shndx, elf_file)) continue; + try self.addAtom(shdr, shndx, name, elf_file); + }, + } + } + + // Parse relocs sections if any. + for (shdrs, 0..) |shdr, i| switch (shdr.sh_type) { + elf.SHT_REL, elf.SHT_RELA => { + const atom_index = self.atoms.items[shdr.sh_info]; + if (elf_file.atom(atom_index)) |atom| { + atom.relocs_section_index = @as(u16, @intCast(i)); + } + }, + else => {}, + }; +} + +fn addAtom(self: *Object, shdr: elf.Elf64_Shdr, shndx: u16, name: [:0]const u8, elf_file: *Elf) !void { + const atom_index = try elf_file.addAtom(); + const atom = elf_file.atom(atom_index).?; + atom.atom_index = atom_index; + atom.name_offset = try elf_file.strtab.insert(elf_file.base.allocator, name); + atom.file_index = self.index; + atom.input_section_index = shndx; + self.atoms.items[shndx] = atom_index; + + if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) { + const data = self.shdrContents(shndx); + const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*; + atom.size = chdr.ch_size; + atom.alignment = math.log2_int(u64, chdr.ch_addralign); + } else { + atom.size = shdr.sh_size; + atom.alignment = math.log2_int(u64, shdr.sh_addralign); + } +} + +fn skipShdr(self: *Object, index: u16, elf_file: *Elf) bool { + const shdr = self.shdrs.items[index]; + const name = self.strings.getAssumeExists(shdr.sh_name); + const ignore = blk: { + if (mem.startsWith(u8, name, ".note")) break :blk true; + if (mem.startsWith(u8, name, ".comment")) break :blk true; + if (mem.startsWith(u8, name, ".llvm_addrsig")) break :blk true; + if (elf_file.base.options.strip and shdr.sh_flags & elf.SHF_ALLOC == 0 and + mem.startsWith(u8, name, ".debug")) break :blk true; + break :blk false; + }; + return ignore; +} + +fn initSymtab(self: *Object, elf_file: *Elf) !void { + const gpa = elf_file.base.allocator; + const first_global = self.first_global orelse self.symtab.len; + const shdrs = self.shdrs.items; + + try self.symbols.ensureTotalCapacityPrecise(gpa, self.symtab.len); + + for (self.symtab[0..first_global], 0..) |sym, i| { + const index = try elf_file.addSymbol(); + self.symbols.appendAssumeCapacity(index); + const sym_ptr = elf_file.symbol(index); + const name = blk: { + if (sym.st_name == 0 and sym.st_type() == elf.STT_SECTION) { + const shdr = shdrs[sym.st_shndx]; + break :blk self.strings.getAssumeExists(shdr.sh_name); + } + break :blk self.getString(sym.st_name); + }; + sym_ptr.value = sym.st_value; + sym_ptr.name_offset = try elf_file.strtab.insert(gpa, name); + sym_ptr.esym_index = @as(u32, @intCast(i)); + sym_ptr.atom_index = if (sym.st_shndx == elf.SHN_ABS) 0 else self.atoms.items[sym.st_shndx]; + sym_ptr.file_index = self.index; + } + + for (self.symtab[first_global..]) |sym| { + const name = self.getString(sym.st_name); + const off = try elf_file.strtab.insert(gpa, name); + const gop = try elf_file.getOrPutGlobal(off); + self.symbols.addOneAssumeCapacity().* = gop.index; + } +} + +fn parseEhFrame(self: *Object, shndx: u16, elf_file: *Elf) !void { + const relocs_shndx = for (self.shdrs.items, 0..) |shdr, i| switch (shdr.sh_type) { + elf.SHT_RELA => if (shdr.sh_info == shndx) break @as(u16, @intCast(i)), + else => {}, + } else { + log.debug("{s}: missing reloc section for unwind info section", .{self.fmtPath()}); + return; + }; + + const gpa = elf_file.base.allocator; + const raw = self.shdrContents(shndx); + const relocs = self.getRelocs(relocs_shndx); + const fdes_start = self.fdes.items.len; + const cies_start = self.cies.items.len; + + var it = eh_frame.Iterator{ .data = raw }; + while (try it.next()) |rec| { + const rel_range = filterRelocs(relocs, rec.offset, rec.size + 4); + switch (rec.tag) { + .cie => try self.cies.append(gpa, .{ + .offset = rec.offset, + .size = rec.size, + .rel_index = @as(u32, @intCast(rel_range.start)), + .rel_num = @as(u32, @intCast(rel_range.len)), + .rel_section_index = relocs_shndx, + .input_section_index = shndx, + .file_index = self.index, + }), + .fde => try self.fdes.append(gpa, .{ + .offset = rec.offset, + .size = rec.size, + .cie_index = undefined, + .rel_index = @as(u32, @intCast(rel_range.start)), + .rel_num = @as(u32, @intCast(rel_range.len)), + .rel_section_index = relocs_shndx, + .input_section_index = shndx, + .file_index = self.index, + }), + } + } + + // Tie each FDE to its CIE + for (self.fdes.items[fdes_start..]) |*fde| { + const cie_ptr = fde.offset + 4 - fde.ciePointer(elf_file); + const cie_index = for (self.cies.items[cies_start..], cies_start..) |cie, cie_index| { + if (cie.offset == cie_ptr) break @as(u32, @intCast(cie_index)); + } else { + // TODO convert into an error + log.debug("{s}: no matching CIE found for FDE at offset {x}", .{ + self.fmtPath(), + fde.offset, + }); + continue; + }; + fde.cie_index = cie_index; + } + + // Tie each FDE record to its matching atom + const SortFdes = struct { + pub fn lessThan(ctx: *Elf, lhs: Fde, rhs: Fde) bool { + const lhs_atom = lhs.atom(ctx); + const rhs_atom = rhs.atom(ctx); + return lhs_atom.priority(ctx) < rhs_atom.priority(ctx); + } + }; + mem.sort(Fde, self.fdes.items[fdes_start..], elf_file, SortFdes.lessThan); + + // Create a back-link from atom to FDEs + var i: u32 = @as(u32, @intCast(fdes_start)); + while (i < self.fdes.items.len) { + const fde = self.fdes.items[i]; + const atom = fde.atom(elf_file); + atom.fde_start = i; + i += 1; + while (i < self.fdes.items.len) : (i += 1) { + const next_fde = self.fdes.items[i]; + if (atom.atom_index != next_fde.atom(elf_file).atom_index) break; + } + atom.fde_end = i; + } +} + +fn filterRelocs( + relocs: []align(1) const elf.Elf64_Rela, + start: u64, + len: u64, +) struct { start: u64, len: u64 } { + const Predicate = struct { + value: u64, + + pub fn predicate(self: @This(), rel: elf.Elf64_Rela) bool { + return rel.r_offset < self.value; + } + }; + const LPredicate = struct { + value: u64, + + pub fn predicate(self: @This(), rel: elf.Elf64_Rela) bool { + return rel.r_offset >= self.value; + } + }; + + const f_start = Elf.bsearch(elf.Elf64_Rela, relocs, Predicate{ .value = start }); + const f_len = Elf.lsearch(elf.Elf64_Rela, relocs[f_start..], LPredicate{ .value = start + len }); + + return .{ .start = f_start, .len = f_len }; +} + +pub fn scanRelocs(self: *Object, elf_file: *Elf) !void { + for (self.atoms.items) |atom_index| { + const atom = elf_file.atom(atom_index) orelse continue; + if (!atom.alive) continue; + const shdr = atom.inputShdr(elf_file); + if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue; + if (shdr.sh_type == elf.SHT_NOBITS) continue; + try atom.scanRelocs(elf_file); + } + + for (self.cies.items) |cie| { + for (cie.getRelocs(elf_file)) |rel| { + const sym = elf_file.symbol(self.symbols.items[rel.r_sym()]); + if (sym.flags.import) { + if (sym.getType(elf_file) != elf.STT_FUNC) + elf_file.base.fatal("{s}: {s}: CIE referencing external data reference", .{ + self.fmtPath(), + sym.getName(elf_file), + }); + sym.flags.plt = true; + } + } + } +} + +pub fn resolveSymbols(self: *Object, elf_file: *Elf) void { + const first_global = self.first_global orelse return; + for (self.globals(), 0..) |index, i| { + const sym_idx = @as(u32, @intCast(first_global + i)); + const this_sym = self.symtab[sym_idx]; + + if (this_sym.st_shndx == elf.SHN_UNDEF) continue; + + if (this_sym.st_shndx != elf.SHN_ABS and this_sym.st_shndx != elf.SHN_COMMON) { + const atom_index = self.atoms.items[this_sym.st_shndx]; + const atom = elf_file.atom(atom_index) orelse continue; + if (!atom.alive) continue; + } + + const global = elf_file.symbol(index); + if (self.asFile().symbolRank(this_sym, !self.alive) < global.symbolRank(elf_file)) { + const atom = switch (this_sym.st_shndx) { + elf.SHN_ABS, elf.SHN_COMMON => 0, + else => self.atoms.items[this_sym.st_shndx], + }; + global.* = .{ + .value = this_sym.st_value, + .name = global.name, + .atom = atom, + .sym_idx = sym_idx, + .file = self.index, + .ver_idx = elf_file.default_sym_version, + }; + if (this_sym.st_bind() == elf.STB_WEAK) global.flags.weak = true; + } + } +} + +pub fn resetGlobals(self: *Object, elf_file: *Elf) void { + for (self.globals()) |index| { + const global = elf_file.symbol(index); + const name = global.name; + global.* = .{}; + global.name = name; + } +} + +pub fn markLive(self: *Object, elf_file: *Elf) void { + const first_global = self.first_global orelse return; + for (self.globals(), 0..) |index, i| { + const sym_idx = first_global + i; + const sym = self.symtab[sym_idx]; + if (sym.st_bind() == elf.STB_WEAK) continue; + + const global = elf_file.symbol(index); + const file = global.getFile(elf_file) orelse continue; + const should_keep = sym.st_shndx == elf.SHN_UNDEF or + (sym.st_shndx == elf.SHN_COMMON and global.sourceSymbol(elf_file).st_shndx != elf.SHN_COMMON); + if (should_keep and !file.isAlive()) { + file.setAlive(); + file.markLive(elf_file); + } + } +} + +pub fn checkDuplicates(self: *Object, elf_file: *Elf) void { + const first_global = self.first_global orelse return; + for (self.globals(), 0..) |index, i| { + const sym_idx = @as(u32, @intCast(first_global + i)); + const this_sym = self.symtab[sym_idx]; + const global = elf_file.symbol(index); + const global_file = global.getFile(elf_file) orelse continue; + + if (self.index == global_file.getIndex() or + this_sym.st_shndx == elf.SHN_UNDEF or + this_sym.st_bind() == elf.STB_WEAK or + this_sym.st_shndx == elf.SHN_COMMON) continue; + + if (this_sym.st_shndx != elf.SHN_ABS) { + const atom_index = self.atoms.items[this_sym.st_shndx]; + const atom = elf_file.atom(atom_index) orelse continue; + if (!atom.alive) continue; + } + + elf_file.base.fatal("multiple definition: {}: {}: {s}", .{ + self.fmtPath(), + global_file.fmtPath(), + global.getName(elf_file), + }); + } +} + +/// We will create dummy shdrs per each resolved common symbols to make it +/// play nicely with the rest of the system. +pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void { + const first_global = self.first_global orelse return; + for (self.globals(), 0..) |index, i| { + const sym_idx = @as(u32, @intCast(first_global + i)); + const this_sym = self.symtab[sym_idx]; + if (this_sym.st_shndx != elf.SHN_COMMON) continue; + + const global = elf_file.symbol(index); + const global_file = global.getFile(elf_file).?; + if (global_file.getIndex() != self.index) { + if (elf_file.options.warn_common) { + elf_file.base.warn("{}: multiple common symbols: {s}", .{ + self.fmtPath(), + global.getName(elf_file), + }); + } + continue; + } + + const gpa = elf_file.base.allocator; + + const atom_index = try elf_file.addAtom(); + try self.atoms.append(gpa, atom_index); + + const is_tls = global.getType(elf_file) == elf.STT_TLS; + const name = if (is_tls) ".tls_common" else ".common"; + + const atom = elf_file.atom(atom_index).?; + atom.atom_index = atom_index; + atom.name = try elf_file.strtab.insert(gpa, name); + atom.file = self.index; + atom.size = this_sym.st_size; + const alignment = this_sym.st_value; + atom.alignment = math.log2_int(u64, alignment); + + var sh_flags: u32 = elf.SHF_ALLOC | elf.SHF_WRITE; + if (is_tls) sh_flags |= elf.SHF_TLS; + const shndx = @as(u16, @intCast(self.shdrs.items.len)); + const shdr = try self.shdrs.addOne(gpa); + shdr.* = .{ + .sh_name = try self.strings.insert(gpa, name), + .sh_type = elf.SHT_NOBITS, + .sh_flags = sh_flags, + .sh_addr = 0, + .sh_offset = 0, + .sh_size = this_sym.st_size, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = alignment, + .sh_entsize = 0, + }; + atom.shndx = shndx; + + global.value = 0; + global.atom = atom_index; + global.flags.weak = false; + } +} + +pub fn calcSymtabSize(self: *Object, elf_file: *Elf) !void { + if (elf_file.options.strip_all) return; + + for (self.locals()) |local_index| { + const local = elf_file.symbol(local_index); + if (local.atom(elf_file)) |atom| if (!atom.alive) continue; + const s_sym = local.getSourceSymbol(elf_file); + switch (s_sym.st_type()) { + elf.STT_SECTION, elf.STT_NOTYPE => continue, + else => {}, + } + local.flags.output_symtab = true; + self.output_symtab_size.nlocals += 1; + self.output_symtab_size.strsize += @as(u32, @intCast(local.getName(elf_file).len + 1)); + } + + for (self.globals()) |global_index| { + const global = elf_file.symbol(global_index); + if (global.getFile(elf_file)) |file| if (file.getIndex() != self.index) continue; + if (global.atom(elf_file)) |atom| if (!atom.alive) continue; + global.flags.output_symtab = true; + if (global.isLocal()) { + self.output_symtab_size.nlocals += 1; + } else { + self.output_symtab_size.nglobals += 1; + } + self.output_symtab_size.strsize += @as(u32, @intCast(global.getName(elf_file).len + 1)); + } +} + +pub fn writeSymtab(self: *Object, elf_file: *Elf, ctx: Elf.WriteSymtabCtx) !void { + if (elf_file.options.strip_all) return; + + const gpa = elf_file.base.allocator; + + var ilocal = ctx.ilocal; + for (self.locals()) |local_index| { + const local = elf_file.symbol(local_index); + if (!local.flags.output_symtab) continue; + const st_name = try ctx.strtab.insert(gpa, local.getName(elf_file)); + ctx.symtab[ilocal] = local.asElfSym(st_name, elf_file); + ilocal += 1; + } + + var iglobal = ctx.iglobal; + for (self.globals()) |global_index| { + const global = elf_file.symbol(global_index); + if (global.getFile(elf_file)) |file| if (file.getIndex() != self.index) continue; + if (!global.flags.output_symtab) continue; + const st_name = try ctx.strtab.insert(gpa, global.getName(elf_file)); + if (global.isLocal()) { + ctx.symtab[ilocal] = global.asElfSym(st_name, elf_file); + ilocal += 1; + } else { + ctx.symtab[iglobal] = global.asElfSym(st_name, elf_file); + iglobal += 1; + } + } +} + +pub fn locals(self: *Object) []const u32 { + const end = self.first_global orelse self.symbols.items.len; + return self.symbols.items[0..end]; +} + +pub fn globals(self: *Object) []const u32 { + const start = self.first_global orelse self.symbols.items.len; + return self.symbols.items[start..]; +} + +pub inline fn shdrContents(self: *Object, index: u32) []const u8 { + assert(index < self.shdrs.items.len); + const shdr = self.shdrs.items[index]; + return self.data[shdr.sh_offset..][0..shdr.sh_size]; +} + +fn getString(self: *Object, off: u32) [:0]const u8 { + assert(off < self.strtab.len); + return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.ptr + off)), 0); +} + +pub fn comdatGroupMembers(self: *Object, index: u16) []align(1) const u32 { + const raw = self.shdrContents(index); + const nmembers = @divExact(raw.len, @sizeOf(u32)); + const members = @as([*]align(1) const u32, @ptrCast(raw.ptr))[1..nmembers]; + return members; +} + +pub fn asFile(self: *Object) File { + return .{ .object = self }; +} + +pub fn getRelocs(self: *Object, shndx: u32) []align(1) const elf.Elf64_Rela { + const raw = self.shdrContents(shndx); + const num = @divExact(raw.len, @sizeOf(elf.Elf64_Rela)); + return @as([*]align(1) const elf.Elf64_Rela, @ptrCast(raw.ptr))[0..num]; +} + +pub fn format( + self: *Object, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = self; + _ = unused_fmt_string; + _ = options; + _ = writer; + @compileError("do not format objects directly"); +} + +pub fn fmtSymtab(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatSymtab) { + return .{ .data = .{ + .object = self, + .elf_file = elf_file, + } }; +} + +const FormatContext = struct { + object: *Object, + elf_file: *Elf, +}; + +fn formatSymtab( + ctx: FormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = unused_fmt_string; + _ = options; + const object = ctx.object; + try writer.writeAll(" locals\n"); + for (object.locals()) |index| { + const local = ctx.elf_file.symbol(index); + try writer.print(" {}\n", .{local.fmt(ctx.elf_file)}); + } + try writer.writeAll(" globals\n"); + for (object.globals()) |index| { + const global = ctx.elf_file.symbol(index); + try writer.print(" {}\n", .{global.fmt(ctx.elf_file)}); + } +} + +pub fn fmtAtoms(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatAtoms) { + return .{ .data = .{ + .object = self, + .elf_file = elf_file, + } }; +} + +fn formatAtoms( + ctx: FormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = unused_fmt_string; + _ = options; + const object = ctx.object; + try writer.writeAll(" atoms\n"); + for (object.atoms.items) |atom_index| { + const atom = ctx.elf_file.atom(atom_index) orelse continue; + try writer.print(" {}\n", .{atom.fmt(ctx.elf_file)}); + } +} + +pub fn fmtCies(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatCies) { + return .{ .data = .{ + .object = self, + .elf_file = elf_file, + } }; +} + +fn formatCies( + ctx: FormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = unused_fmt_string; + _ = options; + const object = ctx.object; + try writer.writeAll(" cies\n"); + for (object.cies.items, 0..) |cie, i| { + try writer.print(" cie({d}) : {}\n", .{ i, cie.fmt(ctx.elf_file) }); + } +} + +pub fn fmtFdes(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatFdes) { + return .{ .data = .{ + .object = self, + .elf_file = elf_file, + } }; +} + +fn formatFdes( + ctx: FormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = unused_fmt_string; + _ = options; + const object = ctx.object; + try writer.writeAll(" fdes\n"); + for (object.fdes.items, 0..) |fde, i| { + try writer.print(" fde({d}) : {}\n", .{ i, fde.fmt(ctx.elf_file) }); + } +} + +pub fn fmtComdatGroups(self: *Object, elf_file: *Elf) std.fmt.Formatter(formatComdatGroups) { + return .{ .data = .{ + .object = self, + .elf_file = elf_file, + } }; +} + +fn formatComdatGroups( + ctx: FormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = unused_fmt_string; + _ = options; + const object = ctx.object; + const elf_file = ctx.elf_file; + try writer.writeAll(" comdat groups\n"); + for (object.comdat_groups.items) |cg_index| { + const cg = elf_file.comdatGroup(cg_index); + const cg_owner = elf_file.comdatGroupOwner(cg.owner); + if (cg_owner.file != object.index) continue; + for (object.comdatGroupMembers(cg.shndx)) |shndx| { + const atom_index = object.atoms.items[shndx]; + const atom = elf_file.atom(atom_index) orelse continue; + try writer.print(" atom({d}) : {s}\n", .{ atom_index, atom.name(elf_file) }); + } + } +} + +pub fn fmtPath(self: *Object) std.fmt.Formatter(formatPath) { + return .{ .data = self }; +} + +fn formatPath( + object: *Object, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = unused_fmt_string; + _ = options; + if (object.archive) |path| { + try writer.writeAll(path); + try writer.writeByte('('); + try writer.writeAll(object.path); + try writer.writeByte(')'); + } else try writer.writeAll(object.path); +} + +const Object = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const eh_frame = @import("eh_frame.zig"); +const elf = std.elf; +const fs = std.fs; +const log = std.log.scoped(.link); +const math = std.math; +const mem = std.mem; + +const Allocator = mem.Allocator; +const Atom = @import("Atom.zig"); +const Cie = eh_frame.Cie; +const Elf = @import("../Elf.zig"); +const Fde = eh_frame.Fde; +const File = @import("file.zig").File; +const StringTable = @import("../strtab.zig").StringTable; +const Symbol = @import("Symbol.zig"); diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index 322e7609eb..a6901d9ab7 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -66,22 +66,23 @@ pub fn file(symbol: Symbol, elf_file: *Elf) ?File { return elf_file.file(symbol.file_index); } -pub fn sourceSymbol(symbol: Symbol, elf_file: *Elf) *elf.Elf64_Sym { +pub fn sourceSymbol(symbol: Symbol, elf_file: *Elf) elf.Elf64_Sym { const file_ptr = symbol.file(elf_file).?; switch (file_ptr) { - .zig_module => return file_ptr.zig_module.sourceSymbol(symbol.index, elf_file), - .linker_defined => return file_ptr.linker_defined.sourceSymbol(symbol.esym_index), + .zig_module => return file_ptr.zig_module.sourceSymbol(symbol.index, elf_file).*, + .linker_defined => return file_ptr.linker_defined.symtab.items[symbol.esym_index], + .object => return file_ptr.object.symtab[symbol.esym_index], } } pub fn symbolRank(symbol: Symbol, elf_file: *Elf) u32 { const file_ptr = symbol.file(elf_file) orelse return std.math.maxInt(u32); const sym = symbol.sourceSymbol(elf_file); - const in_archive = switch (file) { - // .object => |x| !x.alive, + const in_archive = switch (file_ptr) { + .object => |x| !x.alive, else => false, }; - return file_ptr.symbolRank(sym.*, in_archive); + return file_ptr.symbolRank(sym, in_archive); } pub fn address(symbol: Symbol, opts: struct { diff --git a/src/link/Elf/ZigModule.zig b/src/link/Elf/ZigModule.zig index 760a71e14b..72c3ca0132 100644 --- a/src/link/Elf/ZigModule.zig +++ b/src/link/Elf/ZigModule.zig @@ -11,8 +11,6 @@ global_symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{}, atoms: std.AutoArrayHashMapUnmanaged(Atom.Index, void) = .{}, relocs: std.ArrayListUnmanaged(std.ArrayListUnmanaged(elf.Elf64_Rela)) = .{}, -alive: bool = true, - output_symtab_size: Elf.SymtabSize = .{}, pub fn deinit(self: *ZigModule, allocator: Allocator) void { @@ -37,7 +35,7 @@ pub fn createAtom(self: *ZigModule, output_section_index: u16, elf_file: *Elf) ! const symbol_ptr = elf_file.symbol(symbol_index); symbol_ptr.atom_index = atom_index; symbol_ptr.output_section_index = output_section_index; - const local_esym = symbol_ptr.sourceSymbol(elf_file); + const local_esym = self.sourceSymbol(symbol_ptr.index, elf_file); local_esym.st_shndx = output_section_index; const relocs_index = @as(Atom.Index, @intCast(self.relocs.items.len)); const relocs = try self.relocs.addOne(gpa); @@ -82,7 +80,7 @@ pub fn addGlobal(self: *ZigModule, name: [:0]const u8, elf_file: *Elf) !Symbol.I pub fn updateSymtabSize(self: *ZigModule, elf_file: *Elf) void { for (self.locals()) |local_index| { const local = elf_file.symbol(local_index); - const esym = self.sourceSymbol(local_index, elf_file); + const esym = local.sourceSymbol(elf_file); switch (esym.st_type()) { elf.STT_SECTION, elf.STT_NOTYPE => { local.flags.output_symtab = false; diff --git a/src/link/Elf/eh_frame.zig b/src/link/Elf/eh_frame.zig new file mode 100644 index 0000000000..71dd5a6a22 --- /dev/null +++ b/src/link/Elf/eh_frame.zig @@ -0,0 +1,445 @@ +pub const Fde = struct { + /// Includes 4byte size cell. + offset: u64, + size: u64, + cie_index: u32, + rel_index: u32 = 0, + rel_num: u32 = 0, + rel_section_index: u32 = 0, + input_section_index: u32 = 0, + file_index: u32 = 0, + alive: bool = true, + /// Includes 4byte size cell. + out_offset: u64 = 0, + + pub fn address(fde: Fde, elf_file: *Elf) u64 { + const base: u64 = if (elf_file.eh_frame_section_index) |shndx| + elf_file.shdrs.items[shndx].sh_addr + else + 0; + return base + fde.out_offset; + } + + pub fn data(fde: Fde, elf_file: *Elf) []const u8 { + const object = elf_file.file(fde.file_index).?.object; + const contents = object.shdrContents(fde.input_section_index); + return contents[fde.offset..][0..fde.calcSize()]; + } + + pub fn cie(fde: Fde, elf_file: *Elf) Cie { + const object = elf_file.file(fde.file_index).?.object; + return object.cies.items[fde.cie_index]; + } + + pub fn ciePointer(fde: Fde, elf_file: *Elf) u32 { + return std.mem.readIntLittle(u32, fde.data(elf_file)[4..8]); + } + + pub fn calcSize(fde: Fde) u64 { + return fde.size + 4; + } + + pub fn atom(fde: Fde, elf_file: *Elf) *Atom { + const object = elf_file.file(fde.file_index).?.object; + const rel = fde.relocs(elf_file)[0]; + const sym = object.symtab[rel.r_sym()]; + const atom_index = object.atoms.items[sym.st_shndx]; + return elf_file.atom(atom_index).?; + } + + pub fn relocs(fde: Fde, elf_file: *Elf) []align(1) const elf.Elf64_Rela { + const object = elf_file.file(fde.file_index).?.object; + return object.getRelocs(fde.rel_section_index)[fde.rel_index..][0..fde.rel_num]; + } + + pub fn format( + fde: Fde, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = fde; + _ = unused_fmt_string; + _ = options; + _ = writer; + @compileError("do not format FDEs directly"); + } + + pub fn fmt(fde: Fde, elf_file: *Elf) std.fmt.Formatter(format2) { + return .{ .data = .{ + .fde = fde, + .elf_file = elf_file, + } }; + } + + const FdeFormatContext = struct { + fde: Fde, + elf_file: *Elf, + }; + + fn format2( + ctx: FdeFormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = unused_fmt_string; + _ = options; + const fde = ctx.fde; + const elf_file = ctx.elf_file; + const base_addr = fde.address(elf_file); + try writer.print("@{x} : size({x}) : cie({d}) : {s}", .{ + base_addr + fde.out_offset, + fde.calcSize(), + fde.cie_index, + fde.atom(elf_file).name(elf_file), + }); + if (!fde.alive) try writer.writeAll(" : [*]"); + } +}; + +pub const Cie = struct { + /// Includes 4byte size cell. + offset: u64, + size: u64, + rel_index: u32 = 0, + rel_num: u32 = 0, + rel_section_index: u32 = 0, + input_section_index: u32 = 0, + file_index: u32 = 0, + /// Includes 4byte size cell. + out_offset: u64 = 0, + alive: bool = false, + + pub fn address(cie: Cie, elf_file: *Elf) u64 { + const base: u64 = if (elf_file.eh_frame_section_index) |shndx| + elf_file.shdrs.items[shndx].sh_addr + else + 0; + return base + cie.out_offset; + } + + pub fn data(cie: Cie, elf_file: *Elf) []const u8 { + const object = elf_file.file(cie.file_index).?.object; + const contents = object.shdrContents(cie.input_section_index); + return contents[cie.offset..][0..cie.calcSize()]; + } + + pub fn calcSize(cie: Cie) u64 { + return cie.size + 4; + } + + pub fn relocs(cie: Cie, elf_file: *Elf) []align(1) const elf.Elf64_Rela { + const object = elf_file.file(cie.file_index).?.object; + return object.getRelocs(cie.rel_section_index)[cie.rel_index..][0..cie.rel_num]; + } + + pub fn eql(cie: Cie, other: Cie, elf_file: *Elf) bool { + if (!std.mem.eql(u8, cie.data(elf_file), other.data(elf_file))) return false; + + const cie_relocs = cie.relocs(elf_file); + const other_relocs = other.relocs(elf_file); + if (cie_relocs.len != other_relocs.len) return false; + + for (cie_relocs, other_relocs) |cie_rel, other_rel| { + if (cie_rel.r_offset - cie.offset != other_rel.r_offset - other.offset) return false; + if (cie_rel.r_type() != other_rel.r_type()) return false; + if (cie_rel.r_addend != other_rel.r_addend) return false; + + const cie_object = elf_file.file(cie.file_index).?.object; + const other_object = elf_file.file(other.file_index).?.object; + const cie_sym = cie_object.symbol(cie_rel.r_sym(), elf_file); + const other_sym = other_object.symbol(other_rel.r_sym(), elf_file); + if (!std.mem.eql(u8, std.mem.asBytes(&cie_sym), std.mem.asBytes(&other_sym))) return false; + } + return true; + } + + pub fn format( + cie: Cie, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = cie; + _ = unused_fmt_string; + _ = options; + _ = writer; + @compileError("do not format CIEs directly"); + } + + pub fn fmt(cie: Cie, elf_file: *Elf) std.fmt.Formatter(format2) { + return .{ .data = .{ + .cie = cie, + .elf_file = elf_file, + } }; + } + + const CieFormatContext = struct { + cie: Cie, + elf_file: *Elf, + }; + + fn format2( + ctx: CieFormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = unused_fmt_string; + _ = options; + const cie = ctx.cie; + const elf_file = ctx.elf_file; + const base_addr = cie.address(elf_file); + try writer.print("@{x} : size({x})", .{ + base_addr + cie.out_offset, + cie.calcSize(), + }); + if (!cie.alive) try writer.writeAll(" : [*]"); + } +}; + +pub const Iterator = struct { + data: []const u8, + pos: u64 = 0, + + pub const Record = struct { + tag: enum { fde, cie }, + offset: u64, + size: u64, + }; + + pub fn next(it: *Iterator) !?Record { + if (it.pos >= it.data.len) return null; + + var stream = std.io.fixedBufferStream(it.data[it.pos..]); + const reader = stream.reader(); + + var size = try reader.readIntLittle(u32); + if (size == 0xFFFFFFFF) @panic("TODO"); + + const id = try reader.readIntLittle(u32); + const record = Record{ + .tag = if (id == 0) .cie else .fde, + .offset = it.pos, + .size = size, + }; + it.pos += size + 4; + + return record; + } +}; + +pub fn calcEhFrameSize(elf_file: *Elf) !usize { + var offset: u64 = 0; + + var cies = std.ArrayList(Cie).init(elf_file.base.allocator); + defer cies.deinit(); + + for (elf_file.objects.items) |index| { + const object = elf_file.file(index).?.object; + + outer: for (object.cies.items) |*cie| { + for (cies.items) |other| { + if (other.eql(cie.*, elf_file)) { + // We already have a CIE record that has the exact same contents, so instead of + // duplicating them, we mark this one dead and set its output offset to be + // equal to that of the alive record. This way, we won't have to rewrite + // Fde.cie_index field when committing the records to file. + cie.out_offset = other.out_offset; + continue :outer; + } + } + cie.alive = true; + cie.out_offset = offset; + offset += cie.calcSize(); + try cies.append(cie.*); + } + } + + for (elf_file.objects.items) |index| { + const object = elf_file.file(index).?.object; + for (object.fdes.items) |*fde| { + if (!fde.alive) continue; + fde.out_offset = offset; + offset += fde.calcSize(); + } + } + + return offset + 4; // NULL terminator +} + +pub fn calcEhFrameHdrSize(elf_file: *Elf) usize { + var count: usize = 0; + for (elf_file.objects.items) |index| { + for (elf_file.file(index).?.object.fdes.items) |fde| { + if (!fde.alive) continue; + count += 1; + } + } + return eh_frame_hdr_header_size + count * 8; +} + +fn resolveReloc(rec: anytype, sym: *const Symbol, rel: elf.Elf64_Rela, elf_file: *Elf, contents: []u8) !void { + const offset = rel.r_offset - rec.offset; + const P = @as(i64, @intCast(rec.address(elf_file) + offset)); + const S = @as(i64, @intCast(sym.address(.{}, elf_file))); + const A = rel.r_addend; + + relocs_log.debug(" {s}: {x}: [{x} => {x}] ({s})", .{ + Atom.fmtRelocType(rel.r_type()), + offset, + P, + S + A, + sym.name(elf_file), + }); + + var where = contents[offset..]; + switch (rel.r_type()) { + elf.R_X86_64_32 => std.mem.writeIntLittle(i32, where[0..4], @as(i32, @truncate(S + A))), + elf.R_X86_64_64 => std.mem.writeIntLittle(i64, where[0..8], S + A), + elf.R_X86_64_PC32 => std.mem.writeIntLittle(i32, where[0..4], @as(i32, @intCast(S - P + A))), + elf.R_X86_64_PC64 => std.mem.writeIntLittle(i64, where[0..8], S - P + A), + else => unreachable, + } +} + +pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void { + const gpa = elf_file.base.allocator; + + relocs_log.debug("{x}: .eh_frame", .{elf_file.shdrs.items[elf_file.eh_frame_section_index.?].sh_addr}); + + for (elf_file.objects.items) |index| { + const object = elf_file.file(index).?.object; + + for (object.cies.items) |cie| { + if (!cie.alive) continue; + + const contents = try gpa.dupe(u8, cie.data(elf_file)); + defer gpa.free(contents); + + for (cie.relocs(elf_file)) |rel| { + const sym = object.symbol(rel.r_sym(), elf_file); + try resolveReloc(cie, sym, rel, elf_file, contents); + } + + try writer.writeAll(contents); + } + } + + for (elf_file.objects.items) |index| { + const object = elf_file.file(index).?.object; + + for (object.fdes.items) |fde| { + if (!fde.alive) continue; + + const contents = try gpa.dupe(u8, fde.data(elf_file)); + defer gpa.free(contents); + + std.mem.writeIntLittle( + i32, + contents[4..8], + @as(i32, @truncate(@as(i64, @intCast(fde.out_offset + 4)) - @as(i64, @intCast(fde.cie(elf_file).out_offset)))), + ); + + for (fde.relocs(elf_file)) |rel| { + const sym = object.symbol(rel.r_sym(), elf_file); + try resolveReloc(fde, sym, rel, elf_file, contents); + } + + try writer.writeAll(contents); + } + } + + try writer.writeIntLittle(u32, 0); +} + +pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void { + try writer.writeByte(1); // version + try writer.writeByte(EH_PE.pcrel | EH_PE.sdata4); + try writer.writeByte(EH_PE.udata4); + try writer.writeByte(EH_PE.datarel | EH_PE.sdata4); + + const eh_frame_shdr = elf_file.shdrs.items[elf_file.eh_frame_section_index.?]; + const eh_frame_hdr_shdr = elf_file.shdrs.items[elf_file.eh_frame_hdr_section_index.?]; + const num_fdes = @as(u32, @intCast(@divExact(eh_frame_hdr_shdr.sh_size - eh_frame_hdr_header_size, 8))); + try writer.writeIntLittle( + u32, + @as(u32, @bitCast(@as( + i32, + @truncate(@as(i64, @intCast(eh_frame_shdr.sh_addr)) - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)) - 4), + ))), + ); + try writer.writeIntLittle(u32, num_fdes); + + const Entry = struct { + init_addr: u32, + fde_addr: u32, + + pub fn lessThan(ctx: void, lhs: @This(), rhs: @This()) bool { + _ = ctx; + return lhs.init_addr < rhs.init_addr; + } + }; + + var entries = std.ArrayList(Entry).init(elf_file.base.allocator); + defer entries.deinit(); + try entries.ensureTotalCapacityPrecise(num_fdes); + + for (elf_file.objects.items) |index| { + const object = elf_file.file(index).?.object; + for (object.fdes.items) |fde| { + if (!fde.alive) continue; + + const relocs = fde.relocs(elf_file); + assert(relocs.len > 0); // Should this be an error? Things are completely broken anyhow if this trips... + const rel = relocs[0]; + const sym = object.symbol(rel.r_sym(), elf_file); + const P = @as(i64, @intCast(fde.address(elf_file))); + const S = @as(i64, @intCast(sym.address(.{}, elf_file))); + const A = rel.r_addend; + entries.appendAssumeCapacity(.{ + .init_addr = @as(u32, @bitCast(@as(i32, @truncate(S + A - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr)))))), + .fde_addr = @as( + u32, + @bitCast(@as(i32, @truncate(P - @as(i64, @intCast(eh_frame_hdr_shdr.sh_addr))))), + ), + }); + } + } + + std.mem.sort(Entry, entries.items, {}, Entry.lessThan); + try writer.writeAll(std.mem.sliceAsBytes(entries.items)); +} + +const eh_frame_hdr_header_size: u64 = 12; + +const EH_PE = struct { + pub const absptr = 0x00; + pub const uleb128 = 0x01; + pub const udata2 = 0x02; + pub const udata4 = 0x03; + pub const udata8 = 0x04; + pub const sleb128 = 0x09; + pub const sdata2 = 0x0A; + pub const sdata4 = 0x0B; + pub const sdata8 = 0x0C; + pub const pcrel = 0x10; + pub const textrel = 0x20; + pub const datarel = 0x30; + pub const funcrel = 0x40; + pub const aligned = 0x50; + pub const indirect = 0x80; + pub const omit = 0xFF; +}; + +const std = @import("std"); +const assert = std.debug.assert; +const elf = std.elf; +const relocs_log = std.log.scoped(.link_relocs); + +const Allocator = std.mem.Allocator; +const Atom = @import("Atom.zig"); +const Elf = @import("../Elf.zig"); +const Object = @import("Object.zig"); +const Symbol = @import("Symbol.zig"); diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index 823ae5209e..0d7e62da67 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -1,7 +1,7 @@ pub const File = union(enum) { zig_module: *ZigModule, linker_defined: *LinkerDefined, - // object: *Object, + object: *Object, // shared_object: *SharedObject, pub fn index(file: File) Index { @@ -25,7 +25,7 @@ pub const File = union(enum) { switch (file) { .zig_module => try writer.writeAll("(zig module)"), .linker_defined => try writer.writeAll("(linker defined)"), - // .object => |x| try writer.print("{}", .{x.fmtPath()}), + .object => |x| try writer.print("{}", .{x.fmtPath()}), // .shared_object => |x| try writer.writeAll(x.path), } } @@ -95,7 +95,7 @@ pub const File = union(enum) { null: void, zig_module: ZigModule, linker_defined: LinkerDefined, - // object: Object, + object: Object, // shared_object: SharedObject, }; }; @@ -106,7 +106,7 @@ const elf = std.elf; const Allocator = std.mem.Allocator; const Elf = @import("../Elf.zig"); const LinkerDefined = @import("LinkerDefined.zig"); -// const Object = @import("Object.zig"); +const Object = @import("Object.zig"); // const SharedObject = @import("SharedObject.zig"); const Symbol = @import("Symbol.zig"); const ZigModule = @import("ZigModule.zig");