const DebugSymbols = @This(); const std = @import("std"); const assert = std.debug.assert; const fs = std.fs; const log = std.log.scoped(.link); const macho = std.macho; const mem = std.mem; const DW = std.dwarf; const leb = std.leb; const Allocator = mem.Allocator; const build_options = @import("build_options"); const trace = @import("../../tracy.zig").trace; const Module = @import("../../Module.zig"); const Type = @import("../../type.zig").Type; const link = @import("../../link.zig"); const MachO = @import("../MachO.zig"); const SrcFn = MachO.SrcFn; const TextBlock = MachO.TextBlock; const satMul = MachO.satMul; const alloc_num = MachO.alloc_num; const alloc_den = MachO.alloc_den; const makeStaticString = MachO.makeStaticString; usingnamespace @import("commands.zig"); const page_size: u16 = 0x1000; base: *MachO, file: fs.File, /// Mach header header: ?macho.mach_header_64 = null, /// Table of all load commands load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, /// __PAGEZERO segment pagezero_segment_cmd_index: ?u16 = null, /// __TEXT segment text_segment_cmd_index: ?u16 = null, /// __DATA_CONST segment data_const_segment_cmd_index: ?u16 = null, /// __DATA segment data_segment_cmd_index: ?u16 = null, /// __LINKEDIT segment linkedit_segment_cmd_index: ?u16 = null, /// __DWARF segment dwarf_segment_cmd_index: ?u16 = null, /// Symbol table symtab_cmd_index: ?u16 = null, /// UUID load command uuid_cmd_index: ?u16 = null, /// Index into __TEXT,__text section. text_section_index: ?u16 = null, linkedit_off: u16 = page_size, linkedit_size: u16 = page_size, debug_info_section_index: ?u16 = null, debug_abbrev_section_index: ?u16 = null, 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) = .{}, header_dirty: bool = false, load_commands_dirty: bool = false, string_table_dirty: bool = false, debug_string_table_dirty: bool = false, debug_abbrev_section_dirty: bool = false, debug_aranges_section_dirty: bool = false, debug_info_header_dirty: bool = false, debug_line_header_dirty: bool = false, const abbrev_compile_unit = 1; const abbrev_subprogram = 2; const abbrev_subprogram_retvoid = 3; const abbrev_base_type = 4; const abbrev_pad1 = 5; const abbrev_parameter = 6; /// 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 { if (self.header == null) { const base_header = self.base.header.?; var header: macho.mach_header_64 = undefined; header.magic = macho.MH_MAGIC_64; header.cputype = base_header.cputype; header.cpusubtype = base_header.cpusubtype; header.filetype = macho.MH_DSYM; // These will get populated at the end of flushing the results to file. header.ncmds = 0; header.sizeofcmds = 0; header.flags = 0; header.reserved = 0; self.header = header; self.header_dirty = true; } if (self.uuid_cmd_index == null) { const base_cmd = self.base.load_commands.items[self.base.uuid_cmd_index.?]; self.uuid_cmd_index = @intCast(u16, self.load_commands.items.len); try self.load_commands.append(allocator, base_cmd); self.header_dirty = true; self.load_commands_dirty = true; } if (self.symtab_cmd_index == null) { self.symtab_cmd_index = @intCast(u16, self.load_commands.items.len); const base_cmd = self.base.load_commands.items[self.base.symtab_cmd_index.?].Symtab; const symtab_size = base_cmd.nsyms * @sizeOf(macho.nlist_64); const symtab_off = self.findFreeSpaceLinkedit(symtab_size, @sizeOf(macho.nlist_64)); log.debug("found dSym symbol table free space 0x{x} to 0x{x}", .{ symtab_off, symtab_off + symtab_size }); const strtab_off = self.findFreeSpaceLinkedit(base_cmd.strsize, 1); log.debug("found dSym string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + base_cmd.strsize }); try self.load_commands.append(allocator, .{ .Symtab = .{ .cmd = macho.LC_SYMTAB, .cmdsize = @sizeOf(macho.symtab_command), .symoff = @intCast(u32, symtab_off), .nsyms = base_cmd.nsyms, .stroff = @intCast(u32, strtab_off), .strsize = base_cmd.strsize, }, }); self.header_dirty = true; self.load_commands_dirty = true; self.string_table_dirty = true; } if (self.pagezero_segment_cmd_index == null) { self.pagezero_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const base_cmd = self.base.load_commands.items[self.base.pagezero_segment_cmd_index.?].Segment; const cmd = try self.copySegmentCommand(allocator, base_cmd); try self.load_commands.append(allocator, .{ .Segment = cmd }); self.header_dirty = true; self.load_commands_dirty = true; } if (self.text_segment_cmd_index == null) { self.text_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const base_cmd = self.base.load_commands.items[self.base.text_segment_cmd_index.?].Segment; const cmd = try self.copySegmentCommand(allocator, base_cmd); try self.load_commands.append(allocator, .{ .Segment = cmd }); self.header_dirty = true; self.load_commands_dirty = true; } if (self.data_const_segment_cmd_index == null) outer: { if (self.base.data_const_segment_cmd_index == null) break :outer; // __DATA_CONST is optional self.data_const_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const base_cmd = self.base.load_commands.items[self.base.data_const_segment_cmd_index.?].Segment; const cmd = try self.copySegmentCommand(allocator, base_cmd); try self.load_commands.append(allocator, .{ .Segment = cmd }); self.header_dirty = true; self.load_commands_dirty = true; } if (self.data_segment_cmd_index == null) outer: { if (self.base.data_segment_cmd_index == null) break :outer; // __DATA is optional self.data_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const base_cmd = self.base.load_commands.items[self.base.data_segment_cmd_index.?].Segment; const cmd = try self.copySegmentCommand(allocator, base_cmd); try self.load_commands.append(allocator, .{ .Segment = cmd }); self.header_dirty = true; self.load_commands_dirty = true; } if (self.linkedit_segment_cmd_index == null) { self.linkedit_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const base_cmd = self.base.load_commands.items[self.base.linkedit_segment_cmd_index.?].Segment; var cmd = try self.copySegmentCommand(allocator, base_cmd); cmd.inner.vmsize = self.linkedit_size; cmd.inner.fileoff = self.linkedit_off; cmd.inner.filesize = self.linkedit_size; try self.load_commands.append(allocator, .{ .Segment = cmd }); self.header_dirty = true; self.load_commands_dirty = true; } if (self.dwarf_segment_cmd_index == null) { self.dwarf_segment_cmd_index = @intCast(u16, self.load_commands.items.len); const linkedit = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const ideal_size: u16 = 200 + 128 + 160 + 250; const needed_size = mem.alignForwardGeneric(u64, satMul(ideal_size, alloc_num) / alloc_den, page_size); const off = linkedit.inner.fileoff + linkedit.inner.filesize; const vmaddr = linkedit.inner.vmaddr + linkedit.inner.vmsize; log.debug("found dSym __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size }); try self.load_commands.append(allocator, .{ .Segment = SegmentCommand.empty(.{ .cmd = macho.LC_SEGMENT_64, .cmdsize = @sizeOf(macho.segment_command_64), .segname = makeStaticString("__DWARF"), .vmaddr = vmaddr, .vmsize = needed_size, .fileoff = off, .filesize = needed_size, .maxprot = 0, .initprot = 0, .nsects = 0, .flags = 0, }), }); self.header_dirty = true; self.load_commands_dirty = true; } if (self.debug_str_section_index == null) { const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; self.debug_str_section_index = @intCast(u16, dwarf_segment.sections.items.len); assert(self.debug_string_table.items.len == 0); try dwarf_segment.addSection(allocator, .{ .sectname = makeStaticString("__debug_str"), .segname = makeStaticString("__DWARF"), .addr = dwarf_segment.inner.vmaddr, .size = @intCast(u32, self.debug_string_table.items.len), .offset = @intCast(u32, dwarf_segment.inner.fileoff), .@"align" = 1, .reloff = 0, .nreloc = 0, .flags = macho.S_REGULAR, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, }); self.header_dirty = true; self.load_commands_dirty = true; self.debug_string_table_dirty = true; } if (self.debug_info_section_index == null) { const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; self.debug_info_section_index = @intCast(u16, dwarf_segment.sections.items.len); const file_size_hint = 200; const p_align = 1; const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null); log.debug("found dSym __debug_info free space 0x{x} to 0x{x}", .{ off, off + file_size_hint }); try dwarf_segment.addSection(allocator, .{ .sectname = makeStaticString("__debug_info"), .segname = makeStaticString("__DWARF"), .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff, .size = file_size_hint, .offset = @intCast(u32, off), .@"align" = p_align, .reloff = 0, .nreloc = 0, .flags = macho.S_REGULAR, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, }); self.header_dirty = true; self.load_commands_dirty = true; self.debug_info_header_dirty = true; } if (self.debug_abbrev_section_index == null) { const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; self.debug_abbrev_section_index = @intCast(u16, dwarf_segment.sections.items.len); const file_size_hint = 128; const p_align = 1; const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null); log.debug("found dSym __debug_abbrev free space 0x{x} to 0x{x}", .{ off, off + file_size_hint }); try dwarf_segment.addSection(allocator, .{ .sectname = makeStaticString("__debug_abbrev"), .segname = makeStaticString("__DWARF"), .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff, .size = file_size_hint, .offset = @intCast(u32, off), .@"align" = p_align, .reloff = 0, .nreloc = 0, .flags = macho.S_REGULAR, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, }); self.header_dirty = true; self.load_commands_dirty = true; self.debug_abbrev_section_dirty = true; } if (self.debug_aranges_section_index == null) { const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; self.debug_aranges_section_index = @intCast(u16, dwarf_segment.sections.items.len); const file_size_hint = 160; const p_align = 16; const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null); log.debug("found dSym __debug_aranges free space 0x{x} to 0x{x}", .{ off, off + file_size_hint }); try dwarf_segment.addSection(allocator, .{ .sectname = makeStaticString("__debug_aranges"), .segname = makeStaticString("__DWARF"), .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff, .size = file_size_hint, .offset = @intCast(u32, off), .@"align" = p_align, .reloff = 0, .nreloc = 0, .flags = macho.S_REGULAR, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, }); self.header_dirty = true; self.load_commands_dirty = true; self.debug_aranges_section_dirty = true; } if (self.debug_line_section_index == null) { const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; self.debug_line_section_index = @intCast(u16, dwarf_segment.sections.items.len); const file_size_hint = 250; const p_align = 1; const off = dwarf_segment.findFreeSpace(file_size_hint, p_align, null); log.debug("found dSym __debug_line free space 0x{x} to 0x{x}", .{ off, off + file_size_hint }); try dwarf_segment.addSection(allocator, .{ .sectname = makeStaticString("__debug_line"), .segname = makeStaticString("__DWARF"), .addr = dwarf_segment.inner.vmaddr + off - dwarf_segment.inner.fileoff, .size = file_size_hint, .offset = @intCast(u32, off), .@"align" = p_align, .reloff = 0, .nreloc = 0, .flags = macho.S_REGULAR, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, }); self.header_dirty = true; self.load_commands_dirty = true; self.debug_line_header_dirty = true; } } pub fn flushModule(self: *DebugSymbols, allocator: *Allocator, options: link.Options) !void { // 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, // 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, // start VM address DW.AT_high_pc, DW.FORM_data4, DW.AT_type, DW.FORM_ref4, DW.AT_name, DW.FORM_string, DW.AT_decl_line, DW.FORM_data4, DW.AT_decl_file, DW.FORM_data1, 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, DW.AT_decl_line, DW.FORM_data4, DW.AT_decl_file, DW.FORM_data1, 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_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 = dwarf_segment.allocatedSize(debug_abbrev_sect.offset); if (needed_size > allocated_size) { debug_abbrev_sect.size = 0; // free the space const offset = dwarf_segment.findFreeSpace(needed_size, 1, null); 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); 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.?]; var di_buf = std.ArrayList(u8).init(allocator); defer di_buf.deinit(); // 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`. try di_buf.ensureCapacity(self.dbgInfoNeededHeaderBytes()); // 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); // 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); 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.?]; const debug_info_sect = dwarf_segment.sections.items[self.debug_info_section_index.?]; var di_buf = std.ArrayList(u8).init(allocator); defer di_buf.deinit(); // Enough for all the data without resizing. When support for more compilation units // is added, the size of this section will become more variable. try di_buf.ensureCapacity(100); // 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(u32, di_buf.addManyAsArrayAssumeCapacity(4), 0); mem.writeIntLittle(u32, di_buf.addManyAsArrayAssumeCapacity(4), 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 = dwarf_segment.allocatedSize(debug_aranges_sect.offset); if (needed_size > allocated_size) { debug_aranges_sect.size = 0; // free the space const new_offset = dwarf_segment.findFreeSpace(needed_size, 16, null); 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); 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.?]; var di_buf = std.ArrayList(u8).init(allocator); defer di_buf.deinit(); // 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`. try di_buf.ensureCapacity(self.dbgLineNeededHeaderBytes(module)); // 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); 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) { const allocated_size = dwarf_segment.allocatedSize(debug_strtab_sect.offset); const needed_size = self.debug_string_table.items.len; if (needed_size > allocated_size) { debug_strtab_sect.size = 0; // free the space const new_offset = dwarf_segment.findFreeSpace(needed_size, 1, null); debug_strtab_sect.addr = dwarf_segment.inner.vmaddr + new_offset - dwarf_segment.inner.fileoff; debug_strtab_sect.offset = @intCast(u32, new_offset); } debug_strtab_sect.size = @intCast(u32, needed_size); log.debug("__debug_strtab start=0x{x} end=0x{x}", .{ debug_strtab_sect.offset, debug_strtab_sect.offset + needed_size, }); try self.file.pwriteAll(self.debug_string_table.items, debug_strtab_sect.offset); self.load_commands_dirty = true; self.debug_string_table_dirty = false; } } try self.writeStringTable(); self.updateDwarfSegment(); try self.writeLoadCommands(allocator); try self.writeHeader(); assert(!self.header_dirty); assert(!self.load_commands_dirty); assert(!self.string_table_dirty); assert(!self.debug_abbrev_section_dirty); assert(!self.debug_aranges_section_dirty); assert(!self.debug_string_table_dirty); } 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.file.close(); } fn copySegmentCommand(self: *DebugSymbols, allocator: *Allocator, base_cmd: SegmentCommand) !SegmentCommand { var cmd = SegmentCommand.empty(.{ .cmd = macho.LC_SEGMENT_64, .cmdsize = base_cmd.inner.cmdsize, .segname = undefined, .vmaddr = base_cmd.inner.vmaddr, .vmsize = base_cmd.inner.vmsize, .fileoff = 0, .filesize = 0, .maxprot = base_cmd.inner.maxprot, .initprot = base_cmd.inner.initprot, .nsects = base_cmd.inner.nsects, .flags = base_cmd.inner.flags, }); mem.copy(u8, &cmd.inner.segname, &base_cmd.inner.segname); try cmd.sections.ensureCapacity(allocator, cmd.inner.nsects); for (base_cmd.sections.items) |base_sect, i| { var sect = macho.section_64{ .sectname = undefined, .segname = undefined, .addr = base_sect.addr, .size = base_sect.size, .offset = 0, .@"align" = base_sect.@"align", .reloff = 0, .nreloc = 0, .flags = base_sect.flags, .reserved1 = base_sect.reserved1, .reserved2 = base_sect.reserved2, .reserved3 = base_sect.reserved3, }; mem.copy(u8, §.sectname, &base_sect.sectname); mem.copy(u8, §.segname, &base_sect.segname); if (self.base.text_section_index.? == i) { self.text_section_index = @intCast(u16, i); } cmd.sections.appendAssumeCapacity(sect); } return cmd; } fn updateDwarfSegment(self: *DebugSymbols) void { const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].Segment; var file_size: u64 = 0; for (dwarf_segment.sections.items) |sect| { file_size += sect.size; } if (file_size != dwarf_segment.inner.filesize) { dwarf_segment.inner.filesize = file_size; if (dwarf_segment.inner.vmsize < dwarf_segment.inner.filesize) { dwarf_segment.inner.vmsize = mem.alignForwardGeneric(u64, dwarf_segment.inner.filesize, page_size); } self.load_commands_dirty = true; } } /// Writes all load commands and section headers. fn writeLoadCommands(self: *DebugSymbols, allocator: *Allocator) !void { if (!self.load_commands_dirty) return; var sizeofcmds: u32 = 0; for (self.load_commands.items) |lc| { sizeofcmds += lc.cmdsize(); } var buffer = try allocator.alloc(u8, sizeofcmds); defer allocator.free(buffer); var writer = std.io.fixedBufferStream(buffer).writer(); for (self.load_commands.items) |lc| { try lc.write(writer); } const off = @sizeOf(macho.mach_header_64); log.debug("writing {} dSym load commands from 0x{x} to 0x{x}", .{ self.load_commands.items.len, off, off + sizeofcmds }); try self.file.pwriteAll(buffer, off); self.load_commands_dirty = false; } fn writeHeader(self: *DebugSymbols) !void { if (!self.header_dirty) return; self.header.?.ncmds = @intCast(u32, self.load_commands.items.len); var sizeofcmds: u32 = 0; for (self.load_commands.items) |cmd| { sizeofcmds += cmd.cmdsize(); } self.header.?.sizeofcmds = sizeofcmds; log.debug("writing Mach-O dSym header {}", .{self.header.?}); try self.file.pwriteAll(mem.asBytes(&self.header.?), 0); self.header_dirty = false; } fn allocatedSizeLinkedit(self: *DebugSymbols, start: u64) u64 { assert(start > 0); var min_pos: u64 = std.math.maxInt(u64); if (self.symtab_cmd_index) |idx| { const symtab = self.load_commands.items[idx].Symtab; if (symtab.symoff >= start and symtab.symoff < min_pos) min_pos = symtab.symoff; if (symtab.stroff >= start and symtab.stroff < min_pos) min_pos = symtab.stroff; } return min_pos - start; } fn detectAllocCollisionLinkedit(self: *DebugSymbols, start: u64, size: u64) ?u64 { const end = start + satMul(size, alloc_num) / alloc_den; if (self.symtab_cmd_index) |idx| outer: { if (self.load_commands.items.len == idx) break :outer; const symtab = self.load_commands.items[idx].Symtab; { // Symbol table const symsize = symtab.nsyms * @sizeOf(macho.nlist_64); const increased_size = satMul(symsize, alloc_num) / alloc_den; const test_end = symtab.symoff + increased_size; if (end > symtab.symoff and start < test_end) { return test_end; } } { // String table const increased_size = satMul(symtab.strsize, alloc_num) / alloc_den; const test_end = symtab.stroff + increased_size; if (end > symtab.stroff and start < test_end) { return test_end; } } } return null; } fn findFreeSpaceLinkedit(self: *DebugSymbols, object_size: u64, min_alignment: u16) u64 { var start: u64 = self.linkedit_off; while (self.detectAllocCollisionLinkedit(start, object_size)) |item_end| { start = mem.alignForwardGeneric(u64, item_end, min_alignment); } return start; } fn relocateSymbolTable(self: *DebugSymbols) !void { const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const nlocals = self.base.local_symbols.items.len; const nglobals = self.base.global_symbols.items.len; const nsyms = nlocals + nglobals; if (symtab.nsyms < nsyms) { const linkedit_segment = self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; const needed_size = nsyms * @sizeOf(macho.nlist_64); if (needed_size > self.allocatedSizeLinkedit(symtab.symoff)) { // Move the entire symbol table to a new location const new_symoff = self.findFreeSpaceLinkedit(needed_size, @alignOf(macho.nlist_64)); const existing_size = symtab.nsyms * @sizeOf(macho.nlist_64); assert(new_symoff + existing_size <= self.linkedit_off + self.linkedit_size); // TODO expand LINKEDIT segment. log.debug("relocating dSym symbol table from 0x{x}-0x{x} to 0x{x}-0x{x}", .{ symtab.symoff, symtab.symoff + existing_size, new_symoff, new_symoff + existing_size, }); const amt = try self.file.copyRangeAll(symtab.symoff, self.file, new_symoff, existing_size); if (amt != existing_size) return error.InputOutput; symtab.symoff = @intCast(u32, new_symoff); } symtab.nsyms = @intCast(u32, nsyms); self.load_commands_dirty = true; } } pub fn writeLocalSymbol(self: *DebugSymbols, index: usize) !void { const tracy = trace(@src()); defer tracy.end(); try self.relocateSymbolTable(); const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const off = symtab.symoff + @sizeOf(macho.nlist_64) * index; log.debug("writing dSym local symbol {} at 0x{x}", .{ index, off }); try self.file.pwriteAll(mem.asBytes(&self.base.local_symbols.items[index]), off); } fn writeStringTable(self: *DebugSymbols) !void { if (!self.string_table_dirty) return; const tracy = trace(@src()); defer tracy.end(); const symtab = &self.load_commands.items[self.symtab_cmd_index.?].Symtab; const allocated_size = self.allocatedSizeLinkedit(symtab.stroff); const needed_size = mem.alignForwardGeneric(u64, self.base.string_table.items.len, @alignOf(u64)); if (needed_size > allocated_size) { symtab.strsize = 0; symtab.stroff = @intCast(u32, self.findFreeSpaceLinkedit(needed_size, 1)); } symtab.strsize = @intCast(u32, needed_size); log.debug("writing dSym string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); try self.file.pwriteAll(self.base.string_table.items, symtab.stroff); self.load_commands_dirty = true; self.string_table_dirty = false; } pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const Module.Decl) !void { const tracy = trace(@src()); defer tracy.end(); const tree = decl.container.file_scope.contents.tree; const file_ast_decls = tree.root_node.decls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; const block = fn_proto.getBodyNode().?.castTag(.Block).?; const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); const casted_line_off = @intCast(u28, line_delta); 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, casted_line_off); try self.file.pwriteAll(&data, file_pos); } 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 { 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 = .{}; const typed_value = decl.typed_value.most_recent.typed_value; switch (typed_value.ty.zigTypeTag()) { .Fn => { // For functions we need to add a prologue to the debug line program. try dbg_line_buffer.ensureCapacity(26); const line_off: u28 = blk: { const tree = decl.container.file_scope.contents.tree; const file_ast_decls = tree.root_node.decls(); // TODO Look into improving the performance here by adding a token-index-to-line // lookup table. Currently this involves scanning over the source code for newlines. const fn_proto = file_ast_decls[decl.src_index].castTag(.FnProto).?; const block = fn_proto.getBodyNode().?.castTag(.Block).?; const line_delta = std.zig.lineDelta(tree.source, 0, tree.token_locs[block.lbrace].start); break :blk @intCast(u28, line_delta); }; 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_off); 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.lenZ(decl.name) + 1]; try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 27 + decl_name_with_null.len); const fn_ret_type = typed_value.ty.fnReturnType(); const fn_ret_has_bits = fn_ret_type.hasCodeGenBits(); 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.entry.value = .{ .off = undefined, .relocs = .{}, }; } try gop.entry.value.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 mem.writeIntLittle(u32, dbg_info_buffer.addManyAsArrayAssumeCapacity(4), line_off + 1); // DW.AT_decl_line, DW.FORM_data4 dbg_info_buffer.appendAssumeCapacity(file_index); // DW.AT_decl_file, DW.FORM_data1 }, 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: *DebugSymbols, allocator: *Allocator, module: *Module, decl: *Module.Decl, debug_buffers: *DeclDebugBuffers, target: std.Target, ) !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.local_symbols.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. const typed_value = decl.typed_value.most_recent.typed_value; switch (typed_value.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 + (last.len * alloc_num / alloc_den); } } 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 + (last.len * alloc_num / alloc_den); } } 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 = self.dbgLineNeededHeaderBytes(module) * alloc_num / alloc_den; } 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 > dwarf_segment.allocatedSize(debug_line_sect.offset)) { const new_offset = dwarf_segment.findFreeSpace(needed_size, 1, null); 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, }); const amt = try self.file.copyRangeAll(debug_line_sect.offset, self.file, new_offset, existing_size); if (amt != existing_size) return error.InputOutput; 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 => {}, } // 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 = dbg_info_type_relocs.iterator(); while (it.next()) |entry| { entry.value.off = @intCast(u32, dbg_info_buffer.items.len); try self.addDbgInfoType(entry.key, dbg_info_buffer, target); } 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. it = dbg_info_type_relocs.iterator(); while (it.next()) |entry| { for (entry.value.relocs.items) |off| { mem.writeIntLittle( u32, dbg_info_buffer.items[off..][0..4], text_block.dbg_info_off + entry.value.off, ); } } try self.writeDeclDebugInfo(text_block, dbg_info_buffer.items); } /// Asserts the type has codegen bits. fn addDbgInfoType( self: *DebugSymbols, ty: Type, dbg_info_buffer: *std.ArrayList(u8), target: std.Target, ) !void { switch (ty.zigTypeTag()) { .Void => unreachable, .NoReturn => unreachable, .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.ensureCapacity(dbg_info_buffer.items.len + 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}); }, else => { std.log.scoped(.compiler).err("TODO implement .debug_info for type '{}'", .{ty}); try dbg_info_buffer.append(abbrev_pad1); }, } } 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 + (last.dbg_info_len * alloc_num / alloc_den); } } 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 + (last.dbg_info_len * alloc_num / alloc_den); } } 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 = self.dbgInfoNeededHeaderBytes() * alloc_num / alloc_den; } } 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 > dwarf_segment.allocatedSize(debug_info_sect.offset)) { const new_offset = dwarf_segment.findFreeSpace(needed_size, 1, null); 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, }); const amt = try self.file.copyRangeAll(debug_info_sect.offset, self.file, new_offset, existing_size); if (amt != existing_size) return error.InputOutput; 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.ensureCapacity(allocator, self.debug_string_table.items.len + 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 { 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 { 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); }