Commit f2c8d09c4f

Luuk de Gram <luuk@degram.dev>
2022-09-04 21:00:44
wasm-linker: Mix Zig -and Object debug atoms
When linking a Zig-compilation with an object file, we allow mixing the debug atoms to make sure debug information is preserved from object files. By default, we now always initialize all debug sections if the `strip` flag is unset. This also fixes relocations for debug information as previously the offset of an atom wasn't calculated, and neither was the code size itself which meant that debug lines were off and file names from other object files were missing.
1 parent b2718e2
Changed files (3)
src/link/Wasm/Atom.zig
@@ -90,6 +90,19 @@ pub fn getFirst(self: *Atom) *Atom {
     return tmp;
 }
 
+/// Unlike `getFirst` this returns the first `*Atom` that was
+/// produced from Zig code, rather than an object file.
+/// This is useful for debug sections where we want to extend
+/// the bytes, and don't want to overwrite existing Atoms.
+pub fn getFirstZigAtom(self: *Atom) *Atom {
+    if (self.file == null) return self;
+    var tmp = self;
+    return while (tmp.prev) |prev| {
+        if (prev.file == null) break prev;
+        tmp = prev;
+    } else unreachable; // must allocate an Atom first!
+}
+
 /// Returns the location of the symbol that represents this `Atom`
 pub fn symbolLoc(self: Atom) Wasm.SymbolLoc {
     return .{ .file = self.file, .index = self.sym_index };
@@ -184,8 +197,24 @@ fn relocationValue(self: Atom, relocation: types.Relocation, wasm_bin: *const Wa
             return target_atom.offset + segment.offset + (relocation.addend orelse 0);
         },
         .R_WASM_EVENT_INDEX_LEB => return symbol.index,
-        .R_WASM_SECTION_OFFSET_I32,
-        .R_WASM_FUNCTION_OFFSET_I32,
-        => return relocation.addend orelse 0,
+        .R_WASM_SECTION_OFFSET_I32 => {
+            const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
+            return target_atom.offset + (relocation.addend orelse 0);
+        },
+        .R_WASM_FUNCTION_OFFSET_I32 => {
+            const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
+            var atom = target_atom.getFirst();
+            var offset: u32 = 0;
+            // TODO: Calculate this during atom allocation, rather than
+            // this linear calculation. For now it's done here as atoms
+            // are being sorted after atom allocation, as functions aren't
+            // merged until later.
+            while (true) {
+                offset += 5; // each atom uses 5 bytes to store its body's size
+                if (atom == target_atom) break;
+                atom = atom.next.?;
+            }
+            return target_atom.offset + offset + (relocation.addend orelse 0);
+        },
     }
 }
