authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-10-01 01:02:58+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-10-01 01:06:13+02:00
logd97954a8ea1ecc48916f234a429e5cd6a2e38310
tree17c7643f2be8fbd95600bd801f4445fac00aa9fd
parent5a71e15f1f5c641ca8ecf9418d0e168e0ac0b2ce
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

test: remove stack_iterator standalone test

Our new stack trace tests cover all the important parts of this.

6 files changed, 0 insertions(+), 407 deletions(-)

test/standalone/build.zig.zon-3
...@@ -163,9 +163,6 @@...@@ -163,9 +163,6 @@
163 .zerolength_check = .{163 .zerolength_check = .{
164 .path = "zerolength_check",164 .path = "zerolength_check",
165 },165 },
166 .stack_iterator = .{
167 .path = "stack_iterator",
168 },
169 .coff_dwarf = .{166 .coff_dwarf = .{
170 .path = "coff_dwarf",167 .path = "coff_dwarf",
171 },168 },
test/standalone/stack_iterator/build.zig deleted-167
...@@ -1,167 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4pub fn build(b: *std.Build) void {
5 const test_step = b.step("test", "Test it");
6 b.default_step = test_step;
7
8 const target = b.standardTargetOptions(.{});
9 const optimize = b.standardOptimizeOption(.{});
10
11 if (target.result.cpu.arch.isRISCV() and target.result.os.tag == .linux) {
12 // https://github.com/ziglang/zig/issues/24310
13 return;
14 }
15
16 // Unwinding with a frame pointer
17 //
18 // getcontext version: zig std
19 //
20 // Unwind info type:
21 // - ELF: DWARF .debug_frame
22 // - MachO: __unwind_info encodings:
23 // - x86_64: RBP_FRAME
24 // - aarch64: FRAME, DWARF
25 {
26 const exe = b.addExecutable(.{
27 .name = "unwind_fp",
28 .root_module = b.createModule(.{
29 .root_source_file = b.path("unwind.zig"),
30 .target = target,
31 .optimize = optimize,
32 .unwind_tables = if (target.result.os.tag.isDarwin()) .async else null,
33 .omit_frame_pointer = false,
34 }),
35 });
36
37 const run_cmd = b.addRunArtifact(exe);
38 test_step.dependOn(&run_cmd.step);
39 }
40
41 // Unwinding without a frame pointer
42 //
43 // getcontext version: zig std
44 //
45 // Unwind info type:
46 // - ELF: DWARF .eh_frame_hdr + .eh_frame
47 // - MachO: __unwind_info encodings:
48 // - x86_64: STACK_IMMD, STACK_IND
49 // - aarch64: FRAMELESS, DWARF
50 {
51 const exe = b.addExecutable(.{
52 .name = "unwind_nofp",
53 .root_module = b.createModule(.{
54 .root_source_file = b.path("unwind.zig"),
55 .target = target,
56 .optimize = optimize,
57 .unwind_tables = .async,
58 .omit_frame_pointer = true,
59 }),
60 // self-hosted lacks omit_frame_pointer support
61 .use_llvm = true,
62 });
63
64 if (builtin.os.tag != .freebsd) {
65 const run_cmd = b.addRunArtifact(exe);
66 test_step.dependOn(&run_cmd.step);
67 } else {
68 test_step.dependOn(&exe.step);
69 }
70 }
71
72 // https://github.com/ziglang/zig/issues/24522
73 //// Unwinding through a C shared library without a frame pointer (libc)
74 ////
75 //// getcontext version: libc
76 ////
77 //// Unwind info type:
78 //// - ELF: DWARF .eh_frame + .debug_frame
79 //// - MachO: __unwind_info encodings:
80 //// - x86_64: STACK_IMMD, STACK_IND
81 //// - aarch64: FRAMELESS, DWARF
82 //{
83 // const c_shared_lib = b.addLibrary(.{
84 // .linkage = .dynamic,
85 // .name = "c_shared_lib",
86 // .root_module = b.createModule(.{
87 // .root_source_file = null,
88 // .target = target,
89 // .optimize = optimize,
90 // .link_libc = true,
91 // .strip = false,
92 // }),
93 // });
94
95 // if (target.result.os.tag == .windows)
96 // c_shared_lib.root_module.addCMacro("LIB_API", "__declspec(dllexport)");
97
98 // c_shared_lib.root_module.addCSourceFile(.{
99 // .file = b.path("shared_lib.c"),
100 // .flags = &.{"-fomit-frame-pointer"},
101 // });
102
103 // const exe = b.addExecutable(.{
104 // .name = "shared_lib_unwind",
105 // .root_module = b.createModule(.{
106 // .root_source_file = b.path("shared_lib_unwind.zig"),
107 // .target = target,
108 // .optimize = optimize,
109 // .unwind_tables = if (target.result.os.tag.isDarwin()) .async else null,
110 // .omit_frame_pointer = true,
111 // }),
112 // // zig objcopy doesn't support incremental binaries
113 // .use_llvm = true,
114 // });
115
116 // exe.root_module.linkLibrary(c_shared_lib);
117
118 // const run_cmd = b.addRunArtifact(exe);
119 // test_step.dependOn(&run_cmd.step);
120
121 // // Separate debug info ELF file
122 // if (target.result.ofmt == .elf) {
123 // const filename = b.fmt("{s}_stripped", .{exe.out_filename});
124 // const stripped_exe = b.addObjCopy(exe.getEmittedBin(), .{
125 // .basename = filename, // set the name for the debuglink
126 // .compress_debug = true,
127 // .strip = .debug,
128 // .extract_to_separate_file = true,
129 // });
130
131 // const run_stripped = std.Build.Step.Run.create(b, b.fmt("run {s}", .{filename}));
132 // run_stripped.addFileArg(stripped_exe.getOutput());
133 // test_step.dependOn(&run_stripped.step);
134 // }
135 //}
136
137 // Unwinding without libc/posix
138 //
139 // No "getcontext" or "ucontext_t"
140 const no_os_targets = [_]std.Target.Os.Tag{ .freestanding, .other };
141 inline for (no_os_targets) |os_tag| {
142 const exe = b.addExecutable(.{
143 .name = "unwind_freestanding",
144 .root_module = b.createModule(.{
145 .root_source_file = b.path("unwind_freestanding.zig"),
146 .target = b.resolveTargetQuery(.{
147 .cpu_arch = .x86_64,
148 .os_tag = os_tag,
149 }),
150 .optimize = optimize,
151 .unwind_tables = null,
152 .omit_frame_pointer = false,
153 }),
154 // self-hosted lacks omit_frame_pointer support
155 .use_llvm = true,
156 });
157
158 // This "freestanding" binary is runnable because it invokes the
159 // Linux exit syscall directly.
160 if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
161 const run_cmd = b.addRunArtifact(exe);
162 test_step.dependOn(&run_cmd.step);
163 } else {
164 test_step.dependOn(&exe.step);
165 }
166 }
167}
test/standalone/stack_iterator/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
15LIB_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 deleted-48
...@@ -1,48 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const debug = std.debug;
4const testing = std.testing;
5
6noinline fn frame4(expected: *[5]usize, unwound: *[5]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 const 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
21noinline fn frame3(expected: *[5]usize, unwound: *[5]usize) void {
22 expected[1] = @returnAddress();
23 frame4(expected, unwound);
24}
25
26fn frame2(expected: *[5]usize, unwound: *[5]usize) callconv(.c) void {
27 expected[2] = @returnAddress();
28 frame3(expected, unwound);
29}
30
31extern fn frame0(
32 expected: *[5]usize,
33 unwound: *[5]usize,
34 frame_2: *const fn (expected: *[5]usize, unwound: *[5]usize) callconv(.c) void,
35) void;
36
37pub fn main() !void {
38 // Disabled until the DWARF unwinder bugs on .aarch64 are solved
39 if (builtin.omit_frame_pointer and comptime builtin.target.os.tag.isDarwin() and builtin.cpu.arch == .aarch64) return;
40 if (builtin.target.os.tag.isDarwin() and builtin.cpu.arch == .x86_64) return; // https://github.com/ziglang/zig/issues/21337
41
42 if (!std.debug.have_ucontext or !std.debug.have_getcontext) return;
43
44 var expected: [5]usize = undefined;
45 var unwound: [5]usize = undefined;
46 frame0(&expected, &unwound, &frame2);
47 try testing.expectEqual(expected, unwound);
48}
test/standalone/stack_iterator/unwind.zig deleted-101
...@@ -1,101 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const fatal = std.process.fatal;
4
5noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
6 expected[0] = @returnAddress();
7 return std.debug.captureCurrentStackTrace(.{
8 .first_address = @returnAddress(),
9 .allow_unsafe_unwind = true,
10 }, addr_buf);
11}
12
13noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
14 // Exercise different __unwind_info / DWARF CFI encodings by forcing some registers to be restored
15 if (builtin.target.ofmt != .c) {
16 switch (builtin.target.cpu.arch) {
17 .x86 => {
18 if (builtin.omit_frame_pointer) {
19 asm volatile (
20 \\movl $3, %%ebx
21 \\movl $1, %%ecx
22 \\movl $2, %%edx
23 \\movl $7, %%edi
24 \\movl $6, %%esi
25 \\movl $5, %%ebp
26 ::: .{ .ebx = true, .ecx = true, .edx = true, .edi = true, .esi = true, .ebp = true });
27 } else {
28 asm volatile (
29 \\movl $3, %%ebx
30 \\movl $1, %%ecx
31 \\movl $2, %%edx
32 \\movl $7, %%edi
33 \\movl $6, %%esi
34 ::: .{ .ebx = true, .ecx = true, .edx = true, .edi = true, .esi = true });
35 }
36 },
37 .x86_64 => {
38 if (builtin.omit_frame_pointer) {
39 asm volatile (
40 \\movq $3, %%rbx
41 \\movq $12, %%r12
42 \\movq $13, %%r13
43 \\movq $14, %%r14
44 \\movq $15, %%r15
45 \\movq $6, %%rbp
46 ::: .{ .rbx = true, .r12 = true, .r13 = true, .r14 = true, .r15 = true, .rbp = true });
47 } else {
48 asm volatile (
49 \\movq $3, %%rbx
50 \\movq $12, %%r12
51 \\movq $13, %%r13
52 \\movq $14, %%r14
53 \\movq $15, %%r15
54 ::: .{ .rbx = true, .r12 = true, .r13 = true, .r14 = true, .r15 = true });
55 }
56 },
57 else => {},
58 }
59 }
60
61 expected[1] = @returnAddress();
62 return frame3(expected, addr_buf);
63}
64
65noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
66 expected[2] = @returnAddress();
67
68 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
69 // to exercise the stack-indirect encoding path
70 var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
71 _ = std.mem.doNotOptimizeAway(&pad);
72
73 return frame2(expected, addr_buf);
74}
75
76noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
77 expected[3] = @returnAddress();
78 return frame1(expected, addr_buf);
79}
80
81pub fn main() void {
82 if (std.debug.cpu_context.Native == noreturn and builtin.omit_frame_pointer) {
83 // Stack unwinding is impossible.
84 return;
85 }
86
87 var expected: [4]usize = undefined;
88 var addr_buf: [4]usize = undefined;
89 const trace = frame0(&expected, &addr_buf);
90 // There may be *more* than 4 frames (due to the caller of `main`); that's okay.
91 if (trace.index < 4) {
92 fatal("expected at least 4 frames, got '{d}':\n{f}", .{ trace.index, &trace });
93 }
94 if (!std.mem.eql(usize, trace.instruction_addresses, &expected)) {
95 const expected_trace: std.builtin.StackTrace = .{
96 .index = 4,
97 .instruction_addresses = &expected,
98 };
99 fatal("expected trace:\n{f}\nactual trace:\n{f}", .{ &expected_trace, &trace });
100 }
101}
test/standalone/stack_iterator/unwind_freestanding.zig deleted-66
...@@ -1,66 +0,0 @@
1//! Test StackIterator on 'freestanding' target. Based on unwind.zig.
2
3const std = @import("std");
4
5noinline fn frame3(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
6 expected[0] = @returnAddress();
7 return std.debug.captureCurrentStackTrace(.{
8 .first_address = @returnAddress(),
9 .allow_unsafe_unwind = true,
10 }, addr_buf);
11}
12
13noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
14 expected[1] = @returnAddress();
15 return frame3(expected, addr_buf);
16}
17
18noinline fn frame1(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
19 expected[2] = @returnAddress();
20
21 // Use a stack frame that is too big to encode in __unwind_info's stack-immediate encoding
22 // to exercise the stack-indirect encoding path
23 var pad: [std.math.maxInt(u8) * @sizeOf(usize) + 1]u8 = undefined;
24 _ = std.mem.doNotOptimizeAway(&pad);
25
26 return frame2(expected, addr_buf);
27}
28
29noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace {
30 expected[3] = @returnAddress();
31 return frame1(expected, addr_buf);
32}
33
34/// No-OS entrypoint
35export fn _start() callconv(.withStackAlign(.c, 1)) noreturn {
36 var expected: [4]usize = undefined;
37 var addr_buf: [4]usize = undefined;
38 const trace = frame0(&expected, &addr_buf);
39
40 // Since we can't print from this freestanding test, we'll just use the exit
41 // code to communicate error conditions.
42
43 // Unlike `unwind.zig`, we can expect *exactly* 4 frames, as we are the
44 // actual entry point and the frame pointer will be 0 on entry.
45 if (trace.index != 4) exit(1);
46 if (trace.instruction_addresses[0] != expected[0]) exit(2);
47 if (trace.instruction_addresses[1] != expected[1]) exit(3);
48 if (trace.instruction_addresses[2] != expected[2]) exit(4);
49 if (trace.instruction_addresses[3] != expected[3]) exit(5);
50
51 exit(0);
52}
53
54fn exit(code: u8) noreturn {
55 // We are intentionally compiling with the target OS being "freestanding" to
56 // exercise std.debug, but we still need to exit the process somehow; so do
57 // the appropriate x86_64-linux syscall.
58 asm volatile (
59 \\movl $60, %%eax
60 \\syscall
61 :
62 : [code] "{edi}" (code),
63 : .{ .edi = true, .eax = true });
64
65 unreachable;
66}