macho: re-enable creating dSYM bundle

* update number of type abbrevs to match Elf linker
* update `DebugSymbols` to write symbol and string tables
  at the end to match the `MachO` linker
* TODO: update segment vm addresses when growing segments in
  the binary
* TODO: store DWARF relocations in linker's interned arena
This commit is contained in:
Jakub Konka 2022-02-12 18:41:16 +01:00
parent 04f3d93017
commit 27cfbf949a
2 changed files with 370 additions and 235 deletions

View File

@ -354,32 +354,31 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO {
return self;
}
// TODO Migrate DebugSymbols to the merged linker codepaths
// if (!options.strip and options.module != null) {
// // Create dSYM bundle.
// const dir = options.module.?.zig_cache_artifact_directory;
// log.debug("creating {s}.dSYM bundle in {s}", .{ sub_path, dir.path });
if (!options.strip and options.module != null) {
// Create dSYM bundle.
const dir = options.module.?.zig_cache_artifact_directory;
log.debug("creating {s}.dSYM bundle in {s}", .{ emit.sub_path, dir.path });
// const d_sym_path = try fmt.allocPrint(
// allocator,
// "{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF",
// .{sub_path},
// );
// defer allocator.free(d_sym_path);
const d_sym_path = try fmt.allocPrint(
allocator,
"{s}.dSYM" ++ fs.path.sep_str ++ "Contents" ++ fs.path.sep_str ++ "Resources" ++ fs.path.sep_str ++ "DWARF",
.{emit.sub_path},
);
defer allocator.free(d_sym_path);
// var d_sym_bundle = try dir.handle.makeOpenPath(d_sym_path, .{});
// defer d_sym_bundle.close();
var d_sym_bundle = try dir.handle.makeOpenPath(d_sym_path, .{});
defer d_sym_bundle.close();
// const d_sym_file = try d_sym_bundle.createFile(sub_path, .{
// .truncate = false,
// .read = true,
// });
const d_sym_file = try d_sym_bundle.createFile(emit.sub_path, .{
.truncate = false,
.read = true,
});
// self.d_sym = .{
// .base = self,
// .file = d_sym_file,
// };
// }
self.d_sym = .{
.base = self,
.file = d_sym_file,
};
}
// Index 0 is always a null symbol.
try self.locals.append(allocator, .{
@ -393,8 +392,8 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO {
try self.populateMissingMetadata();
if (self.d_sym) |*ds| {
try ds.populateMissingMetadata(allocator);
if (self.d_sym) |*d_sym| {
try d_sym.populateMissingMetadata(allocator);
}
return self;
@ -1048,9 +1047,9 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
try self.updateSectionOrdinals();
try self.writeLinkeditSegment();
if (self.d_sym) |*ds| {
if (self.d_sym) |*d_sym| {
// Flush debug symbols bundle.
try ds.flushModule(self.base.allocator, self.base.options);
try d_sym.flushModule(self.base.allocator, self.base.options);
}
if (self.requires_adhoc_codesig) {
@ -3374,8 +3373,8 @@ pub fn deinit(self: *MachO) void {
if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
}
if (self.d_sym) |*ds| {
ds.deinit(self.base.allocator);
if (self.d_sym) |*d_sym| {
d_sym.deinit(self.base.allocator);
}
self.section_ordinals.deinit(self.base.allocator);
@ -3497,13 +3496,13 @@ fn freeAtom(self: *MachO, atom: *Atom, match: MatchingSection, owns_atom: bool)
}
}
if (self.d_sym) |*ds| {
if (ds.dbg_info_decl_first == atom) {
ds.dbg_info_decl_first = atom.dbg_info_next;
if (self.d_sym) |*d_sym| {
if (d_sym.dbg_info_decl_first == atom) {
d_sym.dbg_info_decl_first = atom.dbg_info_next;
}
if (ds.dbg_info_decl_last == atom) {
if (d_sym.dbg_info_decl_last == atom) {
// TODO shrink the .debug_info section size here
ds.dbg_info_decl_last = atom.dbg_info_prev;
d_sym.dbg_info_decl_last = atom.dbg_info_prev;
}
}
@ -3675,6 +3674,7 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
const decl = func.owner_decl;
self.freeUnnamedConsts(decl);
// TODO clearing the code and relocs buffer should probably be orchestrated
// in a different, smarter, more automatic way somewhere else, in a more centralised
// way than this.
@ -3686,8 +3686,8 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
defer code_buffer.deinit();
var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined;
const debug_buffers = if (self.d_sym) |*ds| blk: {
debug_buffers_buf = try ds.initDeclDebugBuffers(self.base.allocator, module, decl);
const debug_buffers = if (self.d_sym) |*d_sym| blk: {
debug_buffers_buf = try d_sym.initDeclDebugBuffers(self.base.allocator, module, decl);
break :blk &debug_buffers_buf;
} else null;
defer {
@ -3725,13 +3725,9 @@ pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liv
_ = try self.placeDecl(decl, decl.link.macho.code.items.len);
if (debug_buffers) |db| {
try self.d_sym.?.commitDeclDebugInfo(
self.base.allocator,
module,
decl,
db,
self.base.options.target,
);
if (self.d_sym) |*d_sym| {
try d_sym.commitDeclDebugInfo(self.base.allocator, module, decl, db);
}
}
// Since we updated the vaddr and the size, each corresponding export symbol also
@ -3827,8 +3823,8 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
defer code_buffer.deinit();
var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined;
const debug_buffers = if (self.d_sym) |*ds| blk: {
debug_buffers_buf = try ds.initDeclDebugBuffers(self.base.allocator, module, decl);
const debug_buffers = if (self.d_sym) |*d_sym| blk: {
debug_buffers_buf = try d_sym.initDeclDebugBuffers(self.base.allocator, module, decl);
break :blk &debug_buffers_buf;
} else null;
defer {
@ -4125,8 +4121,8 @@ fn placeDecl(self: *MachO, decl: *Module.Decl, code_len: usize) !*macho.nlist_64
}
pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {
if (self.d_sym) |*ds| {
try ds.updateDeclLineNumber(module, decl);
if (self.d_sym) |*d_sym| {
try d_sym.updateDeclLineNumber(module, decl);
}
}
@ -4322,27 +4318,27 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {
_ = self.atom_by_index_table.remove(decl.link.macho.local_sym_index);
decl.link.macho.local_sym_index = 0;
}
if (self.d_sym) |*ds| {
if (self.d_sym) |*d_sym| {
// TODO make this logic match freeAtom. Maybe abstract the logic
// out since the same thing is desired for both.
_ = ds.dbg_line_fn_free_list.remove(&decl.fn_link.macho);
_ = d_sym.dbg_line_fn_free_list.remove(&decl.fn_link.macho);
if (decl.fn_link.macho.prev) |prev| {
ds.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {};
d_sym.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {};
prev.next = decl.fn_link.macho.next;
if (decl.fn_link.macho.next) |next| {
next.prev = prev;
} else {
ds.dbg_line_fn_last = prev;
d_sym.dbg_line_fn_last = prev;
}
} else if (decl.fn_link.macho.next) |next| {
ds.dbg_line_fn_first = next;
d_sym.dbg_line_fn_first = next;
next.prev = null;
}
if (ds.dbg_line_fn_first == &decl.fn_link.macho) {
ds.dbg_line_fn_first = decl.fn_link.macho.next;
if (d_sym.dbg_line_fn_first == &decl.fn_link.macho) {
d_sym.dbg_line_fn_first = decl.fn_link.macho.next;
}
if (ds.dbg_line_fn_last == &decl.fn_link.macho) {
ds.dbg_line_fn_last = decl.fn_link.macho.prev;
if (d_sym.dbg_line_fn_last == &decl.fn_link.macho) {
d_sym.dbg_line_fn_last = decl.fn_link.macho.prev;
}
}
}

View File

@ -3,7 +3,8 @@ const DebugSymbols = @This();
const std = @import("std");
const assert = std.debug.assert;
const fs = std.fs;
const log = std.log.scoped(.dsym);
const log = std.log.scoped(.link);
const leb128 = std.leb;
const macho = std.macho;
const math = std.math;
const mem = std.mem;
@ -22,8 +23,6 @@ const SrcFn = MachO.SrcFn;
const makeStaticString = MachO.makeStaticString;
const padToIdeal = MachO.padToIdeal;
const page_size: u16 = 0x1000;
base: *MachO,
file: fs.File,
@ -49,9 +48,6 @@ 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,
@ -76,7 +72,6 @@ dbg_info_decl_last: ?*TextBlock = null,
debug_string_table: std.ArrayListUnmanaged(u8) = .{},
load_commands_dirty: bool = false,
strtab_dirty: bool = false,
debug_string_table_dirty: bool = false,
debug_abbrev_section_dirty: bool = false,
debug_aranges_section_dirty: bool = false,
@ -87,8 +82,12 @@ 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;
const abbrev_ptr_type = 5;
const abbrev_struct_type = 6;
const abbrev_anon_struct_type = 7;
const abbrev_struct_member = 8;
const abbrev_pad1 = 9;
const abbrev_parameter = 10;
/// The reloc offset for the virtual address of a function in its Line Number Program.
/// Size is a virtual address integer.
@ -108,30 +107,21 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
try self.load_commands.append(allocator, base_cmd);
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 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 string table free space 0x{x} to 0x{x}", .{ strtab_off, strtab_off + base_cmd.strsize });
try self.load_commands.append(allocator, .{
try self.load_commands.append(self.base.base.allocator, .{
.symtab = .{
.cmdsize = @sizeOf(macho.symtab_command),
.symoff = @intCast(u32, symtab_off),
.nsyms = base_cmd.nsyms,
.stroff = @intCast(u32, strtab_off),
.strsize = base_cmd.strsize,
.symoff = 0,
.nsyms = 0,
.stroff = 0,
.strsize = 0,
},
});
self.load_commands_dirty = true;
self.strtab_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;
@ -139,6 +129,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
try self.load_commands.append(allocator, .{ .segment = cmd });
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;
@ -146,6 +137,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
try self.load_commands.append(allocator, .{ .segment = cmd });
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);
@ -154,6 +146,7 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
try self.load_commands.append(allocator, .{ .segment = cmd });
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);
@ -162,26 +155,29 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
try self.load_commands.append(allocator, .{ .segment = cmd });
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;
// TODO this needs reworking
cmd.inner.vmsize = self.base.page_size;
cmd.inner.fileoff = self.base.page_size;
cmd.inner.filesize = self.base.page_size;
try self.load_commands.append(allocator, .{ .segment = cmd });
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, padToIdeal(ideal_size), page_size);
const off = linkedit.inner.fileoff + linkedit.inner.filesize;
const needed_size = mem.alignForwardGeneric(u64, padToIdeal(ideal_size), self.base.page_size);
const fileoff = linkedit.inner.fileoff + linkedit.inner.filesize;
const vmaddr = linkedit.inner.vmaddr + linkedit.inner.vmsize;
log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ off, off + needed_size });
log.debug("found __DWARF segment free space 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size });
try self.load_commands.append(allocator, .{
.segment = .{
@ -189,13 +185,14 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
.segname = makeStaticString("__DWARF"),
.vmaddr = vmaddr,
.vmsize = needed_size,
.fileoff = off,
.fileoff = fileoff,
.filesize = needed_size,
},
},
});
self.load_commands_dirty = true;
}
if (self.debug_str_section_index == null) {
assert(self.debug_string_table.items.len == 0);
self.debug_str_section_index = try self.allocateSection(
@ -205,18 +202,22 @@ pub fn populateMissingMetadata(self: *DebugSymbols, allocator: Allocator) !void
);
self.debug_string_table_dirty = true;
}
if (self.debug_info_section_index == null) {
self.debug_info_section_index = try self.allocateSection("__debug_info", 200, 0);
self.debug_info_header_dirty = true;
}
if (self.debug_abbrev_section_index == null) {
self.debug_abbrev_section_index = try self.allocateSection("__debug_abbrev", 128, 0);
self.debug_abbrev_section_dirty = true;
}
if (self.debug_aranges_section_index == null) {
self.debug_aranges_section_index = try self.allocateSection("__debug_aranges", 160, 4);
self.debug_aranges_section_dirty = true;
}
if (self.debug_line_section_index == null) {
self.debug_line_section_index = try self.allocateSection("__debug_line", 250, 0);
self.debug_line_header_dirty = true;
@ -300,41 +301,91 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
// 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,
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,
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
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_anon_struct_type,
DW.TAG.structure_type,
DW.CHILDREN.yes, // header
DW.AT.byte_size,
DW.FORM.sdata,
0,
0, // table sentinel
abbrev_struct_member,
DW.TAG.member,
DW.CHILDREN.no, // header
DW.AT.name,
DW.FORM.string,
DW.AT.type,
DW.FORM.ref4,
DW.AT.data_member_location,
DW.FORM.sdata,
0,
0, // table sentinel
abbrev_pad1,
DW.TAG.unspecified_type,
DW.CHILDREN.no, // header
0,
0, // table sentinel
abbrev_parameter,
DW.TAG.formal_parameter, DW.CHILDREN.no, // header
DW.AT.location, DW.FORM.exprloc,
DW.AT.type, DW.FORM.ref4,
DW.AT.name, DW.FORM.string,
0,
0, // table sentinel
0,
0,
0, // section sentinel
};
const needed_size = abbrev_buf.len;
@ -583,13 +634,12 @@ pub fn flushModule(self: *DebugSymbols, allocator: Allocator, options: link.Opti
}
}
try self.writeStringTable();
try self.writeLinkeditSegment();
self.updateDwarfSegment();
try self.writeLoadCommands(allocator);
try self.writeHeader();
assert(!self.load_commands_dirty);
assert(!self.strtab_dirty);
assert(!self.debug_abbrev_section_dirty);
assert(!self.debug_aranges_section_dirty);
assert(!self.debug_string_table_dirty);
@ -663,7 +713,7 @@ fn updateDwarfSegment(self: *DebugSymbols) void {
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);
dwarf_segment.inner.vmsize = mem.alignForwardGeneric(u64, dwarf_segment.inner.filesize, self.base.page_size);
}
self.load_commands_dirty = true;
}
@ -719,23 +769,10 @@ fn writeHeader(self: *DebugSymbols) !void {
try self.file.pwriteAll(mem.asBytes(&header), 0);
}
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 allocatedSize(self: *DebugSymbols, start: u64) u64 {
const seg = self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
assert(start >= seg.inner.fileoff);
var min_pos: u64 = seg.inner.fileoff + seg.inner.filesize;
var min_pos: u64 = std.math.maxInt(u64);
for (seg.sections.items) |section| {
if (section.offset <= start) continue;
if (section.offset < min_pos) min_pos = section.offset;
@ -743,102 +780,72 @@ fn allocatedSize(self: *DebugSymbols, start: u64) u64 {
return min_pos - start;
}
fn detectAllocCollisionLinkedit(self: *DebugSymbols, start: u64, size: u64) ?u64 {
const end = start + padToIdeal(size);
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 = padToIdeal(symsize);
const test_end = symtab.symoff + increased_size;
if (end > symtab.symoff and start < test_end) {
return test_end;
}
}
{
// String table
const increased_size = padToIdeal(symtab.strsize);
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.locals.items.len;
const nglobals = self.base.globals.items.len;
const nsyms = nlocals + nglobals;
if (symtab.nsyms < nsyms) {
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 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 {
fn writeLinkeditSegment(self: *DebugSymbols) !void {
const tracy = trace(@src());
defer tracy.end();
try self.relocateSymbolTable();
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
seg.inner.filesize = 0;
try self.writeSymbolTable();
try self.writeStringTable();
}
fn writeSymbolTable(self: *DebugSymbols) !void {
const tracy = trace(@src());
defer tracy.end();
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab;
const off = symtab.symoff + @sizeOf(macho.nlist_64) * index;
log.debug("writing local symbol {} at 0x{x}", .{ index, off });
try self.file.pwriteAll(mem.asBytes(&self.base.locals.items[index]), off);
symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
var locals = std.ArrayList(macho.nlist_64).init(self.base.base.allocator);
defer locals.deinit();
for (self.base.locals.items) |sym| {
if (sym.n_strx == 0) continue;
if (self.base.symbol_resolver.get(sym.n_strx)) |_| continue;
try locals.append(sym);
}
const nlocals = locals.items.len;
const nexports = self.base.globals.items.len;
const locals_off = symtab.symoff;
const locals_size = nlocals * @sizeOf(macho.nlist_64);
log.debug("writing local symbols from 0x{x} to 0x{x}", .{ locals_off, locals_size + locals_off });
try self.file.pwriteAll(mem.sliceAsBytes(locals.items), locals_off);
const exports_off = locals_off + locals_size;
const exports_size = nexports * @sizeOf(macho.nlist_64);
log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off });
try self.file.pwriteAll(mem.sliceAsBytes(self.base.globals.items), exports_off);
symtab.nsyms = @intCast(u32, nlocals + nexports);
seg.inner.filesize += locals_size + exports_size;
self.load_commands_dirty = true;
}
fn writeStringTable(self: *DebugSymbols) !void {
if (!self.strtab_dirty) return;
const tracy = trace(@src());
defer tracy.end();
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
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.strtab.items.len, @alignOf(u64));
symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.base.strtab.items.len, @alignOf(u64)));
seg.inner.filesize += symtab.strsize;
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 string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
try self.file.pwriteAll(self.base.strtab.items, symtab.stroff);
if (symtab.strsize > self.base.strtab.items.len) {
// This is potentially the last section, so we need to pad it out.
try self.file.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1);
}
self.load_commands_dirty = true;
self.strtab_dirty = false;
}
pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const Module.Decl) !void {
@ -846,14 +853,21 @@ pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const M
const tracy = trace(@src());
defer tracy.end();
log.debug("updateDeclLineNumber {s}{*}", .{ decl.name, decl });
const func = decl.val.castTag(.function).?.data;
const line_off = @intCast(u28, decl.src_line + func.lbrace_line);
log.debug(" (decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d})", .{
decl.src_line,
func.lbrace_line,
func.rbrace_line,
});
const line = @intCast(u28, decl.src_line + func.lbrace_line);
const dwarf_segment = &self.load_commands.items[self.dwarf_segment_cmd_index.?].segment;
const shdr = &dwarf_segment.sections.items[self.debug_line_section_index.?];
const file_pos = shdr.offset + decl.fn_link.macho.off + getRelocDbgLineOff();
var data: [4]u8 = undefined;
leb.writeUnsignedFixed(4, &data, line_off);
leb.writeUnsignedFixed(4, &data, line);
try self.file.pwriteAll(&data, file_pos);
}
@ -886,7 +900,13 @@ pub fn initDeclDebugBuffers(
try dbg_line_buffer.ensureTotalCapacity(26);
const func = decl.val.castTag(.function).?.data;
const line_off = @intCast(u28, decl.src_line + func.lbrace_line);
log.debug("updateFunc {s}{*}", .{ decl.name, func.owner_decl });
log.debug(" (decl.src_line={d}, func.lbrace_line={d}, func.rbrace_line={d})", .{
decl.src_line,
func.lbrace_line,
func.rbrace_line,
});
const line = @intCast(u28, decl.src_line + func.lbrace_line);
dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{
DW.LNS.extended_op,
@ -902,7 +922,7 @@ pub fn initDeclDebugBuffers(
// 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);
leb.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line);
dbg_line_buffer.appendAssumeCapacity(DW.LNS.set_file);
assert(getRelocDbgFileIndex() == dbg_line_buffer.items.len);
@ -917,7 +937,7 @@ pub fn initDeclDebugBuffers(
// .debug_info subprogram
const decl_name_with_null = decl.name[0 .. mem.sliceTo(decl.name, 0).len + 1];
try dbg_info_buffer.ensureUnusedCapacity(27 + decl_name_with_null.len);
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();
@ -945,8 +965,6 @@ pub fn initDeclDebugBuffers(
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
@ -966,7 +984,6 @@ pub fn commitDeclDebugInfo(
module: *Module,
decl: *Module.Decl,
debug_buffers: *DeclDebugBuffers,
target: std.Target,
) !void {
const tracy = trace(@src());
defer tracy.end();
@ -1097,14 +1114,26 @@ pub fn commitDeclDebugInfo(
if (dbg_info_buffer.items.len == 0)
return;
// We need this for the duration of this function only so that for composite
// types such as []const u32, if the type *u32 is non-existent, we create
// it synthetically and store the backing bytes in this arena. After we are
// done with the relocations, we can safely deinit the entire memory slab.
// TODO currently, we do not store the relocations for future use, however,
// if that is the case, we should move memory management to a higher scope,
// such as linker scope, or whatnot.
var dbg_type_arena = std.heap.ArenaAllocator.init(allocator);
defer dbg_type_arena.deinit();
{
// Now we emit the .debug_info types of the Decl. These will count towards the size of
// the buffer, so we have to do it before computing the offset, and we can't perform the actual
// relocations yet.
var it = dbg_info_type_relocs.iterator();
while (it.next()) |entry| {
entry.value_ptr.off = @intCast(u32, dbg_info_buffer.items.len);
try self.addDbgInfoType(entry.key_ptr.*, dbg_info_buffer, target);
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);
}
}
@ -1129,24 +1158,25 @@ pub fn commitDeclDebugInfo(
/// Asserts the type has codegen bits.
fn addDbgInfoType(
self: *DebugSymbols,
arena: Allocator,
ty: Type,
dbg_info_buffer: *std.ArrayList(u8),
target: std.Target,
dbg_info_type_relocs: *link.File.DbgInfoTypeRelocsTable,
) !void {
_ = self;
const target = self.base.base.options.target;
var relocs = std.ArrayList(struct { ty: Type, reloc: u32 }).init(arena);
switch (ty.zigTypeTag()) {
.Void => unreachable,
.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
'b', 'o', 'o', 'l', 0, // DW.AT.name, DW.FORM.string
});
},
.Int => {
@ -1163,11 +1193,120 @@ fn addDbgInfoType(
// 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 {
log.debug("TODO implement .debug_info for type '{}'", .{ty});
try dbg_info_buffer.append(abbrev_pad1);
}
},
.Pointer => {
if (ty.isSlice()) {
// Slices are anonymous structs: struct { .ptr = *, .len = N }
try dbg_info_buffer.ensureUnusedCapacity(23);
// DW.AT.structure_type
dbg_info_buffer.appendAssumeCapacity(abbrev_anon_struct_type);
// DW.AT.byte_size, DW.FORM.sdata
dbg_info_buffer.appendAssumeCapacity(16);
// DW.AT.member
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
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
dbg_info_buffer.appendAssumeCapacity(8);
// 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 => {
std.log.scoped(.compiler).err("TODO implement .debug_info for type '{}'", .{ty});
log.debug("TODO implement .debug_info for type '{}'", .{ty});
try dbg_info_buffer.append(abbrev_pad1);
},
}
for (relocs.items) |rel| {
const gop = try dbg_info_type_relocs.getOrPut(self.base.base.allocator, rel.ty);
if (!gop.found_existing) {
gop.value_ptr.* = .{
.off = undefined,
.relocs = .{},
};
}
try gop.value_ptr.relocs.append(self.base.base.allocator, rel.reloc);
}
}
fn updateDeclDebugInfoAllocation(