Commit ccc9f82068
Changed files (5)
test
standalone
dwarf_unwinding
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)
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;
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;
-
- const c_shared_lib = b.addSharedLibrary(.{
- .name = "c_shared_lib",
- .target = target,
- .optimize = optimize,
- });
-
- if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)");
-
- c_shared_lib.strip = false;
- c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"});
- c_shared_lib.linkLibC();
-
- const exe = b.addExecutable(.{
- .name = "main",
- .root_source_file = .{ .path = "main.zig" },
- .target = target,
- .optimize = optimize,
- });
-
- exe.omit_frame_pointer = true;
- exe.linkLibrary(c_shared_lib);
- b.installArtifact(exe);
-
- const run_cmd = b.addRunArtifact(exe);
- test_step.dependOn(&run_cmd.step);
+ // 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,
+ });
+
+ exe.omit_frame_pointer = true;
+
+ const run_cmd = b.addRunArtifact(exe);
+ test_step.dependOn(&run_cmd.step);
+ }
+
+ // Test unwinding through a C shared library
+ {
+ const c_shared_lib = b.addSharedLibrary(.{
+ .name = "c_shared_lib",
+ .target = target,
+ .optimize = optimize,
+ });
+
+ if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)");
+
+ c_shared_lib.strip = false;
+ c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"});
+ c_shared_lib.linkLibC();
+
+ 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);
+ }
}
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);
+}