Commit 89c2151a97

Jakub Konka <kubkon@jakubkonka.com>
2023-09-28 18:35:26
elf: move logic for extracing atom's code into input files
1 parent 91f2e66
Changed files (6)
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;
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);
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,
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,
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;
 }
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;