authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-05-29 01:26:30-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-07-20 22:58:13-04:00
log5781016c35c27a64b08c0701a92ad7c1a253a869
tree00d90503c3fbdedd43c77365104e95a38d9f9cab
parentdd2035735fa2160f85bf8fb8cd8b978d77fac292

dwarf: add support for .eh_frame_hdr when unwinding

- .eh_frame_hdr contains a binary-searchable data structure for finding an FDE. If present, we can use this section to avoid having to parse the entire FDE/CIE list in the binary, instead only entries that are actually required for unwinding are read. - rework the inputs pc-relative pointer decoding to support both already-mapped sections as well as sections mapped from a file - store the VirtualMachine on UnwindContext so the allocations can be reused

3 files changed, 363 insertions(+), 109 deletions(-)

lib/std/debug.zig+44-20
...@@ -171,6 +171,7 @@ pub fn dumpStackTraceFromBase(context: StackTraceContext) void {...@@ -171,6 +171,7 @@ pub fn dumpStackTraceFromBase(context: StackTraceContext) void {
171 }171 }
172172
173 var it = StackIterator.initWithContext(null, debug_info, context) catch return;173 var it = StackIterator.initWithContext(null, debug_info, context) catch return;
174 defer it.deinit();
174 printSourceAtAddress(debug_info, stderr, it.dwarf_context.pc, tty_config) catch return;175 printSourceAtAddress(debug_info, stderr, it.dwarf_context.pc, tty_config) catch return;
175176
176 while (it.next()) |return_address| {177 while (it.next()) |return_address| {
...@@ -219,6 +220,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT...@@ -219,6 +220,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT
219 } else {220 } else {
220 // TODO: This should use the dwarf unwinder if it's available221 // TODO: This should use the dwarf unwinder if it's available
221 var it = StackIterator.init(first_address, null);222 var it = StackIterator.init(first_address, null);
223 defer it.deinit();
222 for (stack_trace.instruction_addresses, 0..) |*addr, i| {224 for (stack_trace.instruction_addresses, 0..) |*addr, i| {
223 addr.* = it.next() orelse {225 addr.* = it.next() orelse {
224 stack_trace.index = i;226 stack_trace.index = i;
...@@ -445,10 +447,18 @@ pub const StackIterator = struct {...@@ -445,10 +447,18 @@ pub const StackIterator = struct {
445 pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const os.ucontext_t) !StackIterator {447 pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const os.ucontext_t) !StackIterator {
446 var iterator = init(first_address, null);448 var iterator = init(first_address, null);
447 iterator.debug_info = debug_info;449 iterator.debug_info = debug_info;
448 iterator.dwarf_context = try DW.UnwindContext.init(context);450 iterator.dwarf_context = try DW.UnwindContext.init(context, &isValidMemory);
449 return iterator;451 return iterator;
450 }452 }
451453
454 pub fn deinit(self: *StackIterator) void {
455 if (supports_context) {
456 if (self.debug_info) |debug_info| {
457 self.dwarf_context.deinit(debug_info.allocator);
458 }
459 }
460 }
461
452 // Offset of the saved BP wrt the frame pointer.462 // Offset of the saved BP wrt the frame pointer.
453 const fp_offset = if (native_arch.isRISCV())463 const fp_offset = if (native_arch.isRISCV())
454 // On RISC-V the frame pointer points to the top of the saved register464 // On RISC-V the frame pointer points to the top of the saved register
...@@ -599,6 +609,8 @@ pub fn writeCurrentStackTrace(...@@ -599,6 +609,8 @@ pub fn writeCurrentStackTrace(
599609
600 // TODO: Capture a context and use initWithContext610 // TODO: Capture a context and use initWithContext
601 var it = StackIterator.init(start_addr, null);611 var it = StackIterator.init(start_addr, null);
612 defer it.deinit();
613
602 while (it.next()) |return_address| {614 while (it.next()) |return_address| {
603 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,615 // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS,
604 // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid616 // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid
...@@ -957,19 +969,15 @@ pub fn readElfDebugInfo(...@@ -957,19 +969,15 @@ pub fn readElfDebugInfo(
957969
958 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;970 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
959971
960 // Take ownership over any owned sections from the parent scope972 // Combine section list. This takes ownership over any owned sections from the parent scope.
961 for (parent_sections, &sections) |*parent, *section| {973 for (parent_sections, &sections) |*parent, *section| {
962 if (parent.*) |*p| {974 if (parent.*) |*p| {
963 section.* = p.*;975 section.* = p.*;
964 p.owned = false;976 p.owned = false;
965 }977 }
966 }978 }
967
968 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);979 errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data);
969980
970 // TODO: This function should take a ptr to GNU_EH_FRAME (which is .eh_frame_hdr) from the ELF headers
971 // and prefil sections[.eh_frame_hdr]
972
973 var separate_debug_filename: ?[]const u8 = null;981 var separate_debug_filename: ?[]const u8 = null;
974 var separate_debug_crc: ?u32 = null;982 var separate_debug_crc: ?u32 = null;
975983
...@@ -992,6 +1000,7 @@ pub fn readElfDebugInfo(...@@ -992,6 +1000,7 @@ pub fn readElfDebugInfo(
992 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;1000 if (mem.eql(u8, "." ++ section.name, name)) section_index = i;
993 }1001 }
994 if (section_index == null) continue;1002 if (section_index == null) continue;
1003 if (sections[section_index.?] != null) continue;
9951004
996 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);1005 const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size);
997 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {1006 sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: {
...@@ -1496,7 +1505,8 @@ pub const DebugInfo = struct {...@@ -1496,7 +1505,8 @@ pub const DebugInfo = struct {
1496 // Output1505 // Output
1497 base_address: usize = undefined,1506 base_address: usize = undefined,
1498 name: []const u8 = undefined,1507 name: []const u8 = undefined,
1499 build_id: ?[]const u8 = undefined,1508 build_id: ?[]const u8 = null,
1509 gnu_eh_frame: ?[]const u8 = null,
1500 } = .{ .address = address };1510 } = .{ .address = address };
1501 const CtxTy = @TypeOf(ctx);1511 const CtxTy = @TypeOf(ctx);
15021512
...@@ -1523,19 +1533,24 @@ pub const DebugInfo = struct {...@@ -1523,19 +1533,24 @@ pub const DebugInfo = struct {
1523 }1533 }
1524 } else return;1534 } else return;
15251535
1526 // TODO: Look for the GNU_EH_FRAME section and pass it to readElfDebugInfo
1527
1528 for (info.dlpi_phdr[0..info.dlpi_phnum]) |phdr| {1536 for (info.dlpi_phdr[0..info.dlpi_phnum]) |phdr| {
1529 if (phdr.p_type != elf.PT_NOTE) continue;1537 switch (phdr.p_type) {
15301538 elf.PT_NOTE => {
1531 const note_bytes = @intToPtr([*]const u8, info.dlpi_addr + phdr.p_vaddr)[0..phdr.p_memsz];1539 // Look for .note.gnu.build-id
1532 const name_size = mem.readIntSliceNative(u32, note_bytes[0..4]);1540 const note_bytes = @intToPtr([*]const u8, info.dlpi_addr + phdr.p_vaddr)[0..phdr.p_memsz];
1533 if (name_size != 4) continue;1541 const name_size = mem.readIntSliceNative(u32, note_bytes[0..4]);
1534 const desc_size = mem.readIntSliceNative(u32, note_bytes[4..8]);1542 if (name_size != 4) continue;
1535 const note_type = mem.readIntSliceNative(u32, note_bytes[8..12]);1543 const desc_size = mem.readIntSliceNative(u32, note_bytes[4..8]);
1536 if (note_type != elf.NT_GNU_BUILD_ID) continue;1544 const note_type = mem.readIntSliceNative(u32, note_bytes[8..12]);
1537 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;1545 if (note_type != elf.NT_GNU_BUILD_ID) continue;
1538 context.build_id = note_bytes[16..][0..desc_size];1546 if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue;
1547 context.build_id = note_bytes[16..][0..desc_size];
1548 },
1549 elf.PT_GNU_EH_FRAME => {
1550 context.gnu_eh_frame = @intToPtr([*]const u8, info.dlpi_addr + phdr.p_vaddr)[0..phdr.p_memsz];
1551 },
1552 else => {},
1553 }
1539 }1554 }
15401555
1541 // Stop the iteration1556 // Stop the iteration
...@@ -1555,7 +1570,16 @@ pub const DebugInfo = struct {...@@ -1555,7 +1570,16 @@ pub const DebugInfo = struct {
1555 errdefer self.allocator.destroy(obj_di);1570 errdefer self.allocator.destroy(obj_di);
15561571
1557 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;1572 var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array;
1558 // TODO: If GNU_EH_FRAME was found, set it in sections1573 if (ctx.gnu_eh_frame) |eh_frame_hdr| {
1574 // This is a special case - pointer offsets inside .eh_frame_hdr
1575 // are encoded relative to its base address, so we must use the
1576 // version that is already memory mapped, and not the one that
1577 // will be mapped separately from the ELF file.
1578 sections[@enumToInt(DW.DwarfSection.eh_frame_hdr)] = .{
1579 .data = eh_frame_hdr,
1580 .owned = false,
1581 };
1582 }
15591583
1560 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);1584 obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null);
1561 obj_di.base_address = ctx.base_address;1585 obj_di.base_address = ctx.base_address;
lib/std/dwarf.zig+312-89
...@@ -682,6 +682,8 @@ pub const DwarfInfo = struct {...@@ -682,6 +682,8 @@ pub const DwarfInfo = struct {
682 compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{},682 compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{},
683 func_list: std.ArrayListUnmanaged(Func) = .{},683 func_list: std.ArrayListUnmanaged(Func) = .{},
684684
685 eh_frame_hdr: ?ExceptionFrameHeader = null,
686 // These lookup tables are only used if `eh_frame_hdr` is null
685 cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{},687 cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{},
686 // Sorted by start_pc688 // Sorted by start_pc
687 fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{},689 fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{},
...@@ -1489,60 +1491,79 @@ pub const DwarfInfo = struct {...@@ -1489,60 +1491,79 @@ pub const DwarfInfo = struct {
1489 }1491 }
14901492
1491 pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, binary_mem: []const u8) !void {1493 pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, binary_mem: []const u8) !void {
1492 var has_eh_frame_hdr = false;1494 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: {
1493 if (di.section(.eh_frame_hdr)) |eh_frame_hdr| {1495 var stream = io.fixedBufferStream(eh_frame_hdr);
1494 has_eh_frame_hdr = true;1496 const reader = stream.reader();
14951497
1496 // TODO: Parse this section to get the lookup table, and skip loading the entire section1498 const version = try reader.readByte();
1499 if (version != 1) break :blk;
1500
1501 const eh_frame_ptr_enc = try reader.readByte();
1502 if (eh_frame_ptr_enc == EH.PE.omit) break :blk;
1503 const fde_count_enc = try reader.readByte();
1504 if (fde_count_enc == EH.PE.omit) break :blk;
1505 const table_enc = try reader.readByte();
1506 if (table_enc == EH.PE.omit) break :blk;
1507
1508 const eh_frame_ptr = std.math.cast(usize, try readEhPointer(reader, eh_frame_ptr_enc, @sizeOf(usize), .{
1509 .pc_rel_base = @ptrToInt(&eh_frame_hdr[stream.pos]),
1510 .follow_indirect = true,
1511 }, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf();
1512
1513 const fde_count = std.math.cast(usize, try readEhPointer(reader, fde_count_enc, @sizeOf(usize), .{
1514 .pc_rel_base = @ptrToInt(&eh_frame_hdr[stream.pos]),
1515 .follow_indirect = true,
1516 }, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf();
1517
1518 const entry_size = try ExceptionFrameHeader.entrySize(table_enc);
1519 const entries_len = fde_count * entry_size;
1520 if (entries_len > eh_frame_hdr.len - stream.pos) return badDwarf();
1521
1522 di.eh_frame_hdr = .{
1523 .eh_frame_ptr = eh_frame_ptr,
1524 .table_enc = table_enc,
1525 .fde_count = fde_count,
1526 .entries = eh_frame_hdr[stream.pos..][0..entries_len],
1527 };
14971528
1498 _ = eh_frame_hdr;1529 // No need to scan .eh_frame, we have a binary search table already
1530 return;
1499 }1531 }
15001532
1501 if (di.section(.eh_frame)) |eh_frame| {1533 if (di.section(.eh_frame)) |eh_frame| {
1502 var stream = io.fixedBufferStream(eh_frame);1534 var stream = io.fixedBufferStream(eh_frame);
1503 const reader = stream.reader();
1504
1505 while (stream.pos < stream.buffer.len) {1535 while (stream.pos < stream.buffer.len) {
1506 const length_offset = stream.pos;1536 const entry_header = try EntryHeader.read(&stream, di.endian);
1507 var length: usize = try reader.readInt(u32, di.endian);1537 switch (entry_header.type) {
1508 if (length == 0) break;1538 .cie => {
15091539 const cie = try CommonInformationEntry.parse(
1510 var is_64 = length == math.maxInt(u32);1540 entry_header.entry_bytes,
1511 if (is_64) {1541 -@intCast(isize, @ptrToInt(binary_mem.ptr)),
1512 length = std.math.cast(usize, try reader.readInt(u64, di.endian)) orelse return error.LengthOverflow;1542 //@ptrToInt(eh_frame.ptr),
1513 }1543 //@ptrToInt(eh_frame.ptr) - @ptrToInt(binary_mem.ptr),
15141544 true,
1515 const id_len = @as(u8, if (is_64) 8 else 4);1545 entry_header.length_offset,
1516 const id = if (is_64) try reader.readInt(u64, di.endian) else try reader.readInt(u32, di.endian);1546 @sizeOf(usize),
1517 const entry_bytes = eh_frame[stream.pos..][0 .. length - id_len];1547 di.endian,
15181548 );
1519 if (id == 0) {1549 try di.cie_map.put(allocator, entry_header.length_offset, cie);
1520 const cie = try CommonInformationEntry.parse(1550 },
1521 entry_bytes,1551 .fde => |cie_offset| {
1522 @ptrToInt(eh_frame.ptr),1552 const cie = di.cie_map.get(cie_offset) orelse return badDwarf();
1523 @ptrToInt(eh_frame.ptr) - @ptrToInt(binary_mem.ptr),1553 const fde = try FrameDescriptionEntry.parse(
1524 true,1554 entry_header.entry_bytes,
1525 length_offset,1555 -@intCast(isize, @ptrToInt(binary_mem.ptr)),
1526 @sizeOf(usize),1556 //@ptrToInt(eh_frame.ptr),
1527 di.endian,1557 //@ptrToInt(eh_frame.ptr) - @ptrToInt(binary_mem.ptr),
1528 );1558 true,
1529 try di.cie_map.put(allocator, length_offset, cie);1559 cie,
1530 } else {1560 @sizeOf(usize),
1531 const cie_offset = stream.pos - id_len - id;1561 di.endian,
1532 const cie = di.cie_map.get(cie_offset) orelse return badDwarf();1562 );
1533 const fde = try FrameDescriptionEntry.parse(1563 try di.fde_list.append(allocator, fde);
1534 entry_bytes,1564 },
1535 @ptrToInt(eh_frame.ptr),1565 .terminator => break,
1536 @ptrToInt(eh_frame.ptr) - @ptrToInt(binary_mem.ptr),
1537 true,
1538 cie,
1539 @sizeOf(usize),
1540 di.endian,
1541 );
1542 try di.fde_list.append(allocator, fde);
1543 }1566 }
1544
1545 stream.pos += entry_bytes.len;
1546 }1567 }
15471568
1548 // TODO: Avoiding sorting if has_eh_frame_hdr exists1569 // TODO: Avoiding sorting if has_eh_frame_hdr exists
...@@ -1560,59 +1581,67 @@ pub const DwarfInfo = struct {...@@ -1560,59 +1581,67 @@ pub const DwarfInfo = struct {
1560 if (context.pc == 0) return;1581 if (context.pc == 0) return;
15611582
1562 // TODO: Handle signal frame (ie. use_prev_instr in libunwind)1583 // TODO: Handle signal frame (ie. use_prev_instr in libunwind)
1563 // TOOD: Use eh_frame_hdr to accelerate the search if available
1564 //const eh_frame_hdr = di.section(.eh_frame_hdr) orelse return error.MissingDebugInfo;
1565
1566 // Find the FDE
1567 const unmapped_pc = context.pc - module_base_address;
1568 const index = std.sort.binarySearch(FrameDescriptionEntry, unmapped_pc, di.fde_list.items, {}, struct {
1569 pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {
1570 if (pc < mid_item.pc_begin) {
1571 return .lt;
1572 } else {
1573 const range_end = mid_item.pc_begin + mid_item.pc_range;
1574 if (pc < range_end) {
1575 return .eq;
1576 }
15771584
1578 return .gt;1585 // Find the FDE and CIE
1579 }1586 var cie: CommonInformationEntry = undefined;
1580 }1587 var fde: FrameDescriptionEntry = undefined;
1581 }.compareFn);
15821588
1583 const fde = if (index) |i| &di.fde_list.items[i] else return error.MissingFDE;1589 // In order to support reading .eh_frame from the ELF file (vs using the already-mapped section),
1584 const cie = di.cie_map.getPtr(fde.cie_length_offset) orelse return error.MissingCIE;1590 // scanAllUnwindInfo has already mapped any pc-relative offsets such that they we be relative to zero
1591 // instead of the actual base address of the module. When using .eh_frame_hdr, PC can be used directly
1592 // as pointers will be decoded relative to the alreayd-mapped .eh_frame.
1593 var mapped_pc: usize = undefined;
15851594
1586 // const prev_cfa = context.cfa;1595 if (di.eh_frame_hdr) |header| {
1587 // const prev_pc = context.pc;1596 mapped_pc = context.pc;
1597 try header.findEntry(context.isValidMemory, @ptrToInt(di.section(.eh_frame_hdr).?.ptr), mapped_pc, &cie, &fde);
1598 } else {
1599 mapped_pc = context.pc - module_base_address;
1600 const index = std.sort.binarySearch(FrameDescriptionEntry, mapped_pc, di.fde_list.items, {}, struct {
1601 pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order {
1602 if (pc < mid_item.pc_begin) {
1603 return .lt;
1604 } else {
1605 const range_end = mid_item.pc_begin + mid_item.pc_range;
1606 if (pc < range_end) {
1607 return .eq;
1608 }
15881609
1589 // TODO: Cache this on self so we can re-use the allocations?1610 return .gt;
1590 var vm = call_frame.VirtualMachine{};1611 }
1591 defer vm.deinit(allocator);1612 }
1613 }.compareFn);
15921614
1593 const row = try vm.runToNative(allocator, unmapped_pc, cie.*, fde.*);1615 fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE;
1616 cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE;
1617 }
1618
1619 context.vm.reset();
1620
1621 const row = try context.vm.runToNative(allocator, mapped_pc, cie, fde);
1594 context.cfa = switch (row.cfa.rule) {1622 context.cfa = switch (row.cfa.rule) {
1595 .val_offset => |offset| blk: {1623 .val_offset => |offset| blk: {
1596 const register = row.cfa.register orelse return error.InvalidCFARule;1624 const register = row.cfa.register orelse return error.InvalidCFARule;
1597 const value = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, register, context.reg_ctx));1625 const value = mem.readIntSliceNative(usize, try abi.regBytes(&context.ucontext, register, context.reg_ctx));
1598
1599 // TODO: Check isValidMemory?
1600 break :blk try call_frame.applyOffset(value, offset);1626 break :blk try call_frame.applyOffset(value, offset);
1601 },1627 },
1602 .expression => |expression| {1628 .expression => |expression| {
16031629
1604 // TODO: Evaluate expression1630 // TODO: Evaluate expression
1605 _ = expression;1631 _ = expression;
1632
1606 return error.UnimplementedTODO;1633 return error.UnimplementedTODO;
1607 },1634 },
1608 else => return error.InvalidCFARule,1635 else => return error.InvalidCFARule,
1609 };1636 };
16101637
1638 if (!context.isValidMemory(context.cfa.?)) return error.InvalidCFA;
1639
1611 // Update the context with the previous frame's values1640 // Update the context with the previous frame's values
1612 var next_ucontext = context.ucontext;1641 var next_ucontext = context.ucontext;
16131642
1614 var has_next_ip = false;1643 var has_next_ip = false;
1615 for (vm.rowColumns(row)) |column| {1644 for (context.vm.rowColumns(row)) |column| {
1616 if (column.register) |register| {1645 if (column.register) |register| {
1617 const dest = try abi.regBytes(&next_ucontext, register, context.reg_ctx);1646 const dest = try abi.regBytes(&next_ucontext, register, context.reg_ctx);
1618 if (register == cie.return_address_register) {1647 if (register == cie.return_address_register) {
...@@ -1640,17 +1669,24 @@ pub const UnwindContext = struct {...@@ -1640,17 +1669,24 @@ pub const UnwindContext = struct {
1640 pc: usize,1669 pc: usize,
1641 ucontext: os.ucontext_t,1670 ucontext: os.ucontext_t,
1642 reg_ctx: abi.RegisterContext,1671 reg_ctx: abi.RegisterContext,
1672 isValidMemory: *const fn (address: usize) bool,
1673 vm: call_frame.VirtualMachine = .{},
16431674
1644 pub fn init(ucontext: *const os.ucontext_t) !UnwindContext {1675 pub fn init(ucontext: *const os.ucontext_t, isValidMemory: *const fn (address: usize) bool) !UnwindContext {
1645 const pc = mem.readIntSliceNative(usize, try abi.regBytes(ucontext, abi.ipRegNum(), null));1676 const pc = mem.readIntSliceNative(usize, try abi.regBytes(ucontext, abi.ipRegNum(), null));
1646 return .{1677 return .{
1647 .cfa = null,1678 .cfa = null,
1648 .pc = pc,1679 .pc = pc,
1649 .ucontext = ucontext.*,1680 .ucontext = ucontext.*,
1650 .reg_ctx = undefined,1681 .reg_ctx = undefined,
1682 .isValidMemory = isValidMemory,
1651 };1683 };
1652 }1684 }
16531685
1686 pub fn deinit(self: *UnwindContext, allocator: mem.Allocator) void {
1687 self.vm.deinit(allocator);
1688 }
1689
1654 pub fn getFp(self: *const UnwindContext) !usize {1690 pub fn getFp(self: *const UnwindContext) !usize {
1655 return mem.readIntSliceNative(usize, try abi.regBytes(&self.ucontext, abi.fpRegNum(self.reg_ctx), self.reg_ctx));1691 return mem.readIntSliceNative(usize, try abi.regBytes(&self.ucontext, abi.fpRegNum(self.reg_ctx), self.reg_ctx));
1656 }1692 }
...@@ -1694,7 +1730,7 @@ const EhPointerContext = struct {...@@ -1694,7 +1730,7 @@ const EhPointerContext = struct {
16941730
1695 // Whether or not to follow indirect pointers. This should only be1731 // Whether or not to follow indirect pointers. This should only be
1696 // used when decoding pointers at runtime using the current process's1732 // used when decoding pointers at runtime using the current process's
1697 // debug info.1733 // debug info
1698 follow_indirect: bool,1734 follow_indirect: bool,
16991735
1700 // These relative addressing modes are only used in specific cases, and1736 // These relative addressing modes are only used in specific cases, and
...@@ -1762,15 +1798,178 @@ fn readEhPointer(reader: anytype, enc: u8, addr_size_bytes: u8, ctx: EhPointerCo...@@ -1762,15 +1798,178 @@ fn readEhPointer(reader: anytype, enc: u8, addr_size_bytes: u8, ctx: EhPointerCo
1762 }1798 }
1763}1799}
17641800
1801/// This represents the decoded .eh_frame_hdr header
1802pub const ExceptionFrameHeader = struct {
1803 eh_frame_ptr: usize,
1804 table_enc: u8,
1805 fde_count: usize,
1806 entries: []const u8,
1807
1808 pub fn entrySize(table_enc: u8) !u8 {
1809 return switch (table_enc & EH.PE.type_mask) {
1810 EH.PE.udata2,
1811 EH.PE.sdata2,
1812 => 4,
1813 EH.PE.udata4,
1814 EH.PE.sdata4,
1815 => 8,
1816 EH.PE.udata8,
1817 EH.PE.sdata8,
1818 => 16,
1819 // This is a binary search table, so all entries must be the same length
1820 else => return badDwarf(),
1821 };
1822 }
1823
1824 pub fn findEntry(
1825 self: ExceptionFrameHeader,
1826 isValidMemory: *const fn (address: usize) bool,
1827 eh_frame_hdr_ptr: usize,
1828 pc: usize,
1829 cie: *CommonInformationEntry,
1830 fde: *FrameDescriptionEntry,
1831 ) !void {
1832 const entry_size = try entrySize(self.table_enc);
1833
1834 var left: usize = 0;
1835 var len: usize = self.fde_count;
1836
1837 var stream = io.fixedBufferStream(self.entries);
1838 const reader = stream.reader();
1839
1840 while (len > 1) {
1841 const mid = left + len / 2;
1842
1843 try stream.seekTo(mid * entry_size);
1844 const pc_begin = try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{
1845 .pc_rel_base = @ptrToInt(&self.entries[stream.pos]),
1846 .follow_indirect = true,
1847 .data_rel_base = eh_frame_hdr_ptr,
1848 }, builtin.cpu.arch.endian()) orelse return badDwarf();
1849
1850 if (pc >= pc_begin) left = mid;
1851 if (pc == pc_begin) break;
1852
1853 len /= 2;
1854 }
1855
1856 try stream.seekTo(left * entry_size);
1857
1858 // Read past pc_begin
1859 _ = try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{
1860 .pc_rel_base = @ptrToInt(&self.entries[stream.pos]),
1861 .follow_indirect = true,
1862 .data_rel_base = eh_frame_hdr_ptr,
1863 }, builtin.cpu.arch.endian()) orelse return badDwarf();
1864
1865 const fde_ptr = try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{
1866 .pc_rel_base = @ptrToInt(&self.entries[stream.pos]),
1867 .follow_indirect = true,
1868 .data_rel_base = eh_frame_hdr_ptr,
1869 }, builtin.cpu.arch.endian()) orelse return badDwarf();
1870
1871 // TODO: Should this also do isValidMemory(fde_ptr) + 11 (worst case header size)?
1872
1873 // The length of the .eh_frame section is unknown at this point, since .eh_frame_hdr only provides the start
1874 if (!isValidMemory(fde_ptr) or fde_ptr < self.eh_frame_ptr) return badDwarf();
1875 const eh_frame = @intToPtr([*]const u8, self.eh_frame_ptr)[0..math.maxInt(usize)];
1876 const fde_offset = fde_ptr - self.eh_frame_ptr;
1877
1878 var eh_frame_stream = io.fixedBufferStream(eh_frame);
1879 try eh_frame_stream.seekTo(fde_offset);
1880
1881 const fde_entry_header = try EntryHeader.read(&eh_frame_stream, builtin.cpu.arch.endian());
1882 if (!isValidMemory(@ptrToInt(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]))) return badDwarf();
1883 if (fde_entry_header.type != .fde) return badDwarf();
1884
1885 const cie_offset = fde_entry_header.type.fde;
1886 try eh_frame_stream.seekTo(cie_offset);
1887 const cie_entry_header = try EntryHeader.read(&eh_frame_stream, builtin.cpu.arch.endian());
1888 if (!isValidMemory(@ptrToInt(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]))) return badDwarf();
1889 if (cie_entry_header.type != .cie) return badDwarf();
1890
1891 cie.* = try CommonInformationEntry.parse(
1892 cie_entry_header.entry_bytes,
1893 0,
1894 true,
1895 cie_entry_header.length_offset,
1896 @sizeOf(usize),
1897 builtin.cpu.arch.endian(),
1898 );
1899
1900 fde.* = try FrameDescriptionEntry.parse(
1901 fde_entry_header.entry_bytes,
1902 0,
1903 true,
1904 cie.*,
1905 @sizeOf(usize),
1906 builtin.cpu.arch.endian(),
1907 );
1908 }
1909};
1910
1911pub const EntryHeader = struct {
1912 /// Offset of the length in the backing buffer
1913 length_offset: usize,
1914 is_64: bool,
1915 type: union(enum) {
1916 cie,
1917 /// Value is the offset of the corresponding CIE
1918 fde: u64,
1919 terminator: void,
1920 },
1921 /// The entry's contents, not including the ID field
1922 entry_bytes: []const u8,
1923
1924 /// Reads a header for either an FDE or a CIE, then advances the stream to the position after the trailing structure.
1925 /// `stream` must be a stream backed by the .eh_frame section.
1926 pub fn read(stream: *std.io.FixedBufferStream([]const u8), endian: std.builtin.Endian) !EntryHeader {
1927 const reader = stream.reader();
1928 const length_offset = stream.pos;
1929
1930 var is_64: bool = undefined;
1931 const length = math.cast(usize, try readUnitLength(reader, endian, &is_64)) orelse return badDwarf();
1932 if (length == 0) return .{
1933 .length_offset = length_offset,
1934 .is_64 = is_64,
1935 .type = .{ .terminator = {} },
1936 .entry_bytes = &.{},
1937 };
1938
1939 const id_len = @as(u8, if (is_64) 8 else 4);
1940 const id = if (is_64) try reader.readInt(u64, endian) else try reader.readInt(u32, endian);
1941 const entry_bytes = stream.buffer[stream.pos..][0 .. length - id_len];
1942
1943 const result = EntryHeader{
1944 .length_offset = length_offset,
1945 .is_64 = is_64,
1946 .type = switch (id) {
1947 0 => .{ .cie = {} },
1948 // TODO: Support CommonInformationEntry.dwarf32_id, CommonInformationEntry.dwarf64_id
1949 else => .{ .fde = stream.pos - id_len - id },
1950 },
1951 .entry_bytes = entry_bytes,
1952 };
1953
1954 stream.pos += entry_bytes.len;
1955 return result;
1956 }
1957
1958 /// The length of the entry including the ID field, but not the length field itself
1959 pub fn entryLength(self: EntryHeader) usize {
1960 return self.entry_bytes.len + @as(u8, if (self.is_64) 8 else 4);
1961 }
1962};
1963
1765pub const CommonInformationEntry = struct {1964pub const CommonInformationEntry = struct {
1766 // Used in .eh_frame1965 // Used in .eh_frame
1767 pub const eh_id = 0;1966 pub const eh_id = 0;
17681967
1769 // Used in .debug_frame (DWARF32)1968 // Used in .debug_frame (DWARF32)
1770 pub const dwarf32_id = std.math.maxInt(u32);1969 pub const dwarf32_id = math.maxInt(u32);
17711970
1772 // Used in .debug_frame (DWARF64)1971 // Used in .debug_frame (DWARF64)
1773 pub const dwarf64_id = std.math.maxInt(u64);1972 pub const dwarf64_id = math.maxInt(u64);
17741973
1775 // Offset of the length field of this entry in the eh_frame section.1974 // Offset of the length field of this entry in the eh_frame section.
1776 // This is the key that FDEs use to reference CIEs.1975 // This is the key that FDEs use to reference CIEs.
...@@ -1804,12 +2003,17 @@ pub const CommonInformationEntry = struct {...@@ -1804,12 +2003,17 @@ pub const CommonInformationEntry = struct {
1804 return false;2003 return false;
1805 }2004 }
18062005
1807 // This function expects to read the CIE starting with the version field.2006 /// This function expects to read the CIE starting with the version field.
1808 // The returned struct references memory backed by cie_bytes.2007 /// The returned struct references memory backed by cie_bytes.
2008 ///
2009 /// See the FrameDescriptionEntry.parse documentation for the description
2010 /// of `pc_rel_offset` and `is_runtime`.
2011 ///
2012 /// `length_offset` specifies the offset of this CIE's length field in the
2013 /// .eh_frame section.
1809 pub fn parse(2014 pub fn parse(
1810 cie_bytes: []const u8,2015 cie_bytes: []const u8,
1811 section_base: u64,2016 pc_rel_offset: i64,
1812 section_offset: u64,
1813 is_runtime: bool,2017 is_runtime: bool,
1814 length_offset: u64,2018 length_offset: u64,
1815 addr_size_bytes: u8,2019 addr_size_bytes: u8,
...@@ -1879,7 +2083,7 @@ pub const CommonInformationEntry = struct {...@@ -1879,7 +2083,7 @@ pub const CommonInformationEntry = struct {
1879 personality_enc.?,2083 personality_enc.?,
1880 addr_size_bytes,2084 addr_size_bytes,
1881 .{2085 .{
1882 .pc_rel_base = @ptrToInt(&cie_bytes[stream.pos]) - section_base + section_offset,2086 .pc_rel_base = try pcRelBase(@ptrToInt(&cie_bytes[stream.pos]), pc_rel_offset),
1883 .follow_indirect = is_runtime,2087 .follow_indirect = is_runtime,
1884 },2088 },
1885 endian,2089 endian,
...@@ -1926,11 +2130,22 @@ pub const FrameDescriptionEntry = struct {...@@ -1926,11 +2130,22 @@ pub const FrameDescriptionEntry = struct {
1926 aug_data: []const u8,2130 aug_data: []const u8,
1927 instructions: []const u8,2131 instructions: []const u8,
19282132
1929 // This function expects to read the FDE starting with the PC Begin field2133 /// This function expects to read the FDE starting at the PC Begin field.
2134 /// The returned struct references memory backed by fde_bytes.
2135 ///
2136 /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values
2137 /// used when decoding pointers. This should be set to zero if fde_bytes is
2138 /// backed by the memory of the .eh_frame section in the running executable.
2139 ///
2140 /// Otherwise, it should be the relative offset to translate addresses from
2141 /// where the section is currently stored in memory, to where it *would* be
2142 /// stored at runtime: section runtime offset - backing section data base ptr.
2143 ///
2144 /// Similarly, `is_runtime` specifies this function is being called on a runtime section, and so
2145 /// indirect pointers can be followed.
1930 pub fn parse(2146 pub fn parse(
1931 fde_bytes: []const u8,2147 fde_bytes: []const u8,
1932 section_base: u64,2148 pc_rel_offset: i64,
1933 section_offset: u64,
1934 is_runtime: bool,2149 is_runtime: bool,
1935 cie: CommonInformationEntry,2150 cie: CommonInformationEntry,
1936 addr_size_bytes: u8,2151 addr_size_bytes: u8,
...@@ -1946,7 +2161,7 @@ pub const FrameDescriptionEntry = struct {...@@ -1946,7 +2161,7 @@ pub const FrameDescriptionEntry = struct {
1946 cie.fde_pointer_enc,2161 cie.fde_pointer_enc,
1947 addr_size_bytes,2162 addr_size_bytes,
1948 .{2163 .{
1949 .pc_rel_base = @ptrToInt(&fde_bytes[stream.pos]) - section_base + section_offset,2164 .pc_rel_base = try pcRelBase(@ptrToInt(&fde_bytes[stream.pos]), pc_rel_offset),
1950 .follow_indirect = is_runtime,2165 .follow_indirect = is_runtime,
1951 },2166 },
1952 endian,2167 endian,
...@@ -1975,7 +2190,7 @@ pub const FrameDescriptionEntry = struct {...@@ -1975,7 +2190,7 @@ pub const FrameDescriptionEntry = struct {
1975 cie.lsda_pointer_enc,2190 cie.lsda_pointer_enc,
1976 addr_size_bytes,2191 addr_size_bytes,
1977 .{2192 .{
1978 .pc_rel_base = @ptrToInt(&fde_bytes[stream.pos]) - section_base + section_offset,2193 .pc_rel_base = try pcRelBase(@ptrToInt(&fde_bytes[stream.pos]), pc_rel_offset),
1979 .follow_indirect = is_runtime,2194 .follow_indirect = is_runtime,
1980 },2195 },
1981 endian,2196 endian,
...@@ -1998,3 +2213,11 @@ pub const FrameDescriptionEntry = struct {...@@ -1998,3 +2213,11 @@ pub const FrameDescriptionEntry = struct {
1998 };2213 };
1999 }2214 }
2000};2215};
2216
2217fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize {
2218 if (pc_rel_offset < 0) {
2219 return math.sub(usize, field_ptr, @intCast(usize, -pc_rel_offset));
2220 } else {
2221 return math.add(usize, field_ptr, @intCast(usize, pc_rel_offset));
2222 }
2223}
lib/std/dwarf/call_frame.zig+7
...@@ -354,6 +354,13 @@ pub const VirtualMachine = struct {...@@ -354,6 +354,13 @@ pub const VirtualMachine = struct {
354 self.* = undefined;354 self.* = undefined;
355 }355 }
356356
357 pub fn reset(self: *VirtualMachine) void {
358 self.stack.clearRetainingCapacity();
359 self.columns.clearRetainingCapacity();
360 self.current_row = .{};
361 self.cie_row = null;
362 }
363
357 /// Return a slice backed by the row's non-CFA columns364 /// Return a slice backed by the row's non-CFA columns
358 pub fn rowColumns(self: VirtualMachine, row: Row) []Column {365 pub fn rowColumns(self: VirtualMachine, row: Row) []Column {
359 return self.columns.items[row.columns.start..][0..row.columns.len];366 return self.columns.items[row.columns.start..][0..row.columns.len];