Commit 2c2bc66ce1

Jakub Konka <kubkon@jakubkonka.com>
2023-10-03 22:06:30
elf: handle .eh_frame and non-alloc sections
1 parent 9ccd94d
Changed files (4)
src/link/Elf/Atom.zig
@@ -460,10 +460,7 @@ fn reportUndefined(
     }
 }
 
-/// TODO mark relocs dirty
-pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
-    relocs_log.debug("0x{x}: {s}", .{ self.value, self.name(elf_file) });
-
+pub fn resolveRelocsAlloc(self: Atom, elf_file: *Elf, code: []u8) !void {
     const file_ptr = self.file(elf_file).?;
     var stream = std.io.fixedBufferStream(code);
     const cwriter = stream.writer();
@@ -505,8 +502,9 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
         const G = @as(i64, @intCast(target.gotAddress(elf_file))) - GOT;
         // // Address of the thread pointer.
         const TP = @as(i64, @intCast(elf_file.tpAddress()));
-        // // Address of the dynamic thread pointer.
-        // const DTP = @as(i64, @intCast(elf_file.dtpAddress()));
+        // Address of the dynamic thread pointer.
+        const DTP = @as(i64, @intCast(elf_file.dtpAddress()));
+        _ = DTP;
 
         relocs_log.debug("  {s}: {x}: [{x} => {x}] G({x}) ({s})", .{
             fmtRelocType(r_type),
@@ -597,6 +595,108 @@ pub fn resolveRelocs(self: Atom, elf_file: *Elf, code: []u8) !void {
     }
 }
 
+pub fn resolveRelocsNonAlloc(self: Atom, elf_file: *Elf, code: []u8, undefs: anytype) !void {
+    relocs_log.debug("0x{x}: {s}", .{ self.value, self.name(elf_file) });
+
+    const file_ptr = self.file(elf_file).?;
+    var stream = std.io.fixedBufferStream(code);
+    const cwriter = stream.writer();
+
+    const rels = 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;
+
+        const r_offset = std.math.cast(usize, rel.r_offset) orelse return error.Overflow;
+
+        const target_index = switch (file_ptr) {
+            .zig_module => |x| x.symbol(rel.r_sym()),
+            .object => |x| x.symbols.items[rel.r_sym()],
+            else => unreachable,
+        };
+        const target = elf_file.symbol(target_index);
+
+        // Check for violation of One Definition Rule for COMDATs.
+        if (target.file(elf_file) == null) {
+            // TODO convert into an error
+            log.debug("{}: {s}: {s} refers to a discarded COMDAT section", .{
+                file_ptr.fmtPath(),
+                self.name(elf_file),
+                target.name(elf_file),
+            });
+            continue;
+        }
+
+        // Report an undefined symbol.
+        try self.reportUndefined(elf_file, target, target_index, rel, undefs);
+
+        // We will use equation format to resolve relocations:
+        // https://intezer.com/blog/malware-analysis/executable-and-linkable-format-101-part-3-relocations/
+        //
+        const P = @as(i64, @intCast(self.value + rel.r_offset));
+        // Addend from the relocation.
+        const A = rel.r_addend;
+        // Address of the target symbol - can be address of the symbol within an atom or address of PLT stub.
+        const S = @as(i64, @intCast(target.address(.{}, elf_file)));
+        // Address of the global offset table.
+        const GOT = blk: {
+            const shndx = if (elf_file.got_plt_section_index) |shndx|
+                shndx
+            else if (elf_file.got_section_index) |shndx|
+                shndx
+            else
+                null;
+            break :blk if (shndx) |index| @as(i64, @intCast(elf_file.shdrs.items[index].sh_addr)) else 0;
+        };
+        // Address of the dynamic thread pointer.
+        const DTP = @as(i64, @intCast(elf_file.dtpAddress()));
+
+        relocs_log.debug("  {s}: {x}: [{x} => {x}] ({s})", .{
+            fmtRelocType(r_type),
+            rel.r_offset,
+            P,
+            S + A,
+            target.name(elf_file),
+        });
+
+        try stream.seekTo(r_offset);
+
+        switch (r_type) {
+            elf.R_X86_64_NONE => unreachable,
+            elf.R_X86_64_8 => try cwriter.writeIntLittle(u8, @as(u8, @bitCast(@as(i8, @intCast(S + A))))),
+            elf.R_X86_64_16 => try cwriter.writeIntLittle(u16, @as(u16, @bitCast(@as(i16, @intCast(S + A))))),
+            elf.R_X86_64_32 => try cwriter.writeIntLittle(u32, @as(u32, @bitCast(@as(i32, @intCast(S + A))))),
+            elf.R_X86_64_32S => try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A))),
+            elf.R_X86_64_64 => try cwriter.writeIntLittle(i64, S + A),
+            elf.R_X86_64_DTPOFF32 => try cwriter.writeIntLittle(i32, @as(i32, @intCast(S + A - DTP))),
+            elf.R_X86_64_DTPOFF64 => try cwriter.writeIntLittle(i64, S + A - DTP),
+            elf.R_X86_64_GOTOFF64 => try cwriter.writeIntLittle(i64, S + A - GOT),
+            elf.R_X86_64_GOTPC64 => try cwriter.writeIntLittle(i64, GOT + A),
+            elf.R_X86_64_SIZE32 => {
+                const size = @as(i64, @intCast(target.elfSym(elf_file).st_size));
+                try cwriter.writeIntLittle(u32, @as(u32, @bitCast(@as(i32, @intCast(size + A)))));
+            },
+            elf.R_X86_64_SIZE64 => {
+                const size = @as(i64, @intCast(target.elfSym(elf_file).st_size));
+                try cwriter.writeIntLittle(i64, @as(i64, @intCast(size + A)));
+            },
+            else => {
+                var err = try elf_file.addErrorWithNotes(1);
+                try err.addMsg(elf_file, "fatal linker error: unhandled relocation type {}", .{
+                    fmtRelocType(r_type),
+                });
+                try err.addNote(elf_file, "in {}:{s} at offset 0x{x}", .{
+                    self.file(elf_file).?.fmtPath(),
+                    self.name(elf_file),
+                    r_offset,
+                });
+            },
+        }
+    }
+}
+
 pub fn fmtRelocType(r_type: u32) std.fmt.Formatter(formatRelocType) {
     return .{ .data = r_type };
 }
