| author | |
| committer | |
| log | 8e6a62ba10326e48eaefd40f89c9452d92f39c9d |
| tree | 9d0baca4c2209676996c3ce0a0530541f989ae51 |
| parent | 6d87bb370a6d9075b2b6628f5f8c09171f25e4e9 |
dwarf: handle signal frame CIE flag6 files changed, 119 insertions(+), 110 deletions(-)
lib/std/debug.zig+1-1| ... | ... | @@ -882,7 +882,7 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz |
| 882 | 882 | pub fn printUnwindError(debug_info: *DebugInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { |
| 883 | 883 | const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; |
| 884 | 884 | try tty_config.setColor(out_stream, .dim); |
| 885 | try out_stream.print("Unwind information for `{s}:{}` was not available ({}), trace may be incomplete\n\n", .{ module_name, address, err }); | |
| 885 | try out_stream.print("Unwind information for `{s}:0x{x}` was not available ({}), trace may be incomplete\n\n", .{ module_name, address, err }); | |
| 886 | 886 | try tty_config.setColor(out_stream, .reset); |
| 887 | 887 | } |
| 888 | 888 |
lib/std/dwarf.zig+9-6| ... | ... | @@ -1659,8 +1659,6 @@ pub const DwarfInfo = struct { |
| 1659 | 1659 | if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture; |
| 1660 | 1660 | if (context.pc == 0) return 0; |
| 1661 | 1661 | |
| 1662 | // TODO: Handle unwinding from a signal frame (ie. use_prev_instr in libunwind) | |
| 1663 | ||
| 1664 | 1662 | // Find the FDE and CIE |
| 1665 | 1663 | var cie: CommonInformationEntry = undefined; |
| 1666 | 1664 | var fde: FrameDescriptionEntry = undefined; |
| ... | ... | @@ -1828,11 +1826,16 @@ pub const DwarfInfo = struct { |
| 1828 | 1826 | |
| 1829 | 1827 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc; |
| 1830 | 1828 | |
| 1831 | // The call instruction will have pushed the address of the instruction that follows the call as the return address | |
| 1832 | // However, this return address may be past the end of the function if the caller was `noreturn`. By subtracting one, | |
| 1833 | // then `context.pc` will always point to an instruction within the FDE for the previous function. | |
| 1829 | // The call instruction will have pushed the address of the instruction that follows the call as the return address. | |
| 1830 | // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in | |
| 1831 | // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up | |
| 1832 | // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, | |
| 1833 | // we subtract one so that the next lookup is guaranteed to land inside the | |
| 1834 | // | |
| 1835 | // The exception to this rule is signal frames, where we return execution would be returned to the instruction | |
| 1836 | // that triggered the handler. | |
| 1834 | 1837 | const return_address = context.pc; |
| 1835 | if (context.pc > 0) context.pc -= 1; | |
| 1838 | if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; | |
| 1836 | 1839 | |
| 1837 | 1840 | return return_address; |
| 1838 | 1841 | } |
test/standalone/stack_iterator/build.zig+6-7| ... | ... | @@ -7,7 +7,7 @@ pub fn build(b: *std.Build) void { |
| 7 | 7 | const target = b.standardTargetOptions(.{}); |
| 8 | 8 | const optimize = b.standardOptimizeOption(.{}); |
| 9 | 9 | |
| 10 | // Unwinding pure zig code, with a frame pointer | |
| 10 | // Unwinding with a frame pointer | |
| 11 | 11 | // |
| 12 | 12 | // getcontext version: zig std |
| 13 | 13 | // |
| ... | ... | @@ -18,8 +18,8 @@ pub fn build(b: *std.Build) void { |
| 18 | 18 | // - aarch64: FRAME, DWARF |
| 19 | 19 | { |
| 20 | 20 | const exe = b.addExecutable(.{ |
| 21 | .name = "zig_unwind_fp", | |
| 22 | .root_source_file = .{ .path = "zig_unwind.zig" }, | |
| 21 | .name = "unwind_fp", | |
| 22 | .root_source_file = .{ .path = "unwind.zig" }, | |
| 23 | 23 | .target = target, |
| 24 | 24 | .optimize = optimize, |
| 25 | 25 | }); |
| ... | ... | @@ -31,7 +31,7 @@ pub fn build(b: *std.Build) void { |
| 31 | 31 | test_step.dependOn(&run_cmd.step); |
| 32 | 32 | } |
| 33 | 33 | |
| 34 | // Unwinding pure zig code, without a frame pointer. | |
| 34 | // Unwinding without a frame pointer | |
| 35 | 35 | // |
| 36 | 36 | // getcontext version: zig std |
| 37 | 37 | // |
| ... | ... | @@ -42,13 +42,12 @@ pub fn build(b: *std.Build) void { |
| 42 | 42 | // - aarch64: FRAMELESS, DWARF |
| 43 | 43 | { |
| 44 | 44 | const exe = b.addExecutable(.{ |
| 45 | .name = "zig_unwind_nofp", | |
| 46 | .root_source_file = .{ .path = "zig_unwind.zig" }, | |
| 45 | .name = "unwind_nofp", | |
| 46 | .root_source_file = .{ .path = "unwind.zig" }, | |
| 47 | 47 | .target = target, |
| 48 | 48 | .optimize = optimize, |
| 49 | 49 | }); |
| 50 | 50 | |
| 51 | if (target.isDarwin()) exe.unwind_tables = true; | |
| 52 | 51 | exe.omit_frame_pointer = true; |
| 53 | 52 | exe.unwind_tables = true; |
| 54 | 53 |
test/standalone/stack_iterator/shared_lib_unwind.zig+4| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); | |
| 2 | 3 | const debug = std.debug; |
| 3 | 4 | const testing = std.testing; |
| 4 | 5 | |
| ... | ... | @@ -34,6 +35,9 @@ extern fn frame0( |
| 34 | 35 | ) void; |
| 35 | 36 | |
| 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.isDarwin() and builtin.cpu.arch == .aarch64) return; | |
| 40 | ||
| 37 | 41 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; |
| 38 | 42 | |
| 39 | 43 | var expected: [5]usize = undefined; |
test/standalone/stack_iterator/unwind.zig created+99| ... | ... | @@ -0,0 +1,99 @@ |
| 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 | // Disabled until the DWARF unwinder bugs on .aarch64 are solved | |
| 91 | if (builtin.omit_frame_pointer and comptime builtin.target.isDarwin() and builtin.cpu.arch == .aarch64) return; | |
| 92 | ||
| 93 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; | |
| 94 | ||
| 95 | var expected: [4]usize = undefined; | |
| 96 | var unwound: [4]usize = undefined; | |
| 97 | frame0(&expected, &unwound); | |
| 98 | try testing.expectEqual(expected, unwound); | |
| 99 | } |
test/standalone/stack_iterator/zig_unwind.zig deleted-96| ... | ... | @@ -1,96 +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 | 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 | } |