| author | |
| committer | |
| log | d97954a8ea1ecc48916f234a429e5cd6a2e38310 |
| tree | 17c7643f2be8fbd95600bd801f4445fac00aa9fd |
| parent | 5a71e15f1f5c641ca8ecf9418d0e168e0ac0b2ce |
| signature |
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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | pub 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 | |||
| 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 deleted-48| ... | @@ -1,48 +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 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 | |||
| 21 | noinline fn frame3(expected: *[5]usize, unwound: *[5]usize) void { | ||
| 22 | expected[1] = @returnAddress(); | ||
| 23 | frame4(expected, unwound); | ||
| 24 | } | ||
| 25 | |||
| 26 | fn frame2(expected: *[5]usize, unwound: *[5]usize) callconv(.c) void { | ||
| 27 | expected[2] = @returnAddress(); | ||
| 28 | frame3(expected, unwound); | ||
| 29 | } | ||
| 30 | |||
| 31 | extern 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 | |||
| 37 | pub 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 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const fatal = std.process.fatal; | ||
| 4 | |||
| 5 | noinline 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 | |||
| 13 | noinline 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 | |||
| 65 | noinline 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 | |||
| 76 | noinline fn frame0(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | ||
| 77 | expected[3] = @returnAddress(); | ||
| 78 | return frame1(expected, addr_buf); | ||
| 79 | } | ||
| 80 | |||
| 81 | pub 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 | |||
| 3 | const std = @import("std"); | ||
| 4 | |||
| 5 | noinline 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 | |||
| 13 | noinline fn frame2(expected: *[4]usize, addr_buf: *[4]usize) std.builtin.StackTrace { | ||
| 14 | expected[1] = @returnAddress(); | ||
| 15 | return frame3(expected, addr_buf); | ||
| 16 | } | ||
| 17 | |||
| 18 | noinline 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 | |||
| 29 | noinline 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 | ||
| 35 | export 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 | |||
| 54 | fn 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 | } | ||