diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 917354edf7..bfe45569d7 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -923,6 +923,11 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn var opt_debug_line: ?[]const u8 = null; var opt_debug_line_str: ?[]const u8 = null; var opt_debug_ranges: ?[]const u8 = null; + var opt_debug_loclists: ?[]const u8 = null; + var opt_debug_rnglists: ?[]const u8 = null; + var opt_debug_addr: ?[]const u8 = null; + var opt_debug_names: ?[]const u8 = null; + var opt_debug_frame: ?[]const u8 = null; for (shdrs) |*shdr| { if (shdr.sh_type == elf.SHT_NULL) continue; @@ -942,6 +947,16 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn opt_debug_line_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); } else if (mem.eql(u8, name, ".debug_ranges")) { opt_debug_ranges = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + } else if (mem.eql(u8, name, ".debug_loclists")) { + opt_debug_loclists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + } else if (mem.eql(u8, name, ".debug_rnglists")) { + opt_debug_rnglists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + } else if (mem.eql(u8, name, ".debug_addr")) { + opt_debug_addr = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + } else if (mem.eql(u8, name, ".debug_names")) { + opt_debug_names = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); + } else if (mem.eql(u8, name, ".debug_frame")) { + opt_debug_frame = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); } } @@ -954,6 +969,11 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn .debug_line = opt_debug_line orelse return error.MissingDebugInfo, .debug_line_str = opt_debug_line_str, .debug_ranges = opt_debug_ranges, + .debug_loclists = opt_debug_loclists, + .debug_rnglists = opt_debug_rnglists, + .debug_addr = opt_debug_addr, + .debug_names = opt_debug_names, + .debug_frame = opt_debug_frame, }; try DW.openDwarfDebugInfo(&di, allocator); @@ -1494,6 +1514,11 @@ pub const ModuleDebugInfo = switch (native_os) { var opt_debug_str: ?macho.section_64 = null; var opt_debug_line_str: ?macho.section_64 = null; var opt_debug_ranges: ?macho.section_64 = null; + var opt_debug_loclists: ?macho.section_64 = null; + var opt_debug_rnglists: ?macho.section_64 = null; + var opt_debug_addr: ?macho.section_64 = null; + var opt_debug_names: ?macho.section_64 = null; + var opt_debug_frame: ?macho.section_64 = null; for (segcmd.?.getSections()) |sect| { const name = sect.sectName(); @@ -1509,6 +1534,16 @@ pub const ModuleDebugInfo = switch (native_os) { opt_debug_line_str = sect; } else if (mem.eql(u8, name, "__debug_ranges")) { opt_debug_ranges = sect; + } else if (mem.eql(u8, name, "__debug_loclists")) { + opt_debug_loclists = sect; + } else if (mem.eql(u8, name, "__debug_rnglists")) { + opt_debug_rnglists = sect; + } else if (mem.eql(u8, name, "__debug_addr")) { + opt_debug_addr = sect; + } else if (mem.eql(u8, name, "__debug_names")) { + opt_debug_names = sect; + } else if (mem.eql(u8, name, "__debug_frame")) { + opt_debug_frame = sect; } } @@ -1536,6 +1571,11 @@ pub const ModuleDebugInfo = switch (native_os) { try chopSlice(mapped_mem, debug_ranges.offset, debug_ranges.size) else null, + .debug_loclists = opt_debug_loclists, + .debug_rnglists = opt_debug_rnglists, + .debug_addr = opt_debug_addr, + .debug_names = opt_debug_names, + .debug_frame = opt_debug_frame, }; try DW.openDwarfDebugInfo(&di, allocator); @@ -1590,7 +1630,7 @@ pub const ModuleDebugInfo = switch (native_os) { .compile_unit_name = compile_unit.die.getAttrString( o_file_di, DW.AT.name, - compile_unit.is_64, + self.di.debug_str, ) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => "???", }, @@ -1712,7 +1752,7 @@ fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *DW.DwarfInfo) if (nosuspend di.findCompileUnit(address)) |compile_unit| { return SymbolInfo{ .symbol_name = nosuspend di.getSymbolName(address) orelse "???", - .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, compile_unit.is_64) catch |err| switch (err) { + .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.debug_str) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => "???", }, .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index 40dfc8cebb..5ddc8cb3dc 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -205,6 +205,7 @@ const AbbrevAttr = struct { const FormValue = union(enum) { Address: u64, + AddrOffset: u64, Block: []u8, Const: Constant, ExprLoc: []u8, @@ -216,14 +217,35 @@ const FormValue = union(enum) { StrPtr: u64, StrOffset: u64, LineStrPtr: u64, + LocListOffset: u64, + RangeListOffset: u64, + + fn getString(fv: FormValue, di: DwarfInfo) ![]const u8 { + switch (fv) { + .String => |s| return s, + .StrPtr => |off| return di.getString(off), + .LineStrPtr => |off| return di.getLineString(off), + else => return badDwarf(), + } + } + + fn getUInt(fv: FormValue, comptime U: type) !U { + switch (fv) { + .Const => |c| { + const int = try c.asUnsignedLe(); + return math.cast(U, int) orelse return badDwarf(); + }, + else => return badDwarf(), + } + } }; const Constant = struct { payload: u64, signed: bool, - fn asUnsignedLe(self: *const Constant) !u64 { - if (self.signed) return error.InvalidDebugInfo; + fn asUnsignedLe(self: Constant) !u64 { + if (self.signed) return badDwarf(); return self.payload; } }; @@ -252,16 +274,57 @@ const Die = struct { return null; } - fn getAttrAddr(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + fn getAttrAddr(self: *const Die, di: *DwarfInfo, id: u64) error{ InvalidDebugInfo, MissingDebugInfo }!u64 { + const form_value = self.getAttr(id) orelse return missingDwarf(); return switch (form_value.*) { FormValue.Address => |value| value, + FormValue.AddrOffset => |index| { + const debug_addr = di.debug_addr orelse return badDwarf(); + if (debug_addr.len < 8) return badDwarf(); + const first_32_bits = mem.readInt(u32, debug_addr[0..4], di.endian); + const is_64 = first_32_bits == 0xffffffff; + var off: usize = undefined; + const length: u64 = l: { + if (is_64) { + if (debug_addr.len < 16) return badDwarf(); + off = 12; + break :l mem.readInt(u64, debug_addr[4..12], di.endian); + } else { + if (debug_addr.len < 8) return badDwarf(); + if (first_32_bits >= 0xfffffff0) return badDwarf(); + off = 4; + break :l first_32_bits; + } + }; + if (index > length) return badDwarf(); + + const version = mem.readInt(u16, debug_addr[off..][0..2], di.endian); + off += 2; + if (version < 5) return badDwarf(); + + const addr_size = debug_addr[off]; + off += 1; + + const seg_size = debug_addr[off]; + off += 1; + + const base_offset = self.getAttrSecOffset(AT.addr_base) catch off; + const byte_offset = base_offset + (addr_size + seg_size) * index; + if (byte_offset + addr_size > debug_addr.len) return badDwarf(); + switch (addr_size) { + 1 => return debug_addr[byte_offset], + 2 => return mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian), + 4 => return mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian), + 8 => return mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian), + else => return badDwarf(), + } + }, else => error.InvalidDebugInfo, }; } fn getAttrSecOffset(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + const form_value = self.getAttr(id) orelse return missingDwarf(); return switch (form_value.*) { FormValue.Const => |value| value.asUnsignedLe(), FormValue.SecOffset => |value| value, @@ -270,7 +333,7 @@ const Die = struct { } fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + const form_value = self.getAttr(id) orelse return missingDwarf(); return switch (form_value.*) { FormValue.Const => |value| value.asUnsignedLe(), else => error.InvalidDebugInfo, @@ -278,33 +341,67 @@ const Die = struct { } fn getAttrRef(self: *const Die, id: u64) !u64 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + const form_value = self.getAttr(id) orelse return missingDwarf(); return switch (form_value.*) { FormValue.Ref => |value| value, else => error.InvalidDebugInfo, }; } - pub fn getAttrString(self: *const Die, di: *DwarfInfo, id: u64, is_64: bool) ![]const u8 { - const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; - return switch (form_value.*) { - FormValue.String => |value| value, - FormValue.StrPtr => |offset| di.getString(offset), - FormValue.StrOffset => |index| blk: { - const base_offset = self.getAttrSecOffset(AT.str_offsets_base) catch 0; - break :blk di.getLineString(try di.getStringOffset(base_offset + index, is_64)); + pub fn getAttrString(self: *const Die, di: *DwarfInfo, id: u64, opt_str: ?[]const u8) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 { + const form_value = self.getAttr(id) orelse return missingDwarf(); + switch (form_value.*) { + FormValue.String => |value| return value, + FormValue.StrPtr => |offset| return di.getString(offset), + FormValue.StrOffset => |index| { + const debug_str_offsets = di.debug_str_offsets orelse return badDwarf(); + if (debug_str_offsets.len < 8) return badDwarf(); + const first_32_bits = mem.readInt(u32, debug_str_offsets[0..4], di.endian); + const is_64 = first_32_bits == 0xffffffff; + var off: usize = undefined; + const length: u64 = l: { + if (is_64) { + if (debug_str_offsets.len < 16) return badDwarf(); + off = 12; + break :l mem.readInt(u64, debug_str_offsets[4..12], di.endian); + } else { + if (debug_str_offsets.len < 8) return badDwarf(); + if (first_32_bits >= 0xfffffff0) return badDwarf(); + off = 4; + break :l first_32_bits; + } + }; + if (index > length) return badDwarf(); + + const version = mem.readInt(u16, debug_str_offsets[off..][0..2], di.endian); + off += 2; + if (version < 5) return badDwarf(); + + off += 2; // reserved + + const base_offset = self.getAttrSecOffset(AT.str_offsets_base) catch off; + if (is_64) { + const byte_offset = base_offset + 8 * index; + const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); + return getStringGeneric(opt_str, offset); + } else { + const byte_offset = base_offset + 4 * index; + const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); + return getStringGeneric(opt_str, offset); + } }, - FormValue.LineStrPtr => |offset| di.getLineString(offset), - else => error.InvalidDebugInfo, - }; + FormValue.LineStrPtr => |offset| return di.getLineString(offset), + else => return badDwarf(), + } } }; const FileEntry = struct { - file_name: []const u8, - dir_index: usize, - mtime: usize, - len_bytes: usize, + path: []const u8, + dir_index: u32 = 0, + mtime: u64 = 0, + size: u64 = 0, + md5: u128 = 0, }; const LineNumberProgram = struct { @@ -312,13 +409,14 @@ const LineNumberProgram = struct { file: usize, line: i64, column: u64, + version: u16, is_stmt: bool, basic_block: bool, end_sequence: bool, default_is_stmt: bool, target_address: u64, - include_dirs: []const []const u8, + include_dirs: []const FileEntry, prev_valid: bool, prev_address: u64, @@ -349,12 +447,18 @@ const LineNumberProgram = struct { self.prev_end_sequence = undefined; } - pub fn init(is_stmt: bool, include_dirs: []const []const u8, target_address: u64) LineNumberProgram { + pub fn init( + is_stmt: bool, + include_dirs: []const FileEntry, + target_address: u64, + version: u16, + ) LineNumberProgram { return LineNumberProgram{ .address = 0, .file = 1, .line = 1, .column = 0, + .version = version, .is_stmt = is_stmt, .basic_block = false, .end_sequence = false, @@ -377,18 +481,24 @@ const LineNumberProgram = struct { allocator: mem.Allocator, file_entries: []const FileEntry, ) !?debug.LineInfo { - if (self.prev_valid and self.target_address >= self.prev_address and self.target_address < self.address) { - const file_entry = if (self.prev_file == 0) { - return error.MissingDebugInfo; - } else if (self.prev_file - 1 >= file_entries.len) { - return error.InvalidDebugInfo; - } else &file_entries[self.prev_file - 1]; + if (self.prev_valid and + self.target_address >= self.prev_address and + self.target_address < self.address) + { + const file_index = if (self.version >= 5) self.prev_file else i: { + if (self.prev_file == 0) return missingDwarf(); + break :i self.prev_file - 1; + }; - const dir_name = if (file_entry.dir_index >= self.include_dirs.len) { - return error.InvalidDebugInfo; - } else self.include_dirs[file_entry.dir_index]; + if (file_index >= file_entries.len) return badDwarf(); + const file_entry = &file_entries[file_index]; - const file_name = try fs.path.join(allocator, &[_][]const u8{ dir_name, file_entry.file_name }); + if (file_entry.dir_index >= self.include_dirs.len) return badDwarf(); + const dir_name = self.include_dirs[file_entry.dir_index].path; + + const file_name = try fs.path.join(allocator, &[_][]const u8{ + dir_name, file_entry.path, + }); return debug.LineInfo{ .line = if (self.prev_line >= 0) @intCast(u64, self.prev_line) else 0, @@ -415,7 +525,7 @@ fn readUnitLength(in_stream: anytype, endian: std.builtin.Endian, is_64: *bool) if (is_64.*) { return in_stream.readInt(u64, endian); } else { - if (first_32_bits >= 0xfffffff0) return error.InvalidDebugInfo; + if (first_32_bits >= 0xfffffff0) return badDwarf(); // TODO this cast should not be needed return @as(u64, first_32_bits); } @@ -492,6 +602,12 @@ fn parseFormValueRef(in_stream: anytype, endian: std.builtin.Endian, size: i32) fn parseFormValue(allocator: mem.Allocator, in_stream: anytype, form_id: u64, endian: std.builtin.Endian, is_64: bool) anyerror!FormValue { return switch (form_id) { FORM.addr => FormValue{ .Address = try readAddress(in_stream, endian, @sizeOf(usize) == 8) }, + FORM.addrx1 => return FormValue{ .AddrOffset = try in_stream.readInt(u8, endian) }, + FORM.addrx2 => return FormValue{ .AddrOffset = try in_stream.readInt(u16, endian) }, + FORM.addrx3 => return FormValue{ .AddrOffset = try in_stream.readInt(u24, endian) }, + FORM.addrx4 => return FormValue{ .AddrOffset = try in_stream.readInt(u32, endian) }, + FORM.addrx => return FormValue{ .AddrOffset = try nosuspend leb.readULEB128(u64, in_stream) }, + FORM.block1 => parseFormValueBlock(allocator, in_stream, endian, 1), FORM.block2 => parseFormValueBlock(allocator, in_stream, endian, 2), FORM.block4 => parseFormValueBlock(allocator, in_stream, endian, 4), @@ -527,10 +643,11 @@ fn parseFormValue(allocator: mem.Allocator, in_stream: anytype, form_id: u64, en FORM.string => FormValue{ .String = try in_stream.readUntilDelimiterAlloc(allocator, 0, math.maxInt(usize)) }, FORM.strp => FormValue{ .StrPtr = try readAddress(in_stream, endian, is_64) }, - FORM.strx1 => return FormValue{ .StrOffset = @intCast(u64, try in_stream.readInt(u8, endian)) }, - FORM.strx2 => return FormValue{ .StrOffset = @intCast(u64, try in_stream.readInt(u16, endian)) }, - FORM.strx3 => return FormValue{ .StrOffset = @intCast(u64, try in_stream.readInt(u24, endian)) }, - FORM.strx4 => return FormValue{ .StrOffset = @intCast(u64, try in_stream.readInt(u32, endian)) }, + FORM.strx1 => return FormValue{ .StrOffset = try in_stream.readInt(u8, endian) }, + FORM.strx2 => return FormValue{ .StrOffset = try in_stream.readInt(u16, endian) }, + FORM.strx3 => return FormValue{ .StrOffset = try in_stream.readInt(u24, endian) }, + FORM.strx4 => return FormValue{ .StrOffset = try in_stream.readInt(u32, endian) }, + FORM.strx => return FormValue{ .StrOffset = try nosuspend leb.readULEB128(u64, in_stream) }, FORM.line_strp => FormValue{ .LineStrPtr = try readAddress(in_stream, endian, is_64) }, FORM.indirect => { const child_form_id = try nosuspend leb.readULEB128(u64, in_stream); @@ -543,9 +660,11 @@ fn parseFormValue(allocator: mem.Allocator, in_stream: anytype, form_id: u64, en return await @asyncCall(frame, {}, parseFormValue, .{ allocator, in_stream, child_form_id, endian, is_64 }); }, FORM.implicit_const => FormValue{ .Const = Constant{ .signed = true, .payload = undefined } }, - + FORM.loclistx => return FormValue{ .LocListOffset = try nosuspend leb.readULEB128(u64, in_stream) }, + FORM.rnglistx => return FormValue{ .RangeListOffset = try nosuspend leb.readULEB128(u64, in_stream) }, else => { - return error.InvalidDebugInfo; + std.debug.print("unrecognized form id: {x}\n", .{form_id}); + return badDwarf(); }, }; } @@ -567,6 +686,11 @@ pub const DwarfInfo = struct { debug_line: []const u8, debug_line_str: ?[]const u8, debug_ranges: ?[]const u8, + debug_loclists: ?[]const u8, + debug_rnglists: ?[]const u8, + debug_addr: ?[]const u8, + debug_names: ?[]const u8, + debug_frame: ?[]const u8, // Filled later by the initializer abbrev_table_list: std.ArrayListUnmanaged(AbbrevTableHeader) = .{}, compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{}, @@ -602,7 +726,7 @@ pub const DwarfInfo = struct { fn scanAllFunctions(di: *DwarfInfo, allocator: mem.Allocator) !void { var stream = io.fixedBufferStream(di.debug_info); - const in = &stream.reader(); + const in = stream.reader(); const seekable = &stream.seekableStream(); var this_unit_offset: u64 = 0; @@ -619,29 +743,26 @@ pub const DwarfInfo = struct { const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4)); const version = try in.readInt(u16, di.endian); - if (version < 2 or version > 5) return error.InvalidDebugInfo; + if (version < 2 or version > 5) return badDwarf(); var address_size: u8 = undefined; var debug_abbrev_offset: u64 = undefined; - switch (version) { - 5 => { - const unit_type = try in.readInt(u8, di.endian); - if (unit_type != UT.compile) return error.InvalidDebugInfo; - address_size = try in.readByte(); - debug_abbrev_offset = if (is_64) - try in.readInt(u64, di.endian) - else - try in.readInt(u32, di.endian); - }, - else => { - debug_abbrev_offset = if (is_64) - try in.readInt(u64, di.endian) - else - try in.readInt(u32, di.endian); - address_size = try in.readByte(); - }, + if (version >= 5) { + const unit_type = try in.readInt(u8, di.endian); + if (unit_type != UT.compile) return badDwarf(); + address_size = try in.readByte(); + debug_abbrev_offset = if (is_64) + try in.readInt(u64, di.endian) + else + try in.readInt(u32, di.endian); + } else { + debug_abbrev_offset = if (is_64) + try in.readInt(u64, di.endian) + else + try in.readInt(u32, di.endian); + address_size = try in.readByte(); } - if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; + if (address_size != @sizeOf(usize)) return badDwarf(); const compile_unit_pos = try seekable.getPos(); const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); @@ -662,30 +783,30 @@ pub const DwarfInfo = struct { // Prevent endless loops while (depth > 0) : (depth -= 1) { if (this_die_obj.getAttr(AT.name)) |_| { - const name = try this_die_obj.getAttrString(di, AT.name, is_64); + const name = try this_die_obj.getAttrString(di, AT.name, di.debug_str); break :x try allocator.dupe(u8, name); } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| { // Follow the DIE it points to and repeat const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); - if (ref_offset > next_offset) return error.InvalidDebugInfo; + if (ref_offset > next_offset) return badDwarf(); try seekable.seekTo(this_unit_offset + ref_offset); this_die_obj = (try di.parseDie( arena, in, abbrev_table, is_64, - )) orelse return error.InvalidDebugInfo; + )) orelse return badDwarf(); } else if (this_die_obj.getAttr(AT.specification)) |_| { // Follow the DIE it points to and repeat const ref_offset = try this_die_obj.getAttrRef(AT.specification); - if (ref_offset > next_offset) return error.InvalidDebugInfo; + if (ref_offset > next_offset) return badDwarf(); try seekable.seekTo(this_unit_offset + ref_offset); this_die_obj = (try di.parseDie( arena, in, abbrev_table, is_64, - )) orelse return error.InvalidDebugInfo; + )) orelse return badDwarf(); } else { break :x null; } @@ -695,7 +816,7 @@ pub const DwarfInfo = struct { }; const pc_range = x: { - if (die_obj.getAttrAddr(AT.low_pc)) |low_pc| { + if (die_obj.getAttrAddr(di, AT.low_pc)) |low_pc| { if (die_obj.getAttr(AT.high_pc)) |high_pc_value| { const pc_end = switch (high_pc_value.*) { FormValue.Address => |value| value, @@ -703,7 +824,7 @@ pub const DwarfInfo = struct { const offset = try value.asUnsignedLe(); break :b (low_pc + offset); }, - else => return error.InvalidDebugInfo, + else => return badDwarf(), }; break :x PcRange{ .start = low_pc, @@ -748,14 +869,14 @@ pub const DwarfInfo = struct { const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4)); const version = try in.readInt(u16, di.endian); - if (version < 2 or version > 5) return error.InvalidDebugInfo; + if (version < 2 or version > 5) return badDwarf(); var address_size: u8 = undefined; var debug_abbrev_offset: u64 = undefined; switch (version) { 5 => { const unit_type = try in.readInt(u8, di.endian); - if (unit_type != UT.compile) return error.InvalidDebugInfo; + if (unit_type != UT.compile) return badDwarf(); address_size = try in.readByte(); debug_abbrev_offset = if (is_64) try in.readInt(u64, di.endian) @@ -770,7 +891,7 @@ pub const DwarfInfo = struct { address_size = try in.readByte(); }, } - if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; + if (address_size != @sizeOf(usize)) return badDwarf(); const compile_unit_pos = try seekable.getPos(); const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); @@ -780,12 +901,12 @@ pub const DwarfInfo = struct { const compile_unit_die = try allocator.create(Die); errdefer allocator.destroy(compile_unit_die); compile_unit_die.* = (try di.parseDie(allocator, in, abbrev_table, is_64)) orelse - return error.InvalidDebugInfo; + return badDwarf(); - if (compile_unit_die.tag_id != TAG.compile_unit) return error.InvalidDebugInfo; + if (compile_unit_die.tag_id != TAG.compile_unit) return badDwarf(); const pc_range = x: { - if (compile_unit_die.getAttrAddr(AT.low_pc)) |low_pc| { + if (compile_unit_die.getAttrAddr(di, AT.low_pc)) |low_pc| { if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| { const pc_end = switch (high_pc_value.*) { FormValue.Address => |value| value, @@ -793,7 +914,7 @@ pub const DwarfInfo = struct { const offset = try value.asUnsignedLe(); break :b (low_pc + offset); }, - else => return error.InvalidDebugInfo, + else => return badDwarf(), }; break :x PcRange{ .start = low_pc, @@ -834,7 +955,7 @@ pub const DwarfInfo = struct { // specified by DW_AT.low_pc or to some other value encoded // in the list itself. // If no starting value is specified use zero. - var base_address = compile_unit.die.getAttrAddr(AT.low_pc) catch |err| switch (err) { + var base_address = compile_unit.die.getAttrAddr(di, AT.low_pc) catch |err| switch (err) { error.MissingDebugInfo => @as(u64, 0), // TODO https://github.com/ziglang/zig/issues/11135 else => return err, }; @@ -862,7 +983,7 @@ pub const DwarfInfo = struct { } } } - return error.MissingDebugInfo; + return missingDwarf(); } /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, @@ -929,7 +1050,7 @@ pub const DwarfInfo = struct { ) !?Die { const abbrev_code = try leb.readULEB128(u64, in_stream); if (abbrev_code == 0) return null; - const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo; + const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return badDwarf(); var result = Die{ // Lives as long as the Die. @@ -966,7 +1087,7 @@ pub const DwarfInfo = struct { const in = &stream.reader(); const seekable = &stream.seekableStream(); - const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, compile_unit.is_64); + const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.debug_line_str); const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); try seekable.seekTo(line_info_offset); @@ -974,18 +1095,25 @@ pub const DwarfInfo = struct { var is_64: bool = undefined; const unit_length = try readUnitLength(in, di.endian, &is_64); if (unit_length == 0) { - return error.MissingDebugInfo; + return missingDwarf(); } const next_offset = unit_length + (if (is_64) @as(usize, 12) else @as(usize, 4)); const version = try in.readInt(u16, di.endian); - if (version < 2 or version > 4) return error.InvalidDebugInfo; + if (version < 2) return badDwarf(); + + var addr_size: u8 = if (is_64) 8 else 4; + var seg_size: u8 = 0; + if (version >= 5) { + addr_size = try in.readByte(); + seg_size = try in.readByte(); + } const prologue_length = if (is_64) try in.readInt(u64, di.endian) else try in.readInt(u32, di.endian); const prog_start_offset = (try seekable.getPos()) + prologue_length; const minimum_instruction_length = try in.readByte(); - if (minimum_instruction_length == 0) return error.InvalidDebugInfo; + if (minimum_instruction_length == 0) return badDwarf(); if (version >= 4) { // maximum_operations_per_instruction @@ -996,7 +1124,7 @@ pub const DwarfInfo = struct { const line_base = try in.readByteSigned(); const line_range = try in.readByte(); - if (line_range == 0) return error.InvalidDebugInfo; + if (line_range == 0) return badDwarf(); const opcode_base = try in.readByte(); @@ -1014,36 +1142,120 @@ pub const DwarfInfo = struct { defer tmp_arena.deinit(); const arena = tmp_arena.allocator(); - var include_directories = std.ArrayList([]const u8).init(arena); - try include_directories.append(compile_unit_cwd); + var include_directories = std.ArrayList(FileEntry).init(arena); + var file_entries = std.ArrayList(FileEntry).init(arena); - while (true) { - const dir = try in.readUntilDelimiterAlloc(arena, 0, math.maxInt(usize)); - if (dir.len == 0) break; - try include_directories.append(dir); + if (version < 5) { + try include_directories.append(.{ .path = compile_unit_cwd }); + + while (true) { + const dir = try in.readUntilDelimiterAlloc(arena, 0, math.maxInt(usize)); + if (dir.len == 0) break; + try include_directories.append(.{ .path = dir }); + } + + while (true) { + const file_name = try in.readUntilDelimiterAlloc(arena, 0, math.maxInt(usize)); + if (file_name.len == 0) break; + const dir_index = try leb.readULEB128(u32, in); + const mtime = try leb.readULEB128(u64, in); + const size = try leb.readULEB128(u64, in); + try file_entries.append(FileEntry{ + .path = file_name, + .dir_index = dir_index, + .mtime = mtime, + .size = size, + }); + } + } else { + const FileEntFmt = struct { + content_type_code: u8, + form_code: u16, + }; + { + var dir_ent_fmt_buf: [10]FileEntFmt = undefined; + const directory_entry_format_count = try in.readByte(); + if (directory_entry_format_count > dir_ent_fmt_buf.len) return badDwarf(); + for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { + ent_fmt.* = .{ + .content_type_code = try leb.readULEB128(u8, in), + .form_code = try leb.readULEB128(u16, in), + }; + } + + const directories_count = try leb.readULEB128(usize, in); + try include_directories.ensureUnusedCapacity(directories_count); + { + var i: usize = 0; + while (i < directories_count) : (i += 1) { + var e: FileEntry = .{ .path = &.{} }; + for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { + const form_value = try parseFormValue( + arena, + in, + ent_fmt.form_code, + di.endian, + is_64, + ); + switch (ent_fmt.content_type_code) { + LNCT.path => e.path = try form_value.getString(di.*), + LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), + LNCT.timestamp => e.mtime = try form_value.getUInt(u64), + LNCT.size => e.size = try form_value.getUInt(u64), + LNCT.MD5 => e.md5 = try form_value.getUInt(u128), + else => continue, + } + } + include_directories.appendAssumeCapacity(e); + } + } + } + + var file_ent_fmt_buf: [10]FileEntFmt = undefined; + const file_name_entry_format_count = try in.readByte(); + if (file_name_entry_format_count > file_ent_fmt_buf.len) return badDwarf(); + for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { + ent_fmt.* = .{ + .content_type_code = try leb.readULEB128(u8, in), + .form_code = try leb.readULEB128(u16, in), + }; + } + + const file_names_count = try leb.readULEB128(usize, in); + try file_entries.ensureUnusedCapacity(file_names_count); + { + var i: usize = 0; + while (i < file_names_count) : (i += 1) { + var e: FileEntry = .{ .path = &.{} }; + for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { + const form_value = try parseFormValue( + arena, + in, + ent_fmt.form_code, + di.endian, + is_64, + ); + switch (ent_fmt.content_type_code) { + LNCT.path => e.path = try form_value.getString(di.*), + LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), + LNCT.timestamp => e.mtime = try form_value.getUInt(u64), + LNCT.size => e.size = try form_value.getUInt(u64), + LNCT.MD5 => e.md5 = try form_value.getUInt(u128), + else => continue, + } + } + file_entries.appendAssumeCapacity(e); + } + } } - var file_entries = std.ArrayList(FileEntry).init(arena); var prog = LineNumberProgram.init( default_is_stmt, include_directories.items, target_address, + version, ); - while (true) { - const file_name = try in.readUntilDelimiterAlloc(arena, 0, math.maxInt(usize)); - if (file_name.len == 0) break; - const dir_index = try leb.readULEB128(usize, in); - const mtime = try leb.readULEB128(usize, in); - const len_bytes = try leb.readULEB128(usize, in); - try file_entries.append(FileEntry{ - .file_name = file_name, - .dir_index = dir_index, - .mtime = mtime, - .len_bytes = len_bytes, - }); - } - try seekable.seekTo(prog_start_offset); const next_unit_pos = line_info_offset + next_offset; @@ -1053,7 +1265,7 @@ pub const DwarfInfo = struct { if (opcode == LNS.extended_op) { const op_size = try leb.readULEB128(u64, in); - if (op_size < 1) return error.InvalidDebugInfo; + if (op_size < 1) return badDwarf(); var sub_op = try in.readByte(); switch (sub_op) { LNE.end_sequence => { @@ -1066,19 +1278,19 @@ pub const DwarfInfo = struct { prog.address = addr; }, LNE.define_file => { - const file_name = try in.readUntilDelimiterAlloc(arena, 0, math.maxInt(usize)); - const dir_index = try leb.readULEB128(usize, in); - const mtime = try leb.readULEB128(usize, in); - const len_bytes = try leb.readULEB128(usize, in); + const path = try in.readUntilDelimiterAlloc(arena, 0, math.maxInt(usize)); + const dir_index = try leb.readULEB128(u32, in); + const mtime = try leb.readULEB128(u64, in); + const size = try leb.readULEB128(u64, in); try file_entries.append(FileEntry{ - .file_name = file_name, + .path = path, .dir_index = dir_index, .mtime = mtime, - .len_bytes = len_bytes, + .size = size, }); }, else => { - const fwd_amt = math.cast(isize, op_size - 1) orelse return error.InvalidDebugInfo; + const fwd_amt = math.cast(isize, op_size - 1) orelse return badDwarf(); try seekable.seekBy(fwd_amt); }, } @@ -1129,7 +1341,7 @@ pub const DwarfInfo = struct { }, LNS.set_prologue_end => {}, else => { - if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; + if (opcode - 1 >= standard_opcode_lengths.len) return badDwarf(); const len_bytes = standard_opcode_lengths[opcode - 1]; try seekable.seekBy(len_bytes); }, @@ -1137,50 +1349,15 @@ pub const DwarfInfo = struct { } } - return error.MissingDebugInfo; + return missingDwarf(); } - fn getString(di: *DwarfInfo, offset: u64) ![]const u8 { - if (offset > di.debug_str.len) - return error.InvalidDebugInfo; - const casted_offset = math.cast(usize, offset) orelse - return error.InvalidDebugInfo; - - // Valid strings always have a terminating zero byte - if (mem.indexOfScalarPos(u8, di.debug_str, casted_offset, 0)) |last| { - return di.debug_str[casted_offset..last]; - } - - return error.InvalidDebugInfo; + fn getString(di: DwarfInfo, offset: u64) ![]const u8 { + return getStringGeneric(di.debug_str, offset); } - fn getStringOffset(di: *DwarfInfo, offset: u64, is_64: bool) !u64 { - if (di.debug_str_offsets) |debug_str_offsets| { - if (offset >= debug_str_offsets.len) { - return error.InvalidDebugInfo; - } - const offset_casted = @intCast(usize, offset); - if (is_64) { - return std.mem.readIntSlice(u64, debug_str_offsets[offset_casted .. offset_casted + 8], di.endian); - } - return @intCast(u64, std.mem.readIntSlice(u32, debug_str_offsets[offset_casted .. offset_casted + 4], di.endian)); - } - return error.InvalidDebugInfo; - } - - fn getLineString(di: *DwarfInfo, offset: u64) ![]const u8 { - const debug_line_str = di.debug_line_str orelse return error.InvalidDebugInfo; - if (offset > debug_line_str.len) - return error.InvalidDebugInfo; - const casted_offset = math.cast(usize, offset) orelse - return error.InvalidDebugInfo; - - // Valid strings always have a terminating zero byte - if (mem.indexOfScalarPos(u8, debug_line_str, casted_offset, 0)) |last| { - return debug_line_str[casted_offset..last]; - } - - return error.InvalidDebugInfo; + fn getLineString(di: DwarfInfo, offset: u64) ![]const u8 { + return getStringGeneric(di.debug_line_str, offset); } }; @@ -1190,3 +1367,24 @@ pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void { try di.scanAllFunctions(allocator); try di.scanAllCompileUnits(allocator); } + +/// This function is to make it handy to comment out the return and make it +/// into a crash when working on this file. +inline fn badDwarf() error{InvalidDebugInfo} { + //std.os.abort(); // can be handy to uncomment when working on this file + return error.InvalidDebugInfo; +} + +inline fn missingDwarf() error{MissingDebugInfo} { + //std.os.abort(); // can be handy to uncomment when working on this file + return error.MissingDebugInfo; +} + +fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { + const str = opt_str orelse return badDwarf(); + if (offset > str.len) return badDwarf(); + const casted_offset = math.cast(usize, offset) orelse return badDwarf(); + // Valid strings always have a terminating zero byte + const last = mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf(); + return str[casted_offset..last :0]; +} diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 8eb29dcfc7..91eaedc734 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -5862,8 +5862,8 @@ pub fn generateSymbolStabs( else => |e| return e, }; - const tu_name = try compile_unit.die.getAttrString(&debug_info, dwarf.AT.name, compile_unit.is_64); - const tu_comp_dir = try compile_unit.die.getAttrString(&debug_info, dwarf.AT.comp_dir, compile_unit.is_64); + const tu_name = try compile_unit.die.getAttrString(&debug_info, dwarf.AT.name, debug_info.debug_str); + const tu_comp_dir = try compile_unit.die.getAttrString(&debug_info, dwarf.AT.comp_dir, debug_info.debug_str); // Open scope try locals.ensureUnusedCapacity(3); diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 9bcf5243c1..22627975f2 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -584,6 +584,11 @@ pub fn parseDwarfInfo(self: Object) error{Overflow}!dwarf.DwarfInfo { .debug_line = &[0]u8{}, .debug_line_str = &[0]u8{}, .debug_ranges = &[0]u8{}, + .debug_loclists = &[0]u8{}, + .debug_rnglists = &[0]u8{}, + .debug_addr = &[0]u8{}, + .debug_names = &[0]u8{}, + .debug_frame = &[0]u8{}, }; for (self.sections.items) |sect| { const segname = sect.segName(); @@ -601,6 +606,16 @@ pub fn parseDwarfInfo(self: Object) error{Overflow}!dwarf.DwarfInfo { di.debug_line_str = try self.getSectionContents(sect); } else if (mem.eql(u8, sectname, "__debug_ranges")) { di.debug_ranges = try self.getSectionContents(sect); + } else if (mem.eql(u8, sectname, "__debug_loclists")) { + di.debug_loclists = try self.getSectionContents(sect); + } else if (mem.eql(u8, sectname, "__debug_rnglists")) { + di.debug_rnglists = try self.getSectionContents(sect); + } else if (mem.eql(u8, sectname, "__debug_addr")) { + di.debug_addr = try self.getSectionContents(sect); + } else if (mem.eql(u8, sectname, "__debug_names")) { + di.debug_names = try self.getSectionContents(sect); + } else if (mem.eql(u8, sectname, "__debug_frame")) { + di.debug_frame = try self.getSectionContents(sect); } } } diff --git a/test/stack_traces.zig b/test/stack_traces.zig index 06534a2f7e..ed0dc9e235 100644 --- a/test/stack_traces.zig +++ b/test/stack_traces.zig @@ -21,7 +21,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void { }, .ReleaseSafe = .{ .exclude_os = .{ - .windows, // segfault + .windows, // TODO .linux, // defeated by aggressive inlining }, .expect = @@ -71,7 +71,8 @@ pub fn addCases(cases: *tests.StackTracesContext) void { }, .ReleaseSafe = .{ .exclude_os = .{ - .windows, // segfault + .windows, // TODO + .linux, // TODO }, .expect = \\error: TheSkyIsFalling @@ -137,7 +138,8 @@ pub fn addCases(cases: *tests.StackTracesContext) void { }, .ReleaseSafe = .{ .exclude_os = .{ - .windows, // segfault + .windows, // TODO + .linux, // TODO }, .expect = \\error: TheSkyIsFalling