@@ -696,17 +796,16 @@ fn format2(
         atom.atom_index,           atom.name(elf_file), atom.value,
         atom.output_section_index, atom.alignment,      atom.size,
     });
-    // if (atom.fde_start != atom.fde_end) {
-    //     try writer.writeAll(" : fdes{ ");
-    //     for (atom.getFdes(elf_file), atom.fde_start..) |fde, i| {
-    //         try writer.print("{d}", .{i});
-    //         if (!fde.alive) try writer.writeAll("([*])");
-    //         if (i < atom.fde_end - 1) try writer.writeAll(", ");
-    //     }
-    //     try writer.writeAll(" }");
-    // }
-    const gc_sections = if (elf_file.base.options.gc_sections) |gc_sections| gc_sections else false;
-    if (gc_sections and !atom.flags.alive) {
+    if (atom.fde_start != atom.fde_end) {
+        try writer.writeAll(" : fdes{ ");
+        for (atom.fdes(elf_file), atom.fde_start..) |fde, i| {
+            try writer.print("{d}", .{i});
+            if (!fde.alive) try writer.writeAll("([*])");
+            if (i < atom.fde_end - 1) try writer.writeAll(", ");
+        }
+        try writer.writeAll(" }");
+    }
+    if (!atom.flags.alive) {
         try writer.writeAll(" : [*]");
     }
 }
src/link/Elf/eh_frame.zig
@@ -321,7 +321,7 @@ pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void {
             defer gpa.free(contents);
 
             for (cie.relocs(elf_file)) |rel| {
-                const sym = object.symbol(rel.r_sym(), elf_file);
+                const sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);
                 try resolveReloc(cie, sym, rel, elf_file, contents);
             }
 
@@ -345,7 +345,7 @@ pub fn writeEhFrame(elf_file: *Elf, writer: anytype) !void {
             );
 
             for (fde.relocs(elf_file)) |rel| {
-                const sym = object.symbol(rel.r_sym(), elf_file);
+                const sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);
                 try resolveReloc(fde, sym, rel, elf_file, contents);
             }
 
@@ -396,7 +396,7 @@ pub fn writeEhFrameHdr(elf_file: *Elf, writer: anytype) !void {
             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 sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);
             const P = @as(i64, @intCast(fde.address(elf_file)));
             const S = @as(i64, @intCast(sym.address(.{}, elf_file)));
             const A = rel.r_addend;
