diff --git a/src/link/Elf.zig b/src/link/Elf.zig index d8d6a28471..313d72ba3a 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1321,16 +1321,14 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node // Beyond this point, everything has been allocated a virtual address and we can resolve // the relocations, and commit objects to file. if (self.zig_module_index) |index| { - for (self.file(index).?.zig_module.atoms.keys()) |atom_index| { + const zig_module = self.file(index).?.zig_module; + for (zig_module.atoms.keys()) |atom_index| { const atom_ptr = self.atom(atom_index).?; if (!atom_ptr.flags.alive) continue; + const code = try zig_module.codeAlloc(self, atom_index); + defer gpa.free(code); const shdr = &self.shdrs.items[atom_ptr.outputShndx().?]; const file_offset = shdr.sh_offset + atom_ptr.value - shdr.sh_addr; - const size = math.cast(usize, atom_ptr.size) orelse return error.Overflow; - const code = try gpa.alloc(u8, size); - defer gpa.free(code); - const amt = try self.base.file.?.preadAll(code, file_offset); - if (amt != code.len) return error.InputOutput; try atom_ptr.resolveRelocs(self, code); try self.base.file.?.pwriteAll(code, file_offset); } @@ -1864,7 +1862,7 @@ fn writeObjects(self: *Elf) !void { const file_offset = shdr.sh_offset + atom_ptr.value - shdr.sh_addr; log.debug("writing atom({d}) at 0x{x}", .{ atom_ptr.atom_index, file_offset }); - const code = try atom_ptr.codeInObjectUncompressAlloc(self); + const code = try object.codeDecompressAlloc(self, atom_ptr.atom_index); defer gpa.free(code); try atom_ptr.resolveRelocs(self, code); @@ -3905,6 +3903,10 @@ pub fn calcImageBase(self: Elf) u64 { }; } +pub fn isStatic(self: Elf) bool { + return self.base.options.link_mode == .Static; +} + pub fn isDynLib(self: Elf) bool { return self.base.options.output_mode == .Lib and self.base.options.link_mode == .Dynamic; } diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 3435260adb..acac7c1925 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -59,38 +59,6 @@ pub fn outputShndx(self: Atom) ?u16 { return self.output_section_index; } -pub fn codeInObject(self: Atom, elf_file: *Elf) error{Overflow}![]const u8 { - const object = self.file(elf_file).?.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 = try self.codeInObject(elf_file); - const shdr = self.inputShdr(elf_file); - if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) { - const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*; - switch (chdr.ch_type) { - .ZLIB => { - var stream = std.io.fixedBufferStream(data[@sizeOf(elf.Elf64_Chdr)..]); - var zlib_stream = std.compress.zlib.decompressStream(gpa, stream.reader()) catch - return error.InputOutput; - defer zlib_stream.deinit(); - const size = std.math.cast(usize, chdr.ch_size) orelse return error.Overflow; - const decomp = try gpa.alloc(u8, size); - const nread = zlib_stream.reader().readAll(decomp) catch return error.InputOutput; - if (nread != decomp.len) { - return error.InputOutput; - } - return decomp; - }, - else => @panic("TODO unhandled compression scheme"), - } - } else return gpa.dupe(u8, data); -} - pub fn priority(self: Atom, elf_file: *Elf) u64 { const index = self.file(elf_file).?.index(); return (@as(u64, @intCast(index)) << 32) | @as(u64, @intCast(self.input_section_index)); @@ -327,7 +295,15 @@ pub fn freeRelocs(self: Atom, elf_file: *Elf) void { zig_module.relocs.items[self.relocs_section_index].clearRetainingCapacity(); } -pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void { +pub fn scanRelocsRequiresCode(self: Atom, elf_file: *Elf) error{Overflow}!bool { + for (try self.relocs(elf_file)) |rel| { + if (rel.r_type() == elf.R_X86_64_GOTTPOFF) return true; + } + return false; +} + +pub fn scanRelocs(self: Atom, elf_file: *Elf, code: ?[]const u8, undefs: anytype) !void { + const is_dyn_lib = elf_file.isDynLib(); const file_ptr = self.file(elf_file).?; const rels = try self.relocs(elf_file); var i: usize = 0; @@ -391,7 +367,38 @@ pub fn scanRelocs(self: Atom, elf_file: *Elf, undefs: anytype) !void { elf.R_X86_64_TPOFF32, elf.R_X86_64_TPOFF64, => { - // if (is_shared) self.picError(symbol, rel, elf_file); + if (is_dyn_lib) { + // TODO + // self.picError(symbol, rel, elf_file); + } + }, + + elf.R_X86_64_TLSGD => { + // TODO verify followed by appropriate relocation such as PLT32 __tls_get_addr + + if (elf_file.isStatic() or + (!symbol.flags.import and !is_dyn_lib)) + { + // Relax if building with -static flag as __tls_get_addr() will not be present in libc.a + // We skip the next relocation. + i += 1; + } else if (!symbol.flags.import and is_dyn_lib) { + symbol.flags.needs_gottp = true; + i += 1; + } else { + symbol.flags.needs_tlsgd = true; + } + }, + + elf.R_X86_64_GOTTPOFF => { + const should_relax = blk: { + // if (!elf_file.options.relax or is_shared or symbol.flags.import) break :blk false; + if (!x86_64.canRelaxGotTpOff(code.?[rel.r_offset - 3 ..])) break :blk false; + break :blk true; + }; + if (!should_relax) { + symbol.flags.needs_gottp = true; + } }, else => { @@ -446,7 +453,10 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void { var stream = std.io.fixedBufferStream(code); const cwriter = stream.writer(); - for (try self.relocs(elf_file)) |rel| { + const rels = try self.relocs(elf_file); + var i: usize = 0; + while (i < rels.len) : (i += 1) { + const rel = rels[i]; const r_type = rel.r_type(); if (r_type == elf.R_X86_64_NONE) continue; @@ -531,6 +541,39 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void { elf.R_X86_64_TPOFF32 => try cwriter.writeIntLittle(i32, @as(i32, @truncate(S + A - TP))), elf.R_X86_64_TPOFF64 => try cwriter.writeIntLittle(i64, S + A - TP), + elf.R_X86_64_TLSGD => { + if (target.flags.has_tlsgd) { + // TODO + // const S_ = @as(i64, @intCast(target.tlsGdAddress(elf_file))); + // try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P))); + } else if (target.flags.has_gottp) { + // TODO + // const S_ = @as(i64, @intCast(target.getGotTpAddress(elf_file))); + // try relaxTlsGdToIe(relocs[i .. i + 2], @intCast(S_ - P), elf_file, &stream); + i += 1; + } else { + try x86_64.relaxTlsGdToLe( + self, + rels[i .. i + 2], + @as(i32, @intCast(S - TP)), + elf_file, + &stream, + ); + i += 1; + } + }, + + elf.R_X86_64_GOTTPOFF => { + if (target.flags.has_gottp) { + // TODO + // const S_ = @as(i64, @intCast(target.gotTpAddress(elf_file))); + // try cwriter.writeIntLittle(i32, @as(i32, @intCast(S_ + A - P))); + } else { + x86_64.relaxGotTpOff(code[rel.r_offset - 3 ..]) catch unreachable; + try cwriter.writeIntLittle(i32, @as(i32, @intCast(S - TP))); + } + }, + else => {}, } } @@ -697,6 +740,80 @@ const x86_64 = struct { } } + pub fn canRelaxGotTpOff(code: []const u8) bool { + const old_inst = disassemble(code) orelse return false; + switch (old_inst.encoding.mnemonic) { + .mov => if (Instruction.new(old_inst.prefix, .mov, &.{ + old_inst.ops[0], + // TODO: hack to force imm32s in the assembler + .{ .imm = Immediate.s(-129) }, + })) |inst| { + inst.encode(std.io.null_writer, .{}) catch return false; + return true; + } else |_| return false, + else => return false, + } + } + + pub fn relaxGotTpOff(code: []u8) !void { + const old_inst = disassemble(code) orelse return error.RelaxFail; + switch (old_inst.encoding.mnemonic) { + .mov => { + const inst = try Instruction.new(old_inst.prefix, .mov, &.{ + old_inst.ops[0], + // TODO: hack to force imm32s in the assembler + .{ .imm = Immediate.s(-129) }, + }); + relocs_log.debug(" relaxing {} => {}", .{ old_inst.encoding, inst.encoding }); + encode(&.{inst}, code) catch return error.RelaxFail; + }, + else => return error.RelaxFail, + } + } + + pub fn relaxTlsGdToLe( + self: Atom, + rels: []align(1) const elf.Elf64_Rela, + value: i32, + elf_file: *Elf, + stream: anytype, + ) !void { + assert(rels.len == 2); + const writer = stream.writer(); + switch (rels[1].r_type()) { + elf.R_X86_64_PC32, + elf.R_X86_64_PLT32, + elf.R_X86_64_GOTPCREL, + elf.R_X86_64_GOTPCRELX, + => { + var insts = [_]u8{ + 0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // movq %fs:0,%rax + 0x48, 0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %rax + }; + std.mem.writeIntLittle(i32, insts[12..][0..4], value); + try stream.seekBy(-4); + try writer.writeAll(&insts); + relocs_log.debug(" relaxing {} and {}", .{ + fmtRelocType(rels[0].r_type()), + fmtRelocType(rels[1].r_type()), + }); + }, + + else => { + var err = try elf_file.addErrorWithNotes(1); + try err.addMsg(elf_file, "fatal linker error: rewrite {} when followed by {}", .{ + fmtRelocType(rels[0].r_type()), + fmtRelocType(rels[1].r_type()), + }); + try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{ + self.file(elf_file).?.fmtPath(), + self.name(elf_file), + rels[0].r_offset, + }); + }, + } + } + fn disassemble(code: []const u8) ?Instruction { var disas = Disassembler.init(code); const inst = disas.next() catch return null; diff --git a/src/link/Elf/Object.zig b/src/link/Elf/Object.zig index 9126190ed4..191f6774a5 100644 --- a/src/link/Elf/Object.zig +++ b/src/link/Elf/Object.zig @@ -208,6 +208,8 @@ fn getOutputSectionIndex(self: *Object, elf_file: *Elf, shdr: elf.Elf64_Shdr) er break :blk prefix; } } + if (std.mem.eql(u8, name, ".tcommon")) break :blk ".tbss"; + if (std.mem.eql(u8, name, ".common")) break :blk ".bss"; break :blk name; }; const @"type" = switch (shdr.sh_type) { @@ -427,7 +429,13 @@ pub fn scanRelocs(self: *Object, elf_file: *Elf, undefs: anytype) !void { const shdr = atom.inputShdr(elf_file); if (shdr.sh_flags & elf.SHF_ALLOC == 0) continue; if (shdr.sh_type == elf.SHT_NOBITS) continue; - try atom.scanRelocs(elf_file, undefs); + if (try atom.scanRelocsRequiresCode(elf_file)) { + // TODO ideally, we don't have to decompress at this stage (should already be done) + // and we just fetch the code slice. + const code = try self.codeDecompressAlloc(elf_file, atom_index); + defer elf_file.base.allocator.free(code); + try atom.scanRelocs(elf_file, code, undefs); + } else try atom.scanRelocs(elf_file, null, undefs); } for (self.cies.items) |cie| { @@ -590,7 +598,7 @@ pub fn convertCommonSymbols(self: *Object, elf_file: *Elf) !void { 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 name = if (is_tls) ".tbss" else ".bss"; const atom = elf_file.atom(atom_index).?; atom.atom_index = atom_index; @@ -684,7 +692,7 @@ pub fn globals(self: *Object) []const Symbol.Index { return self.symbols.items[start..]; } -pub fn shdrContents(self: *Object, index: u32) error{Overflow}![]const u8 { +fn shdrContents(self: Object, index: u32) error{Overflow}![]const u8 { assert(index < self.shdrs.items.len); const shdr = self.shdrs.items[index]; const offset = math.cast(usize, shdr.sh_offset) orelse return error.Overflow; @@ -692,6 +700,35 @@ pub fn shdrContents(self: *Object, index: u32) error{Overflow}![]const u8 { return self.data[offset..][0..size]; } +/// Returns atom's code and optionally uncompresses data if required (for compressed sections). +/// Caller owns the memory. +pub fn codeDecompressAlloc(self: Object, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { + const gpa = elf_file.base.allocator; + const atom_ptr = elf_file.atom(atom_index).?; + assert(atom_ptr.file_index == self.index); + const data = try self.shdrContents(atom_ptr.input_section_index); + const shdr = atom_ptr.inputShdr(elf_file); + if (shdr.sh_flags & elf.SHF_COMPRESSED != 0) { + const chdr = @as(*align(1) const elf.Elf64_Chdr, @ptrCast(data.ptr)).*; + switch (chdr.ch_type) { + .ZLIB => { + var stream = std.io.fixedBufferStream(data[@sizeOf(elf.Elf64_Chdr)..]); + var zlib_stream = std.compress.zlib.decompressStream(gpa, stream.reader()) catch + return error.InputOutput; + defer zlib_stream.deinit(); + const size = std.math.cast(usize, chdr.ch_size) orelse return error.Overflow; + const decomp = try gpa.alloc(u8, size); + const nread = zlib_stream.reader().readAll(decomp) catch return error.InputOutput; + if (nread != decomp.len) { + return error.InputOutput; + } + return decomp; + }, + else => @panic("TODO unhandled compression scheme"), + } + } else return gpa.dupe(u8, data); +} + 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); diff --git a/src/link/Elf/Symbol.zig b/src/link/Elf/Symbol.zig index ceda554929..e6cbc5ae04 100644 --- a/src/link/Elf/Symbol.zig +++ b/src/link/Elf/Symbol.zig @@ -327,10 +327,12 @@ pub const Flags = packed struct { has_dynamic: bool = false, /// Whether the symbol contains TLSGD indirection. - tlsgd: bool = false, + needs_tlsgd: bool = false, + has_tlsgd: bool = false, /// Whether the symbol contains GOTTP indirection. - gottp: bool = false, + needs_gottp: bool = false, + has_gottp: bool = false, /// Whether the symbol contains TLSDESC indirection. tlsdesc: bool = false, diff --git a/src/link/Elf/ZigModule.zig b/src/link/Elf/ZigModule.zig index 93908e5f1f..c79680dbf5 100644 --- a/src/link/Elf/ZigModule.zig +++ b/src/link/Elf/ZigModule.zig @@ -144,7 +144,14 @@ pub fn scanRelocs(self: *ZigModule, elf_file: *Elf, undefs: anytype) !void { for (self.atoms.keys()) |atom_index| { const atom = elf_file.atom(atom_index) orelse continue; if (!atom.flags.alive) continue; - try atom.scanRelocs(elf_file, undefs); + if (try atom.scanRelocsRequiresCode(elf_file)) { + // TODO ideally we don't have to fetch the code here. + // Perhaps it would make sense to save the code until flushModule where we + // would free all of generated code? + const code = try self.codeAlloc(elf_file, atom_index); + defer elf_file.base.allocator.free(code); + try atom.scanRelocs(elf_file, code, undefs); + } else try atom.scanRelocs(elf_file, null, undefs); } } @@ -253,6 +260,22 @@ pub fn asFile(self: *ZigModule) File { return .{ .zig_module = self }; } +/// Returns atom's code. +/// Caller owns the memory. +pub fn codeAlloc(self: ZigModule, elf_file: *Elf, atom_index: Atom.Index) ![]u8 { + const gpa = elf_file.base.allocator; + const atom = elf_file.atom(atom_index).?; + assert(atom.file_index == self.index); + const shdr = &elf_file.shdrs.items[atom.outputShndx().?]; + const file_offset = shdr.sh_offset + atom.value - shdr.sh_addr; + const size = std.math.cast(usize, atom.size) orelse return error.Overflow; + const code = try gpa.alloc(u8, size); + errdefer gpa.free(code); + const amt = try elf_file.base.file.?.preadAll(code, file_offset); + if (amt != code.len) return error.InputOutput; + return code; +} + pub fn fmtSymtab(self: *ZigModule, elf_file: *Elf) std.fmt.Formatter(formatSymtab) { return .{ .data = .{ .self = self, diff --git a/test/link/elf.zig b/test/link/elf.zig index d8bbc36d72..4748746cc9 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -158,19 +158,25 @@ fn addRunArtifact(comp: *Compile) *Run { return run; } -fn addZigSourceBytes(comp: *Compile, bytes: []const u8) void { +fn addZigSourceBytes(comp: *Compile, comptime bytes: []const u8) void { const b = comp.step.owner; const file = WriteFile.create(b).add("a.zig", bytes); file.addStepDependencies(&comp.step); comp.root_src = file; } -fn addCSourceBytes(comp: *Compile, bytes: []const u8) void { +fn addCSourceBytes(comp: *Compile, comptime bytes: []const u8) void { const b = comp.step.owner; const file = WriteFile.create(b).add("a.c", bytes); comp.addCSourceFile(.{ .file = file, .flags = &.{} }); } +fn addAsmSourceBytes(comp: *Compile, comptime bytes: []const u8) void { + const b = comp.step.owner; + const file = WriteFile.create(b).add("a.s", bytes ++ "\n"); + comp.addAssemblyFile(file); +} + const std = @import("std"); const Build = std.Build;