diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2022-03-06 18:35:58 +0100 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2022-03-08 09:46:27 +0100 |
| commit | ba17552b4eb8def495053013eebbe39fc324c8ae (patch) | |
| tree | 33f871441f432bfdee7f6f555b95062be565ded8 /src/link/Dwarf.zig | |
| parent | 38c161afabe1dc7d0df7ad981d70b962ef87120a (diff) | |
| download | zig-ba17552b4eb8def495053013eebbe39fc324c8ae.tar.gz zig-ba17552b4eb8def495053013eebbe39fc324c8ae.zip | |
dwarf: move all dwarf into standalone module
Hook up Elf and MachO linkers to the new solution.
Diffstat (limited to 'src/link/Dwarf.zig')
| -rw-r--r-- | src/link/Dwarf.zig | 1625 |
1 files changed, 1625 insertions, 0 deletions
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)); +} |
