From 2ae2ac33d9ddd1fb181e08a811d97b1bf238bced Mon Sep 17 00:00:00 2001 From: Luuk de Gram Date: Wed, 4 May 2022 21:25:33 +0200 Subject: [PATCH] wasm: Emit debug sections This commit adds the ability to emit the following debug sections: .debug_info .debug_abbrev .debug_line .debug_str Line information and files are now being loaded correctly by browser debuggers. --- src/link/Dwarf.zig | 56 ++++++++++++++++++++++++++++++-------- src/link/Wasm.zig | 61 +++++++++++++++++++++++++++++++++++++----- src/link/Wasm/Atom.zig | 9 ------- 3 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index bf054c524c..a7d2744491 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -963,21 +963,18 @@ pub fn commitDeclState( const wasm_file = file.cast(File.Wasm).?; const segment_index = try wasm_file.getDebugLineIndex(); const segment = &wasm_file.segments.items[segment_index]; - const debug_atom = wasm_file.atoms.get(segment_index).?; + const debug_line = &wasm_file.debug_line; if (needed_size != segment.size) { log.debug(" needed size does not equal allocated size: {d}", .{needed_size}); if (needed_size > segment.size) { - log.debug(" allocating {d} bytes for debug line information", .{needed_size - segment.size}); - try debug_atom.code.resize(self.allocator, needed_size); - std.mem.set(u8, debug_atom.code.items[segment.size..], 0); + log.debug(" allocating {d} bytes for 'debug line' information", .{needed_size - segment.size}); + try debug_line.resize(self.allocator, needed_size); + mem.set(u8, debug_line.items[segment.size..], 0); } - debug_atom.size = needed_size; segment.size = needed_size; } - // since we can tighly pack the debug lines, wasm does not require - // us to pad with Nops. const offset = segment.offset + src_fn.off; - std.mem.copy(u8, debug_atom.code.items[offset..], dbg_line_buffer.items); + mem.copy(u8, debug_line.items[offset..], dbg_line_buffer.items); }, else => unreachable, } @@ -1116,7 +1113,9 @@ fn updateDeclDebugInfoAllocation(self: *Dwarf, file: *File, atom: *Atom, len: u3 const file_pos = debug_info_sect.offset + atom.off; try pwriteDbgInfoNops(d_sym.file, file_pos, 0, &[0]u8{}, atom.len, false); }, - .wasm => {}, + .wasm => { + log.debug(" todo: updateDeclDebugInfoAllocation for Wasm: {d}", .{atom.len}); + }, else => unreachable, } // TODO Look at the free list before appending at the end. @@ -1241,6 +1240,23 @@ fn writeDeclDebugInfo(self: *Dwarf, file: *File, atom: *Atom, dbg_info_buf: []co trailing_zero, ); }, + .wasm => { + const wasm_file = file.cast(File.Wasm).?; + const segment_index = try wasm_file.getDebugInfoIndex(); + const segment = &wasm_file.segments.items[segment_index]; + const debug_info = &wasm_file.debug_info; + if (needed_size != segment.size) { + log.debug(" needed size does not equal allocated size: {d}", .{needed_size}); + if (needed_size > segment.size) { + log.debug(" allocating {d} bytes for 'debug info' information", .{needed_size - segment.size}); + try debug_info.resize(self.allocator, needed_size); + mem.set(u8, debug_info.items[segment.size..], 0); + } + segment.size = needed_size; + } + const offset = segment.offset + atom.off; + mem.copy(u8, debug_info.items[offset..], dbg_info_buf); + }, else => unreachable, } } @@ -1279,8 +1295,7 @@ pub fn updateDeclLineNumber(self: *Dwarf, file: *File, decl: *const Module.Decl) const segment_index = wasm_file.getDebugLineIndex() catch unreachable; const segment = wasm_file.segments.items[segment_index]; const offset = segment.offset + decl.fn_link.wasm.src_fn.off + self.getRelocDbgLineOff(); - const debug_atom = wasm_file.atoms.get(segment_index).?; - std.mem.copy(u8, debug_atom.code.items[offset..], &data); + mem.copy(u8, wasm_file.debug_line.items[offset..], &data); }, else => unreachable, } @@ -1514,6 +1529,11 @@ pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void { const file_pos = debug_abbrev_sect.offset + abbrev_offset; try d_sym.file.pwriteAll(&abbrev_buf, file_pos); }, + .wasm => { + const wasm_file = file.cast(File.Wasm).?; + try wasm_file.debug_abbrev.resize(wasm_file.base.allocator, needed_size); + mem.copy(u8, wasm_file.debug_abbrev.items, &abbrev_buf); + }, else => unreachable, } } @@ -1621,6 +1641,10 @@ pub fn writeDbgInfoHeader(self: *Dwarf, file: *File, module: *Module, low_pc: u6 const file_pos = debug_info_sect.offset; try pwriteDbgInfoNops(d_sym.file, file_pos, 0, di_buf.items, jmp_amt, false); }, + .wasm => { + const wasm_file = file.cast(File.Wasm).?; + mem.copy(u8, wasm_file.debug_info.items, di_buf.items); + }, else => unreachable, } } @@ -2004,6 +2028,10 @@ pub fn writeDbgLineHeader(self: *Dwarf, file: *File, module: *Module) !void { const file_pos = debug_line_sect.offset; try pwriteDbgLineNops(d_sym.file, file_pos, 0, di_buf.items, jmp_amt); }, + .wasm => { + const wasm_file = file.cast(File.Wasm).?; + mem.copy(u8, wasm_file.debug_line.items, di_buf.items); + }, else => unreachable, } } @@ -2127,6 +2155,8 @@ pub fn flushModule(self: *Dwarf, file: *File, module: *Module) !void { const debug_info_sect = &dwarf_segment.sections.items[d_sym.debug_info_section_index.?]; break :blk debug_info_sect.offset; }, + // for wasm, the offset is always 0 as we write to memory first + .wasm => break :blk @as(u32, 0), else => unreachable, } }; @@ -2145,6 +2175,10 @@ pub fn flushModule(self: *Dwarf, file: *File, module: *Module) !void { const d_sym = &macho_file.d_sym.?; try d_sym.file.pwriteAll(&buf, file_pos + reloc.atom.off + reloc.offset); }, + .wasm => { + const wasm_file = file.cast(File.Wasm).?; + mem.copy(u8, wasm_file.debug_info.items[reloc.atom.off + reloc.offset ..], &buf); + }, else => unreachable, } } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index d2ef331a3d..4cc21260c3 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -90,6 +90,14 @@ string_table: StringTable = .{}, /// Debug information for wasm dwarf: ?Dwarf = null, +// *debug information* // +/// Contains all bytes for the '.debug_info' section +debug_info: std.ArrayListUnmanaged(u8) = .{}, +/// Contains all bytes for the '.debug_line' section +debug_line: std.ArrayListUnmanaged(u8) = .{}, +/// Contains all bytes for the '.debug_abbrev' section +debug_abbrev: std.ArrayListUnmanaged(u8) = .{}, + // Output sections /// Output type section func_types: std.ArrayListUnmanaged(wasm.Type) = .{}, @@ -501,6 +509,10 @@ pub fn deinit(self: *Wasm) void { if (self.dwarf) |*dwarf| { dwarf.deinit(); } + + self.debug_info.deinit(gpa); + self.debug_line.deinit(gpa); + self.debug_abbrev.deinit(gpa); } pub fn allocateDeclIndexes(self: *Wasm, decl_index: Module.Decl.Index) !void { @@ -576,7 +588,9 @@ pub fn updateFunc(self: *Wasm, mod: *Module, func: *Module.Fn, air: Air, livenes &self.base, mod, decl, - // Actual value will be written after relocation + // Actual value will be written after relocation. + // For Wasm, this is the offset relative to the code section + // which isn't known until flush(). 0, code.len, &decl_state.?, @@ -1016,10 +1030,12 @@ fn parseAtom(self: *Wasm, atom: *Atom, kind: Kind) !void { // segment indexes can be off by 1 due to also containing a segment // for the code section, so we must check if the existing segment // is larger than that of the code section, and substract the index by 1 in such case. - const info_add = if (self.code_section_index) |idx| blk: { + var info_add = if (self.code_section_index) |idx| blk: { if (idx < index) break :blk @as(u32, 1); break :blk 0; } else @as(u32, 0); + if (self.debug_info_index != null) info_add += 1; + if (self.debug_line_index != null) info_add += 1; symbol.index = index - info_add; // segment info already exists, so free its memory self.base.allocator.free(segment_name); @@ -1320,11 +1336,8 @@ fn setupMemory(self: *Wasm) !void { } var offset: u32 = @intCast(u32, memory_ptr); - for (self.segments.items) |*segment, i| { - // skip 'code' segments - if (self.code_section_index) |index| { - if (index == i) continue; - } + for (self.data_segments.values()) |segment_index| { + const segment = &self.segments.items[segment_index]; memory_ptr = std.mem.alignForwardGeneric(u64, memory_ptr, segment.alignment); memory_ptr += segment.size; segment.offset = offset; @@ -1588,6 +1601,7 @@ fn resetState(self: *Wasm) void { self.symbol_atom.clearRetainingCapacity(); self.code_section_index = null; self.debug_info_index = null; + self.debug_line_index = null; } pub fn flush(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !void { @@ -2016,10 +2030,43 @@ pub fn flushModule(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Nod try self.emitDataRelocations(file, arena, data_index, symbol_table); } } else if (!self.base.options.strip) { + if (self.dwarf) |*dwarf| { + if (self.debug_info_index != null) { + _ = dwarf; + try dwarf.writeDbgAbbrev(&self.base); + try dwarf.writeDbgInfoHeader(&self.base, mod, 0, 0); + try dwarf.writeDbgLineHeader(&self.base, mod); + + try emitDebugSection(file, self.debug_info.items, ".debug_info"); + try emitDebugSection(file, self.debug_abbrev.items, ".debug_abbrev"); // TODO + try emitDebugSection(file, self.debug_line.items, ".debug_line"); + try emitDebugSection(file, dwarf.strtab.items, ".debug_str"); + } + } try self.emitNameSection(file, arena); } } +fn emitDebugSection(file: fs.File, data: []const u8, name: []const u8) !void { + const header_offset = try reserveCustomSectionHeader(file); + const writer = file.writer(); + try leb.writeULEB128(writer, @intCast(u32, name.len)); + try writer.writeAll(name); + + try file.writevAll(&[_]std.os.iovec_const{.{ + .iov_base = data.ptr, + .iov_len = data.len, + }}); + const start = header_offset + 6 + name.len + getULEB128Size(@intCast(u32, name.len)); + log.debug("Emit debug section: '{s}' start=0x{x:0>8} end=0x{x:0>8}", .{ name, start, start + data.len }); + + try writeCustomSectionHeader( + file, + header_offset, + @intCast(u32, (try file.getPos()) - header_offset - 6), + ); +} + fn emitNameSection(self: *Wasm, file: fs.File, arena: Allocator) !void { const Name = struct { index: u32, diff --git a/src/link/Wasm/Atom.zig b/src/link/Wasm/Atom.zig index f56a0995bf..6f65b1b83a 100644 --- a/src/link/Wasm/Atom.zig +++ b/src/link/Wasm/Atom.zig @@ -90,15 +90,6 @@ pub fn getFirst(self: *Atom) *Atom { return tmp; } -/// Returns the atom for the given `symbol_index`. -/// This can be either the `Atom` itself, or one of its locals. -pub fn symbolAtom(self: *Atom, symbol_index: u32) *Atom { - if (self.sym_index == symbol_index) return self; - return for (self.locals.items) |*local_atom| { - if (local_atom.sym_index == symbol_index) break local_atom; - } else unreachable; // Used a symbol index not present in this atom or its children. -} - /// 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 };