| 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,7 +882,7 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz |
| 882 | pub fn printUnwindError(debug_info: *DebugInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { | 882 | pub fn printUnwindError(debug_info: *DebugInfo, out_stream: anytype, address: usize, err: UnwindError, tty_config: io.tty.Config) !void { |
| 883 | const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; | 883 | const module_name = debug_info.getModuleNameForAddress(address) orelse "???"; |
| 884 | try tty_config.setColor(out_stream, .dim); | 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 | try tty_config.setColor(out_stream, .reset); | 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,8 +1659,6 @@ pub const DwarfInfo = struct { |
| 1659 | if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture; | 1659 | if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture; |
| 1660 | if (context.pc == 0) return 0; | 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 | // Find the FDE and CIE | 1662 | // Find the FDE and CIE |
| 1665 | var cie: CommonInformationEntry = undefined; | 1663 | var cie: CommonInformationEntry = undefined; |
| 1666 | var fde: FrameDescriptionEntry = undefined; | 1664 | var fde: FrameDescriptionEntry = undefined; |
| ... | @@ -1828,11 +1826,16 @@ pub const DwarfInfo = struct { | ... | @@ -1828,11 +1826,16 @@ pub const DwarfInfo = struct { |
| 1828 | 1826 | ||
| 1829 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc; | 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 | 1829 | // 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, | 1830 | // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in |
| 1833 | // then `context.pc` will always point to an instruction within the FDE for the previous function. | 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 | const return_address = context.pc; | 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 | return return_address; | 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 +7,7 @@ pub fn build(b: *std.Build) void { |
| 7 | const target = b.standardTargetOptions(.{}); | 7 | const target = b.standardTargetOptions(.{}); |
| 8 | const optimize = b.standardOptimizeOption(.{}); | 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 | // getcontext version: zig std | 12 | // getcontext version: zig std |
| 13 | // | 13 | // |
| ... | @@ -18,8 +18,8 @@ pub fn build(b: *std.Build) void { | ... | @@ -18,8 +18,8 @@ pub fn build(b: *std.Build) void { |
| 18 | // - aarch64: FRAME, DWARF | 18 | // - aarch64: FRAME, DWARF |
| 19 | { | 19 | { |
| 20 | const exe = b.addExecutable(.{ | 20 | const exe = b.addExecutable(.{ |
| 21 | .name = "zig_unwind_fp", | 21 | .name = "unwind_fp", |
| 22 | .root_source_file = .{ .path = "zig_unwind.zig" }, | 22 | .root_source_file = .{ .path = "unwind.zig" }, |
| 23 | .target = target, | 23 | .target = target, |
| 24 | .optimize = optimize, | 24 | .optimize = optimize, |
| 25 | }); | 25 | }); |
| ... | @@ -31,7 +31,7 @@ pub fn build(b: *std.Build) void { | ... | @@ -31,7 +31,7 @@ pub fn build(b: *std.Build) void { |
| 31 | test_step.dependOn(&run_cmd.step); | 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 | // getcontext version: zig std | 36 | // getcontext version: zig std |
| 37 | // | 37 | // |
| ... | @@ -42,13 +42,12 @@ pub fn build(b: *std.Build) void { | ... | @@ -42,13 +42,12 @@ pub fn build(b: *std.Build) void { |
| 42 | // - aarch64: FRAMELESS, DWARF | 42 | // - aarch64: FRAMELESS, DWARF |
| 43 | { | 43 | { |
| 44 | const exe = b.addExecutable(.{ | 44 | const exe = b.addExecutable(.{ |
| 45 | .name = "zig_unwind_nofp", | 45 | .name = "unwind_nofp", |
| 46 | .root_source_file = .{ .path = "zig_unwind.zig" }, | 46 | .root_source_file = .{ .path = "unwind.zig" }, |
| 47 | .target = target, | 47 | .target = target, |
| 48 | .optimize = optimize, | 48 | .optimize = optimize, |
| 49 | }); | 49 | }); |
| 50 | 50 | ||
| 51 | if (target.isDarwin()) exe.unwind_tables = true; | ||
| 52 | exe.omit_frame_pointer = true; | 51 | exe.omit_frame_pointer = true; |
| 53 | exe.unwind_tables = true; | 52 | exe.unwind_tables = true; |
| 54 | 53 |
test/standalone/stack_iterator/shared_lib_unwind.zig+4| ... | @@ -1,4 +1,5 @@ | ... | @@ -1,4 +1,5 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); | ||
| 2 | const debug = std.debug; | 3 | const debug = std.debug; |
| 3 | const testing = std.testing; | 4 | const testing = std.testing; |
| 4 | 5 | ||
| ... | @@ -34,6 +35,9 @@ extern fn frame0( | ... | @@ -34,6 +35,9 @@ extern fn frame0( |
| 34 | ) void; | 35 | ) void; |
| 35 | 36 | ||
| 36 | pub fn main() !void { | 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 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; | 41 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; |
| 38 | 42 | ||
| 39 | var expected: [5]usize = undefined; | 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 | } | ||