macho: put all DWARF-related logic into DebugSymbols

This commit is contained in:
Jakub Konka 2020-12-30 22:18:21 +01:00
parent d189614647
commit ea4ff34e13
2 changed files with 342 additions and 283 deletions

View File

@ -11,8 +11,6 @@ const codegen = @import("../codegen.zig");
const aarch64 = @import("../codegen/aarch64.zig");
const math = std.math;
const mem = std.mem;
const DW = std.dwarf;
const leb = std.leb;
const trace = @import("../tracy.zig").trace;
const build_options = @import("build_options");
@ -1119,128 +1117,30 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
defer code_buffer.deinit();
var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator);
defer dbg_line_buffer.deinit();
var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator);
defer dbg_info_buffer.deinit();
var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{};
var debug_buffers = if (self.d_sym) |*ds| try ds.initDeclDebugBuffers(self.base.allocator, module, decl) else null;
defer {
var it = dbg_info_type_relocs.iterator();
while (it.next()) |entry| {
entry.value.relocs.deinit(self.base.allocator);
if (debug_buffers) |*dbg| {
dbg.dbg_line_buffer.deinit();
dbg.dbg_info_buffer.deinit();
var it = dbg.dbg_info_type_relocs.iterator();
while (it.next()) |entry| {
entry.value.relocs.deinit(self.base.allocator);
}
dbg.dbg_info_type_relocs.deinit(self.base.allocator);
}
dbg_info_type_relocs.deinit(self.base.allocator);
}
const typed_value = decl.typed_value.most_recent.typed_value;
const is_fn: bool = switch (typed_value.ty.zigTypeTag()) {
.Fn => true,
else => false,
};
if (is_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(DebugSymbols.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(DebugSymbols.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(DebugSymbols.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(DebugSymbols.abbrev_subprogram);
} else {
dbg_info_buffer.appendAssumeCapacity(DebugSymbols.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(DebugSymbols.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(DebugSymbols.getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len);
dbg_info_buffer.items.len += 4; // DW.AT_high_pc, DW.FORM_data4
if (fn_ret_has_bits) {
const gop = try dbg_info_type_relocs.getOrPut(self.base.allocator, fn_ret_type);
if (!gop.found_existing) {
gop.entry.value = .{
.off = undefined,
.relocs = .{},
};
}
try gop.entry.value.relocs.append(self.base.allocator, @intCast(u32, dbg_info_buffer.items.len));
dbg_info_buffer.items.len += 4; // DW.AT_type, DW.FORM_ref4
}
dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT_name, DW.FORM_string
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
}
const res = try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{
.dwarf = .{
.dbg_line = &dbg_line_buffer,
.dbg_info = &dbg_info_buffer,
.dbg_info_type_relocs = &dbg_info_type_relocs,
},
});
const res = if (debug_buffers) |*dbg|
try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .{
.dwarf = .{
.dbg_line = &dbg.dbg_line_buffer,
.dbg_info = &dbg.dbg_info_buffer,
.dbg_info_type_relocs = &dbg.dbg_info_type_relocs,
},
})
else
try codegen.generateSymbol(&self.base, decl.src(), typed_value, &code_buffer, .none);
const code = switch (res) {
.externally_managed => |x| x,
@ -1328,132 +1228,16 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
const file_offset = text_section.offset + section_offset;
try self.base.file.?.pwriteAll(code, file_offset);
const text_block = &decl.link.macho;
// If the Decl is a function, we need to update the __debug_line program.
if (is_fn) {
// Perform the relocations based on vaddr.
{
const ptr = dbg_line_buffer.items[DebugSymbols.dbg_line_vaddr_reloc_index..][0..8];
mem.writeIntLittle(u64, ptr, symbol.n_value);
}
{
const ptr = dbg_info_buffer.items[DebugSymbols.dbg_info_low_pc_reloc_index..][0..8];
mem.writeIntLittle(u64, ptr, symbol.n_value);
}
{
const ptr = dbg_info_buffer.items[DebugSymbols.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.d_sym.?.load_commands.items[self.d_sym.?.dwarf_segment_cmd_index.?].Segment;
const debug_line_sect = &dwarf_segment.sections.items[self.d_sym.?.debug_line_section_index.?];
const src_fn = &decl.fn_link.macho;
src_fn.len = @intCast(u32, dbg_line_buffer.items.len);
if (self.d_sym.?.dbg_line_fn_last) |last| {
if (src_fn.next) |next| {
// Update existing function - non-last item.
if (src_fn.off + src_fn.len + DebugSymbols.min_nop_size > next.off) {
// It grew too big, so we move it to a new location.
if (src_fn.prev) |prev| {
_ = self.d_sym.?.dbg_line_fn_free_list.put(self.base.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.d_sym.?.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.d_sym.?.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.d_sym.?.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.d_sym.?.dbg_line_fn_first = src_fn;
self.d_sym.?.dbg_line_fn_last = src_fn;
src_fn.off = self.d_sym.?.dbgLineNeededHeaderBytes(module) * alloc_num / alloc_den;
}
const last_src_fn = self.d_sym.?.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.d_sym.?.file.copyRangeAll(debug_line_sect.offset, self.d_sym.?.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.d_sym.?.load_commands_dirty = true; // TODO look into making only the one section dirty
self.d_sym.?.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.d_sym.?.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);
if (debug_buffers) |*db| {
try self.d_sym.?.commitDeclDebugInfo(
self.base.allocator,
module,
decl,
db,
self.base.options.target,
);
}
// 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.d_sym.?.addDbgInfoType(entry.key, &dbg_info_buffer, self.base.options.target);
}
try self.d_sym.?.updateDeclDebugInfoAllocation(self.base.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.d_sym.?.writeDeclDebugInfo(text_block, dbg_info_buffer.items);
// Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
try self.updateDeclExports(module, decl, decl_exports);

View File

@ -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);
}