src/link/Elf/Object.zig
@@ -255,7 +255,6 @@ fn skipShdr(self: *Object, index: u16, elf_file: *Elf) bool {
         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 (mem.startsWith(u8, name, ".eh_frame")) 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;
@@ -681,7 +680,7 @@ pub fn allocateAtoms(self: Object, elf_file: *Elf) void {
     }
 }
 
-pub fn writeAtoms(self: Object, elf_file: *Elf, output_section_index: u16, buffer: []u8) !void {
+pub fn writeAtoms(self: Object, elf_file: *Elf, output_section_index: u16, buffer: []u8, undefs: anytype) !void {
     const gpa = elf_file.base.allocator;
     const atom_list = self.output_sections.get(output_section_index) orelse return;
     const shdr = elf_file.shdrs.items[output_section_index];
@@ -695,7 +694,11 @@ pub fn writeAtoms(self: Object, elf_file: *Elf, output_section_index: u16, buffe
         const in_code = try self.codeDecompressAlloc(elf_file, atom_index);
         defer gpa.free(in_code);
         @memcpy(out_code, in_code);
-        try atom.resolveRelocs(elf_file, out_code);
+
+        if (shdr.sh_flags & elf.SHF_ALLOC == 0)
+            try atom.resolveRelocsNonAlloc(elf_file, out_code, undefs)
+        else
+            try atom.resolveRelocsAlloc(elf_file, out_code);
     }
 }
 
src/link/Elf.zig
@@ -1316,7 +1316,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
             const code = try zig_module.codeAlloc(self, atom_index);
             defer gpa.free(code);
             const file_offset = shdr.sh_offset + atom_ptr.value - shdr.sh_addr;
-            try atom_ptr.resolveRelocs(self, code);
+            try atom_ptr.resolveRelocsAlloc(self, code);
             try self.base.file.?.pwriteAll(code, file_offset);
         }
 
@@ -3912,6 +3912,16 @@ fn allocateAtoms(self: *Elf) void {
 
 fn writeAtoms(self: *Elf) !void {
     const gpa = self.base.allocator;
+
+    var undefs = std.AutoHashMap(Symbol.Index, std.ArrayList(Atom.Index)).init(gpa);
+    defer {
+        var it = undefs.iterator();
+        while (it.next()) |entry| {
+            entry.value_ptr.deinit();
+        }
+        undefs.deinit();
+    }
+
     for (self.shdrs.items, 0..) |shdr, shndx| {
         if (shdr.sh_type == elf.SHT_NULL) continue;
         if (shdr.sh_type == elf.SHT_NOBITS) continue;
@@ -3928,11 +3938,13 @@ fn writeAtoms(self: *Elf) !void {
         @memset(buffer, padding_byte);
 
         for (self.objects.items) |index| {
-            try self.file(index).?.object.writeAtoms(self, @intCast(shndx), buffer);
+            try self.file(index).?.object.writeAtoms(self, @intCast(shndx), buffer, &undefs);
         }
 
         try self.base.file.?.pwriteAll(buffer, shdr.sh_offset);
     }
+
+    try self.reportUndefined(&undefs);
 }
 
 fn updateSymtabSize(self: *Elf) !void {
@@ -3983,6 +3995,22 @@ fn updateSymtabSize(self: *Elf) !void {
 fn writeSyntheticSections(self: *Elf) !void {
     const gpa = self.base.allocator;
 
+    if (self.eh_frame_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, shdr.sh_size);
+        defer buffer.deinit();
+        try eh_frame.writeEhFrame(self, buffer.writer());
+        try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+    }
+
+    if (self.eh_frame_hdr_section_index) |shndx| {
+        const shdr = self.shdrs.items[shndx];
+        var buffer = try std.ArrayList(u8).initCapacity(gpa, shdr.sh_size);
+        defer buffer.deinit();
+        try eh_frame.writeEhFrameHdr(self, buffer.writer());
+        try self.base.file.?.pwriteAll(buffer.items, shdr.sh_offset);
+    }
+
     if (self.got_section_index) |index| {
         const shdr = self.shdrs.items[index];
         var buffer = try std.ArrayList(u8).initCapacity(gpa, self.got.size(self));