diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Module.zig | 8 | ||||
| -rw-r--r-- | src/arch/arm/Emit.zig | 4 | ||||
| -rw-r--r-- | src/arch/riscv64/CodeGen.zig | 2 | ||||
| -rw-r--r-- | src/arch/x86_64/Emit.zig | 18 | ||||
| -rw-r--r-- | src/link.zig | 5 | ||||
| -rw-r--r-- | src/link/Dwarf.zig | 1625 | ||||
| -rw-r--r-- | src/link/Elf.zig | 1342 | ||||
| -rw-r--r-- | src/link/MachO.zig | 82 | ||||
| -rw-r--r-- | src/link/MachO/Atom.zig | 15 | ||||
| -rw-r--r-- | src/link/MachO/DebugSymbols.zig | 1170 |
10 files changed, 1786 insertions, 2485 deletions
diff --git a/src/Module.zig b/src/Module.zig index 0bcdc85c98..fa58add2e6 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4439,8 +4439,8 @@ pub fn clearDecl( }; decl.fn_link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = {} }, - .elf => .{ .elf = link.File.Elf.SrcFn.empty }, - .macho => .{ .macho = link.File.MachO.SrcFn.empty }, + .elf => .{ .elf = link.File.Dwarf.SrcFn.empty }, + .macho => .{ .macho = link.File.Dwarf.SrcFn.empty }, .plan9 => .{ .plan9 = {} }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.FnData.empty }, @@ -4776,8 +4776,8 @@ pub fn allocateNewDecl( }, .fn_link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = {} }, - .elf => .{ .elf = link.File.Elf.SrcFn.empty }, - .macho => .{ .macho = link.File.MachO.SrcFn.empty }, + .elf => .{ .elf = link.File.Dwarf.SrcFn.empty }, + .macho => .{ .macho = link.File.Dwarf.SrcFn.empty }, .plan9 => .{ .plan9 = {} }, .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.FnData.empty }, diff --git a/src/arch/arm/Emit.zig b/src/arch/arm/Emit.zig index 7ffd6de2dd..963879519f 100644 --- a/src/arch/arm/Emit.zig +++ b/src/arch/arm/Emit.zig @@ -409,7 +409,7 @@ fn genArgDbgInfo(self: *Emit, inst: Air.Inst.Index, arg_index: u32) !void { switch (self.debug_output) { .dwarf => |dbg_out| { try dbg_out.dbg_info.ensureUnusedCapacity(3); - dbg_out.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); + dbg_out.dbg_info.appendAssumeCapacity(link.File.Dwarf.abbrev_parameter); dbg_out.dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc 1, // ULEB128 dwarf expression length reg.dwarfLocOp(), @@ -440,7 +440,7 @@ fn genArgDbgInfo(self: *Emit, inst: Air.Inst.Index, arg_index: u32) !void { else => unreachable, }; - try dbg_out.dbg_info.append(link.File.Elf.abbrev_parameter); + try dbg_out.dbg_info.append(link.File.Dwarf.abbrev_parameter); // Get length of the LEB128 stack offset var counting_writer = std.io.countingWriter(std.io.null_writer); diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index c14e54e9e4..8819db6d64 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -1384,7 +1384,7 @@ fn genArgDbgInfo(self: *Self, inst: Air.Inst.Index, mcv: MCValue, arg_index: u32 switch (self.debug_output) { .dwarf => |dbg_out| { try dbg_out.dbg_info.ensureUnusedCapacity(3); - dbg_out.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); + dbg_out.dbg_info.appendAssumeCapacity(link.File.Dwarf.abbrev_parameter); dbg_out.dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc 1, // ULEB128 dwarf expression length reg.dwarfLocOp(), diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 86cdf8dcdb..3409894fc4 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -1019,14 +1019,7 @@ fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, max_stack: u32 switch (emit.debug_output) { .dwarf => |dbg_out| { try dbg_out.dbg_info.ensureUnusedCapacity(3); - - // TODO this will go away once we pull DWARF into a cross-platform module. - if (emit.bin_file.cast(link.File.MachO)) |_| { - dbg_out.dbg_info.appendAssumeCapacity(link.File.MachO.DebugSymbols.abbrev_parameter); - } else if (emit.bin_file.cast(link.File.Elf)) |_| { - dbg_out.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); - } else return emit.fail("TODO DWARF in non-MachO and non-ELF backend", .{}); - + dbg_out.dbg_info.appendAssumeCapacity(link.File.Dwarf.abbrev_parameter); dbg_out.dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc 1, // ULEB128 dwarf expression length reg.dwarfLocOp(), @@ -1049,14 +1042,7 @@ fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue, max_stack: u32 // for example when -fomit-frame-pointer is set. const disp = @intCast(i32, max_stack) - off + 16; try dbg_out.dbg_info.ensureUnusedCapacity(8); - - // TODO this will go away once we pull DWARF into a cross-platform module. - if (emit.bin_file.cast(link.File.MachO)) |_| { - dbg_out.dbg_info.appendAssumeCapacity(link.File.MachO.DebugSymbols.abbrev_parameter); - } else if (emit.bin_file.cast(link.File.Elf)) |_| { - dbg_out.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); - } else return emit.fail("TODO DWARF in non-MachO and non-ELF backend", .{}); - + dbg_out.dbg_info.appendAssumeCapacity(link.File.Dwarf.abbrev_parameter); const fixup = dbg_out.dbg_info.items.len; dbg_out.dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc 1, // we will backpatch it after we encode the displacement in LEB128 diff --git a/src/link.zig b/src/link.zig index 6e33b11669..577a78d893 100644 --- a/src/link.zig +++ b/src/link.zig @@ -221,9 +221,9 @@ pub const File = struct { }; pub const LinkFn = union { - elf: Elf.SrcFn, + elf: Dwarf.SrcFn, coff: Coff.SrcFn, - macho: MachO.SrcFn, + macho: Dwarf.SrcFn, plan9: void, c: void, wasm: Wasm.FnData, @@ -915,6 +915,7 @@ pub const File = struct { pub const SpirV = @import("link/SpirV.zig"); pub const Wasm = @import("link/Wasm.zig"); pub const NvPtx = @import("link/NvPtx.zig"); + pub const Dwarf = @import("link/Dwarf.zig"); }; pub fn determineMode(options: Options) fs.File.Mode { diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig new file mode 100644 index 0000000000..c4e31eed6d --- /dev/null +++ b/src/link/Dwarf.zig @@ -0,0 +1,1625 @@ +const Dwarf = @This(); + +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const fs = std.fs; +const leb128 = std.leb; +const log = std.log.scoped(.dwarf); +const mem = std.mem; + +const link = @import("../link.zig"); +const trace = @import("../tracy.zig").trace; + +const Allocator = mem.Allocator; +const DW = std.dwarf; +const File = link.File; +const LinkBlock = File.LinkBlock; +const LinkFn = File.LinkFn; +const Module = @import("../Module.zig"); +const Value = @import("../value.zig").Value; +const Type = @import("../type.zig").Type; + +allocator: Allocator, +tag: File.Tag, +ptr_width: PtrWidth, +target: std.Target, + +/// A list of `File.LinkFn` whose Line Number Programs have surplus capacity. +/// This is the same concept as `text_block_free_list`; see those doc comments. +dbg_line_fn_free_list: std.AutoHashMapUnmanaged(*SrcFn, void) = .{}, +dbg_line_fn_first: ?*SrcFn = null, +dbg_line_fn_last: ?*SrcFn = null, + +/// A list of `TextBlock` whose corresponding .debug_info tags have surplus capacity. /// This is the same concept as `text_block_free_list`; see those doc comments. +dbg_info_decl_free_list: std.AutoHashMapUnmanaged(*DebugInfoAtom, void) = .{}, +dbg_info_decl_first: ?*DebugInfoAtom = null, +dbg_info_decl_last: ?*DebugInfoAtom = null, + +abbrev_table_offset: ?u64 = null, + +/// Table of debug symbol names. +strtab: std.ArrayListUnmanaged(u8) = .{}, + +pub const DebugInfoAtom = struct { + /// Previous/next linked list pointers. + /// This is the linked list node for this Decl's corresponding .debug_info tag. + prev: ?*DebugInfoAtom, + next: ?*DebugInfoAtom, + /// Offset into .debug_info pointing to the tag for this Decl. + off: u32, + /// Size of the .debug_info tag for this Decl, not including padding. + len: u32, +}; + +pub const SrcFn = struct { + /// Offset from the beginning of the Debug Line Program header that contains this function. + off: u32, + /// Size of the line number program component belonging to this function, not + /// including padding. + len: u32, + + /// Points to the previous and next neighbors, based on the offset from .debug_line. + /// This can be used to find, for example, the capacity of this `SrcFn`. + prev: ?*SrcFn, + next: ?*SrcFn, + + pub const empty: SrcFn = .{ + .off = 0, + .len = 0, + .prev = null, + .next = null, + }; +}; + +pub const PtrWidth = enum { p32, p64 }; + +pub const abbrev_compile_unit = 1; +pub const abbrev_subprogram = 2; +pub const abbrev_subprogram_retvoid = 3; +pub const abbrev_base_type = 4; +pub const abbrev_ptr_type = 5; +pub const abbrev_struct_type = 6; +pub const abbrev_struct_member = 7; +pub const abbrev_pad1 = 8; +pub const abbrev_parameter = 9; + +/// The reloc offset for the virtual address of a function in its Line Number Program. +/// Size is a virtual address integer. +const dbg_line_vaddr_reloc_index = 3; +/// The reloc offset for the virtual address of a function in its .debug_info TAG.subprogram. +/// Size is a virtual address integer. +const dbg_info_low_pc_reloc_index = 1; + +const min_nop_size = 2; + +/// When allocating, the ideal_capacity is calculated by +/// actual_capacity + (actual_capacity / ideal_factor) +const ideal_factor = 3; + +pub fn init(allocator: Allocator, tag: File.Tag, target: std.Target) Dwarf { + const ptr_width: PtrWidth = switch (target.cpu.arch.ptrBitWidth()) { + 0...32 => .p32, + 33...64 => .p64, + else => unreachable, + }; + return Dwarf{ + .allocator = allocator, + .tag = tag, + .ptr_width = ptr_width, + .target = target, + }; +} + +pub fn deinit(self: *Dwarf) void { + const gpa = self.allocator; + self.dbg_line_fn_free_list.deinit(gpa); + self.dbg_info_decl_free_list.deinit(gpa); + self.strtab.deinit(gpa); +} + +pub const DeclDebugBuffers = struct { + dbg_line_buffer: std.ArrayList(u8), + dbg_info_buffer: std.ArrayList(u8), + dbg_info_type_relocs: File.DbgInfoTypeRelocsTable, +}; + +pub fn initDeclDebugInfo(self: *Dwarf, decl: *Module.Decl) !DeclDebugBuffers { + const tracy = trace(@src()); + defer tracy.end(); + + const decl_name = try decl.getFullyQualifiedName(self.allocator); + defer self.allocator.free(decl_name); + + log.debug("initDeclDebugInfo {s}{*}", .{ decl_name, decl }); + + const gpa = self.allocator; + var dbg_line_buffer = std.ArrayList(u8).init(gpa); + var dbg_info_buffer = std.ArrayList(u8).init(gpa); + var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{}; + + assert(decl.has_tv); + + switch (decl.ty.zigTypeTag()) { + .Fn => { + // For functions we need to add a prologue to the debug line program. + try dbg_line_buffer.ensureTotalCapacity(26); + + const func = decl.val.castTag(.function).?.data; + log.debug("decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d}", .{ + decl.src_line, + func.lbrace_line, + func.rbrace_line, + }); + const line = @intCast(u28, decl.src_line + func.lbrace_line); + + const ptr_width_bytes = self.ptrWidthBytes(); + dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{ + DW.LNS.extended_op, + ptr_width_bytes + 1, + DW.LNE.set_address, + }); + // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`. + assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len); + dbg_line_buffer.items.len += ptr_width_bytes; + + dbg_line_buffer.appendAssumeCapacity(DW.LNS.advance_line); + // This is the "relocatable" relative line offset from the previous function's end curly + // to this function's begin curly. + assert(self.getRelocDbgLineOff() == dbg_line_buffer.items.len); + // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later. + leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line); + + dbg_line_buffer.appendAssumeCapacity(DW.LNS.set_file); + assert(self.getRelocDbgFileIndex() == dbg_line_buffer.items.len); + // Once we support more than one source file, this will have the ability to be more + // than one possible value. + const file_index = 1; + leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index); + + // Emit a line for the begin curly with prologue_end=false. The codegen will + // do the work of setting prologue_end=true and epilogue_begin=true. + dbg_line_buffer.appendAssumeCapacity(DW.LNS.copy); + + // .debug_info subprogram + const decl_name_with_null = decl_name[0 .. decl_name.len + 1]; + try dbg_info_buffer.ensureUnusedCapacity(25 + decl_name_with_null.len); + + const fn_ret_type = decl.ty.fnReturnType(); + const fn_ret_has_bits = fn_ret_type.hasRuntimeBits(); + if (fn_ret_has_bits) { + dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram); + } else { + dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram_retvoid); + } + // These get overwritten after generating the machine code. These values are + // "relocations" and have to be in this fixed place so that functions can be + // moved in virtual address space. + assert(dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len); + dbg_info_buffer.items.len += ptr_width_bytes; // DW.AT.low_pc, DW.FORM.addr + assert(self.getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len); + dbg_info_buffer.items.len += 4; // DW.AT.high_pc, DW.FORM.data4 + if (fn_ret_has_bits) { + const gop = try dbg_info_type_relocs.getOrPut(gpa, fn_ret_type); + if (!gop.found_existing) { + gop.value_ptr.* = .{ + .off = undefined, + .relocs = .{}, + }; + } + try gop.value_ptr.relocs.append(gpa, @intCast(u32, dbg_info_buffer.items.len)); + dbg_info_buffer.items.len += 4; // DW.AT.type, DW.FORM.ref4 + } + dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT.name, DW.FORM.string + + }, + else => { + // TODO implement .debug_info for global variables + }, + } + + return DeclDebugBuffers{ + .dbg_info_buffer = dbg_info_buffer, + .dbg_line_buffer = dbg_line_buffer, + .dbg_info_type_relocs = dbg_info_type_relocs, + }; +} + +pub fn commitDeclDebugInfo( + self: *Dwarf, + file: *File, + module: *Module, + decl: *Module.Decl, + sym_addr: u64, + sym_size: u64, + debug_buffers: *DeclDebugBuffers, +) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = self.allocator; + var dbg_line_buffer = &debug_buffers.dbg_line_buffer; + var dbg_info_buffer = &debug_buffers.dbg_info_buffer; + var dbg_info_type_relocs = &debug_buffers.dbg_info_type_relocs; + + const target_endian = self.target.cpu.arch.endian(); + + assert(decl.has_tv); + switch (decl.ty.zigTypeTag()) { + .Fn => { + // Since the Decl is a function, we need to update the .debug_line program. + // Perform the relocations based on vaddr. + switch (self.ptr_width) { + .p32 => { + { + const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..4]; + mem.writeInt(u32, ptr, @intCast(u32, sym_addr), target_endian); + } + { + const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..4]; + mem.writeInt(u32, ptr, @intCast(u32, sym_addr), target_endian); + } + }, + .p64 => { + { + const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8]; + mem.writeInt(u64, ptr, sym_addr, target_endian); + } + { + const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..8]; + mem.writeInt(u64, ptr, sym_addr, target_endian); + } + }, + } + { + const ptr = dbg_info_buffer.items[self.getRelocDbgInfoSubprogramHighPC()..][0..4]; + mem.writeInt(u32, ptr, @intCast(u32, sym_size), target_endian); + } + + try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS.extended_op, 1, DW.LNE.end_sequence }); + + // Now we have the full contents and may allocate a region to store it. + + // This logic is nearly identical to the logic below in `updateDeclDebugInfo` for + // `TextBlock` and the .debug_info. If you are editing this logic, you + // probably need to edit that logic too. + const src_fn = switch (self.tag) { + .elf => &decl.fn_link.elf, + .macho => &decl.fn_link.macho, + else => unreachable, // TODO + }; + src_fn.len = @intCast(u32, dbg_line_buffer.items.len); + + if (self.dbg_line_fn_last) |last| blk: { + if (src_fn == last) break :blk; + if (src_fn.next) |next| { + // Update existing function - non-last item. + if (src_fn.off + src_fn.len + min_nop_size > next.off) { + // It grew too big, so we move it to a new location. + if (src_fn.prev) |prev| { + self.dbg_line_fn_free_list.put(gpa, prev, {}) catch {}; + prev.next = src_fn.next; + } + next.prev = src_fn.prev; + src_fn.next = null; + // Populate where it used to be with NOPs. + switch (self.tag) { + .elf => { + const elf_file = file.cast(File.Elf).?; + const debug_line_sect = &elf_file.sections.items[elf_file.debug_line_section_index.?]; + const file_pos = debug_line_sect.sh_offset + src_fn.off; + try pwriteDbgLineNops(elf_file.base.file.?, file_pos, 0, &[0]u8{}, src_fn.len); + }, + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = &macho_file.d_sym.?; + const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment; + const debug_line_sect = &dwarf_segment.sections.items[d_sym.debug_line_section_index.?]; + const file_pos = debug_line_sect.offset + src_fn.off; + try pwriteDbgLineNops(d_sym.file, file_pos, 0, &[0]u8{}, src_fn.len); + }, + else => unreachable, + } + // TODO Look at the free list before appending at the end. + src_fn.prev = last; + last.next = src_fn; + self.dbg_line_fn_last = src_fn; + + src_fn.off = last.off + padToIdeal(last.len); + } + } else if (src_fn.prev == null) { + // Append new function. + // TODO Look at the free list before appending at the end. + src_fn.prev = last; + last.next = src_fn; + self.dbg_line_fn_last = src_fn; + + src_fn.off = last.off + padToIdeal(last.len); + } + } else { + // This is the first function of the Line Number Program. + self.dbg_line_fn_first = src_fn; + self.dbg_line_fn_last = src_fn; + + src_fn.off = padToIdeal(self.dbgLineNeededHeaderBytes(module)); + } + + const last_src_fn = self.dbg_line_fn_last.?; + const needed_size = last_src_fn.off + last_src_fn.len; + const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0; + const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0; + + // We only have support for one compilation unit so far, so the offsets are directly + // from the .debug_line section. + switch (self.tag) { + .elf => { + const elf_file = file.cast(File.Elf).?; + const debug_line_sect = &elf_file.sections.items[elf_file.debug_line_section_index.?]; + if (needed_size != debug_line_sect.sh_size) { + if (needed_size > elf_file.allocatedSize(debug_line_sect.sh_offset)) { + const new_offset = elf_file.findFreeSpace(needed_size, 1); + const existing_size = last_src_fn.off; + log.debug("moving .debug_line section: {d} bytes from 0x{x} to 0x{x}", .{ + existing_size, + debug_line_sect.sh_offset, + new_offset, + }); + const amt = try elf_file.base.file.?.copyRangeAll( + debug_line_sect.sh_offset, + elf_file.base.file.?, + new_offset, + existing_size, + ); + if (amt != existing_size) return error.InputOutput; + debug_line_sect.sh_offset = new_offset; + } + debug_line_sect.sh_size = needed_size; + elf_file.shdr_table_dirty = true; // TODO look into making only the one section dirty + elf_file.debug_line_header_dirty = true; + } + const file_pos = debug_line_sect.sh_offset + src_fn.off; + try pwriteDbgLineNops( + elf_file.base.file.?, + file_pos, + prev_padding_size, + dbg_line_buffer.items, + next_padding_size, + ); + }, + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = &macho_file.d_sym.?; + const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment; + const debug_line_sect = &dwarf_segment.sections.items[d_sym.debug_line_section_index.?]; + if (needed_size != debug_line_sect.size) { + if (needed_size > d_sym.allocatedSize(debug_line_sect.offset)) { + const new_offset = d_sym.findFreeSpace(needed_size, 1); + const existing_size = last_src_fn.off; + + log.debug("moving __debug_line section: {} bytes from 0x{x} to 0x{x}", .{ + existing_size, + debug_line_sect.offset, + new_offset, + }); + + try File.MachO.copyRangeAllOverlappingAlloc( + gpa, + d_sym.file, + debug_line_sect.offset, + new_offset, + existing_size, + ); + + debug_line_sect.offset = @intCast(u32, new_offset); + debug_line_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; + } + debug_line_sect.size = needed_size; + d_sym.load_commands_dirty = true; // TODO look into making only the one section dirty + d_sym.debug_line_header_dirty = true; + } + const file_pos = debug_line_sect.offset + src_fn.off; + try pwriteDbgLineNops( + d_sym.file, + file_pos, + prev_padding_size, + dbg_line_buffer.items, + next_padding_size, + ); + }, + else => unreachable, + } + + // .debug_info - End the TAG.subprogram children. + try dbg_info_buffer.append(0); + }, + else => {}, + } + + if (dbg_info_buffer.items.len == 0) + return; + + // We need this for the duration of this function only so that for composite + // types such as []const u32, if the type *u32 is non-existent, we create + // it synthetically and store the backing bytes in this arena. After we are + // done with the relocations, we can safely deinit the entire memory slab. + // TODO currently, we do not store the relocations for future use, however, + // if that is the case, we should move memory management to a higher scope, + // such as linker scope, or whatnot. + var dbg_type_arena = std.heap.ArenaAllocator.init(gpa); + defer dbg_type_arena.deinit(); + + { + // Now we emit the .debug_info types of the Decl. These will count towards the size of + // the buffer, so we have to do it before computing the offset, and we can't perform the actual + // relocations yet. + var it: usize = 0; + while (it < dbg_info_type_relocs.count()) : (it += 1) { + const ty = dbg_info_type_relocs.keys()[it]; + const value_ptr = dbg_info_type_relocs.getPtr(ty).?; + value_ptr.off = @intCast(u32, dbg_info_buffer.items.len); + try self.addDbgInfoType(dbg_type_arena.allocator(), ty, dbg_info_buffer, dbg_info_type_relocs); + } + } + + const atom = switch (self.tag) { + .elf => &decl.link.elf.dbg_info_atom, + .macho => &decl.link.macho.dbg_info_atom, + else => unreachable, + }; + try self.updateDeclDebugInfoAllocation(file, atom, @intCast(u32, dbg_info_buffer.items.len)); + + { + // Now that we have the offset assigned we can finally perform type relocations. + for (dbg_info_type_relocs.values()) |value| { + for (value.relocs.items) |off| { + mem.writeIntLittle( + u32, + dbg_info_buffer.items[off..][0..4], + atom.off + value.off, + ); + } + } + } + + try self.writeDeclDebugInfo(file, atom, dbg_info_buffer.items); +} + +fn updateDeclDebugInfoAllocation(self: *Dwarf, file: *File, atom: *DebugInfoAtom, len: u32) !void { + const tracy = trace(@src()); + defer tracy.end(); + + // This logic is nearly identical to the logic above in `updateDecl` for + // `SrcFn` and the line number programs. If you are editing this logic, you + // probably need to edit that logic too. + const gpa = self.allocator; + + atom.len = len; + if (self.dbg_info_decl_last) |last| blk: { + if (atom == last) break :blk; + if (atom.next) |next| { + // Update existing Decl - non-last item. + if (atom.off + atom.len + min_nop_size > next.off) { + // It grew too big, so we move it to a new location. + if (atom.prev) |prev| { + self.dbg_info_decl_free_list.put(gpa, prev, {}) catch {}; + prev.next = atom.next; + } + next.prev = atom.prev; + atom.next = null; + // Populate where it used to be with NOPs. + switch (self.tag) { + .elf => { + const elf_file = file.cast(File.Elf).?; + const debug_info_sect = &elf_file.sections.items[elf_file.debug_info_section_index.?]; + const file_pos = debug_info_sect.sh_offset + atom.off; + try pwriteDbgInfoNops(elf_file.base.file.?, file_pos, 0, &[0]u8{}, atom.len, false); + }, + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = &macho_file.d_sym.?; + const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment; + const debug_info_sect = &dwarf_segment.sections.items[d_sym.debug_info_section_index.?]; + const file_pos = debug_info_sect.offset + atom.off; + try pwriteDbgInfoNops(d_sym.file, file_pos, 0, &[0]u8{}, atom.len, false); + }, + else => unreachable, + } + // TODO Look at the free list before appending at the end. + atom.prev = last; + last.next = atom; + self.dbg_info_decl_last = atom; + + atom.off = last.off + padToIdeal(last.len); + } + } else if (atom.prev == null) { + // Append new Decl. + // TODO Look at the free list before appending at the end. + atom.prev = last; + last.next = atom; + self.dbg_info_decl_last = atom; + + atom.off = last.off + padToIdeal(last.len); + } + } else { + // This is the first Decl of the .debug_info + self.dbg_info_decl_first = atom; + self.dbg_info_decl_last = atom; + + atom.off = @intCast(u32, padToIdeal(self.dbgInfoHeaderBytes())); + } +} + +fn writeDeclDebugInfo(self: *Dwarf, file: *File, atom: *DebugInfoAtom, dbg_info_buf: []const u8) !void { + const tracy = trace(@src()); + defer tracy.end(); + + // This logic is nearly identical to the logic above in `updateDecl` for + // `SrcFn` and the line number programs. If you are editing this logic, you + // probably need to edit that logic too. + const gpa = self.allocator; + + const last_decl = self.dbg_info_decl_last.?; + // +1 for a trailing zero to end the children of the decl tag. + const needed_size = last_decl.off + last_decl.len + 1; + const prev_padding_size: u32 = if (atom.prev) |prev| atom.off - (prev.off + prev.len) else 0; + const next_padding_size: u32 = if (atom.next) |next| next.off - (atom.off + atom.len) else 0; + + // To end the children of the decl tag. + const trailing_zero = atom.next == null; + + // We only have support for one compilation unit so far, so the offsets are directly + // from the .debug_info section. + switch (self.tag) { + .elf => { + const elf_file = file.cast(File.Elf).?; + const debug_info_sect = &elf_file.sections.items[elf_file.debug_info_section_index.?]; + if (needed_size != debug_info_sect.sh_size) { + if (needed_size > elf_file.allocatedSize(debug_info_sect.sh_offset)) { + const new_offset = elf_file.findFreeSpace(needed_size, 1); + const existing_size = last_decl.off; + log.debug("moving .debug_info section: {d} bytes from 0x{x} to 0x{x}", .{ + existing_size, + debug_info_sect.sh_offset, + new_offset, + }); + const amt = try elf_file.base.file.?.copyRangeAll( + debug_info_sect.sh_offset, + elf_file.base.file.?, + new_offset, + existing_size, + ); + if (amt != existing_size) return error.InputOutput; + debug_info_sect.sh_offset = new_offset; + } + debug_info_sect.sh_size = needed_size; + elf_file.shdr_table_dirty = true; // TODO look into making only the one section dirty + elf_file.debug_info_header_dirty = true; + } + const file_pos = debug_info_sect.sh_offset + atom.off; + try pwriteDbgInfoNops( + elf_file.base.file.?, + file_pos, + prev_padding_size, + dbg_info_buf, + next_padding_size, + trailing_zero, + ); + }, + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = &macho_file.d_sym.?; + const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment; + const debug_info_sect = &dwarf_segment.sections.items[d_sym.debug_info_section_index.?]; + if (needed_size != debug_info_sect.size) { + if (needed_size > d_sym.allocatedSize(debug_info_sect.offset)) { + const new_offset = d_sym.findFreeSpace(needed_size, 1); + const existing_size = last_decl.off; + + log.debug("moving __debug_info section: {} bytes from 0x{x} to 0x{x}", .{ + existing_size, + debug_info_sect.offset, + new_offset, + }); + + try File.MachO.copyRangeAllOverlappingAlloc( + gpa, + d_sym.file, + debug_info_sect.offset, + new_offset, + existing_size, + ); + + debug_info_sect.offset = @intCast(u32, new_offset); + debug_info_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; + } + debug_info_sect.size = needed_size; + d_sym.load_commands_dirty = true; // TODO look into making only the one section dirty + d_sym.debug_line_header_dirty = true; + } + const file_pos = debug_info_sect.offset + atom.off; + try pwriteDbgInfoNops( + d_sym.file, + file_pos, + prev_padding_size, + dbg_info_buf, + next_padding_size, + trailing_zero, + ); + }, + else => unreachable, + } +} + +pub fn updateDeclLineNumber(self: *Dwarf, file: *File, decl: *const Module.Decl) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const func = decl.val.castTag(.function).?.data; + log.debug("decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d}", .{ + decl.src_line, + func.lbrace_line, + func.rbrace_line, + }); + const line = @intCast(u28, decl.src_line + func.lbrace_line); + var data: [4]u8 = undefined; + leb128.writeUnsignedFixed(4, &data, line); + + switch (self.tag) { + .elf => { + const elf_file = file.cast(File.Elf).?; + const shdr = elf_file.sections.items[elf_file.debug_line_section_index.?]; + const file_pos = shdr.sh_offset + decl.fn_link.elf.off + self.getRelocDbgLineOff(); + try elf_file.base.file.?.pwriteAll(&data, file_pos); + }, + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = macho_file.d_sym.?; + const dwarf_seg = d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment; + const sect = dwarf_seg.sections.items[d_sym.debug_line_section_index.?]; + const file_pos = sect.offset + decl.fn_link.macho.off + self.getRelocDbgLineOff(); + try d_sym.file.pwriteAll(&data, file_pos); + }, + else => unreachable, + } +} + +pub fn freeAtom(self: *Dwarf, atom: *DebugInfoAtom) void { + if (self.dbg_info_decl_first == atom) { + self.dbg_info_decl_first = atom.next; + } + if (self.dbg_info_decl_last == atom) { + // TODO shrink the .debug_info section size here + self.dbg_info_decl_last = atom.prev; + } + + if (atom.prev) |prev| { + prev.next = atom.next; + + // TODO the free list logic like we do for text blocks above + } else { + atom.prev = null; + } + + if (atom.next) |next| { + next.prev = atom.prev; + } else { + atom.next = null; + } +} + +pub fn freeDecl(self: *Dwarf, decl: *Module.Decl) void { + // TODO make this logic match freeTextBlock. Maybe abstract the logic out since the same thing + // is desired for both. + const gpa = self.allocator; + const fn_link = switch (self.tag) { + .elf => &decl.fn_link.elf, + .macho => &decl.fn_link.macho, + else => unreachable, + }; + _ = self.dbg_line_fn_free_list.remove(fn_link); + + if (fn_link.prev) |prev| { + self.dbg_line_fn_free_list.put(gpa, prev, {}) catch {}; + prev.next = fn_link.next; + if (fn_link.next) |next| { + next.prev = prev; + } else { + self.dbg_line_fn_last = prev; + } + } else if (fn_link.next) |next| { + self.dbg_line_fn_first = next; + next.prev = null; + } + if (self.dbg_line_fn_first == fn_link) { + self.dbg_line_fn_first = fn_link.next; + } + if (self.dbg_line_fn_last == fn_link) { + self.dbg_line_fn_last = fn_link.prev; + } +} + +/// Asserts the type has codegen bits. +fn addDbgInfoType( + self: *Dwarf, + arena: Allocator, + ty: Type, + dbg_info_buffer: *std.ArrayList(u8), + dbg_info_type_relocs: *File.DbgInfoTypeRelocsTable, +) error{OutOfMemory}!void { + const target = self.target; + var relocs = std.ArrayList(struct { ty: Type, reloc: u32 }).init(arena); + + switch (ty.zigTypeTag()) { + .NoReturn => unreachable, + .Void => { + try dbg_info_buffer.append(abbrev_pad1); + }, + .Bool => { + try dbg_info_buffer.appendSlice(&[_]u8{ + abbrev_base_type, + DW.ATE.boolean, // DW.AT.encoding , DW.FORM.data1 + 1, // DW.AT.byte_size, DW.FORM.data1 + 'b', 'o', 'o', 'l', 0, // DW.AT.name, DW.FORM.string + }); + }, + .Int => { + const info = ty.intInfo(target); + try dbg_info_buffer.ensureUnusedCapacity(12); + dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); + // DW.AT.encoding, DW.FORM.data1 + dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) { + .signed => DW.ATE.signed, + .unsigned => DW.ATE.unsigned, + }); + // DW.AT.byte_size, DW.FORM.data1 + dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target))); + // DW.AT.name, DW.FORM.string + try dbg_info_buffer.writer().print("{}\x00", .{ty}); + }, + .Optional => { + if (ty.isPtrLikeOptional()) { + try dbg_info_buffer.ensureUnusedCapacity(12); + dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); + // DW.AT.encoding, DW.FORM.data1 + dbg_info_buffer.appendAssumeCapacity(DW.ATE.address); + // DW.AT.byte_size, DW.FORM.data1 + dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target))); + // DW.AT.name, DW.FORM.string + try dbg_info_buffer.writer().print("{}\x00", .{ty}); + } else { + // Non-pointer optionals are structs: struct { .maybe = *, .val = * } + var buf = try arena.create(Type.Payload.ElemType); + const payload_ty = ty.optionalChild(buf); + // DW.AT.structure_type + try dbg_info_buffer.append(abbrev_struct_type); + // DW.AT.byte_size, DW.FORM.sdata + const abi_size = ty.abiSize(target); + try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size); + // DW.AT.name, DW.FORM.string + try dbg_info_buffer.writer().print("{}\x00", .{ty}); + // DW.AT.member + try dbg_info_buffer.ensureUnusedCapacity(7); + dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); + // DW.AT.name, DW.FORM.string + dbg_info_buffer.appendSliceAssumeCapacity("maybe"); + dbg_info_buffer.appendAssumeCapacity(0); + // DW.AT.type, DW.FORM.ref4 + var index = dbg_info_buffer.items.len; + try dbg_info_buffer.resize(index + 4); + try relocs.append(.{ .ty = Type.bool, .reloc = @intCast(u32, index) }); + // DW.AT.data_member_location, DW.FORM.sdata + try dbg_info_buffer.ensureUnusedCapacity(6); + dbg_info_buffer.appendAssumeCapacity(0); + // DW.AT.member + dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); + // DW.AT.name, DW.FORM.string + dbg_info_buffer.appendSliceAssumeCapacity("val"); + dbg_info_buffer.appendAssumeCapacity(0); + // DW.AT.type, DW.FORM.ref4 + index = dbg_info_buffer.items.len; + try dbg_info_buffer.resize(index + 4); + try relocs.append(.{ .ty = payload_ty, .reloc = @intCast(u32, index) }); + // DW.AT.data_member_location, DW.FORM.sdata + const offset = abi_size - payload_ty.abiSize(target); + try leb128.writeULEB128(dbg_info_buffer.writer(), offset); + // DW.AT.structure_type delimit children + try dbg_info_buffer.append(0); + } + }, + .Pointer => { + if (ty.isSlice()) { + // Slices are structs: struct { .ptr = *, .len = N } + // DW.AT.structure_type + try dbg_info_buffer.ensureUnusedCapacity(2); + dbg_info_buffer.appendAssumeCapacity(abbrev_struct_type); + // DW.AT.byte_size, DW.FORM.sdata + dbg_info_buffer.appendAssumeCapacity(@sizeOf(usize) * 2); + // DW.AT.name, DW.FORM.string + try dbg_info_buffer.writer().print("{}\x00", .{ty}); + // DW.AT.member + try dbg_info_buffer.ensureUnusedCapacity(5); + dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); + // DW.AT.name, DW.FORM.string + dbg_info_buffer.appendSliceAssumeCapacity("ptr"); + dbg_info_buffer.appendAssumeCapacity(0); + // DW.AT.type, DW.FORM.ref4 + var index = dbg_info_buffer.items.len; + try dbg_info_buffer.resize(index + 4); + var buf = try arena.create(Type.SlicePtrFieldTypeBuffer); + const ptr_ty = ty.slicePtrFieldType(buf); + try relocs.append(.{ .ty = ptr_ty, .reloc = @intCast(u32, index) }); + // DW.AT.data_member_location, DW.FORM.sdata + try dbg_info_buffer.ensureUnusedCapacity(6); + dbg_info_buffer.appendAssumeCapacity(0); + // DW.AT.member + dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); + // DW.AT.name, DW.FORM.string + dbg_info_buffer.appendSliceAssumeCapacity("len"); + dbg_info_buffer.appendAssumeCapacity(0); + // DW.AT.type, DW.FORM.ref4 + index = dbg_info_buffer.items.len; + try dbg_info_buffer.resize(index + 4); + try relocs.append(.{ .ty = Type.initTag(.usize), .reloc = @intCast(u32, index) }); + // DW.AT.data_member_location, DW.FORM.sdata + try dbg_info_buffer.ensureUnusedCapacity(2); + dbg_info_buffer.appendAssumeCapacity(@sizeOf(usize)); + // DW.AT.structure_type delimit children + dbg_info_buffer.appendAssumeCapacity(0); + } else { + try dbg_info_buffer.ensureUnusedCapacity(5); + dbg_info_buffer.appendAssumeCapacity(abbrev_ptr_type); + // DW.AT.type, DW.FORM.ref4 + const index = dbg_info_buffer.items.len; + try dbg_info_buffer.resize(index + 4); + try relocs.append(.{ .ty = ty.childType(), .reloc = @intCast(u32, index) }); + } + }, + .Struct => blk: { + // try dbg_info_buffer.ensureUnusedCapacity(23); + // DW.AT.structure_type + try dbg_info_buffer.append(abbrev_struct_type); + // DW.AT.byte_size, DW.FORM.sdata + const abi_size = ty.abiSize(target); + try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size); + // DW.AT.name, DW.FORM.string + const struct_name = try ty.nameAlloc(arena); + try dbg_info_buffer.ensureUnusedCapacity(struct_name.len + 1); + dbg_info_buffer.appendSliceAssumeCapacity(struct_name); + dbg_info_buffer.appendAssumeCapacity(0); + + const struct_obj = ty.castTag(.@"struct").?.data; + if (struct_obj.layout == .Packed) { + log.debug("TODO implement .debug_info for packed structs", .{}); + break :blk; + } + + const fields = ty.structFields(); + for (fields.keys()) |field_name, field_index| { + const field = fields.get(field_name).?; + // DW.AT.member + try dbg_info_buffer.ensureUnusedCapacity(field_name.len + 2); + dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); + // DW.AT.name, DW.FORM.string + dbg_info_buffer.appendSliceAssumeCapacity(field_name); + dbg_info_buffer.appendAssumeCapacity(0); + // DW.AT.type, DW.FORM.ref4 + var index = dbg_info_buffer.items.len; + try dbg_info_buffer.resize(index + 4); + try relocs.append(.{ .ty = field.ty, .reloc = @intCast(u32, index) }); + // DW.AT.data_member_location, DW.FORM.sdata + const field_off = ty.structFieldOffset(field_index, target); + try leb128.writeULEB128(dbg_info_buffer.writer(), field_off); + } + + // DW.AT.structure_type delimit children + try dbg_info_buffer.append(0); + }, + else => { + log.debug("TODO implement .debug_info for type '{}'", .{ty}); + try dbg_info_buffer.append(abbrev_pad1); + }, + } + + for (relocs.items) |rel| { + const gop = try dbg_info_type_relocs.getOrPut(self.allocator, rel.ty); + if (!gop.found_existing) { + gop.value_ptr.* = .{ + .off = undefined, + .relocs = .{}, + }; + } + try gop.value_ptr.relocs.append(self.allocator, rel.reloc); + } +} + +pub fn writeDbgAbbrev(self: *Dwarf, file: *File) !void { + // These are LEB encoded but since the values are all less than 127 + // we can simply append these bytes. + const abbrev_buf = [_]u8{ + abbrev_compile_unit, DW.TAG.compile_unit, DW.CHILDREN.yes, // header + DW.AT.stmt_list, DW.FORM.sec_offset, DW.AT.low_pc, + DW.FORM.addr, DW.AT.high_pc, DW.FORM.addr, + DW.AT.name, DW.FORM.strp, DW.AT.comp_dir, + DW.FORM.strp, DW.AT.producer, DW.FORM.strp, + DW.AT.language, DW.FORM.data2, 0, + 0, // table sentinel + abbrev_subprogram, + DW.TAG.subprogram, + DW.CHILDREN.yes, // header + DW.AT.low_pc, + DW.FORM.addr, + DW.AT.high_pc, + DW.FORM.data4, + DW.AT.type, + DW.FORM.ref4, + DW.AT.name, + DW.FORM.string, + 0, 0, // table sentinel + abbrev_subprogram_retvoid, + DW.TAG.subprogram, DW.CHILDREN.yes, // header + DW.AT.low_pc, DW.FORM.addr, + DW.AT.high_pc, DW.FORM.data4, + DW.AT.name, DW.FORM.string, + 0, + 0, // table sentinel + abbrev_base_type, + DW.TAG.base_type, + DW.CHILDREN.no, // header + DW.AT.encoding, + DW.FORM.data1, + DW.AT.byte_size, + DW.FORM.data1, + DW.AT.name, + DW.FORM.string, + 0, + 0, // table sentinel + abbrev_ptr_type, + DW.TAG.pointer_type, + DW.CHILDREN.no, // header + DW.AT.type, + DW.FORM.ref4, + 0, + 0, // table sentinel + abbrev_struct_type, + DW.TAG.structure_type, + DW.CHILDREN.yes, // header + DW.AT.byte_size, + DW.FORM.sdata, + DW.AT.name, + DW.FORM.string, + 0, + 0, // table sentinel + abbrev_struct_member, + DW.TAG.member, + DW.CHILDREN.no, // header + DW.AT.name, + DW.FORM.string, + DW.AT.type, + DW.FORM.ref4, + DW.AT.data_member_location, + DW.FORM.sdata, + 0, + 0, // table sentinel + abbrev_pad1, + DW.TAG.unspecified_type, + DW.CHILDREN.no, // header + 0, + 0, // table sentinel + abbrev_parameter, + DW.TAG.formal_parameter, DW.CHILDREN.no, // header + DW.AT.location, DW.FORM.exprloc, + DW.AT.type, DW.FORM.ref4, + DW.AT.name, DW.FORM.string, + 0, + 0, // table sentinel + 0, + 0, + 0, // section sentinel + }; + const abbrev_offset = 0; + self.abbrev_table_offset = abbrev_offset; + + const needed_size = abbrev_buf.len; + switch (self.tag) { + .elf => { + const elf_file = file.cast(File.Elf).?; + const debug_abbrev_sect = &elf_file.sections.items[elf_file.debug_abbrev_section_index.?]; + const allocated_size = elf_file.allocatedSize(debug_abbrev_sect.sh_offset); + if (needed_size > allocated_size) { + debug_abbrev_sect.sh_size = 0; // free the space + debug_abbrev_sect.sh_offset = elf_file.findFreeSpace(needed_size, 1); + } + debug_abbrev_sect.sh_size = needed_size; + log.debug(".debug_abbrev start=0x{x} end=0x{x}", .{ + debug_abbrev_sect.sh_offset, + debug_abbrev_sect.sh_offset + needed_size, + }); + + const file_pos = debug_abbrev_sect.sh_offset + abbrev_offset; + try elf_file.base.file.?.pwriteAll(&abbrev_buf, file_pos); + }, + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = &macho_file.d_sym.?; + const dwarf_segment = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment; + const debug_abbrev_sect = &dwarf_segment.sections.items[d_sym.debug_abbrev_section_index.?]; + const allocated_size = d_sym.allocatedSize(debug_abbrev_sect.offset); + if (needed_size > allocated_size) { + debug_abbrev_sect.size = 0; // free the space + const offset = d_sym.findFreeSpace(needed_size, 1); + debug_abbrev_sect.offset = @intCast(u32, offset); + debug_abbrev_sect.addr = dwarf_segment.inner.vmaddr + offset - dwarf_segment.inner.fileoff; + } + debug_abbrev_sect.size = needed_size; + log.debug("__debug_abbrev start=0x{x} end=0x{x}", .{ + debug_abbrev_sect.offset, + debug_abbrev_sect.offset + needed_size, + }); + const file_pos = debug_abbrev_sect.offset + abbrev_offset; + try d_sym.file.pwriteAll(&abbrev_buf, file_pos); + }, + else => unreachable, + } +} + +fn dbgInfoHeaderBytes(self: *Dwarf) usize { + _ = self; + return 120; +} + +pub fn writeDbgInfoHeader(self: *Dwarf, file: *File, module: *Module, low_pc: u64, high_pc: u64) !void { + // If this value is null it means there is an error in the module; + // leave debug_info_header_dirty=true. + const first_dbg_info_off = self.getDebugInfoOff() orelse return; + + // We have a function to compute the upper bound size, because it's needed + // for determining where to put the offset of the first `LinkBlock`. + const needed_bytes = self.dbgInfoHeaderBytes(); + var di_buf = try std.ArrayList(u8).initCapacity(self.allocator, needed_bytes); + defer di_buf.deinit(); + + const target_endian = self.target.cpu.arch.endian(); + const init_len_size: usize = if (self.tag == .macho) + 4 + else switch (self.ptr_width) { + .p32 => @as(usize, 4), + .p64 => 12, + }; + + // initial length - length of the .debug_info contribution for this compilation unit, + // not including the initial length itself. + // We have to come back and write it later after we know the size. + const after_init_len = di_buf.items.len + init_len_size; + // +1 for the final 0 that ends the compilation unit children. + const dbg_info_end = self.getDebugInfoEnd().? + 1; + const init_len = dbg_info_end - after_init_len; + if (self.tag == .macho) { + mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len)); + } else switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len), target_endian); + }, + .p64 => { + di_buf.appendNTimesAssumeCapacity(0xff, 4); + mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), init_len, target_endian); + }, + } + mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // DWARF version + const abbrev_offset = self.abbrev_table_offset.?; + if (self.tag == .macho) { + mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, abbrev_offset)); + di_buf.appendAssumeCapacity(8); // address size + } else switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, abbrev_offset), target_endian); + di_buf.appendAssumeCapacity(4); // address size + }, + .p64 => { + mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), abbrev_offset, target_endian); + di_buf.appendAssumeCapacity(8); // address size + }, + } + // Write the form for the compile unit, which must match the abbrev table above. + const name_strp = try self.makeString(module.root_pkg.root_src_path); + const comp_dir_strp = try self.makeString(module.root_pkg.root_src_directory.path orelse "."); + const producer_strp = try self.makeString(link.producer_string); + + di_buf.appendAssumeCapacity(abbrev_compile_unit); + if (self.tag == .macho) { + mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); // DW.AT.stmt_list, DW.FORM.sec_offset + mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), low_pc); + mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), high_pc); + mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, name_strp)); + mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, comp_dir_strp)); + mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, producer_strp)); + } else { + self.writeAddrAssumeCapacity(&di_buf, 0); // DW.AT.stmt_list, DW.FORM.sec_offset + self.writeAddrAssumeCapacity(&di_buf, low_pc); + self.writeAddrAssumeCapacity(&di_buf, high_pc); + self.writeAddrAssumeCapacity(&di_buf, name_strp); + self.writeAddrAssumeCapacity(&di_buf, comp_dir_strp); + self.writeAddrAssumeCapacity(&di_buf, producer_strp); + } + // We are still waiting on dwarf-std.org to assign DW_LANG_Zig a number: + // http://dwarfstd.org/ShowIssue.php?issue=171115.1 + // Until then we say it is C99. + mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), DW.LANG.C99, target_endian); + + if (di_buf.items.len > first_dbg_info_off) { + // Move the first N decls to the end to make more padding for the header. + @panic("TODO: handle .debug_info header exceeding its padding"); + } + const jmp_amt = first_dbg_info_off - di_buf.items.len; + switch (self.tag) { + .elf => { + const elf_file = file.cast(File.Elf).?; + const debug_info_sect = elf_file.sections.items[elf_file.debug_info_section_index.?]; + const file_pos = debug_info_sect.sh_offset; + try pwriteDbgInfoNops(elf_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt, false); + }, + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = &macho_file.d_sym.?; + const dwarf_seg = d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment; + const debug_info_sect = dwarf_seg.sections.items[d_sym.debug_info_section_index.?]; + const file_pos = debug_info_sect.offset; + try pwriteDbgInfoNops(d_sym.file, file_pos, 0, di_buf.items, jmp_amt, false); + }, + else => unreachable, + } +} + +fn writeAddrAssumeCapacity(self: *Dwarf, buf: *std.ArrayList(u8), addr: u64) void { + const target_endian = self.target.cpu.arch.endian(); + switch (self.ptr_width) { + .p32 => mem.writeInt(u32, buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, addr), target_endian), + .p64 => mem.writeInt(u64, buf.addManyAsArrayAssumeCapacity(8), addr, target_endian), + } +} + +/// Writes to the file a buffer, prefixed and suffixed by the specified number of +/// bytes of NOPs. Asserts each padding size is at least `min_nop_size` and total padding bytes +/// are less than 1044480 bytes (if this limit is ever reached, this function can be +/// improved to make more than one pwritev call, or the limit can be raised by a fixed +/// amount by increasing the length of `vecs`). +fn pwriteDbgLineNops( + file: fs.File, + offset: u64, + prev_padding_size: usize, + buf: []const u8, + next_padding_size: usize, +) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const page_of_nops = [1]u8{DW.LNS.negate_stmt} ** 4096; + const three_byte_nop = [3]u8{ DW.LNS.advance_pc, 0b1000_0000, 0 }; + var vecs: [512]std.os.iovec_const = undefined; + var vec_index: usize = 0; + { + var padding_left = prev_padding_size; + if (padding_left % 2 != 0) { + vecs[vec_index] = .{ + .iov_base = &three_byte_nop, + .iov_len = three_byte_nop.len, + }; + vec_index += 1; + padding_left -= three_byte_nop.len; + } + while (padding_left > page_of_nops.len) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = page_of_nops.len, + }; + vec_index += 1; + padding_left -= page_of_nops.len; + } + if (padding_left > 0) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = padding_left, + }; + vec_index += 1; + } + } + + vecs[vec_index] = .{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }; + vec_index += 1; + + { + var padding_left = next_padding_size; + if (padding_left % 2 != 0) { + vecs[vec_index] = .{ + .iov_base = &three_byte_nop, + .iov_len = three_byte_nop.len, + }; + vec_index += 1; + padding_left -= three_byte_nop.len; + } + while (padding_left > page_of_nops.len) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = page_of_nops.len, + }; + vec_index += 1; + padding_left -= page_of_nops.len; + } + if (padding_left > 0) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = padding_left, + }; + vec_index += 1; + } + } + try file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); +} + +/// Writes to the file a buffer, prefixed and suffixed by the specified number of +/// bytes of padding. +fn pwriteDbgInfoNops( + file: fs.File, + offset: u64, + prev_padding_size: usize, + buf: []const u8, + next_padding_size: usize, + trailing_zero: bool, +) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const page_of_nops = [1]u8{abbrev_pad1} ** 4096; + var vecs: [32]std.os.iovec_const = undefined; + var vec_index: usize = 0; + { + var padding_left = prev_padding_size; + while (padding_left > page_of_nops.len) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = page_of_nops.len, + }; + vec_index += 1; + padding_left -= page_of_nops.len; + } + if (padding_left > 0) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = padding_left, + }; + vec_index += 1; + } + } + + vecs[vec_index] = .{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }; + vec_index += 1; + + { + var padding_left = next_padding_size; + while (padding_left > page_of_nops.len) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = page_of_nops.len, + }; + vec_index += 1; + padding_left -= page_of_nops.len; + } + if (padding_left > 0) { + vecs[vec_index] = .{ + .iov_base = &page_of_nops, + .iov_len = padding_left, + }; + vec_index += 1; + } + } + + if (trailing_zero) { + var zbuf = [1]u8{0}; + vecs[vec_index] = .{ + .iov_base = &zbuf, + .iov_len = zbuf.len, + }; + vec_index += 1; + } + + try file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); +} + +pub fn writeDbgAranges(self: *Dwarf, file: *File, addr: u64, size: u64) !void { + const target_endian = self.target.cpu.arch.endian(); + const init_len_size: usize = if (self.tag == .macho) + 4 + else switch (self.ptr_width) { + .p32 => @as(usize, 4), + .p64 => 12, + }; + const ptr_width_bytes: u8 = self.ptrWidthBytes(); + + // Enough for all the data without resizing. When support for more compilation units + // is added, the size of this section will become more variable. + var di_buf = try std.ArrayList(u8).initCapacity(self.allocator, 100); + defer di_buf.deinit(); + + // initial length - length of the .debug_aranges contribution for this compilation unit, + // not including the initial length itself. + // We have to come back and write it later after we know the size. + const init_len_index = di_buf.items.len; + di_buf.items.len += init_len_size; + const after_init_len = di_buf.items.len; + mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 2, target_endian); // version + // When more than one compilation unit is supported, this will be the offset to it. + // For now it is always at offset 0 in .debug_info. + if (self.tag == .macho) { + mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); // __debug_info offset + } else { + self.writeAddrAssumeCapacity(&di_buf, 0); // .debug_info offset + } + di_buf.appendAssumeCapacity(ptr_width_bytes); // address_size + di_buf.appendAssumeCapacity(0); // segment_selector_size + + const end_header_offset = di_buf.items.len; + const begin_entries_offset = mem.alignForward(end_header_offset, ptr_width_bytes * 2); + di_buf.appendNTimesAssumeCapacity(0, begin_entries_offset - end_header_offset); + + // Currently only one compilation unit is supported, so the address range is simply + // identical to the main program header virtual address and memory size. + self.writeAddrAssumeCapacity(&di_buf, addr); + self.writeAddrAssumeCapacity(&di_buf, size); + + // Sentinel. + self.writeAddrAssumeCapacity(&di_buf, 0); + self.writeAddrAssumeCapacity(&di_buf, 0); + + // Go back and populate the initial length. + const init_len = di_buf.items.len - after_init_len; + if (self.tag == .macho) { + mem.writeIntLittle(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len)); + } else switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len), target_endian); + }, + .p64 => { + // initial length - length of the .debug_aranges contribution for this compilation unit, + // not including the initial length itself. + di_buf.items[init_len_index..][0..4].* = [_]u8{ 0xff, 0xff, 0xff, 0xff }; + mem.writeInt(u64, di_buf.items[init_len_index + 4 ..][0..8], init_len, target_endian); + }, + } + + const needed_size = di_buf.items.len; + switch (self.tag) { + .elf => { + const elf_file = file.cast(File.Elf).?; + const debug_aranges_sect = &elf_file.sections.items[elf_file.debug_aranges_section_index.?]; + const allocated_size = elf_file.allocatedSize(debug_aranges_sect.sh_offset); + if (needed_size > allocated_size) { + debug_aranges_sect.sh_size = 0; // free the space + debug_aranges_sect.sh_offset = elf_file.findFreeSpace(needed_size, 16); + } + debug_aranges_sect.sh_size = needed_size; + log.debug(".debug_aranges start=0x{x} end=0x{x}", .{ + debug_aranges_sect.sh_offset, + debug_aranges_sect.sh_offset + needed_size, + }); + const file_pos = debug_aranges_sect.sh_offset; + try elf_file.base.file.?.pwriteAll(di_buf.items, file_pos); + }, + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = &macho_file.d_sym.?; + const dwarf_seg = &d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment; + const debug_aranges_sect = &dwarf_seg.sections.items[d_sym.debug_aranges_section_index.?]; + const allocated_size = d_sym.allocatedSize(debug_aranges_sect.offset); + if (needed_size > allocated_size) { + debug_aranges_sect.size = 0; // free the space + const new_offset = d_sym.findFreeSpace(needed_size, 16); + debug_aranges_sect.addr = dwarf_seg.inner.vmaddr + new_offset - dwarf_seg.inner.fileoff; + debug_aranges_sect.offset = @intCast(u32, new_offset); + } + debug_aranges_sect.size = needed_size; + log.debug("__debug_aranges start=0x{x} end=0x{x}", .{ + debug_aranges_sect.offset, + debug_aranges_sect.offset + needed_size, + }); + const file_pos = debug_aranges_sect.offset; + try d_sym.file.pwriteAll(di_buf.items, file_pos); + }, + else => unreachable, + } +} + +pub fn writeDbgLineHeader(self: *Dwarf, file: *File, module: *Module) !void { + const ptr_width_bytes: u8 = self.ptrWidthBytes(); + const target_endian = self.target.cpu.arch.endian(); + const init_len_size: usize = if (self.tag == .macho) + 4 + else switch (self.ptr_width) { + .p32 => @as(usize, 4), + .p64 => 12, + }; + + const dbg_line_prg_off = self.getDebugLineProgramOff() orelse return; + const dbg_line_prg_end = self.getDebugLineProgramEnd().?; + assert(dbg_line_prg_end != 0); + + // The size of this header is variable, depending on the number of directories, + // files, and padding. We have a function to compute the upper bound size, however, + // because it's needed for determining where to put the offset of the first `SrcFn`. + const needed_bytes = self.dbgLineNeededHeaderBytes(module); + var di_buf = try std.ArrayList(u8).initCapacity(self.allocator, needed_bytes); + defer di_buf.deinit(); + + // initial length - length of the .debug_line contribution for this compilation unit, + // not including the initial length itself. + const after_init_len = di_buf.items.len + init_len_size; + const init_len = dbg_line_prg_end - after_init_len; + if (self.tag == .macho) { + mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len)); + } else switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len), target_endian); + }, + .p64 => { + di_buf.appendNTimesAssumeCapacity(0xff, 4); + mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), init_len, target_endian); + }, + } + + mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // version + + // Empirically, debug info consumers do not respect this field, or otherwise + // consider it to be an error when it does not point exactly to the end of the header. + // Therefore we rely on the NOP jump at the beginning of the Line Number Program for + // padding rather than this field. + const before_header_len = di_buf.items.len; + di_buf.items.len += if (self.tag == .macho) @sizeOf(u32) else ptr_width_bytes; // We will come back and write this. + const after_header_len = di_buf.items.len; + + const opcode_base = DW.LNS.set_isa + 1; + di_buf.appendSliceAssumeCapacity(&[_]u8{ + 1, // minimum_instruction_length + 1, // maximum_operations_per_instruction + 1, // default_is_stmt + 1, // line_base (signed) + 1, // line_range + opcode_base, + + // Standard opcode lengths. The number of items here is based on `opcode_base`. + // The value is the number of LEB128 operands the instruction takes. + 0, // `DW.LNS.copy` + 1, // `DW.LNS.advance_pc` + 1, // `DW.LNS.advance_line` + 1, // `DW.LNS.set_file` + 1, // `DW.LNS.set_column` + 0, // `DW.LNS.negate_stmt` + 0, // `DW.LNS.set_basic_block` + 0, // `DW.LNS.const_add_pc` + 1, // `DW.LNS.fixed_advance_pc` + 0, // `DW.LNS.set_prologue_end` + 0, // `DW.LNS.set_epilogue_begin` + 1, // `DW.LNS.set_isa` + 0, // include_directories (none except the compilation unit cwd) + }); + // file_names[0] + di_buf.appendSliceAssumeCapacity(module.root_pkg.root_src_path); // relative path name + di_buf.appendSliceAssumeCapacity(&[_]u8{ + 0, // null byte for the relative path name + 0, // directory_index + 0, // mtime (TODO supply this) + 0, // file size bytes (TODO supply this) + 0, // file_names sentinel + }); + + const header_len = di_buf.items.len - after_header_len; + if (self.tag == .macho) { + mem.writeIntLittle(u32, di_buf.items[before_header_len..][0..4], @intCast(u32, header_len)); + } else switch (self.ptr_width) { + .p32 => { + mem.writeInt(u32, di_buf.items[before_header_len..][0..4], @intCast(u32, header_len), target_endian); + }, + .p64 => { + mem.writeInt(u64, di_buf.items[before_header_len..][0..8], header_len, target_endian); + }, + } + + // We use NOPs because consumers empirically do not respect the header length field. + if (di_buf.items.len > dbg_line_prg_off) { + // Move the first N files to the end to make more padding for the header. + @panic("TODO: handle .debug_line header exceeding its padding"); + } + const jmp_amt = dbg_line_prg_off - di_buf.items.len; + switch (self.tag) { + .elf => { + const elf_file = file.cast(File.Elf).?; + const debug_line_sect = elf_file.sections.items[elf_file.debug_line_section_index.?]; + const file_pos = debug_line_sect.sh_offset; + try pwriteDbgLineNops(elf_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt); + }, + .macho => { + const macho_file = file.cast(File.MachO).?; + const d_sym = &macho_file.d_sym.?; + const dwarf_seg = d_sym.load_commands.items[d_sym.dwarf_segment_cmd_index.?].segment; + const debug_line_sect = dwarf_seg.sections.items[d_sym.debug_line_section_index.?]; + const file_pos = debug_line_sect.offset; + try pwriteDbgLineNops(d_sym.file, file_pos, 0, di_buf.items, jmp_amt); + }, + else => unreachable, + } +} + +fn getDebugInfoOff(self: Dwarf) ?u32 { + const first = self.dbg_info_decl_first orelse return null; + return first.off; +} + +fn getDebugInfoEnd(self: Dwarf) ?u32 { + const last = self.dbg_info_decl_last orelse return null; + return last.off + last.len; +} + +fn getDebugLineProgramOff(self: Dwarf) ?u32 { + const first = self.dbg_line_fn_first orelse return null; + return first.off; +} + +fn getDebugLineProgramEnd(self: Dwarf) ?u32 { + const last = self.dbg_line_fn_last orelse return null; + return last.off + last.len; +} + +/// Always 4 or 8 depending on whether this is 32-bit or 64-bit format. +fn ptrWidthBytes(self: Dwarf) u8 { + return switch (self.ptr_width) { + .p32 => 4, + .p64 => 8, + }; +} + +fn dbgLineNeededHeaderBytes(self: Dwarf, module: *Module) u32 { + _ = self; + const directory_entry_format_count = 1; + const file_name_entry_format_count = 1; + const directory_count = 1; + const file_name_count = 1; + const root_src_dir_path_len = if (module.root_pkg.root_src_directory.path) |p| p.len else 1; // "." + return @intCast(u32, 53 + directory_entry_format_count * 2 + file_name_entry_format_count * 2 + + directory_count * 8 + file_name_count * 8 + + // These are encoded as DW.FORM.string rather than DW.FORM.strp as we would like + // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly. + root_src_dir_path_len + + module.root_pkg.root_src_path.len); +} + +/// The reloc offset for the line offset of a function from the previous function's line. +/// It's a fixed-size 4-byte ULEB128. +fn getRelocDbgLineOff(self: Dwarf) usize { + return dbg_line_vaddr_reloc_index + self.ptrWidthBytes() + 1; +} + +fn getRelocDbgFileIndex(self: Dwarf) usize { + return self.getRelocDbgLineOff() + 5; +} + +fn getRelocDbgInfoSubprogramHighPC(self: Dwarf) u32 { + return dbg_info_low_pc_reloc_index + self.ptrWidthBytes(); +} + +/// TODO Improve this to use a table. +fn makeString(self: *Dwarf, bytes: []const u8) !u32 { + try self.strtab.ensureUnusedCapacity(self.allocator, bytes.len + 1); + const result = self.strtab.items.len; + self.strtab.appendSliceAssumeCapacity(bytes); + self.strtab.appendAssumeCapacity(0); + return @intCast(u32, result); +} + +fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) { + // TODO https://github.com/ziglang/zig/issues/1284 + return std.math.add(@TypeOf(actual_size), actual_size, actual_size / ideal_factor) catch + std.math.maxInt(@TypeOf(actual_size)); +} diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 810bef2f9b..64d6df6756 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -9,11 +9,10 @@ const Allocator = std.mem.Allocator; const fs = std.fs; const elf = std.elf; const log = std.log.scoped(.link); -const DW = std.dwarf; -const leb128 = std.leb; const Module = @import("../Module.zig"); const Compilation = @import("../Compilation.zig"); +const Dwarf = @import("Dwarf.zig"); const codegen = @import("../codegen.zig"); const lldMain = @import("../main.zig").lldMain; const trace = @import("../tracy.zig").trace; @@ -37,6 +36,7 @@ const default_entry_addr = 0x8000000; pub const base_tag: File.Tag = .elf; base: File, +dwarf: ?Dwarf = null, ptr_width: PtrWidth, @@ -67,7 +67,6 @@ phdr_shdr_table: std.AutoHashMapUnmanaged(u16, u16) = .{}, entry_addr: ?u64 = null, page_size: u16, -debug_strtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, shstrtab_index: ?u16 = null, @@ -82,8 +81,6 @@ debug_str_section_index: ?u16 = null, debug_aranges_section_index: ?u16 = null, debug_line_section_index: ?u16 = null, -debug_abbrev_table_offset: ?u64 = null, - /// The same order as in the file. ELF requires global symbols to all be after the /// local symbols, they cannot be mixed. So we must buffer all the global symbols and /// write them at the end. These are only the local symbols. The length of this array @@ -168,18 +165,6 @@ atom_by_index_table: std.AutoHashMapUnmanaged(u32, *TextBlock) = .{}, /// with `Decl` `main`, and lives as long as that `Decl`. unnamed_const_atoms: UnnamedConstTable = .{}, -/// A list of `SrcFn` whose Line Number Programs have surplus capacity. -/// This is the same concept as `text_block_free_list`; see those doc comments. -dbg_line_fn_free_list: std.AutoHashMapUnmanaged(*SrcFn, void) = .{}, -dbg_line_fn_first: ?*SrcFn = null, -dbg_line_fn_last: ?*SrcFn = null, - -/// A list of `TextBlock` whose corresponding .debug_info tags have surplus capacity. -/// This is the same concept as `text_block_free_list`; see those doc comments. -dbg_info_decl_free_list: std.AutoHashMapUnmanaged(*TextBlock, void) = .{}, -dbg_info_decl_first: ?*TextBlock = null, -dbg_info_decl_last: ?*TextBlock = null, - /// A table of relocations indexed by the owning them `TextBlock`. /// Note that once we refactor `TextBlock`'s lifetime and ownership rules, /// this will be a table indexed by index into the list of Atoms. @@ -222,24 +207,14 @@ pub const TextBlock = struct { prev: ?*TextBlock, next: ?*TextBlock, - /// Previous/next linked list pointers. - /// This is the linked list node for this Decl's corresponding .debug_info tag. - dbg_info_prev: ?*TextBlock, - dbg_info_next: ?*TextBlock, - /// Offset into .debug_info pointing to the tag for this Decl. - dbg_info_off: u32, - /// Size of the .debug_info tag for this Decl, not including padding. - dbg_info_len: u32, + dbg_info_atom: Dwarf.DebugInfoAtom, pub const empty = TextBlock{ .local_sym_index = 0, .offset_table_index = undefined, .prev = null, .next = null, - .dbg_info_prev = null, - .dbg_info_next = null, - .dbg_info_off = undefined, - .dbg_info_len = undefined, + .dbg_info_atom = undefined, }; /// Returns how much room there is to grow in virtual address space. @@ -273,26 +248,6 @@ pub const Export = struct { sym_index: ?u32 = null, }; -pub const SrcFn = struct { - /// Offset from the beginning of the Debug Line Program header that contains this function. - off: u32, - /// Size of the line number program component belonging to this function, not - /// including padding. - len: u32, - - /// Points to the previous and next neighbors, based on the offset from .debug_line. - /// This can be used to find, for example, the capacity of this `SrcFn`. - prev: ?*SrcFn, - next: ?*SrcFn, - - pub const empty: SrcFn = .{ - .off = 0, - .len = 0, - .prev = null, - .next = null, - }; -}; - pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Elf { assert(options.object_format == .elf); @@ -351,6 +306,11 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf { errdefer gpa.destroy(self); const page_size: u16 = 0x1000; // TODO ppc64le requires 64KB + var dwarf: ?Dwarf = if (!options.strip and options.module != null) + Dwarf.init(gpa, .elf, options.target) + else + null; + self.* = .{ .base = .{ .tag = .elf, @@ -358,6 +318,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf { .allocator = gpa, .file = null, }, + .dwarf = dwarf, .ptr_width = ptr_width, .page_size = page_size, }; @@ -377,14 +338,11 @@ pub fn deinit(self: *Elf) void { self.sections.deinit(self.base.allocator); self.program_headers.deinit(self.base.allocator); self.shstrtab.deinit(self.base.allocator); - self.debug_strtab.deinit(self.base.allocator); self.local_symbols.deinit(self.base.allocator); self.global_symbols.deinit(self.base.allocator); self.global_symbol_free_list.deinit(self.base.allocator); self.local_symbol_free_list.deinit(self.base.allocator); self.offset_table_free_list.deinit(self.base.allocator); - self.dbg_line_fn_free_list.deinit(self.base.allocator); - self.dbg_info_decl_free_list.deinit(self.base.allocator); self.offset_table.deinit(self.base.allocator); self.phdr_shdr_table.deinit(self.base.allocator); self.decls.deinit(self.base.allocator); @@ -420,6 +378,10 @@ pub fn deinit(self: *Elf) void { } self.atom_by_index_table.deinit(self.base.allocator); + + if (self.dwarf) |*dw| { + dw.deinit(); + } } pub fn getDeclVAddr(self: *Elf, decl: *const Module.Decl, reloc_info: File.RelocInfo) !u64 { @@ -443,14 +405,6 @@ pub fn getDeclVAddr(self: *Elf, decl: *const Module.Decl, reloc_info: File.Reloc return vaddr; } -fn getDebugLineProgramOff(self: Elf) u32 { - return self.dbg_line_fn_first.?.off; -} - -fn getDebugLineProgramEnd(self: Elf) u32 { - return self.dbg_line_fn_last.?.off + self.dbg_line_fn_last.?.len; -} - /// Returns end pos of collision, if any. fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { const small_ptr = self.ptr_width == .p32; @@ -497,7 +451,7 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { return null; } -fn allocatedSize(self: *Elf, start: u64) u64 { +pub fn allocatedSize(self: *Elf, start: u64) u64 { if (start == 0) return 0; var min_pos: u64 = std.math.maxInt(u64); @@ -518,7 +472,7 @@ fn allocatedSize(self: *Elf, start: u64) u64 { return min_pos - start; } -fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u16) u64 { +pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u16) u64 { var start: u64 = 0; while (self.detectAllocCollision(start, object_size)) |item_end| { start = mem.alignForwardGeneric(u64, item_end, min_alignment); @@ -535,15 +489,6 @@ fn makeString(self: *Elf, bytes: []const u8) !u32 { return @intCast(u32, result); } -/// TODO Improve this to use a table. -fn makeDebugString(self: *Elf, bytes: []const u8) !u32 { - try self.debug_strtab.ensureUnusedCapacity(self.base.allocator, bytes.len + 1); - const result = self.debug_strtab.items.len; - self.debug_strtab.appendSliceAssumeCapacity(bytes); - self.debug_strtab.appendAssumeCapacity(0); - return @intCast(u32, result); -} - fn getString(self: *Elf, str_off: u32) []const u8 { assert(str_off < self.shstrtab.items.len); return mem.sliceTo(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off), 0); @@ -806,14 +751,14 @@ pub fn populateMissingMetadata(self: *Elf) !void { if (self.debug_str_section_index == null) { self.debug_str_section_index = @intCast(u16, self.sections.items.len); - assert(self.debug_strtab.items.len == 0); + assert(self.dwarf.?.strtab.items.len == 0); try self.sections.append(self.base.allocator, .{ .sh_name = try self.makeString(".debug_str"), .sh_type = elf.SHT_PROGBITS, .sh_flags = elf.SHF_MERGE | elf.SHF_STRINGS, .sh_addr = 0, .sh_offset = 0, - .sh_size = self.debug_strtab.items.len, + .sh_size = 0, .sh_link = 0, .sh_info = 0, .sh_addralign = 1, @@ -977,16 +922,6 @@ pub fn populateMissingMetadata(self: *Elf) !void { } } -pub const abbrev_compile_unit = 1; -pub const abbrev_subprogram = 2; -pub const abbrev_subprogram_retvoid = 3; -pub const abbrev_base_type = 4; -pub const abbrev_ptr_type = 5; -pub const abbrev_struct_type = 6; -pub const abbrev_struct_member = 7; -pub const abbrev_pad1 = 8; -pub const abbrev_parameter = 9; - pub fn flush(self: *Elf, comp: *Compilation) !void { if (self.base.options.emit == null) { if (build_options.have_llvm) { @@ -1022,11 +957,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { const target_endian = self.base.options.target.cpu.arch.endian(); const foreign_endian = target_endian != builtin.cpu.arch.endian(); - const ptr_width_bytes: u8 = self.ptrWidthBytes(); - const init_len_size: usize = switch (self.ptr_width) { - .p32 => 4, - .p64 => 12, - }; { var it = self.relocs.iterator(); @@ -1068,349 +998,38 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { try self.writeAllGlobalSymbols(); if (self.debug_abbrev_section_dirty) { - const debug_abbrev_sect = &self.sections.items[self.debug_abbrev_section_index.?]; - - // These are LEB encoded but since the values are all less than 127 - // we can simply append these bytes. - const abbrev_buf = [_]u8{ - abbrev_compile_unit, DW.TAG.compile_unit, DW.CHILDREN.yes, // header - DW.AT.stmt_list, DW.FORM.sec_offset, DW.AT.low_pc, - DW.FORM.addr, DW.AT.high_pc, DW.FORM.addr, - DW.AT.name, DW.FORM.strp, DW.AT.comp_dir, - DW.FORM.strp, DW.AT.producer, DW.FORM.strp, - DW.AT.language, DW.FORM.data2, 0, - 0, // table sentinel - abbrev_subprogram, - DW.TAG.subprogram, - DW.CHILDREN.yes, // header - DW.AT.low_pc, - DW.FORM.addr, - DW.AT.high_pc, - DW.FORM.data4, - DW.AT.type, - DW.FORM.ref4, - DW.AT.name, - DW.FORM.string, - 0, 0, // table sentinel - abbrev_subprogram_retvoid, - DW.TAG.subprogram, DW.CHILDREN.yes, // header - DW.AT.low_pc, DW.FORM.addr, - DW.AT.high_pc, DW.FORM.data4, - DW.AT.name, DW.FORM.string, - 0, - 0, // table sentinel - abbrev_base_type, - DW.TAG.base_type, - DW.CHILDREN.no, // header - DW.AT.encoding, - DW.FORM.data1, - DW.AT.byte_size, - DW.FORM.data1, - DW.AT.name, - DW.FORM.string, - 0, - 0, // table sentinel - abbrev_ptr_type, - DW.TAG.pointer_type, - DW.CHILDREN.no, // header - DW.AT.type, - DW.FORM.ref4, - 0, - 0, // table sentinel - abbrev_struct_type, - DW.TAG.structure_type, - DW.CHILDREN.yes, // header - DW.AT.byte_size, - DW.FORM.sdata, - DW.AT.name, - DW.FORM.string, - 0, - 0, // table sentinel - abbrev_struct_member, - DW.TAG.member, - DW.CHILDREN.no, // header - DW.AT.name, - DW.FORM.string, - DW.AT.type, - DW.FORM.ref4, - DW.AT.data_member_location, - DW.FORM.sdata, - 0, - 0, // table sentinel - abbrev_pad1, - DW.TAG.unspecified_type, - DW.CHILDREN.no, // header - 0, - 0, // table sentinel - abbrev_parameter, - DW.TAG.formal_parameter, DW.CHILDREN.no, // header - DW.AT.location, DW.FORM.exprloc, - DW.AT.type, DW.FORM.ref4, - DW.AT.name, DW.FORM.string, - 0, - 0, // table sentinel - 0, - 0, - 0, // section sentinel - }; - - const needed_size = abbrev_buf.len; - const allocated_size = self.allocatedSize(debug_abbrev_sect.sh_offset); - if (needed_size > allocated_size) { - debug_abbrev_sect.sh_size = 0; // free the space - debug_abbrev_sect.sh_offset = self.findFreeSpace(needed_size, 1); - } - debug_abbrev_sect.sh_size = needed_size; - log.debug(".debug_abbrev start=0x{x} end=0x{x}", .{ - debug_abbrev_sect.sh_offset, - debug_abbrev_sect.sh_offset + needed_size, - }); - - const abbrev_offset = 0; - self.debug_abbrev_table_offset = abbrev_offset; - try self.base.file.?.pwriteAll(&abbrev_buf, debug_abbrev_sect.sh_offset + abbrev_offset); + try self.dwarf.?.writeDbgAbbrev(&self.base); if (!self.shdr_table_dirty) { // Then it won't get written with the others and we need to do it. try self.writeSectHeader(self.debug_abbrev_section_index.?); } - self.debug_abbrev_section_dirty = false; } - if (self.debug_info_header_dirty) debug_info: { - // If this value is null it means there is an error in the module; - // leave debug_info_header_dirty=true. - const first_dbg_info_decl = self.dbg_info_decl_first orelse break :debug_info; - const last_dbg_info_decl = self.dbg_info_decl_last.?; - const debug_info_sect = &self.sections.items[self.debug_info_section_index.?]; - - // We have a function to compute the upper bound size, because it's needed - // for determining where to put the offset of the first `LinkBlock`. - const needed_bytes = self.dbgInfoNeededHeaderBytes(); - var di_buf = try std.ArrayList(u8).initCapacity(self.base.allocator, needed_bytes); - defer di_buf.deinit(); - - // initial length - length of the .debug_info contribution for this compilation unit, - // not including the initial length itself. - // We have to come back and write it later after we know the size. - const after_init_len = di_buf.items.len + init_len_size; - // +1 for the final 0 that ends the compilation unit children. - const dbg_info_end = last_dbg_info_decl.dbg_info_off + last_dbg_info_decl.dbg_info_len + 1; - const init_len = dbg_info_end - after_init_len; - switch (self.ptr_width) { - .p32 => { - mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len), target_endian); - }, - .p64 => { - di_buf.appendNTimesAssumeCapacity(0xff, 4); - mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), init_len, target_endian); - }, - } - mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // DWARF version - const abbrev_offset = self.debug_abbrev_table_offset.?; - switch (self.ptr_width) { - .p32 => { - mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, abbrev_offset), target_endian); - di_buf.appendAssumeCapacity(4); // address size - }, - .p64 => { - mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), abbrev_offset, target_endian); - di_buf.appendAssumeCapacity(8); // address size - }, - } - // Write the form for the compile unit, which must match the abbrev table above. - const name_strp = try self.makeDebugString(module.root_pkg.root_src_path); - const comp_dir_strp = try self.makeDebugString(module.root_pkg.root_src_directory.path orelse "."); - const producer_strp = try self.makeDebugString(link.producer_string); + if (self.debug_info_header_dirty) { // Currently only one compilation unit is supported, so the address range is simply // identical to the main program header virtual address and memory size. const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?]; const low_pc = text_phdr.p_vaddr; const high_pc = text_phdr.p_vaddr + text_phdr.p_memsz; - - di_buf.appendAssumeCapacity(abbrev_compile_unit); - self.writeDwarfAddrAssumeCapacity(&di_buf, 0); // DW.AT.stmt_list, DW.FORM.sec_offset - self.writeDwarfAddrAssumeCapacity(&di_buf, low_pc); - self.writeDwarfAddrAssumeCapacity(&di_buf, high_pc); - self.writeDwarfAddrAssumeCapacity(&di_buf, name_strp); - self.writeDwarfAddrAssumeCapacity(&di_buf, comp_dir_strp); - self.writeDwarfAddrAssumeCapacity(&di_buf, producer_strp); - // We are still waiting on dwarf-std.org to assign DW_LANG_Zig a number: - // http://dwarfstd.org/ShowIssue.php?issue=171115.1 - // Until then we say it is C99. - mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), DW.LANG.C99, target_endian); - - if (di_buf.items.len > first_dbg_info_decl.dbg_info_off) { - // Move the first N decls to the end to make more padding for the header. - @panic("TODO: handle .debug_info header exceeding its padding"); - } - const jmp_amt = first_dbg_info_decl.dbg_info_off - di_buf.items.len; - try self.pwriteDbgInfoNops(0, di_buf.items, jmp_amt, false, debug_info_sect.sh_offset); + try self.dwarf.?.writeDbgInfoHeader(&self.base, module, low_pc, high_pc); self.debug_info_header_dirty = false; } if (self.debug_aranges_section_dirty) { - const debug_aranges_sect = &self.sections.items[self.debug_aranges_section_index.?]; - - // Enough for all the data without resizing. When support for more compilation units - // is added, the size of this section will become more variable. - var di_buf = try std.ArrayList(u8).initCapacity(self.base.allocator, 100); - defer di_buf.deinit(); - - // initial length - length of the .debug_aranges contribution for this compilation unit, - // not including the initial length itself. - // We have to come back and write it later after we know the size. - const init_len_index = di_buf.items.len; - di_buf.items.len += init_len_size; - const after_init_len = di_buf.items.len; - mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 2, target_endian); // version - // When more than one compilation unit is supported, this will be the offset to it. - // For now it is always at offset 0 in .debug_info. - self.writeDwarfAddrAssumeCapacity(&di_buf, 0); // .debug_info offset - di_buf.appendAssumeCapacity(ptr_width_bytes); // address_size - di_buf.appendAssumeCapacity(0); // segment_selector_size - - const end_header_offset = di_buf.items.len; - const begin_entries_offset = mem.alignForward(end_header_offset, ptr_width_bytes * 2); - di_buf.appendNTimesAssumeCapacity(0, begin_entries_offset - end_header_offset); - // Currently only one compilation unit is supported, so the address range is simply // identical to the main program header virtual address and memory size. const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?]; - self.writeDwarfAddrAssumeCapacity(&di_buf, text_phdr.p_vaddr); - self.writeDwarfAddrAssumeCapacity(&di_buf, text_phdr.p_memsz); - - // Sentinel. - self.writeDwarfAddrAssumeCapacity(&di_buf, 0); - self.writeDwarfAddrAssumeCapacity(&di_buf, 0); - - // Go back and populate the initial length. - const init_len = di_buf.items.len - after_init_len; - switch (self.ptr_width) { - .p32 => { - mem.writeInt(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len), target_endian); - }, - .p64 => { - // initial length - length of the .debug_aranges contribution for this compilation unit, - // not including the initial length itself. - di_buf.items[init_len_index..][0..4].* = [_]u8{ 0xff, 0xff, 0xff, 0xff }; - mem.writeInt(u64, di_buf.items[init_len_index + 4 ..][0..8], init_len, target_endian); - }, - } - - const needed_size = di_buf.items.len; - const allocated_size = self.allocatedSize(debug_aranges_sect.sh_offset); - if (needed_size > allocated_size) { - debug_aranges_sect.sh_size = 0; // free the space - debug_aranges_sect.sh_offset = self.findFreeSpace(needed_size, 16); - } - debug_aranges_sect.sh_size = needed_size; - log.debug(".debug_aranges start=0x{x} end=0x{x}", .{ - debug_aranges_sect.sh_offset, - debug_aranges_sect.sh_offset + needed_size, - }); - - try self.base.file.?.pwriteAll(di_buf.items, debug_aranges_sect.sh_offset); + try self.dwarf.?.writeDbgAranges(&self.base, text_phdr.p_vaddr, text_phdr.p_memsz); if (!self.shdr_table_dirty) { // Then it won't get written with the others and we need to do it. try self.writeSectHeader(self.debug_aranges_section_index.?); } - self.debug_aranges_section_dirty = false; } - if (self.debug_line_header_dirty) debug_line: { - if (self.dbg_line_fn_first == null) { - break :debug_line; // Error in module; leave debug_line_header_dirty=true. - } - const dbg_line_prg_off = self.getDebugLineProgramOff(); - const dbg_line_prg_end = self.getDebugLineProgramEnd(); - assert(dbg_line_prg_end != 0); - - const debug_line_sect = &self.sections.items[self.debug_line_section_index.?]; - - // The size of this header is variable, depending on the number of directories, - // files, and padding. We have a function to compute the upper bound size, however, - // because it's needed for determining where to put the offset of the first `SrcFn`. - const needed_bytes = self.dbgLineNeededHeaderBytes(); - var di_buf = try std.ArrayList(u8).initCapacity(self.base.allocator, needed_bytes); - defer di_buf.deinit(); - - // initial length - length of the .debug_line contribution for this compilation unit, - // not including the initial length itself. - const after_init_len = di_buf.items.len + init_len_size; - const init_len = dbg_line_prg_end - after_init_len; - switch (self.ptr_width) { - .p32 => { - mem.writeInt(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len), target_endian); - }, - .p64 => { - di_buf.appendNTimesAssumeCapacity(0xff, 4); - mem.writeInt(u64, di_buf.addManyAsArrayAssumeCapacity(8), init_len, target_endian); - }, - } - - mem.writeInt(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4, target_endian); // version - - // Empirically, debug info consumers do not respect this field, or otherwise - // consider it to be an error when it does not point exactly to the end of the header. - // Therefore we rely on the NOP jump at the beginning of the Line Number Program for - // padding rather than this field. - const before_header_len = di_buf.items.len; - di_buf.items.len += ptr_width_bytes; // We will come back and write this. - const after_header_len = di_buf.items.len; - - const opcode_base = DW.LNS.set_isa + 1; - di_buf.appendSliceAssumeCapacity(&[_]u8{ - 1, // minimum_instruction_length - 1, // maximum_operations_per_instruction - 1, // default_is_stmt - 1, // line_base (signed) - 1, // line_range - opcode_base, - - // Standard opcode lengths. The number of items here is based on `opcode_base`. - // The value is the number of LEB128 operands the instruction takes. - 0, // `DW.LNS.copy` - 1, // `DW.LNS.advance_pc` - 1, // `DW.LNS.advance_line` - 1, // `DW.LNS.set_file` - 1, // `DW.LNS.set_column` - 0, // `DW.LNS.negate_stmt` - 0, // `DW.LNS.set_basic_block` - 0, // `DW.LNS.const_add_pc` - 1, // `DW.LNS.fixed_advance_pc` - 0, // `DW.LNS.set_prologue_end` - 0, // `DW.LNS.set_epilogue_begin` - 1, // `DW.LNS.set_isa` - 0, // include_directories (none except the compilation unit cwd) - }); - // file_names[0] - di_buf.appendSliceAssumeCapacity(module.root_pkg.root_src_path); // relative path name - di_buf.appendSliceAssumeCapacity(&[_]u8{ - 0, // null byte for the relative path name - 0, // directory_index - 0, // mtime (TODO supply this) - 0, // file size bytes (TODO supply this) - 0, // file_names sentinel - }); - const header_len = di_buf.items.len - after_header_len; - switch (self.ptr_width) { - .p32 => { - mem.writeInt(u32, di_buf.items[before_header_len..][0..4], @intCast(u32, header_len), target_endian); - }, - .p64 => { - mem.writeInt(u64, di_buf.items[before_header_len..][0..8], header_len, target_endian); - }, - } - - // We use NOPs because consumers empirically do not respect the header length field. - if (di_buf.items.len > dbg_line_prg_off) { - // Move the first N files to the end to make more padding for the header. - @panic("TODO: handle .debug_line header exceeding its padding"); - } - const jmp_amt = dbg_line_prg_off - di_buf.items.len; - try self.pwriteDbgLineNops(0, di_buf.items, jmp_amt, debug_line_sect.sh_offset); + if (self.debug_line_header_dirty) { + try self.dwarf.?.writeDbgLineHeader(&self.base, module); self.debug_line_header_dirty = false; } @@ -1481,11 +1100,13 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { self.shstrtab_dirty = false; } } + { const debug_strtab_sect = &self.sections.items[self.debug_str_section_index.?]; - if (self.debug_strtab_dirty or self.debug_strtab.items.len != debug_strtab_sect.sh_size) { + const dwarf = self.dwarf.?; + if (self.debug_strtab_dirty or dwarf.strtab.items.len != debug_strtab_sect.sh_size) { const allocated_size = self.allocatedSize(debug_strtab_sect.sh_offset); - const needed_size = self.debug_strtab.items.len; + const needed_size = dwarf.strtab.items.len; if (needed_size > allocated_size) { debug_strtab_sect.sh_size = 0; // free the space @@ -1494,7 +1115,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { debug_strtab_sect.sh_size = needed_size; log.debug("debug_strtab start=0x{x} end=0x{x}", .{ debug_strtab_sect.sh_offset, debug_strtab_sect.sh_offset + needed_size }); - try self.base.file.?.pwriteAll(self.debug_strtab.items, debug_strtab_sect.sh_offset); + try self.base.file.?.pwriteAll(dwarf.strtab.items, debug_strtab_sect.sh_offset); if (!self.shdr_table_dirty) { // Then it won't get written with the others and we need to do it. try self.writeSectHeader(self.debug_str_section_index.?); @@ -1502,6 +1123,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { self.debug_strtab_dirty = false; } } + if (self.shdr_table_dirty) { const shsize: u64 = switch (self.ptr_width) { .p32 => @sizeOf(elf.Elf32_Shdr), @@ -2340,7 +1962,6 @@ fn freeTextBlock(self: *Elf, text_block: *TextBlock, phdr_index: u16) void { i += 1; } } - // TODO process free list for dbg info just like we do above for vaddrs if (self.atoms.getPtr(phdr_index)) |last_block| { if (last_block.* == text_block) { @@ -2353,14 +1974,6 @@ fn freeTextBlock(self: *Elf, text_block: *TextBlock, phdr_index: u16) void { } } - if (self.dbg_info_decl_first == text_block) { - self.dbg_info_decl_first = text_block.dbg_info_next; - } - if (self.dbg_info_decl_last == text_block) { - // TODO shrink the .debug_info section size here - self.dbg_info_decl_last = text_block.dbg_info_prev; - } - if (text_block.prev) |prev| { prev.next = text_block.next; @@ -2379,18 +1992,8 @@ fn freeTextBlock(self: *Elf, text_block: *TextBlock, phdr_index: u16) void { text_block.next = null; } - if (text_block.dbg_info_prev) |prev| { - prev.dbg_info_next = text_block.dbg_info_next; - - // TODO the free list logic like we do for text blocks above - } else { - text_block.dbg_info_prev = null; - } - - if (text_block.dbg_info_next) |next| { - next.dbg_info_prev = text_block.dbg_info_prev; - } else { - text_block.dbg_info_next = null; + if (self.dwarf) |*dw| { + dw.freeAtom(&text_block.dbg_info_atom); } } @@ -2619,26 +2222,9 @@ pub fn freeDecl(self: *Elf, decl: *Module.Decl) void { self.offset_table_free_list.append(self.base.allocator, decl.link.elf.offset_table_index) catch {}; } - // TODO make this logic match freeTextBlock. Maybe abstract the logic out since the same thing - // is desired for both. - _ = self.dbg_line_fn_free_list.remove(&decl.fn_link.elf); - if (decl.fn_link.elf.prev) |prev| { - self.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {}; - prev.next = decl.fn_link.elf.next; - if (decl.fn_link.elf.next) |next| { - next.prev = prev; - } else { - self.dbg_line_fn_last = prev; - } - } else if (decl.fn_link.elf.next) |next| { - self.dbg_line_fn_first = next; - next.prev = null; - } - if (self.dbg_line_fn_first == &decl.fn_link.elf) { - self.dbg_line_fn_first = decl.fn_link.elf.next; - } - if (self.dbg_line_fn_last == &decl.fn_link.elf) { - self.dbg_line_fn_last = decl.fn_link.elf.prev; + + if (self.dwarf) |*dw| { + dw.freeDecl(decl); } } @@ -2739,62 +2325,6 @@ fn updateDeclCode(self: *Elf, decl: *Module.Decl, code: []const u8, stt_bits: u8 return local_sym; } -fn finishUpdateDecl( - self: *Elf, - module: *Module, - decl: *Module.Decl, - dbg_info_type_relocs: *File.DbgInfoTypeRelocsTable, - dbg_info_buffer: *std.ArrayList(u8), -) !void { - // We need this for the duration of this function only so that for composite - // types such as []const u32, if the type *u32 is non-existent, we create - // it synthetically and store the backing bytes in this arena. After we are - // done with the relocations, we can safely deinit the entire memory slab. - // TODO currently, we do not store the relocations for future use, however, - // if that is the case, we should move memory management to a higher scope, - // such as linker scope, or whatnot. - var dbg_type_arena = std.heap.ArenaAllocator.init(self.base.allocator); - defer dbg_type_arena.deinit(); - - // Now we emit the .debug_info types of the Decl. These will count towards the size of - // the buffer, so we have to do it before computing the offset, and we can't perform the actual - // relocations yet. - { - var it: usize = 0; - while (it < dbg_info_type_relocs.count()) : (it += 1) { - const ty = dbg_info_type_relocs.keys()[it]; - const value_ptr = dbg_info_type_relocs.getPtr(ty).?; - value_ptr.off = @intCast(u32, dbg_info_buffer.items.len); - try self.addDbgInfoType(dbg_type_arena.allocator(), ty, dbg_info_buffer, dbg_info_type_relocs); - } - } - - const text_block = &decl.link.elf; - try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len)); - - const target_endian = self.base.options.target.cpu.arch.endian(); - - { - // Now that we have the offset assigned we can finally perform type relocations. - for (dbg_info_type_relocs.values()) |value| { - for (value.relocs.items) |off| { - mem.writeInt( - u32, - dbg_info_buffer.items[off..][0..4], - text_block.dbg_info_off + value.off, - target_endian, - ); - } - } - } - - try self.writeDeclDebugInfo(text_block, dbg_info_buffer.items); - - // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. - const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; - return self.updateDeclExports(module, decl, decl_exports); -} - pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void { if (build_options.skip_non_native and builtin.object_format != .elf) { @panic("Attempted to compile for object format that was disabled by build configuration"); @@ -2809,96 +2339,33 @@ pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liven var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); - // For functions we need to add a prologue to the debug line program. - var dbg_line_buffer = try std.ArrayList(u8).initCapacity(self.base.allocator, 26); - defer dbg_line_buffer.deinit(); - - var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator); - defer dbg_info_buffer.deinit(); - - var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{}; - defer deinitRelocs(self.base.allocator, &dbg_info_type_relocs); - const decl = func.owner_decl; self.freeUnnamedConsts(decl); - const decl_name = try decl.getFullyQualifiedName(self.base.allocator); - defer self.base.allocator.free(decl_name); - - log.debug("updateFunc {s}{*}", .{ decl_name, func.owner_decl }); - log.debug(" (decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d})", .{ - decl.src_line, - func.lbrace_line, - func.rbrace_line, - }); - const line = @intCast(u28, decl.src_line + func.lbrace_line); - - const ptr_width_bytes = self.ptrWidthBytes(); - dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{ - DW.LNS.extended_op, - ptr_width_bytes + 1, - DW.LNE.set_address, - }); - // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`. - assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len); - dbg_line_buffer.items.len += ptr_width_bytes; - - dbg_line_buffer.appendAssumeCapacity(DW.LNS.advance_line); - // This is the "relocatable" relative line offset from the previous function's end curly - // to this function's begin curly. - assert(self.getRelocDbgLineOff() == dbg_line_buffer.items.len); - // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later. - leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line); - - dbg_line_buffer.appendAssumeCapacity(DW.LNS.set_file); - assert(self.getRelocDbgFileIndex() == dbg_line_buffer.items.len); - // Once we support more than one source file, this will have the ability to be more - // than one possible value. - const file_index = 1; - leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index); - - // Emit a line for the begin curly with prologue_end=false. The codegen will - // do the work of setting prologue_end=true and epilogue_begin=true. - dbg_line_buffer.appendAssumeCapacity(DW.LNS.copy); - - // .debug_info subprogram - const decl_name_with_null = decl_name[0 .. decl_name.len + 1]; - try dbg_info_buffer.ensureUnusedCapacity(25 + decl_name_with_null.len); - - const fn_ret_type = decl.ty.fnReturnType(); - const fn_ret_has_bits = fn_ret_type.hasRuntimeBits(); - if (fn_ret_has_bits) { - dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram); - } else { - dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram_retvoid); - } - // These get overwritten after generating the machine code. These values are - // "relocations" and have to be in this fixed place so that functions can be - // moved in virtual address space. - assert(dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len); - dbg_info_buffer.items.len += ptr_width_bytes; // DW.AT.low_pc, DW.FORM.addr - assert(self.getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len); - dbg_info_buffer.items.len += 4; // DW.AT.high_pc, DW.FORM.data4 - if (fn_ret_has_bits) { - const gop = try dbg_info_type_relocs.getOrPut(self.base.allocator, fn_ret_type); - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .off = undefined, - .relocs = .{}, - }; + var debug_buffers_buf: Dwarf.DeclDebugBuffers = undefined; + const debug_buffers = if (self.dwarf) |*dw| blk: { + debug_buffers_buf = try dw.initDeclDebugInfo(decl); + break :blk &debug_buffers_buf; + } else null; + defer { + if (debug_buffers) |dbg| { + dbg.dbg_line_buffer.deinit(); + dbg.dbg_info_buffer.deinit(); + deinitRelocs(self.base.allocator, &dbg.dbg_info_type_relocs); } - try gop.value_ptr.relocs.append(self.base.allocator, @intCast(u32, dbg_info_buffer.items.len)); - dbg_info_buffer.items.len += 4; // DW.AT.type, DW.FORM.ref4 } - dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT.name, DW.FORM.string - const res = try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{ - .dwarf = .{ - .dbg_line = &dbg_line_buffer, - .dbg_info = &dbg_info_buffer, - .dbg_info_type_relocs = &dbg_info_type_relocs, - }, - }); + const res = if (debug_buffers) |dbg| + try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{ + .dwarf = .{ + .dbg_line = &dbg.dbg_line_buffer, + .dbg_info = &dbg.dbg_info_buffer, + .dbg_info_type_relocs = &dbg.dbg_info_type_relocs, + }, + }) + else + try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .none); + const code = switch (res) { .appended => code_buffer.items, .fail => |em| { @@ -2907,128 +2374,14 @@ pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liven return; }, }; - const local_sym = try self.updateDeclCode(decl, code, elf.STT_FUNC); - - const target_endian = self.base.options.target.cpu.arch.endian(); - - // Since the Decl is a function, we need to update the .debug_line program. - // Perform the relocations based on vaddr. - switch (self.ptr_width) { - .p32 => { - { - const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..4]; - mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian); - } - { - const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..4]; - mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian); - } - }, - .p64 => { - { - const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8]; - mem.writeInt(u64, ptr, local_sym.st_value, target_endian); - } - { - const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..8]; - mem.writeInt(u64, ptr, local_sym.st_value, target_endian); - } - }, + if (debug_buffers) |dbg| { + try self.dwarf.?.commitDeclDebugInfo(&self.base, module, decl, local_sym.st_value, local_sym.st_size, dbg); } - { - const ptr = dbg_info_buffer.items[self.getRelocDbgInfoSubprogramHighPC()..][0..4]; - mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_size), target_endian); - } - - try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS.extended_op, 1, DW.LNE.end_sequence }); - - // Now we have the full contents and may allocate a region to store it. - - // This logic is nearly identical to the logic below in `updateDeclDebugInfoAllocation` for - // `TextBlock` and the .debug_info. If you are editing this logic, you - // probably need to edit that logic too. - - const debug_line_sect = &self.sections.items[self.debug_line_section_index.?]; - const src_fn = &decl.fn_link.elf; - src_fn.len = @intCast(u32, dbg_line_buffer.items.len); - if (self.dbg_line_fn_last) |last| not_first: { - if (src_fn.next) |next| { - // Update existing function - non-last item. - if (src_fn.off + src_fn.len + min_nop_size > next.off) { - // It grew too big, so we move it to a new location. - if (src_fn.prev) |prev| { - self.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {}; - prev.next = src_fn.next; - } - assert(src_fn.prev != next); - next.prev = src_fn.prev; - src_fn.next = null; - // Populate where it used to be with NOPs. - const file_pos = debug_line_sect.sh_offset + src_fn.off; - try self.pwriteDbgLineNops(0, &[0]u8{}, src_fn.len, file_pos); - // TODO Look at the free list before appending at the end. - src_fn.prev = last; - last.next = src_fn; - self.dbg_line_fn_last = src_fn; - - src_fn.off = last.off + padToIdeal(last.len); - } - } else if (src_fn.prev == null) { - if (src_fn == last) { - // Special case: there is only 1 function and it is being updated. - // In this case there is nothing to do. The function's length has - // already been updated, and the logic below takes care of - // resizing the .debug_line section. - break :not_first; - } - // Append new function. - // TODO Look at the free list before appending at the end. - src_fn.prev = last; - last.next = src_fn; - self.dbg_line_fn_last = src_fn; - src_fn.off = last.off + padToIdeal(last.len); - } - } else { - // This is the first function of the Line Number Program. - self.dbg_line_fn_first = src_fn; - self.dbg_line_fn_last = src_fn; - - src_fn.off = padToIdeal(self.dbgLineNeededHeaderBytes()); - } - - const last_src_fn = self.dbg_line_fn_last.?; - const needed_size = last_src_fn.off + last_src_fn.len; - if (needed_size != debug_line_sect.sh_size) { - if (needed_size > self.allocatedSize(debug_line_sect.sh_offset)) { - const new_offset = self.findFreeSpace(needed_size, 1); - const existing_size = last_src_fn.off; - log.debug("moving .debug_line section: {d} bytes from 0x{x} to 0x{x}", .{ - existing_size, - debug_line_sect.sh_offset, - new_offset, - }); - const amt = try self.base.file.?.copyRangeAll(debug_line_sect.sh_offset, self.base.file.?, new_offset, existing_size); - if (amt != existing_size) return error.InputOutput; - debug_line_sect.sh_offset = new_offset; - } - debug_line_sect.sh_size = needed_size; - self.shdr_table_dirty = true; // TODO look into making only the one section dirty - self.debug_line_header_dirty = true; - } - const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0; - const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0; - - // We only have support for one compilation unit so far, so the offsets are directly - // from the .debug_line section. - const file_pos = debug_line_sect.sh_offset + src_fn.off; - try self.pwriteDbgLineNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos); - - // .debug_info - End the TAG.subprogram children. - try dbg_info_buffer.append(0); - - return self.finishUpdateDecl(module, decl, &dbg_info_type_relocs, &dbg_info_buffer); + // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. + const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl, decl_exports); } pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { @@ -3057,29 +2410,42 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); - var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator); - defer dbg_line_buffer.deinit(); - - var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator); - defer dbg_info_buffer.deinit(); - - var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{}; - defer deinitRelocs(self.base.allocator, &dbg_info_type_relocs); + var debug_buffers_buf: Dwarf.DeclDebugBuffers = undefined; + const debug_buffers = if (self.dwarf) |*dw| blk: { + debug_buffers_buf = try dw.initDeclDebugInfo(decl); + break :blk &debug_buffers_buf; + } else null; + defer { + if (debug_buffers) |dbg| { + dbg.dbg_line_buffer.deinit(); + dbg.dbg_info_buffer.deinit(); + deinitRelocs(self.base.allocator, &dbg.dbg_info_type_relocs); + } + } // TODO implement .debug_info for global variables const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val; - const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{ - .ty = decl.ty, - .val = decl_val, - }, &code_buffer, .{ - .dwarf = .{ - .dbg_line = &dbg_line_buffer, - .dbg_info = &dbg_info_buffer, - .dbg_info_type_relocs = &dbg_info_type_relocs, - }, - }, .{ - .parent_atom_index = decl.link.elf.local_sym_index, - }); + const res = if (debug_buffers) |dbg| + try codegen.generateSymbol(&self.base, decl.srcLoc(), .{ + .ty = decl.ty, + .val = decl_val, + }, &code_buffer, .{ + .dwarf = .{ + .dbg_line = &dbg.dbg_line_buffer, + .dbg_info = &dbg.dbg_info_buffer, + .dbg_info_type_relocs = &dbg.dbg_info_type_relocs, + }, + }, .{ + .parent_atom_index = decl.link.elf.local_sym_index, + }) + else + try codegen.generateSymbol(&self.base, decl.srcLoc(), .{ + .ty = decl.ty, + .val = decl_val, + }, &code_buffer, .none, .{ + .parent_atom_index = decl.link.elf.local_sym_index, + }); + const code = switch (res) { .externally_managed => |x| x, .appended => code_buffer.items, @@ -3090,8 +2456,14 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { }, }; - _ = try self.updateDeclCode(decl, code, elf.STT_OBJECT); - return self.finishUpdateDecl(module, decl, &dbg_info_type_relocs, &dbg_info_buffer); + const local_sym = try self.updateDeclCode(decl, code, elf.STT_OBJECT); + if (debug_buffers) |dbg| { + try self.dwarf.?.commitDeclDebugInfo(&self.base, module, decl, local_sym.st_value, local_sym.st_size, dbg); + } + + // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. + const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl, decl_exports); } pub fn lowerUnnamedConst(self: *Elf, typed_value: TypedValue, decl: *Module.Decl) !u32 { @@ -3170,304 +2542,6 @@ pub fn lowerUnnamedConst(self: *Elf, typed_value: TypedValue, decl: *Module.Decl return atom.local_sym_index; } -/// Asserts the type has codegen bits. -fn addDbgInfoType( - self: *Elf, - arena: Allocator, - ty: Type, - dbg_info_buffer: *std.ArrayList(u8), - dbg_info_type_relocs: *File.DbgInfoTypeRelocsTable, -) error{OutOfMemory}!void { - const target = self.base.options.target; - var relocs = std.ArrayList(struct { ty: Type, reloc: u32 }).init(arena); - - switch (ty.zigTypeTag()) { - .NoReturn => unreachable, - .Void => { - try dbg_info_buffer.append(abbrev_pad1); - }, - .Bool => { - try dbg_info_buffer.appendSlice(&[_]u8{ - abbrev_base_type, - DW.ATE.boolean, // DW.AT.encoding , DW.FORM.data1 - 1, // DW.AT.byte_size, DW.FORM.data1 - 'b', 'o', 'o', 'l', 0, // DW.AT.name, DW.FORM.string - }); - }, - .Int => { - const info = ty.intInfo(target); - try dbg_info_buffer.ensureUnusedCapacity(12); - dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); - // DW.AT.encoding, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) { - .signed => DW.ATE.signed, - .unsigned => DW.ATE.unsigned, - }); - // DW.AT.byte_size, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target))); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty}); - }, - .Optional => { - if (ty.isPtrLikeOptional()) { - try dbg_info_buffer.ensureUnusedCapacity(12); - dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); - // DW.AT.encoding, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(DW.ATE.address); - // DW.AT.byte_size, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target))); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty}); - } else { - // Non-pointer optionals are structs: struct { .maybe = *, .val = * } - var buf = try arena.create(Type.Payload.ElemType); - const payload_ty = ty.optionalChild(buf); - // DW.AT.structure_type - try dbg_info_buffer.append(abbrev_struct_type); - // DW.AT.byte_size, DW.FORM.sdata - const abi_size = ty.abiSize(target); - try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty}); - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(7); - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("maybe"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - var index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = Type.bool, .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - try dbg_info_buffer.ensureUnusedCapacity(6); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.member - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("val"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = payload_ty, .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - const offset = abi_size - payload_ty.abiSize(target); - try leb128.writeULEB128(dbg_info_buffer.writer(), offset); - // DW.AT.structure_type delimit children - try dbg_info_buffer.append(0); - } - }, - .Pointer => { - if (ty.isSlice()) { - // Slices are structs: struct { .ptr = *, .len = N } - // DW.AT.structure_type - try dbg_info_buffer.ensureUnusedCapacity(2); - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_type); - // DW.AT.byte_size, DW.FORM.sdata - dbg_info_buffer.appendAssumeCapacity(@sizeOf(usize) * 2); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty}); - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(5); - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("ptr"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - var index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - var buf = try arena.create(Type.SlicePtrFieldTypeBuffer); - const ptr_ty = ty.slicePtrFieldType(buf); - try relocs.append(.{ .ty = ptr_ty, .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - try dbg_info_buffer.ensureUnusedCapacity(6); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.member - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("len"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = Type.initTag(.usize), .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - try dbg_info_buffer.ensureUnusedCapacity(2); - dbg_info_buffer.appendAssumeCapacity(@sizeOf(usize)); - // DW.AT.structure_type delimit children - dbg_info_buffer.appendAssumeCapacity(0); - } else { - try dbg_info_buffer.ensureUnusedCapacity(5); - dbg_info_buffer.appendAssumeCapacity(abbrev_ptr_type); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = ty.childType(), .reloc = @intCast(u32, index) }); - } - }, - .Struct => blk: { - // try dbg_info_buffer.ensureUnusedCapacity(23); - // DW.AT.structure_type - try dbg_info_buffer.append(abbrev_struct_type); - // DW.AT.byte_size, DW.FORM.sdata - const abi_size = ty.abiSize(target); - try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size); - // DW.AT.name, DW.FORM.string - const struct_name = try ty.nameAlloc(arena); - try dbg_info_buffer.ensureUnusedCapacity(struct_name.len + 1); - dbg_info_buffer.appendSliceAssumeCapacity(struct_name); - dbg_info_buffer.appendAssumeCapacity(0); - - const struct_obj = ty.castTag(.@"struct").?.data; - if (struct_obj.layout == .Packed) { - log.debug("TODO implement .debug_info for packed structs", .{}); - break :blk; - } - - const fields = ty.structFields(); - for (fields.keys()) |field_name, field_index| { - const field = fields.get(field_name).?; - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(field_name.len + 2); - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity(field_name); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - var index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = field.ty, .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - const field_off = ty.structFieldOffset(field_index, target); - try leb128.writeULEB128(dbg_info_buffer.writer(), field_off); - } - - // DW.AT.structure_type delimit children - try dbg_info_buffer.append(0); - }, - else => { - log.debug("TODO implement .debug_info for type '{}'", .{ty}); - try dbg_info_buffer.append(abbrev_pad1); - }, - } - - for (relocs.items) |rel| { - const gop = try dbg_info_type_relocs.getOrPut(self.base.allocator, rel.ty); - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .off = undefined, - .relocs = .{}, - }; - } - try gop.value_ptr.relocs.append(self.base.allocator, rel.reloc); - } -} - -fn updateDeclDebugInfoAllocation(self: *Elf, text_block: *TextBlock, len: u32) !void { - const tracy = trace(@src()); - defer tracy.end(); - - // This logic is nearly identical to the logic above in `updateDecl` for - // `SrcFn` and the line number programs. If you are editing this logic, you - // probably need to edit that logic too. - - const debug_info_sect = &self.sections.items[self.debug_info_section_index.?]; - text_block.dbg_info_len = len; - if (self.dbg_info_decl_last) |last| not_first: { - if (text_block.dbg_info_next) |next| { - // Update existing Decl - non-last item. - if (text_block.dbg_info_off + text_block.dbg_info_len + min_nop_size > next.dbg_info_off) { - // It grew too big, so we move it to a new location. - if (text_block.dbg_info_prev) |prev| { - self.dbg_info_decl_free_list.put(self.base.allocator, prev, {}) catch {}; - prev.dbg_info_next = text_block.dbg_info_next; - } - next.dbg_info_prev = text_block.dbg_info_prev; - text_block.dbg_info_next = null; - // Populate where it used to be with NOPs. - const file_pos = debug_info_sect.sh_offset + text_block.dbg_info_off; - try self.pwriteDbgInfoNops(0, &[0]u8{}, text_block.dbg_info_len, false, file_pos); - // TODO Look at the free list before appending at the end. - text_block.dbg_info_prev = last; - last.dbg_info_next = text_block; - self.dbg_info_decl_last = text_block; - - text_block.dbg_info_off = last.dbg_info_off + padToIdeal(last.dbg_info_len); - } - } else if (text_block.dbg_info_prev == null) { - if (text_block == last) { - // Special case: there is only 1 .debug_info block and it is being updated. - // In this case there is nothing to do. The block's length has - // already been updated, and logic in writeDeclDebugInfo takes care of - // resizing the .debug_info section. - break :not_first; - } - // Append new Decl. - // TODO Look at the free list before appending at the end. - text_block.dbg_info_prev = last; - last.dbg_info_next = text_block; - self.dbg_info_decl_last = text_block; - - text_block.dbg_info_off = last.dbg_info_off + padToIdeal(last.dbg_info_len); - } - } else { - // This is the first Decl of the .debug_info - self.dbg_info_decl_first = text_block; - self.dbg_info_decl_last = text_block; - - text_block.dbg_info_off = padToIdeal(self.dbgInfoNeededHeaderBytes()); - } -} - -fn writeDeclDebugInfo(self: *Elf, text_block: *TextBlock, dbg_info_buf: []const u8) !void { - const tracy = trace(@src()); - defer tracy.end(); - - // This logic is nearly identical to the logic above in `updateDecl` for - // `SrcFn` and the line number programs. If you are editing this logic, you - // probably need to edit that logic too. - - const debug_info_sect = &self.sections.items[self.debug_info_section_index.?]; - - const last_decl = self.dbg_info_decl_last.?; - // +1 for a trailing zero to end the children of the decl tag. - const needed_size = last_decl.dbg_info_off + last_decl.dbg_info_len + 1; - if (needed_size != debug_info_sect.sh_size) { - if (needed_size > self.allocatedSize(debug_info_sect.sh_offset)) { - const new_offset = self.findFreeSpace(needed_size, 1); - const existing_size = last_decl.dbg_info_off; - log.debug("moving .debug_info section: {} bytes from 0x{x} to 0x{x}", .{ - existing_size, - debug_info_sect.sh_offset, - new_offset, - }); - const amt = try self.base.file.?.copyRangeAll(debug_info_sect.sh_offset, self.base.file.?, new_offset, existing_size); - if (amt != existing_size) return error.InputOutput; - debug_info_sect.sh_offset = new_offset; - } - debug_info_sect.sh_size = needed_size; - self.shdr_table_dirty = true; // TODO look into making only the one section dirty - self.debug_info_header_dirty = true; - } - const prev_padding_size: u32 = if (text_block.dbg_info_prev) |prev| - text_block.dbg_info_off - (prev.dbg_info_off + prev.dbg_info_len) - else - 0; - const next_padding_size: u32 = if (text_block.dbg_info_next) |next| - next.dbg_info_off - (text_block.dbg_info_off + text_block.dbg_info_len) - else - 0; - - // To end the children of the decl tag. - const trailing_zero = text_block.dbg_info_next == null; - - // We only have support for one compilation unit so far, so the offsets are directly - // from the .debug_info section. - const file_pos = debug_info_sect.sh_offset + text_block.dbg_info_off; - try self.pwriteDbgInfoNops(prev_padding_size, dbg_info_buf, next_padding_size, trailing_zero, file_pos); -} - pub fn updateDeclExports( self: *Elf, module: *Module, @@ -3568,20 +2642,9 @@ pub fn updateDeclLineNumber(self: *Elf, module: *Module, decl: *const Module.Dec log.debug("updateDeclLineNumber {s}{*}", .{ decl_name, decl }); if (self.llvm_object) |_| return; - - const func = decl.val.castTag(.function).?.data; - log.debug(" (decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d})", .{ - decl.src_line, - func.lbrace_line, - func.rbrace_line, - }); - const line = @intCast(u28, decl.src_line + func.lbrace_line); - - const shdr = &self.sections.items[self.debug_line_section_index.?]; - const file_pos = shdr.sh_offset + decl.fn_link.elf.off + self.getRelocDbgLineOff(); - var data: [4]u8 = undefined; - leb128.writeUnsignedFixed(4, &data, line); - try self.base.file.?.pwriteAll(&data, file_pos); + if (self.dwarf) |*dw| { + try dw.updateDeclLineNumber(&self.base, decl); + } } pub fn deleteExport(self: *Elf, exp: Export) void { @@ -3806,201 +2869,6 @@ fn archPtrWidthBytes(self: Elf) u8 { return @intCast(u8, self.base.options.target.cpu.arch.ptrBitWidth() / 8); } -/// The reloc offset for the virtual address of a function in its Line Number Program. -/// Size is a virtual address integer. -const dbg_line_vaddr_reloc_index = 3; -/// The reloc offset for the virtual address of a function in its .debug_info TAG.subprogram. -/// Size is a virtual address integer. -const dbg_info_low_pc_reloc_index = 1; - -/// The reloc offset for the line offset of a function from the previous function's line. -/// It's a fixed-size 4-byte ULEB128. -fn getRelocDbgLineOff(self: Elf) usize { - return dbg_line_vaddr_reloc_index + self.ptrWidthBytes() + 1; -} - -fn getRelocDbgFileIndex(self: Elf) usize { - return self.getRelocDbgLineOff() + 5; -} - -fn getRelocDbgInfoSubprogramHighPC(self: Elf) u32 { - return dbg_info_low_pc_reloc_index + self.ptrWidthBytes(); -} - -fn dbgLineNeededHeaderBytes(self: Elf) u32 { - const directory_entry_format_count = 1; - const file_name_entry_format_count = 1; - const directory_count = 1; - const file_name_count = 1; - const root_src_dir_path_len = if (self.base.options.module.?.root_pkg.root_src_directory.path) |p| p.len else 1; // "." - return @intCast(u32, 53 + directory_entry_format_count * 2 + file_name_entry_format_count * 2 + - directory_count * 8 + file_name_count * 8 + - // These are encoded as DW.FORM.string rather than DW.FORM.strp as we would like - // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly. - root_src_dir_path_len + - self.base.options.module.?.root_pkg.root_src_path.len); -} - -fn dbgInfoNeededHeaderBytes(self: Elf) u32 { - _ = self; - return 120; -} - -const min_nop_size = 2; - -/// Writes to the file a buffer, prefixed and suffixed by the specified number of -/// bytes of NOPs. Asserts each padding size is at least `min_nop_size` and total padding bytes -/// are less than 1044480 bytes (if this limit is ever reached, this function can be -/// improved to make more than one pwritev call, or the limit can be raised by a fixed -/// amount by increasing the length of `vecs`). -fn pwriteDbgLineNops( - self: *Elf, - prev_padding_size: usize, - buf: []const u8, - next_padding_size: usize, - offset: u64, -) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const page_of_nops = [1]u8{DW.LNS.negate_stmt} ** 4096; - const three_byte_nop = [3]u8{ DW.LNS.advance_pc, 0b1000_0000, 0 }; - var vecs: [512]std.os.iovec_const = undefined; - var vec_index: usize = 0; - { - var padding_left = prev_padding_size; - if (padding_left % 2 != 0) { - vecs[vec_index] = .{ - .iov_base = &three_byte_nop, - .iov_len = three_byte_nop.len, - }; - vec_index += 1; - padding_left -= three_byte_nop.len; - } - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, - }; - vec_index += 1; - } - } - - vecs[vec_index] = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }; - vec_index += 1; - - { - var padding_left = next_padding_size; - if (padding_left % 2 != 0) { - vecs[vec_index] = .{ - .iov_base = &three_byte_nop, - .iov_len = three_byte_nop.len, - }; - vec_index += 1; - padding_left -= three_byte_nop.len; - } - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, - }; - vec_index += 1; - } - } - try self.base.file.?.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); -} - -/// Writes to the file a buffer, prefixed and suffixed by the specified number of -/// bytes of padding. -fn pwriteDbgInfoNops( - self: *Elf, - prev_padding_size: usize, - buf: []const u8, - next_padding_size: usize, - trailing_zero: bool, - offset: u64, -) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const page_of_nops = [1]u8{abbrev_pad1} ** 4096; - var vecs: [32]std.os.iovec_const = undefined; - var vec_index: usize = 0; - { - var padding_left = prev_padding_size; - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, - }; - vec_index += 1; - } - } - - vecs[vec_index] = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }; - vec_index += 1; - - { - var padding_left = next_padding_size; - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, - }; - vec_index += 1; - } - } - - if (trailing_zero) { - var zbuf = [1]u8{0}; - vecs[vec_index] = .{ - .iov_base = &zbuf, - .iov_len = zbuf.len, - }; - vec_index += 1; - } - - try self.base.file.?.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); -} - fn progHeaderTo32(phdr: elf.Elf64_Phdr) elf.Elf32_Phdr { return .{ .p_type = phdr.p_type, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6bf7d183df..a394ea703d 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -295,26 +295,6 @@ pub const Export = struct { sym_index: ?u32 = null, }; -pub const SrcFn = struct { - /// Offset from the beginning of the Debug Line Program header that contains this function. - off: u32, - /// Size of the line number program component belonging to this function, not - /// including padding. - len: u32, - - /// Points to the previous and next neighbors, based on the offset from .debug_line. - /// This can be used to find, for example, the capacity of this `SrcFn`. - prev: ?*SrcFn, - next: ?*SrcFn, - - pub const empty: SrcFn = .{ - .off = 0, - .len = 0, - .prev = null, - .next = null, - }; -}; - pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { assert(options.object_format == .macho); @@ -376,6 +356,7 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { self.d_sym = .{ .base = self, + .dwarf = link.File.Dwarf.init(allocator, .macho, options.target), .file = d_sym_file, }; } @@ -3523,7 +3504,6 @@ fn freeAtom(self: *MachO, atom: *Atom, match: MatchingSection, owns_atom: bool) i += 1; } } - // TODO process free list for dbg info just like we do above for vaddrs if (self.atoms.getPtr(match)) |last_atom| { if (last_atom.* == atom) { @@ -3536,16 +3516,6 @@ fn freeAtom(self: *MachO, atom: *Atom, match: MatchingSection, owns_atom: bool) } } - if (self.d_sym) |*d_sym| { - if (d_sym.dbg_info_decl_first == atom) { - d_sym.dbg_info_decl_first = atom.dbg_info_next; - } - if (d_sym.dbg_info_decl_last == atom) { - // TODO shrink the .debug_info section size here - d_sym.dbg_info_decl_last = atom.dbg_info_prev; - } - } - if (atom.prev) |prev| { prev.next = atom.next; @@ -3564,18 +3534,8 @@ fn freeAtom(self: *MachO, atom: *Atom, match: MatchingSection, owns_atom: bool) atom.next = null; } - if (atom.dbg_info_prev) |prev| { - prev.dbg_info_next = atom.dbg_info_next; - - // TODO the free list logic like we do for atoms above - } else { - atom.dbg_info_prev = null; - } - - if (atom.dbg_info_next) |next| { - next.dbg_info_prev = atom.dbg_info_prev; - } else { - atom.dbg_info_next = null; + if (self.d_sym) |*d_sym| { + d_sym.dwarf.freeAtom(&atom.dbg_info_atom); } } @@ -3725,9 +3685,9 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); - var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined; + var debug_buffers_buf: link.File.Dwarf.DeclDebugBuffers = undefined; const debug_buffers = if (self.d_sym) |*d_sym| blk: { - debug_buffers_buf = try d_sym.initDeclDebugBuffers(self.base.allocator, module, decl); + debug_buffers_buf = try d_sym.initDeclDebugInfo(module, decl); break :blk &debug_buffers_buf; } else null; defer { @@ -3766,7 +3726,7 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv if (debug_buffers) |db| { if (self.d_sym) |*d_sym| { - try d_sym.commitDeclDebugInfo(self.base.allocator, module, decl, db); + try d_sym.commitDeclDebugInfo(module, decl, db); } } @@ -3805,9 +3765,7 @@ pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl: *Module.De const atom = try self.createEmptyAtom(local_sym_index, @sizeOf(u64), math.log2(required_alignment)); try self.atom_by_index_table.putNoClobber(self.base.allocator, local_sym_index, atom); - const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{ - .none = .{}, - }, .{ + const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none, .{ .parent_atom_index = local_sym_index, }); const code = switch (res) { @@ -3869,9 +3827,9 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void { var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); - var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined; + var debug_buffers_buf: link.File.Dwarf.DeclDebugBuffers = undefined; const debug_buffers = if (self.d_sym) |*d_sym| blk: { - debug_buffers_buf = try d_sym.initDeclDebugBuffers(self.base.allocator, module, decl); + debug_buffers_buf = try d_sym.initDeclDebugInfo(module, decl); break :blk &debug_buffers_buf; } else null; defer { @@ -4364,27 +4322,7 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { decl.link.macho.local_sym_index = 0; } if (self.d_sym) |*d_sym| { - // TODO make this logic match freeAtom. Maybe abstract the logic - // out since the same thing is desired for both. - _ = d_sym.dbg_line_fn_free_list.remove(&decl.fn_link.macho); - if (decl.fn_link.macho.prev) |prev| { - d_sym.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {}; - prev.next = decl.fn_link.macho.next; - if (decl.fn_link.macho.next) |next| { - next.prev = prev; - } else { - d_sym.dbg_line_fn_last = prev; - } - } else if (decl.fn_link.macho.next) |next| { - d_sym.dbg_line_fn_first = next; - next.prev = null; - } - if (d_sym.dbg_line_fn_first == &decl.fn_link.macho) { - d_sym.dbg_line_fn_first = decl.fn_link.macho.next; - } - if (d_sym.dbg_line_fn_last == &decl.fn_link.macho) { - d_sym.dbg_line_fn_last = decl.fn_link.macho.prev; - } + d_sym.dwarf.freeDecl(decl); } } diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index d0d82a12af..4e58946735 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -13,6 +13,7 @@ const trace = @import("../../tracy.zig").trace; const Allocator = mem.Allocator; const Arch = std.Target.Cpu.Arch; +const Dwarf = @import("../Dwarf.zig"); const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); const StringIndexAdapter = std.hash_map.StringIndexAdapter; @@ -71,14 +72,7 @@ stab: ?Stab = null, next: ?*Atom, prev: ?*Atom, -/// Previous/next linked list pointers. -/// This is the linked list node for this Decl's corresponding .debug_info tag. -dbg_info_prev: ?*Atom, -dbg_info_next: ?*Atom, -/// Offset into .debug_info pointing to the tag for this Decl. -dbg_info_off: u32, -/// Size of the .debug_info tag for this Decl, not including padding. -dbg_info_len: u32, +dbg_info_atom: Dwarf.DebugInfoAtom, dirty: bool = true, @@ -188,10 +182,7 @@ pub const empty = Atom{ .alignment = 0, .prev = null, .next = null, - .dbg_info_prev = null, - .dbg_info_next = null, - .dbg_info_off = undefined, - .dbg_info_len = undefined, + .dbg_info_atom = undefined, }; pub fn deinit(self: *Atom, allocator: Allocator) void { diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index f9393476dc..3d80e9a9b5 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -1,29 +1,27 @@ const DebugSymbols = @This(); const std = @import("std"); +const build_options = @import("build_options"); const assert = std.debug.assert; const fs = std.fs; +const link = @import("../../link.zig"); const log = std.log.scoped(.link); -const leb128 = std.leb; const macho = std.macho; +const makeStaticString = MachO.makeStaticString; const math = std.math; const mem = std.mem; -const DW = std.dwarf; -const leb = std.leb; -const Allocator = mem.Allocator; - -const build_options = @import("build_options"); +const padToIdeal = MachO.padToIdeal; const trace = @import("../../tracy.zig").trace; -const Module = @import("../../Module.zig"); -const Type = @import("../../type.zig").Type; -const link = @import("../../link.zig"); + +const Allocator = mem.Allocator; +const Dwarf = @import("../Dwarf.zig"); const MachO = @import("../MachO.zig"); +const Module = @import("../../Module.zig"); const TextBlock = MachO.TextBlock; -const SrcFn = MachO.SrcFn; -const makeStaticString = MachO.makeStaticString; -const padToIdeal = MachO.padToIdeal; +const Type = @import("../../type.zig").Type; base: *MachO, +dwarf: Dwarf, file: fs.File, /// Table of all load commands @@ -54,23 +52,6 @@ debug_str_section_index: ?u16 = null, debug_aranges_section_index: ?u16 = null, debug_line_section_index: ?u16 = null, -debug_abbrev_table_offset: ?u64 = null, - -/// A list of `SrcFn` whose Line Number Programs have surplus capacity. -/// This is the same concept as `text_block_free_list`; see those doc comments. -dbg_line_fn_free_list: std.AutoHashMapUnmanaged(*SrcFn, void) = .{}, -dbg_line_fn_first: ?*SrcFn = null, -dbg_line_fn_last: ?*SrcFn = null, - -/// A list of `TextBlock` whose corresponding .debug_info tags have surplus capacity. -/// This is the same concept as `text_block_free_list`; see those doc comments. -dbg_info_decl_free_list: std.AutoHashMapUnmanaged(*TextBlock, void) = .{}, -dbg_info_decl_first: ?*TextBlock = null, -dbg_info_decl_last: ?*TextBlock = null, - -/// Table of debug symbol names aka the debug string table. -debug_string_table: std.ArrayListUnmanaged(u8) = .{}, - load_commands_dirty: bool = false, debug_string_table_dirty: bool = false, debug_abbrev_section_dirty: bool = false, @@ -78,25 +59,6 @@ debug_aranges_section_dirty: bool = false, debug_info_header_dirty: bool = false, debug_line_header_dirty: bool = false, -pub const abbrev_compile_unit = 1; -pub const abbrev_subprogram = 2; -pub const abbrev_subprogram_retvoid = 3; -pub const abbrev_base_type = 4; -pub const abbrev_ptr_type = 5; -pub const abbrev_struct_type = 6; -pub const abbrev_struct_member = 7; -pub const abbrev_pad1 = 8; -pub const abbrev_parameter = 9; - -/// The reloc offset for the virtual address of a function in its Line Number Program. -/// Size is a virtual address integer. -const dbg_line_vaddr_reloc_index = 3; -/// The reloc offset for the virtual address of a function in its .debug_info TAG.subprogram. -/// Size is a virtual address integer. -const dbg_info_low_pc_reloc_index = 1; - -const min_nop_size = 2; - /// You must call this function *after* `MachO.populateMissingMetadata()` /// has been called to get a viable debug symbols output. pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void { @@ -193,10 +155,10 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void } if (self.debug_str_section_index == null) { - assert(self.debug_string_table.items.len == 0); + assert(self.dwarf.strtab.items.len == 0); self.debug_str_section_index = try self.allocateSection( "__debug_str", - @intCast(u32, self.debug_string_table.items.len), + @intCast(u32, self.dwarf.strtab.items.len), 0, ); self.debug_string_table_dirty = true; @@ -277,7 +239,7 @@ fn detectAllocCollision(self: *DebugSymbols, start: u64, size: u64) ?u64 { return null; } -fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) u64 { +pub fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) u64 { const seg = self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; var offset: u64 = seg.inner.fileoff; while (self.detectAllocCollision(offset, object_size)) |item_end| { @@ -290,322 +252,45 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti // TODO This linker code currently assumes there is only 1 compilation unit and it corresponds to the // Zig source code. const module = options.module orelse return error.LinkingWithoutZigSourceUnimplemented; - const init_len_size: usize = 4; if (self.debug_abbrev_section_dirty) { - const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; - const debug_abbrev_sect = &dwarf_segment.sections.items[self.debug_abbrev_section_index.?]; - - // These are LEB encoded but since the values are all less than 127 - // we can simply append these bytes. - const abbrev_buf = [_]u8{ - abbrev_compile_unit, DW.TAG.compile_unit, DW.CHILDREN.yes, // header - DW.AT.stmt_list, DW.FORM.sec_offset, DW.AT.low_pc, - DW.FORM.addr, DW.AT.high_pc, DW.FORM.addr, - DW.AT.name, DW.FORM.strp, DW.AT.comp_dir, - DW.FORM.strp, DW.AT.producer, DW.FORM.strp, - DW.AT.language, DW.FORM.data2, 0, - 0, // table sentinel - abbrev_subprogram, - DW.TAG.subprogram, - DW.CHILDREN.yes, // header - DW.AT.low_pc, - DW.FORM.addr, - DW.AT.high_pc, - DW.FORM.data4, - DW.AT.type, - DW.FORM.ref4, - DW.AT.name, - DW.FORM.string, - 0, 0, // table sentinel - abbrev_subprogram_retvoid, - DW.TAG.subprogram, DW.CHILDREN.yes, // header - DW.AT.low_pc, DW.FORM.addr, - DW.AT.high_pc, DW.FORM.data4, - DW.AT.name, DW.FORM.string, - 0, - 0, // table sentinel - abbrev_base_type, - DW.TAG.base_type, - DW.CHILDREN.no, // header - DW.AT.encoding, - DW.FORM.data1, - DW.AT.byte_size, - DW.FORM.data1, - DW.AT.name, - DW.FORM.string, - 0, - 0, // table sentinel - abbrev_ptr_type, - DW.TAG.pointer_type, - DW.CHILDREN.no, // header - DW.AT.type, - DW.FORM.ref4, - 0, - 0, // table sentinel - abbrev_struct_type, - DW.TAG.structure_type, - DW.CHILDREN.yes, // header - DW.AT.byte_size, - DW.FORM.sdata, - DW.AT.name, - DW.FORM.string, - 0, - 0, // table sentinel - abbrev_struct_member, - DW.TAG.member, - DW.CHILDREN.no, // header - DW.AT.name, - DW.FORM.string, - DW.AT.type, - DW.FORM.ref4, - DW.AT.data_member_location, - DW.FORM.sdata, - 0, - 0, // table sentinel - abbrev_pad1, - DW.TAG.unspecified_type, - DW.CHILDREN.no, // header - 0, - 0, // table sentinel - abbrev_parameter, - DW.TAG.formal_parameter, DW.CHILDREN.no, // header - DW.AT.location, DW.FORM.exprloc, - DW.AT.type, DW.FORM.ref4, - DW.AT.name, DW.FORM.string, - 0, - 0, // table sentinel - 0, - 0, - 0, // section sentinel - }; - - const needed_size = abbrev_buf.len; - const allocated_size = self.allocatedSize(debug_abbrev_sect.offset); - if (needed_size > allocated_size) { - debug_abbrev_sect.size = 0; // free the space - const offset = self.findFreeSpace(needed_size, 1); - debug_abbrev_sect.offset = @intCast(u32, offset); - debug_abbrev_sect.addr = dwarf_segment.inner.vmaddr + offset - dwarf_segment.inner.fileoff; - } - debug_abbrev_sect.size = needed_size; - log.debug("__debug_abbrev start=0x{x} end=0x{x}", .{ - debug_abbrev_sect.offset, - debug_abbrev_sect.offset + needed_size, - }); - - const abbrev_offset = 0; - self.debug_abbrev_table_offset = abbrev_offset; - try self.file.pwriteAll(&abbrev_buf, debug_abbrev_sect.offset + abbrev_offset); + try self.dwarf.writeDbgAbbrev(&self.base.base); self.load_commands_dirty = true; self.debug_abbrev_section_dirty = false; } - if (self.debug_info_header_dirty) debug_info: { - // If this value is null it means there is an error in the module; - // leave debug_info_header_dirty=true. - const first_dbg_info_decl = self.dbg_info_decl_first orelse break :debug_info; - const last_dbg_info_decl = self.dbg_info_decl_last.?; - const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; - const debug_info_sect = &dwarf_segment.sections.items[self.debug_info_section_index.?]; - - // We have a function to compute the upper bound size, because it's needed - // for determining where to put the offset of the first `LinkBlock`. - const needed_bytes = self.dbgInfoNeededHeaderBytes(); - var di_buf = try std.ArrayList(u8).initCapacity(allocator, needed_bytes); - defer di_buf.deinit(); - - // initial length - length of the .debug_info contribution for this compilation unit, - // not including the initial length itself. - // We have to come back and write it later after we know the size. - const after_init_len = di_buf.items.len + init_len_size; - // +1 for the final 0 that ends the compilation unit children. - const dbg_info_end = last_dbg_info_decl.dbg_info_off + last_dbg_info_decl.dbg_info_len + 1; - const init_len = dbg_info_end - after_init_len; - mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len)); - mem.writeIntLittle(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4); // DWARF version - const abbrev_offset = self.debug_abbrev_table_offset.?; - mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, abbrev_offset)); - di_buf.appendAssumeCapacity(8); // address size - // Write the form for the compile unit, which must match the abbrev table above. - const name_strp = try self.makeDebugString(allocator, module.root_pkg.root_src_path); - const comp_dir_strp = try self.makeDebugString(allocator, module.root_pkg.root_src_directory.path orelse "."); - const producer_strp = try self.makeDebugString(allocator, link.producer_string); + if (self.debug_info_header_dirty) { // Currently only one compilation unit is supported, so the address range is simply // identical to the main program header virtual address and memory size. const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].segment; const text_section = text_segment.sections.items[self.text_section_index.?]; const low_pc = text_section.addr; const high_pc = text_section.addr + text_section.size; - - di_buf.appendAssumeCapacity(abbrev_compile_unit); - mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); // DW.AT.stmt_list, DW.FORM.sec_offset - mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), low_pc); - mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), high_pc); - mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, name_strp)); - mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, comp_dir_strp)); - mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, producer_strp)); - // We are still waiting on dwarf-std.org to assign DW_LANG_Zig a number: - // http://dwarfstd.org/ShowIssue.php?issue=171115.1 - // Until then we say it is C99. - mem.writeIntLittle(u16, di_buf.addManyAsArrayAssumeCapacity(2), DW.LANG.C99); - - if (di_buf.items.len > first_dbg_info_decl.dbg_info_off) { - // Move the first N decls to the end to make more padding for the header. - @panic("TODO: handle __debug_info header exceeding its padding"); - } - const jmp_amt = first_dbg_info_decl.dbg_info_off - di_buf.items.len; - try self.pwriteDbgInfoNops(0, di_buf.items, jmp_amt, false, debug_info_sect.offset); + try self.dwarf.writeDbgInfoHeader(&self.base.base, module, low_pc, high_pc); self.debug_info_header_dirty = false; } if (self.debug_aranges_section_dirty) { - const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; - const debug_aranges_sect = &dwarf_segment.sections.items[self.debug_aranges_section_index.?]; - - // Enough for all the data without resizing. When support for more compilation units - // is added, the size of this section will become more variable. - var di_buf = try std.ArrayList(u8).initCapacity(allocator, 100); - defer di_buf.deinit(); - - // initial length - length of the .debug_aranges contribution for this compilation unit, - // not including the initial length itself. - // We have to come back and write it later after we know the size. - const init_len_index = di_buf.items.len; - di_buf.items.len += init_len_size; - const after_init_len = di_buf.items.len; - mem.writeIntLittle(u16, di_buf.addManyAsArrayAssumeCapacity(2), 2); // version - // When more than one compilation unit is supported, this will be the offset to it. - // For now it is always at offset 0 in .debug_info. - mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); // __debug_info offset - di_buf.appendAssumeCapacity(@sizeOf(u64)); // address_size - di_buf.appendAssumeCapacity(0); // segment_selector_size - - const end_header_offset = di_buf.items.len; - const begin_entries_offset = mem.alignForward(end_header_offset, @sizeOf(u64) * 2); - di_buf.appendNTimesAssumeCapacity(0, begin_entries_offset - end_header_offset); - // Currently only one compilation unit is supported, so the address range is simply // identical to the main program header virtual address and memory size. const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].segment; const text_section = text_segment.sections.items[self.text_section_index.?]; - mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), text_section.addr); - mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), text_section.size); - - // Sentinel. - mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), 0); - mem.writeIntLittle(u64, di_buf.addManyAsArrayAssumeCapacity(8), 0); - - // Go back and populate the initial length. - const init_len = di_buf.items.len - after_init_len; - // initial length - length of the .debug_aranges contribution for this compilation unit, - // not including the initial length itself. - mem.writeIntLittle(u32, di_buf.items[init_len_index..][0..4], @intCast(u32, init_len)); - - const needed_size = di_buf.items.len; - const allocated_size = self.allocatedSize(debug_aranges_sect.offset); - if (needed_size > allocated_size) { - debug_aranges_sect.size = 0; // free the space - const new_offset = self.findFreeSpace(needed_size, 16); - debug_aranges_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; - debug_aranges_sect.offset = @intCast(u32, new_offset); - } - debug_aranges_sect.size = needed_size; - log.debug("__debug_aranges start=0x{x} end=0x{x}", .{ - debug_aranges_sect.offset, - debug_aranges_sect.offset + needed_size, - }); - - try self.file.pwriteAll(di_buf.items, debug_aranges_sect.offset); + try self.dwarf.writeDbgAranges(&self.base.base, text_section.addr, text_section.size); self.load_commands_dirty = true; self.debug_aranges_section_dirty = false; } - if (self.debug_line_header_dirty) debug_line: { - if (self.dbg_line_fn_first == null) { - break :debug_line; // Error in module; leave debug_line_header_dirty=true. - } - const dbg_line_prg_off = self.getDebugLineProgramOff(); - const dbg_line_prg_end = self.getDebugLineProgramEnd(); - assert(dbg_line_prg_end != 0); - const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; - const debug_line_sect = &dwarf_segment.sections.items[self.debug_line_section_index.?]; - - // The size of this header is variable, depending on the number of directories, - // files, and padding. We have a function to compute the upper bound size, however, - // because it's needed for determining where to put the offset of the first `SrcFn`. - const needed_bytes = self.dbgLineNeededHeaderBytes(module); - var di_buf = try std.ArrayList(u8).initCapacity(allocator, needed_bytes); - defer di_buf.deinit(); - - // initial length - length of the .debug_line contribution for this compilation unit, - // not including the initial length itself. - const after_init_len = di_buf.items.len + init_len_size; - const init_len = dbg_line_prg_end - after_init_len; - mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), @intCast(u32, init_len)); - mem.writeIntLittle(u16, di_buf.addManyAsArrayAssumeCapacity(2), 4); // version - - // Empirically, debug info consumers do not respect this field, or otherwise - // consider it to be an error when it does not point exactly to the end of the header. - // Therefore we rely on the NOP jump at the beginning of the Line Number Program for - // padding rather than this field. - const before_header_len = di_buf.items.len; - di_buf.items.len += @sizeOf(u32); // We will come back and write this. - const after_header_len = di_buf.items.len; - - const opcode_base = DW.LNS.set_isa + 1; - di_buf.appendSliceAssumeCapacity(&[_]u8{ - 1, // minimum_instruction_length - 1, // maximum_operations_per_instruction - 1, // default_is_stmt - 1, // line_base (signed) - 1, // line_range - opcode_base, - - // Standard opcode lengths. The number of items here is based on `opcode_base`. - // The value is the number of LEB128 operands the instruction takes. - 0, // `DW.LNS.copy` - 1, // `DW.LNS.advance_pc` - 1, // `DW.LNS.advance_line` - 1, // `DW.LNS.set_file` - 1, // `DW.LNS.set_column` - 0, // `DW.LNS.negate_stmt` - 0, // `DW.LNS.set_basic_block` - 0, // `DW.LNS.const_add_pc` - 1, // `DW.LNS.fixed_advance_pc` - 0, // `DW.LNS.set_prologue_end` - 0, // `DW.LNS.set_epilogue_begin` - 1, // `DW.LNS.set_isa` - 0, // include_directories (none except the compilation unit cwd) - }); - // file_names[0] - di_buf.appendSliceAssumeCapacity(module.root_pkg.root_src_path); // relative path name - di_buf.appendSliceAssumeCapacity(&[_]u8{ - 0, // null byte for the relative path name - 0, // directory_index - 0, // mtime (TODO supply this) - 0, // file size bytes (TODO supply this) - 0, // file_names sentinel - }); - - const header_len = di_buf.items.len - after_header_len; - mem.writeIntLittle(u32, di_buf.items[before_header_len..][0..4], @intCast(u32, header_len)); - - // We use NOPs because consumers empirically do not respect the header length field. - if (di_buf.items.len > dbg_line_prg_off) { - // Move the first N files to the end to make more padding for the header. - @panic("TODO: handle __debug_line header exceeding its padding"); - } - const jmp_amt = dbg_line_prg_off - di_buf.items.len; - try self.pwriteDbgLineNops(0, di_buf.items, jmp_amt, debug_line_sect.offset); + if (self.debug_line_header_dirty) { + try self.dwarf.writeDbgLineHeader(&self.base.base, module); self.debug_line_header_dirty = false; } + { const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; const debug_strtab_sect = &dwarf_segment.sections.items[self.debug_str_section_index.?]; - if (self.debug_string_table_dirty or self.debug_string_table.items.len != debug_strtab_sect.size) { + if (self.debug_string_table_dirty or self.dwarf.strtab.items.len != debug_strtab_sect.size) { const allocated_size = self.allocatedSize(debug_strtab_sect.offset); - const needed_size = self.debug_string_table.items.len; + const needed_size = self.dwarf.strtab.items.len; if (needed_size > allocated_size) { debug_strtab_sect.size = 0; // free the space @@ -620,7 +305,7 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti debug_strtab_sect.offset + needed_size, }); - try self.file.pwriteAll(self.debug_string_table.items, debug_strtab_sect.offset); + try self.file.pwriteAll(self.dwarf.strtab.items, debug_strtab_sect.offset); self.load_commands_dirty = true; self.debug_string_table_dirty = false; } @@ -639,13 +324,11 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti } pub fn deinit(self: *DebugSymbols, allocator: Allocator) void { - self.dbg_info_decl_free_list.deinit(allocator); - self.dbg_line_fn_free_list.deinit(allocator); - self.debug_string_table.deinit(allocator); for (self.load_commands.items) |*lc| { lc.deinit(allocator); } self.load_commands.deinit(allocator); + self.dwarf.deinit(); self.file.close(); } @@ -777,7 +460,7 @@ fn writeHeader(self: *DebugSymbols) !void { try self.file.pwriteAll(mem.asBytes(&header), 0); } -fn allocatedSize(self: *DebugSymbols, start: u64) u64 { +pub fn allocatedSize(self: *DebugSymbols, start: u64) u64 { const seg = self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; assert(start >= seg.inner.fileoff); var min_pos: u64 = std.math.maxInt(u64); @@ -964,813 +647,22 @@ fn writeStringTable(self: *DebugSymbols) !void { pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const Module.Decl) !void { _ = module; - const tracy = trace(@src()); - defer tracy.end(); - - log.debug("updateDeclLineNumber {s}{*}", .{ decl.name, decl }); - - const func = decl.val.castTag(.function).?.data; - log.debug(" (decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d})", .{ - decl.src_line, - func.lbrace_line, - func.rbrace_line, - }); - const line = @intCast(u28, decl.src_line + func.lbrace_line); - - const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; - const shdr = &dwarf_segment.sections.items[self.debug_line_section_index.?]; - const file_pos = shdr.offset + decl.fn_link.macho.off + getRelocDbgLineOff(); - var data: [4]u8 = undefined; - leb.writeUnsignedFixed(4, &data, line); - try self.file.pwriteAll(&data, file_pos); + return self.dwarf.updateDeclLineNumber(&self.base.base, decl); } -pub const DeclDebugBuffers = struct { - dbg_line_buffer: std.ArrayList(u8), - dbg_info_buffer: std.ArrayList(u8), - dbg_info_type_relocs: link.File.DbgInfoTypeRelocsTable, -}; - /// Caller owns the returned memory. -pub fn initDeclDebugBuffers( - self: *DebugSymbols, - allocator: Allocator, - module: *Module, - decl: *Module.Decl, -) !DeclDebugBuffers { - _ = self; +pub fn initDeclDebugInfo(self: *DebugSymbols, module: *Module, decl: *Module.Decl) !Dwarf.DeclDebugBuffers { _ = module; - const tracy = trace(@src()); - defer tracy.end(); - - var dbg_line_buffer = std.ArrayList(u8).init(allocator); - var dbg_info_buffer = std.ArrayList(u8).init(allocator); - var dbg_info_type_relocs: link.File.DbgInfoTypeRelocsTable = .{}; - - assert(decl.has_tv); - switch (decl.ty.zigTypeTag()) { - .Fn => { - // For functions we need to add a prologue to the debug line program. - try dbg_line_buffer.ensureTotalCapacity(26); - - const func = decl.val.castTag(.function).?.data; - log.debug("updateFunc {s}{*}", .{ decl.name, func.owner_decl }); - log.debug(" (decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d})", .{ - decl.src_line, - func.lbrace_line, - func.rbrace_line, - }); - const line = @intCast(u28, decl.src_line + func.lbrace_line); - - dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{ - DW.LNS.extended_op, - @sizeOf(u64) + 1, - DW.LNE.set_address, - }); - // This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`. - assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len); - dbg_line_buffer.items.len += @sizeOf(u64); - - dbg_line_buffer.appendAssumeCapacity(DW.LNS.advance_line); - // This is the "relocatable" relative line offset from the previous function's end curly - // to this function's begin curly. - assert(getRelocDbgLineOff() == dbg_line_buffer.items.len); - // Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later. - leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line); - - dbg_line_buffer.appendAssumeCapacity(DW.LNS.set_file); - assert(getRelocDbgFileIndex() == dbg_line_buffer.items.len); - // Once we support more than one source file, this will have the ability to be more - // than one possible value. - const file_index = 1; - leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index); - - // Emit a line for the begin curly with prologue_end=false. The codegen will - // do the work of setting prologue_end=true and epilogue_begin=true. - dbg_line_buffer.appendAssumeCapacity(DW.LNS.copy); - - // .debug_info subprogram - const decl_name_with_null = decl.name[0 .. mem.sliceTo(decl.name, 0).len + 1]; - try dbg_info_buffer.ensureUnusedCapacity(25 + decl_name_with_null.len); - - const fn_ret_type = decl.ty.fnReturnType(); - const fn_ret_has_bits = fn_ret_type.hasRuntimeBits(); - if (fn_ret_has_bits) { - dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram); - } else { - dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram_retvoid); - } - // These get overwritten after generating the machine code. These values are - // "relocations" and have to be in this fixed place so that functions can be - // moved in virtual address space. - assert(dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len); - dbg_info_buffer.items.len += @sizeOf(u64); // DW.AT.low_pc, DW.FORM.addr - assert(getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len); - dbg_info_buffer.items.len += 4; // DW.AT.high_pc, DW.FORM.data4 - if (fn_ret_has_bits) { - const gop = try dbg_info_type_relocs.getOrPut(allocator, fn_ret_type); - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .off = undefined, - .relocs = .{}, - }; - } - try gop.value_ptr.relocs.append(allocator, @intCast(u32, dbg_info_buffer.items.len)); - dbg_info_buffer.items.len += 4; // DW.AT.type, DW.FORM.ref4 - } - dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT.name, DW.FORM.string - }, - else => { - // TODO implement .debug_info for global variables - }, - } - - return DeclDebugBuffers{ - .dbg_info_buffer = dbg_info_buffer, - .dbg_line_buffer = dbg_line_buffer, - .dbg_info_type_relocs = dbg_info_type_relocs, - }; + return self.dwarf.initDeclDebugInfo(decl); } pub fn commitDeclDebugInfo( self: *DebugSymbols, - allocator: Allocator, module: *Module, decl: *Module.Decl, - debug_buffers: *DeclDebugBuffers, + debug_buffers: *Dwarf.DeclDebugBuffers, ) !void { - const tracy = trace(@src()); - defer tracy.end(); - - var dbg_line_buffer = &debug_buffers.dbg_line_buffer; - var dbg_info_buffer = &debug_buffers.dbg_info_buffer; - var dbg_info_type_relocs = &debug_buffers.dbg_info_type_relocs; - const symbol = self.base.locals.items[decl.link.macho.local_sym_index]; - const text_block = &decl.link.macho; - // If the Decl is a function, we need to update the __debug_line program. - assert(decl.has_tv); - switch (decl.ty.zigTypeTag()) { - .Fn => { - // Perform the relocations based on vaddr. - { - const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8]; - mem.writeIntLittle(u64, ptr, symbol.n_value); - } - { - const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..8]; - mem.writeIntLittle(u64, ptr, symbol.n_value); - } - { - const ptr = dbg_info_buffer.items[getRelocDbgInfoSubprogramHighPC()..][0..4]; - mem.writeIntLittle(u32, ptr, @intCast(u32, text_block.size)); - } - - try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS.extended_op, 1, DW.LNE.end_sequence }); - - // Now we have the full contents and may allocate a region to store it. - - // This logic is nearly identical to the logic below in `updateDeclDebugInfo` for - // `TextBlock` and the .debug_info. If you are editing this logic, you - // probably need to edit that logic too. - - const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; - const debug_line_sect = &dwarf_segment.sections.items[self.debug_line_section_index.?]; - const src_fn = &decl.fn_link.macho; - src_fn.len = @intCast(u32, dbg_line_buffer.items.len); - if (self.dbg_line_fn_last) |last| blk: { - if (src_fn == last) break :blk; - if (src_fn.next) |next| { - // Update existing function - non-last item. - if (src_fn.off + src_fn.len + min_nop_size > next.off) { - // It grew too big, so we move it to a new location. - if (src_fn.prev) |prev| { - self.dbg_line_fn_free_list.put(allocator, prev, {}) catch {}; - prev.next = src_fn.next; - } - next.prev = src_fn.prev; - src_fn.next = null; - // Populate where it used to be with NOPs. - const file_pos = debug_line_sect.offset + src_fn.off; - try self.pwriteDbgLineNops(0, &[0]u8{}, src_fn.len, file_pos); - // TODO Look at the free list before appending at the end. - src_fn.prev = last; - last.next = src_fn; - self.dbg_line_fn_last = src_fn; - - src_fn.off = last.off + padToIdeal(last.len); - } - } else if (src_fn.prev == null) { - // Append new function. - // TODO Look at the free list before appending at the end. - src_fn.prev = last; - last.next = src_fn; - self.dbg_line_fn_last = src_fn; - - src_fn.off = last.off + padToIdeal(last.len); - } - } else { - // This is the first function of the Line Number Program. - self.dbg_line_fn_first = src_fn; - self.dbg_line_fn_last = src_fn; - - src_fn.off = padToIdeal(self.dbgLineNeededHeaderBytes(module)); - } - - const last_src_fn = self.dbg_line_fn_last.?; - const needed_size = last_src_fn.off + last_src_fn.len; - if (needed_size != debug_line_sect.size) { - if (needed_size > self.allocatedSize(debug_line_sect.offset)) { - const new_offset = self.findFreeSpace(needed_size, 1); - const existing_size = last_src_fn.off; - - log.debug("moving __debug_line section: {} bytes from 0x{x} to 0x{x}", .{ - existing_size, - debug_line_sect.offset, - new_offset, - }); - - try MachO.copyRangeAllOverlappingAlloc( - self.base.base.allocator, - self.file, - debug_line_sect.offset, - new_offset, - existing_size, - ); - - debug_line_sect.offset = @intCast(u32, new_offset); - debug_line_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; - } - debug_line_sect.size = needed_size; - self.load_commands_dirty = true; // TODO look into making only the one section dirty - self.debug_line_header_dirty = true; - } - const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0; - const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0; - - // We only have support for one compilation unit so far, so the offsets are directly - // from the .debug_line section. - const file_pos = debug_line_sect.offset + src_fn.off; - try self.pwriteDbgLineNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos); - - // .debug_info - End the TAG.subprogram children. - try dbg_info_buffer.append(0); - }, - else => {}, - } - - if (dbg_info_buffer.items.len == 0) - return; - - // We need this for the duration of this function only so that for composite - // types such as []const u32, if the type *u32 is non-existent, we create - // it synthetically and store the backing bytes in this arena. After we are - // done with the relocations, we can safely deinit the entire memory slab. - // TODO currently, we do not store the relocations for future use, however, - // if that is the case, we should move memory management to a higher scope, - // such as linker scope, or whatnot. - var dbg_type_arena = std.heap.ArenaAllocator.init(allocator); - defer dbg_type_arena.deinit(); - - { - // Now we emit the .debug_info types of the Decl. These will count towards the size of - // the buffer, so we have to do it before computing the offset, and we can't perform the actual - // relocations yet. - var it: usize = 0; - while (it < dbg_info_type_relocs.count()) : (it += 1) { - const ty = dbg_info_type_relocs.keys()[it]; - const value_ptr = dbg_info_type_relocs.getPtr(ty).?; - value_ptr.off = @intCast(u32, dbg_info_buffer.items.len); - try self.addDbgInfoType(dbg_type_arena.allocator(), ty, dbg_info_buffer, dbg_info_type_relocs); - } - } - - try self.updateDeclDebugInfoAllocation(allocator, text_block, @intCast(u32, dbg_info_buffer.items.len)); - - { - // Now that we have the offset assigned we can finally perform type relocations. - for (dbg_info_type_relocs.values()) |value| { - for (value.relocs.items) |off| { - mem.writeIntLittle( - u32, - dbg_info_buffer.items[off..][0..4], - text_block.dbg_info_off + value.off, - ); - } - } - } - - try self.writeDeclDebugInfo(text_block, dbg_info_buffer.items); -} - -/// Asserts the type has codegen bits. -fn addDbgInfoType( - self: *DebugSymbols, - arena: Allocator, - ty: Type, - dbg_info_buffer: *std.ArrayList(u8), - dbg_info_type_relocs: *link.File.DbgInfoTypeRelocsTable, -) !void { - const target = self.base.base.options.target; - var relocs = std.ArrayList(struct { ty: Type, reloc: u32 }).init(arena); - - switch (ty.zigTypeTag()) { - .NoReturn => unreachable, - .Void => { - try dbg_info_buffer.append(abbrev_pad1); - }, - .Bool => { - try dbg_info_buffer.appendSlice(&[_]u8{ - abbrev_base_type, - DW.ATE.boolean, // DW.AT.encoding , DW.FORM.data1 - 1, // DW.AT.byte_size, DW.FORM.data1 - 'b', 'o', 'o', 'l', 0, // DW.AT.name, DW.FORM.string - }); - }, - .Int => { - const info = ty.intInfo(target); - try dbg_info_buffer.ensureUnusedCapacity(12); - dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); - // DW.AT.encoding, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) { - .signed => DW.ATE.signed, - .unsigned => DW.ATE.unsigned, - }); - // DW.AT.byte_size, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target))); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty}); - }, - .Optional => { - if (ty.isPtrLikeOptional()) { - try dbg_info_buffer.ensureUnusedCapacity(12); - dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); - // DW.AT.encoding, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(DW.ATE.address); - // DW.AT.byte_size, DW.FORM.data1 - dbg_info_buffer.appendAssumeCapacity(@intCast(u8, ty.abiSize(target))); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty}); - } else { - // Non-pointer optionals are structs: struct { .maybe = *, .val = * } - var buf = try arena.create(Type.Payload.ElemType); - const payload_ty = ty.optionalChild(buf); - // DW.AT.structure_type - try dbg_info_buffer.append(abbrev_struct_type); - // DW.AT.byte_size, DW.FORM.sdata - const abi_size = ty.abiSize(target); - try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty}); - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(7); - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("maybe"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - var index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = Type.bool, .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - try dbg_info_buffer.ensureUnusedCapacity(6); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.member - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("val"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = payload_ty, .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - const offset = abi_size - payload_ty.abiSize(target); - try leb128.writeULEB128(dbg_info_buffer.writer(), offset); - // DW.AT.structure_type delimit children - try dbg_info_buffer.append(0); - } - }, - .Pointer => { - if (ty.isSlice()) { - // Slices are structs: struct { .ptr = *, .len = N } - // DW.AT.structure_type - try dbg_info_buffer.ensureUnusedCapacity(2); - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_type); - // DW.AT.byte_size, DW.FORM.sdata - dbg_info_buffer.appendAssumeCapacity(@sizeOf(usize) * 2); - // DW.AT.name, DW.FORM.string - try dbg_info_buffer.writer().print("{}\x00", .{ty}); - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(5); - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("ptr"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - var index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - var buf = try arena.create(Type.SlicePtrFieldTypeBuffer); - const ptr_ty = ty.slicePtrFieldType(buf); - try relocs.append(.{ .ty = ptr_ty, .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - try dbg_info_buffer.ensureUnusedCapacity(6); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.member - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity("len"); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = Type.initTag(.usize), .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - try dbg_info_buffer.ensureUnusedCapacity(2); - dbg_info_buffer.appendAssumeCapacity(@sizeOf(usize)); - // DW.AT.structure_type delimit children - dbg_info_buffer.appendAssumeCapacity(0); - } else { - try dbg_info_buffer.ensureUnusedCapacity(5); - dbg_info_buffer.appendAssumeCapacity(abbrev_ptr_type); - // DW.AT.type, DW.FORM.ref4 - const index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = ty.childType(), .reloc = @intCast(u32, index) }); - } - }, - .Struct => blk: { - // try dbg_info_buffer.ensureUnusedCapacity(23); - // DW.AT.structure_type - try dbg_info_buffer.append(abbrev_struct_type); - // DW.AT.byte_size, DW.FORM.sdata - const abi_size = ty.abiSize(target); - try leb128.writeULEB128(dbg_info_buffer.writer(), abi_size); - // DW.AT.name, DW.FORM.string - const struct_name = try ty.nameAlloc(arena); - try dbg_info_buffer.ensureUnusedCapacity(struct_name.len + 1); - dbg_info_buffer.appendSliceAssumeCapacity(struct_name); - dbg_info_buffer.appendAssumeCapacity(0); - - const struct_obj = ty.castTag(.@"struct").?.data; - if (struct_obj.layout == .Packed) { - log.debug("TODO implement .debug_info for packed structs", .{}); - break :blk; - } - - const fields = ty.structFields(); - for (fields.keys()) |field_name, field_index| { - const field = fields.get(field_name).?; - // DW.AT.member - try dbg_info_buffer.ensureUnusedCapacity(field_name.len + 2); - dbg_info_buffer.appendAssumeCapacity(abbrev_struct_member); - // DW.AT.name, DW.FORM.string - dbg_info_buffer.appendSliceAssumeCapacity(field_name); - dbg_info_buffer.appendAssumeCapacity(0); - // DW.AT.type, DW.FORM.ref4 - var index = dbg_info_buffer.items.len; - try dbg_info_buffer.resize(index + 4); - try relocs.append(.{ .ty = field.ty, .reloc = @intCast(u32, index) }); - // DW.AT.data_member_location, DW.FORM.sdata - const field_off = ty.structFieldOffset(field_index, target); - try leb128.writeULEB128(dbg_info_buffer.writer(), field_off); - } - - // DW.AT.structure_type delimit children - try dbg_info_buffer.append(0); - }, - else => { - log.debug("TODO implement .debug_info for type '{}'", .{ty}); - try dbg_info_buffer.append(abbrev_pad1); - }, - } - - for (relocs.items) |rel| { - const gop = try dbg_info_type_relocs.getOrPut(self.base.base.allocator, rel.ty); - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .off = undefined, - .relocs = .{}, - }; - } - try gop.value_ptr.relocs.append(self.base.base.allocator, rel.reloc); - } -} - -fn updateDeclDebugInfoAllocation( - self: *DebugSymbols, - allocator: Allocator, - text_block: *TextBlock, - len: u32, -) !void { - const tracy = trace(@src()); - defer tracy.end(); - - // This logic is nearly identical to the logic above in `updateDecl` for - // `SrcFn` and the line number programs. If you are editing this logic, you - // probably need to edit that logic too. - - const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; - const debug_info_sect = &dwarf_segment.sections.items[self.debug_info_section_index.?]; - text_block.dbg_info_len = len; - if (self.dbg_info_decl_last) |last| blk: { - if (text_block == last) break :blk; - if (text_block.dbg_info_next) |next| { - // Update existing Decl - non-last item. - if (text_block.dbg_info_off + text_block.dbg_info_len + min_nop_size > next.dbg_info_off) { - // It grew too big, so we move it to a new location. - if (text_block.dbg_info_prev) |prev| { - self.dbg_info_decl_free_list.put(allocator, prev, {}) catch {}; - prev.dbg_info_next = text_block.dbg_info_next; - } - next.dbg_info_prev = text_block.dbg_info_prev; - text_block.dbg_info_next = null; - // Populate where it used to be with NOPs. - const file_pos = debug_info_sect.offset + text_block.dbg_info_off; - try self.pwriteDbgInfoNops(0, &[0]u8{}, text_block.dbg_info_len, false, file_pos); - // TODO Look at the free list before appending at the end. - text_block.dbg_info_prev = last; - last.dbg_info_next = text_block; - self.dbg_info_decl_last = text_block; - - text_block.dbg_info_off = last.dbg_info_off + padToIdeal(last.dbg_info_len); - } - } else if (text_block.dbg_info_prev == null) { - // Append new Decl. - // TODO Look at the free list before appending at the end. - text_block.dbg_info_prev = last; - last.dbg_info_next = text_block; - self.dbg_info_decl_last = text_block; - - text_block.dbg_info_off = last.dbg_info_off + padToIdeal(last.dbg_info_len); - } - } else { - // This is the first Decl of the .debug_info - self.dbg_info_decl_first = text_block; - self.dbg_info_decl_last = text_block; - - text_block.dbg_info_off = padToIdeal(self.dbgInfoNeededHeaderBytes()); - } -} - -fn writeDeclDebugInfo(self: *DebugSymbols, text_block: *TextBlock, dbg_info_buf: []const u8) !void { - const tracy = trace(@src()); - defer tracy.end(); - - // This logic is nearly identical to the logic above in `updateDecl` for - // `SrcFn` and the line number programs. If you are editing this logic, you - // probably need to edit that logic too. - - const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment; - const debug_info_sect = &dwarf_segment.sections.items[self.debug_info_section_index.?]; - - const last_decl = self.dbg_info_decl_last.?; - // +1 for a trailing zero to end the children of the decl tag. - const needed_size = last_decl.dbg_info_off + last_decl.dbg_info_len + 1; - if (needed_size != debug_info_sect.size) { - if (needed_size > self.allocatedSize(debug_info_sect.offset)) { - const new_offset = self.findFreeSpace(needed_size, 1); - const existing_size = last_decl.dbg_info_off; - - log.debug("moving __debug_info section: {} bytes from 0x{x} to 0x{x}", .{ - existing_size, - debug_info_sect.offset, - new_offset, - }); - - try MachO.copyRangeAllOverlappingAlloc( - self.base.base.allocator, - self.file, - debug_info_sect.offset, - new_offset, - existing_size, - ); - - debug_info_sect.offset = @intCast(u32, new_offset); - debug_info_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; - } - debug_info_sect.size = needed_size; - self.load_commands_dirty = true; // TODO look into making only the one section dirty - self.debug_info_header_dirty = true; - } - const prev_padding_size: u32 = if (text_block.dbg_info_prev) |prev| - text_block.dbg_info_off - (prev.dbg_info_off + prev.dbg_info_len) - else - 0; - const next_padding_size: u32 = if (text_block.dbg_info_next) |next| - next.dbg_info_off - (text_block.dbg_info_off + text_block.dbg_info_len) - else - 0; - - // To end the children of the decl tag. - const trailing_zero = text_block.dbg_info_next == null; - - // We only have support for one compilation unit so far, so the offsets are directly - // from the .debug_info section. - const file_pos = debug_info_sect.offset + text_block.dbg_info_off; - try self.pwriteDbgInfoNops(prev_padding_size, dbg_info_buf, next_padding_size, trailing_zero, file_pos); -} - -fn getDebugLineProgramOff(self: DebugSymbols) u32 { - return self.dbg_line_fn_first.?.off; -} - -fn getDebugLineProgramEnd(self: DebugSymbols) u32 { - return self.dbg_line_fn_last.?.off + self.dbg_line_fn_last.?.len; -} - -/// TODO Improve this to use a table. -fn makeDebugString(self: *DebugSymbols, allocator: Allocator, bytes: []const u8) !u32 { - try self.debug_string_table.ensureUnusedCapacity(allocator, bytes.len + 1); - const result = self.debug_string_table.items.len; - self.debug_string_table.appendSliceAssumeCapacity(bytes); - self.debug_string_table.appendAssumeCapacity(0); - return @intCast(u32, result); -} - -/// The reloc offset for the line offset of a function from the previous function's line. -/// It's a fixed-size 4-byte ULEB128. -fn getRelocDbgLineOff() usize { - return dbg_line_vaddr_reloc_index + @sizeOf(u64) + 1; -} - -fn getRelocDbgFileIndex() usize { - return getRelocDbgLineOff() + 5; -} - -fn getRelocDbgInfoSubprogramHighPC() u32 { - return dbg_info_low_pc_reloc_index + @sizeOf(u64); -} - -fn dbgLineNeededHeaderBytes(self: DebugSymbols, module: *Module) u32 { - _ = self; - const directory_entry_format_count = 1; - const file_name_entry_format_count = 1; - const directory_count = 1; - const file_name_count = 1; - const root_src_dir_path_len = if (module.root_pkg.root_src_directory.path) |p| p.len else 1; // "." - return @intCast(u32, 53 + directory_entry_format_count * 2 + file_name_entry_format_count * 2 + - directory_count * 8 + file_name_count * 8 + - // These are encoded as DW.FORM.string rather than DW.FORM.strp as we would like - // because of a workaround for readelf and gdb failing to understand DWARFv5 correctly. - root_src_dir_path_len + - module.root_pkg.root_src_path.len); -} - -fn dbgInfoNeededHeaderBytes(self: DebugSymbols) u32 { - _ = self; - return 120; -} - -/// Writes to the file a buffer, prefixed and suffixed by the specified number of -/// bytes of NOPs. Asserts each padding size is at least `min_nop_size` and total padding bytes -/// are less than 126,976 bytes (if this limit is ever reached, this function can be -/// improved to make more than one pwritev call, or the limit can be raised by a fixed -/// amount by increasing the length of `vecs`). -fn pwriteDbgLineNops( - self: *DebugSymbols, - prev_padding_size: usize, - buf: []const u8, - next_padding_size: usize, - offset: u64, -) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const page_of_nops = [1]u8{DW.LNS.negate_stmt} ** 4096; - const three_byte_nop = [3]u8{ DW.LNS.advance_pc, 0b1000_0000, 0 }; - var vecs: [32]std.os.iovec_const = undefined; - var vec_index: usize = 0; - { - var padding_left = prev_padding_size; - if (padding_left % 2 != 0) { - vecs[vec_index] = .{ - .iov_base = &three_byte_nop, - .iov_len = three_byte_nop.len, - }; - vec_index += 1; - padding_left -= three_byte_nop.len; - } - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, - }; - vec_index += 1; - } - } - - vecs[vec_index] = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }; - vec_index += 1; - - { - var padding_left = next_padding_size; - if (padding_left % 2 != 0) { - vecs[vec_index] = .{ - .iov_base = &three_byte_nop, - .iov_len = three_byte_nop.len, - }; - vec_index += 1; - padding_left -= three_byte_nop.len; - } - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, - }; - vec_index += 1; - } - } - try self.file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); -} - -/// Writes to the file a buffer, prefixed and suffixed by the specified number of -/// bytes of padding. -fn pwriteDbgInfoNops( - self: *DebugSymbols, - prev_padding_size: usize, - buf: []const u8, - next_padding_size: usize, - trailing_zero: bool, - offset: u64, -) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const page_of_nops = [1]u8{abbrev_pad1} ** 4096; - var vecs: [32]std.os.iovec_const = undefined; - var vec_index: usize = 0; - { - var padding_left = prev_padding_size; - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, - }; - vec_index += 1; - } - } - - vecs[vec_index] = .{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }; - vec_index += 1; - - { - var padding_left = next_padding_size; - while (padding_left > page_of_nops.len) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = page_of_nops.len, - }; - vec_index += 1; - padding_left -= page_of_nops.len; - } - if (padding_left > 0) { - vecs[vec_index] = .{ - .iov_base = &page_of_nops, - .iov_len = padding_left, - }; - vec_index += 1; - } - } - - if (trailing_zero) { - var zbuf = [1]u8{0}; - vecs[vec_index] = .{ - .iov_base = &zbuf, - .iov_len = zbuf.len, - }; - vec_index += 1; - } - - try self.file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size); + const atom = &decl.link.macho; + return self.dwarf.commitDeclDebugInfo(&self.base.base, module, decl, symbol.n_value, atom.size, debug_buffers); } |
