From 48d584e3a33a76ef4ea643905a11d311e9ed8bbf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 1 Aug 2024 22:04:40 -0700 Subject: std.debug: reorg and clarify API goals After this commit: `std.debug.SelfInfo` is a cross-platform abstraction for the current executable's own debug information, with a goal of minimal code bloat and compilation speed penalty. `std.debug.Dwarf` does not assume the current executable is itself the thing being debugged, however, it does assume the debug info has the same CPU architecture and OS as the current executable. It is planned to remove this limitation. --- lib/std/debug/Dwarf/expression.zig | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'lib/std/debug/Dwarf/expression.zig') diff --git a/lib/std/debug/Dwarf/expression.zig b/lib/std/debug/Dwarf/expression.zig index 6243ea9717..7a3a4ed740 100644 --- a/lib/std/debug/Dwarf/expression.zig +++ b/lib/std/debug/Dwarf/expression.zig @@ -1,11 +1,13 @@ -const std = @import("std"); const builtin = @import("builtin"); +const native_arch = builtin.cpu.arch; +const native_endian = native_arch.endian(); + +const std = @import("std"); const leb = std.leb; const OP = std.dwarf.OP; const abi = std.debug.Dwarf.abi; const mem = std.mem; const assert = std.debug.assert; -const native_endian = builtin.cpu.arch.endian(); /// Expressions can be evaluated in different contexts, each requiring its own set of inputs. /// Callers should specify all the fields relevant to their context. If a field is required @@ -14,7 +16,7 @@ pub const Context = struct { /// The dwarf format of the section this expression is in format: std.dwarf.Format = .@"32", /// If specified, any addresses will pass through before being accessed - memory_accessor: ?*std.debug.StackIterator.MemoryAccessor = null, + memory_accessor: ?*std.debug.MemoryAccessor = null, /// The compilation unit this expression relates to, if any compile_unit: ?*const std.debug.Dwarf.CompileUnit = null, /// When evaluating a user-presented expression, this is the address of the object being evaluated @@ -34,7 +36,7 @@ pub const Options = struct { /// The address size of the target architecture addr_size: u8 = @sizeOf(usize), /// Endianness of the target architecture - endian: std.builtin.Endian = builtin.target.cpu.arch.endian(), + endian: std.builtin.Endian = native_endian, /// Restrict the stack machine to a subset of opcodes used in call frame instructions call_frame_context: bool = false, }; @@ -60,7 +62,7 @@ pub const Error = error{ InvalidTypeLength, TruncatedIntegralType, -} || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; +} || abi.RegBytesError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; /// A stack machine that can decode and run DWARF expressions. /// Expressions can be decoded for non-native address size and endianness, @@ -304,7 +306,7 @@ pub fn StackMachine(comptime options: Options) type { allocator: std.mem.Allocator, context: Context, ) Error!bool { - if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian()) + if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != native_endian) @compileError("Execution of non-native address sizes / endianness is not supported"); const opcode = try stream.reader().readByte(); @@ -1186,13 +1188,13 @@ test "DWARF expressions" { // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); - (try abi.regValueNative(usize, &thread_context, abi.fpRegNum(reg_context), reg_context)).* = 1; - (try abi.regValueNative(usize, &thread_context, abi.spRegNum(reg_context), reg_context)).* = 2; - (try abi.regValueNative(usize, &thread_context, abi.ipRegNum(), reg_context)).* = 3; + (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1; + (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2; + (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch), reg_context)).* = 3; - try b.writeBreg(writer, abi.fpRegNum(reg_context), @as(usize, 100)); - try b.writeBreg(writer, abi.spRegNum(reg_context), @as(usize, 200)); - try b.writeBregx(writer, abi.ipRegNum(), @as(usize, 300)); + try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100)); + try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200)); + try b.writeBregx(writer, abi.ipRegNum(native_arch), @as(usize, 300)); try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); _ = try stack_machine.run(program.items, allocator, context, 0); -- cgit v1.2.3 From 975c185b92a7d470ea705b28f46a8004bdda3c60 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Aug 2024 12:00:08 -0700 Subject: fix compilation on powerpc GNU systems ...which have a ucontext_t but not a PC register. The current stack unwinding implementation does not yet support this architecture. Also fix name of `std.debug.SelfInfo.openSelf` to remove redundancy. Also removed this hook into root providing an "openSelfDebugInfo" function. Sorry, this debugging code is not of sufficient quality to offer a plugin API right now. --- lib/std/debug.zig | 22 +++++++++++++++------- lib/std/debug/Dwarf.zig | 24 ++++++++++++++++++++++++ lib/std/debug/Dwarf/abi.zig | 27 +++------------------------ lib/std/debug/Dwarf/expression.zig | 4 ++-- lib/std/debug/SelfInfo.zig | 13 ++++++------- test/standalone/coff_dwarf/main.zig | 2 +- 6 files changed, 51 insertions(+), 41 deletions(-) (limited to 'lib/std/debug/Dwarf/expression.zig') diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 0f10bada71..4d3437f665 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -88,7 +88,7 @@ pub fn getSelfDebugInfo() !*SelfInfo { if (self_debug_info) |*info| { return info; } else { - self_debug_info = try SelfInfo.openSelf(getDebugInfoAllocator()); + self_debug_info = try SelfInfo.open(getDebugInfoAllocator()); return &self_debug_info.?; } } @@ -573,17 +573,19 @@ pub const StackIterator = struct { pub fn initWithContext(first_address: ?usize, debug_info: *SelfInfo, context: *posix.ucontext_t) !StackIterator { // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. - if (builtin.target.isDarwin() and native_arch == .aarch64) { + if (builtin.target.isDarwin() and native_arch == .aarch64) return init(first_address, context.mcontext.ss.fp); - } else { + + if (SelfInfo.supports_unwinding) { var iterator = init(first_address, null); iterator.unwind_state = .{ .debug_info = debug_info, .dwarf_context = try SelfInfo.UnwindContext.init(debug_info.allocator, context), }; - return iterator; } + + return init(first_address, null); } pub fn deinit(it: *StackIterator) void { @@ -725,11 +727,13 @@ pub fn writeCurrentStackTrace( tty_config: io.tty.Config, start_addr: ?usize, ) !void { - var context: ThreadContext = undefined; - const has_context = getContext(&context); if (native_os == .windows) { + var context: ThreadContext = undefined; + assert(getContext(&context)); return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); } + var context: ThreadContext = undefined; + const has_context = getContext(&context); var it = (if (has_context) blk: { break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null; @@ -1340,7 +1344,7 @@ test "manage resources correctly" { } const writer = std.io.null_writer; - var di = try SelfInfo.openSelf(testing.allocator); + var di = try SelfInfo.open(testing.allocator); defer di.deinit(); try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr())); } @@ -1581,5 +1585,9 @@ pub inline fn inValgrind() bool { } test { + _ = &Dwarf; + _ = &MemoryAccessor; + _ = &Pdb; + _ = &SelfInfo; _ = &dumpHex; } diff --git a/lib/std/debug/Dwarf.zig b/lib/std/debug/Dwarf.zig index 991c731549..e30e3d06ac 100644 --- a/lib/std/debug/Dwarf.zig +++ b/lib/std/debug/Dwarf.zig @@ -2023,3 +2023,27 @@ fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); } } + +pub fn supportsUnwinding(target: std.Target) bool { + return switch (target.cpu.arch) { + .x86 => switch (target.os.tag) { + .linux, .netbsd, .solaris, .illumos => true, + else => false, + }, + .x86_64 => switch (target.os.tag) { + .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, + else => false, + }, + .arm => switch (target.os.tag) { + .linux => true, + else => false, + }, + .aarch64 => switch (target.os.tag) { + .linux, .netbsd, .freebsd, .macos, .ios => true, + else => false, + }, + // Unwinding is possible on other targets but this implementation does + // not support them...yet! + else => false, + }; +} diff --git a/lib/std/debug/Dwarf/abi.zig b/lib/std/debug/Dwarf/abi.zig index e87f023d72..f153a10ba4 100644 --- a/lib/std/debug/Dwarf/abi.zig +++ b/lib/std/debug/Dwarf/abi.zig @@ -5,35 +5,14 @@ const mem = std.mem; const posix = std.posix; const Arch = std.Target.Cpu.Arch; -pub fn supportsUnwinding(target: std.Target) bool { - return switch (target.cpu.arch) { - .x86 => switch (target.os.tag) { - .linux, .netbsd, .solaris, .illumos => true, - else => false, - }, - .x86_64 => switch (target.os.tag) { - .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, - else => false, - }, - .arm => switch (target.os.tag) { - .linux => true, - else => false, - }, - .aarch64 => switch (target.os.tag) { - .linux, .netbsd, .freebsd, .macos, .ios => true, - else => false, - }, - else => false, - }; -} - -pub fn ipRegNum(arch: Arch) u8 { +/// Returns `null` for CPU architectures without an instruction pointer register. +pub fn ipRegNum(arch: Arch) ?u8 { return switch (arch) { .x86 => 8, .x86_64 => 16, .arm => 15, .aarch64 => 32, - else => unreachable, + else => null, }; } diff --git a/lib/std/debug/Dwarf/expression.zig b/lib/std/debug/Dwarf/expression.zig index 7a3a4ed740..5fab56de6e 100644 --- a/lib/std/debug/Dwarf/expression.zig +++ b/lib/std/debug/Dwarf/expression.zig @@ -1190,11 +1190,11 @@ test "DWARF expressions" { mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); (try abi.regValueNative(&thread_context, abi.fpRegNum(native_arch, reg_context), reg_context)).* = 1; (try abi.regValueNative(&thread_context, abi.spRegNum(native_arch, reg_context), reg_context)).* = 2; - (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch), reg_context)).* = 3; + (try abi.regValueNative(&thread_context, abi.ipRegNum(native_arch).?, reg_context)).* = 3; try b.writeBreg(writer, abi.fpRegNum(native_arch, reg_context), @as(usize, 100)); try b.writeBreg(writer, abi.spRegNum(native_arch, reg_context), @as(usize, 200)); - try b.writeBregx(writer, abi.ipRegNum(native_arch), @as(usize, 300)); + try b.writeBregx(writer, abi.ipRegNum(native_arch).?, @as(usize, 300)); try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); _ = try stack_machine.run(program.items, allocator, context, 0); diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index 80a8cb4cd9..b1679a224b 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -34,18 +34,15 @@ allocator: Allocator, address_map: std.AutoHashMap(usize, *Module), modules: if (native_os == .windows) std.ArrayListUnmanaged(WindowsModule) else void, -pub const OpenSelfError = error{ +pub const OpenError = error{ MissingDebugInfo, UnsupportedOperatingSystem, } || @typeInfo(@typeInfo(@TypeOf(SelfInfo.init)).Fn.return_type.?).ErrorUnion.error_set; -pub fn openSelf(allocator: Allocator) OpenSelfError!SelfInfo { +pub fn open(allocator: Allocator) OpenError!SelfInfo { nosuspend { if (builtin.strip_debug_info) return error.MissingDebugInfo; - if (@hasDecl(root, "os") and @hasDecl(root.os, "debug") and @hasDecl(root.os.debug, "openSelfDebugInfo")) { - return root.os.debug.openSelfDebugInfo(allocator); - } switch (native_os) { .linux, .freebsd, @@ -1721,6 +1718,8 @@ pub const UnwindContext = struct { allocator: Allocator, thread_context: *std.debug.ThreadContext, ) !UnwindContext { + comptime assert(supports_unwinding); + const pc = stripInstructionPtrAuthCode( (try regValueNative(thread_context, ip_reg_num, null)).*, ); @@ -1982,8 +1981,8 @@ fn spRegNum(reg_context: Dwarf.abi.RegisterContext) u8 { return Dwarf.abi.spRegNum(native_arch, reg_context); } -const ip_reg_num = Dwarf.abi.ipRegNum(native_arch); -const supports_unwinding = Dwarf.abi.supportsUnwinding(builtin.target); +const ip_reg_num = Dwarf.abi.ipRegNum(native_arch).?; +pub const supports_unwinding = Dwarf.supportsUnwinding(builtin.target); fn unwindFrameMachODwarf( context: *UnwindContext, diff --git a/test/standalone/coff_dwarf/main.zig b/test/standalone/coff_dwarf/main.zig index 236aa1c5fa..1cf2587e58 100644 --- a/test/standalone/coff_dwarf/main.zig +++ b/test/standalone/coff_dwarf/main.zig @@ -9,7 +9,7 @@ pub fn main() !void { defer assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); - var debug_info = try std.debug.openSelfDebugInfo(allocator); + var debug_info = try std.debug.SelfInfo.open(allocator); defer debug_info.deinit(); var add_addr: usize = undefined; -- cgit v1.2.3