aboutsummaryrefslogtreecommitdiff
path: root/src/link/MachO/DebugSymbols.zig
diff options
context:
space:
mode:
authorJakub Konka <kubkon@jakubkonka.com>2020-12-30 22:18:21 +0100
committerJakub Konka <kubkon@jakubkonka.com>2020-12-31 10:19:04 +0100
commitea4ff34e13d9768eedcc9add608be2366d6ad030 (patch)
treef7b031444e312b77407eaa46bcd7f32b3c9ebf7b /src/link/MachO/DebugSymbols.zig
parentd189614647d6c4195081e40f08f92e15db153592 (diff)
downloadzig-ea4ff34e13d9768eedcc9add608be2366d6ad030.tar.gz
zig-ea4ff34e13d9768eedcc9add608be2366d6ad030.zip
macho: put all DWARF-related logic into DebugSymbols
Diffstat (limited to 'src/link/MachO/DebugSymbols.zig')
-rw-r--r--src/link/MachO/DebugSymbols.zig355
1 files changed, 315 insertions, 40 deletions
diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig
index a6ac2dfde6..90f8cf9a00 100644
--- a/src/link/MachO/DebugSymbols.zig
+++ b/src/link/MachO/DebugSymbols.zig
@@ -10,6 +10,7 @@ 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;
@@ -87,21 +88,21 @@ debug_aranges_section_dirty: bool = false,
debug_info_header_dirty: bool = false,
debug_line_header_dirty: bool = false,
-pub const abbrev_compile_unit = 1;
-pub const abbrev_subprogram = 2;
-pub const abbrev_subprogram_retvoid = 3;
-pub const abbrev_base_type = 4;
-pub const abbrev_pad1 = 5;
-pub const abbrev_parameter = 6;
+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.
-pub const dbg_line_vaddr_reloc_index = 3;
+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.
-pub const dbg_info_low_pc_reloc_index = 1;
+const dbg_info_low_pc_reloc_index = 1;
-pub const min_nop_size = 2;
+const min_nop_size = 2;
/// You must call this function *after* `MachO.populateMissingMetadata()`
/// has been called to get a viable debug symbols output.
@@ -888,8 +889,304 @@ fn writeStringTable(self: *DebugSymbols) !void {
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 container_scope = decl.scope.cast(Module.Scope.Container).?;
+ const tree = container_scope.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 => {
+ const zir_dumps = if (std.builtin.is_test) &[0][]const u8{} else build_options.zir_dumps;
+ if (zir_dumps.len != 0) {
+ for (zir_dumps) |fn_name| {
+ if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) {
+ std.debug.print("\n{}\n", .{decl.name});
+ typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
+ }
+ }
+ }
+
+ // For functions we need to add a prologue to the debug line program.
+ try dbg_line_buffer.ensureCapacity(26);
+
+ const line_off: u28 = blk: {
+ if (decl.scope.cast(Module.Scope.Container)) |container_scope| {
+ const tree = container_scope.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);
+ } else if (decl.scope.cast(Module.Scope.ZIRModule)) |zir_module| {
+ const byte_off = zir_module.contents.module.decls[decl.src_index].inst.src;
+ const line_delta = std.zig.lineDelta(zir_module.source.bytes, 0, byte_off);
+ break :blk @intCast(u28, line_delta);
+ } else {
+ unreachable;
+ }
+ };
+
+ 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| {
+ 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.
-pub fn addDbgInfoType(
+fn addDbgInfoType(
self: *DebugSymbols,
ty: Type,
dbg_info_buffer: *std.ArrayList(u8),
@@ -931,7 +1228,7 @@ pub fn addDbgInfoType(
}
}
-pub fn updateDeclDebugInfoAllocation(
+fn updateDeclDebugInfoAllocation(
self: *DebugSymbols,
allocator: *Allocator,
text_block: *TextBlock,
@@ -986,7 +1283,7 @@ pub fn updateDeclDebugInfoAllocation(
}
}
-pub fn writeDeclDebugInfo(self: *DebugSymbols, text_block: *TextBlock, dbg_info_buf: []const u8) !void {
+fn writeDeclDebugInfo(self: *DebugSymbols, text_block: *TextBlock, dbg_info_buf: []const u8) !void {
const tracy = trace(@src());
defer tracy.end();
@@ -1057,19 +1354,19 @@ fn makeDebugString(self: *DebugSymbols, allocator: *Allocator, bytes: []const u8
/// The reloc offset for the line offset of a function from the previous function's line.
/// It's a fixed-size 4-byte ULEB128.
-pub fn getRelocDbgLineOff() usize {
+fn getRelocDbgLineOff() usize {
return dbg_line_vaddr_reloc_index + @sizeOf(u64) + 1;
}
-pub fn getRelocDbgFileIndex() usize {
+fn getRelocDbgFileIndex() usize {
return getRelocDbgLineOff() + 5;
}
-pub fn getRelocDbgInfoSubprogramHighPC() u32 {
+fn getRelocDbgInfoSubprogramHighPC() u32 {
return dbg_info_low_pc_reloc_index + @sizeOf(u64);
}
-pub fn dbgLineNeededHeaderBytes(self: DebugSymbols, module: *Module) u32 {
+fn dbgLineNeededHeaderBytes(self: DebugSymbols, module: *Module) u32 {
const directory_entry_format_count = 1;
const file_name_entry_format_count = 1;
const directory_count = 1;
@@ -1092,7 +1389,7 @@ fn dbgInfoNeededHeaderBytes(self: DebugSymbols) u32 {
/// 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`).
-pub fn pwriteDbgLineNops(
+fn pwriteDbgLineNops(
self: *DebugSymbols,
prev_padding_size: usize,
buf: []const u8,
@@ -1170,7 +1467,7 @@ pub fn pwriteDbgLineNops(
/// Writes to the file a buffer, prefixed and suffixed by the specified number of
/// bytes of padding.
-pub fn pwriteDbgInfoNops(
+fn pwriteDbgInfoNops(
self: *DebugSymbols,
prev_padding_size: usize,
buf: []const u8,
@@ -1239,25 +1536,3 @@ pub fn pwriteDbgInfoNops(
try self.file.pwritevAll(vecs[0..vec_index], offset - prev_padding_size);
}
-
-pub fn updateDeclLineNumber(self: *DebugSymbols, module: *Module, decl: *const Module.Decl) !void {
- const tracy = trace(@src());
- defer tracy.end();
-
- const container_scope = decl.scope.cast(Module.Scope.Container).?;
- const tree = container_scope.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);
-}