mirror of
https://github.com/ziglang/zig.git
synced 2025-12-26 08:03:08 +00:00
macho: put all DWARF-related logic into DebugSymbols
This commit is contained in:
parent
d189614647
commit
ea4ff34e13
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user