Commit ccc9f82068

kcbanner <kcbanner@gmail.com>
2023-07-01 21:23:37
c: fixup getcontext debug: supports_context -> have_ucontext, supports_getcontext -> have_getcontext test: rework dwarf_unwind test case to also test the non-libc path
1 parent 7bc1695
Changed files (5)
lib
test
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/main.zig → 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);
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);
+}