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
This commit is contained in:
kcbanner 2023-07-01 15:23:37 -04:00
parent 7bc1695f15
commit ccc9f82068
5 changed files with 100 additions and 41 deletions

View File

@ -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 { pub usingnamespace if (builtin.os.tag == .linux and builtin.target.isMusl()) struct {
// musl does not implement getcontext // musl does not implement getcontext
const getcontext = std.os.linux.getcontext; pub const getcontext = std.os.linux.getcontext;
} else struct { } 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) pub const max_align_t = if (builtin.abi == .msvc)

View File

@ -136,7 +136,7 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
pub const StackTraceContext = blk: { pub const StackTraceContext = blk: {
if (native_os == .windows) { if (native_os == .windows) {
break :blk std.os.windows.CONTEXT; break :blk std.os.windows.CONTEXT;
} else if (StackIterator.supports_context) { } else if (have_ucontext) {
break :blk os.ucontext_t; break :blk os.ucontext_t;
} else { } else {
break :blk void; 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 { pub inline fn getContext(context: *StackTraceContext) bool {
if (native_os == .windows) { if (native_os == .windows) {
context.* = std.mem.zeroes(windows.CONTEXT); context.* = std.mem.zeroes(windows.CONTEXT);
@ -427,13 +439,7 @@ pub inline fn getContext(context: *StackTraceContext) bool {
return true; return true;
} }
const supports_getcontext = @hasDecl(os.system, "getcontext") and return have_getcontext and os.system.getcontext(context) == 0;
(builtin.os.tag != .linux or switch (builtin.cpu.arch) {
.x86, .x86_64 => true,
else => false,
});
return supports_getcontext and os.system.getcontext(context) == 0;
} }
pub const StackIterator = struct { pub const StackIterator = struct {
@ -445,13 +451,7 @@ pub const StackIterator = struct {
// When DebugInfo and a register context is available, this iterator can unwind // 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). // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer).
debug_info: ?*DebugInfo, debug_info: ?*DebugInfo,
dwarf_context: if (supports_context) DW.UnwindContext else void = undefined, dwarf_context: if (have_ucontext) 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,
});
pub fn init(first_address: ?usize, fp: ?usize) StackIterator { pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
if (native_arch == .sparc64) { if (native_arch == .sparc64) {
@ -476,7 +476,7 @@ pub const StackIterator = struct {
} }
pub fn deinit(self: *StackIterator) void { pub fn deinit(self: *StackIterator) void {
if (supports_context) { if (have_ucontext) {
if (self.debug_info) |debug_info| { if (self.debug_info) |debug_info| {
self.dwarf_context.deinit(debug_info.allocator); self.dwarf_context.deinit(debug_info.allocator);
} }
@ -574,7 +574,7 @@ pub const StackIterator = struct {
} }
fn next_internal(self: *StackIterator) ?usize { 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.dwarf_context.pc == 0) return null;
if (self.next_dwarf()) |return_address| { if (self.next_dwarf()) |return_address| {
return return_address; return return_address;

View File

@ -7,31 +7,46 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); 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(.{ exe.omit_frame_pointer = true;
.name = "c_shared_lib",
.target = target,
.optimize = optimize,
});
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; // Test unwinding through a C shared library
c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"}); {
c_shared_lib.linkLibC(); const c_shared_lib = b.addSharedLibrary(.{
.name = "c_shared_lib",
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{ if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)");
.name = "main",
.root_source_file = .{ .path = "main.zig" },
.target = target,
.optimize = optimize,
});
exe.omit_frame_pointer = true; c_shared_lib.strip = false;
exe.linkLibrary(c_shared_lib); c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"});
b.installArtifact(exe); c_shared_lib.linkLibC();
const run_cmd = b.addRunArtifact(exe); const exe = b.addExecutable(.{
test_step.dependOn(&run_cmd.step); .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);
}
} }

View File

@ -9,7 +9,7 @@ noinline fn frame4(expected: *[4]usize, unwound: *[4]usize) void {
testing.expect(debug.getContext(&context)) catch @panic("failed to getContext"); testing.expect(debug.getContext(&context)) catch @panic("failed to getContext");
var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo"); 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(); defer it.deinit();
for (unwound) |*addr| { for (unwound) |*addr| {
@ -33,6 +33,8 @@ extern fn frame0(
) void; ) void;
pub fn main() !void { pub fn main() !void {
if (!std.debug.have_ucontext or !std.debug.have_getcontext) return;
var expected: [4]usize = undefined; var expected: [4]usize = undefined;
var unwound: [4]usize = undefined; var unwound: [4]usize = undefined;
frame0(&expected, &unwound, &frame2); frame0(&expected, &unwound, &frame2);

View File

@ -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);
}