src/link/Dwarf.zig
@@ -862,7 +862,8 @@ pub fn commitDeclState(
                             .wasm => {
                                 const wasm_file = file.cast(File.Wasm).?;
                                 const segment_index = wasm_file.debug_line_index.?;
-                                const debug_line = wasm_file.atoms.get(segment_index).?.code;
+                                const atom = wasm_file.atoms.get(segment_index).?;
+                                const debug_line = atom.getFirstZigAtom().code;
                                 writeDbgLineNopsBuffered(debug_line.items, src_fn.off, 0, &.{}, src_fn.len);
                             },
                             else => unreachable,
@@ -974,9 +975,10 @@ pub fn commitDeclState(
                 },
                 .wasm => {
                     const wasm_file = file.cast(File.Wasm).?;
-                    const segment_index = try wasm_file.getOrSetDebugIndex(&wasm_file.debug_line_index);
+                    const segment_index = wasm_file.debug_line_index.?;
                     const segment = &wasm_file.segments.items[segment_index];
-                    const debug_line = &wasm_file.atoms.get(segment_index).?.code;
+                    const atom = wasm_file.atoms.get(segment_index).?;
+                    const debug_line = &atom.getFirstZigAtom().code;
                     if (needed_size != segment.size) {
                         log.debug(" needed size does not equal allocated size: {d}", .{needed_size});
                         if (needed_size > segment.size) {
@@ -1148,9 +1150,10 @@ fn updateDeclDebugInfoAllocation(self: *Dwarf, file: *File, atom: *Atom, len: u3
                     },
                     .wasm => {
                         const wasm_file = file.cast(File.Wasm).?;
-                        const segment_index = try wasm_file.getOrSetDebugIndex(&wasm_file.debug_info_index);
+                        const segment_index = wasm_file.debug_info_index.?;
                         const segment = &wasm_file.segments.items[segment_index];
-                        const debug_info = &wasm_file.atoms.get(segment_index).?.code;
+                        const info_atom = wasm_file.atoms.get(segment_index).?;
+                        const debug_info = &info_atom.getFirstZigAtom().code;
                         const offset = segment.offset + atom.off;
                         try writeDbgInfoNopsToArrayList(gpa, debug_info, offset, 0, &.{0}, atom.len, false);
                     },
@@ -1279,9 +1282,10 @@ fn writeDeclDebugInfo(self: *Dwarf, file: *File, atom: *Atom, dbg_info_buf: []co
         },
         .wasm => {
             const wasm_file = file.cast(File.Wasm).?;
-            const segment_index = try wasm_file.getOrSetDebugIndex(&wasm_file.debug_info_index);
+            const segment_index = wasm_file.debug_info_index.?;
             const segment = &wasm_file.segments.items[segment_index];
-            const debug_info = &wasm_file.atoms.get(segment_index).?.code;
+            const info_atom = wasm_file.atoms.get(segment_index).?;
+            const debug_info = &info_atom.getFirstZigAtom().code;
             if (needed_size != segment.size) {
                 log.debug(" needed size does not equal allocated size: {d}", .{needed_size});
                 if (needed_size > segment.size) {
@@ -1343,7 +1347,8 @@ pub fn updateDeclLineNumber(self: *Dwarf, file: *File, decl: *const Module.Decl)
             const segment_index = wasm_file.debug_line_index.?;
             const segment = wasm_file.segments.items[segment_index];
             const offset = segment.offset + decl.fn_link.wasm.src_fn.off + self.getRelocDbgLineOff();
-            mem.copy(u8, wasm_file.atoms.get(segment_index).?.code.items[offset..], &data);
+            const atom = wasm_file.atoms.get(segment_index).?.getFirstZigAtom();
+            mem.copy(u8, atom.code.items[offset..], &data);
         },
         else => unreachable,
     }
@@ -1579,8 +1584,8 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void {
         },
         .wasm => {
             const wasm_file = file.cast(File.Wasm).?;
-            const segment_index = try wasm_file.getOrSetDebugIndex(&wasm_file.debug_abbrev_index);
-            const debug_abbrev = &wasm_file.atoms.get(segment_index).?.code;
+            const segment_index = wasm_file.debug_abbrev_index.?;
+            const debug_abbrev = &wasm_file.atoms.get(segment_index).?.getFirstZigAtom().code;
             try debug_abbrev.resize(wasm_file.base.allocator, needed_size);
             mem.copy(u8, debug_abbrev.items, &abbrev_buf);
         },
@@ -1693,7 +1698,7 @@ pub fn writeDbgInfoHeader(self: *Dwarf, file: *File, module: *Module, low_pc: u6
         .wasm => {
             const wasm_file = file.cast(File.Wasm).?;
             const segment_index = wasm_file.debug_info_index.?;
-            const debug_info = &wasm_file.atoms.get(segment_index).?.code;
+            const debug_info = &wasm_file.atoms.get(segment_index).?.getFirstZigAtom().code;
             try writeDbgInfoNopsToArrayList(self.allocator, debug_info, 0, 0, di_buf.items, jmp_amt, false);
         },
         else => unreachable,
@@ -2023,8 +2028,8 @@ pub fn writeDbgAranges(self: *Dwarf, file: *File, addr: u64, size: u64) !void {
         },
         .wasm => {
             const wasm_file = file.cast(File.Wasm).?;
-            const segment_index = try wasm_file.getOrSetDebugIndex(&wasm_file.debug_ranges_index);
-            const debug_ranges = &wasm_file.atoms.get(segment_index).?.code;
+            const segment_index = wasm_file.debug_ranges_index.?;
+            const debug_ranges = &wasm_file.atoms.get(segment_index).?.getFirstZigAtom().code;
             try debug_ranges.resize(wasm_file.base.allocator, needed_size);
             mem.copy(u8, debug_ranges.items, di_buf.items);
         },
@@ -2149,7 +2154,7 @@ pub fn writeDbgLineHeader(self: *Dwarf, file: *File, module: *Module) !void {
         .wasm => {
             const wasm_file = file.cast(File.Wasm).?;
             const segment_index = wasm_file.debug_line_index.?;
-            const debug_line = wasm_file.atoms.get(segment_index).?.code;
+            const debug_line = wasm_file.atoms.get(segment_index).?.getFirstZigAtom().code;
             writeDbgLineNopsBuffered(debug_line.items, 0, 0, di_buf.items, jmp_amt);
         },
         else => unreachable,
@@ -2299,7 +2304,7 @@ pub fn flushModule(self: *Dwarf, file: *File, module: *Module) !void {
                 .wasm => {
                     const wasm_file = file.cast(File.Wasm).?;
                     const segment_index = wasm_file.debug_info_index.?;
-                    const debug_info = wasm_file.atoms.get(segment_index).?.code;
+                    const debug_info = wasm_file.atoms.get(segment_index).?.getFirstZigAtom().code;
                     mem.copy(u8, debug_info.items[reloc.atom.off + reloc.offset ..], &buf);
                 },
                 else => unreachable,
src/link/Wasm.zig
@@ -349,6 +349,7 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
         };
     }
 
+    try wasm_bin.initDebugSections();
     return wasm_bin;
 }
 
@@ -377,6 +378,23 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Wasm {
     return self;
 }
 
+/// Initializes symbols and atoms for the debug sections
+/// Initialization is only done when compiling Zig code.
+/// When Zig is invoked as a linker instead, the atoms
+/// and symbols come from the object files instead.
+pub fn initDebugSections(self: *Wasm) !void {
+    if (self.dwarf == null) return; // not compiling Zig code, so no need to pre-initialize debug sections
+    // this will create an Atom and set the index for us.
+    try self.createDebugSectionForIndex(&self.debug_info_index);
+    try self.createDebugSectionForIndex(&self.debug_line_index);
+    try self.createDebugSectionForIndex(&self.debug_loc_index);
+    try self.createDebugSectionForIndex(&self.debug_abbrev_index);
+    try self.createDebugSectionForIndex(&self.debug_ranges_index);
+    try self.createDebugSectionForIndex(&self.debug_str_index);
+    try self.createDebugSectionForIndex(&self.debug_pubnames_index);
+    try self.createDebugSectionForIndex(&self.debug_pubtypes_index);
+}
+
 fn parseInputFiles(self: *Wasm, files: []const []const u8) !void {
     for (files) |path| {
         if (try self.parseObjectFile(path)) continue;
@@ -1968,23 +1986,19 @@ fn populateErrorNameTable(self: *Wasm) !void {
     try self.parseAtom(names_atom, .{ .data = .read_only });
 }
 
-/// From a given index variable, returns it value if set.
-/// When not set, initialises a new segment, sets the index,
-/// and returns it value.
-/// When a new segment is initialised. It also creates an atom.
-pub fn getOrSetDebugIndex(self: *Wasm, index: *?u32) !u32 {
-    return (index.*) orelse {
-        const new_index = @intCast(u32, self.segments.items.len);
-        index.* = new_index;
-        try self.appendDummySegment();
-
-        const atom = try self.base.allocator.create(Atom);
-        atom.* = Atom.empty;
-        atom.alignment = 1; // debug sections are always 1-byte-aligned
-        try self.managed_atoms.append(self.base.allocator, atom);
-        try self.atoms.put(self.base.allocator, new_index, atom);
-        return new_index;
-    };
+/// From a given index variable, creates a new debug section.
+/// This initializes the index, appends a new segment,
+/// and finally, creates a managed `Atom`.
+pub fn createDebugSectionForIndex(self: *Wasm, index: *?u32) !void {
+    const new_index = @intCast(u32, self.segments.items.len);
+    index.* = new_index;
+    try self.appendDummySegment();
+
+    const atom = try self.base.allocator.create(Atom);
+    atom.* = Atom.empty;
+    atom.alignment = 1; // debug sections are always 1-byte-aligned
+    try self.managed_atoms.append(self.base.allocator, atom);
+    try self.atoms.put(self.base.allocator, new_index, atom);
 }
 
 fn resetState(self: *Wasm) void {