diff --git a/lib/std/c.zig b/lib/std/c.zig index 6f193fb9ee..860fdab929 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -415,9 +415,9 @@ pub extern "c" fn timer_gettime(timerid: c.timer_t, flags: c_int, curr_value: *c pub usingnamespace if (builtin.os.tag == .linux and builtin.target.isMusl()) struct { // musl does not implement getcontext - const getcontext = std.os.linux.getcontext; + pub const getcontext = std.os.linux.getcontext; } else struct { - extern "c" fn getcontext(ucp: *std.os.ucontext_t) c_int; + pub extern "c" fn getcontext(ucp: *std.os.ucontext_t) c_int; }; pub const max_align_t = if (builtin.abi == .msvc) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 4905672f21..66ab27bbf0 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -136,7 +136,7 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { pub const StackTraceContext = blk: { if (native_os == .windows) { break :blk std.os.windows.CONTEXT; - } else if (StackIterator.supports_context) { + } else if (have_ucontext) { break :blk os.ucontext_t; } else { break :blk void; @@ -420,6 +420,18 @@ pub fn writeStackTrace( } } +pub const have_getcontext = @hasDecl(os.system, "getcontext") and + (builtin.os.tag != .linux or switch (builtin.cpu.arch) { + .x86, .x86_64 => true, + else => false, +}); + +pub const have_ucontext = @hasDecl(os.system, "ucontext_t") and + (builtin.os.tag != .linux or switch (builtin.cpu.arch) { + .mips, .mipsel, .mips64, .mips64el, .riscv64 => false, + else => true, +}); + pub inline fn getContext(context: *StackTraceContext) bool { if (native_os == .windows) { context.* = std.mem.zeroes(windows.CONTEXT); @@ -427,13 +439,7 @@ pub inline fn getContext(context: *StackTraceContext) bool { return true; } - const supports_getcontext = @hasDecl(os.system, "getcontext") and - (builtin.os.tag != .linux or switch (builtin.cpu.arch) { - .x86, .x86_64 => true, - else => false, - }); - - return supports_getcontext and os.system.getcontext(context) == 0; + return have_getcontext and os.system.getcontext(context) == 0; } pub const StackIterator = struct { @@ -445,13 +451,7 @@ pub const StackIterator = struct { // When DebugInfo and a register context is available, this iterator can unwind // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer). debug_info: ?*DebugInfo, - dwarf_context: if (supports_context) DW.UnwindContext else void = undefined, - - pub const supports_context = @hasDecl(os.system, "ucontext_t") and - (builtin.os.tag != .linux or switch (builtin.cpu.arch) { - .mips, .mipsel, .mips64, .mips64el, .riscv64 => false, - else => true, - }); + dwarf_context: if (have_ucontext) DW.UnwindContext else void = undefined, pub fn init(first_address: ?usize, fp: ?usize) StackIterator { if (native_arch == .sparc64) { @@ -476,7 +476,7 @@ pub const StackIterator = struct { } pub fn deinit(self: *StackIterator) void { - if (supports_context) { + if (have_ucontext) { if (self.debug_info) |debug_info| { self.dwarf_context.deinit(debug_info.allocator); } @@ -574,7 +574,7 @@ pub const StackIterator = struct { } fn next_internal(self: *StackIterator) ?usize { - if (supports_context and self.debug_info != null) { + if (have_ucontext and self.debug_info != null) { if (self.dwarf_context.pc == 0) return null; if (self.next_dwarf()) |return_address| { return return_address; diff --git a/test/standalone/dwarf_unwinding/build.zig b/test/standalone/dwarf_unwinding/build.zig index 85d0a9ac7d..c59effda9f 100644 --- a/test/standalone/dwarf_unwinding/build.zig +++ b/test/standalone/dwarf_unwinding/build.zig @@ -7,31 +7,46 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - if (!std.debug.StackIterator.supports_context) return; + // Test unwinding pure zig code (no libc) + { + const exe = b.addExecutable(.{ + .name = "zig_unwind", + .root_source_file = .{ .path = "zig_unwind.zig" }, + .target = target, + .optimize = optimize, + }); - const c_shared_lib = b.addSharedLibrary(.{ - .name = "c_shared_lib", - .target = target, - .optimize = optimize, - }); + exe.omit_frame_pointer = true; - if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)"); + const run_cmd = b.addRunArtifact(exe); + test_step.dependOn(&run_cmd.step); + } - c_shared_lib.strip = false; - c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"}); - c_shared_lib.linkLibC(); + // Test unwinding through a C shared library + { + const c_shared_lib = b.addSharedLibrary(.{ + .name = "c_shared_lib", + .target = target, + .optimize = optimize, + }); - const exe = b.addExecutable(.{ - .name = "main", - .root_source_file = .{ .path = "main.zig" }, - .target = target, - .optimize = optimize, - }); + if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)"); - exe.omit_frame_pointer = true; - exe.linkLibrary(c_shared_lib); - b.installArtifact(exe); + c_shared_lib.strip = false; + c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"}); + c_shared_lib.linkLibC(); - const run_cmd = b.addRunArtifact(exe); - test_step.dependOn(&run_cmd.step); + const exe = b.addExecutable(.{ + .name = "shared_lib_unwind", + .root_source_file = .{ .path = "shared_lib_unwind.zig" }, + .target = target, + .optimize = optimize, + }); + + exe.omit_frame_pointer = true; + exe.linkLibrary(c_shared_lib); + + const run_cmd = b.addRunArtifact(exe); + test_step.dependOn(&run_cmd.step); + } } diff --git a/test/standalone/dwarf_unwinding/main.zig b/test/standalone/dwarf_unwinding/shared_lib_unwind.zig similarity index 85% rename from test/standalone/dwarf_unwinding/main.zig rename to test/standalone/dwarf_unwinding/shared_lib_unwind.zig index 2edf93ea94..8f42197972 100644 --- a/test/standalone/dwarf_unwinding/main.zig +++ b/test/standalone/dwarf_unwinding/shared_lib_unwind.zig @@ -9,7 +9,7 @@ noinline fn frame4(expected: *[4]usize, unwound: *[4]usize) void { testing.expect(debug.getContext(&context)) catch @panic("failed to getContext"); var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo"); - var it = debug.StackIterator.initWithContext(null, debug_info, &context) catch @panic("failed to initWithContext"); + var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext"); defer it.deinit(); for (unwound) |*addr| { @@ -33,6 +33,8 @@ extern fn frame0( ) void; pub fn main() !void { + if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; + var expected: [4]usize = undefined; var unwound: [4]usize = undefined; frame0(&expected, &unwound, &frame2); diff --git a/test/standalone/dwarf_unwinding/zig_unwind.zig b/test/standalone/dwarf_unwinding/zig_unwind.zig new file mode 100644 index 0000000000..707c2b7632 --- /dev/null +++ b/test/standalone/dwarf_unwinding/zig_unwind.zig @@ -0,0 +1,42 @@ +const std = @import("std"); +const debug = std.debug; +const testing = std.testing; + +noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void { + expected[0] = @returnAddress(); + + var context: debug.StackTraceContext = undefined; + testing.expect(debug.getContext(&context)) catch @panic("failed to getContext"); + + var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo"); + var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext"); + defer it.deinit(); + + for (unwound) |*addr| { + if (it.next()) |return_address| addr.* = return_address; + } +} + +noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void { + expected[1] = @returnAddress(); + frame3(expected, unwound); +} + +noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void { + expected[2] = @returnAddress(); + frame2(expected, unwound); +} + +noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void { + expected[3] = @returnAddress(); + frame1(expected, unwound); +} + +pub fn main() !void { + if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; + + var expected: [4]usize = undefined; + var unwound: [4]usize = undefined; + frame0(&expected, &unwound); + try testing.expectEqual(expected, unwound); +}