| author | |
| committer | |
| log | ec96095efd8671ae280df15eaf73f63bf029fbfa |
| tree | 06308ef842ad3f6984f673d607fabebcbed28a7b |
| parent | 7d8b4234774200ff071103399613ed444280a8d0 |
Renamed dwarf_unwinding -> stack_iterator to better reflect that it's not just DWARF unwinding.
Added a test for unwinding with a frame pointer.11 files changed, 236 insertions(+), 198 deletions(-)
src/Compilation.zig+2| ... | @@ -5288,6 +5288,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca | ... | @@ -5288,6 +5288,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca |
| 5288 | \\pub const position_independent_executable = {}; | 5288 | \\pub const position_independent_executable = {}; |
| 5289 | \\pub const strip_debug_info = {}; | 5289 | \\pub const strip_debug_info = {}; |
| 5290 | \\pub const code_model = std.builtin.CodeModel.{}; | 5290 | \\pub const code_model = std.builtin.CodeModel.{}; |
| 5291 | \\pub const omit_frame_pointer = {}; | ||
| 5291 | \\ | 5292 | \\ |
| 5292 | , .{ | 5293 | , .{ |
| 5293 | std.zig.fmtId(@tagName(target.ofmt)), | 5294 | std.zig.fmtId(@tagName(target.ofmt)), |
| ... | @@ -5301,6 +5302,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca | ... | @@ -5301,6 +5302,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca |
| 5301 | comp.bin_file.options.pie, | 5302 | comp.bin_file.options.pie, |
| 5302 | comp.bin_file.options.strip, | 5303 | comp.bin_file.options.strip, |
| 5303 | std.zig.fmtId(@tagName(comp.bin_file.options.machine_code_model)), | 5304 | std.zig.fmtId(@tagName(comp.bin_file.options.machine_code_model)), |
| 5305 | comp.bin_file.options.omit_frame_pointer, | ||
| 5304 | }); | 5306 | }); |
| 5305 | 5307 | ||
| 5306 | if (target.os.tag == .wasi) { | 5308 | if (target.os.tag == .wasi) { |
src/target.zig+1-1| ... | @@ -510,7 +510,7 @@ pub fn clangAssemblerSupportsMcpuArg(target: std.Target) bool { | ... | @@ -510,7 +510,7 @@ pub fn clangAssemblerSupportsMcpuArg(target: std.Target) bool { |
| 510 | } | 510 | } |
| 511 | 511 | ||
| 512 | pub fn needUnwindTables(target: std.Target) bool { | 512 | pub fn needUnwindTables(target: std.Target) bool { |
| 513 | return target.os.tag == .windows or target.ofmt == .macho; | 513 | return target.os.tag == .windows or target.isDarwin(); |
| 514 | } | 514 | } |
| 515 | 515 | ||
| 516 | pub fn defaultAddressSpace( | 516 | pub fn defaultAddressSpace( |
test/standalone.zig+2-2| ... | @@ -231,8 +231,8 @@ pub const build_cases = [_]BuildCase{ | ... | @@ -231,8 +231,8 @@ pub const build_cases = [_]BuildCase{ |
| 231 | .import = @import("standalone/zerolength_check/build.zig"), | 231 | .import = @import("standalone/zerolength_check/build.zig"), |
| 232 | }, | 232 | }, |
| 233 | .{ | 233 | .{ |
| 234 | .build_root = "test/standalone/dwarf_unwinding", | 234 | .build_root = "test/standalone/stack_iterator", |
| 235 | .import = @import("standalone/dwarf_unwinding/build.zig"), | 235 | .import = @import("standalone/stack_iterator/build.zig"), |
| 236 | }, | 236 | }, |
| 237 | }; | 237 | }; |
| 238 | 238 |
test/standalone/dwarf_unwinding/build.zig deleted-54| ... | @@ -1,54 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub fn build(b: *std.Build) void { | ||
| 4 | const test_step = b.step("test", "Test it"); | ||
| 5 | b.default_step = test_step; | ||
| 6 | |||
| 7 | const target = b.standardTargetOptions(.{}); | ||
| 8 | const optimize = b.standardOptimizeOption(.{}); | ||
| 9 | |||
| 10 | // Test unwinding pure zig code (no libc) | ||
| 11 | { | ||
| 12 | const exe = b.addExecutable(.{ | ||
| 13 | .name = "zig_unwind", | ||
| 14 | .root_source_file = .{ .path = "zig_unwind.zig" }, | ||
| 15 | .target = target, | ||
| 16 | .optimize = optimize, | ||
| 17 | }); | ||
| 18 | |||
| 19 | if (target.isDarwin()) exe.unwind_tables = true; | ||
| 20 | exe.omit_frame_pointer = true; | ||
| 21 | |||
| 22 | const run_cmd = b.addRunArtifact(exe); | ||
| 23 | test_step.dependOn(&run_cmd.step); | ||
| 24 | } | ||
| 25 | |||
| 26 | // Test unwinding through a C shared library | ||
| 27 | { | ||
| 28 | const c_shared_lib = b.addSharedLibrary(.{ | ||
| 29 | .name = "c_shared_lib", | ||
| 30 | .target = target, | ||
| 31 | .optimize = optimize, | ||
| 32 | }); | ||
| 33 | |||
| 34 | if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)"); | ||
| 35 | |||
| 36 | c_shared_lib.strip = false; | ||
| 37 | c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"}); | ||
| 38 | c_shared_lib.linkLibC(); | ||
| 39 | |||
| 40 | const exe = b.addExecutable(.{ | ||
| 41 | .name = "shared_lib_unwind", | ||
| 42 | .root_source_file = .{ .path = "shared_lib_unwind.zig" }, | ||
| 43 | .target = target, | ||
| 44 | .optimize = optimize, | ||
| 45 | }); | ||
| 46 | |||
| 47 | if (target.isDarwin()) exe.unwind_tables = true; | ||
| 48 | exe.omit_frame_pointer = true; | ||
| 49 | exe.linkLibrary(c_shared_lib); | ||
| 50 | |||
| 51 | const run_cmd = b.addRunArtifact(exe); | ||
| 52 | test_step.dependOn(&run_cmd.step); | ||
| 53 | } | ||
| 54 | } | ||
test/standalone/dwarf_unwinding/shared_lib.c deleted-22| ... | @@ -1,22 +0,0 @@ | ||
| 1 | #include <stdint.h> | ||
| 2 | |||
| 3 | #ifndef LIB_API | ||
| 4 | #define LIB_API | ||
| 5 | #endif | ||
| 6 | |||
| 7 | __attribute__((noinline)) void frame1( | ||
| 8 | void** expected, | ||
| 9 | void** unwound, | ||
| 10 | void (*frame2)(void** expected, void** unwound)) { | ||
| 11 | expected[3] = __builtin_extract_return_addr(__builtin_return_address(0)); | ||
| 12 | frame2(expected, unwound); | ||
| 13 | } | ||
| 14 | |||
| 15 | LIB_API void frame0( | ||
| 16 | void** expected, | ||
| 17 | void** unwound, | ||
| 18 | void (*frame2)(void** expected, void** unwound)) { | ||
| 19 | expected[4] = __builtin_extract_return_addr(__builtin_return_address(0)); | ||
| 20 | frame1(expected, unwound, frame2); | ||
| 21 | } | ||
| 22 | |||
test/standalone/dwarf_unwinding/shared_lib_unwind.zig deleted-43| ... | @@ -1,43 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const debug = std.debug; | ||
| 3 | const testing = std.testing; | ||
| 4 | |||
| 5 | noinline fn frame4(expected: *[5]usize, unwound: *[5]usize) void { | ||
| 6 | expected[0] = @returnAddress(); | ||
| 7 | |||
| 8 | var context: debug.ThreadContext = undefined; | ||
| 9 | testing.expect(debug.getContext(&context)) catch @panic("failed to getContext"); | ||
| 10 | |||
| 11 | var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo"); | ||
| 12 | var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext"); | ||
| 13 | defer it.deinit(); | ||
| 14 | |||
| 15 | for (unwound) |*addr| { | ||
| 16 | if (it.next()) |return_address| addr.* = return_address; | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | noinline fn frame3(expected: *[5]usize, unwound: *[5]usize) void { | ||
| 21 | expected[1] = @returnAddress(); | ||
| 22 | frame4(expected, unwound); | ||
| 23 | } | ||
| 24 | |||
| 25 | fn frame2(expected: *[5]usize, unwound: *[5]usize) callconv(.C) void { | ||
| 26 | expected[2] = @returnAddress(); | ||
| 27 | frame3(expected, unwound); | ||
| 28 | } | ||
| 29 | |||
| 30 | extern fn frame0( | ||
| 31 | expected: *[5]usize, | ||
| 32 | unwound: *[5]usize, | ||
| 33 | frame_2: *const fn (expected: *[5]usize, unwound: *[5]usize) callconv(.C) void, | ||
| 34 | ) void; | ||
| 35 | |||
| 36 | pub fn main() !void { | ||
| 37 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; | ||
| 38 | |||
| 39 | var expected: [5]usize = undefined; | ||
| 40 | var unwound: [5]usize = undefined; | ||
| 41 | frame0(&expected, &unwound, &frame2); | ||
| 42 | try testing.expectEqual(expected, unwound); | ||
| 43 | } | ||
test/standalone/dwarf_unwinding/zig_unwind.zig deleted-76| ... | @@ -1,76 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const debug = std.debug; | ||
| 4 | const testing = std.testing; | ||
| 5 | |||
| 6 | noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void { | ||
| 7 | expected[0] = @returnAddress(); | ||
| 8 | |||
| 9 | var context: debug.ThreadContext = undefined; | ||
| 10 | testing.expect(debug.getContext(&context)) catch @panic("failed to getContext"); | ||
| 11 | |||
| 12 | var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo"); | ||
| 13 | var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext"); | ||
| 14 | defer it.deinit(); | ||
| 15 | |||
| 16 | for (unwound) |*addr| { | ||
| 17 | if (it.next()) |return_address| addr.* = return_address; | ||
| 18 | } | ||
| 19 | } | ||
| 20 | |||
| 21 | noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void { | ||
| 22 | // Excercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored | ||
| 23 | if (builtin.target.ofmt != .c) { | ||
| 24 | switch (builtin.cpu.arch) { | ||
| 25 | .x86 => { | ||
| 26 | asm volatile ( | ||
| 27 | \\movl $3, %%ebx | ||
| 28 | \\movl $1, %%ecx | ||
| 29 | \\movl $2, %%edx | ||
| 30 | \\movl $7, %%edi | ||
| 31 | \\movl $6, %%esi | ||
| 32 | \\movl $5, %%ebp | ||
| 33 | ::: "ebx", "ecx", "edx", "edi", "esi", "ebp"); | ||
| 34 | }, | ||
| 35 | .x86_64 => { | ||
| 36 | asm volatile ( | ||
| 37 | \\movq $3, %%rbx | ||
| 38 | \\movq $12, %%r12 | ||
| 39 | \\movq $13, %%r13 | ||
| 40 | \\movq $14, %%r14 | ||
| 41 | \\movq $15, %%r15 | ||
| 42 | \\movq $6, %%rbp | ||
| 43 | ::: "rbx", "r12", "r13", "r14", "r15", "rbp"); | ||
| 44 | }, | ||
| 45 | else => {}, | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | expected[1] = @returnAddress(); | ||
| 50 | frame3(expected, unwound); | ||
| 51 | } | ||
| 52 | |||
| 53 | noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void { | ||
| 54 | expected[2] = @returnAddress(); | ||
| 55 | |||
| 56 | // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding | ||
| 57 | // to exercise the stack-indirect encoding path | ||
| 58 | var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined; | ||
| 59 | _ = pad; | ||
| 60 | |||
| 61 | frame2(expected, unwound); | ||
| 62 | } | ||
| 63 | |||
| 64 | noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void { | ||
| 65 | expected[3] = @returnAddress(); | ||
| 66 | frame1(expected, unwound); | ||
| 67 | } | ||
| 68 | |||
| 69 | pub fn main() !void { | ||
| 70 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; | ||
| 71 | |||
| 72 | var expected: [4]usize = undefined; | ||
| 73 | var unwound: [4]usize = undefined; | ||
| 74 | frame0(&expected, &unwound); | ||
| 75 | try testing.expectEqual(expected, unwound); | ||
| 76 | } | ||
test/standalone/stack_iterator/build.zig created+70| ... | @@ -0,0 +1,70 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | |||
| 3 | pub fn build(b: *std.Build) void { | ||
| 4 | const test_step = b.step("test", "Test it"); | ||
| 5 | b.default_step = test_step; | ||
| 6 | |||
| 7 | const target = b.standardTargetOptions(.{}); | ||
| 8 | const optimize = b.standardOptimizeOption(.{}); | ||
| 9 | |||
| 10 | // Unwinding pure zig code, with a frame pointer | ||
| 11 | { | ||
| 12 | const exe = b.addExecutable(.{ | ||
| 13 | .name = "zig_unwind_fp", | ||
| 14 | .root_source_file = .{ .path = "zig_unwind.zig" }, | ||
| 15 | .target = target, | ||
| 16 | .optimize = optimize, | ||
| 17 | }); | ||
| 18 | |||
| 19 | if (target.isDarwin()) exe.unwind_tables = true; | ||
| 20 | exe.omit_frame_pointer = false; | ||
| 21 | |||
| 22 | const run_cmd = b.addRunArtifact(exe); | ||
| 23 | test_step.dependOn(&run_cmd.step); | ||
| 24 | } | ||
| 25 | |||
| 26 | // Unwinding pure zig code, without a frame pointer | ||
| 27 | { | ||
| 28 | const exe = b.addExecutable(.{ | ||
| 29 | .name = "zig_unwind_nofp", | ||
| 30 | .root_source_file = .{ .path = "zig_unwind.zig" }, | ||
| 31 | .target = target, | ||
| 32 | .optimize = optimize, | ||
| 33 | }); | ||
| 34 | |||
| 35 | if (target.isDarwin()) exe.unwind_tables = true; | ||
| 36 | exe.omit_frame_pointer = true; | ||
| 37 | |||
| 38 | const run_cmd = b.addRunArtifact(exe); | ||
| 39 | test_step.dependOn(&run_cmd.step); | ||
| 40 | } | ||
| 41 | |||
| 42 | // Unwinding through a C shared library without a frame pointer (libc) | ||
| 43 | { | ||
| 44 | const c_shared_lib = b.addSharedLibrary(.{ | ||
| 45 | .name = "c_shared_lib", | ||
| 46 | .target = target, | ||
| 47 | .optimize = optimize, | ||
| 48 | }); | ||
| 49 | |||
| 50 | if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)"); | ||
| 51 | |||
| 52 | c_shared_lib.strip = false; | ||
| 53 | c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"}); | ||
| 54 | c_shared_lib.linkLibC(); | ||
| 55 | |||
| 56 | const exe = b.addExecutable(.{ | ||
| 57 | .name = "shared_lib_unwind", | ||
| 58 | .root_source_file = .{ .path = "shared_lib_unwind.zig" }, | ||
| 59 | .target = target, | ||
| 60 | .optimize = optimize, | ||
| 61 | }); | ||
| 62 | |||
| 63 | if (target.isDarwin()) exe.unwind_tables = true; | ||
| 64 | exe.omit_frame_pointer = true; | ||
| 65 | exe.linkLibrary(c_shared_lib); | ||
| 66 | |||
| 67 | const run_cmd = b.addRunArtifact(exe); | ||
| 68 | test_step.dependOn(&run_cmd.step); | ||
| 69 | } | ||
| 70 | } | ||
test/standalone/stack_iterator/shared_lib.c created+22| ... | @@ -0,0 +1,22 @@ | ||
| 1 | #include <stdint.h> | ||
| 2 | |||
| 3 | #ifndef LIB_API | ||
| 4 | #define LIB_API | ||
| 5 | #endif | ||
| 6 | |||
| 7 | __attribute__((noinline)) void frame1( | ||
| 8 | void** expected, | ||
| 9 | void** unwound, | ||
| 10 | void (*frame2)(void** expected, void** unwound)) { | ||
| 11 | expected[3] = __builtin_extract_return_addr(__builtin_return_address(0)); | ||
| 12 | frame2(expected, unwound); | ||
| 13 | } | ||
| 14 | |||
| 15 | LIB_API void frame0( | ||
| 16 | void** expected, | ||
| 17 | void** unwound, | ||
| 18 | void (*frame2)(void** expected, void** unwound)) { | ||
| 19 | expected[4] = __builtin_extract_return_addr(__builtin_return_address(0)); | ||
| 20 | frame1(expected, unwound, frame2); | ||
| 21 | } | ||
| 22 | |||
test/standalone/stack_iterator/shared_lib_unwind.zig created+43| ... | @@ -0,0 +1,43 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const debug = std.debug; | ||
| 3 | const testing = std.testing; | ||
| 4 | |||
| 5 | noinline fn frame4(expected: *[5]usize, unwound: *[5]usize) void { | ||
| 6 | expected[0] = @returnAddress(); | ||
| 7 | |||
| 8 | var context: debug.ThreadContext = undefined; | ||
| 9 | testing.expect(debug.getContext(&context)) catch @panic("failed to getContext"); | ||
| 10 | |||
| 11 | var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo"); | ||
| 12 | var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext"); | ||
| 13 | defer it.deinit(); | ||
| 14 | |||
| 15 | for (unwound) |*addr| { | ||
| 16 | if (it.next()) |return_address| addr.* = return_address; | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | noinline fn frame3(expected: *[5]usize, unwound: *[5]usize) void { | ||
| 21 | expected[1] = @returnAddress(); | ||
| 22 | frame4(expected, unwound); | ||
| 23 | } | ||
| 24 | |||
| 25 | fn frame2(expected: *[5]usize, unwound: *[5]usize) callconv(.C) void { | ||
| 26 | expected[2] = @returnAddress(); | ||
| 27 | frame3(expected, unwound); | ||
| 28 | } | ||
| 29 | |||
| 30 | extern fn frame0( | ||
| 31 | expected: *[5]usize, | ||
| 32 | unwound: *[5]usize, | ||
| 33 | frame_2: *const fn (expected: *[5]usize, unwound: *[5]usize) callconv(.C) void, | ||
| 34 | ) void; | ||
| 35 | |||
| 36 | pub fn main() !void { | ||
| 37 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; | ||
| 38 | |||
| 39 | var expected: [5]usize = undefined; | ||
| 40 | var unwound: [5]usize = undefined; | ||
| 41 | frame0(&expected, &unwound, &frame2); | ||
| 42 | try testing.expectEqual(expected, unwound); | ||
| 43 | } | ||
test/standalone/stack_iterator/zig_unwind.zig created+96| ... | @@ -0,0 +1,96 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const debug = std.debug; | ||
| 4 | const testing = std.testing; | ||
| 5 | |||
| 6 | noinline fn frame3(expected: *[4]usize, unwound: *[4]usize) void { | ||
| 7 | expected[0] = @returnAddress(); | ||
| 8 | |||
| 9 | var context: debug.ThreadContext = undefined; | ||
| 10 | testing.expect(debug.getContext(&context)) catch @panic("failed to getContext"); | ||
| 11 | |||
| 12 | var debug_info = debug.getSelfDebugInfo() catch @panic("failed to openSelfDebugInfo"); | ||
| 13 | var it = debug.StackIterator.initWithContext(expected[0], debug_info, &context) catch @panic("failed to initWithContext"); | ||
| 14 | defer it.deinit(); | ||
| 15 | |||
| 16 | for (unwound) |*addr| { | ||
| 17 | if (it.next()) |return_address| addr.* = return_address; | ||
| 18 | } | ||
| 19 | } | ||
| 20 | |||
| 21 | noinline fn frame2(expected: *[4]usize, unwound: *[4]usize) void { | ||
| 22 | // Excercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored | ||
| 23 | if (builtin.target.ofmt != .c) { | ||
| 24 | switch (builtin.cpu.arch) { | ||
| 25 | .x86 => { | ||
| 26 | if (builtin.omit_frame_pointer) { | ||
| 27 | asm volatile ( | ||
| 28 | \\movl $3, %%ebx | ||
| 29 | \\movl $1, %%ecx | ||
| 30 | \\movl $2, %%edx | ||
| 31 | \\movl $7, %%edi | ||
| 32 | \\movl $6, %%esi | ||
| 33 | \\movl $5, %%ebp | ||
| 34 | ::: "ebx", "ecx", "edx", "edi", "esi", "ebp"); | ||
| 35 | } else { | ||
| 36 | asm volatile ( | ||
| 37 | \\movl $3, %%ebx | ||
| 38 | \\movl $1, %%ecx | ||
| 39 | \\movl $2, %%edx | ||
| 40 | \\movl $7, %%edi | ||
| 41 | \\movl $6, %%esi | ||
| 42 | ::: "ebx", "ecx", "edx", "edi", "esi"); | ||
| 43 | } | ||
| 44 | }, | ||
| 45 | .x86_64 => { | ||
| 46 | if (builtin.omit_frame_pointer) { | ||
| 47 | asm volatile ( | ||
| 48 | \\movq $3, %%rbx | ||
| 49 | \\movq $12, %%r12 | ||
| 50 | \\movq $13, %%r13 | ||
| 51 | \\movq $14, %%r14 | ||
| 52 | \\movq $15, %%r15 | ||
| 53 | \\movq $6, %%rbp | ||
| 54 | ::: "rbx", "r12", "r13", "r14", "r15", "rbp"); | ||
| 55 | } else { | ||
| 56 | asm volatile ( | ||
| 57 | \\movq $3, %%rbx | ||
| 58 | \\movq $12, %%r12 | ||
| 59 | \\movq $13, %%r13 | ||
| 60 | \\movq $14, %%r14 | ||
| 61 | \\movq $15, %%r15 | ||
| 62 | ::: "rbx", "r12", "r13", "r14", "r15"); | ||
| 63 | } | ||
| 64 | }, | ||
| 65 | else => {}, | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | expected[1] = @returnAddress(); | ||
| 70 | frame3(expected, unwound); | ||
| 71 | } | ||
| 72 | |||
| 73 | noinline fn frame1(expected: *[4]usize, unwound: *[4]usize) void { | ||
| 74 | expected[2] = @returnAddress(); | ||
| 75 | |||
| 76 | // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding | ||
| 77 | // to exercise the stack-indirect encoding path | ||
| 78 | var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined; | ||
| 79 | _ = pad; | ||
| 80 | |||
| 81 | frame2(expected, unwound); | ||
| 82 | } | ||
| 83 | |||
| 84 | noinline fn frame0(expected: *[4]usize, unwound: *[4]usize) void { | ||
| 85 | expected[3] = @returnAddress(); | ||
| 86 | frame1(expected, unwound); | ||
| 87 | } | ||
| 88 | |||
| 89 | pub fn main() !void { | ||
| 90 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; | ||
| 91 | |||
| 92 | var expected: [4]usize = undefined; | ||
| 93 | var unwound: [4]usize = undefined; | ||
| 94 | frame0(&expected, &unwound); | ||
| 95 | try testing.expectEqual(expected, unwound); | ||
| 96 | } | ||