| author | |
| committer | |
| log | 61d5b7c957e63239f1ebbdbfb94105d53f2dbaa4 |
| tree | a49da9a773faff32301383f9bffaed99852c26f6 |
| parent | c43ee5bb22298eefc3fae919807f5da8f7be70f1 |
| parent | b1d86db7b45c57b2a9d48655738bce8d77327438 |
| signature |
Add DWARF unwinding, and an external debug info loader for ELF29 files changed, 5603 insertions(+), 515 deletions(-)
lib/std/c.zig+7| ... | @@ -413,6 +413,13 @@ pub extern "c" fn timer_delete(timerid: c.timer_t) c_int; | ... | @@ -413,6 +413,13 @@ pub extern "c" fn timer_delete(timerid: c.timer_t) c_int; |
| 413 | pub extern "c" fn timer_settime(timerid: c.timer_t, flags: c_int, new_value: *const c.itimerspec, old_value: *c.itimerspec) c_int; | 413 | pub extern "c" fn timer_settime(timerid: c.timer_t, flags: c_int, new_value: *const c.itimerspec, old_value: *c.itimerspec) c_int; |
| 414 | pub extern "c" fn timer_gettime(timerid: c.timer_t, flags: c_int, curr_value: *c.itimerspec) c_int; | 414 | pub extern "c" fn timer_gettime(timerid: c.timer_t, flags: c_int, curr_value: *c.itimerspec) c_int; |
| 415 | 415 | ||
| 416 | pub usingnamespace if (builtin.os.tag == .linux and builtin.target.isMusl()) struct { | ||
| 417 | // musl does not implement getcontext | ||
| 418 | pub const getcontext = std.os.linux.getcontext; | ||
| 419 | } else struct { | ||
| 420 | pub extern "c" fn getcontext(ucp: *std.os.ucontext_t) c_int; | ||
| 421 | }; | ||
| 422 | |||
| 416 | pub const max_align_t = if (builtin.abi == .msvc) | 423 | pub const max_align_t = if (builtin.abi == .msvc) |
| 417 | f64 | 424 | f64 |
| 418 | else if (builtin.target.isDarwin()) | 425 | else if (builtin.target.isDarwin()) |
lib/std/c/darwin.zig+2-4| ... | @@ -148,12 +148,10 @@ pub const ucontext_t = extern struct { | ... | @@ -148,12 +148,10 @@ pub const ucontext_t = extern struct { |
| 148 | link: ?*ucontext_t, | 148 | link: ?*ucontext_t, |
| 149 | mcsize: u64, | 149 | mcsize: u64, |
| 150 | mcontext: *mcontext_t, | 150 | mcontext: *mcontext_t, |
| 151 | __mcontext_data: mcontext_t, | ||
| 151 | }; | 152 | }; |
| 152 | 153 | ||
| 153 | pub const mcontext_t = extern struct { | 154 | pub const mcontext_t = arch_bits.mcontext_t; |
| 154 | es: arch_bits.exception_state, | ||
| 155 | ss: arch_bits.thread_state, | ||
| 156 | }; | ||
| 157 | 155 | ||
| 158 | extern "c" fn __error() *c_int; | 156 | extern "c" fn __error() *c_int; |
| 159 | pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32; | 157 | pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32; |
lib/std/c/darwin/aarch64.zig+13| ... | @@ -1,5 +1,12 @@ | ... | @@ -1,5 +1,12 @@ |
| 1 | // See C headers in | 1 | // See C headers in |
| 2 | // lib/libc/include/aarch64-macos.12-gnu/mach/arm/_structs.h | 2 | // lib/libc/include/aarch64-macos.12-gnu/mach/arm/_structs.h |
| 3 | // lib/libc/include/aarch64-macos.13-none/arm/_mcontext.h | ||
| 4 | |||
| 5 | pub const mcontext_t = extern struct { | ||
| 6 | es: exception_state, | ||
| 7 | ss: thread_state, | ||
| 8 | ns: neon_state, | ||
| 9 | }; | ||
| 3 | 10 | ||
| 4 | pub const exception_state = extern struct { | 11 | pub const exception_state = extern struct { |
| 5 | far: u64, // Virtual Fault Address | 12 | far: u64, // Virtual Fault Address |
| ... | @@ -17,6 +24,12 @@ pub const thread_state = extern struct { | ... | @@ -17,6 +24,12 @@ pub const thread_state = extern struct { |
| 17 | __pad: u32, | 24 | __pad: u32, |
| 18 | }; | 25 | }; |
| 19 | 26 | ||
| 27 | pub const neon_state = extern struct { | ||
| 28 | q: [32]u128, | ||
| 29 | fpsr: u32, | ||
| 30 | fpcr: u32, | ||
| 31 | }; | ||
| 32 | |||
| 20 | pub const EXC_TYPES_COUNT = 14; | 33 | pub const EXC_TYPES_COUNT = 14; |
| 21 | pub const EXC_MASK_MACHINE = 0; | 34 | pub const EXC_MASK_MACHINE = 0; |
| 22 | 35 |
lib/std/c/darwin/x86_64.zig+29| ... | @@ -1,5 +1,11 @@ | ... | @@ -1,5 +1,11 @@ |
| 1 | const c = @import("../darwin.zig"); | 1 | const c = @import("../darwin.zig"); |
| 2 | 2 | ||
| 3 | pub const mcontext_t = extern struct { | ||
| 4 | es: exception_state, | ||
| 5 | ss: thread_state, | ||
| 6 | fs: float_state, | ||
| 7 | }; | ||
| 8 | |||
| 3 | pub const exception_state = extern struct { | 9 | pub const exception_state = extern struct { |
| 4 | trapno: u16, | 10 | trapno: u16, |
| 5 | cpu: u16, | 11 | cpu: u16, |
| ... | @@ -31,6 +37,29 @@ pub const thread_state = extern struct { | ... | @@ -31,6 +37,29 @@ pub const thread_state = extern struct { |
| 31 | gs: u64, | 37 | gs: u64, |
| 32 | }; | 38 | }; |
| 33 | 39 | ||
| 40 | const stmm_reg = [16]u8; | ||
| 41 | const xmm_reg = [16]u8; | ||
| 42 | pub const float_state = extern struct { | ||
| 43 | reserved: [2]c_int, | ||
| 44 | fcw: u16, | ||
| 45 | fsw: u16, | ||
| 46 | ftw: u8, | ||
| 47 | rsrv1: u8, | ||
| 48 | fop: u16, | ||
| 49 | ip: u32, | ||
| 50 | cs: u16, | ||
| 51 | rsrv2: u16, | ||
| 52 | dp: u32, | ||
| 53 | ds: u16, | ||
| 54 | rsrv3: u16, | ||
| 55 | mxcsr: u32, | ||
| 56 | mxcsrmask: u32, | ||
| 57 | stmm: [8]stmm_reg, | ||
| 58 | xmm: [16]xmm_reg, | ||
| 59 | rsrv4: [96]u8, | ||
| 60 | reserved1: c_int, | ||
| 61 | }; | ||
| 62 | |||
| 34 | pub const THREAD_STATE = 4; | 63 | pub const THREAD_STATE = 4; |
| 35 | pub const THREAD_STATE_COUNT: c.mach_msg_type_number_t = @sizeOf(thread_state) / @sizeOf(c_int); | 64 | pub const THREAD_STATE_COUNT: c.mach_msg_type_number_t = @sizeOf(thread_state) / @sizeOf(c_int); |
| 36 | 65 |
lib/std/coff.zig+8-5| ... | @@ -1214,6 +1214,11 @@ pub const Coff = struct { | ... | @@ -1214,6 +1214,11 @@ pub const Coff = struct { |
| 1214 | return Strtab{ .buffer = self.data[offset..][0..size] }; | 1214 | return Strtab{ .buffer = self.data[offset..][0..size] }; |
| 1215 | } | 1215 | } |
| 1216 | 1216 | ||
| 1217 | pub fn strtabRequired(self: *const Coff) bool { | ||
| 1218 | for (self.getSectionHeaders()) |*sect_hdr| if (sect_hdr.getName() == null) return true; | ||
| 1219 | return false; | ||
| 1220 | } | ||
| 1221 | |||
| 1217 | pub fn getSectionHeaders(self: *const Coff) []align(1) const SectionHeader { | 1222 | pub fn getSectionHeaders(self: *const Coff) []align(1) const SectionHeader { |
| 1218 | const coff_header = self.getCoffHeader(); | 1223 | const coff_header = self.getCoffHeader(); |
| 1219 | const offset = self.coff_header_offset + @sizeOf(CoffHeader) + coff_header.size_of_optional_header; | 1224 | const offset = self.coff_header_offset + @sizeOf(CoffHeader) + coff_header.size_of_optional_header; |
| ... | @@ -1248,14 +1253,12 @@ pub const Coff = struct { | ... | @@ -1248,14 +1253,12 @@ pub const Coff = struct { |
| 1248 | return null; | 1253 | return null; |
| 1249 | } | 1254 | } |
| 1250 | 1255 | ||
| 1251 | pub fn getSectionData(self: *const Coff, comptime name: []const u8) ![]const u8 { | 1256 | pub fn getSectionData(self: *const Coff, sec: *align(1) const SectionHeader) []const u8 { |
| 1252 | const sec = self.getSectionByName(name) orelse return error.MissingCoffSection; | ||
| 1253 | return self.data[sec.pointer_to_raw_data..][0..sec.virtual_size]; | 1257 | return self.data[sec.pointer_to_raw_data..][0..sec.virtual_size]; |
| 1254 | } | 1258 | } |
| 1255 | 1259 | ||
| 1256 | // Return an owned slice full of the section data | 1260 | pub fn getSectionDataAlloc(self: *const Coff, sec: *align(1) const SectionHeader, allocator: mem.Allocator) ![]u8 { |
| 1257 | pub fn getSectionDataAlloc(self: *const Coff, comptime name: []const u8, allocator: mem.Allocator) ![]u8 { | 1261 | const section_data = self.getSectionData(sec); |
| 1258 | const section_data = try self.getSectionData(name); | ||
| 1259 | return allocator.dupe(u8, section_data); | 1262 | return allocator.dupe(u8, section_data); |
| 1260 | } | 1263 | } |
| 1261 | }; | 1264 | }; |
lib/std/debug.zig+764-331| ... | @@ -133,10 +133,80 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { | ... | @@ -133,10 +133,80 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { |
| 133 | } | 133 | } |
| 134 | } | 134 | } |
| 135 | 135 | ||
| 136 | pub const have_ucontext = @hasDecl(os.system, "ucontext_t") and | ||
| 137 | (builtin.os.tag != .linux or switch (builtin.cpu.arch) { | ||
| 138 | .mips, .mipsel, .mips64, .mips64el, .riscv64 => false, | ||
| 139 | else => true, | ||
| 140 | }); | ||
| 141 | |||
| 142 | /// Platform-specific thread state. This contains register state, and on some platforms | ||
| 143 | /// information about the stack. This is not safe to trivially copy, because some platforms | ||
| 144 | /// use internal pointers within this structure. To make a copy, use `copyContext`. | ||
| 145 | pub const ThreadContext = blk: { | ||
| 146 | if (native_os == .windows) { | ||
| 147 | break :blk std.os.windows.CONTEXT; | ||
| 148 | } else if (have_ucontext) { | ||
| 149 | break :blk os.ucontext_t; | ||
| 150 | } else { | ||
| 151 | break :blk void; | ||
| 152 | } | ||
| 153 | }; | ||
| 154 | |||
| 155 | /// Copies one context to another, updating any internal pointers | ||
| 156 | pub fn copyContext(source: *const ThreadContext, dest: *ThreadContext) void { | ||
| 157 | if (!have_ucontext) return {}; | ||
| 158 | dest.* = source.*; | ||
| 159 | relocateContext(dest); | ||
| 160 | } | ||
| 161 | |||
| 162 | /// Updates any internal pointers in the context to reflect its current location | ||
| 163 | pub fn relocateContext(context: *ThreadContext) void { | ||
| 164 | return switch (native_os) { | ||
| 165 | .macos => { | ||
| 166 | context.mcontext = &context.__mcontext_data; | ||
| 167 | }, | ||
| 168 | else => {}, | ||
| 169 | }; | ||
| 170 | } | ||
| 171 | |||
| 172 | pub const have_getcontext = @hasDecl(os.system, "getcontext") and | ||
| 173 | (builtin.os.tag != .linux or switch (builtin.cpu.arch) { | ||
| 174 | .x86, | ||
| 175 | .x86_64, | ||
| 176 | => true, | ||
| 177 | else => builtin.link_libc and !builtin.target.isMusl(), | ||
| 178 | }); | ||
| 179 | |||
| 180 | /// Capture the current context. The register values in the context will reflect the | ||
| 181 | /// state after the platform `getcontext` function returns. | ||
| 182 | /// | ||
| 183 | /// It is valid to call this if the platform doesn't have context capturing support, | ||
| 184 | /// in that case false will be returned. | ||
| 185 | pub inline fn getContext(context: *ThreadContext) bool { | ||
| 186 | if (native_os == .windows) { | ||
| 187 | context.* = std.mem.zeroes(windows.CONTEXT); | ||
| 188 | windows.ntdll.RtlCaptureContext(context); | ||
| 189 | return true; | ||
| 190 | } | ||
| 191 | |||
| 192 | const result = have_getcontext and os.system.getcontext(context) == 0; | ||
| 193 | if (native_os == .macos) { | ||
| 194 | assert(context.mcsize == @sizeOf(std.c.mcontext_t)); | ||
| 195 | |||
| 196 | // On aarch64-macos, the system getcontext doesn't write anything into the pc | ||
| 197 | // register slot, it only writes lr. This makes the context consistent with | ||
| 198 | // other aarch64 getcontext implementations which write the current lr | ||
| 199 | // (where getcontext will return to) into both the lr and pc slot of the context. | ||
| 200 | if (native_arch == .aarch64) context.mcontext.ss.pc = context.mcontext.ss.lr; | ||
| 201 | } | ||
| 202 | |||
| 203 | return result; | ||
| 204 | } | ||
| 205 | |||
| 136 | /// Tries to print the stack trace starting from the supplied base pointer to stderr, | 206 | /// Tries to print the stack trace starting from the supplied base pointer to stderr, |
| 137 | /// unbuffered, and ignores any error returned. | 207 | /// unbuffered, and ignores any error returned. |
| 138 | /// TODO multithreaded awareness | 208 | /// TODO multithreaded awareness |
| 139 | pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { | 209 | pub fn dumpStackTraceFromBase(context: *const ThreadContext) void { |
| 140 | nosuspend { | 210 | nosuspend { |
| 141 | if (comptime builtin.target.isWasm()) { | 211 | if (comptime builtin.target.isWasm()) { |
| 142 | if (native_os == .wasi) { | 212 | if (native_os == .wasi) { |
| ... | @@ -156,13 +226,25 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { | ... | @@ -156,13 +226,25 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { |
| 156 | }; | 226 | }; |
| 157 | const tty_config = io.tty.detectConfig(io.getStdErr()); | 227 | const tty_config = io.tty.detectConfig(io.getStdErr()); |
| 158 | if (native_os == .windows) { | 228 | if (native_os == .windows) { |
| 159 | writeCurrentStackTraceWindows(stderr, debug_info, tty_config, ip) catch return; | 229 | // On x86_64 and aarch64, the stack will be unwound using RtlVirtualUnwind using the context |
| 230 | // provided by the exception handler. On x86, RtlVirtualUnwind doesn't exist. Instead, a new backtrace | ||
| 231 | // will be captured and frames prior to the exception will be filtered. | ||
| 232 | // The caveat is that RtlCaptureStackBackTrace does not include the KiUserExceptionDispatcher frame, | ||
| 233 | // which is where the IP in `context` points to, so it can't be used as start_addr. | ||
| 234 | // Instead, start_addr is recovered from the stack. | ||
| 235 | const start_addr = if (builtin.cpu.arch == .x86) @as(*const usize, @ptrFromInt(context.getRegs().bp + 4)).* else null; | ||
| 236 | writeStackTraceWindows(stderr, debug_info, tty_config, context, start_addr) catch return; | ||
| 160 | return; | 237 | return; |
| 161 | } | 238 | } |
| 162 | 239 | ||
| 163 | printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return; | 240 | var it = StackIterator.initWithContext(null, debug_info, context) catch return; |
| 164 | var it = StackIterator.init(null, bp); | 241 | defer it.deinit(); |
| 242 | printSourceAtAddress(debug_info, stderr, it.unwind_state.?.dwarf_context.pc, tty_config) catch return; | ||
| 243 | |||
| 165 | while (it.next()) |return_address| { | 244 | while (it.next()) |return_address| { |
| 245 | if (it.getLastError()) |unwind_error| | ||
| 246 | printUnwindError(debug_info, stderr, unwind_error.address, unwind_error.err, tty_config) catch {}; | ||
| 247 | |||
| 166 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, | 248 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, |
| 167 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid | 249 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid |
| 168 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this | 250 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this |
| ... | @@ -184,12 +266,12 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT | ... | @@ -184,12 +266,12 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT |
| 184 | if (native_os == .windows) { | 266 | if (native_os == .windows) { |
| 185 | const addrs = stack_trace.instruction_addresses; | 267 | const addrs = stack_trace.instruction_addresses; |
| 186 | const first_addr = first_address orelse { | 268 | const first_addr = first_address orelse { |
| 187 | stack_trace.index = walkStackWindows(addrs[0..]); | 269 | stack_trace.index = walkStackWindows(addrs[0..], null); |
| 188 | return; | 270 | return; |
| 189 | }; | 271 | }; |
| 190 | var addr_buf_stack: [32]usize = undefined; | 272 | var addr_buf_stack: [32]usize = undefined; |
| 191 | const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs; | 273 | const addr_buf = if (addr_buf_stack.len > addrs.len) addr_buf_stack[0..] else addrs; |
| 192 | const n = walkStackWindows(addr_buf[0..]); | 274 | const n = walkStackWindows(addr_buf[0..], null); |
| 193 | const first_index = for (addr_buf[0..n], 0..) |addr, i| { | 275 | const first_index = for (addr_buf[0..n], 0..) |addr, i| { |
| 194 | if (addr == first_addr) { | 276 | if (addr == first_addr) { |
| 195 | break i; | 277 | break i; |
| ... | @@ -206,7 +288,11 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT | ... | @@ -206,7 +288,11 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *std.builtin.StackT |
| 206 | } | 288 | } |
| 207 | stack_trace.index = slice.len; | 289 | stack_trace.index = slice.len; |
| 208 | } else { | 290 | } else { |
| 291 | // TODO: This should use the DWARF unwinder if .eh_frame_hdr is available (so that full debug info parsing isn't required). | ||
| 292 | // A new path for loading DebugInfo needs to be created which will only attempt to parse in-memory sections, because | ||
| 293 | // stopping to load other debug info (ie. source line info) from disk here is not required for unwinding. | ||
| 209 | var it = StackIterator.init(first_address, null); | 294 | var it = StackIterator.init(first_address, null); |
| 295 | defer it.deinit(); | ||
| 210 | for (stack_trace.instruction_addresses, 0..) |*addr, i| { | 296 | for (stack_trace.instruction_addresses, 0..) |*addr, i| { |
| 211 | addr.* = it.next() orelse { | 297 | addr.* = it.next() orelse { |
| 212 | stack_trace.index = i; | 298 | stack_trace.index = i; |
| ... | @@ -399,12 +485,27 @@ pub fn writeStackTrace( | ... | @@ -399,12 +485,27 @@ pub fn writeStackTrace( |
| 399 | } | 485 | } |
| 400 | } | 486 | } |
| 401 | 487 | ||
| 488 | pub const UnwindError = if (have_ucontext) | ||
| 489 | @typeInfo(@typeInfo(@TypeOf(StackIterator.next_unwind)).Fn.return_type.?).ErrorUnion.error_set | ||
| 490 | else | ||
| 491 | void; | ||
| 492 | |||
| 402 | pub const StackIterator = struct { | 493 | pub const StackIterator = struct { |
| 403 | // Skip every frame before this address is found. | 494 | // Skip every frame before this address is found. |
| 404 | first_address: ?usize, | 495 | first_address: ?usize, |
| 405 | // Last known value of the frame pointer register. | 496 | // Last known value of the frame pointer register. |
| 406 | fp: usize, | 497 | fp: usize, |
| 407 | 498 | ||
| 499 | // When DebugInfo and a register context is available, this iterator can unwind | ||
| 500 | // stacks with frames that don't use a frame pointer (ie. -fomit-frame-pointer), | ||
| 501 | // using DWARF and MachO unwind info. | ||
| 502 | unwind_state: if (have_ucontext) ?struct { | ||
| 503 | debug_info: *DebugInfo, | ||
| 504 | dwarf_context: DW.UnwindContext, | ||
| 505 | last_error: ?UnwindError = null, | ||
| 506 | failed: bool = false, | ||
| 507 | } else void = if (have_ucontext) null else {}, | ||
| 508 | |||
| 408 | pub fn init(first_address: ?usize, fp: ?usize) StackIterator { | 509 | pub fn init(first_address: ?usize, fp: ?usize) StackIterator { |
| 409 | if (native_arch == .sparc64) { | 510 | if (native_arch == .sparc64) { |
| 410 | // Flush all the register windows on stack. | 511 | // Flush all the register windows on stack. |
| ... | @@ -419,6 +520,44 @@ pub const StackIterator = struct { | ... | @@ -419,6 +520,44 @@ pub const StackIterator = struct { |
| 419 | }; | 520 | }; |
| 420 | } | 521 | } |
| 421 | 522 | ||
| 523 | pub fn initWithContext(first_address: ?usize, debug_info: *DebugInfo, context: *const os.ucontext_t) !StackIterator { | ||
| 524 | // The implementation of DWARF unwinding on aarch64-macos is not complete. However, Apple mandates that | ||
| 525 | // the frame pointer register is always used, so on this platform we can safely use the FP-based unwinder. | ||
| 526 | if (comptime builtin.target.isDarwin() and native_arch == .aarch64) { | ||
| 527 | return init(first_address, context.mcontext.ss.fp); | ||
| 528 | } else { | ||
| 529 | var iterator = init(first_address, null); | ||
| 530 | iterator.unwind_state = .{ | ||
| 531 | .debug_info = debug_info, | ||
| 532 | .dwarf_context = try DW.UnwindContext.init(debug_info.allocator, context, &isValidMemory), | ||
| 533 | }; | ||
| 534 | |||
| 535 | return iterator; | ||
| 536 | } | ||
| 537 | } | ||
| 538 | |||
| 539 | pub fn deinit(self: *StackIterator) void { | ||
| 540 | if (have_ucontext and self.unwind_state != null) self.unwind_state.?.dwarf_context.deinit(); | ||
| 541 | } | ||
| 542 | |||
| 543 | pub fn getLastError(self: *StackIterator) ?struct { | ||
| 544 | err: UnwindError, | ||
| 545 | address: usize, | ||
| 546 | } { | ||
| 547 | if (!have_ucontext) return null; | ||
| 548 | if (self.unwind_state) |*unwind_state| { | ||
| 549 | if (unwind_state.last_error) |err| { | ||
| 550 | unwind_state.last_error = null; | ||
| 551 | return .{ | ||
| 552 | .err = err, | ||
| 553 | .address = unwind_state.dwarf_context.pc, | ||
| 554 | }; | ||
| 555 | } | ||
| 556 | } | ||
| 557 | |||
| 558 | return null; | ||
| 559 | } | ||
| 560 | |||
| 422 | // Offset of the saved BP wrt the frame pointer. | 561 | // Offset of the saved BP wrt the frame pointer. |
| 423 | const fp_offset = if (native_arch.isRISCV()) | 562 | const fp_offset = if (native_arch.isRISCV()) |
| 424 | // On RISC-V the frame pointer points to the top of the saved register | 563 | // On RISC-V the frame pointer points to the top of the saved register |
| ... | @@ -461,6 +600,7 @@ pub const StackIterator = struct { | ... | @@ -461,6 +600,7 @@ pub const StackIterator = struct { |
| 461 | if (native_os == .freestanding) return true; | 600 | if (native_os == .freestanding) return true; |
| 462 | 601 | ||
| 463 | const aligned_address = address & ~@as(usize, @intCast((mem.page_size - 1))); | 602 | const aligned_address = address & ~@as(usize, @intCast((mem.page_size - 1))); |
| 603 | if (aligned_address == 0) return false; | ||
| 464 | const aligned_memory = @as([*]align(mem.page_size) u8, @ptrFromInt(aligned_address))[0..mem.page_size]; | 604 | const aligned_memory = @as([*]align(mem.page_size) u8, @ptrFromInt(aligned_address))[0..mem.page_size]; |
| 465 | 605 | ||
| 466 | if (native_os != .windows) { | 606 | if (native_os != .windows) { |
| ... | @@ -500,7 +640,49 @@ pub const StackIterator = struct { | ... | @@ -500,7 +640,49 @@ pub const StackIterator = struct { |
| 500 | } | 640 | } |
| 501 | } | 641 | } |
| 502 | 642 | ||
| 643 | fn next_unwind(self: *StackIterator) !usize { | ||
| 644 | const unwind_state = &self.unwind_state.?; | ||
| 645 | const module = try unwind_state.debug_info.getModuleForAddress(unwind_state.dwarf_context.pc); | ||
| 646 | switch (native_os) { | ||
| 647 | .macos, .ios, .watchos, .tvos => { | ||
| 648 | // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding | ||
| 649 | // via DWARF before attempting to use the compact unwind info will produce incorrect results. | ||
| 650 | if (module.unwind_info) |unwind_info| { | ||
| 651 | if (DW.unwindFrameMachO(&unwind_state.dwarf_context, unwind_info, module.eh_frame, module.base_address)) |return_address| { | ||
| 652 | return return_address; | ||
| 653 | } else |err| { | ||
| 654 | if (err != error.RequiresDWARFUnwind) return err; | ||
| 655 | } | ||
| 656 | } else return error.MissingUnwindInfo; | ||
| 657 | }, | ||
| 658 | else => {}, | ||
| 659 | } | ||
| 660 | |||
| 661 | if (try module.getDwarfInfoForAddress(unwind_state.debug_info.allocator, unwind_state.dwarf_context.pc)) |di| { | ||
| 662 | return di.unwindFrame(&unwind_state.dwarf_context, null); | ||
| 663 | } else return error.MissingDebugInfo; | ||
| 664 | } | ||
| 665 | |||
| 503 | fn next_internal(self: *StackIterator) ?usize { | 666 | fn next_internal(self: *StackIterator) ?usize { |
| 667 | if (have_ucontext) { | ||
| 668 | if (self.unwind_state) |*unwind_state| { | ||
| 669 | if (!unwind_state.failed) { | ||
| 670 | if (unwind_state.dwarf_context.pc == 0) return null; | ||
| 671 | if (self.next_unwind()) |return_address| { | ||
| 672 | self.fp = unwind_state.dwarf_context.getFp() catch 0; | ||
| 673 | return return_address; | ||
| 674 | } else |err| { | ||
| 675 | unwind_state.last_error = err; | ||
| 676 | unwind_state.failed = true; | ||
| 677 | |||
| 678 | // Fall back to fp-based unwinding on the first failure. | ||
| 679 | // We can't attempt it again for other modules higher in the | ||
| 680 | // stack because the full register state won't have been unwound. | ||
| 681 | } | ||
| 682 | } | ||
| 683 | } | ||
| 684 | } | ||
| 685 | |||
| 504 | const fp = if (comptime native_arch.isSPARC()) | 686 | const fp = if (comptime native_arch.isSPARC()) |
| 505 | // On SPARC the offset is positive. (!) | 687 | // On SPARC the offset is positive. (!) |
| 506 | math.add(usize, self.fp, fp_offset) catch return null | 688 | math.add(usize, self.fp, fp_offset) catch return null |
| ... | @@ -537,11 +719,21 @@ pub fn writeCurrentStackTrace( | ... | @@ -537,11 +719,21 @@ pub fn writeCurrentStackTrace( |
| 537 | tty_config: io.tty.Config, | 719 | tty_config: io.tty.Config, |
| 538 | start_addr: ?usize, | 720 | start_addr: ?usize, |
| 539 | ) !void { | 721 | ) !void { |
| 722 | var context: ThreadContext = undefined; | ||
| 723 | const has_context = getContext(&context); | ||
| 540 | if (native_os == .windows) { | 724 | if (native_os == .windows) { |
| 541 | return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr); | 725 | return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); |
| 542 | } | 726 | } |
| 543 | var it = StackIterator.init(start_addr, null); | 727 | |
| 728 | var it = (if (has_context) blk: { | ||
| 729 | break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null; | ||
| 730 | } else null) orelse StackIterator.init(start_addr, null); | ||
| 731 | defer it.deinit(); | ||
| 732 | |||
| 544 | while (it.next()) |return_address| { | 733 | while (it.next()) |return_address| { |
| 734 | if (it.getLastError()) |unwind_error| | ||
| 735 | try printUnwindError(debug_info, out_stream, unwind_error.address, unwind_error.err, tty_config); | ||
| 736 | |||
| 545 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, | 737 | // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, |
| 546 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid | 738 | // therefore, we do a check for `return_address == 0` before subtracting 1 from it to avoid |
| 547 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this | 739 | // an overflow. We do not need to signal `StackIterator` as it will correctly detect this |
| ... | @@ -552,7 +744,7 @@ pub fn writeCurrentStackTrace( | ... | @@ -552,7 +744,7 @@ pub fn writeCurrentStackTrace( |
| 552 | } | 744 | } |
| 553 | } | 745 | } |
| 554 | 746 | ||
| 555 | pub noinline fn walkStackWindows(addresses: []usize) usize { | 747 | pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize { |
| 556 | if (builtin.cpu.arch == .x86) { | 748 | if (builtin.cpu.arch == .x86) { |
| 557 | // RtlVirtualUnwind doesn't exist on x86 | 749 | // RtlVirtualUnwind doesn't exist on x86 |
| 558 | return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null); | 750 | return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null); |
| ... | @@ -560,8 +752,13 @@ pub noinline fn walkStackWindows(addresses: []usize) usize { | ... | @@ -560,8 +752,13 @@ pub noinline fn walkStackWindows(addresses: []usize) usize { |
| 560 | 752 | ||
| 561 | const tib = @as(*const windows.NT_TIB, @ptrCast(&windows.teb().Reserved1)); | 753 | const tib = @as(*const windows.NT_TIB, @ptrCast(&windows.teb().Reserved1)); |
| 562 | 754 | ||
| 563 | var context: windows.CONTEXT = std.mem.zeroes(windows.CONTEXT); | 755 | var context: windows.CONTEXT = undefined; |
| 564 | windows.ntdll.RtlCaptureContext(&context); | 756 | if (existing_context) |context_ptr| { |
| 757 | context = context_ptr.*; | ||
| 758 | } else { | ||
| 759 | context = std.mem.zeroes(windows.CONTEXT); | ||
| 760 | windows.ntdll.RtlCaptureContext(&context); | ||
| 761 | } | ||
| 565 | 762 | ||
| 566 | var i: usize = 0; | 763 | var i: usize = 0; |
| 567 | var image_base: usize = undefined; | 764 | var image_base: usize = undefined; |
| ... | @@ -603,14 +800,15 @@ pub noinline fn walkStackWindows(addresses: []usize) usize { | ... | @@ -603,14 +800,15 @@ pub noinline fn walkStackWindows(addresses: []usize) usize { |
| 603 | return i; | 800 | return i; |
| 604 | } | 801 | } |
| 605 | 802 | ||
| 606 | pub fn writeCurrentStackTraceWindows( | 803 | pub fn writeStackTraceWindows( |
| 607 | out_stream: anytype, | 804 | out_stream: anytype, |
| 608 | debug_info: *DebugInfo, | 805 | debug_info: *DebugInfo, |
| 609 | tty_config: io.tty.Config, | 806 | tty_config: io.tty.Config, |
| 807 | context: *const windows.CONTEXT, | ||
| 610 | start_addr: ?usize, | 808 | start_addr: ?usize, |
| 611 | ) !void { | 809 | ) !void { |
| 612 | var addr_buf: [1024]usize = undefined; | 810 | var addr_buf: [1024]usize = undefined; |
| 613 | const n = walkStackWindows(addr_buf[0..]); | 811 | const n = walkStackWindows(addr_buf[0..], context); |
| 614 | const addrs = addr_buf[0..n]; | 812 | const addrs = addr_buf[0..n]; |
| 615 | var start_i: usize = if (start_addr) |saddr| blk: { | 813 | var start_i: usize = if (start_addr) |saddr| blk: { |
| 616 | for (addrs, 0..) |addr, i| { | 814 | for (addrs, 0..) |addr, i| { |
| ... | @@ -681,6 +879,13 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz | ... | @@ -681,6 +879,13 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz |
| 681 | ); | 879 | ); |
| 682 | } | 880 | } |
| 683 | 881 | ||
| 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 "???"; | ||
| 884 | try tty_config.setColor(out_stream, .dim); | ||
| 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); | ||
| 887 | } | ||
| 888 | |||
| 684 | pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { | 889 | pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { |
| 685 | const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { | 890 | const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { |
| 686 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), | 891 | error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), |
| ... | @@ -779,12 +984,8 @@ pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugI | ... | @@ -779,12 +984,8 @@ pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugI |
| 779 | } | 984 | } |
| 780 | } | 985 | } |
| 781 | 986 | ||
| 782 | fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDebugInfo { | 987 | fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebugInfo { |
| 783 | nosuspend { | 988 | nosuspend { |
| 784 | const coff_obj = try allocator.create(coff.Coff); | ||
| 785 | defer allocator.destroy(coff_obj); | ||
| 786 | coff_obj.* = try coff.Coff.init(coff_bytes); | ||
| 787 | |||
| 788 | var di = ModuleDebugInfo{ | 989 | var di = ModuleDebugInfo{ |
| 789 | .base_address = undefined, | 990 | .base_address = undefined, |
| 790 | .coff_image_base = coff_obj.getImageBase(), | 991 | .coff_image_base = coff_obj.getImageBase(), |
| ... | @@ -792,62 +993,35 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDe | ... | @@ -792,62 +993,35 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDe |
| 792 | .debug_data = undefined, | 993 | .debug_data = undefined, |
| 793 | }; | 994 | }; |
| 794 | 995 | ||
| 795 | if (coff_obj.getSectionByName(".debug_info")) |sec| { | 996 | if (coff_obj.getSectionByName(".debug_info")) |_| { |
| 796 | // This coff file has embedded DWARF debug info | 997 | // This coff file has embedded DWARF debug info |
| 797 | _ = sec; | 998 | var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; |
| 798 | 999 | errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); | |
| 799 | const debug_info = coff_obj.getSectionDataAlloc(".debug_info", allocator) catch return error.MissingDebugInfo; | 1000 | |
| 800 | errdefer allocator.free(debug_info); | 1001 | inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { |
| 801 | const debug_abbrev = coff_obj.getSectionDataAlloc(".debug_abbrev", allocator) catch return error.MissingDebugInfo; | 1002 | sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { |
| 802 | errdefer allocator.free(debug_abbrev); | 1003 | break :blk .{ |
| 803 | const debug_str = coff_obj.getSectionDataAlloc(".debug_str", allocator) catch return error.MissingDebugInfo; | 1004 | .data = try coff_obj.getSectionDataAlloc(section_header, allocator), |
| 804 | errdefer allocator.free(debug_str); | 1005 | .virtual_address = section_header.virtual_address, |
| 805 | const debug_line = coff_obj.getSectionDataAlloc(".debug_line", allocator) catch return error.MissingDebugInfo; | 1006 | .owned = true, |
| 806 | errdefer allocator.free(debug_line); | 1007 | }; |
| 807 | 1008 | } else null; | |
| 808 | const debug_str_offsets = coff_obj.getSectionDataAlloc(".debug_str_offsets", allocator) catch null; | 1009 | } |
| 809 | const debug_line_str = coff_obj.getSectionDataAlloc(".debug_line_str", allocator) catch null; | ||
| 810 | const debug_ranges = coff_obj.getSectionDataAlloc(".debug_ranges", allocator) catch null; | ||
| 811 | const debug_loclists = coff_obj.getSectionDataAlloc(".debug_loclists", allocator) catch null; | ||
| 812 | const debug_rnglists = coff_obj.getSectionDataAlloc(".debug_rnglists", allocator) catch null; | ||
| 813 | const debug_addr = coff_obj.getSectionDataAlloc(".debug_addr", allocator) catch null; | ||
| 814 | const debug_names = coff_obj.getSectionDataAlloc(".debug_names", allocator) catch null; | ||
| 815 | const debug_frame = coff_obj.getSectionDataAlloc(".debug_frame", allocator) catch null; | ||
| 816 | 1010 | ||
| 817 | var dwarf = DW.DwarfInfo{ | 1011 | var dwarf = DW.DwarfInfo{ |
| 818 | .endian = native_endian, | 1012 | .endian = native_endian, |
| 819 | .debug_info = debug_info, | 1013 | .sections = sections, |
| 820 | .debug_abbrev = debug_abbrev, | 1014 | .is_macho = false, |
| 821 | .debug_str = debug_str, | ||
| 822 | .debug_str_offsets = debug_str_offsets, | ||
| 823 | .debug_line = debug_line, | ||
| 824 | .debug_line_str = debug_line_str, | ||
| 825 | .debug_ranges = debug_ranges, | ||
| 826 | .debug_loclists = debug_loclists, | ||
| 827 | .debug_rnglists = debug_rnglists, | ||
| 828 | .debug_addr = debug_addr, | ||
| 829 | .debug_names = debug_names, | ||
| 830 | .debug_frame = debug_frame, | ||
| 831 | }; | ||
| 832 | |||
| 833 | DW.openDwarfDebugInfo(&dwarf, allocator) catch |err| { | ||
| 834 | if (debug_str_offsets) |d| allocator.free(d); | ||
| 835 | if (debug_line_str) |d| allocator.free(d); | ||
| 836 | if (debug_ranges) |d| allocator.free(d); | ||
| 837 | if (debug_loclists) |d| allocator.free(d); | ||
| 838 | if (debug_rnglists) |d| allocator.free(d); | ||
| 839 | if (debug_addr) |d| allocator.free(d); | ||
| 840 | if (debug_names) |d| allocator.free(d); | ||
| 841 | if (debug_frame) |d| allocator.free(d); | ||
| 842 | return err; | ||
| 843 | }; | 1015 | }; |
| 844 | 1016 | ||
| 1017 | try DW.openDwarfDebugInfo(&dwarf, allocator); | ||
| 845 | di.debug_data = PdbOrDwarf{ .dwarf = dwarf }; | 1018 | di.debug_data = PdbOrDwarf{ .dwarf = dwarf }; |
| 846 | return di; | 1019 | return di; |
| 847 | } | 1020 | } |
| 848 | 1021 | ||
| 849 | // Only used by pdb path | 1022 | // Only used by pdb path |
| 850 | di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator); | 1023 | di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator); |
| 1024 | errdefer allocator.free(di.coff_section_headers); | ||
| 851 | 1025 | ||
| 852 | var path_buf: [windows.MAX_PATH]u8 = undefined; | 1026 | var path_buf: [windows.MAX_PATH]u8 = undefined; |
| 853 | const len = try coff_obj.getPdbPath(path_buf[0..]); | 1027 | const len = try coff_obj.getPdbPath(path_buf[0..]); |
| ... | @@ -877,13 +1051,35 @@ fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 | ... | @@ -877,13 +1051,35 @@ fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 |
| 877 | return ptr[start..end]; | 1051 | return ptr[start..end]; |
| 878 | } | 1052 | } |
| 879 | 1053 | ||
| 880 | /// This takes ownership of elf_file: users of this function should not close | 1054 | /// Reads debug info from an ELF file, or the current binary if none in specified. |
| 881 | /// it themselves, even on error. | 1055 | /// If the required sections aren't present but a reference to external debug info is, |
| 882 | /// TODO it's weird to take ownership even on error, rework this code. | 1056 | /// then this this function will recurse to attempt to load the debug sections from |
| 883 | pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugInfo { | 1057 | /// an external file. |
| 1058 | pub fn readElfDebugInfo( | ||
| 1059 | allocator: mem.Allocator, | ||
| 1060 | elf_filename: ?[]const u8, | ||
| 1061 | build_id: ?[]const u8, | ||
| 1062 | expected_crc: ?u32, | ||
| 1063 | parent_sections: *DW.DwarfInfo.SectionArray, | ||
| 1064 | parent_mapped_mem: ?[]align(mem.page_size) const u8, | ||
| 1065 | ) !ModuleDebugInfo { | ||
| 884 | nosuspend { | 1066 | nosuspend { |
| 1067 | |||
| 1068 | // TODO https://github.com/ziglang/zig/issues/5525 | ||
| 1069 | const elf_file = (if (elf_filename) |filename| blk: { | ||
| 1070 | break :blk if (fs.path.isAbsolute(filename)) | ||
| 1071 | fs.openFileAbsolute(filename, .{ .intended_io_mode = .blocking }) | ||
| 1072 | else | ||
| 1073 | fs.cwd().openFile(filename, .{ .intended_io_mode = .blocking }); | ||
| 1074 | } else fs.openSelfExe(.{ .intended_io_mode = .blocking })) catch |err| switch (err) { | ||
| 1075 | error.FileNotFound => return error.MissingDebugInfo, | ||
| 1076 | else => return err, | ||
| 1077 | }; | ||
| 1078 | |||
| 885 | const mapped_mem = try mapWholeFile(elf_file); | 1079 | const mapped_mem = try mapWholeFile(elf_file); |
| 886 | const hdr = @as(*const elf.Ehdr, @ptrCast(&mapped_mem[0])); | 1080 | if (expected_crc) |crc| if (crc != std.hash.crc.Crc32SmallWithPoly(.IEEE).hash(mapped_mem)) return error.InvalidDebugInfo; |
| 1081 | |||
| 1082 | const hdr: *const elf.Ehdr = @ptrCast(&mapped_mem[0]); | ||
| 887 | if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; | 1083 | if (!mem.eql(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; |
| 888 | if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; | 1084 | if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; |
| 889 | 1085 | ||
| ... | @@ -896,73 +1092,152 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn | ... | @@ -896,73 +1092,152 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn |
| 896 | 1092 | ||
| 897 | const shoff = hdr.e_shoff; | 1093 | const shoff = hdr.e_shoff; |
| 898 | const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); | 1094 | const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); |
| 899 | const str_shdr: *const elf.Shdr = @ptrCast(@alignCast( | 1095 | const str_shdr: *const elf.Shdr = @ptrCast(@alignCast(&mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow])); |
| 900 | &mapped_mem[math.cast(usize, str_section_off) orelse return error.Overflow], | 1096 | const header_strings = mapped_mem[str_shdr.sh_offset..][0..str_shdr.sh_size]; |
| 901 | )); | ||
| 902 | const header_strings = mapped_mem[str_shdr.sh_offset .. str_shdr.sh_offset + str_shdr.sh_size]; | ||
| 903 | const shdrs = @as( | 1097 | const shdrs = @as( |
| 904 | [*]const elf.Shdr, | 1098 | [*]const elf.Shdr, |
| 905 | @ptrCast(@alignCast(&mapped_mem[shoff])), | 1099 | @ptrCast(@alignCast(&mapped_mem[shoff])), |
| 906 | )[0..hdr.e_shnum]; | 1100 | )[0..hdr.e_shnum]; |
| 907 | 1101 | ||
| 908 | var opt_debug_info: ?[]const u8 = null; | 1102 | var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; |
| 909 | var opt_debug_abbrev: ?[]const u8 = null; | ||
| 910 | var opt_debug_str: ?[]const u8 = null; | ||
| 911 | var opt_debug_str_offsets: ?[]const u8 = null; | ||
| 912 | var opt_debug_line: ?[]const u8 = null; | ||
| 913 | var opt_debug_line_str: ?[]const u8 = null; | ||
| 914 | var opt_debug_ranges: ?[]const u8 = null; | ||
| 915 | var opt_debug_loclists: ?[]const u8 = null; | ||
| 916 | var opt_debug_rnglists: ?[]const u8 = null; | ||
| 917 | var opt_debug_addr: ?[]const u8 = null; | ||
| 918 | var opt_debug_names: ?[]const u8 = null; | ||
| 919 | var opt_debug_frame: ?[]const u8 = null; | ||
| 920 | 1103 | ||
| 921 | for (shdrs) |*shdr| { | 1104 | // Combine section list. This takes ownership over any owned sections from the parent scope. |
| 922 | if (shdr.sh_type == elf.SHT_NULL) continue; | 1105 | for (parent_sections, &sections) |*parent, *section| { |
| 1106 | if (parent.*) |*p| { | ||
| 1107 | section.* = p.*; | ||
| 1108 | p.owned = false; | ||
| 1109 | } | ||
| 1110 | } | ||
| 1111 | errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); | ||
| 1112 | |||
| 1113 | var separate_debug_filename: ?[]const u8 = null; | ||
| 1114 | var separate_debug_crc: ?u32 = null; | ||
| 923 | 1115 | ||
| 1116 | for (shdrs) |*shdr| { | ||
| 1117 | if (shdr.sh_type == elf.SHT_NULL or shdr.sh_type == elf.SHT_NOBITS) continue; | ||
| 924 | const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); | 1118 | const name = mem.sliceTo(header_strings[shdr.sh_name..], 0); |
| 925 | if (mem.eql(u8, name, ".debug_info")) { | 1119 | |
| 926 | opt_debug_info = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1120 | if (mem.eql(u8, name, ".gnu_debuglink")) { |
| 927 | } else if (mem.eql(u8, name, ".debug_abbrev")) { | 1121 | const gnu_debuglink = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 928 | opt_debug_abbrev = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1122 | const debug_filename = mem.sliceTo(@as([*:0]const u8, @ptrCast(gnu_debuglink.ptr)), 0); |
| 929 | } else if (mem.eql(u8, name, ".debug_str")) { | 1123 | const crc_offset = mem.alignForward(usize, @intFromPtr(&debug_filename[debug_filename.len]) + 1, 4) - @intFromPtr(gnu_debuglink.ptr); |
| 930 | opt_debug_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1124 | const crc_bytes = gnu_debuglink[crc_offset .. crc_offset + 4]; |
| 931 | } else if (mem.eql(u8, name, ".debug_str_offsets")) { | 1125 | separate_debug_crc = mem.readIntSliceNative(u32, crc_bytes); |
| 932 | opt_debug_str_offsets = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1126 | separate_debug_filename = debug_filename; |
| 933 | } else if (mem.eql(u8, name, ".debug_line")) { | 1127 | continue; |
| 934 | opt_debug_line = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1128 | } |
| 935 | } else if (mem.eql(u8, name, ".debug_line_str")) { | 1129 | |
| 936 | opt_debug_line_str = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1130 | var section_index: ?usize = null; |
| 937 | } else if (mem.eql(u8, name, ".debug_ranges")) { | 1131 | inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { |
| 938 | opt_debug_ranges = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1132 | if (mem.eql(u8, "." ++ section.name, name)) section_index = i; |
| 939 | } else if (mem.eql(u8, name, ".debug_loclists")) { | 1133 | } |
| 940 | opt_debug_loclists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1134 | if (section_index == null) continue; |
| 941 | } else if (mem.eql(u8, name, ".debug_rnglists")) { | 1135 | if (sections[section_index.?] != null) continue; |
| 942 | opt_debug_rnglists = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1136 | |
| 943 | } else if (mem.eql(u8, name, ".debug_addr")) { | 1137 | const section_bytes = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); |
| 944 | opt_debug_addr = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1138 | sections[section_index.?] = if ((shdr.sh_flags & elf.SHF_COMPRESSED) > 0) blk: { |
| 945 | } else if (mem.eql(u8, name, ".debug_names")) { | 1139 | var section_stream = io.fixedBufferStream(section_bytes); |
| 946 | opt_debug_names = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1140 | var section_reader = section_stream.reader(); |
| 947 | } else if (mem.eql(u8, name, ".debug_frame")) { | 1141 | const chdr = section_reader.readStruct(elf.Chdr) catch continue; |
| 948 | opt_debug_frame = try chopSlice(mapped_mem, shdr.sh_offset, shdr.sh_size); | 1142 | if (chdr.ch_type != .ZLIB) continue; |
| 1143 | |||
| 1144 | var zlib_stream = std.compress.zlib.decompressStream(allocator, section_stream.reader()) catch continue; | ||
| 1145 | defer zlib_stream.deinit(); | ||
| 1146 | |||
| 1147 | var decompressed_section = try allocator.alloc(u8, chdr.ch_size); | ||
| 1148 | errdefer allocator.free(decompressed_section); | ||
| 1149 | |||
| 1150 | const read = zlib_stream.reader().readAll(decompressed_section) catch continue; | ||
| 1151 | assert(read == decompressed_section.len); | ||
| 1152 | |||
| 1153 | break :blk .{ | ||
| 1154 | .data = decompressed_section, | ||
| 1155 | .virtual_address = shdr.sh_addr, | ||
| 1156 | .owned = true, | ||
| 1157 | }; | ||
| 1158 | } else .{ | ||
| 1159 | .data = section_bytes, | ||
| 1160 | .virtual_address = shdr.sh_addr, | ||
| 1161 | .owned = false, | ||
| 1162 | }; | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | const missing_debug_info = | ||
| 1166 | sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or | ||
| 1167 | sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or | ||
| 1168 | sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or | ||
| 1169 | sections[@intFromEnum(DW.DwarfSection.debug_line)] == null; | ||
| 1170 | |||
| 1171 | // Attempt to load debug info from an external file | ||
| 1172 | // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html | ||
| 1173 | if (missing_debug_info) { | ||
| 1174 | |||
| 1175 | // Only allow one level of debug info nesting | ||
| 1176 | if (parent_mapped_mem) |_| { | ||
| 1177 | return error.MissingDebugInfo; | ||
| 1178 | } | ||
| 1179 | |||
| 1180 | const global_debug_directories = [_][]const u8{ | ||
| 1181 | "/usr/lib/debug", | ||
| 1182 | }; | ||
| 1183 | |||
| 1184 | // <global debug directory>/.build-id/<2-character id prefix>/<id remainder>.debug | ||
| 1185 | if (build_id) |id| blk: { | ||
| 1186 | if (id.len < 3) break :blk; | ||
| 1187 | |||
| 1188 | // Either md5 (16 bytes) or sha1 (20 bytes) are used here in practice | ||
| 1189 | const extension = ".debug"; | ||
| 1190 | var id_prefix_buf: [2]u8 = undefined; | ||
| 1191 | var filename_buf: [38 + extension.len]u8 = undefined; | ||
| 1192 | |||
| 1193 | _ = std.fmt.bufPrint(&id_prefix_buf, "{s}", .{std.fmt.fmtSliceHexLower(id[0..1])}) catch unreachable; | ||
| 1194 | const filename = std.fmt.bufPrint( | ||
| 1195 | &filename_buf, | ||
| 1196 | "{s}" ++ extension, | ||
| 1197 | .{std.fmt.fmtSliceHexLower(id[1..])}, | ||
| 1198 | ) catch break :blk; | ||
| 1199 | |||
| 1200 | for (global_debug_directories) |global_directory| { | ||
| 1201 | const path = try fs.path.join(allocator, &.{ global_directory, ".build-id", &id_prefix_buf, filename }); | ||
| 1202 | defer allocator.free(path); | ||
| 1203 | |||
| 1204 | return readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem) catch continue; | ||
| 1205 | } | ||
| 1206 | } | ||
| 1207 | |||
| 1208 | // use the path from .gnu_debuglink, in the same search order as gdb | ||
| 1209 | if (separate_debug_filename) |separate_filename| blk: { | ||
| 1210 | if (elf_filename != null and mem.eql(u8, elf_filename.?, separate_filename)) return error.MissingDebugInfo; | ||
| 1211 | |||
| 1212 | // <cwd>/<gnu_debuglink> | ||
| 1213 | if (readElfDebugInfo(allocator, separate_filename, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | ||
| 1214 | |||
| 1215 | // <cwd>/.debug/<gnu_debuglink> | ||
| 1216 | { | ||
| 1217 | const path = try fs.path.join(allocator, &.{ ".debug", separate_filename }); | ||
| 1218 | defer allocator.free(path); | ||
| 1219 | |||
| 1220 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | var cwd_buf: [fs.MAX_PATH_BYTES]u8 = undefined; | ||
| 1224 | const cwd_path = fs.cwd().realpath("", &cwd_buf) catch break :blk; | ||
| 1225 | |||
| 1226 | // <global debug directory>/<absolute folder of current binary>/<gnu_debuglink> | ||
| 1227 | for (global_debug_directories) |global_directory| { | ||
| 1228 | const path = try fs.path.join(allocator, &.{ global_directory, cwd_path, separate_filename }); | ||
| 1229 | defer allocator.free(path); | ||
| 1230 | if (readElfDebugInfo(allocator, path, null, separate_debug_crc, &sections, mapped_mem)) |debug_info| return debug_info else |_| {} | ||
| 1231 | } | ||
| 949 | } | 1232 | } |
| 1233 | |||
| 1234 | return error.MissingDebugInfo; | ||
| 950 | } | 1235 | } |
| 951 | 1236 | ||
| 952 | var di = DW.DwarfInfo{ | 1237 | var di = DW.DwarfInfo{ |
| 953 | .endian = endian, | 1238 | .endian = endian, |
| 954 | .debug_info = opt_debug_info orelse return error.MissingDebugInfo, | 1239 | .sections = sections, |
| 955 | .debug_abbrev = opt_debug_abbrev orelse return error.MissingDebugInfo, | 1240 | .is_macho = false, |
| 956 | .debug_str = opt_debug_str orelse return error.MissingDebugInfo, | ||
| 957 | .debug_str_offsets = opt_debug_str_offsets, | ||
| 958 | .debug_line = opt_debug_line orelse return error.MissingDebugInfo, | ||
| 959 | .debug_line_str = opt_debug_line_str, | ||
| 960 | .debug_ranges = opt_debug_ranges, | ||
| 961 | .debug_loclists = opt_debug_loclists, | ||
| 962 | .debug_rnglists = opt_debug_rnglists, | ||
| 963 | .debug_addr = opt_debug_addr, | ||
| 964 | .debug_names = opt_debug_names, | ||
| 965 | .debug_frame = opt_debug_frame, | ||
| 966 | }; | 1241 | }; |
| 967 | 1242 | ||
| 968 | try DW.openDwarfDebugInfo(&di, allocator); | 1243 | try DW.openDwarfDebugInfo(&di, allocator); |
| ... | @@ -970,7 +1245,8 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn | ... | @@ -970,7 +1245,8 @@ pub fn readElfDebugInfo(allocator: mem.Allocator, elf_file: File) !ModuleDebugIn |
| 970 | return ModuleDebugInfo{ | 1245 | return ModuleDebugInfo{ |
| 971 | .base_address = undefined, | 1246 | .base_address = undefined, |
| 972 | .dwarf = di, | 1247 | .dwarf = di, |
| 973 | .mapped_memory = mapped_mem, | 1248 | .mapped_memory = parent_mapped_mem orelse mapped_mem, |
| 1249 | .external_mapped_memory = if (parent_mapped_mem != null) mapped_mem else null, | ||
| 974 | }; | 1250 | }; |
| 975 | } | 1251 | } |
| 976 | } | 1252 | } |
| ... | @@ -1094,6 +1370,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn | ... | @@ -1094,6 +1370,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn |
| 1094 | 1370 | ||
| 1095 | return ModuleDebugInfo{ | 1371 | return ModuleDebugInfo{ |
| 1096 | .base_address = undefined, | 1372 | .base_address = undefined, |
| 1373 | .vmaddr_slide = undefined, | ||
| 1097 | .mapped_memory = mapped_mem, | 1374 | .mapped_memory = mapped_mem, |
| 1098 | .ofiles = ModuleDebugInfo.OFileTable.init(allocator), | 1375 | .ofiles = ModuleDebugInfo.OFileTable.init(allocator), |
| 1099 | .symbols = symbols, | 1376 | .symbols = symbols, |
| ... | @@ -1180,6 +1457,21 @@ pub const WindowsModuleInfo = struct { | ... | @@ -1180,6 +1457,21 @@ pub const WindowsModuleInfo = struct { |
| 1180 | base_address: usize, | 1457 | base_address: usize, |
| 1181 | size: u32, | 1458 | size: u32, |
| 1182 | name: []const u8, | 1459 | name: []const u8, |
| 1460 | handle: windows.HMODULE, | ||
| 1461 | |||
| 1462 | // Set when the image file needed to be mapped from disk | ||
| 1463 | mapped_file: ?struct { | ||
| 1464 | file: File, | ||
| 1465 | section_handle: windows.HANDLE, | ||
| 1466 | section_view: []const u8, | ||
| 1467 | |||
| 1468 | pub fn deinit(self: @This()) void { | ||
| 1469 | const process_handle = windows.kernel32.GetCurrentProcess(); | ||
| 1470 | assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(@ptrCast(self.section_view.ptr))) == .SUCCESS); | ||
| 1471 | windows.CloseHandle(self.section_handle); | ||
| 1472 | self.file.close(); | ||
| 1473 | } | ||
| 1474 | } = null, | ||
| 1183 | }; | 1475 | }; |
| 1184 | 1476 | ||
| 1185 | pub const DebugInfo = struct { | 1477 | pub const DebugInfo = struct { |
| ... | @@ -1195,6 +1487,8 @@ pub const DebugInfo = struct { | ... | @@ -1195,6 +1487,8 @@ pub const DebugInfo = struct { |
| 1195 | }; | 1487 | }; |
| 1196 | 1488 | ||
| 1197 | if (native_os == .windows) { | 1489 | if (native_os == .windows) { |
| 1490 | errdefer debug_info.modules.deinit(allocator); | ||
| 1491 | |||
| 1198 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); | 1492 | const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); |
| 1199 | if (handle == windows.INVALID_HANDLE_VALUE) { | 1493 | if (handle == windows.INVALID_HANDLE_VALUE) { |
| 1200 | switch (windows.kernel32.GetLastError()) { | 1494 | switch (windows.kernel32.GetLastError()) { |
| ... | @@ -1212,9 +1506,16 @@ pub const DebugInfo = struct { | ... | @@ -1212,9 +1506,16 @@ pub const DebugInfo = struct { |
| 1212 | var module_valid = true; | 1506 | var module_valid = true; |
| 1213 | while (module_valid) { | 1507 | while (module_valid) { |
| 1214 | const module_info = try debug_info.modules.addOne(allocator); | 1508 | const module_info = try debug_info.modules.addOne(allocator); |
| 1215 | module_info.base_address = @intFromPtr(module_entry.modBaseAddr); | 1509 | const name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; |
| 1216 | module_info.size = module_entry.modBaseSize; | 1510 | errdefer allocator.free(name); |
| 1217 | module_info.name = allocator.dupe(u8, mem.sliceTo(&module_entry.szModule, 0)) catch &.{}; | 1511 | |
| 1512 | module_info.* = .{ | ||
| 1513 | .base_address = @intFromPtr(module_entry.modBaseAddr), | ||
| 1514 | .size = module_entry.modBaseSize, | ||
| 1515 | .name = name, | ||
| 1516 | .handle = module_entry.hModule, | ||
| 1517 | }; | ||
| 1518 | |||
| 1218 | module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; | 1519 | module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1; |
| 1219 | } | 1520 | } |
| 1220 | } | 1521 | } |
| ... | @@ -1233,6 +1534,7 @@ pub const DebugInfo = struct { | ... | @@ -1233,6 +1534,7 @@ pub const DebugInfo = struct { |
| 1233 | if (native_os == .windows) { | 1534 | if (native_os == .windows) { |
| 1234 | for (self.modules.items) |module| { | 1535 | for (self.modules.items) |module| { |
| 1235 | self.allocator.free(module.name); | 1536 | self.allocator.free(module.name); |
| 1537 | if (module.mapped_file) |mapped_file| mapped_file.deinit(); | ||
| 1236 | } | 1538 | } |
| 1237 | self.modules.deinit(self.allocator); | 1539 | self.modules.deinit(self.allocator); |
| 1238 | } | 1540 | } |
| ... | @@ -1252,9 +1554,12 @@ pub const DebugInfo = struct { | ... | @@ -1252,9 +1554,12 @@ pub const DebugInfo = struct { |
| 1252 | } | 1554 | } |
| 1253 | } | 1555 | } |
| 1254 | 1556 | ||
| 1557 | // Returns the module name for a given address. | ||
| 1558 | // This can be called when getModuleForAddress fails, so implementations should provide | ||
| 1559 | // a path that doesn't rely on any side-effects of a prior successful module lookup. | ||
| 1255 | pub fn getModuleNameForAddress(self: *DebugInfo, address: usize) ?[]const u8 { | 1560 | pub fn getModuleNameForAddress(self: *DebugInfo, address: usize) ?[]const u8 { |
| 1256 | if (comptime builtin.target.isDarwin()) { | 1561 | if (comptime builtin.target.isDarwin()) { |
| 1257 | return null; | 1562 | return self.lookupModuleNameDyld(address); |
| 1258 | } else if (native_os == .windows) { | 1563 | } else if (native_os == .windows) { |
| 1259 | return self.lookupModuleNameWin32(address); | 1564 | return self.lookupModuleNameWin32(address); |
| 1260 | } else if (native_os == .haiku) { | 1565 | } else if (native_os == .haiku) { |
| ... | @@ -1262,7 +1567,7 @@ pub const DebugInfo = struct { | ... | @@ -1262,7 +1567,7 @@ pub const DebugInfo = struct { |
| 1262 | } else if (comptime builtin.target.isWasm()) { | 1567 | } else if (comptime builtin.target.isWasm()) { |
| 1263 | return null; | 1568 | return null; |
| 1264 | } else { | 1569 | } else { |
| 1265 | return null; | 1570 | return self.lookupModuleNameDl(address); |
| 1266 | } | 1571 | } |
| 1267 | } | 1572 | } |
| 1268 | 1573 | ||
| ... | @@ -1271,11 +1576,10 @@ pub const DebugInfo = struct { | ... | @@ -1271,11 +1576,10 @@ pub const DebugInfo = struct { |
| 1271 | 1576 | ||
| 1272 | var i: u32 = 0; | 1577 | var i: u32 = 0; |
| 1273 | while (i < image_count) : (i += 1) { | 1578 | while (i < image_count) : (i += 1) { |
| 1274 | const base_address = std.c._dyld_get_image_vmaddr_slide(i); | ||
| 1275 | |||
| 1276 | if (address < base_address) continue; | ||
| 1277 | |||
| 1278 | const header = std.c._dyld_get_image_header(i) orelse continue; | 1579 | const header = std.c._dyld_get_image_header(i) orelse continue; |
| 1580 | const base_address = @intFromPtr(header); | ||
| 1581 | if (address < base_address) continue; | ||
| 1582 | const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); | ||
| 1279 | 1583 | ||
| 1280 | var it = macho.LoadCommandIterator{ | 1584 | var it = macho.LoadCommandIterator{ |
| 1281 | .ncmds = header.ncmds, | 1585 | .ncmds = header.ncmds, |
| ... | @@ -1284,18 +1588,29 @@ pub const DebugInfo = struct { | ... | @@ -1284,18 +1588,29 @@ pub const DebugInfo = struct { |
| 1284 | @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), | 1588 | @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), |
| 1285 | )[0..header.sizeofcmds]), | 1589 | )[0..header.sizeofcmds]), |
| 1286 | }; | 1590 | }; |
| 1591 | |||
| 1592 | var unwind_info: ?[]const u8 = null; | ||
| 1593 | var eh_frame: ?[]const u8 = null; | ||
| 1287 | while (it.next()) |cmd| switch (cmd.cmd()) { | 1594 | while (it.next()) |cmd| switch (cmd.cmd()) { |
| 1288 | .SEGMENT_64 => { | 1595 | .SEGMENT_64 => { |
| 1289 | const segment_cmd = cmd.cast(macho.segment_command_64).?; | 1596 | const segment_cmd = cmd.cast(macho.segment_command_64).?; |
| 1290 | const rebased_address = address - base_address; | 1597 | if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; |
| 1291 | const seg_start = segment_cmd.vmaddr; | ||
| 1292 | const seg_end = seg_start + segment_cmd.vmsize; | ||
| 1293 | 1598 | ||
| 1294 | if (rebased_address >= seg_start and rebased_address < seg_end) { | 1599 | const seg_start = segment_cmd.vmaddr + vmaddr_slide; |
| 1600 | const seg_end = seg_start + segment_cmd.vmsize; | ||
| 1601 | if (address >= seg_start and address < seg_end) { | ||
| 1295 | if (self.address_map.get(base_address)) |obj_di| { | 1602 | if (self.address_map.get(base_address)) |obj_di| { |
| 1296 | return obj_di; | 1603 | return obj_di; |
| 1297 | } | 1604 | } |
| 1298 | 1605 | ||
| 1606 | for (cmd.getSections()) |sect| { | ||
| 1607 | if (mem.eql(u8, "__unwind_info", sect.sectName())) { | ||
| 1608 | unwind_info = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; | ||
| 1609 | } else if (mem.eql(u8, "__eh_frame", sect.sectName())) { | ||
| 1610 | eh_frame = @as([*]const u8, @ptrFromInt(sect.addr + vmaddr_slide))[0..sect.size]; | ||
| 1611 | } | ||
| 1612 | } | ||
| 1613 | |||
| 1299 | const obj_di = try self.allocator.create(ModuleDebugInfo); | 1614 | const obj_di = try self.allocator.create(ModuleDebugInfo); |
| 1300 | errdefer self.allocator.destroy(obj_di); | 1615 | errdefer self.allocator.destroy(obj_di); |
| 1301 | 1616 | ||
| ... | @@ -1308,6 +1623,9 @@ pub const DebugInfo = struct { | ... | @@ -1308,6 +1623,9 @@ pub const DebugInfo = struct { |
| 1308 | }; | 1623 | }; |
| 1309 | obj_di.* = try readMachODebugInfo(self.allocator, macho_file); | 1624 | obj_di.* = try readMachODebugInfo(self.allocator, macho_file); |
| 1310 | obj_di.base_address = base_address; | 1625 | obj_di.base_address = base_address; |
| 1626 | obj_di.vmaddr_slide = vmaddr_slide; | ||
| 1627 | obj_di.unwind_info = unwind_info; | ||
| 1628 | obj_di.eh_frame = eh_frame; | ||
| 1311 | 1629 | ||
| 1312 | try self.address_map.putNoClobber(base_address, obj_di); | 1630 | try self.address_map.putNoClobber(base_address, obj_di); |
| 1313 | 1631 | ||
| ... | @@ -1321,18 +1639,124 @@ pub const DebugInfo = struct { | ... | @@ -1321,18 +1639,124 @@ pub const DebugInfo = struct { |
| 1321 | return error.MissingDebugInfo; | 1639 | return error.MissingDebugInfo; |
| 1322 | } | 1640 | } |
| 1323 | 1641 | ||
| 1642 | fn lookupModuleNameDyld(self: *DebugInfo, address: usize) ?[]const u8 { | ||
| 1643 | _ = self; | ||
| 1644 | const image_count = std.c._dyld_image_count(); | ||
| 1645 | |||
| 1646 | var i: u32 = 0; | ||
| 1647 | while (i < image_count) : (i += 1) { | ||
| 1648 | const header = std.c._dyld_get_image_header(i) orelse continue; | ||
| 1649 | const base_address = @intFromPtr(header); | ||
| 1650 | if (address < base_address) continue; | ||
| 1651 | const vmaddr_slide = std.c._dyld_get_image_vmaddr_slide(i); | ||
| 1652 | |||
| 1653 | var it = macho.LoadCommandIterator{ | ||
| 1654 | .ncmds = header.ncmds, | ||
| 1655 | .buffer = @alignCast(@as( | ||
| 1656 | [*]u8, | ||
| 1657 | @ptrFromInt(@intFromPtr(header) + @sizeOf(macho.mach_header_64)), | ||
| 1658 | )[0..header.sizeofcmds]), | ||
| 1659 | }; | ||
| 1660 | |||
| 1661 | while (it.next()) |cmd| switch (cmd.cmd()) { | ||
| 1662 | .SEGMENT_64 => { | ||
| 1663 | const segment_cmd = cmd.cast(macho.segment_command_64).?; | ||
| 1664 | if (!mem.eql(u8, "__TEXT", segment_cmd.segName())) continue; | ||
| 1665 | |||
| 1666 | const original_address = address - vmaddr_slide; | ||
| 1667 | const seg_start = segment_cmd.vmaddr; | ||
| 1668 | const seg_end = seg_start + segment_cmd.vmsize; | ||
| 1669 | if (original_address >= seg_start and original_address < seg_end) { | ||
| 1670 | return fs.path.basename(mem.sliceTo(std.c._dyld_get_image_name(i), 0)); | ||
| 1671 | } | ||
| 1672 | }, | ||
| 1673 | else => {}, | ||
| 1674 | }; | ||
| 1675 | } | ||
| 1676 | |||
| 1677 | return null; | ||
| 1678 | } | ||
| 1679 | |||
| 1324 | fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo { | 1680 | fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo { |
| 1325 | for (self.modules.items) |module| { | 1681 | for (self.modules.items) |*module| { |
| 1326 | if (address >= module.base_address and address < module.base_address + module.size) { | 1682 | if (address >= module.base_address and address < module.base_address + module.size) { |
| 1327 | if (self.address_map.get(module.base_address)) |obj_di| { | 1683 | if (self.address_map.get(module.base_address)) |obj_di| { |
| 1328 | return obj_di; | 1684 | return obj_di; |
| 1329 | } | 1685 | } |
| 1330 | 1686 | ||
| 1331 | const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; | ||
| 1332 | const obj_di = try self.allocator.create(ModuleDebugInfo); | 1687 | const obj_di = try self.allocator.create(ModuleDebugInfo); |
| 1333 | errdefer self.allocator.destroy(obj_di); | 1688 | errdefer self.allocator.destroy(obj_di); |
| 1334 | 1689 | ||
| 1335 | obj_di.* = try readCoffDebugInfo(self.allocator, mapped_module); | 1690 | const mapped_module = @as([*]const u8, @ptrFromInt(module.base_address))[0..module.size]; |
| 1691 | var coff_obj = try coff.Coff.init(mapped_module); | ||
| 1692 | |||
| 1693 | // The string table is not mapped into memory by the loader, so if a section name is in the | ||
| 1694 | // string table then we have to map the full image file from disk. This can happen when | ||
| 1695 | // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. | ||
| 1696 | if (coff_obj.strtabRequired()) { | ||
| 1697 | var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; | ||
| 1698 | // openFileAbsoluteW requires the prefix to be present | ||
| 1699 | mem.copy(u16, name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' }); | ||
| 1700 | |||
| 1701 | const process_handle = windows.kernel32.GetCurrentProcess(); | ||
| 1702 | const len = windows.kernel32.K32GetModuleFileNameExW( | ||
| 1703 | process_handle, | ||
| 1704 | module.handle, | ||
| 1705 | @ptrCast(&name_buffer[4]), | ||
| 1706 | windows.PATH_MAX_WIDE, | ||
| 1707 | ); | ||
| 1708 | |||
| 1709 | if (len == 0) return error.MissingDebugInfo; | ||
| 1710 | const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) { | ||
| 1711 | error.FileNotFound => return error.MissingDebugInfo, | ||
| 1712 | else => return err, | ||
| 1713 | }; | ||
| 1714 | errdefer coff_file.close(); | ||
| 1715 | |||
| 1716 | var section_handle: windows.HANDLE = undefined; | ||
| 1717 | const create_section_rc = windows.ntdll.NtCreateSection( | ||
| 1718 | &section_handle, | ||
| 1719 | windows.STANDARD_RIGHTS_REQUIRED | windows.SECTION_QUERY | windows.SECTION_MAP_READ, | ||
| 1720 | null, | ||
| 1721 | null, | ||
| 1722 | windows.PAGE_READONLY, | ||
| 1723 | // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. | ||
| 1724 | // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. | ||
| 1725 | windows.SEC_COMMIT, | ||
| 1726 | coff_file.handle, | ||
| 1727 | ); | ||
| 1728 | if (create_section_rc != .SUCCESS) return error.MissingDebugInfo; | ||
| 1729 | errdefer windows.CloseHandle(section_handle); | ||
| 1730 | |||
| 1731 | var coff_len: usize = 0; | ||
| 1732 | var base_ptr: usize = 0; | ||
| 1733 | const map_section_rc = windows.ntdll.NtMapViewOfSection( | ||
| 1734 | section_handle, | ||
| 1735 | process_handle, | ||
| 1736 | @ptrCast(&base_ptr), | ||
| 1737 | null, | ||
| 1738 | 0, | ||
| 1739 | null, | ||
| 1740 | &coff_len, | ||
| 1741 | .ViewUnmap, | ||
| 1742 | 0, | ||
| 1743 | windows.PAGE_READONLY, | ||
| 1744 | ); | ||
| 1745 | if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; | ||
| 1746 | errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @ptrFromInt(base_ptr)) == .SUCCESS); | ||
| 1747 | |||
| 1748 | const section_view = @as([*]const u8, @ptrFromInt(base_ptr))[0..coff_len]; | ||
| 1749 | coff_obj = try coff.Coff.init(section_view); | ||
| 1750 | |||
| 1751 | module.mapped_file = .{ | ||
| 1752 | .file = coff_file, | ||
| 1753 | .section_handle = section_handle, | ||
| 1754 | .section_view = section_view, | ||
| 1755 | }; | ||
| 1756 | } | ||
| 1757 | errdefer if (module.mapped_file) |mapped_file| mapped_file.deinit(); | ||
| 1758 | |||
| 1759 | obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); | ||
| 1336 | obj_di.base_address = module.base_address; | 1760 | obj_di.base_address = module.base_address; |
| 1337 | 1761 | ||
| 1338 | try self.address_map.putNoClobber(module.base_address, obj_di); | 1762 | try self.address_map.putNoClobber(module.base_address, obj_di); |
| ... | @@ -1352,6 +1776,44 @@ pub const DebugInfo = struct { | ... | @@ -1352,6 +1776,44 @@ pub const DebugInfo = struct { |
| 1352 | return null; | 1776 | return null; |
| 1353 | } | 1777 | } |
| 1354 | 1778 | ||
| 1779 | fn lookupModuleNameDl(self: *DebugInfo, address: usize) ?[]const u8 { | ||
| 1780 | _ = self; | ||
| 1781 | |||
| 1782 | var ctx: struct { | ||
| 1783 | // Input | ||
| 1784 | address: usize, | ||
| 1785 | // Output | ||
| 1786 | name: []const u8 = "", | ||
| 1787 | } = .{ .address = address }; | ||
| 1788 | const CtxTy = @TypeOf(ctx); | ||
| 1789 | |||
| 1790 | if (os.dl_iterate_phdr(&ctx, error{Found}, struct { | ||
| 1791 | fn callback(info: *os.dl_phdr_info, size: usize, context: *CtxTy) !void { | ||
| 1792 | _ = size; | ||
| 1793 | if (context.address < info.dlpi_addr) return; | ||
| 1794 | const phdrs = info.dlpi_phdr[0..info.dlpi_phnum]; | ||
| 1795 | for (phdrs) |*phdr| { | ||
| 1796 | if (phdr.p_type != elf.PT_LOAD) continue; | ||
| 1797 | |||
| 1798 | const seg_start = info.dlpi_addr +% phdr.p_vaddr; | ||
| 1799 | const seg_end = seg_start + phdr.p_memsz; | ||
| 1800 | if (context.address >= seg_start and context.address < seg_end) { | ||
| 1801 | context.name = mem.sliceTo(info.dlpi_name, 0) orelse ""; | ||
| 1802 | break; | ||
| 1803 | } | ||
| 1804 | } else return; | ||
| 1805 | |||
| 1806 | return error.Found; | ||
| 1807 | } | ||
| 1808 | }.callback)) { | ||
| 1809 | return null; | ||
| 1810 | } else |err| switch (err) { | ||
| 1811 | error.Found => return fs.path.basename(ctx.name), | ||
| 1812 | } | ||
| 1813 | |||
| 1814 | return null; | ||
| 1815 | } | ||
| 1816 | |||
| 1355 | fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo { | 1817 | fn lookupModuleDl(self: *DebugInfo, address: usize) !*ModuleDebugInfo { |
| 1356 | var ctx: struct { | 1818 | var ctx: struct { |
| 1357 | // Input | 1819 | // Input |
| ... | @@ -1359,6 +1821,8 @@ pub const DebugInfo = struct { | ... | @@ -1359,6 +1821,8 @@ pub const DebugInfo = struct { |
| 1359 | // Output | 1821 | // Output |
| 1360 | base_address: usize = undefined, | 1822 | base_address: usize = undefined, |
| 1361 | name: []const u8 = undefined, | 1823 | name: []const u8 = undefined, |
| 1824 | build_id: ?[]const u8 = null, | ||
| 1825 | gnu_eh_frame: ?[]const u8 = null, | ||
| 1362 | } = .{ .address = address }; | 1826 | } = .{ .address = address }; |
| 1363 | const CtxTy = @TypeOf(ctx); | 1827 | const CtxTy = @TypeOf(ctx); |
| 1364 | 1828 | ||
| ... | @@ -1373,18 +1837,40 @@ pub const DebugInfo = struct { | ... | @@ -1373,18 +1837,40 @@ pub const DebugInfo = struct { |
| 1373 | for (phdrs) |*phdr| { | 1837 | for (phdrs) |*phdr| { |
| 1374 | if (phdr.p_type != elf.PT_LOAD) continue; | 1838 | if (phdr.p_type != elf.PT_LOAD) continue; |
| 1375 | 1839 | ||
| 1376 | const seg_start = info.dlpi_addr + phdr.p_vaddr; | 1840 | // Overflowing addition is used to handle the case of VSDOs having a p_vaddr = 0xffffffffff700000 |
| 1841 | const seg_start = info.dlpi_addr +% phdr.p_vaddr; | ||
| 1377 | const seg_end = seg_start + phdr.p_memsz; | 1842 | const seg_end = seg_start + phdr.p_memsz; |
| 1378 | |||
| 1379 | if (context.address >= seg_start and context.address < seg_end) { | 1843 | if (context.address >= seg_start and context.address < seg_end) { |
| 1380 | // Android libc uses NULL instead of an empty string to mark the | 1844 | // Android libc uses NULL instead of an empty string to mark the |
| 1381 | // main program | 1845 | // main program |
| 1382 | context.name = mem.sliceTo(info.dlpi_name, 0) orelse ""; | 1846 | context.name = mem.sliceTo(info.dlpi_name, 0) orelse ""; |
| 1383 | context.base_address = info.dlpi_addr; | 1847 | context.base_address = info.dlpi_addr; |
| 1384 | // Stop the iteration | 1848 | break; |
| 1385 | return error.Found; | 1849 | } |
| 1850 | } else return; | ||
| 1851 | |||
| 1852 | for (info.dlpi_phdr[0..info.dlpi_phnum]) |phdr| { | ||
| 1853 | switch (phdr.p_type) { | ||
| 1854 | elf.PT_NOTE => { | ||
| 1855 | // Look for .note.gnu.build-id | ||
| 1856 | const note_bytes = @as([*]const u8, @ptrFromInt(info.dlpi_addr + phdr.p_vaddr))[0..phdr.p_memsz]; | ||
| 1857 | const name_size = mem.readIntSliceNative(u32, note_bytes[0..4]); | ||
| 1858 | if (name_size != 4) continue; | ||
| 1859 | const desc_size = mem.readIntSliceNative(u32, note_bytes[4..8]); | ||
| 1860 | const note_type = mem.readIntSliceNative(u32, note_bytes[8..12]); | ||
| 1861 | if (note_type != elf.NT_GNU_BUILD_ID) continue; | ||
| 1862 | if (!mem.eql(u8, "GNU\x00", note_bytes[12..16])) continue; | ||
| 1863 | context.build_id = note_bytes[16..][0..desc_size]; | ||
| 1864 | }, | ||
| 1865 | elf.PT_GNU_EH_FRAME => { | ||
| 1866 | context.gnu_eh_frame = @as([*]const u8, @ptrFromInt(info.dlpi_addr + phdr.p_vaddr))[0..phdr.p_memsz]; | ||
| 1867 | }, | ||
| 1868 | else => {}, | ||
| 1386 | } | 1869 | } |
| 1387 | } | 1870 | } |
| 1871 | |||
| 1872 | // Stop the iteration | ||
| 1873 | return error.Found; | ||
| 1388 | } | 1874 | } |
| 1389 | }.callback)) { | 1875 | }.callback)) { |
| 1390 | return error.MissingDebugInfo; | 1876 | return error.MissingDebugInfo; |
| ... | @@ -1399,20 +1885,24 @@ pub const DebugInfo = struct { | ... | @@ -1399,20 +1885,24 @@ pub const DebugInfo = struct { |
| 1399 | const obj_di = try self.allocator.create(ModuleDebugInfo); | 1885 | const obj_di = try self.allocator.create(ModuleDebugInfo); |
| 1400 | errdefer self.allocator.destroy(obj_di); | 1886 | errdefer self.allocator.destroy(obj_di); |
| 1401 | 1887 | ||
| 1402 | // TODO https://github.com/ziglang/zig/issues/5525 | 1888 | var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; |
| 1403 | const copy = if (ctx.name.len > 0) | 1889 | if (ctx.gnu_eh_frame) |eh_frame_hdr| { |
| 1404 | fs.cwd().openFile(ctx.name, .{ .intended_io_mode = .blocking }) | 1890 | // This is a special case - pointer offsets inside .eh_frame_hdr |
| 1405 | else | 1891 | // are encoded relative to its base address, so we must use the |
| 1406 | fs.openSelfExe(.{ .intended_io_mode = .blocking }); | 1892 | // version that is already memory mapped, and not the one that |
| 1407 | 1893 | // will be mapped separately from the ELF file. | |
| 1408 | const elf_file = copy catch |err| switch (err) { | 1894 | sections[@intFromEnum(DW.DwarfSection.eh_frame_hdr)] = .{ |
| 1409 | error.FileNotFound => return error.MissingDebugInfo, | 1895 | .data = eh_frame_hdr, |
| 1410 | else => return err, | 1896 | .owned = false, |
| 1411 | }; | 1897 | }; |
| 1898 | } | ||
| 1412 | 1899 | ||
| 1413 | obj_di.* = try readElfDebugInfo(self.allocator, elf_file); | 1900 | obj_di.* = try readElfDebugInfo(self.allocator, if (ctx.name.len > 0) ctx.name else null, ctx.build_id, null, &sections, null); |
| 1414 | obj_di.base_address = ctx.base_address; | 1901 | obj_di.base_address = ctx.base_address; |
| 1415 | 1902 | ||
| 1903 | // Missing unwind info isn't treated as a failure, as the unwinder will fall back to FP-based unwinding | ||
| 1904 | obj_di.dwarf.scanAllUnwindInfo(self.allocator, ctx.base_address) catch {}; | ||
| 1905 | |||
| 1416 | try self.address_map.putNoClobber(ctx.base_address, obj_di); | 1906 | try self.address_map.putNoClobber(ctx.base_address, obj_di); |
| 1417 | 1907 | ||
| 1418 | return obj_di; | 1908 | return obj_di; |
| ... | @@ -1434,11 +1924,16 @@ pub const DebugInfo = struct { | ... | @@ -1434,11 +1924,16 @@ pub const DebugInfo = struct { |
| 1434 | pub const ModuleDebugInfo = switch (native_os) { | 1924 | pub const ModuleDebugInfo = switch (native_os) { |
| 1435 | .macos, .ios, .watchos, .tvos => struct { | 1925 | .macos, .ios, .watchos, .tvos => struct { |
| 1436 | base_address: usize, | 1926 | base_address: usize, |
| 1927 | vmaddr_slide: usize, | ||
| 1437 | mapped_memory: []align(mem.page_size) const u8, | 1928 | mapped_memory: []align(mem.page_size) const u8, |
| 1438 | symbols: []const MachoSymbol, | 1929 | symbols: []const MachoSymbol, |
| 1439 | strings: [:0]const u8, | 1930 | strings: [:0]const u8, |
| 1440 | ofiles: OFileTable, | 1931 | ofiles: OFileTable, |
| 1441 | 1932 | ||
| 1933 | // Backed by the in-memory sections mapped by the loader | ||
| 1934 | unwind_info: ?[]const u8 = null, | ||
| 1935 | eh_frame: ?[]const u8 = null, | ||
| 1936 | |||
| 1442 | const OFileTable = std.StringHashMap(OFileInfo); | 1937 | const OFileTable = std.StringHashMap(OFileInfo); |
| 1443 | const OFileInfo = struct { | 1938 | const OFileInfo = struct { |
| 1444 | di: DW.DwarfInfo, | 1939 | di: DW.DwarfInfo, |
| ... | @@ -1457,7 +1952,7 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -1457,7 +1952,7 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 1457 | os.munmap(self.mapped_memory); | 1952 | os.munmap(self.mapped_memory); |
| 1458 | } | 1953 | } |
| 1459 | 1954 | ||
| 1460 | fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !OFileInfo { | 1955 | fn loadOFile(self: *@This(), allocator: mem.Allocator, o_file_path: []const u8) !*OFileInfo { |
| 1461 | const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking }); | 1956 | const o_file = try fs.cwd().openFile(o_file_path, .{ .intended_io_mode = .blocking }); |
| 1462 | const mapped_mem = try mapWholeFile(o_file); | 1957 | const mapped_mem = try mapWholeFile(o_file); |
| 1463 | 1958 | ||
| ... | @@ -1500,95 +1995,40 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -1500,95 +1995,40 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 1500 | addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); | 1995 | addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); |
| 1501 | } | 1996 | } |
| 1502 | 1997 | ||
| 1503 | var opt_debug_line: ?macho.section_64 = null; | 1998 | var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; |
| 1504 | var opt_debug_info: ?macho.section_64 = null; | 1999 | if (self.eh_frame) |eh_frame| sections[@intFromEnum(DW.DwarfSection.eh_frame)] = .{ |
| 1505 | var opt_debug_abbrev: ?macho.section_64 = null; | 2000 | .data = eh_frame, |
| 1506 | var opt_debug_str: ?macho.section_64 = null; | 2001 | .owned = false, |
| 1507 | var opt_debug_str_offsets: ?macho.section_64 = null; | 2002 | }; |
| 1508 | var opt_debug_line_str: ?macho.section_64 = null; | ||
| 1509 | var opt_debug_ranges: ?macho.section_64 = null; | ||
| 1510 | var opt_debug_loclists: ?macho.section_64 = null; | ||
| 1511 | var opt_debug_rnglists: ?macho.section_64 = null; | ||
| 1512 | var opt_debug_addr: ?macho.section_64 = null; | ||
| 1513 | var opt_debug_names: ?macho.section_64 = null; | ||
| 1514 | var opt_debug_frame: ?macho.section_64 = null; | ||
| 1515 | 2003 | ||
| 1516 | for (segcmd.?.getSections()) |sect| { | 2004 | for (segcmd.?.getSections()) |sect| { |
| 1517 | const name = sect.sectName(); | 2005 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; |
| 1518 | if (mem.eql(u8, name, "__debug_line")) { | 2006 | |
| 1519 | opt_debug_line = sect; | 2007 | var section_index: ?usize = null; |
| 1520 | } else if (mem.eql(u8, name, "__debug_info")) { | 2008 | inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { |
| 1521 | opt_debug_info = sect; | 2009 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; |
| 1522 | } else if (mem.eql(u8, name, "__debug_abbrev")) { | ||
| 1523 | opt_debug_abbrev = sect; | ||
| 1524 | } else if (mem.eql(u8, name, "__debug_str")) { | ||
| 1525 | opt_debug_str = sect; | ||
| 1526 | } else if (mem.eql(u8, name, "__debug_str_offsets")) { | ||
| 1527 | opt_debug_str_offsets = sect; | ||
| 1528 | } else if (mem.eql(u8, name, "__debug_line_str")) { | ||
| 1529 | opt_debug_line_str = sect; | ||
| 1530 | } else if (mem.eql(u8, name, "__debug_ranges")) { | ||
| 1531 | opt_debug_ranges = sect; | ||
| 1532 | } else if (mem.eql(u8, name, "__debug_loclists")) { | ||
| 1533 | opt_debug_loclists = sect; | ||
| 1534 | } else if (mem.eql(u8, name, "__debug_rnglists")) { | ||
| 1535 | opt_debug_rnglists = sect; | ||
| 1536 | } else if (mem.eql(u8, name, "__debug_addr")) { | ||
| 1537 | opt_debug_addr = sect; | ||
| 1538 | } else if (mem.eql(u8, name, "__debug_names")) { | ||
| 1539 | opt_debug_names = sect; | ||
| 1540 | } else if (mem.eql(u8, name, "__debug_frame")) { | ||
| 1541 | opt_debug_frame = sect; | ||
| 1542 | } | 2010 | } |
| 2011 | if (section_index == null) continue; | ||
| 2012 | |||
| 2013 | const section_bytes = try chopSlice(mapped_mem, sect.offset, sect.size); | ||
| 2014 | sections[section_index.?] = .{ | ||
| 2015 | .data = section_bytes, | ||
| 2016 | .virtual_address = sect.addr, | ||
| 2017 | .owned = false, | ||
| 2018 | }; | ||
| 1543 | } | 2019 | } |
| 1544 | 2020 | ||
| 1545 | const debug_line = opt_debug_line orelse | 2021 | const missing_debug_info = |
| 1546 | return error.MissingDebugInfo; | 2022 | sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or |
| 1547 | const debug_info = opt_debug_info orelse | 2023 | sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or |
| 1548 | return error.MissingDebugInfo; | 2024 | sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or |
| 1549 | const debug_str = opt_debug_str orelse | 2025 | sections[@intFromEnum(DW.DwarfSection.debug_line)] == null; |
| 1550 | return error.MissingDebugInfo; | 2026 | if (missing_debug_info) return error.MissingDebugInfo; |
| 1551 | const debug_abbrev = opt_debug_abbrev orelse | ||
| 1552 | return error.MissingDebugInfo; | ||
| 1553 | 2027 | ||
| 1554 | var di = DW.DwarfInfo{ | 2028 | var di = DW.DwarfInfo{ |
| 1555 | .endian = .Little, | 2029 | .endian = .Little, |
| 1556 | .debug_info = try chopSlice(mapped_mem, debug_info.offset, debug_info.size), | 2030 | .sections = sections, |
| 1557 | .debug_abbrev = try chopSlice(mapped_mem, debug_abbrev.offset, debug_abbrev.size), | 2031 | .is_macho = true, |
| 1558 | .debug_str = try chopSlice(mapped_mem, debug_str.offset, debug_str.size), | ||
| 1559 | .debug_str_offsets = if (opt_debug_str_offsets) |debug_str_offsets| | ||
| 1560 | try chopSlice(mapped_mem, debug_str_offsets.offset, debug_str_offsets.size) | ||
| 1561 | else | ||
| 1562 | null, | ||
| 1563 | .debug_line = try chopSlice(mapped_mem, debug_line.offset, debug_line.size), | ||
| 1564 | .debug_line_str = if (opt_debug_line_str) |debug_line_str| | ||
| 1565 | try chopSlice(mapped_mem, debug_line_str.offset, debug_line_str.size) | ||
| 1566 | else | ||
| 1567 | null, | ||
| 1568 | .debug_ranges = if (opt_debug_ranges) |debug_ranges| | ||
| 1569 | try chopSlice(mapped_mem, debug_ranges.offset, debug_ranges.size) | ||
| 1570 | else | ||
| 1571 | null, | ||
| 1572 | .debug_loclists = if (opt_debug_loclists) |debug_loclists| | ||
| 1573 | try chopSlice(mapped_mem, debug_loclists.offset, debug_loclists.size) | ||
| 1574 | else | ||
| 1575 | null, | ||
| 1576 | .debug_rnglists = if (opt_debug_rnglists) |debug_rnglists| | ||
| 1577 | try chopSlice(mapped_mem, debug_rnglists.offset, debug_rnglists.size) | ||
| 1578 | else | ||
| 1579 | null, | ||
| 1580 | .debug_addr = if (opt_debug_addr) |debug_addr| | ||
| 1581 | try chopSlice(mapped_mem, debug_addr.offset, debug_addr.size) | ||
| 1582 | else | ||
| 1583 | null, | ||
| 1584 | .debug_names = if (opt_debug_names) |debug_names| | ||
| 1585 | try chopSlice(mapped_mem, debug_names.offset, debug_names.size) | ||
| 1586 | else | ||
| 1587 | null, | ||
| 1588 | .debug_frame = if (opt_debug_frame) |debug_frame| | ||
| 1589 | try chopSlice(mapped_mem, debug_frame.offset, debug_frame.size) | ||
| 1590 | else | ||
| 1591 | null, | ||
| 1592 | }; | 2032 | }; |
| 1593 | 2033 | ||
| 1594 | try DW.openDwarfDebugInfo(&di, allocator); | 2034 | try DW.openDwarfDebugInfo(&di, allocator); |
| ... | @@ -1598,52 +2038,38 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -1598,52 +2038,38 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 1598 | }; | 2038 | }; |
| 1599 | 2039 | ||
| 1600 | // Add the debug info to the cache | 2040 | // Add the debug info to the cache |
| 1601 | try self.ofiles.putNoClobber(o_file_path, info); | 2041 | const result = try self.ofiles.getOrPut(o_file_path); |
| 2042 | assert(!result.found_existing); | ||
| 2043 | result.value_ptr.* = info; | ||
| 1602 | 2044 | ||
| 1603 | return info; | 2045 | return result.value_ptr; |
| 1604 | } | 2046 | } |
| 1605 | 2047 | ||
| 1606 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { | 2048 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { |
| 1607 | nosuspend { | 2049 | nosuspend { |
| 1608 | // Translate the VA into an address into this object | 2050 | const result = try self.getOFileInfoForAddress(allocator, address); |
| 1609 | const relocated_address = address - self.base_address; | 2051 | if (result.symbol == null) return .{}; |
| 1610 | |||
| 1611 | // Find the .o file where this symbol is defined | ||
| 1612 | const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse | ||
| 1613 | return SymbolInfo{}; | ||
| 1614 | const addr_off = relocated_address - symbol.addr; | ||
| 1615 | 2052 | ||
| 1616 | // Take the symbol name from the N_FUN STAB entry, we're going to | 2053 | // Take the symbol name from the N_FUN STAB entry, we're going to |
| 1617 | // use it if we fail to find the DWARF infos | 2054 | // use it if we fail to find the DWARF infos |
| 1618 | const stab_symbol = mem.sliceTo(self.strings[symbol.strx..], 0); | 2055 | const stab_symbol = mem.sliceTo(self.strings[result.symbol.?.strx..], 0); |
| 1619 | const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); | 2056 | if (result.o_file_info == null) return .{ .symbol_name = stab_symbol }; |
| 1620 | |||
| 1621 | // Check if its debug infos are already in the cache | ||
| 1622 | var o_file_info = self.ofiles.get(o_file_path) orelse | ||
| 1623 | (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { | ||
| 1624 | error.FileNotFound, | ||
| 1625 | error.MissingDebugInfo, | ||
| 1626 | error.InvalidDebugInfo, | ||
| 1627 | => { | ||
| 1628 | return SymbolInfo{ .symbol_name = stab_symbol }; | ||
| 1629 | }, | ||
| 1630 | else => return err, | ||
| 1631 | }); | ||
| 1632 | const o_file_di = &o_file_info.di; | ||
| 1633 | 2057 | ||
| 1634 | // Translate again the address, this time into an address inside the | 2058 | // Translate again the address, this time into an address inside the |
| 1635 | // .o file | 2059 | // .o file |
| 1636 | const relocated_address_o = o_file_info.addr_table.get(stab_symbol) orelse return SymbolInfo{ | 2060 | const relocated_address_o = result.o_file_info.?.addr_table.get(stab_symbol) orelse return .{ |
| 1637 | .symbol_name = "???", | 2061 | .symbol_name = "???", |
| 1638 | }; | 2062 | }; |
| 1639 | 2063 | ||
| 2064 | const addr_off = result.relocated_address - result.symbol.?.addr; | ||
| 2065 | const o_file_di = &result.o_file_info.?.di; | ||
| 1640 | if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { | 2066 | if (o_file_di.findCompileUnit(relocated_address_o)) |compile_unit| { |
| 1641 | return SymbolInfo{ | 2067 | return SymbolInfo{ |
| 1642 | .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", | 2068 | .symbol_name = o_file_di.getSymbolName(relocated_address_o) orelse "???", |
| 1643 | .compile_unit_name = compile_unit.die.getAttrString( | 2069 | .compile_unit_name = compile_unit.die.getAttrString( |
| 1644 | o_file_di, | 2070 | o_file_di, |
| 1645 | DW.AT.name, | 2071 | DW.AT.name, |
| 1646 | o_file_di.debug_str, | 2072 | o_file_di.section(.debug_str), |
| 1647 | compile_unit.*, | 2073 | compile_unit.*, |
| 1648 | ) catch |err| switch (err) { | 2074 | ) catch |err| switch (err) { |
| 1649 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | 2075 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", |
| ... | @@ -1663,39 +2089,61 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -1663,39 +2089,61 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 1663 | }, | 2089 | }, |
| 1664 | else => return err, | 2090 | else => return err, |
| 1665 | } | 2091 | } |
| 2092 | } | ||
| 2093 | } | ||
| 2094 | |||
| 2095 | pub fn getOFileInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !struct { | ||
| 2096 | relocated_address: usize, | ||
| 2097 | symbol: ?*const MachoSymbol = null, | ||
| 2098 | o_file_info: ?*OFileInfo = null, | ||
| 2099 | } { | ||
| 2100 | nosuspend { | ||
| 2101 | // Translate the VA into an address into this object | ||
| 2102 | const relocated_address = address - self.vmaddr_slide; | ||
| 2103 | |||
| 2104 | // Find the .o file where this symbol is defined | ||
| 2105 | const symbol = machoSearchSymbols(self.symbols, relocated_address) orelse return .{ | ||
| 2106 | .relocated_address = relocated_address, | ||
| 2107 | }; | ||
| 2108 | |||
| 2109 | // Check if its debug infos are already in the cache | ||
| 2110 | const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); | ||
| 2111 | var o_file_info = self.ofiles.getPtr(o_file_path) orelse | ||
| 2112 | (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { | ||
| 2113 | error.FileNotFound, | ||
| 2114 | error.MissingDebugInfo, | ||
| 2115 | error.InvalidDebugInfo, | ||
| 2116 | => return .{ | ||
| 2117 | .relocated_address = relocated_address, | ||
| 2118 | .symbol = symbol, | ||
| 2119 | }, | ||
| 2120 | else => return err, | ||
| 2121 | }); | ||
| 1666 | 2122 | ||
| 1667 | unreachable; | 2123 | return .{ |
| 2124 | .relocated_address = relocated_address, | ||
| 2125 | .symbol = symbol, | ||
| 2126 | .o_file_info = o_file_info, | ||
| 2127 | }; | ||
| 1668 | } | 2128 | } |
| 1669 | } | 2129 | } |
| 2130 | |||
| 2131 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { | ||
| 2132 | return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; | ||
| 2133 | } | ||
| 1670 | }, | 2134 | }, |
| 1671 | .uefi, .windows => struct { | 2135 | .uefi, .windows => struct { |
| 1672 | base_address: usize, | 2136 | base_address: usize, |
| 1673 | debug_data: PdbOrDwarf, | 2137 | debug_data: PdbOrDwarf, |
| 1674 | coff_image_base: u64, | 2138 | coff_image_base: u64, |
| 2139 | /// Only used if debug_data is .pdb | ||
| 1675 | coff_section_headers: []coff.SectionHeader, | 2140 | coff_section_headers: []coff.SectionHeader, |
| 1676 | 2141 | ||
| 1677 | fn deinit(self: *@This(), allocator: mem.Allocator) void { | 2142 | fn deinit(self: *@This(), allocator: mem.Allocator) void { |
| 1678 | switch (self.debug_data) { | ||
| 1679 | .dwarf => |*dwarf| { | ||
| 1680 | allocator.free(dwarf.debug_info); | ||
| 1681 | allocator.free(dwarf.debug_abbrev); | ||
| 1682 | allocator.free(dwarf.debug_str); | ||
| 1683 | allocator.free(dwarf.debug_line); | ||
| 1684 | if (dwarf.debug_str_offsets) |d| allocator.free(d); | ||
| 1685 | if (dwarf.debug_line_str) |d| allocator.free(d); | ||
| 1686 | if (dwarf.debug_ranges) |d| allocator.free(d); | ||
| 1687 | if (dwarf.debug_loclists) |d| allocator.free(d); | ||
| 1688 | if (dwarf.debug_rnglists) |d| allocator.free(d); | ||
| 1689 | if (dwarf.debug_addr) |d| allocator.free(d); | ||
| 1690 | if (dwarf.debug_names) |d| allocator.free(d); | ||
| 1691 | if (dwarf.debug_frame) |d| allocator.free(d); | ||
| 1692 | }, | ||
| 1693 | .pdb => { | ||
| 1694 | allocator.free(self.coff_section_headers); | ||
| 1695 | }, | ||
| 1696 | } | ||
| 1697 | |||
| 1698 | self.debug_data.deinit(allocator); | 2143 | self.debug_data.deinit(allocator); |
| 2144 | if (self.debug_data == .pdb) { | ||
| 2145 | allocator.free(self.coff_section_headers); | ||
| 2146 | } | ||
| 1699 | } | 2147 | } |
| 1700 | 2148 | ||
| 1701 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { | 2149 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { |
| ... | @@ -1747,15 +2195,27 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -1747,15 +2195,27 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 1747 | .line_info = opt_line_info, | 2195 | .line_info = opt_line_info, |
| 1748 | }; | 2196 | }; |
| 1749 | } | 2197 | } |
| 2198 | |||
| 2199 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { | ||
| 2200 | _ = allocator; | ||
| 2201 | _ = address; | ||
| 2202 | |||
| 2203 | return switch (self.debug_data) { | ||
| 2204 | .dwarf => |*dwarf| dwarf, | ||
| 2205 | else => null, | ||
| 2206 | }; | ||
| 2207 | } | ||
| 1750 | }, | 2208 | }, |
| 1751 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris => struct { | 2209 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris => struct { |
| 1752 | base_address: usize, | 2210 | base_address: usize, |
| 1753 | dwarf: DW.DwarfInfo, | 2211 | dwarf: DW.DwarfInfo, |
| 1754 | mapped_memory: []align(mem.page_size) const u8, | 2212 | mapped_memory: []align(mem.page_size) const u8, |
| 2213 | external_mapped_memory: ?[]align(mem.page_size) const u8, | ||
| 1755 | 2214 | ||
| 1756 | fn deinit(self: *@This(), allocator: mem.Allocator) void { | 2215 | fn deinit(self: *@This(), allocator: mem.Allocator) void { |
| 1757 | self.dwarf.deinit(allocator); | 2216 | self.dwarf.deinit(allocator); |
| 1758 | os.munmap(self.mapped_memory); | 2217 | os.munmap(self.mapped_memory); |
| 2218 | if (self.external_mapped_memory) |m| os.munmap(m); | ||
| 1759 | } | 2219 | } |
| 1760 | 2220 | ||
| 1761 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { | 2221 | pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo { |
| ... | @@ -1763,6 +2223,12 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -1763,6 +2223,12 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 1763 | const relocated_address = address - self.base_address; | 2223 | const relocated_address = address - self.base_address; |
| 1764 | return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); | 2224 | return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); |
| 1765 | } | 2225 | } |
| 2226 | |||
| 2227 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { | ||
| 2228 | _ = allocator; | ||
| 2229 | _ = address; | ||
| 2230 | return &self.dwarf; | ||
| 2231 | } | ||
| 1766 | }, | 2232 | }, |
| 1767 | .wasi => struct { | 2233 | .wasi => struct { |
| 1768 | fn deinit(self: *@This(), allocator: mem.Allocator) void { | 2234 | fn deinit(self: *@This(), allocator: mem.Allocator) void { |
| ... | @@ -1776,6 +2242,13 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -1776,6 +2242,13 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 1776 | _ = address; | 2242 | _ = address; |
| 1777 | return SymbolInfo{}; | 2243 | return SymbolInfo{}; |
| 1778 | } | 2244 | } |
| 2245 | |||
| 2246 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { | ||
| 2247 | _ = self; | ||
| 2248 | _ = allocator; | ||
| 2249 | _ = address; | ||
| 2250 | return null; | ||
| 2251 | } | ||
| 1779 | }, | 2252 | }, |
| 1780 | else => DW.DwarfInfo, | 2253 | else => DW.DwarfInfo, |
| 1781 | }; | 2254 | }; |
| ... | @@ -1784,7 +2257,7 @@ fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *DW.DwarfInfo) | ... | @@ -1784,7 +2257,7 @@ fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *DW.DwarfInfo) |
| 1784 | if (nosuspend di.findCompileUnit(address)) |compile_unit| { | 2257 | if (nosuspend di.findCompileUnit(address)) |compile_unit| { |
| 1785 | return SymbolInfo{ | 2258 | return SymbolInfo{ |
| 1786 | .symbol_name = nosuspend di.getSymbolName(address) orelse "???", | 2259 | .symbol_name = nosuspend di.getSymbolName(address) orelse "???", |
| 1787 | .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.debug_str, compile_unit.*) catch |err| switch (err) { | 2260 | .compile_unit_name = compile_unit.die.getAttrString(di, DW.AT.name, di.section(.debug_str), compile_unit.*) catch |err| switch (err) { |
| 1788 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", | 2261 | error.MissingDebugInfo, error.InvalidDebugInfo => "???", |
| 1789 | }, | 2262 | }, |
| 1790 | .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { | 2263 | .line_info = nosuspend di.getLineNumberInfo(allocator, compile_unit.*, address) catch |err| switch (err) { |
| ... | @@ -1932,52 +2405,13 @@ fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void | ... | @@ -1932,52 +2405,13 @@ fn dumpSegfaultInfoPosix(sig: i32, addr: usize, ctx_ptr: ?*const anyopaque) void |
| 1932 | } catch os.abort(); | 2405 | } catch os.abort(); |
| 1933 | 2406 | ||
| 1934 | switch (native_arch) { | 2407 | switch (native_arch) { |
| 1935 | .x86 => { | 2408 | .x86, |
| 1936 | const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | 2409 | .x86_64, |
| 1937 | const ip = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EIP])); | 2410 | .arm, |
| 1938 | const bp = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EBP])); | 2411 | .aarch64, |
| 1939 | dumpStackTraceFromBase(bp, ip); | 2412 | => { |
| 1940 | }, | ||
| 1941 | .x86_64 => { | ||
| 1942 | const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | ||
| 1943 | const ip = switch (native_os) { | ||
| 1944 | .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RIP])), | ||
| 1945 | .freebsd => @as(usize, @intCast(ctx.mcontext.rip)), | ||
| 1946 | .openbsd => @as(usize, @intCast(ctx.sc_rip)), | ||
| 1947 | .macos => @as(usize, @intCast(ctx.mcontext.ss.rip)), | ||
| 1948 | else => unreachable, | ||
| 1949 | }; | ||
| 1950 | const bp = switch (native_os) { | ||
| 1951 | .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RBP])), | ||
| 1952 | .openbsd => @as(usize, @intCast(ctx.sc_rbp)), | ||
| 1953 | .freebsd => @as(usize, @intCast(ctx.mcontext.rbp)), | ||
| 1954 | .macos => @as(usize, @intCast(ctx.mcontext.ss.rbp)), | ||
| 1955 | else => unreachable, | ||
| 1956 | }; | ||
| 1957 | dumpStackTraceFromBase(bp, ip); | ||
| 1958 | }, | ||
| 1959 | .arm => { | ||
| 1960 | const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | 2413 | const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); |
| 1961 | const ip = @as(usize, @intCast(ctx.mcontext.arm_pc)); | 2414 | dumpStackTraceFromBase(ctx); |
| 1962 | const bp = @as(usize, @intCast(ctx.mcontext.arm_fp)); | ||
| 1963 | dumpStackTraceFromBase(bp, ip); | ||
| 1964 | }, | ||
| 1965 | .aarch64 => { | ||
| 1966 | const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | ||
| 1967 | const ip = switch (native_os) { | ||
| 1968 | .macos => @as(usize, @intCast(ctx.mcontext.ss.pc)), | ||
| 1969 | .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.PC])), | ||
| 1970 | .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.elr)), | ||
| 1971 | else => @as(usize, @intCast(ctx.mcontext.pc)), | ||
| 1972 | }; | ||
| 1973 | // x29 is the ABI-designated frame pointer | ||
| 1974 | const bp = switch (native_os) { | ||
| 1975 | .macos => @as(usize, @intCast(ctx.mcontext.ss.fp)), | ||
| 1976 | .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.FP])), | ||
| 1977 | .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.x[os.REG.FP])), | ||
| 1978 | else => @as(usize, @intCast(ctx.mcontext.regs[29])), | ||
| 1979 | }; | ||
| 1980 | dumpStackTraceFromBase(bp, ip); | ||
| 1981 | }, | 2415 | }, |
| 1982 | else => {}, | 2416 | else => {}, |
| 1983 | } | 2417 | } |
| ... | @@ -2036,16 +2470,15 @@ fn handleSegfaultWindowsExtra( | ... | @@ -2036,16 +2470,15 @@ fn handleSegfaultWindowsExtra( |
| 2036 | } | 2470 | } |
| 2037 | 2471 | ||
| 2038 | fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void { | 2472 | fn dumpSegfaultInfoWindows(info: *windows.EXCEPTION_POINTERS, msg: u8, label: ?[]const u8) void { |
| 2039 | const regs = info.ContextRecord.getRegs(); | ||
| 2040 | const stderr = io.getStdErr().writer(); | 2473 | const stderr = io.getStdErr().writer(); |
| 2041 | _ = switch (msg) { | 2474 | _ = switch (msg) { |
| 2042 | 0 => stderr.print("{s}\n", .{label.?}), | 2475 | 0 => stderr.print("{s}\n", .{label.?}), |
| 2043 | 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), | 2476 | 1 => stderr.print("Segmentation fault at address 0x{x}\n", .{info.ExceptionRecord.ExceptionInformation[1]}), |
| 2044 | 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{regs.ip}), | 2477 | 2 => stderr.print("Illegal instruction at address 0x{x}\n", .{info.ContextRecord.getRegs().ip}), |
| 2045 | else => unreachable, | 2478 | else => unreachable, |
| 2046 | } catch os.abort(); | 2479 | } catch os.abort(); |
| 2047 | 2480 | ||
| 2048 | dumpStackTraceFromBase(regs.bp, regs.ip); | 2481 | dumpStackTraceFromBase(info.ContextRecord); |
| 2049 | } | 2482 | } |
| 2050 | 2483 | ||
| 2051 | pub fn dumpStackPointerAddr(prefix: []const u8) void { | 2484 | pub fn dumpStackPointerAddr(prefix: []const u8) void { |
lib/std/dwarf.zig+1462-116| ... | @@ -3,9 +3,11 @@ const std = @import("std.zig"); | ... | @@ -3,9 +3,11 @@ const std = @import("std.zig"); |
| 3 | const debug = std.debug; | 3 | const debug = std.debug; |
| 4 | const fs = std.fs; | 4 | const fs = std.fs; |
| 5 | const io = std.io; | 5 | const io = std.io; |
| 6 | const os = std.os; | ||
| 6 | const mem = std.mem; | 7 | const mem = std.mem; |
| 7 | const math = std.math; | 8 | const math = std.math; |
| 8 | const leb = @import("leb128.zig"); | 9 | const leb = @import("leb128.zig"); |
| 10 | const assert = std.debug.assert; | ||
| 9 | 11 | ||
| 10 | pub const TAG = @import("dwarf/TAG.zig"); | 12 | pub const TAG = @import("dwarf/TAG.zig"); |
| 11 | pub const AT = @import("dwarf/AT.zig"); | 13 | pub const AT = @import("dwarf/AT.zig"); |
| ... | @@ -13,6 +15,10 @@ pub const OP = @import("dwarf/OP.zig"); | ... | @@ -13,6 +15,10 @@ pub const OP = @import("dwarf/OP.zig"); |
| 13 | pub const LANG = @import("dwarf/LANG.zig"); | 15 | pub const LANG = @import("dwarf/LANG.zig"); |
| 14 | pub const FORM = @import("dwarf/FORM.zig"); | 16 | pub const FORM = @import("dwarf/FORM.zig"); |
| 15 | pub const ATE = @import("dwarf/ATE.zig"); | 17 | pub const ATE = @import("dwarf/ATE.zig"); |
| 18 | pub const EH = @import("dwarf/EH.zig"); | ||
| 19 | pub const abi = @import("dwarf/abi.zig"); | ||
| 20 | pub const call_frame = @import("dwarf/call_frame.zig"); | ||
| 21 | pub const expressions = @import("dwarf/expressions.zig"); | ||
| 16 | 22 | ||
| 17 | pub const LLE = struct { | 23 | pub const LLE = struct { |
| 18 | pub const end_of_list = 0x00; | 24 | pub const end_of_list = 0x00; |
| ... | @@ -140,11 +146,11 @@ pub const CC = enum(u8) { | ... | @@ -140,11 +146,11 @@ pub const CC = enum(u8) { |
| 140 | pass_by_reference = 0x4, | 146 | pass_by_reference = 0x4, |
| 141 | pass_by_value = 0x5, | 147 | pass_by_value = 0x5, |
| 142 | 148 | ||
| 143 | lo_user = 0x40, | ||
| 144 | hi_user = 0xff, | ||
| 145 | |||
| 146 | GNU_renesas_sh = 0x40, | 149 | GNU_renesas_sh = 0x40, |
| 147 | GNU_borland_fastcall_i386 = 0x41, | 150 | GNU_borland_fastcall_i386 = 0x41, |
| 151 | |||
| 152 | pub const lo_user = 0x40; | ||
| 153 | pub const hi_user = 0xff; | ||
| 148 | }; | 154 | }; |
| 149 | 155 | ||
| 150 | pub const Format = enum { @"32", @"64" }; | 156 | pub const Format = enum { @"32", @"64" }; |
| ... | @@ -157,15 +163,9 @@ const PcRange = struct { | ... | @@ -157,15 +163,9 @@ const PcRange = struct { |
| 157 | const Func = struct { | 163 | const Func = struct { |
| 158 | pc_range: ?PcRange, | 164 | pc_range: ?PcRange, |
| 159 | name: ?[]const u8, | 165 | name: ?[]const u8, |
| 160 | |||
| 161 | fn deinit(func: *Func, allocator: mem.Allocator) void { | ||
| 162 | if (func.name) |name| { | ||
| 163 | allocator.free(name); | ||
| 164 | } | ||
| 165 | } | ||
| 166 | }; | 166 | }; |
| 167 | 167 | ||
| 168 | const CompileUnit = struct { | 168 | pub const CompileUnit = struct { |
| 169 | version: u16, | 169 | version: u16, |
| 170 | is_64: bool, | 170 | is_64: bool, |
| 171 | die: *Die, | 171 | die: *Die, |
| ... | @@ -175,6 +175,7 @@ const CompileUnit = struct { | ... | @@ -175,6 +175,7 @@ const CompileUnit = struct { |
| 175 | addr_base: usize, | 175 | addr_base: usize, |
| 176 | rnglists_base: usize, | 176 | rnglists_base: usize, |
| 177 | loclists_base: usize, | 177 | loclists_base: usize, |
| 178 | frame_base: ?*const FormValue, | ||
| 178 | }; | 179 | }; |
| 179 | 180 | ||
| 180 | const AbbrevTable = std.ArrayList(AbbrevTableEntry); | 181 | const AbbrevTable = std.ArrayList(AbbrevTableEntry); |
| ... | @@ -210,7 +211,7 @@ const AbbrevAttr = struct { | ... | @@ -210,7 +211,7 @@ const AbbrevAttr = struct { |
| 210 | payload: i64, | 211 | payload: i64, |
| 211 | }; | 212 | }; |
| 212 | 213 | ||
| 213 | const FormValue = union(enum) { | 214 | pub const FormValue = union(enum) { |
| 214 | Address: u64, | 215 | Address: u64, |
| 215 | AddrOffset: usize, | 216 | AddrOffset: usize, |
| 216 | Block: []u8, | 217 | Block: []u8, |
| ... | @@ -292,7 +293,7 @@ const Die = struct { | ... | @@ -292,7 +293,7 @@ const Die = struct { |
| 292 | 293 | ||
| 293 | fn getAttrAddr( | 294 | fn getAttrAddr( |
| 294 | self: *const Die, | 295 | self: *const Die, |
| 295 | di: *DwarfInfo, | 296 | di: *const DwarfInfo, |
| 296 | id: u64, | 297 | id: u64, |
| 297 | compile_unit: CompileUnit, | 298 | compile_unit: CompileUnit, |
| 298 | ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 { | 299 | ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 { |
| ... | @@ -337,7 +338,7 @@ const Die = struct { | ... | @@ -337,7 +338,7 @@ const Die = struct { |
| 337 | FormValue.String => |value| return value, | 338 | FormValue.String => |value| return value, |
| 338 | FormValue.StrPtr => |offset| return di.getString(offset), | 339 | FormValue.StrPtr => |offset| return di.getString(offset), |
| 339 | FormValue.StrOffset => |index| { | 340 | FormValue.StrOffset => |index| { |
| 340 | const debug_str_offsets = di.debug_str_offsets orelse return badDwarf(); | 341 | const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf(); |
| 341 | if (compile_unit.str_offsets_base == 0) return badDwarf(); | 342 | if (compile_unit.str_offsets_base == 0) return badDwarf(); |
| 342 | if (compile_unit.is_64) { | 343 | if (compile_unit.is_64) { |
| 343 | const byte_offset = compile_unit.str_offsets_base + 8 * index; | 344 | const byte_offset = compile_unit.str_offsets_base + 8 * index; |
| ... | @@ -642,27 +643,75 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con | ... | @@ -642,27 +643,75 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con |
| 642 | return null; | 643 | return null; |
| 643 | } | 644 | } |
| 644 | 645 | ||
| 646 | pub const DwarfSection = enum { | ||
| 647 | debug_info, | ||
| 648 | debug_abbrev, | ||
| 649 | debug_str, | ||
| 650 | debug_str_offsets, | ||
| 651 | debug_line, | ||
| 652 | debug_line_str, | ||
| 653 | debug_ranges, | ||
| 654 | debug_loclists, | ||
| 655 | debug_rnglists, | ||
| 656 | debug_addr, | ||
| 657 | debug_names, | ||
| 658 | debug_frame, | ||
| 659 | eh_frame, | ||
| 660 | eh_frame_hdr, | ||
| 661 | }; | ||
| 662 | |||
| 645 | pub const DwarfInfo = struct { | 663 | pub const DwarfInfo = struct { |
| 664 | pub const Section = struct { | ||
| 665 | data: []const u8, | ||
| 666 | // Module-relative virtual address. | ||
| 667 | // Only set if the section data was loaded from disk. | ||
| 668 | virtual_address: ?usize = null, | ||
| 669 | // If `data` is owned by this DwarfInfo. | ||
| 670 | owned: bool, | ||
| 671 | |||
| 672 | // For sections that are not memory mapped by the loader, this is an offset | ||
| 673 | // from `data.ptr` to where the section would have been mapped. Otherwise, | ||
| 674 | // `data` is directly backed by the section and the offset is zero. | ||
| 675 | pub fn virtualOffset(self: Section, base_address: usize) i64 { | ||
| 676 | return if (self.virtual_address) |va| | ||
| 677 | @as(i64, @intCast(base_address + va)) - | ||
| 678 | @as(i64, @intCast(@intFromPtr(self.data.ptr))) | ||
| 679 | else | ||
| 680 | 0; | ||
| 681 | } | ||
| 682 | }; | ||
| 683 | |||
| 684 | const num_sections = std.enums.directEnumArrayLen(DwarfSection, 0); | ||
| 685 | pub const SectionArray = [num_sections]?Section; | ||
| 686 | pub const null_section_array = [_]?Section{null} ** num_sections; | ||
| 687 | |||
| 646 | endian: std.builtin.Endian, | 688 | endian: std.builtin.Endian, |
| 647 | // No memory is owned by the DwarfInfo | 689 | sections: SectionArray = null_section_array, |
| 648 | debug_info: []const u8, | 690 | is_macho: bool, |
| 649 | debug_abbrev: []const u8, | 691 | |
| 650 | debug_str: []const u8, | ||
| 651 | debug_str_offsets: ?[]const u8, | ||
| 652 | debug_line: []const u8, | ||
| 653 | debug_line_str: ?[]const u8, | ||
| 654 | debug_ranges: ?[]const u8, | ||
| 655 | debug_loclists: ?[]const u8, | ||
| 656 | debug_rnglists: ?[]const u8, | ||
| 657 | debug_addr: ?[]const u8, | ||
| 658 | debug_names: ?[]const u8, | ||
| 659 | debug_frame: ?[]const u8, | ||
| 660 | // Filled later by the initializer | 692 | // Filled later by the initializer |
| 661 | abbrev_table_list: std.ArrayListUnmanaged(AbbrevTableHeader) = .{}, | 693 | abbrev_table_list: std.ArrayListUnmanaged(AbbrevTableHeader) = .{}, |
| 662 | compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{}, | 694 | compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{}, |
| 663 | func_list: std.ArrayListUnmanaged(Func) = .{}, | 695 | func_list: std.ArrayListUnmanaged(Func) = .{}, |
| 664 | 696 | ||
| 697 | eh_frame_hdr: ?ExceptionFrameHeader = null, | ||
| 698 | // These lookup tables are only used if `eh_frame_hdr` is null | ||
| 699 | cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{}, | ||
| 700 | // Sorted by start_pc | ||
| 701 | fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{}, | ||
| 702 | |||
| 703 | pub fn section(di: DwarfInfo, dwarf_section: DwarfSection) ?[]const u8 { | ||
| 704 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null; | ||
| 705 | } | ||
| 706 | |||
| 707 | pub fn sectionVirtualOffset(di: DwarfInfo, dwarf_section: DwarfSection, base_address: usize) ?i64 { | ||
| 708 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; | ||
| 709 | } | ||
| 710 | |||
| 665 | pub fn deinit(di: *DwarfInfo, allocator: mem.Allocator) void { | 711 | pub fn deinit(di: *DwarfInfo, allocator: mem.Allocator) void { |
| 712 | for (di.sections) |opt_section| { | ||
| 713 | if (opt_section) |s| if (s.owned) allocator.free(s.data); | ||
| 714 | } | ||
| 666 | for (di.abbrev_table_list.items) |*abbrev| { | 715 | for (di.abbrev_table_list.items) |*abbrev| { |
| 667 | abbrev.deinit(); | 716 | abbrev.deinit(); |
| 668 | } | 717 | } |
| ... | @@ -672,10 +721,9 @@ pub const DwarfInfo = struct { | ... | @@ -672,10 +721,9 @@ pub const DwarfInfo = struct { |
| 672 | allocator.destroy(cu.die); | 721 | allocator.destroy(cu.die); |
| 673 | } | 722 | } |
| 674 | di.compile_unit_list.deinit(allocator); | 723 | di.compile_unit_list.deinit(allocator); |
| 675 | for (di.func_list.items) |*func| { | ||
| 676 | func.deinit(allocator); | ||
| 677 | } | ||
| 678 | di.func_list.deinit(allocator); | 724 | di.func_list.deinit(allocator); |
| 725 | di.cie_map.deinit(allocator); | ||
| 726 | di.fde_list.deinit(allocator); | ||
| 679 | } | 727 | } |
| 680 | 728 | ||
| 681 | pub fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 { | 729 | pub fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 { |
| ... | @@ -691,7 +739,7 @@ pub const DwarfInfo = struct { | ... | @@ -691,7 +739,7 @@ pub const DwarfInfo = struct { |
| 691 | } | 739 | } |
| 692 | 740 | ||
| 693 | fn scanAllFunctions(di: *DwarfInfo, allocator: mem.Allocator) !void { | 741 | fn scanAllFunctions(di: *DwarfInfo, allocator: mem.Allocator) !void { |
| 694 | var stream = io.fixedBufferStream(di.debug_info); | 742 | var stream = io.fixedBufferStream(di.section(.debug_info).?); |
| 695 | const in = stream.reader(); | 743 | const in = stream.reader(); |
| 696 | const seekable = &stream.seekableStream(); | 744 | const seekable = &stream.seekableStream(); |
| 697 | var this_unit_offset: u64 = 0; | 745 | var this_unit_offset: u64 = 0; |
| ... | @@ -755,6 +803,7 @@ pub const DwarfInfo = struct { | ... | @@ -755,6 +803,7 @@ pub const DwarfInfo = struct { |
| 755 | .addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0, | 803 | .addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0, |
| 756 | .rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, | 804 | .rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, |
| 757 | .loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, | 805 | .loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, |
| 806 | .frame_base = die_obj.getAttr(AT.frame_base), | ||
| 758 | }; | 807 | }; |
| 759 | }, | 808 | }, |
| 760 | TAG.subprogram, TAG.inlined_subroutine, TAG.subroutine, TAG.entry_point => { | 809 | TAG.subprogram, TAG.inlined_subroutine, TAG.subroutine, TAG.entry_point => { |
| ... | @@ -764,8 +813,7 @@ pub const DwarfInfo = struct { | ... | @@ -764,8 +813,7 @@ pub const DwarfInfo = struct { |
| 764 | // Prevent endless loops | 813 | // Prevent endless loops |
| 765 | while (depth > 0) : (depth -= 1) { | 814 | while (depth > 0) : (depth -= 1) { |
| 766 | if (this_die_obj.getAttr(AT.name)) |_| { | 815 | if (this_die_obj.getAttr(AT.name)) |_| { |
| 767 | const name = try this_die_obj.getAttrString(di, AT.name, di.debug_str, compile_unit); | 816 | break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit); |
| 768 | break :x try allocator.dupe(u8, name); | ||
| 769 | } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| { | 817 | } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| { |
| 770 | // Follow the DIE it points to and repeat | 818 | // Follow the DIE it points to and repeat |
| 771 | const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); | 819 | const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); |
| ... | @@ -796,34 +844,58 @@ pub const DwarfInfo = struct { | ... | @@ -796,34 +844,58 @@ pub const DwarfInfo = struct { |
| 796 | break :x null; | 844 | break :x null; |
| 797 | }; | 845 | }; |
| 798 | 846 | ||
| 799 | const pc_range = x: { | 847 | var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: { |
| 800 | if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| { | 848 | if (die_obj.getAttr(AT.high_pc)) |high_pc_value| { |
| 801 | if (die_obj.getAttr(AT.high_pc)) |high_pc_value| { | 849 | const pc_end = switch (high_pc_value.*) { |
| 802 | const pc_end = switch (high_pc_value.*) { | 850 | FormValue.Address => |value| value, |
| 803 | FormValue.Address => |value| value, | 851 | FormValue.Const => |value| b: { |
| 804 | FormValue.Const => |value| b: { | 852 | const offset = try value.asUnsignedLe(); |
| 805 | const offset = try value.asUnsignedLe(); | 853 | break :b (low_pc + offset); |
| 806 | break :b (low_pc + offset); | 854 | }, |
| 807 | }, | 855 | else => return badDwarf(), |
| 808 | else => return badDwarf(), | 856 | }; |
| 809 | }; | 857 | |
| 810 | break :x PcRange{ | 858 | try di.func_list.append(allocator, Func{ |
| 859 | .name = fn_name, | ||
| 860 | .pc_range = .{ | ||
| 811 | .start = low_pc, | 861 | .start = low_pc, |
| 812 | .end = pc_end, | 862 | .end = pc_end, |
| 813 | }; | 863 | }, |
| 814 | } else { | 864 | }); |
| 815 | break :x null; | 865 | |
| 816 | } | 866 | break :blk true; |
| 817 | } else |err| { | ||
| 818 | if (err != error.MissingDebugInfo) return err; | ||
| 819 | break :x null; | ||
| 820 | } | 867 | } |
| 868 | |||
| 869 | break :blk false; | ||
| 870 | } else |err| blk: { | ||
| 871 | if (err != error.MissingDebugInfo) return err; | ||
| 872 | break :blk false; | ||
| 821 | }; | 873 | }; |
| 822 | 874 | ||
| 823 | try di.func_list.append(allocator, Func{ | 875 | if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: { |
| 824 | .name = fn_name, | 876 | var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| { |
| 825 | .pc_range = pc_range, | 877 | if (err != error.MissingDebugInfo) return err; |
| 826 | }); | 878 | break :blk; |
| 879 | }; | ||
| 880 | |||
| 881 | while (try iter.next()) |range| { | ||
| 882 | range_added = true; | ||
| 883 | try di.func_list.append(allocator, Func{ | ||
| 884 | .name = fn_name, | ||
| 885 | .pc_range = .{ | ||
| 886 | .start = range.start_addr, | ||
| 887 | .end = range.end_addr, | ||
| 888 | }, | ||
| 889 | }); | ||
| 890 | } | ||
| 891 | } | ||
| 892 | |||
| 893 | if (fn_name != null and !range_added) { | ||
| 894 | try di.func_list.append(allocator, Func{ | ||
| 895 | .name = fn_name, | ||
| 896 | .pc_range = null, | ||
| 897 | }); | ||
| 898 | } | ||
| 827 | }, | 899 | }, |
| 828 | else => {}, | 900 | else => {}, |
| 829 | } | 901 | } |
| ... | @@ -836,7 +908,7 @@ pub const DwarfInfo = struct { | ... | @@ -836,7 +908,7 @@ pub const DwarfInfo = struct { |
| 836 | } | 908 | } |
| 837 | 909 | ||
| 838 | fn scanAllCompileUnits(di: *DwarfInfo, allocator: mem.Allocator) !void { | 910 | fn scanAllCompileUnits(di: *DwarfInfo, allocator: mem.Allocator) !void { |
| 839 | var stream = io.fixedBufferStream(di.debug_info); | 911 | var stream = io.fixedBufferStream(di.section(.debug_info).?); |
| 840 | const in = &stream.reader(); | 912 | const in = &stream.reader(); |
| 841 | const seekable = &stream.seekableStream(); | 913 | const seekable = &stream.seekableStream(); |
| 842 | var this_unit_offset: u64 = 0; | 914 | var this_unit_offset: u64 = 0; |
| ... | @@ -892,6 +964,7 @@ pub const DwarfInfo = struct { | ... | @@ -892,6 +964,7 @@ pub const DwarfInfo = struct { |
| 892 | .addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0, | 964 | .addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0, |
| 893 | .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, | 965 | .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, |
| 894 | .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, | 966 | .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, |
| 967 | .frame_base = compile_unit_die.getAttr(AT.frame_base), | ||
| 895 | }; | 968 | }; |
| 896 | 969 | ||
| 897 | compile_unit.pc_range = x: { | 970 | compile_unit.pc_range = x: { |
| ... | @@ -924,17 +997,18 @@ pub const DwarfInfo = struct { | ... | @@ -924,17 +997,18 @@ pub const DwarfInfo = struct { |
| 924 | } | 997 | } |
| 925 | } | 998 | } |
| 926 | 999 | ||
| 927 | pub fn findCompileUnit(di: *DwarfInfo, target_address: u64) !*const CompileUnit { | 1000 | const DebugRangeIterator = struct { |
| 928 | for (di.compile_unit_list.items) |*compile_unit| { | 1001 | base_address: u64, |
| 929 | if (compile_unit.pc_range) |range| { | 1002 | section_type: DwarfSection, |
| 930 | if (target_address >= range.start and target_address < range.end) return compile_unit; | 1003 | di: *const DwarfInfo, |
| 931 | } | 1004 | compile_unit: *const CompileUnit, |
| 1005 | stream: io.FixedBufferStream([]const u8), | ||
| 932 | 1006 | ||
| 933 | const opt_debug_ranges = if (compile_unit.version >= 5) di.debug_rnglists else di.debug_ranges; | 1007 | pub fn init(ranges_value: *const FormValue, di: *const DwarfInfo, compile_unit: *const CompileUnit) !@This() { |
| 934 | const debug_ranges = opt_debug_ranges orelse continue; | 1008 | const section_type = if (compile_unit.version >= 5) DwarfSection.debug_rnglists else DwarfSection.debug_ranges; |
| 1009 | const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo; | ||
| 935 | 1010 | ||
| 936 | const ranges_val = compile_unit.die.getAttr(AT.ranges) orelse continue; | 1011 | const ranges_offset = switch (ranges_value.*) { |
| 937 | const ranges_offset = switch (ranges_val.*) { | ||
| 938 | .SecOffset => |off| off, | 1012 | .SecOffset => |off| off, |
| 939 | .Const => |c| try c.asUnsignedLe(), | 1013 | .Const => |c| try c.asUnsignedLe(), |
| 940 | .RangeListOffset => |idx| off: { | 1014 | .RangeListOffset => |idx| off: { |
| ... | @@ -954,8 +1028,7 @@ pub const DwarfInfo = struct { | ... | @@ -954,8 +1028,7 @@ pub const DwarfInfo = struct { |
| 954 | }; | 1028 | }; |
| 955 | 1029 | ||
| 956 | var stream = io.fixedBufferStream(debug_ranges); | 1030 | var stream = io.fixedBufferStream(debug_ranges); |
| 957 | const in = &stream.reader(); | 1031 | try stream.seekTo(ranges_offset); |
| 958 | const seekable = &stream.seekableStream(); | ||
| 959 | 1032 | ||
| 960 | // All the addresses in the list are relative to the value | 1033 | // All the addresses in the list are relative to the value |
| 961 | // specified by DW_AT.low_pc or to some other value encoded | 1034 | // specified by DW_AT.low_pc or to some other value encoded |
| ... | @@ -966,86 +1039,122 @@ pub const DwarfInfo = struct { | ... | @@ -966,86 +1039,122 @@ pub const DwarfInfo = struct { |
| 966 | else => return err, | 1039 | else => return err, |
| 967 | }; | 1040 | }; |
| 968 | 1041 | ||
| 969 | try seekable.seekTo(ranges_offset); | 1042 | return .{ |
| 1043 | .base_address = base_address, | ||
| 1044 | .section_type = section_type, | ||
| 1045 | .di = di, | ||
| 1046 | .compile_unit = compile_unit, | ||
| 1047 | .stream = stream, | ||
| 1048 | }; | ||
| 1049 | } | ||
| 970 | 1050 | ||
| 971 | if (compile_unit.version >= 5) { | 1051 | // Returns the next range in the list, or null if the end was reached. |
| 972 | while (true) { | 1052 | pub fn next(self: *@This()) !?struct { start_addr: u64, end_addr: u64 } { |
| 1053 | const in = self.stream.reader(); | ||
| 1054 | switch (self.section_type) { | ||
| 1055 | .debug_rnglists => { | ||
| 973 | const kind = try in.readByte(); | 1056 | const kind = try in.readByte(); |
| 974 | switch (kind) { | 1057 | switch (kind) { |
| 975 | RLE.end_of_list => break, | 1058 | RLE.end_of_list => return null, |
| 976 | RLE.base_addressx => { | 1059 | RLE.base_addressx => { |
| 977 | const index = try leb.readULEB128(usize, in); | 1060 | const index = try leb.readULEB128(usize, in); |
| 978 | base_address = try di.readDebugAddr(compile_unit.*, index); | 1061 | self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index); |
| 1062 | return try self.next(); | ||
| 979 | }, | 1063 | }, |
| 980 | RLE.startx_endx => { | 1064 | RLE.startx_endx => { |
| 981 | const start_index = try leb.readULEB128(usize, in); | 1065 | const start_index = try leb.readULEB128(usize, in); |
| 982 | const start_addr = try di.readDebugAddr(compile_unit.*, start_index); | 1066 | const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); |
| 983 | 1067 | ||
| 984 | const end_index = try leb.readULEB128(usize, in); | 1068 | const end_index = try leb.readULEB128(usize, in); |
| 985 | const end_addr = try di.readDebugAddr(compile_unit.*, end_index); | 1069 | const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index); |
| 986 | 1070 | ||
| 987 | if (target_address >= start_addr and target_address < end_addr) { | 1071 | return .{ |
| 988 | return compile_unit; | 1072 | .start_addr = start_addr, |
| 989 | } | 1073 | .end_addr = end_addr, |
| 1074 | }; | ||
| 990 | }, | 1075 | }, |
| 991 | RLE.startx_length => { | 1076 | RLE.startx_length => { |
| 992 | const start_index = try leb.readULEB128(usize, in); | 1077 | const start_index = try leb.readULEB128(usize, in); |
| 993 | const start_addr = try di.readDebugAddr(compile_unit.*, start_index); | 1078 | const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); |
| 994 | 1079 | ||
| 995 | const len = try leb.readULEB128(usize, in); | 1080 | const len = try leb.readULEB128(usize, in); |
| 996 | const end_addr = start_addr + len; | 1081 | const end_addr = start_addr + len; |
| 997 | 1082 | ||
| 998 | if (target_address >= start_addr and target_address < end_addr) { | 1083 | return .{ |
| 999 | return compile_unit; | 1084 | .start_addr = start_addr, |
| 1000 | } | 1085 | .end_addr = end_addr, |
| 1086 | }; | ||
| 1001 | }, | 1087 | }, |
| 1002 | RLE.offset_pair => { | 1088 | RLE.offset_pair => { |
| 1003 | const start_addr = try leb.readULEB128(usize, in); | 1089 | const start_addr = try leb.readULEB128(usize, in); |
| 1004 | const end_addr = try leb.readULEB128(usize, in); | 1090 | const end_addr = try leb.readULEB128(usize, in); |
| 1091 | |||
| 1005 | // This is the only kind that uses the base address | 1092 | // This is the only kind that uses the base address |
| 1006 | if (target_address >= base_address + start_addr and target_address < base_address + end_addr) { | 1093 | return .{ |
| 1007 | return compile_unit; | 1094 | .start_addr = self.base_address + start_addr, |
| 1008 | } | 1095 | .end_addr = self.base_address + end_addr, |
| 1096 | }; | ||
| 1009 | }, | 1097 | }, |
| 1010 | RLE.base_address => { | 1098 | RLE.base_address => { |
| 1011 | base_address = try in.readInt(usize, di.endian); | 1099 | self.base_address = try in.readInt(usize, self.di.endian); |
| 1100 | return try self.next(); | ||
| 1012 | }, | 1101 | }, |
| 1013 | RLE.start_end => { | 1102 | RLE.start_end => { |
| 1014 | const start_addr = try in.readInt(usize, di.endian); | 1103 | const start_addr = try in.readInt(usize, self.di.endian); |
| 1015 | const end_addr = try in.readInt(usize, di.endian); | 1104 | const end_addr = try in.readInt(usize, self.di.endian); |
| 1016 | if (target_address >= start_addr and target_address < end_addr) { | 1105 | |
| 1017 | return compile_unit; | 1106 | return .{ |
| 1018 | } | 1107 | .start_addr = start_addr, |
| 1108 | .end_addr = end_addr, | ||
| 1109 | }; | ||
| 1019 | }, | 1110 | }, |
| 1020 | RLE.start_length => { | 1111 | RLE.start_length => { |
| 1021 | const start_addr = try in.readInt(usize, di.endian); | 1112 | const start_addr = try in.readInt(usize, self.di.endian); |
| 1022 | const len = try leb.readULEB128(usize, in); | 1113 | const len = try leb.readULEB128(usize, in); |
| 1023 | const end_addr = start_addr + len; | 1114 | const end_addr = start_addr + len; |
| 1024 | if (target_address >= start_addr and target_address < end_addr) { | 1115 | |
| 1025 | return compile_unit; | 1116 | return .{ |
| 1026 | } | 1117 | .start_addr = start_addr, |
| 1118 | .end_addr = end_addr, | ||
| 1119 | }; | ||
| 1027 | }, | 1120 | }, |
| 1028 | else => return badDwarf(), | 1121 | else => return badDwarf(), |
| 1029 | } | 1122 | } |
| 1030 | } | 1123 | }, |
| 1031 | } else { | 1124 | .debug_ranges => { |
| 1032 | while (true) { | 1125 | const start_addr = try in.readInt(usize, self.di.endian); |
| 1033 | const begin_addr = try in.readInt(usize, di.endian); | 1126 | const end_addr = try in.readInt(usize, self.di.endian); |
| 1034 | const end_addr = try in.readInt(usize, di.endian); | 1127 | if (start_addr == 0 and end_addr == 0) return null; |
| 1035 | if (begin_addr == 0 and end_addr == 0) { | 1128 | |
| 1036 | break; | ||
| 1037 | } | ||
| 1038 | // This entry selects a new value for the base address | 1129 | // This entry selects a new value for the base address |
| 1039 | if (begin_addr == math.maxInt(usize)) { | 1130 | if (start_addr == math.maxInt(usize)) { |
| 1040 | base_address = end_addr; | 1131 | self.base_address = end_addr; |
| 1041 | continue; | 1132 | return try self.next(); |
| 1042 | } | 1133 | } |
| 1043 | if (target_address >= base_address + begin_addr and target_address < base_address + end_addr) { | 1134 | |
| 1044 | return compile_unit; | 1135 | return .{ |
| 1045 | } | 1136 | .start_addr = self.base_address + start_addr, |
| 1046 | } | 1137 | .end_addr = self.base_address + end_addr, |
| 1138 | }; | ||
| 1139 | }, | ||
| 1140 | else => unreachable, | ||
| 1047 | } | 1141 | } |
| 1048 | } | 1142 | } |
| 1143 | }; | ||
| 1144 | |||
| 1145 | pub fn findCompileUnit(di: *const DwarfInfo, target_address: u64) !*const CompileUnit { | ||
| 1146 | for (di.compile_unit_list.items) |*compile_unit| { | ||
| 1147 | if (compile_unit.pc_range) |range| { | ||
| 1148 | if (target_address >= range.start and target_address < range.end) return compile_unit; | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue; | ||
| 1152 | var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue; | ||
| 1153 | while (try iter.next()) |range| { | ||
| 1154 | if (target_address >= range.start_addr and target_address < range.end_addr) return compile_unit; | ||
| 1155 | } | ||
| 1156 | } | ||
| 1157 | |||
| 1049 | return missingDwarf(); | 1158 | return missingDwarf(); |
| 1050 | } | 1159 | } |
| 1051 | 1160 | ||
| ... | @@ -1065,7 +1174,7 @@ pub const DwarfInfo = struct { | ... | @@ -1065,7 +1174,7 @@ pub const DwarfInfo = struct { |
| 1065 | } | 1174 | } |
| 1066 | 1175 | ||
| 1067 | fn parseAbbrevTable(di: *DwarfInfo, allocator: mem.Allocator, offset: u64) !AbbrevTable { | 1176 | fn parseAbbrevTable(di: *DwarfInfo, allocator: mem.Allocator, offset: u64) !AbbrevTable { |
| 1068 | var stream = io.fixedBufferStream(di.debug_abbrev); | 1177 | var stream = io.fixedBufferStream(di.section(.debug_abbrev).?); |
| 1069 | const in = &stream.reader(); | 1178 | const in = &stream.reader(); |
| 1070 | const seekable = &stream.seekableStream(); | 1179 | const seekable = &stream.seekableStream(); |
| 1071 | 1180 | ||
| ... | @@ -1146,11 +1255,11 @@ pub const DwarfInfo = struct { | ... | @@ -1146,11 +1255,11 @@ pub const DwarfInfo = struct { |
| 1146 | compile_unit: CompileUnit, | 1255 | compile_unit: CompileUnit, |
| 1147 | target_address: u64, | 1256 | target_address: u64, |
| 1148 | ) !debug.LineInfo { | 1257 | ) !debug.LineInfo { |
| 1149 | var stream = io.fixedBufferStream(di.debug_line); | 1258 | var stream = io.fixedBufferStream(di.section(.debug_line).?); |
| 1150 | const in = &stream.reader(); | 1259 | const in = &stream.reader(); |
| 1151 | const seekable = &stream.seekableStream(); | 1260 | const seekable = &stream.seekableStream(); |
| 1152 | 1261 | ||
| 1153 | const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.debug_line_str, compile_unit); | 1262 | const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); |
| 1154 | const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); | 1263 | const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); |
| 1155 | 1264 | ||
| 1156 | try seekable.seekTo(line_info_offset); | 1265 | try seekable.seekTo(line_info_offset); |
| ... | @@ -1416,15 +1525,15 @@ pub const DwarfInfo = struct { | ... | @@ -1416,15 +1525,15 @@ pub const DwarfInfo = struct { |
| 1416 | } | 1525 | } |
| 1417 | 1526 | ||
| 1418 | fn getString(di: DwarfInfo, offset: u64) ![]const u8 { | 1527 | fn getString(di: DwarfInfo, offset: u64) ![]const u8 { |
| 1419 | return getStringGeneric(di.debug_str, offset); | 1528 | return getStringGeneric(di.section(.debug_str), offset); |
| 1420 | } | 1529 | } |
| 1421 | 1530 | ||
| 1422 | fn getLineString(di: DwarfInfo, offset: u64) ![]const u8 { | 1531 | fn getLineString(di: DwarfInfo, offset: u64) ![]const u8 { |
| 1423 | return getStringGeneric(di.debug_line_str, offset); | 1532 | return getStringGeneric(di.section(.debug_line_str), offset); |
| 1424 | } | 1533 | } |
| 1425 | 1534 | ||
| 1426 | fn readDebugAddr(di: DwarfInfo, compile_unit: CompileUnit, index: u64) !u64 { | 1535 | fn readDebugAddr(di: DwarfInfo, compile_unit: CompileUnit, index: u64) !u64 { |
| 1427 | const debug_addr = di.debug_addr orelse return badDwarf(); | 1536 | const debug_addr = di.section(.debug_addr) orelse return badDwarf(); |
| 1428 | 1537 | ||
| 1429 | // addr_base points to the first item after the header, however we | 1538 | // addr_base points to the first item after the header, however we |
| 1430 | // need to read the header to know the size of each item. Empirically, | 1539 | // need to read the header to know the size of each item. Empirically, |
| ... | @@ -1448,10 +1557,689 @@ pub const DwarfInfo = struct { | ... | @@ -1448,10 +1557,689 @@ pub const DwarfInfo = struct { |
| 1448 | else => badDwarf(), | 1557 | else => badDwarf(), |
| 1449 | }; | 1558 | }; |
| 1450 | } | 1559 | } |
| 1560 | |||
| 1561 | /// If .eh_frame_hdr is present, then only the header needs to be parsed. | ||
| 1562 | /// | ||
| 1563 | /// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list | ||
| 1564 | /// of FDEs is built for binary searching during unwinding. | ||
| 1565 | pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, base_address: usize) !void { | ||
| 1566 | if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { | ||
| 1567 | var stream = io.fixedBufferStream(eh_frame_hdr); | ||
| 1568 | const reader = stream.reader(); | ||
| 1569 | |||
| 1570 | const version = try reader.readByte(); | ||
| 1571 | if (version != 1) break :blk; | ||
| 1572 | |||
| 1573 | const eh_frame_ptr_enc = try reader.readByte(); | ||
| 1574 | if (eh_frame_ptr_enc == EH.PE.omit) break :blk; | ||
| 1575 | const fde_count_enc = try reader.readByte(); | ||
| 1576 | if (fde_count_enc == EH.PE.omit) break :blk; | ||
| 1577 | const table_enc = try reader.readByte(); | ||
| 1578 | if (table_enc == EH.PE.omit) break :blk; | ||
| 1579 | |||
| 1580 | const eh_frame_ptr = std.math.cast(usize, try readEhPointer(reader, eh_frame_ptr_enc, @sizeOf(usize), .{ | ||
| 1581 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[stream.pos]), | ||
| 1582 | .follow_indirect = true, | ||
| 1583 | }, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf(); | ||
| 1584 | |||
| 1585 | const fde_count = std.math.cast(usize, try readEhPointer(reader, fde_count_enc, @sizeOf(usize), .{ | ||
| 1586 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[stream.pos]), | ||
| 1587 | .follow_indirect = true, | ||
| 1588 | }, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf(); | ||
| 1589 | |||
| 1590 | const entry_size = try ExceptionFrameHeader.entrySize(table_enc); | ||
| 1591 | const entries_len = fde_count * entry_size; | ||
| 1592 | if (entries_len > eh_frame_hdr.len - stream.pos) return badDwarf(); | ||
| 1593 | |||
| 1594 | di.eh_frame_hdr = .{ | ||
| 1595 | .eh_frame_ptr = eh_frame_ptr, | ||
| 1596 | .table_enc = table_enc, | ||
| 1597 | .fde_count = fde_count, | ||
| 1598 | .entries = eh_frame_hdr[stream.pos..][0..entries_len], | ||
| 1599 | }; | ||
| 1600 | |||
| 1601 | // No need to scan .eh_frame, we have a binary search table already | ||
| 1602 | return; | ||
| 1603 | } | ||
| 1604 | |||
| 1605 | const frame_sections = [2]DwarfSection{ .eh_frame, .debug_frame }; | ||
| 1606 | for (frame_sections) |frame_section| { | ||
| 1607 | if (di.section(frame_section)) |section_data| { | ||
| 1608 | var stream = io.fixedBufferStream(section_data); | ||
| 1609 | while (stream.pos < stream.buffer.len) { | ||
| 1610 | const entry_header = try EntryHeader.read(&stream, frame_section, di.endian); | ||
| 1611 | switch (entry_header.type) { | ||
| 1612 | .cie => { | ||
| 1613 | const cie = try CommonInformationEntry.parse( | ||
| 1614 | entry_header.entry_bytes, | ||
| 1615 | di.sectionVirtualOffset(frame_section, base_address).?, | ||
| 1616 | true, | ||
| 1617 | entry_header.is_64, | ||
| 1618 | frame_section, | ||
| 1619 | entry_header.length_offset, | ||
| 1620 | @sizeOf(usize), | ||
| 1621 | di.endian, | ||
| 1622 | ); | ||
| 1623 | try di.cie_map.put(allocator, entry_header.length_offset, cie); | ||
| 1624 | }, | ||
| 1625 | .fde => |cie_offset| { | ||
| 1626 | const cie = di.cie_map.get(cie_offset) orelse return badDwarf(); | ||
| 1627 | const fde = try FrameDescriptionEntry.parse( | ||
| 1628 | entry_header.entry_bytes, | ||
| 1629 | di.sectionVirtualOffset(frame_section, base_address).?, | ||
| 1630 | true, | ||
| 1631 | cie, | ||
| 1632 | @sizeOf(usize), | ||
| 1633 | di.endian, | ||
| 1634 | ); | ||
| 1635 | try di.fde_list.append(allocator, fde); | ||
| 1636 | }, | ||
| 1637 | .terminator => break, | ||
| 1638 | } | ||
| 1639 | } | ||
| 1640 | |||
| 1641 | std.mem.sort(FrameDescriptionEntry, di.fde_list.items, {}, struct { | ||
| 1642 | fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool { | ||
| 1643 | _ = ctx; | ||
| 1644 | return a.pc_begin < b.pc_begin; | ||
| 1645 | } | ||
| 1646 | }.lessThan); | ||
| 1647 | } | ||
| 1648 | } | ||
| 1649 | } | ||
| 1650 | |||
| 1651 | /// Unwind a stack frame using DWARF unwinding info, updating the register context. | ||
| 1652 | /// | ||
| 1653 | /// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE. | ||
| 1654 | /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. | ||
| 1655 | /// | ||
| 1656 | /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info | ||
| 1657 | /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. | ||
| 1658 | pub fn unwindFrame(di: *const DwarfInfo, context: *UnwindContext, explicit_fde_offset: ?usize) !usize { | ||
| 1659 | if (!comptime abi.isSupportedArch(builtin.target.cpu.arch)) return error.UnsupportedCpuArchitecture; | ||
| 1660 | if (context.pc == 0) return 0; | ||
| 1661 | |||
| 1662 | // Find the FDE and CIE | ||
| 1663 | var cie: CommonInformationEntry = undefined; | ||
| 1664 | var fde: FrameDescriptionEntry = undefined; | ||
| 1665 | |||
| 1666 | if (explicit_fde_offset) |fde_offset| { | ||
| 1667 | const dwarf_section: DwarfSection = .eh_frame; | ||
| 1668 | const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; | ||
| 1669 | if (fde_offset >= frame_section.len) return error.MissingFDE; | ||
| 1670 | |||
| 1671 | var stream = io.fixedBufferStream(frame_section); | ||
| 1672 | try stream.seekTo(fde_offset); | ||
| 1673 | |||
| 1674 | const fde_entry_header = try EntryHeader.read(&stream, dwarf_section, di.endian); | ||
| 1675 | if (fde_entry_header.type != .fde) return error.MissingFDE; | ||
| 1676 | |||
| 1677 | const cie_offset = fde_entry_header.type.fde; | ||
| 1678 | try stream.seekTo(cie_offset); | ||
| 1679 | |||
| 1680 | const cie_entry_header = try EntryHeader.read(&stream, dwarf_section, builtin.cpu.arch.endian()); | ||
| 1681 | if (cie_entry_header.type != .cie) return badDwarf(); | ||
| 1682 | |||
| 1683 | cie = try CommonInformationEntry.parse( | ||
| 1684 | cie_entry_header.entry_bytes, | ||
| 1685 | 0, | ||
| 1686 | true, | ||
| 1687 | cie_entry_header.is_64, | ||
| 1688 | dwarf_section, | ||
| 1689 | cie_entry_header.length_offset, | ||
| 1690 | @sizeOf(usize), | ||
| 1691 | builtin.cpu.arch.endian(), | ||
| 1692 | ); | ||
| 1693 | |||
| 1694 | fde = try FrameDescriptionEntry.parse( | ||
| 1695 | fde_entry_header.entry_bytes, | ||
| 1696 | 0, | ||
| 1697 | true, | ||
| 1698 | cie, | ||
| 1699 | @sizeOf(usize), | ||
| 1700 | builtin.cpu.arch.endian(), | ||
| 1701 | ); | ||
| 1702 | } else if (di.eh_frame_hdr) |header| { | ||
| 1703 | const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; | ||
| 1704 | try header.findEntry( | ||
| 1705 | context.isValidMemory, | ||
| 1706 | eh_frame_len, | ||
| 1707 | @intFromPtr(di.section(.eh_frame_hdr).?.ptr), | ||
| 1708 | context.pc, | ||
| 1709 | &cie, | ||
| 1710 | &fde, | ||
| 1711 | ); | ||
| 1712 | } else { | ||
| 1713 | const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { | ||
| 1714 | pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order { | ||
| 1715 | if (pc < mid_item.pc_begin) return .lt; | ||
| 1716 | |||
| 1717 | const range_end = mid_item.pc_begin + mid_item.pc_range; | ||
| 1718 | if (pc < range_end) return .eq; | ||
| 1719 | |||
| 1720 | return .gt; | ||
| 1721 | } | ||
| 1722 | }.compareFn); | ||
| 1723 | |||
| 1724 | fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; | ||
| 1725 | cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; | ||
| 1726 | } | ||
| 1727 | |||
| 1728 | var expression_context = .{ | ||
| 1729 | .is_64 = cie.is_64, | ||
| 1730 | .isValidMemory = context.isValidMemory, | ||
| 1731 | .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, | ||
| 1732 | .thread_context = context.thread_context, | ||
| 1733 | .reg_context = context.reg_context, | ||
| 1734 | .cfa = context.cfa, | ||
| 1735 | }; | ||
| 1736 | |||
| 1737 | context.vm.reset(); | ||
| 1738 | context.reg_context.eh_frame = cie.version != 4; | ||
| 1739 | context.reg_context.is_macho = di.is_macho; | ||
| 1740 | |||
| 1741 | const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); | ||
| 1742 | context.cfa = switch (row.cfa.rule) { | ||
| 1743 | .val_offset => |offset| blk: { | ||
| 1744 | const register = row.cfa.register orelse return error.InvalidCFARule; | ||
| 1745 | const value = mem.readIntSliceNative(usize, try abi.regBytes(context.thread_context, register, context.reg_context)); | ||
| 1746 | break :blk try call_frame.applyOffset(value, offset); | ||
| 1747 | }, | ||
| 1748 | .expression => |expression| blk: { | ||
| 1749 | context.stack_machine.reset(); | ||
| 1750 | const value = try context.stack_machine.run( | ||
| 1751 | expression, | ||
| 1752 | context.allocator, | ||
| 1753 | expression_context, | ||
| 1754 | context.cfa, | ||
| 1755 | ); | ||
| 1756 | |||
| 1757 | if (value) |v| { | ||
| 1758 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 1759 | break :blk v.generic; | ||
| 1760 | } else return error.NoExpressionValue; | ||
| 1761 | }, | ||
| 1762 | else => return error.InvalidCFARule, | ||
| 1763 | }; | ||
| 1764 | |||
| 1765 | if (!context.isValidMemory(context.cfa.?)) return error.InvalidCFA; | ||
| 1766 | expression_context.cfa = context.cfa; | ||
| 1767 | |||
| 1768 | // Buffering the modifications is done because copying the thread context is not portable, | ||
| 1769 | // some implementations (ie. darwin) use internal pointers to the mcontext. | ||
| 1770 | var arena = std.heap.ArenaAllocator.init(context.allocator); | ||
| 1771 | defer arena.deinit(); | ||
| 1772 | const update_allocator = arena.allocator(); | ||
| 1773 | |||
| 1774 | const RegisterUpdate = struct { | ||
| 1775 | // Backed by thread_context | ||
| 1776 | dest: []u8, | ||
| 1777 | // Backed by arena | ||
| 1778 | src: []const u8, | ||
| 1779 | prev: ?*@This(), | ||
| 1780 | }; | ||
| 1781 | |||
| 1782 | var update_tail: ?*RegisterUpdate = null; | ||
| 1783 | var has_return_address = true; | ||
| 1784 | for (context.vm.rowColumns(row)) |column| { | ||
| 1785 | if (column.register) |register| { | ||
| 1786 | if (register == cie.return_address_register) { | ||
| 1787 | has_return_address = column.rule != .undefined; | ||
| 1788 | } | ||
| 1789 | |||
| 1790 | const dest = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 1791 | const src = try update_allocator.alloc(u8, dest.len); | ||
| 1792 | |||
| 1793 | const prev = update_tail; | ||
| 1794 | update_tail = try update_allocator.create(RegisterUpdate); | ||
| 1795 | update_tail.?.* = .{ | ||
| 1796 | .dest = dest, | ||
| 1797 | .src = src, | ||
| 1798 | .prev = prev, | ||
| 1799 | }; | ||
| 1800 | |||
| 1801 | try column.resolveValue( | ||
| 1802 | context, | ||
| 1803 | expression_context, | ||
| 1804 | src, | ||
| 1805 | ); | ||
| 1806 | } | ||
| 1807 | } | ||
| 1808 | |||
| 1809 | // On all implemented architectures, the CFA is defined as being the previous frame's SP | ||
| 1810 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; | ||
| 1811 | |||
| 1812 | while (update_tail) |tail| { | ||
| 1813 | @memcpy(tail.dest, tail.src); | ||
| 1814 | update_tail = tail.prev; | ||
| 1815 | } | ||
| 1816 | |||
| 1817 | if (has_return_address) { | ||
| 1818 | context.pc = abi.stripInstructionPtrAuthCode(mem.readIntSliceNative(usize, try abi.regBytes( | ||
| 1819 | context.thread_context, | ||
| 1820 | cie.return_address_register, | ||
| 1821 | context.reg_context, | ||
| 1822 | ))); | ||
| 1823 | } else { | ||
| 1824 | context.pc = 0; | ||
| 1825 | } | ||
| 1826 | |||
| 1827 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc; | ||
| 1828 | |||
| 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. | ||
| 1837 | const return_address = context.pc; | ||
| 1838 | if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; | ||
| 1839 | |||
| 1840 | return return_address; | ||
| 1841 | } | ||
| 1842 | }; | ||
| 1843 | |||
| 1844 | /// Returns the DWARF register number for an x86_64 register number found in compact unwind info | ||
| 1845 | fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { | ||
| 1846 | return switch (unwind_reg_number) { | ||
| 1847 | 1 => 3, // RBX | ||
| 1848 | 2 => 12, // R12 | ||
| 1849 | 3 => 13, // R13 | ||
| 1850 | 4 => 14, // R14 | ||
| 1851 | 5 => 15, // R15 | ||
| 1852 | 6 => 6, // RBP | ||
| 1853 | else => error.InvalidUnwindRegisterNumber, | ||
| 1854 | }; | ||
| 1855 | } | ||
| 1856 | |||
| 1857 | const macho = std.macho; | ||
| 1858 | |||
| 1859 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). | ||
| 1860 | /// If the compact encoding can't encode a way to unwind a frame, it will | ||
| 1861 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. | ||
| 1862 | pub fn unwindFrameMachO(context: *UnwindContext, unwind_info: []const u8, eh_frame: ?[]const u8, module_base_address: usize) !usize { | ||
| 1863 | const header = mem.bytesAsValue( | ||
| 1864 | macho.unwind_info_section_header, | ||
| 1865 | unwind_info[0..@sizeOf(macho.unwind_info_section_header)], | ||
| 1866 | ); | ||
| 1867 | const indices = mem.bytesAsSlice( | ||
| 1868 | macho.unwind_info_section_header_index_entry, | ||
| 1869 | unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], | ||
| 1870 | ); | ||
| 1871 | if (indices.len == 0) return error.MissingUnwindInfo; | ||
| 1872 | |||
| 1873 | const mapped_pc = context.pc - module_base_address; | ||
| 1874 | const second_level_index = blk: { | ||
| 1875 | var left: usize = 0; | ||
| 1876 | var len: usize = indices.len; | ||
| 1877 | |||
| 1878 | while (len > 1) { | ||
| 1879 | const mid = left + len / 2; | ||
| 1880 | const offset = indices[mid].functionOffset; | ||
| 1881 | if (mapped_pc < offset) { | ||
| 1882 | len /= 2; | ||
| 1883 | } else { | ||
| 1884 | left = mid; | ||
| 1885 | if (mapped_pc == offset) break; | ||
| 1886 | len -= len / 2; | ||
| 1887 | } | ||
| 1888 | } | ||
| 1889 | |||
| 1890 | // Last index is a sentinel containing the highest address as its functionOffset | ||
| 1891 | if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; | ||
| 1892 | break :blk &indices[left]; | ||
| 1893 | }; | ||
| 1894 | |||
| 1895 | const common_encodings = mem.bytesAsSlice( | ||
| 1896 | macho.compact_unwind_encoding_t, | ||
| 1897 | unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 1898 | ); | ||
| 1899 | |||
| 1900 | const start_offset = second_level_index.secondLevelPagesSectionOffset; | ||
| 1901 | const kind = mem.bytesAsValue( | ||
| 1902 | macho.UNWIND_SECOND_LEVEL, | ||
| 1903 | unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], | ||
| 1904 | ); | ||
| 1905 | |||
| 1906 | const entry: struct { | ||
| 1907 | function_offset: usize, | ||
| 1908 | raw_encoding: u32, | ||
| 1909 | } = switch (kind.*) { | ||
| 1910 | .REGULAR => blk: { | ||
| 1911 | const page_header = mem.bytesAsValue( | ||
| 1912 | macho.unwind_info_regular_second_level_page_header, | ||
| 1913 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], | ||
| 1914 | ); | ||
| 1915 | |||
| 1916 | const entries = mem.bytesAsSlice( | ||
| 1917 | macho.unwind_info_regular_second_level_entry, | ||
| 1918 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], | ||
| 1919 | ); | ||
| 1920 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 1921 | |||
| 1922 | var left: usize = 0; | ||
| 1923 | var len: usize = entries.len; | ||
| 1924 | while (len > 1) { | ||
| 1925 | const mid = left + len / 2; | ||
| 1926 | const offset = entries[mid].functionOffset; | ||
| 1927 | if (mapped_pc < offset) { | ||
| 1928 | len /= 2; | ||
| 1929 | } else { | ||
| 1930 | left = mid; | ||
| 1931 | if (mapped_pc == offset) break; | ||
| 1932 | len -= len / 2; | ||
| 1933 | } | ||
| 1934 | } | ||
| 1935 | |||
| 1936 | break :blk .{ | ||
| 1937 | .function_offset = entries[left].functionOffset, | ||
| 1938 | .raw_encoding = entries[left].encoding, | ||
| 1939 | }; | ||
| 1940 | }, | ||
| 1941 | .COMPRESSED => blk: { | ||
| 1942 | const page_header = mem.bytesAsValue( | ||
| 1943 | macho.unwind_info_compressed_second_level_page_header, | ||
| 1944 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], | ||
| 1945 | ); | ||
| 1946 | |||
| 1947 | const entries = mem.bytesAsSlice( | ||
| 1948 | macho.UnwindInfoCompressedEntry, | ||
| 1949 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], | ||
| 1950 | ); | ||
| 1951 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 1952 | |||
| 1953 | var left: usize = 0; | ||
| 1954 | var len: usize = entries.len; | ||
| 1955 | while (len > 1) { | ||
| 1956 | const mid = left + len / 2; | ||
| 1957 | const offset = second_level_index.functionOffset + entries[mid].funcOffset; | ||
| 1958 | if (mapped_pc < offset) { | ||
| 1959 | len /= 2; | ||
| 1960 | } else { | ||
| 1961 | left = mid; | ||
| 1962 | if (mapped_pc == offset) break; | ||
| 1963 | len -= len / 2; | ||
| 1964 | } | ||
| 1965 | } | ||
| 1966 | |||
| 1967 | const entry = entries[left]; | ||
| 1968 | const function_offset = second_level_index.functionOffset + entry.funcOffset; | ||
| 1969 | if (entry.encodingIndex < header.commonEncodingsArrayCount) { | ||
| 1970 | if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; | ||
| 1971 | break :blk .{ | ||
| 1972 | .function_offset = function_offset, | ||
| 1973 | .raw_encoding = common_encodings[entry.encodingIndex], | ||
| 1974 | }; | ||
| 1975 | } else { | ||
| 1976 | const local_index = try std.math.sub( | ||
| 1977 | u8, | ||
| 1978 | entry.encodingIndex, | ||
| 1979 | std.math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, | ||
| 1980 | ); | ||
| 1981 | const local_encodings = mem.bytesAsSlice( | ||
| 1982 | macho.compact_unwind_encoding_t, | ||
| 1983 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 1984 | ); | ||
| 1985 | if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; | ||
| 1986 | break :blk .{ | ||
| 1987 | .function_offset = function_offset, | ||
| 1988 | .raw_encoding = local_encodings[local_index], | ||
| 1989 | }; | ||
| 1990 | } | ||
| 1991 | }, | ||
| 1992 | else => return error.InvalidUnwindInfo, | ||
| 1993 | }; | ||
| 1994 | |||
| 1995 | if (entry.raw_encoding == 0) return error.NoUnwindInfo; | ||
| 1996 | const reg_context = abi.RegisterContext{ | ||
| 1997 | .eh_frame = false, | ||
| 1998 | .is_macho = true, | ||
| 1999 | }; | ||
| 2000 | |||
| 2001 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | ||
| 2002 | const new_ip = switch (builtin.cpu.arch) { | ||
| 2003 | .x86_64 => switch (encoding.mode.x86_64) { | ||
| 2004 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 2005 | .RBP_FRAME => blk: { | ||
| 2006 | const regs: [5]u3 = .{ | ||
| 2007 | encoding.value.x86_64.frame.reg0, | ||
| 2008 | encoding.value.x86_64.frame.reg1, | ||
| 2009 | encoding.value.x86_64.frame.reg2, | ||
| 2010 | encoding.value.x86_64.frame.reg3, | ||
| 2011 | encoding.value.x86_64.frame.reg4, | ||
| 2012 | }; | ||
| 2013 | |||
| 2014 | const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); | ||
| 2015 | var max_reg: usize = 0; | ||
| 2016 | inline for (regs, 0..) |reg, i| { | ||
| 2017 | if (reg > 0) max_reg = i; | ||
| 2018 | } | ||
| 2019 | |||
| 2020 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | ||
| 2021 | const new_sp = fp + 2 * @sizeOf(usize); | ||
| 2022 | |||
| 2023 | // Verify the stack range we're about to read register values from | ||
| 2024 | if (!context.isValidMemory(new_sp) or !context.isValidMemory(fp - frame_offset + max_reg * @sizeOf(usize))) return error.InvalidUnwindInfo; | ||
| 2025 | |||
| 2026 | const ip_ptr = fp + @sizeOf(usize); | ||
| 2027 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2028 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 2029 | |||
| 2030 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 2031 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2032 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2033 | |||
| 2034 | for (regs, 0..) |reg, i| { | ||
| 2035 | if (reg == 0) continue; | ||
| 2036 | const addr = fp - frame_offset + i * @sizeOf(usize); | ||
| 2037 | const reg_number = try compactUnwindToDwarfRegNumber(reg); | ||
| 2038 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; | ||
| 2039 | } | ||
| 2040 | |||
| 2041 | break :blk new_ip; | ||
| 2042 | }, | ||
| 2043 | .STACK_IMMD, | ||
| 2044 | .STACK_IND, | ||
| 2045 | => blk: { | ||
| 2046 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | ||
| 2047 | const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) | ||
| 2048 | @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) | ||
| 2049 | else stack_size: { | ||
| 2050 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | ||
| 2051 | const sub_offset_addr = | ||
| 2052 | module_base_address + | ||
| 2053 | entry.function_offset + | ||
| 2054 | encoding.value.x86_64.frameless.stack.indirect.sub_offset; | ||
| 2055 | if (!context.isValidMemory(sub_offset_addr)) return error.InvalidUnwindInfo; | ||
| 2056 | |||
| 2057 | // `sub_offset_addr` points to the offset of the literal within the instruction | ||
| 2058 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | ||
| 2059 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); | ||
| 2060 | }; | ||
| 2061 | |||
| 2062 | // Decode the Lehmer-coded sequence of registers. | ||
| 2063 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | ||
| 2064 | |||
| 2065 | // Decode the variable-based permutation number into its digits. Each digit represents | ||
| 2066 | // an index into the list of register numbers that weren't yet used in the sequence at | ||
| 2067 | // the time the digit was added. | ||
| 2068 | const reg_count = encoding.value.x86_64.frameless.stack_reg_count; | ||
| 2069 | const ip_ptr = if (reg_count > 0) reg_blk: { | ||
| 2070 | var digits: [6]u3 = undefined; | ||
| 2071 | var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; | ||
| 2072 | var base: usize = 2; | ||
| 2073 | for (0..reg_count) |i| { | ||
| 2074 | const div = accumulator / base; | ||
| 2075 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | ||
| 2076 | accumulator = div; | ||
| 2077 | base += 1; | ||
| 2078 | } | ||
| 2079 | |||
| 2080 | const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; | ||
| 2081 | var registers: [reg_numbers.len]u3 = undefined; | ||
| 2082 | var used_indices = [_]bool{false} ** reg_numbers.len; | ||
| 2083 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | ||
| 2084 | var unused_count: u8 = 0; | ||
| 2085 | const unused_index = for (used_indices, 0..) |used, index| { | ||
| 2086 | if (!used) { | ||
| 2087 | if (target_unused_index == unused_count) break index; | ||
| 2088 | unused_count += 1; | ||
| 2089 | } | ||
| 2090 | } else unreachable; | ||
| 2091 | |||
| 2092 | registers[i] = reg_numbers[unused_index]; | ||
| 2093 | used_indices[unused_index] = true; | ||
| 2094 | } | ||
| 2095 | |||
| 2096 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | ||
| 2097 | if (!context.isValidMemory(reg_addr)) return error.InvalidUnwindInfo; | ||
| 2098 | for (0..reg_count) |i| { | ||
| 2099 | const reg_number = try compactUnwindToDwarfRegNumber(registers[i]); | ||
| 2100 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2101 | reg_addr += @sizeOf(usize); | ||
| 2102 | } | ||
| 2103 | |||
| 2104 | break :reg_blk reg_addr; | ||
| 2105 | } else sp + stack_size - @sizeOf(usize); | ||
| 2106 | |||
| 2107 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2108 | const new_sp = ip_ptr + @sizeOf(usize); | ||
| 2109 | if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo; | ||
| 2110 | |||
| 2111 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2112 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2113 | |||
| 2114 | break :blk new_ip; | ||
| 2115 | }, | ||
| 2116 | .DWARF => { | ||
| 2117 | return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); | ||
| 2118 | }, | ||
| 2119 | }, | ||
| 2120 | .aarch64 => switch (encoding.mode.arm64) { | ||
| 2121 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 2122 | .FRAMELESS => blk: { | ||
| 2123 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | ||
| 2124 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | ||
| 2125 | const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*; | ||
| 2126 | if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo; | ||
| 2127 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2128 | break :blk new_ip; | ||
| 2129 | }, | ||
| 2130 | .DWARF => { | ||
| 2131 | return unwindFrameMachODwarf(context, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); | ||
| 2132 | }, | ||
| 2133 | .FRAME => blk: { | ||
| 2134 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | ||
| 2135 | const new_sp = fp + 16; | ||
| 2136 | const ip_ptr = fp + @sizeOf(usize); | ||
| 2137 | |||
| 2138 | const num_restored_pairs: usize = | ||
| 2139 | @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + | ||
| 2140 | @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); | ||
| 2141 | const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); | ||
| 2142 | |||
| 2143 | if (!context.isValidMemory(new_sp) or !context.isValidMemory(min_reg_addr)) return error.InvalidUnwindInfo; | ||
| 2144 | |||
| 2145 | var reg_addr = fp - @sizeOf(usize); | ||
| 2146 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 2147 | if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { | ||
| 2148 | (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2149 | reg_addr += @sizeOf(usize); | ||
| 2150 | (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2151 | reg_addr += @sizeOf(usize); | ||
| 2152 | } | ||
| 2153 | } | ||
| 2154 | |||
| 2155 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 2156 | if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { | ||
| 2157 | // Only the lower half of the 128-bit V registers are restored during unwinding | ||
| 2158 | @memcpy( | ||
| 2159 | try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context), | ||
| 2160 | mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 2161 | ); | ||
| 2162 | reg_addr += @sizeOf(usize); | ||
| 2163 | @memcpy( | ||
| 2164 | try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context), | ||
| 2165 | mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 2166 | ); | ||
| 2167 | reg_addr += @sizeOf(usize); | ||
| 2168 | } | ||
| 2169 | } | ||
| 2170 | |||
| 2171 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2172 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 2173 | |||
| 2174 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 2175 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2176 | |||
| 2177 | break :blk new_ip; | ||
| 2178 | }, | ||
| 2179 | }, | ||
| 2180 | else => return error.UnimplementedArch, | ||
| 2181 | }; | ||
| 2182 | |||
| 2183 | context.pc = abi.stripInstructionPtrAuthCode(new_ip); | ||
| 2184 | if (context.pc > 0) context.pc -= 1; | ||
| 2185 | return new_ip; | ||
| 2186 | } | ||
| 2187 | |||
| 2188 | fn unwindFrameMachODwarf(context: *UnwindContext, eh_frame: []const u8, fde_offset: usize) !usize { | ||
| 2189 | var di = DwarfInfo{ | ||
| 2190 | .endian = builtin.cpu.arch.endian(), | ||
| 2191 | .is_macho = true, | ||
| 2192 | }; | ||
| 2193 | defer di.deinit(context.allocator); | ||
| 2194 | |||
| 2195 | di.sections[@intFromEnum(DwarfSection.eh_frame)] = .{ | ||
| 2196 | .data = eh_frame, | ||
| 2197 | .owned = false, | ||
| 2198 | }; | ||
| 2199 | |||
| 2200 | return di.unwindFrame(context, fde_offset); | ||
| 2201 | } | ||
| 2202 | |||
| 2203 | pub const UnwindContext = struct { | ||
| 2204 | allocator: mem.Allocator, | ||
| 2205 | cfa: ?usize, | ||
| 2206 | pc: usize, | ||
| 2207 | thread_context: *debug.ThreadContext, | ||
| 2208 | reg_context: abi.RegisterContext, | ||
| 2209 | isValidMemory: *const fn (address: usize) bool, | ||
| 2210 | vm: call_frame.VirtualMachine = .{}, | ||
| 2211 | stack_machine: expressions.StackMachine(.{ .call_frame_context = true }) = .{}, | ||
| 2212 | |||
| 2213 | pub fn init(allocator: mem.Allocator, thread_context: *const debug.ThreadContext, isValidMemory: *const fn (address: usize) bool) !UnwindContext { | ||
| 2214 | const pc = abi.stripInstructionPtrAuthCode((try abi.regValueNative(usize, thread_context, abi.ipRegNum(), null)).*); | ||
| 2215 | |||
| 2216 | const context_copy = try allocator.create(debug.ThreadContext); | ||
| 2217 | debug.copyContext(thread_context, context_copy); | ||
| 2218 | |||
| 2219 | return .{ | ||
| 2220 | .allocator = allocator, | ||
| 2221 | .cfa = null, | ||
| 2222 | .pc = pc, | ||
| 2223 | .thread_context = context_copy, | ||
| 2224 | .reg_context = undefined, | ||
| 2225 | .isValidMemory = isValidMemory, | ||
| 2226 | }; | ||
| 2227 | } | ||
| 2228 | |||
| 2229 | pub fn deinit(self: *UnwindContext) void { | ||
| 2230 | self.vm.deinit(self.allocator); | ||
| 2231 | self.stack_machine.deinit(self.allocator); | ||
| 2232 | self.allocator.destroy(self.thread_context); | ||
| 2233 | } | ||
| 2234 | |||
| 2235 | pub fn getFp(self: *const UnwindContext) !usize { | ||
| 2236 | return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*; | ||
| 2237 | } | ||
| 1451 | }; | 2238 | }; |
| 1452 | 2239 | ||
| 1453 | /// Initialize DWARF info. The caller has the responsibility to initialize most | 2240 | /// Initialize DWARF info. The caller has the responsibility to initialize most |
| 1454 | /// the DwarfInfo fields before calling. | 2241 | /// the DwarfInfo fields before calling. `binary_mem` is the raw bytes of the |
| 2242 | /// main binary file (not the secondary debug info file). | ||
| 1455 | pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void { | 2243 | pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void { |
| 1456 | try di.scanAllFunctions(allocator); | 2244 | try di.scanAllFunctions(allocator); |
| 1457 | try di.scanAllCompileUnits(allocator); | 2245 | try di.scanAllCompileUnits(allocator); |
| ... | @@ -1477,3 +2265,561 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { | ... | @@ -1477,3 +2265,561 @@ fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { |
| 1477 | const last = mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf(); | 2265 | const last = mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf(); |
| 1478 | return str[casted_offset..last :0]; | 2266 | return str[casted_offset..last :0]; |
| 1479 | } | 2267 | } |
| 2268 | |||
| 2269 | const EhPointerContext = struct { | ||
| 2270 | // The address of the pointer field itself | ||
| 2271 | pc_rel_base: u64, | ||
| 2272 | |||
| 2273 | // Whether or not to follow indirect pointers. This should only be | ||
| 2274 | // used when decoding pointers at runtime using the current process's | ||
| 2275 | // debug info | ||
| 2276 | follow_indirect: bool, | ||
| 2277 | |||
| 2278 | // These relative addressing modes are only used in specific cases, and | ||
| 2279 | // might not be available / required in all parsing contexts | ||
| 2280 | data_rel_base: ?u64 = null, | ||
| 2281 | text_rel_base: ?u64 = null, | ||
| 2282 | function_rel_base: ?u64 = null, | ||
| 2283 | }; | ||
| 2284 | |||
| 2285 | fn readEhPointer(reader: anytype, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext, endian: std.builtin.Endian) !?u64 { | ||
| 2286 | if (enc == EH.PE.omit) return null; | ||
| 2287 | |||
| 2288 | const value: union(enum) { | ||
| 2289 | signed: i64, | ||
| 2290 | unsigned: u64, | ||
| 2291 | } = switch (enc & EH.PE.type_mask) { | ||
| 2292 | EH.PE.absptr => .{ | ||
| 2293 | .unsigned = switch (addr_size_bytes) { | ||
| 2294 | 2 => try reader.readInt(u16, endian), | ||
| 2295 | 4 => try reader.readInt(u32, endian), | ||
| 2296 | 8 => try reader.readInt(u64, endian), | ||
| 2297 | else => return error.InvalidAddrSize, | ||
| 2298 | }, | ||
| 2299 | }, | ||
| 2300 | EH.PE.uleb128 => .{ .unsigned = try leb.readULEB128(u64, reader) }, | ||
| 2301 | EH.PE.udata2 => .{ .unsigned = try reader.readInt(u16, endian) }, | ||
| 2302 | EH.PE.udata4 => .{ .unsigned = try reader.readInt(u32, endian) }, | ||
| 2303 | EH.PE.udata8 => .{ .unsigned = try reader.readInt(u64, endian) }, | ||
| 2304 | EH.PE.sleb128 => .{ .signed = try leb.readILEB128(i64, reader) }, | ||
| 2305 | EH.PE.sdata2 => .{ .signed = try reader.readInt(i16, endian) }, | ||
| 2306 | EH.PE.sdata4 => .{ .signed = try reader.readInt(i32, endian) }, | ||
| 2307 | EH.PE.sdata8 => .{ .signed = try reader.readInt(i64, endian) }, | ||
| 2308 | else => return badDwarf(), | ||
| 2309 | }; | ||
| 2310 | |||
| 2311 | var base = switch (enc & EH.PE.rel_mask) { | ||
| 2312 | EH.PE.pcrel => ctx.pc_rel_base, | ||
| 2313 | EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified, | ||
| 2314 | EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified, | ||
| 2315 | EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified, | ||
| 2316 | else => null, | ||
| 2317 | }; | ||
| 2318 | |||
| 2319 | const ptr: u64 = if (base) |b| switch (value) { | ||
| 2320 | .signed => |s| @intCast(try math.add(i64, s, @as(i64, @intCast(b)))), | ||
| 2321 | // absptr can actually contain signed values in some cases (aarch64 MachO) | ||
| 2322 | .unsigned => |u| u +% b, | ||
| 2323 | } else switch (value) { | ||
| 2324 | .signed => |s| @as(u64, @intCast(s)), | ||
| 2325 | .unsigned => |u| u, | ||
| 2326 | }; | ||
| 2327 | |||
| 2328 | if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) { | ||
| 2329 | if (@sizeOf(usize) != addr_size_bytes) { | ||
| 2330 | // See the documentation for `follow_indirect` | ||
| 2331 | return error.NonNativeIndirection; | ||
| 2332 | } | ||
| 2333 | |||
| 2334 | const native_ptr = math.cast(usize, ptr) orelse return error.PointerOverflow; | ||
| 2335 | return switch (addr_size_bytes) { | ||
| 2336 | 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*, | ||
| 2337 | else => return error.UnsupportedAddrSize, | ||
| 2338 | }; | ||
| 2339 | } else { | ||
| 2340 | return ptr; | ||
| 2341 | } | ||
| 2342 | } | ||
| 2343 | |||
| 2344 | /// This represents the decoded .eh_frame_hdr header | ||
| 2345 | pub const ExceptionFrameHeader = struct { | ||
| 2346 | eh_frame_ptr: usize, | ||
| 2347 | table_enc: u8, | ||
| 2348 | fde_count: usize, | ||
| 2349 | entries: []const u8, | ||
| 2350 | |||
| 2351 | pub fn entrySize(table_enc: u8) !u8 { | ||
| 2352 | return switch (table_enc & EH.PE.type_mask) { | ||
| 2353 | EH.PE.udata2, | ||
| 2354 | EH.PE.sdata2, | ||
| 2355 | => 4, | ||
| 2356 | EH.PE.udata4, | ||
| 2357 | EH.PE.sdata4, | ||
| 2358 | => 8, | ||
| 2359 | EH.PE.udata8, | ||
| 2360 | EH.PE.sdata8, | ||
| 2361 | => 16, | ||
| 2362 | // This is a binary search table, so all entries must be the same length | ||
| 2363 | else => return badDwarf(), | ||
| 2364 | }; | ||
| 2365 | } | ||
| 2366 | |||
| 2367 | fn isValidPtr( | ||
| 2368 | self: ExceptionFrameHeader, | ||
| 2369 | ptr: usize, | ||
| 2370 | isValidMemory: *const fn (address: usize) bool, | ||
| 2371 | eh_frame_len: ?usize, | ||
| 2372 | ) bool { | ||
| 2373 | if (eh_frame_len) |len| { | ||
| 2374 | return ptr >= self.eh_frame_ptr and ptr < self.eh_frame_ptr + len; | ||
| 2375 | } else { | ||
| 2376 | return isValidMemory(ptr); | ||
| 2377 | } | ||
| 2378 | } | ||
| 2379 | |||
| 2380 | /// Find an entry by binary searching the eh_frame_hdr section. | ||
| 2381 | /// | ||
| 2382 | /// Since the length of the eh_frame section (`eh_frame_len`) may not be known by the caller, | ||
| 2383 | /// `isValidMemory` will be called before accessing any memory referenced by | ||
| 2384 | /// the header entries. If `eh_frame_len` is provided, then these checks can be skipped. | ||
| 2385 | pub fn findEntry( | ||
| 2386 | self: ExceptionFrameHeader, | ||
| 2387 | isValidMemory: *const fn (address: usize) bool, | ||
| 2388 | eh_frame_len: ?usize, | ||
| 2389 | eh_frame_hdr_ptr: usize, | ||
| 2390 | pc: usize, | ||
| 2391 | cie: *CommonInformationEntry, | ||
| 2392 | fde: *FrameDescriptionEntry, | ||
| 2393 | ) !void { | ||
| 2394 | const entry_size = try entrySize(self.table_enc); | ||
| 2395 | |||
| 2396 | var left: usize = 0; | ||
| 2397 | var len: usize = self.fde_count; | ||
| 2398 | |||
| 2399 | var stream = io.fixedBufferStream(self.entries); | ||
| 2400 | const reader = stream.reader(); | ||
| 2401 | |||
| 2402 | while (len > 1) { | ||
| 2403 | const mid = left + len / 2; | ||
| 2404 | |||
| 2405 | try stream.seekTo(mid * entry_size); | ||
| 2406 | const pc_begin = try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{ | ||
| 2407 | .pc_rel_base = @intFromPtr(&self.entries[stream.pos]), | ||
| 2408 | .follow_indirect = true, | ||
| 2409 | .data_rel_base = eh_frame_hdr_ptr, | ||
| 2410 | }, builtin.cpu.arch.endian()) orelse return badDwarf(); | ||
| 2411 | |||
| 2412 | if (pc < pc_begin) { | ||
| 2413 | len /= 2; | ||
| 2414 | } else { | ||
| 2415 | left = mid; | ||
| 2416 | if (pc == pc_begin) break; | ||
| 2417 | len -= len / 2; | ||
| 2418 | } | ||
| 2419 | } | ||
| 2420 | |||
| 2421 | if (len == 0) return badDwarf(); | ||
| 2422 | try stream.seekTo(left * entry_size); | ||
| 2423 | |||
| 2424 | // Read past the pc_begin field of the entry | ||
| 2425 | _ = try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{ | ||
| 2426 | .pc_rel_base = @intFromPtr(&self.entries[stream.pos]), | ||
| 2427 | .follow_indirect = true, | ||
| 2428 | .data_rel_base = eh_frame_hdr_ptr, | ||
| 2429 | }, builtin.cpu.arch.endian()) orelse return badDwarf(); | ||
| 2430 | |||
| 2431 | const fde_ptr = math.cast(usize, try readEhPointer(reader, self.table_enc, @sizeOf(usize), .{ | ||
| 2432 | .pc_rel_base = @intFromPtr(&self.entries[stream.pos]), | ||
| 2433 | .follow_indirect = true, | ||
| 2434 | .data_rel_base = eh_frame_hdr_ptr, | ||
| 2435 | }, builtin.cpu.arch.endian()) orelse return badDwarf()) orelse return badDwarf(); | ||
| 2436 | |||
| 2437 | // Verify the length fields of the FDE header are readable | ||
| 2438 | if (!self.isValidPtr(fde_ptr, isValidMemory, eh_frame_len) or fde_ptr < self.eh_frame_ptr) return badDwarf(); | ||
| 2439 | |||
| 2440 | var fde_entry_header_len: usize = 4; | ||
| 2441 | if (!self.isValidPtr(fde_ptr + 3, isValidMemory, eh_frame_len)) return badDwarf(); | ||
| 2442 | if (self.isValidPtr(fde_ptr + 11, isValidMemory, eh_frame_len)) fde_entry_header_len = 12; | ||
| 2443 | |||
| 2444 | // Even if eh_frame_len is not specified, all ranges accssed are checked by isValidPtr | ||
| 2445 | const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse math.maxInt(u32)]; | ||
| 2446 | |||
| 2447 | const fde_offset = fde_ptr - self.eh_frame_ptr; | ||
| 2448 | var eh_frame_stream = io.fixedBufferStream(eh_frame); | ||
| 2449 | try eh_frame_stream.seekTo(fde_offset); | ||
| 2450 | |||
| 2451 | const fde_entry_header = try EntryHeader.read(&eh_frame_stream, .eh_frame, builtin.cpu.arch.endian()); | ||
| 2452 | if (!self.isValidPtr(@intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf(); | ||
| 2453 | if (fde_entry_header.type != .fde) return badDwarf(); | ||
| 2454 | |||
| 2455 | // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable | ||
| 2456 | const cie_offset = fde_entry_header.type.fde; | ||
| 2457 | try eh_frame_stream.seekTo(cie_offset); | ||
| 2458 | const cie_entry_header = try EntryHeader.read(&eh_frame_stream, .eh_frame, builtin.cpu.arch.endian()); | ||
| 2459 | if (!self.isValidPtr(@intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), isValidMemory, eh_frame_len)) return badDwarf(); | ||
| 2460 | if (cie_entry_header.type != .cie) return badDwarf(); | ||
| 2461 | |||
| 2462 | cie.* = try CommonInformationEntry.parse( | ||
| 2463 | cie_entry_header.entry_bytes, | ||
| 2464 | 0, | ||
| 2465 | true, | ||
| 2466 | cie_entry_header.is_64, | ||
| 2467 | .eh_frame, | ||
| 2468 | cie_entry_header.length_offset, | ||
| 2469 | @sizeOf(usize), | ||
| 2470 | builtin.cpu.arch.endian(), | ||
| 2471 | ); | ||
| 2472 | |||
| 2473 | fde.* = try FrameDescriptionEntry.parse( | ||
| 2474 | fde_entry_header.entry_bytes, | ||
| 2475 | 0, | ||
| 2476 | true, | ||
| 2477 | cie.*, | ||
| 2478 | @sizeOf(usize), | ||
| 2479 | builtin.cpu.arch.endian(), | ||
| 2480 | ); | ||
| 2481 | } | ||
| 2482 | }; | ||
| 2483 | |||
| 2484 | pub const EntryHeader = struct { | ||
| 2485 | /// Offset of the length field in the backing buffer | ||
| 2486 | length_offset: usize, | ||
| 2487 | is_64: bool, | ||
| 2488 | type: union(enum) { | ||
| 2489 | cie, | ||
| 2490 | /// Value is the offset of the corresponding CIE | ||
| 2491 | fde: u64, | ||
| 2492 | terminator: void, | ||
| 2493 | }, | ||
| 2494 | /// The entry's contents, not including the ID field | ||
| 2495 | entry_bytes: []const u8, | ||
| 2496 | |||
| 2497 | /// Reads a header for either an FDE or a CIE, then advances the stream to the position after the trailing structure. | ||
| 2498 | /// `stream` must be a stream backed by either the .eh_frame or .debug_frame sections. | ||
| 2499 | pub fn read(stream: *std.io.FixedBufferStream([]const u8), dwarf_section: DwarfSection, endian: std.builtin.Endian) !EntryHeader { | ||
| 2500 | assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); | ||
| 2501 | |||
| 2502 | const reader = stream.reader(); | ||
| 2503 | const length_offset = stream.pos; | ||
| 2504 | |||
| 2505 | var is_64: bool = undefined; | ||
| 2506 | const length = math.cast(usize, try readUnitLength(reader, endian, &is_64)) orelse return badDwarf(); | ||
| 2507 | if (length == 0) return .{ | ||
| 2508 | .length_offset = length_offset, | ||
| 2509 | .is_64 = is_64, | ||
| 2510 | .type = .{ .terminator = {} }, | ||
| 2511 | .entry_bytes = &.{}, | ||
| 2512 | }; | ||
| 2513 | |||
| 2514 | const id_len = @as(u8, if (is_64) 8 else 4); | ||
| 2515 | const id = if (is_64) try reader.readInt(u64, endian) else try reader.readInt(u32, endian); | ||
| 2516 | const entry_bytes = stream.buffer[stream.pos..][0 .. length - id_len]; | ||
| 2517 | const cie_id: u64 = switch (dwarf_section) { | ||
| 2518 | .eh_frame => CommonInformationEntry.eh_id, | ||
| 2519 | .debug_frame => if (is_64) CommonInformationEntry.dwarf64_id else CommonInformationEntry.dwarf32_id, | ||
| 2520 | else => unreachable, | ||
| 2521 | }; | ||
| 2522 | |||
| 2523 | const result = EntryHeader{ | ||
| 2524 | .length_offset = length_offset, | ||
| 2525 | .is_64 = is_64, | ||
| 2526 | .type = if (id == cie_id) .{ .cie = {} } else .{ | ||
| 2527 | .fde = switch (dwarf_section) { | ||
| 2528 | .eh_frame => try std.math.sub(u64, stream.pos - id_len, id), | ||
| 2529 | .debug_frame => id, | ||
| 2530 | else => unreachable, | ||
| 2531 | }, | ||
| 2532 | }, | ||
| 2533 | .entry_bytes = entry_bytes, | ||
| 2534 | }; | ||
| 2535 | |||
| 2536 | stream.pos += entry_bytes.len; | ||
| 2537 | return result; | ||
| 2538 | } | ||
| 2539 | |||
| 2540 | /// The length of the entry including the ID field, but not the length field itself | ||
| 2541 | pub fn entryLength(self: EntryHeader) usize { | ||
| 2542 | return self.entry_bytes.len + @as(u8, if (self.is_64) 8 else 4); | ||
| 2543 | } | ||
| 2544 | }; | ||
| 2545 | |||
| 2546 | pub const CommonInformationEntry = struct { | ||
| 2547 | // Used in .eh_frame | ||
| 2548 | pub const eh_id = 0; | ||
| 2549 | |||
| 2550 | // Used in .debug_frame (DWARF32) | ||
| 2551 | pub const dwarf32_id = math.maxInt(u32); | ||
| 2552 | |||
| 2553 | // Used in .debug_frame (DWARF64) | ||
| 2554 | pub const dwarf64_id = math.maxInt(u64); | ||
| 2555 | |||
| 2556 | // Offset of the length field of this entry in the eh_frame section. | ||
| 2557 | // This is the key that FDEs use to reference CIEs. | ||
| 2558 | length_offset: u64, | ||
| 2559 | version: u8, | ||
| 2560 | address_size: u8, | ||
| 2561 | is_64: bool, | ||
| 2562 | |||
| 2563 | // Only present in version 4 | ||
| 2564 | segment_selector_size: ?u8, | ||
| 2565 | |||
| 2566 | code_alignment_factor: u32, | ||
| 2567 | data_alignment_factor: i32, | ||
| 2568 | return_address_register: u8, | ||
| 2569 | |||
| 2570 | aug_str: []const u8, | ||
| 2571 | aug_data: []const u8, | ||
| 2572 | lsda_pointer_enc: u8, | ||
| 2573 | personality_enc: ?u8, | ||
| 2574 | personality_routine_pointer: ?u64, | ||
| 2575 | fde_pointer_enc: u8, | ||
| 2576 | initial_instructions: []const u8, | ||
| 2577 | |||
| 2578 | pub fn isSignalFrame(self: CommonInformationEntry) bool { | ||
| 2579 | for (self.aug_str) |c| if (c == 'S') return true; | ||
| 2580 | return false; | ||
| 2581 | } | ||
| 2582 | |||
| 2583 | pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool { | ||
| 2584 | for (self.aug_str) |c| if (c == 'B') return true; | ||
| 2585 | return false; | ||
| 2586 | } | ||
| 2587 | |||
| 2588 | pub fn mteTaggedFrame(self: CommonInformationEntry) bool { | ||
| 2589 | for (self.aug_str) |c| if (c == 'G') return true; | ||
| 2590 | return false; | ||
| 2591 | } | ||
| 2592 | |||
| 2593 | /// This function expects to read the CIE starting with the version field. | ||
| 2594 | /// The returned struct references memory backed by cie_bytes. | ||
| 2595 | /// | ||
| 2596 | /// See the FrameDescriptionEntry.parse documentation for the description | ||
| 2597 | /// of `pc_rel_offset` and `is_runtime`. | ||
| 2598 | /// | ||
| 2599 | /// `length_offset` specifies the offset of this CIE's length field in the | ||
| 2600 | /// .eh_frame / .debug_frame section. | ||
| 2601 | pub fn parse( | ||
| 2602 | cie_bytes: []const u8, | ||
| 2603 | pc_rel_offset: i64, | ||
| 2604 | is_runtime: bool, | ||
| 2605 | is_64: bool, | ||
| 2606 | dwarf_section: DwarfSection, | ||
| 2607 | length_offset: u64, | ||
| 2608 | addr_size_bytes: u8, | ||
| 2609 | endian: std.builtin.Endian, | ||
| 2610 | ) !CommonInformationEntry { | ||
| 2611 | if (addr_size_bytes > 8) return error.UnsupportedAddrSize; | ||
| 2612 | |||
| 2613 | var stream = io.fixedBufferStream(cie_bytes); | ||
| 2614 | const reader = stream.reader(); | ||
| 2615 | |||
| 2616 | const version = try reader.readByte(); | ||
| 2617 | switch (dwarf_section) { | ||
| 2618 | .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, | ||
| 2619 | .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, | ||
| 2620 | else => return error.UnsupportedDwarfSection, | ||
| 2621 | } | ||
| 2622 | |||
| 2623 | var has_eh_data = false; | ||
| 2624 | var has_aug_data = false; | ||
| 2625 | |||
| 2626 | var aug_str_len: usize = 0; | ||
| 2627 | var aug_str_start = stream.pos; | ||
| 2628 | var aug_byte = try reader.readByte(); | ||
| 2629 | while (aug_byte != 0) : (aug_byte = try reader.readByte()) { | ||
| 2630 | switch (aug_byte) { | ||
| 2631 | 'z' => { | ||
| 2632 | if (aug_str_len != 0) return badDwarf(); | ||
| 2633 | has_aug_data = true; | ||
| 2634 | }, | ||
| 2635 | 'e' => { | ||
| 2636 | if (has_aug_data or aug_str_len != 0) return badDwarf(); | ||
| 2637 | if (try reader.readByte() != 'h') return badDwarf(); | ||
| 2638 | has_eh_data = true; | ||
| 2639 | }, | ||
| 2640 | else => if (has_eh_data) return badDwarf(), | ||
| 2641 | } | ||
| 2642 | |||
| 2643 | aug_str_len += 1; | ||
| 2644 | } | ||
| 2645 | |||
| 2646 | if (has_eh_data) { | ||
| 2647 | // legacy data created by older versions of gcc - unsupported here | ||
| 2648 | for (0..addr_size_bytes) |_| _ = try reader.readByte(); | ||
| 2649 | } | ||
| 2650 | |||
| 2651 | const address_size = if (version == 4) try reader.readByte() else addr_size_bytes; | ||
| 2652 | const segment_selector_size = if (version == 4) try reader.readByte() else null; | ||
| 2653 | |||
| 2654 | const code_alignment_factor = try leb.readULEB128(u32, reader); | ||
| 2655 | const data_alignment_factor = try leb.readILEB128(i32, reader); | ||
| 2656 | const return_address_register = if (version == 1) try reader.readByte() else try leb.readULEB128(u8, reader); | ||
| 2657 | |||
| 2658 | var lsda_pointer_enc: u8 = EH.PE.omit; | ||
| 2659 | var personality_enc: ?u8 = null; | ||
| 2660 | var personality_routine_pointer: ?u64 = null; | ||
| 2661 | var fde_pointer_enc: u8 = EH.PE.absptr; | ||
| 2662 | |||
| 2663 | var aug_data: []const u8 = &[_]u8{}; | ||
| 2664 | const aug_str = if (has_aug_data) blk: { | ||
| 2665 | const aug_data_len = try leb.readULEB128(usize, reader); | ||
| 2666 | const aug_data_start = stream.pos; | ||
| 2667 | aug_data = cie_bytes[aug_data_start..][0..aug_data_len]; | ||
| 2668 | |||
| 2669 | const aug_str = cie_bytes[aug_str_start..][0..aug_str_len]; | ||
| 2670 | for (aug_str[1..]) |byte| { | ||
| 2671 | switch (byte) { | ||
| 2672 | 'L' => { | ||
| 2673 | lsda_pointer_enc = try reader.readByte(); | ||
| 2674 | }, | ||
| 2675 | 'P' => { | ||
| 2676 | personality_enc = try reader.readByte(); | ||
| 2677 | personality_routine_pointer = try readEhPointer( | ||
| 2678 | reader, | ||
| 2679 | personality_enc.?, | ||
| 2680 | addr_size_bytes, | ||
| 2681 | .{ | ||
| 2682 | .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[stream.pos]), pc_rel_offset), | ||
| 2683 | .follow_indirect = is_runtime, | ||
| 2684 | }, | ||
| 2685 | endian, | ||
| 2686 | ); | ||
| 2687 | }, | ||
| 2688 | 'R' => { | ||
| 2689 | fde_pointer_enc = try reader.readByte(); | ||
| 2690 | }, | ||
| 2691 | 'S', 'B', 'G' => {}, | ||
| 2692 | else => return badDwarf(), | ||
| 2693 | } | ||
| 2694 | } | ||
| 2695 | |||
| 2696 | // aug_data_len can include padding so the CIE ends on an address boundary | ||
| 2697 | try stream.seekTo(aug_data_start + aug_data_len); | ||
| 2698 | break :blk aug_str; | ||
| 2699 | } else &[_]u8{}; | ||
| 2700 | |||
| 2701 | const initial_instructions = cie_bytes[stream.pos..]; | ||
| 2702 | return .{ | ||
| 2703 | .length_offset = length_offset, | ||
| 2704 | .version = version, | ||
| 2705 | .address_size = address_size, | ||
| 2706 | .is_64 = is_64, | ||
| 2707 | .segment_selector_size = segment_selector_size, | ||
| 2708 | .code_alignment_factor = code_alignment_factor, | ||
| 2709 | .data_alignment_factor = data_alignment_factor, | ||
| 2710 | .return_address_register = return_address_register, | ||
| 2711 | .aug_str = aug_str, | ||
| 2712 | .aug_data = aug_data, | ||
| 2713 | .lsda_pointer_enc = lsda_pointer_enc, | ||
| 2714 | .personality_enc = personality_enc, | ||
| 2715 | .personality_routine_pointer = personality_routine_pointer, | ||
| 2716 | .fde_pointer_enc = fde_pointer_enc, | ||
| 2717 | .initial_instructions = initial_instructions, | ||
| 2718 | }; | ||
| 2719 | } | ||
| 2720 | }; | ||
| 2721 | |||
| 2722 | pub const FrameDescriptionEntry = struct { | ||
| 2723 | // Offset into eh_frame where the CIE for this FDE is stored | ||
| 2724 | cie_length_offset: u64, | ||
| 2725 | |||
| 2726 | pc_begin: u64, | ||
| 2727 | pc_range: u64, | ||
| 2728 | lsda_pointer: ?u64, | ||
| 2729 | aug_data: []const u8, | ||
| 2730 | instructions: []const u8, | ||
| 2731 | |||
| 2732 | /// This function expects to read the FDE starting at the PC Begin field. | ||
| 2733 | /// The returned struct references memory backed by `fde_bytes`. | ||
| 2734 | /// | ||
| 2735 | /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values | ||
| 2736 | /// used when decoding pointers. This should be set to zero if fde_bytes is | ||
| 2737 | /// backed by the memory of a .eh_frame / .debug_frame section in the running executable. | ||
| 2738 | /// Otherwise, it should be the relative offset to translate addresses from | ||
| 2739 | /// where the section is currently stored in memory, to where it *would* be | ||
| 2740 | /// stored at runtime: section base addr - backing data base ptr. | ||
| 2741 | /// | ||
| 2742 | /// Similarly, `is_runtime` specifies this function is being called on a runtime | ||
| 2743 | /// section, and so indirect pointers can be followed. | ||
| 2744 | pub fn parse( | ||
| 2745 | fde_bytes: []const u8, | ||
| 2746 | pc_rel_offset: i64, | ||
| 2747 | is_runtime: bool, | ||
| 2748 | cie: CommonInformationEntry, | ||
| 2749 | addr_size_bytes: u8, | ||
| 2750 | endian: std.builtin.Endian, | ||
| 2751 | ) !FrameDescriptionEntry { | ||
| 2752 | if (addr_size_bytes > 8) return error.InvalidAddrSize; | ||
| 2753 | |||
| 2754 | var stream = io.fixedBufferStream(fde_bytes); | ||
| 2755 | const reader = stream.reader(); | ||
| 2756 | |||
| 2757 | const pc_begin = try readEhPointer( | ||
| 2758 | reader, | ||
| 2759 | cie.fde_pointer_enc, | ||
| 2760 | addr_size_bytes, | ||
| 2761 | .{ | ||
| 2762 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[stream.pos]), pc_rel_offset), | ||
| 2763 | .follow_indirect = is_runtime, | ||
| 2764 | }, | ||
| 2765 | endian, | ||
| 2766 | ) orelse return badDwarf(); | ||
| 2767 | |||
| 2768 | const pc_range = try readEhPointer( | ||
| 2769 | reader, | ||
| 2770 | cie.fde_pointer_enc, | ||
| 2771 | addr_size_bytes, | ||
| 2772 | .{ | ||
| 2773 | .pc_rel_base = 0, | ||
| 2774 | .follow_indirect = false, | ||
| 2775 | }, | ||
| 2776 | endian, | ||
| 2777 | ) orelse return badDwarf(); | ||
| 2778 | |||
| 2779 | var aug_data: []const u8 = &[_]u8{}; | ||
| 2780 | const lsda_pointer = if (cie.aug_str.len > 0) blk: { | ||
| 2781 | const aug_data_len = try leb.readULEB128(usize, reader); | ||
| 2782 | const aug_data_start = stream.pos; | ||
| 2783 | aug_data = fde_bytes[aug_data_start..][0..aug_data_len]; | ||
| 2784 | |||
| 2785 | const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit) | ||
| 2786 | try readEhPointer( | ||
| 2787 | reader, | ||
| 2788 | cie.lsda_pointer_enc, | ||
| 2789 | addr_size_bytes, | ||
| 2790 | .{ | ||
| 2791 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[stream.pos]), pc_rel_offset), | ||
| 2792 | .follow_indirect = is_runtime, | ||
| 2793 | }, | ||
| 2794 | endian, | ||
| 2795 | ) | ||
| 2796 | else | ||
| 2797 | null; | ||
| 2798 | |||
| 2799 | try stream.seekTo(aug_data_start + aug_data_len); | ||
| 2800 | break :blk lsda_pointer; | ||
| 2801 | } else null; | ||
| 2802 | |||
| 2803 | const instructions = fde_bytes[stream.pos..]; | ||
| 2804 | return .{ | ||
| 2805 | .cie_length_offset = cie.length_offset, | ||
| 2806 | .pc_begin = pc_begin, | ||
| 2807 | .pc_range = pc_range, | ||
| 2808 | .lsda_pointer = lsda_pointer, | ||
| 2809 | .aug_data = aug_data, | ||
| 2810 | .instructions = instructions, | ||
| 2811 | }; | ||
| 2812 | } | ||
| 2813 | }; | ||
| 2814 | |||
| 2815 | fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { | ||
| 2816 | if (pc_rel_offset < 0) { | ||
| 2817 | return math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset))); | ||
| 2818 | } else { | ||
| 2819 | return math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); | ||
| 2820 | } | ||
| 2821 | } | ||
| 2822 | |||
| 2823 | test { | ||
| 2824 | std.testing.refAllDecls(@This()); | ||
| 2825 | } |
lib/std/dwarf/EH.zig created+27| ... | @@ -0,0 +1,27 @@ | ||
| 1 | pub const PE = struct { | ||
| 2 | pub const absptr = 0x00; | ||
| 3 | |||
| 4 | pub const size_mask = 0x7; | ||
| 5 | pub const sign_mask = 0x8; | ||
| 6 | pub const type_mask = size_mask | sign_mask; | ||
| 7 | |||
| 8 | pub const uleb128 = 0x01; | ||
| 9 | pub const udata2 = 0x02; | ||
| 10 | pub const udata4 = 0x03; | ||
| 11 | pub const udata8 = 0x04; | ||
| 12 | pub const sleb128 = 0x09; | ||
| 13 | pub const sdata2 = 0x0A; | ||
| 14 | pub const sdata4 = 0x0B; | ||
| 15 | pub const sdata8 = 0x0C; | ||
| 16 | |||
| 17 | pub const rel_mask = 0x70; | ||
| 18 | pub const pcrel = 0x10; | ||
| 19 | pub const textrel = 0x20; | ||
| 20 | pub const datarel = 0x30; | ||
| 21 | pub const funcrel = 0x40; | ||
| 22 | pub const aligned = 0x50; | ||
| 23 | |||
| 24 | pub const indirect = 0x80; | ||
| 25 | |||
| 26 | pub const omit = 0xff; | ||
| 27 | }; | ||
lib/std/dwarf/abi.zig created+387| ... | @@ -0,0 +1,387 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const std = @import("../std.zig"); | ||
| 3 | const os = std.os; | ||
| 4 | const mem = std.mem; | ||
| 5 | |||
| 6 | pub fn isSupportedArch(arch: std.Target.Cpu.Arch) bool { | ||
| 7 | return switch (arch) { | ||
| 8 | .x86, | ||
| 9 | .x86_64, | ||
| 10 | .arm, | ||
| 11 | .aarch64, | ||
| 12 | => true, | ||
| 13 | else => false, | ||
| 14 | }; | ||
| 15 | } | ||
| 16 | |||
| 17 | pub fn ipRegNum() u8 { | ||
| 18 | return switch (builtin.cpu.arch) { | ||
| 19 | .x86 => 8, | ||
| 20 | .x86_64 => 16, | ||
| 21 | .arm => 15, | ||
| 22 | .aarch64 => 32, | ||
| 23 | else => unreachable, | ||
| 24 | }; | ||
| 25 | } | ||
| 26 | |||
| 27 | pub fn fpRegNum(reg_context: RegisterContext) u8 { | ||
| 28 | return switch (builtin.cpu.arch) { | ||
| 29 | // GCC on OS X historicaly did the opposite of ELF for these registers (only in .eh_frame), and that is now the convention for MachO | ||
| 30 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5, | ||
| 31 | .x86_64 => 6, | ||
| 32 | .arm => 11, | ||
| 33 | .aarch64 => 29, | ||
| 34 | else => unreachable, | ||
| 35 | }; | ||
| 36 | } | ||
| 37 | |||
| 38 | pub fn spRegNum(reg_context: RegisterContext) u8 { | ||
| 39 | return switch (builtin.cpu.arch) { | ||
| 40 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4, | ||
| 41 | .x86_64 => 7, | ||
| 42 | .arm => 13, | ||
| 43 | .aarch64 => 31, | ||
| 44 | else => unreachable, | ||
| 45 | }; | ||
| 46 | } | ||
| 47 | |||
| 48 | /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. | ||
| 49 | /// This function clears these signature bits to make the pointer usable. | ||
| 50 | pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { | ||
| 51 | if (builtin.cpu.arch == .aarch64) { | ||
| 52 | // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) | ||
| 53 | // The save / restore is because `xpaclri` operates on x30 (LR) | ||
| 54 | return asm ( | ||
| 55 | \\mov x16, x30 | ||
| 56 | \\mov x30, x15 | ||
| 57 | \\hint 0x07 | ||
| 58 | \\mov x15, x30 | ||
| 59 | \\mov x30, x16 | ||
| 60 | : [ret] "={x15}" (-> usize), | ||
| 61 | : [ptr] "{x15}" (ptr), | ||
| 62 | : "x16" | ||
| 63 | ); | ||
| 64 | } | ||
| 65 | |||
| 66 | return ptr; | ||
| 67 | } | ||
| 68 | |||
| 69 | pub const RegisterContext = struct { | ||
| 70 | eh_frame: bool, | ||
| 71 | is_macho: bool, | ||
| 72 | }; | ||
| 73 | |||
| 74 | pub const AbiError = error{ | ||
| 75 | InvalidRegister, | ||
| 76 | UnimplementedArch, | ||
| 77 | UnimplementedOs, | ||
| 78 | RegisterContextRequired, | ||
| 79 | ThreadContextNotSupported, | ||
| 80 | }; | ||
| 81 | |||
| 82 | fn RegValueReturnType(comptime ContextPtrType: type, comptime T: type) type { | ||
| 83 | const reg_bytes_type = comptime RegBytesReturnType(ContextPtrType); | ||
| 84 | const info = @typeInfo(reg_bytes_type).Pointer; | ||
| 85 | return @Type(.{ | ||
| 86 | .Pointer = .{ | ||
| 87 | .size = .One, | ||
| 88 | .is_const = info.is_const, | ||
| 89 | .is_volatile = info.is_volatile, | ||
| 90 | .is_allowzero = info.is_allowzero, | ||
| 91 | .alignment = info.alignment, | ||
| 92 | .address_space = info.address_space, | ||
| 93 | .child = T, | ||
| 94 | .sentinel = null, | ||
| 95 | }, | ||
| 96 | }); | ||
| 97 | } | ||
| 98 | |||
| 99 | /// Returns a pointer to a register stored in a ThreadContext, preserving the pointer attributes of the context. | ||
| 100 | pub fn regValueNative( | ||
| 101 | comptime T: type, | ||
| 102 | thread_context_ptr: anytype, | ||
| 103 | reg_number: u8, | ||
| 104 | reg_context: ?RegisterContext, | ||
| 105 | ) !RegValueReturnType(@TypeOf(thread_context_ptr), T) { | ||
| 106 | const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context); | ||
| 107 | if (@sizeOf(T) != reg_bytes.len) return error.IncompatibleRegisterSize; | ||
| 108 | return mem.bytesAsValue(T, reg_bytes[0..@sizeOf(T)]); | ||
| 109 | } | ||
| 110 | |||
| 111 | fn RegBytesReturnType(comptime ContextPtrType: type) type { | ||
| 112 | const info = @typeInfo(ContextPtrType); | ||
| 113 | if (info != .Pointer or info.Pointer.child != std.debug.ThreadContext) { | ||
| 114 | @compileError("Expected a pointer to std.debug.ThreadContext, got " ++ @typeName(@TypeOf(ContextPtrType))); | ||
| 115 | } | ||
| 116 | |||
| 117 | return if (info.Pointer.is_const) return []const u8 else []u8; | ||
| 118 | } | ||
| 119 | |||
| 120 | /// Returns a slice containing the backing storage for `reg_number`. | ||
| 121 | /// | ||
| 122 | /// `reg_context` describes in what context the register number is used, as it can have different | ||
| 123 | /// meanings depending on the DWARF container. It is only required when getting the stack or | ||
| 124 | /// frame pointer register on some architectures. | ||
| 125 | pub fn regBytes( | ||
| 126 | thread_context_ptr: anytype, | ||
| 127 | reg_number: u8, | ||
| 128 | reg_context: ?RegisterContext, | ||
| 129 | ) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) { | ||
| 130 | if (builtin.os.tag == .windows) { | ||
| 131 | return switch (builtin.cpu.arch) { | ||
| 132 | .x86 => switch (reg_number) { | ||
| 133 | 0 => mem.asBytes(&thread_context_ptr.Eax), | ||
| 134 | 1 => mem.asBytes(&thread_context_ptr.Ecx), | ||
| 135 | 2 => mem.asBytes(&thread_context_ptr.Edx), | ||
| 136 | 3 => mem.asBytes(&thread_context_ptr.Ebx), | ||
| 137 | 4 => mem.asBytes(&thread_context_ptr.Esp), | ||
| 138 | 5 => mem.asBytes(&thread_context_ptr.Ebp), | ||
| 139 | 6 => mem.asBytes(&thread_context_ptr.Esi), | ||
| 140 | 7 => mem.asBytes(&thread_context_ptr.Edi), | ||
| 141 | 8 => mem.asBytes(&thread_context_ptr.Eip), | ||
| 142 | 9 => mem.asBytes(&thread_context_ptr.EFlags), | ||
| 143 | 10 => mem.asBytes(&thread_context_ptr.SegCs), | ||
| 144 | 11 => mem.asBytes(&thread_context_ptr.SegSs), | ||
| 145 | 12 => mem.asBytes(&thread_context_ptr.SegDs), | ||
| 146 | 13 => mem.asBytes(&thread_context_ptr.SegEs), | ||
| 147 | 14 => mem.asBytes(&thread_context_ptr.SegFs), | ||
| 148 | 15 => mem.asBytes(&thread_context_ptr.SegGs), | ||
| 149 | else => error.InvalidRegister, | ||
| 150 | }, | ||
| 151 | .x86_64 => switch (reg_number) { | ||
| 152 | 0 => mem.asBytes(&thread_context_ptr.Rax), | ||
| 153 | 1 => mem.asBytes(&thread_context_ptr.Rdx), | ||
| 154 | 2 => mem.asBytes(&thread_context_ptr.Rcx), | ||
| 155 | 3 => mem.asBytes(&thread_context_ptr.Rbx), | ||
| 156 | 4 => mem.asBytes(&thread_context_ptr.Rsi), | ||
| 157 | 5 => mem.asBytes(&thread_context_ptr.Rdi), | ||
| 158 | 6 => mem.asBytes(&thread_context_ptr.Rbp), | ||
| 159 | 7 => mem.asBytes(&thread_context_ptr.Rsp), | ||
| 160 | 8 => mem.asBytes(&thread_context_ptr.R8), | ||
| 161 | 9 => mem.asBytes(&thread_context_ptr.R9), | ||
| 162 | 10 => mem.asBytes(&thread_context_ptr.R10), | ||
| 163 | 11 => mem.asBytes(&thread_context_ptr.R11), | ||
| 164 | 12 => mem.asBytes(&thread_context_ptr.R12), | ||
| 165 | 13 => mem.asBytes(&thread_context_ptr.R13), | ||
| 166 | 14 => mem.asBytes(&thread_context_ptr.R14), | ||
| 167 | 15 => mem.asBytes(&thread_context_ptr.R15), | ||
| 168 | 16 => mem.asBytes(&thread_context_ptr.Rip), | ||
| 169 | else => error.InvalidRegister, | ||
| 170 | }, | ||
| 171 | .aarch64 => switch (reg_number) { | ||
| 172 | 0...30 => mem.asBytes(&thread_context_ptr.DUMMYUNIONNAME.X[reg_number]), | ||
| 173 | 31 => mem.asBytes(&thread_context_ptr.Sp), | ||
| 174 | 32 => mem.asBytes(&thread_context_ptr.Pc), | ||
| 175 | else => error.InvalidRegister, | ||
| 176 | }, | ||
| 177 | else => error.UnimplementedArch, | ||
| 178 | }; | ||
| 179 | } | ||
| 180 | |||
| 181 | if (!std.debug.have_ucontext) return error.ThreadContextNotSupported; | ||
| 182 | |||
| 183 | const ucontext_ptr = thread_context_ptr; | ||
| 184 | return switch (builtin.cpu.arch) { | ||
| 185 | .x86 => switch (builtin.os.tag) { | ||
| 186 | .linux, .netbsd, .solaris => switch (reg_number) { | ||
| 187 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EAX]), | ||
| 188 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ECX]), | ||
| 189 | 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDX]), | ||
| 190 | 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBX]), | ||
| 191 | 4...5 => if (reg_context) |r| bytes: { | ||
| 192 | if (reg_number == 4) { | ||
| 193 | break :bytes if (r.eh_frame and r.is_macho) | ||
| 194 | mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP]) | ||
| 195 | else | ||
| 196 | mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP]); | ||
| 197 | } else { | ||
| 198 | break :bytes if (r.eh_frame and r.is_macho) | ||
| 199 | mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESP]) | ||
| 200 | else | ||
| 201 | mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EBP]); | ||
| 202 | } | ||
| 203 | } else error.RegisterContextRequired, | ||
| 204 | 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ESI]), | ||
| 205 | 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EDI]), | ||
| 206 | 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EIP]), | ||
| 207 | 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.EFL]), | ||
| 208 | 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.CS]), | ||
| 209 | 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.SS]), | ||
| 210 | 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.DS]), | ||
| 211 | 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.ES]), | ||
| 212 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.FS]), | ||
| 213 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.GS]), | ||
| 214 | 16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs | ||
| 215 | 32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs | ||
| 216 | else => error.InvalidRegister, | ||
| 217 | }, | ||
| 218 | else => error.UnimplementedOs, | ||
| 219 | }, | ||
| 220 | .x86_64 => switch (builtin.os.tag) { | ||
| 221 | .linux, .netbsd, .solaris => switch (reg_number) { | ||
| 222 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RAX]), | ||
| 223 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDX]), | ||
| 224 | 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RCX]), | ||
| 225 | 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBX]), | ||
| 226 | 4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSI]), | ||
| 227 | 5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RDI]), | ||
| 228 | 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RBP]), | ||
| 229 | 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RSP]), | ||
| 230 | 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R8]), | ||
| 231 | 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R9]), | ||
| 232 | 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R10]), | ||
| 233 | 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R11]), | ||
| 234 | 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R12]), | ||
| 235 | 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R13]), | ||
| 236 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R14]), | ||
| 237 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.R15]), | ||
| 238 | 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[os.REG.RIP]), | ||
| 239 | 17...32 => |i| mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]), | ||
| 240 | else => error.InvalidRegister, | ||
| 241 | }, | ||
| 242 | .freebsd => switch (reg_number) { | ||
| 243 | 0 => mem.asBytes(&ucontext_ptr.mcontext.rax), | ||
| 244 | 1 => mem.asBytes(&ucontext_ptr.mcontext.rdx), | ||
| 245 | 2 => mem.asBytes(&ucontext_ptr.mcontext.rcx), | ||
| 246 | 3 => mem.asBytes(&ucontext_ptr.mcontext.rbx), | ||
| 247 | 4 => mem.asBytes(&ucontext_ptr.mcontext.rsi), | ||
| 248 | 5 => mem.asBytes(&ucontext_ptr.mcontext.rdi), | ||
| 249 | 6 => mem.asBytes(&ucontext_ptr.mcontext.rbp), | ||
| 250 | 7 => mem.asBytes(&ucontext_ptr.mcontext.rsp), | ||
| 251 | 8 => mem.asBytes(&ucontext_ptr.mcontext.r8), | ||
| 252 | 9 => mem.asBytes(&ucontext_ptr.mcontext.r9), | ||
| 253 | 10 => mem.asBytes(&ucontext_ptr.mcontext.r10), | ||
| 254 | 11 => mem.asBytes(&ucontext_ptr.mcontext.r11), | ||
| 255 | 12 => mem.asBytes(&ucontext_ptr.mcontext.r12), | ||
| 256 | 13 => mem.asBytes(&ucontext_ptr.mcontext.r13), | ||
| 257 | 14 => mem.asBytes(&ucontext_ptr.mcontext.r14), | ||
| 258 | 15 => mem.asBytes(&ucontext_ptr.mcontext.r15), | ||
| 259 | 16 => mem.asBytes(&ucontext_ptr.mcontext.rip), | ||
| 260 | // TODO: Extract xmm state from mcontext.fpstate? | ||
| 261 | else => error.InvalidRegister, | ||
| 262 | }, | ||
| 263 | .openbsd => switch (reg_number) { | ||
| 264 | 0 => mem.asBytes(&ucontext_ptr.sc_rax), | ||
| 265 | 1 => mem.asBytes(&ucontext_ptr.sc_rdx), | ||
| 266 | 2 => mem.asBytes(&ucontext_ptr.sc_rcx), | ||
| 267 | 3 => mem.asBytes(&ucontext_ptr.sc_rbx), | ||
| 268 | 4 => mem.asBytes(&ucontext_ptr.sc_rsi), | ||
| 269 | 5 => mem.asBytes(&ucontext_ptr.sc_rdi), | ||
| 270 | 6 => mem.asBytes(&ucontext_ptr.sc_rbp), | ||
| 271 | 7 => mem.asBytes(&ucontext_ptr.sc_rsp), | ||
| 272 | 8 => mem.asBytes(&ucontext_ptr.sc_r8), | ||
| 273 | 9 => mem.asBytes(&ucontext_ptr.sc_r9), | ||
| 274 | 10 => mem.asBytes(&ucontext_ptr.sc_r10), | ||
| 275 | 11 => mem.asBytes(&ucontext_ptr.sc_r11), | ||
| 276 | 12 => mem.asBytes(&ucontext_ptr.sc_r12), | ||
| 277 | 13 => mem.asBytes(&ucontext_ptr.sc_r13), | ||
| 278 | 14 => mem.asBytes(&ucontext_ptr.sc_r14), | ||
| 279 | 15 => mem.asBytes(&ucontext_ptr.sc_r15), | ||
| 280 | 16 => mem.asBytes(&ucontext_ptr.sc_rip), | ||
| 281 | // TODO: Extract xmm state from sc_fpstate? | ||
| 282 | else => error.InvalidRegister, | ||
| 283 | }, | ||
| 284 | .macos => switch (reg_number) { | ||
| 285 | 0 => mem.asBytes(&ucontext_ptr.mcontext.ss.rax), | ||
| 286 | 1 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdx), | ||
| 287 | 2 => mem.asBytes(&ucontext_ptr.mcontext.ss.rcx), | ||
| 288 | 3 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbx), | ||
| 289 | 4 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsi), | ||
| 290 | 5 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdi), | ||
| 291 | 6 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbp), | ||
| 292 | 7 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsp), | ||
| 293 | 8 => mem.asBytes(&ucontext_ptr.mcontext.ss.r8), | ||
| 294 | 9 => mem.asBytes(&ucontext_ptr.mcontext.ss.r9), | ||
| 295 | 10 => mem.asBytes(&ucontext_ptr.mcontext.ss.r10), | ||
| 296 | 11 => mem.asBytes(&ucontext_ptr.mcontext.ss.r11), | ||
| 297 | 12 => mem.asBytes(&ucontext_ptr.mcontext.ss.r12), | ||
| 298 | 13 => mem.asBytes(&ucontext_ptr.mcontext.ss.r13), | ||
| 299 | 14 => mem.asBytes(&ucontext_ptr.mcontext.ss.r14), | ||
| 300 | 15 => mem.asBytes(&ucontext_ptr.mcontext.ss.r15), | ||
| 301 | 16 => mem.asBytes(&ucontext_ptr.mcontext.ss.rip), | ||
| 302 | else => error.InvalidRegister, | ||
| 303 | }, | ||
| 304 | else => error.UnimplementedOs, | ||
| 305 | }, | ||
| 306 | .arm => switch (builtin.os.tag) { | ||
| 307 | .linux => switch (reg_number) { | ||
| 308 | 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0), | ||
| 309 | 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1), | ||
| 310 | 2 => mem.asBytes(&ucontext_ptr.mcontext.arm_r2), | ||
| 311 | 3 => mem.asBytes(&ucontext_ptr.mcontext.arm_r3), | ||
| 312 | 4 => mem.asBytes(&ucontext_ptr.mcontext.arm_r4), | ||
| 313 | 5 => mem.asBytes(&ucontext_ptr.mcontext.arm_r5), | ||
| 314 | 6 => mem.asBytes(&ucontext_ptr.mcontext.arm_r6), | ||
| 315 | 7 => mem.asBytes(&ucontext_ptr.mcontext.arm_r7), | ||
| 316 | 8 => mem.asBytes(&ucontext_ptr.mcontext.arm_r8), | ||
| 317 | 9 => mem.asBytes(&ucontext_ptr.mcontext.arm_r9), | ||
| 318 | 10 => mem.asBytes(&ucontext_ptr.mcontext.arm_r10), | ||
| 319 | 11 => mem.asBytes(&ucontext_ptr.mcontext.arm_fp), | ||
| 320 | 12 => mem.asBytes(&ucontext_ptr.mcontext.arm_ip), | ||
| 321 | 13 => mem.asBytes(&ucontext_ptr.mcontext.arm_sp), | ||
| 322 | 14 => mem.asBytes(&ucontext_ptr.mcontext.arm_lr), | ||
| 323 | 15 => mem.asBytes(&ucontext_ptr.mcontext.arm_pc), | ||
| 324 | // CPSR is not allocated a register number (See: https://github.com/ARM-software/abi-aa/blob/main/aadwarf32/aadwarf32.rst, Section 4.1) | ||
| 325 | else => error.InvalidRegister, | ||
| 326 | }, | ||
| 327 | else => error.UnimplementedOs, | ||
| 328 | }, | ||
| 329 | .aarch64 => switch (builtin.os.tag) { | ||
| 330 | .macos => switch (reg_number) { | ||
| 331 | 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]), | ||
| 332 | 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp), | ||
| 333 | 30 => mem.asBytes(&ucontext_ptr.mcontext.ss.lr), | ||
| 334 | 31 => mem.asBytes(&ucontext_ptr.mcontext.ss.sp), | ||
| 335 | 32 => mem.asBytes(&ucontext_ptr.mcontext.ss.pc), | ||
| 336 | |||
| 337 | // TODO: Find storage for this state | ||
| 338 | //34 => mem.asBytes(&ucontext_ptr.ra_sign_state), | ||
| 339 | |||
| 340 | // V0-V31 | ||
| 341 | 64...95 => mem.asBytes(&ucontext_ptr.mcontext.ns.q[reg_number - 64]), | ||
| 342 | else => error.InvalidRegister, | ||
| 343 | }, | ||
| 344 | .netbsd => switch (reg_number) { | ||
| 345 | 0...34 => mem.asBytes(&ucontext_ptr.mcontext.gregs[reg_number]), | ||
| 346 | else => error.InvalidRegister, | ||
| 347 | }, | ||
| 348 | .freebsd => switch (reg_number) { | ||
| 349 | 0...29 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.x[reg_number]), | ||
| 350 | 30 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.lr), | ||
| 351 | 31 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.sp), | ||
| 352 | |||
| 353 | // TODO: This seems wrong, but it was in the previous debug.zig code for mapping PC, check this | ||
| 354 | 32 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.elr), | ||
| 355 | |||
| 356 | else => error.InvalidRegister, | ||
| 357 | }, | ||
| 358 | else => switch (reg_number) { | ||
| 359 | 0...30 => mem.asBytes(&ucontext_ptr.mcontext.regs[reg_number]), | ||
| 360 | 31 => mem.asBytes(&ucontext_ptr.mcontext.sp), | ||
| 361 | 32 => mem.asBytes(&ucontext_ptr.mcontext.pc), | ||
| 362 | else => error.InvalidRegister, | ||
| 363 | }, | ||
| 364 | }, | ||
| 365 | else => error.UnimplementedArch, | ||
| 366 | }; | ||
| 367 | } | ||
| 368 | |||
| 369 | /// Returns the ABI-defined default value this register has in the unwinding table | ||
| 370 | /// before running any of the CIE instructions. The DWARF spec defines these as having | ||
| 371 | /// the .undefined rule by default, but allows ABI authors to override that. | ||
| 372 | pub fn getRegDefaultValue(reg_number: u8, context: *std.dwarf.UnwindContext, out: []u8) !void { | ||
| 373 | switch (builtin.cpu.arch) { | ||
| 374 | .aarch64 => { | ||
| 375 | // Callee-saved registers are initialized as if they had the .same_value rule | ||
| 376 | if (reg_number >= 19 and reg_number <= 28) { | ||
| 377 | const src = try regBytes(context.thread_context, reg_number, context.reg_context); | ||
| 378 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 379 | @memcpy(out, src); | ||
| 380 | return; | ||
| 381 | } | ||
| 382 | }, | ||
| 383 | else => {}, | ||
| 384 | } | ||
| 385 | |||
| 386 | @memset(out, undefined); | ||
| 387 | } | ||
lib/std/dwarf/call_frame.zig created+610| ... | @@ -0,0 +1,610 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const std = @import("../std.zig"); | ||
| 3 | const mem = std.mem; | ||
| 4 | const debug = std.debug; | ||
| 5 | const leb = std.leb; | ||
| 6 | const dwarf = std.dwarf; | ||
| 7 | const abi = dwarf.abi; | ||
| 8 | const expressions = dwarf.expressions; | ||
| 9 | const assert = std.debug.assert; | ||
| 10 | |||
| 11 | const Opcode = enum(u8) { | ||
| 12 | advance_loc = 0x1 << 6, | ||
| 13 | offset = 0x2 << 6, | ||
| 14 | restore = 0x3 << 6, | ||
| 15 | |||
| 16 | nop = 0x00, | ||
| 17 | set_loc = 0x01, | ||
| 18 | advance_loc1 = 0x02, | ||
| 19 | advance_loc2 = 0x03, | ||
| 20 | advance_loc4 = 0x04, | ||
| 21 | offset_extended = 0x05, | ||
| 22 | restore_extended = 0x06, | ||
| 23 | undefined = 0x07, | ||
| 24 | same_value = 0x08, | ||
| 25 | register = 0x09, | ||
| 26 | remember_state = 0x0a, | ||
| 27 | restore_state = 0x0b, | ||
| 28 | def_cfa = 0x0c, | ||
| 29 | def_cfa_register = 0x0d, | ||
| 30 | def_cfa_offset = 0x0e, | ||
| 31 | def_cfa_expression = 0x0f, | ||
| 32 | expression = 0x10, | ||
| 33 | offset_extended_sf = 0x11, | ||
| 34 | def_cfa_sf = 0x12, | ||
| 35 | def_cfa_offset_sf = 0x13, | ||
| 36 | val_offset = 0x14, | ||
| 37 | val_offset_sf = 0x15, | ||
| 38 | val_expression = 0x16, | ||
| 39 | |||
| 40 | // These opcodes encode an operand in the lower 6 bits of the opcode itself | ||
| 41 | pub const lo_inline = @intFromEnum(Opcode.advance_loc); | ||
| 42 | pub const hi_inline = @intFromEnum(Opcode.restore) | 0b111111; | ||
| 43 | |||
| 44 | // These opcodes are trailed by zero or more operands | ||
| 45 | pub const lo_reserved = @intFromEnum(Opcode.nop); | ||
| 46 | pub const hi_reserved = @intFromEnum(Opcode.val_expression); | ||
| 47 | |||
| 48 | // Vendor-specific opcodes | ||
| 49 | pub const lo_user = 0x1c; | ||
| 50 | pub const hi_user = 0x3f; | ||
| 51 | }; | ||
| 52 | |||
| 53 | const Operand = enum { | ||
| 54 | opcode_delta, | ||
| 55 | opcode_register, | ||
| 56 | uleb128_register, | ||
| 57 | uleb128_offset, | ||
| 58 | sleb128_offset, | ||
| 59 | address, | ||
| 60 | u8_delta, | ||
| 61 | u16_delta, | ||
| 62 | u32_delta, | ||
| 63 | block, | ||
| 64 | |||
| 65 | fn Storage(comptime self: Operand) type { | ||
| 66 | return switch (self) { | ||
| 67 | .opcode_delta, .opcode_register => u8, | ||
| 68 | .uleb128_register => u8, | ||
| 69 | .uleb128_offset => u64, | ||
| 70 | .sleb128_offset => i64, | ||
| 71 | .address => u64, | ||
| 72 | .u8_delta => u8, | ||
| 73 | .u16_delta => u16, | ||
| 74 | .u32_delta => u32, | ||
| 75 | .block => []const u8, | ||
| 76 | }; | ||
| 77 | } | ||
| 78 | |||
| 79 | fn read( | ||
| 80 | comptime self: Operand, | ||
| 81 | stream: *std.io.FixedBufferStream([]const u8), | ||
| 82 | opcode_value: ?u6, | ||
| 83 | addr_size_bytes: u8, | ||
| 84 | endian: std.builtin.Endian, | ||
| 85 | ) !Storage(self) { | ||
| 86 | const reader = stream.reader(); | ||
| 87 | return switch (self) { | ||
| 88 | .opcode_delta, .opcode_register => opcode_value orelse return error.InvalidOperand, | ||
| 89 | .uleb128_register => try leb.readULEB128(u8, reader), | ||
| 90 | .uleb128_offset => try leb.readULEB128(u64, reader), | ||
| 91 | .sleb128_offset => try leb.readILEB128(i64, reader), | ||
| 92 | .address => switch (addr_size_bytes) { | ||
| 93 | 2 => try reader.readInt(u16, endian), | ||
| 94 | 4 => try reader.readInt(u32, endian), | ||
| 95 | 8 => try reader.readInt(u64, endian), | ||
| 96 | else => return error.InvalidAddrSize, | ||
| 97 | }, | ||
| 98 | .u8_delta => try reader.readByte(), | ||
| 99 | .u16_delta => try reader.readInt(u16, endian), | ||
| 100 | .u32_delta => try reader.readInt(u32, endian), | ||
| 101 | .block => { | ||
| 102 | const block_len = try leb.readULEB128(usize, reader); | ||
| 103 | if (stream.pos + block_len > stream.buffer.len) return error.InvalidOperand; | ||
| 104 | |||
| 105 | const block = stream.buffer[stream.pos..][0..block_len]; | ||
| 106 | reader.context.pos += block_len; | ||
| 107 | |||
| 108 | return block; | ||
| 109 | }, | ||
| 110 | }; | ||
| 111 | } | ||
| 112 | }; | ||
| 113 | |||
| 114 | fn InstructionType(comptime definition: anytype) type { | ||
| 115 | const definition_type = @typeInfo(@TypeOf(definition)); | ||
| 116 | assert(definition_type == .Struct); | ||
| 117 | |||
| 118 | const definition_len = definition_type.Struct.fields.len; | ||
| 119 | comptime var fields: [definition_len]std.builtin.Type.StructField = undefined; | ||
| 120 | inline for (definition_type.Struct.fields, &fields) |definition_field, *operands_field| { | ||
| 121 | const opcode = std.enums.nameCast(Operand, @field(definition, definition_field.name)); | ||
| 122 | const storage_type = opcode.Storage(); | ||
| 123 | operands_field.* = .{ | ||
| 124 | .name = definition_field.name, | ||
| 125 | .type = storage_type, | ||
| 126 | .default_value = null, | ||
| 127 | .is_comptime = false, | ||
| 128 | .alignment = @alignOf(storage_type), | ||
| 129 | }; | ||
| 130 | } | ||
| 131 | |||
| 132 | const InstructionOperands = @Type(.{ | ||
| 133 | .Struct = .{ | ||
| 134 | .layout = .Auto, | ||
| 135 | .fields = &fields, | ||
| 136 | .decls = &.{}, | ||
| 137 | .is_tuple = false, | ||
| 138 | }, | ||
| 139 | }); | ||
| 140 | |||
| 141 | return struct { | ||
| 142 | const Self = @This(); | ||
| 143 | operands: InstructionOperands, | ||
| 144 | |||
| 145 | pub fn read( | ||
| 146 | stream: *std.io.FixedBufferStream([]const u8), | ||
| 147 | opcode_value: ?u6, | ||
| 148 | addr_size_bytes: u8, | ||
| 149 | endian: std.builtin.Endian, | ||
| 150 | ) !Self { | ||
| 151 | var operands: InstructionOperands = undefined; | ||
| 152 | inline for (definition_type.Struct.fields) |definition_field| { | ||
| 153 | const operand = comptime std.enums.nameCast(Operand, @field(definition, definition_field.name)); | ||
| 154 | @field(operands, definition_field.name) = try operand.read(stream, opcode_value, addr_size_bytes, endian); | ||
| 155 | } | ||
| 156 | |||
| 157 | return .{ .operands = operands }; | ||
| 158 | } | ||
| 159 | }; | ||
| 160 | } | ||
| 161 | |||
| 162 | pub const Instruction = union(Opcode) { | ||
| 163 | advance_loc: InstructionType(.{ .delta = .opcode_delta }), | ||
| 164 | offset: InstructionType(.{ .register = .opcode_register, .offset = .uleb128_offset }), | ||
| 165 | offset_extended: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }), | ||
| 166 | restore: InstructionType(.{ .register = .opcode_register }), | ||
| 167 | restore_extended: InstructionType(.{ .register = .uleb128_register }), | ||
| 168 | nop: InstructionType(.{}), | ||
| 169 | set_loc: InstructionType(.{ .address = .address }), | ||
| 170 | advance_loc1: InstructionType(.{ .delta = .u8_delta }), | ||
| 171 | advance_loc2: InstructionType(.{ .delta = .u16_delta }), | ||
| 172 | advance_loc4: InstructionType(.{ .delta = .u32_delta }), | ||
| 173 | undefined: InstructionType(.{ .register = .uleb128_register }), | ||
| 174 | same_value: InstructionType(.{ .register = .uleb128_register }), | ||
| 175 | register: InstructionType(.{ .register = .uleb128_register, .target_register = .uleb128_register }), | ||
| 176 | remember_state: InstructionType(.{}), | ||
| 177 | restore_state: InstructionType(.{}), | ||
| 178 | def_cfa: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }), | ||
| 179 | def_cfa_register: InstructionType(.{ .register = .uleb128_register }), | ||
| 180 | def_cfa_offset: InstructionType(.{ .offset = .uleb128_offset }), | ||
| 181 | def_cfa_expression: InstructionType(.{ .block = .block }), | ||
| 182 | expression: InstructionType(.{ .register = .uleb128_register, .block = .block }), | ||
| 183 | offset_extended_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }), | ||
| 184 | def_cfa_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }), | ||
| 185 | def_cfa_offset_sf: InstructionType(.{ .offset = .sleb128_offset }), | ||
| 186 | val_offset: InstructionType(.{ .register = .uleb128_register, .offset = .uleb128_offset }), | ||
| 187 | val_offset_sf: InstructionType(.{ .register = .uleb128_register, .offset = .sleb128_offset }), | ||
| 188 | val_expression: InstructionType(.{ .register = .uleb128_register, .block = .block }), | ||
| 189 | |||
| 190 | fn readOperands( | ||
| 191 | self: *Instruction, | ||
| 192 | stream: *std.io.FixedBufferStream([]const u8), | ||
| 193 | opcode_value: ?u6, | ||
| 194 | addr_size_bytes: u8, | ||
| 195 | endian: std.builtin.Endian, | ||
| 196 | ) !void { | ||
| 197 | switch (self.*) { | ||
| 198 | inline else => |*inst| inst.* = try @TypeOf(inst.*).read(stream, opcode_value, addr_size_bytes, endian), | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | pub fn read( | ||
| 203 | stream: *std.io.FixedBufferStream([]const u8), | ||
| 204 | addr_size_bytes: u8, | ||
| 205 | endian: std.builtin.Endian, | ||
| 206 | ) !Instruction { | ||
| 207 | return switch (try stream.reader().readByte()) { | ||
| 208 | inline Opcode.lo_inline...Opcode.hi_inline => |opcode| blk: { | ||
| 209 | const e: Opcode = @enumFromInt(opcode & 0b11000000); | ||
| 210 | var result = @unionInit(Instruction, @tagName(e), undefined); | ||
| 211 | try result.readOperands(stream, @as(u6, @intCast(opcode & 0b111111)), addr_size_bytes, endian); | ||
| 212 | break :blk result; | ||
| 213 | }, | ||
| 214 | inline Opcode.lo_reserved...Opcode.hi_reserved => |opcode| blk: { | ||
| 215 | const e: Opcode = @enumFromInt(opcode); | ||
| 216 | var result = @unionInit(Instruction, @tagName(e), undefined); | ||
| 217 | try result.readOperands(stream, null, addr_size_bytes, endian); | ||
| 218 | break :blk result; | ||
| 219 | }, | ||
| 220 | Opcode.lo_user...Opcode.hi_user => error.UnimplementedUserOpcode, | ||
| 221 | else => error.InvalidOpcode, | ||
| 222 | }; | ||
| 223 | } | ||
| 224 | }; | ||
| 225 | |||
| 226 | /// Since register rules are applied (usually) during a panic, | ||
| 227 | /// checked addition / subtraction is used so that we can return | ||
| 228 | /// an error and fall back to FP-based unwinding. | ||
| 229 | pub fn applyOffset(base: usize, offset: i64) !usize { | ||
| 230 | return if (offset >= 0) | ||
| 231 | try std.math.add(usize, base, @as(usize, @intCast(offset))) | ||
| 232 | else | ||
| 233 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); | ||
| 234 | } | ||
| 235 | |||
| 236 | /// This is a virtual machine that runs DWARF call frame instructions. | ||
| 237 | pub const VirtualMachine = struct { | ||
| 238 | /// See section 6.4.1 of the DWARF5 specification for details on each | ||
| 239 | const RegisterRule = union(enum) { | ||
| 240 | // The spec says that the default rule for each column is the undefined rule. | ||
| 241 | // However, it also allows ABI / compiler authors to specify alternate defaults, so | ||
| 242 | // there is a distinction made here. | ||
| 243 | default: void, | ||
| 244 | |||
| 245 | undefined: void, | ||
| 246 | same_value: void, | ||
| 247 | |||
| 248 | // offset(N) | ||
| 249 | offset: i64, | ||
| 250 | |||
| 251 | // val_offset(N) | ||
| 252 | val_offset: i64, | ||
| 253 | |||
| 254 | // register(R) | ||
| 255 | register: u8, | ||
| 256 | |||
| 257 | // expression(E) | ||
| 258 | expression: []const u8, | ||
| 259 | |||
| 260 | // val_expression(E) | ||
| 261 | val_expression: []const u8, | ||
| 262 | |||
| 263 | // Augmenter-defined rule | ||
| 264 | architectural: void, | ||
| 265 | }; | ||
| 266 | |||
| 267 | /// Each row contains unwinding rules for a set of registers. | ||
| 268 | pub const Row = struct { | ||
| 269 | /// Offset from `FrameDescriptionEntry.pc_begin` | ||
| 270 | offset: u64 = 0, | ||
| 271 | |||
| 272 | /// Special-case column that defines the CFA (Canonical Frame Address) rule. | ||
| 273 | /// The register field of this column defines the register that CFA is derived from. | ||
| 274 | cfa: Column = .{}, | ||
| 275 | |||
| 276 | /// The register fields in these columns define the register the rule applies to. | ||
| 277 | columns: ColumnRange = .{}, | ||
| 278 | |||
| 279 | /// Indicates that the next write to any column in this row needs to copy | ||
| 280 | /// the backing column storage first, as it may be referenced by previous rows. | ||
| 281 | copy_on_write: bool = false, | ||
| 282 | }; | ||
| 283 | |||
| 284 | pub const Column = struct { | ||
| 285 | register: ?u8 = null, | ||
| 286 | rule: RegisterRule = .{ .default = {} }, | ||
| 287 | |||
| 288 | /// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes) | ||
| 289 | pub fn resolveValue( | ||
| 290 | self: Column, | ||
| 291 | context: *dwarf.UnwindContext, | ||
| 292 | expression_context: dwarf.expressions.ExpressionContext, | ||
| 293 | out: []u8, | ||
| 294 | ) !void { | ||
| 295 | switch (self.rule) { | ||
| 296 | .default => { | ||
| 297 | const register = self.register orelse return error.InvalidRegister; | ||
| 298 | try abi.getRegDefaultValue(register, context, out); | ||
| 299 | }, | ||
| 300 | .undefined => { | ||
| 301 | @memset(out, undefined); | ||
| 302 | }, | ||
| 303 | .same_value => { | ||
| 304 | // TODO: This copy could be eliminated if callers always copy the state then call this function to update it | ||
| 305 | const register = self.register orelse return error.InvalidRegister; | ||
| 306 | const src = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 307 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 308 | @memcpy(out, src); | ||
| 309 | }, | ||
| 310 | .offset => |offset| { | ||
| 311 | if (context.cfa) |cfa| { | ||
| 312 | const addr = try applyOffset(cfa, offset); | ||
| 313 | if (expression_context.isValidMemory) |isValidMemory| if (!isValidMemory(addr)) return error.InvalidAddress; | ||
| 314 | const ptr: *const usize = @ptrFromInt(addr); | ||
| 315 | mem.writeIntSliceNative(usize, out, ptr.*); | ||
| 316 | } else return error.InvalidCFA; | ||
| 317 | }, | ||
| 318 | .val_offset => |offset| { | ||
| 319 | if (context.cfa) |cfa| { | ||
| 320 | mem.writeIntSliceNative(usize, out, try applyOffset(cfa, offset)); | ||
| 321 | } else return error.InvalidCFA; | ||
| 322 | }, | ||
| 323 | .register => |register| { | ||
| 324 | const src = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 325 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 326 | @memcpy(out, try abi.regBytes(context.thread_context, register, context.reg_context)); | ||
| 327 | }, | ||
| 328 | .expression => |expression| { | ||
| 329 | context.stack_machine.reset(); | ||
| 330 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 331 | const addr = if (value) |v| blk: { | ||
| 332 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 333 | break :blk v.generic; | ||
| 334 | } else return error.NoExpressionValue; | ||
| 335 | |||
| 336 | if (!context.isValidMemory(addr)) return error.InvalidExpressionAddress; | ||
| 337 | const ptr: *usize = @ptrFromInt(addr); | ||
| 338 | mem.writeIntSliceNative(usize, out, ptr.*); | ||
| 339 | }, | ||
| 340 | .val_expression => |expression| { | ||
| 341 | context.stack_machine.reset(); | ||
| 342 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 343 | if (value) |v| { | ||
| 344 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 345 | mem.writeIntSliceNative(usize, out, v.generic); | ||
| 346 | } else return error.NoExpressionValue; | ||
| 347 | }, | ||
| 348 | .architectural => return error.UnimplementedRegisterRule, | ||
| 349 | } | ||
| 350 | } | ||
| 351 | }; | ||
| 352 | |||
| 353 | const ColumnRange = struct { | ||
| 354 | /// Index into `columns` of the first column in this row. | ||
| 355 | start: usize = undefined, | ||
| 356 | len: u8 = 0, | ||
| 357 | }; | ||
| 358 | |||
| 359 | columns: std.ArrayListUnmanaged(Column) = .{}, | ||
| 360 | stack: std.ArrayListUnmanaged(ColumnRange) = .{}, | ||
| 361 | current_row: Row = .{}, | ||
| 362 | |||
| 363 | /// The result of executing the CIE's initial_instructions | ||
| 364 | cie_row: ?Row = null, | ||
| 365 | |||
| 366 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { | ||
| 367 | self.stack.deinit(allocator); | ||
| 368 | self.columns.deinit(allocator); | ||
| 369 | self.* = undefined; | ||
| 370 | } | ||
| 371 | |||
| 372 | pub fn reset(self: *VirtualMachine) void { | ||
| 373 | self.stack.clearRetainingCapacity(); | ||
| 374 | self.columns.clearRetainingCapacity(); | ||
| 375 | self.current_row = .{}; | ||
| 376 | self.cie_row = null; | ||
| 377 | } | ||
| 378 | |||
| 379 | /// Return a slice backed by the row's non-CFA columns | ||
| 380 | pub fn rowColumns(self: VirtualMachine, row: Row) []Column { | ||
| 381 | return self.columns.items[row.columns.start..][0..row.columns.len]; | ||
| 382 | } | ||
| 383 | |||
| 384 | /// Either retrieves or adds a column for `register` (non-CFA) in the current row. | ||
| 385 | fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { | ||
| 386 | for (self.rowColumns(self.current_row)) |*c| { | ||
| 387 | if (c.register == register) return c; | ||
| 388 | } | ||
| 389 | |||
| 390 | if (self.current_row.columns.len == 0) { | ||
| 391 | self.current_row.columns.start = self.columns.items.len; | ||
| 392 | } | ||
| 393 | self.current_row.columns.len += 1; | ||
| 394 | |||
| 395 | const column = try self.columns.addOne(allocator); | ||
| 396 | column.* = .{ | ||
| 397 | .register = register, | ||
| 398 | }; | ||
| 399 | |||
| 400 | return column; | ||
| 401 | } | ||
| 402 | |||
| 403 | /// Runs the CIE instructions, then the FDE instructions. Execution halts | ||
| 404 | /// once the row that corresponds to `pc` is known, and the row is returned. | ||
| 405 | pub fn runTo( | ||
| 406 | self: *VirtualMachine, | ||
| 407 | allocator: std.mem.Allocator, | ||
| 408 | pc: u64, | ||
| 409 | cie: dwarf.CommonInformationEntry, | ||
| 410 | fde: dwarf.FrameDescriptionEntry, | ||
| 411 | addr_size_bytes: u8, | ||
| 412 | endian: std.builtin.Endian, | ||
| 413 | ) !Row { | ||
| 414 | assert(self.cie_row == null); | ||
| 415 | if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; | ||
| 416 | |||
| 417 | var prev_row: Row = self.current_row; | ||
| 418 | |||
| 419 | var cie_stream = std.io.fixedBufferStream(cie.initial_instructions); | ||
| 420 | var fde_stream = std.io.fixedBufferStream(fde.instructions); | ||
| 421 | var streams = [_]*std.io.FixedBufferStream([]const u8){ | ||
| 422 | &cie_stream, | ||
| 423 | &fde_stream, | ||
| 424 | }; | ||
| 425 | |||
| 426 | for (&streams, 0..) |stream, i| { | ||
| 427 | while (stream.pos < stream.buffer.len) { | ||
| 428 | const instruction = try dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); | ||
| 429 | prev_row = try self.step(allocator, cie, i == 0, instruction); | ||
| 430 | if (pc < fde.pc_begin + self.current_row.offset) return prev_row; | ||
| 431 | } | ||
| 432 | } | ||
| 433 | |||
| 434 | return self.current_row; | ||
| 435 | } | ||
| 436 | |||
| 437 | pub fn runToNative( | ||
| 438 | self: *VirtualMachine, | ||
| 439 | allocator: std.mem.Allocator, | ||
| 440 | pc: u64, | ||
| 441 | cie: dwarf.CommonInformationEntry, | ||
| 442 | fde: dwarf.FrameDescriptionEntry, | ||
| 443 | ) !Row { | ||
| 444 | return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian()); | ||
| 445 | } | ||
| 446 | |||
| 447 | fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { | ||
| 448 | if (!self.current_row.copy_on_write) return; | ||
| 449 | |||
| 450 | const new_start = self.columns.items.len; | ||
| 451 | if (self.current_row.columns.len > 0) { | ||
| 452 | try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); | ||
| 453 | self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); | ||
| 454 | self.current_row.columns.start = new_start; | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | /// Executes a single instruction. | ||
| 459 | /// If this instruction is from the CIE, `is_initial` should be set. | ||
| 460 | /// Returns the value of `current_row` before executing this instruction. | ||
| 461 | pub fn step( | ||
| 462 | self: *VirtualMachine, | ||
| 463 | allocator: std.mem.Allocator, | ||
| 464 | cie: dwarf.CommonInformationEntry, | ||
| 465 | is_initial: bool, | ||
| 466 | instruction: Instruction, | ||
| 467 | ) !Row { | ||
| 468 | // CIE instructions must be run before FDE instructions | ||
| 469 | assert(!is_initial or self.cie_row == null); | ||
| 470 | if (!is_initial and self.cie_row == null) { | ||
| 471 | self.cie_row = self.current_row; | ||
| 472 | self.current_row.copy_on_write = true; | ||
| 473 | } | ||
| 474 | |||
| 475 | const prev_row = self.current_row; | ||
| 476 | switch (instruction) { | ||
| 477 | .set_loc => |i| { | ||
| 478 | if (i.operands.address <= self.current_row.offset) return error.InvalidOperation; | ||
| 479 | // TODO: Check cie.segment_selector_size != 0 for DWARFV4 | ||
| 480 | self.current_row.offset = i.operands.address; | ||
| 481 | }, | ||
| 482 | inline .advance_loc, | ||
| 483 | .advance_loc1, | ||
| 484 | .advance_loc2, | ||
| 485 | .advance_loc4, | ||
| 486 | => |i| { | ||
| 487 | self.current_row.offset += i.operands.delta * cie.code_alignment_factor; | ||
| 488 | self.current_row.copy_on_write = true; | ||
| 489 | }, | ||
| 490 | inline .offset, | ||
| 491 | .offset_extended, | ||
| 492 | .offset_extended_sf, | ||
| 493 | => |i| { | ||
| 494 | try self.resolveCopyOnWrite(allocator); | ||
| 495 | const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 496 | column.rule = .{ .offset = @as(i64, @intCast(i.operands.offset)) * cie.data_alignment_factor }; | ||
| 497 | }, | ||
| 498 | inline .restore, | ||
| 499 | .restore_extended, | ||
| 500 | => |i| { | ||
| 501 | try self.resolveCopyOnWrite(allocator); | ||
| 502 | if (self.cie_row) |cie_row| { | ||
| 503 | const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 504 | column.rule = for (self.rowColumns(cie_row)) |cie_column| { | ||
| 505 | if (cie_column.register == i.operands.register) break cie_column.rule; | ||
| 506 | } else .{ .default = {} }; | ||
| 507 | } else return error.InvalidOperation; | ||
| 508 | }, | ||
| 509 | .nop => {}, | ||
| 510 | .undefined => |i| { | ||
| 511 | try self.resolveCopyOnWrite(allocator); | ||
| 512 | const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 513 | column.rule = .{ .undefined = {} }; | ||
| 514 | }, | ||
| 515 | .same_value => |i| { | ||
| 516 | try self.resolveCopyOnWrite(allocator); | ||
| 517 | const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 518 | column.rule = .{ .same_value = {} }; | ||
| 519 | }, | ||
| 520 | .register => |i| { | ||
| 521 | try self.resolveCopyOnWrite(allocator); | ||
| 522 | const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 523 | column.rule = .{ .register = i.operands.target_register }; | ||
| 524 | }, | ||
| 525 | .remember_state => { | ||
| 526 | try self.stack.append(allocator, self.current_row.columns); | ||
| 527 | self.current_row.copy_on_write = true; | ||
| 528 | }, | ||
| 529 | .restore_state => { | ||
| 530 | const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; | ||
| 531 | self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); | ||
| 532 | try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); | ||
| 533 | |||
| 534 | self.current_row.columns.start = self.columns.items.len; | ||
| 535 | self.current_row.columns.len = restored_columns.len; | ||
| 536 | self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); | ||
| 537 | }, | ||
| 538 | .def_cfa => |i| { | ||
| 539 | try self.resolveCopyOnWrite(allocator); | ||
| 540 | self.current_row.cfa = .{ | ||
| 541 | .register = i.operands.register, | ||
| 542 | .rule = .{ .val_offset = @intCast(i.operands.offset) }, | ||
| 543 | }; | ||
| 544 | }, | ||
| 545 | .def_cfa_sf => |i| { | ||
| 546 | try self.resolveCopyOnWrite(allocator); | ||
| 547 | self.current_row.cfa = .{ | ||
| 548 | .register = i.operands.register, | ||
| 549 | .rule = .{ .val_offset = i.operands.offset * cie.data_alignment_factor }, | ||
| 550 | }; | ||
| 551 | }, | ||
| 552 | .def_cfa_register => |i| { | ||
| 553 | try self.resolveCopyOnWrite(allocator); | ||
| 554 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 555 | self.current_row.cfa.register = i.operands.register; | ||
| 556 | }, | ||
| 557 | .def_cfa_offset => |i| { | ||
| 558 | try self.resolveCopyOnWrite(allocator); | ||
| 559 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 560 | self.current_row.cfa.rule = .{ | ||
| 561 | .val_offset = @intCast(i.operands.offset), | ||
| 562 | }; | ||
| 563 | }, | ||
| 564 | .def_cfa_offset_sf => |i| { | ||
| 565 | try self.resolveCopyOnWrite(allocator); | ||
| 566 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 567 | self.current_row.cfa.rule = .{ | ||
| 568 | .val_offset = i.operands.offset * cie.data_alignment_factor, | ||
| 569 | }; | ||
| 570 | }, | ||
| 571 | .def_cfa_expression => |i| { | ||
| 572 | try self.resolveCopyOnWrite(allocator); | ||
| 573 | self.current_row.cfa.register = undefined; | ||
| 574 | self.current_row.cfa.rule = .{ | ||
| 575 | .expression = i.operands.block, | ||
| 576 | }; | ||
| 577 | }, | ||
| 578 | .expression => |i| { | ||
| 579 | try self.resolveCopyOnWrite(allocator); | ||
| 580 | const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 581 | column.rule = .{ | ||
| 582 | .expression = i.operands.block, | ||
| 583 | }; | ||
| 584 | }, | ||
| 585 | .val_offset => |i| { | ||
| 586 | try self.resolveCopyOnWrite(allocator); | ||
| 587 | const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 588 | column.rule = .{ | ||
| 589 | .val_offset = @as(i64, @intCast(i.operands.offset)) * cie.data_alignment_factor, | ||
| 590 | }; | ||
| 591 | }, | ||
| 592 | .val_offset_sf => |i| { | ||
| 593 | try self.resolveCopyOnWrite(allocator); | ||
| 594 | const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 595 | column.rule = .{ | ||
| 596 | .val_offset = i.operands.offset * cie.data_alignment_factor, | ||
| 597 | }; | ||
| 598 | }, | ||
| 599 | .val_expression => |i| { | ||
| 600 | try self.resolveCopyOnWrite(allocator); | ||
| 601 | const column = try self.getOrAddColumn(allocator, i.operands.register); | ||
| 602 | column.rule = .{ | ||
| 603 | .val_expression = i.operands.block, | ||
| 604 | }; | ||
| 605 | }, | ||
| 606 | } | ||
| 607 | |||
| 608 | return prev_row; | ||
| 609 | } | ||
| 610 | }; | ||
lib/std/dwarf/expressions.zig created+1639| ... | @@ -0,0 +1,1639 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const OP = @import("OP.zig"); | ||
| 4 | const leb = std.leb; | ||
| 5 | const dwarf = std.dwarf; | ||
| 6 | const abi = dwarf.abi; | ||
| 7 | const mem = std.mem; | ||
| 8 | const assert = std.debug.assert; | ||
| 9 | |||
| 10 | /// Expressions can be evaluated in different contexts, each requiring its own set of inputs. | ||
| 11 | /// Callers should specify all the fields relevant to their context. If a field is required | ||
| 12 | /// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned. | ||
| 13 | pub const ExpressionContext = struct { | ||
| 14 | /// This expression is from a DWARF64 section | ||
| 15 | is_64: bool = false, | ||
| 16 | |||
| 17 | /// If specified, any addresses will pass through this function before being acccessed | ||
| 18 | isValidMemory: ?*const fn (address: usize) bool = null, | ||
| 19 | |||
| 20 | /// The compilation unit this expression relates to, if any | ||
| 21 | compile_unit: ?*const dwarf.CompileUnit = null, | ||
| 22 | |||
| 23 | /// When evaluating a user-presented expression, this is the address of the object being evaluated | ||
| 24 | object_address: ?*const anyopaque = null, | ||
| 25 | |||
| 26 | /// .debug_addr section | ||
| 27 | debug_addr: ?[]const u8 = null, | ||
| 28 | |||
| 29 | /// Thread context | ||
| 30 | thread_context: ?*std.debug.ThreadContext = null, | ||
| 31 | reg_context: ?abi.RegisterContext = null, | ||
| 32 | |||
| 33 | /// Call frame address, if in a CFI context | ||
| 34 | cfa: ?usize = null, | ||
| 35 | |||
| 36 | /// This expression is a sub-expression from an OP.entry_value instruction | ||
| 37 | entry_value_context: bool = false, | ||
| 38 | }; | ||
| 39 | |||
| 40 | pub const ExpressionOptions = struct { | ||
| 41 | /// The address size of the target architecture | ||
| 42 | addr_size: u8 = @sizeOf(usize), | ||
| 43 | |||
| 44 | /// Endianess of the target architecture | ||
| 45 | endian: std.builtin.Endian = builtin.target.cpu.arch.endian(), | ||
| 46 | |||
| 47 | /// Restrict the stack machine to a subset of opcodes used in call frame instructions | ||
| 48 | call_frame_context: bool = false, | ||
| 49 | }; | ||
| 50 | |||
| 51 | // Explcitly defined to support executing sub-expressions | ||
| 52 | pub const ExpressionError = error{ | ||
| 53 | UnimplementedExpressionCall, | ||
| 54 | UnimplementedOpcode, | ||
| 55 | UnimplementedUserOpcode, | ||
| 56 | UnimplementedTypedComparison, | ||
| 57 | UnimplementedTypeConversion, | ||
| 58 | |||
| 59 | UnknownExpressionOpcode, | ||
| 60 | |||
| 61 | IncompleteExpressionContext, | ||
| 62 | |||
| 63 | InvalidCFAOpcode, | ||
| 64 | InvalidExpression, | ||
| 65 | InvalidFrameBase, | ||
| 66 | InvalidIntegralTypeSize, | ||
| 67 | InvalidRegister, | ||
| 68 | InvalidSubExpression, | ||
| 69 | InvalidTypeLength, | ||
| 70 | |||
| 71 | TruncatedIntegralType, | ||
| 72 | } || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; | ||
| 73 | |||
| 74 | /// A stack machine that can decode and run DWARF expressions. | ||
| 75 | /// Expressions can be decoded for non-native address size and endianness, | ||
| 76 | /// but can only be executed if the current target matches the configuration. | ||
| 77 | pub fn StackMachine(comptime options: ExpressionOptions) type { | ||
| 78 | const addr_type = switch (options.addr_size) { | ||
| 79 | 2 => u16, | ||
| 80 | 4 => u32, | ||
| 81 | 8 => u64, | ||
| 82 | else => @compileError("Unsupported address size of " ++ options.addr_size), | ||
| 83 | }; | ||
| 84 | |||
| 85 | const addr_type_signed = switch (options.addr_size) { | ||
| 86 | 2 => i16, | ||
| 87 | 4 => i32, | ||
| 88 | 8 => i64, | ||
| 89 | else => @compileError("Unsupported address size of " ++ options.addr_size), | ||
| 90 | }; | ||
| 91 | |||
| 92 | return struct { | ||
| 93 | const Self = @This(); | ||
| 94 | |||
| 95 | const Operand = union(enum) { | ||
| 96 | generic: addr_type, | ||
| 97 | register: u8, | ||
| 98 | type_size: u8, | ||
| 99 | branch_offset: i16, | ||
| 100 | base_register: struct { | ||
| 101 | base_register: u8, | ||
| 102 | offset: i64, | ||
| 103 | }, | ||
| 104 | composite_location: struct { | ||
| 105 | size: u64, | ||
| 106 | offset: i64, | ||
| 107 | }, | ||
| 108 | block: []const u8, | ||
| 109 | register_type: struct { | ||
| 110 | register: u8, | ||
| 111 | type_offset: addr_type, | ||
| 112 | }, | ||
| 113 | const_type: struct { | ||
| 114 | type_offset: addr_type, | ||
| 115 | value_bytes: []const u8, | ||
| 116 | }, | ||
| 117 | deref_type: struct { | ||
| 118 | size: u8, | ||
| 119 | type_offset: addr_type, | ||
| 120 | }, | ||
| 121 | }; | ||
| 122 | |||
| 123 | const Value = union(enum) { | ||
| 124 | generic: addr_type, | ||
| 125 | |||
| 126 | // Typed value with a maximum size of a register | ||
| 127 | regval_type: struct { | ||
| 128 | // Offset of DW_TAG_base_type DIE | ||
| 129 | type_offset: addr_type, | ||
| 130 | type_size: u8, | ||
| 131 | value: addr_type, | ||
| 132 | }, | ||
| 133 | |||
| 134 | // Typed value specified directly in the instruction stream | ||
| 135 | const_type: struct { | ||
| 136 | // Offset of DW_TAG_base_type DIE | ||
| 137 | type_offset: addr_type, | ||
| 138 | // Backed by the instruction stream | ||
| 139 | value_bytes: []const u8, | ||
| 140 | }, | ||
| 141 | |||
| 142 | pub fn asIntegral(self: Value) !addr_type { | ||
| 143 | return switch (self) { | ||
| 144 | .generic => |v| v, | ||
| 145 | |||
| 146 | // TODO: For these two prongs, look up the type and assert it's integral? | ||
| 147 | .regval_type => |regval_type| regval_type.value, | ||
| 148 | .const_type => |const_type| { | ||
| 149 | const value: u64 = switch (const_type.value_bytes.len) { | ||
| 150 | 1 => mem.readIntSliceNative(u8, const_type.value_bytes), | ||
| 151 | 2 => mem.readIntSliceNative(u16, const_type.value_bytes), | ||
| 152 | 4 => mem.readIntSliceNative(u32, const_type.value_bytes), | ||
| 153 | 8 => mem.readIntSliceNative(u64, const_type.value_bytes), | ||
| 154 | else => return error.InvalidIntegralTypeSize, | ||
| 155 | }; | ||
| 156 | |||
| 157 | return std.math.cast(addr_type, value) orelse error.TruncatedIntegralType; | ||
| 158 | }, | ||
| 159 | }; | ||
| 160 | } | ||
| 161 | }; | ||
| 162 | |||
| 163 | stack: std.ArrayListUnmanaged(Value) = .{}, | ||
| 164 | |||
| 165 | pub fn reset(self: *Self) void { | ||
| 166 | self.stack.clearRetainingCapacity(); | ||
| 167 | } | ||
| 168 | |||
| 169 | pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { | ||
| 170 | self.stack.deinit(allocator); | ||
| 171 | } | ||
| 172 | |||
| 173 | fn generic(value: anytype) Operand { | ||
| 174 | const int_info = @typeInfo(@TypeOf(value)).Int; | ||
| 175 | if (@sizeOf(@TypeOf(value)) > options.addr_size) { | ||
| 176 | return .{ .generic = switch (int_info.signedness) { | ||
| 177 | .signed => @bitCast(@as(addr_type_signed, @truncate(value))), | ||
| 178 | .unsigned => @truncate(value), | ||
| 179 | } }; | ||
| 180 | } else { | ||
| 181 | return .{ .generic = switch (int_info.signedness) { | ||
| 182 | .signed => @bitCast(@as(addr_type_signed, @intCast(value))), | ||
| 183 | .unsigned => @intCast(value), | ||
| 184 | } }; | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8, context: ExpressionContext) !?Operand { | ||
| 189 | const reader = stream.reader(); | ||
| 190 | return switch (opcode) { | ||
| 191 | OP.addr => generic(try reader.readInt(addr_type, options.endian)), | ||
| 192 | OP.call_ref => if (context.is_64) | ||
| 193 | generic(try reader.readInt(u64, options.endian)) | ||
| 194 | else | ||
| 195 | generic(try reader.readInt(u32, options.endian)), | ||
| 196 | OP.const1u, | ||
| 197 | OP.pick, | ||
| 198 | => generic(try reader.readByte()), | ||
| 199 | OP.deref_size, | ||
| 200 | OP.xderef_size, | ||
| 201 | => .{ .type_size = try reader.readByte() }, | ||
| 202 | OP.const1s => generic(try reader.readByteSigned()), | ||
| 203 | OP.const2u, | ||
| 204 | OP.call2, | ||
| 205 | => generic(try reader.readInt(u16, options.endian)), | ||
| 206 | OP.call4 => generic(try reader.readInt(u32, options.endian)), | ||
| 207 | OP.const2s => generic(try reader.readInt(i16, options.endian)), | ||
| 208 | OP.bra, | ||
| 209 | OP.skip, | ||
| 210 | => .{ .branch_offset = try reader.readInt(i16, options.endian) }, | ||
| 211 | OP.const4u => generic(try reader.readInt(u32, options.endian)), | ||
| 212 | OP.const4s => generic(try reader.readInt(i32, options.endian)), | ||
| 213 | OP.const8u => generic(try reader.readInt(u64, options.endian)), | ||
| 214 | OP.const8s => generic(try reader.readInt(i64, options.endian)), | ||
| 215 | OP.constu, | ||
| 216 | OP.plus_uconst, | ||
| 217 | OP.addrx, | ||
| 218 | OP.constx, | ||
| 219 | OP.convert, | ||
| 220 | OP.reinterpret, | ||
| 221 | => generic(try leb.readULEB128(u64, reader)), | ||
| 222 | OP.consts, | ||
| 223 | OP.fbreg, | ||
| 224 | => generic(try leb.readILEB128(i64, reader)), | ||
| 225 | OP.lit0...OP.lit31 => |n| generic(n - OP.lit0), | ||
| 226 | OP.reg0...OP.reg31 => |n| .{ .register = n - OP.reg0 }, | ||
| 227 | OP.breg0...OP.breg31 => |n| .{ .base_register = .{ | ||
| 228 | .base_register = n - OP.breg0, | ||
| 229 | .offset = try leb.readILEB128(i64, reader), | ||
| 230 | } }, | ||
| 231 | OP.regx => .{ .register = try leb.readULEB128(u8, reader) }, | ||
| 232 | OP.bregx => blk: { | ||
| 233 | const base_register = try leb.readULEB128(u8, reader); | ||
| 234 | const offset = try leb.readILEB128(i64, reader); | ||
| 235 | break :blk .{ .base_register = .{ | ||
| 236 | .base_register = base_register, | ||
| 237 | .offset = offset, | ||
| 238 | } }; | ||
| 239 | }, | ||
| 240 | OP.regval_type => blk: { | ||
| 241 | const register = try leb.readULEB128(u8, reader); | ||
| 242 | const type_offset = try leb.readULEB128(addr_type, reader); | ||
| 243 | break :blk .{ .register_type = .{ | ||
| 244 | .register = register, | ||
| 245 | .type_offset = type_offset, | ||
| 246 | } }; | ||
| 247 | }, | ||
| 248 | OP.piece => .{ | ||
| 249 | .composite_location = .{ | ||
| 250 | .size = try leb.readULEB128(u8, reader), | ||
| 251 | .offset = 0, | ||
| 252 | }, | ||
| 253 | }, | ||
| 254 | OP.bit_piece => blk: { | ||
| 255 | const size = try leb.readULEB128(u8, reader); | ||
| 256 | const offset = try leb.readILEB128(i64, reader); | ||
| 257 | break :blk .{ .composite_location = .{ | ||
| 258 | .size = size, | ||
| 259 | .offset = offset, | ||
| 260 | } }; | ||
| 261 | }, | ||
| 262 | OP.implicit_value, OP.entry_value => blk: { | ||
| 263 | const size = try leb.readULEB128(u8, reader); | ||
| 264 | if (stream.pos + size > stream.buffer.len) return error.InvalidExpression; | ||
| 265 | const block = stream.buffer[stream.pos..][0..size]; | ||
| 266 | stream.pos += size; | ||
| 267 | break :blk .{ | ||
| 268 | .block = block, | ||
| 269 | }; | ||
| 270 | }, | ||
| 271 | OP.const_type => blk: { | ||
| 272 | const type_offset = try leb.readULEB128(addr_type, reader); | ||
| 273 | const size = try reader.readByte(); | ||
| 274 | if (stream.pos + size > stream.buffer.len) return error.InvalidExpression; | ||
| 275 | const value_bytes = stream.buffer[stream.pos..][0..size]; | ||
| 276 | stream.pos += size; | ||
| 277 | break :blk .{ .const_type = .{ | ||
| 278 | .type_offset = type_offset, | ||
| 279 | .value_bytes = value_bytes, | ||
| 280 | } }; | ||
| 281 | }, | ||
| 282 | OP.deref_type, | ||
| 283 | OP.xderef_type, | ||
| 284 | => .{ | ||
| 285 | .deref_type = .{ | ||
| 286 | .size = try reader.readByte(), | ||
| 287 | .type_offset = try leb.readULEB128(addr_type, reader), | ||
| 288 | }, | ||
| 289 | }, | ||
| 290 | OP.lo_user...OP.hi_user => return error.UnimplementedUserOpcode, | ||
| 291 | else => null, | ||
| 292 | }; | ||
| 293 | } | ||
| 294 | |||
| 295 | pub fn run( | ||
| 296 | self: *Self, | ||
| 297 | expression: []const u8, | ||
| 298 | allocator: std.mem.Allocator, | ||
| 299 | context: ExpressionContext, | ||
| 300 | initial_value: ?usize, | ||
| 301 | ) ExpressionError!?Value { | ||
| 302 | if (initial_value) |i| try self.stack.append(allocator, .{ .generic = i }); | ||
| 303 | var stream = std.io.fixedBufferStream(expression); | ||
| 304 | while (try self.step(&stream, allocator, context)) {} | ||
| 305 | if (self.stack.items.len == 0) return null; | ||
| 306 | return self.stack.items[self.stack.items.len - 1]; | ||
| 307 | } | ||
| 308 | |||
| 309 | /// Reads an opcode and its operands from `stream`, then executes it | ||
| 310 | pub fn step( | ||
| 311 | self: *Self, | ||
| 312 | stream: *std.io.FixedBufferStream([]const u8), | ||
| 313 | allocator: std.mem.Allocator, | ||
| 314 | context: ExpressionContext, | ||
| 315 | ) ExpressionError!bool { | ||
| 316 | if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian()) | ||
| 317 | @compileError("Execution of non-native address sizes / endianness is not supported"); | ||
| 318 | |||
| 319 | const opcode = try stream.reader().readByte(); | ||
| 320 | if (options.call_frame_context and !isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; | ||
| 321 | switch (opcode) { | ||
| 322 | |||
| 323 | // 2.5.1.1: Literal Encodings | ||
| 324 | OP.lit0...OP.lit31, | ||
| 325 | OP.addr, | ||
| 326 | OP.const1u, | ||
| 327 | OP.const2u, | ||
| 328 | OP.const4u, | ||
| 329 | OP.const8u, | ||
| 330 | OP.const1s, | ||
| 331 | OP.const2s, | ||
| 332 | OP.const4s, | ||
| 333 | OP.const8s, | ||
| 334 | OP.constu, | ||
| 335 | OP.consts, | ||
| 336 | => try self.stack.append(allocator, .{ .generic = (try readOperand(stream, opcode, context)).?.generic }), | ||
| 337 | |||
| 338 | OP.const_type => { | ||
| 339 | const const_type = (try readOperand(stream, opcode, context)).?.const_type; | ||
| 340 | try self.stack.append(allocator, .{ .const_type = .{ | ||
| 341 | .type_offset = const_type.type_offset, | ||
| 342 | .value_bytes = const_type.value_bytes, | ||
| 343 | } }); | ||
| 344 | }, | ||
| 345 | |||
| 346 | OP.addrx, | ||
| 347 | OP.constx, | ||
| 348 | => { | ||
| 349 | if (context.compile_unit == null) return error.IncompleteExpressionContext; | ||
| 350 | if (context.debug_addr == null) return error.IncompleteExpressionContext; | ||
| 351 | const debug_addr_index = (try readOperand(stream, opcode, context)).?.generic; | ||
| 352 | const offset = context.compile_unit.?.addr_base + debug_addr_index; | ||
| 353 | if (offset >= context.debug_addr.?.len) return error.InvalidExpression; | ||
| 354 | const value = mem.readIntSliceNative(usize, context.debug_addr.?[offset..][0..@sizeOf(usize)]); | ||
| 355 | try self.stack.append(allocator, .{ .generic = value }); | ||
| 356 | }, | ||
| 357 | |||
| 358 | // 2.5.1.2: Register Values | ||
| 359 | OP.fbreg => { | ||
| 360 | if (context.compile_unit == null) return error.IncompleteExpressionContext; | ||
| 361 | if (context.compile_unit.?.frame_base == null) return error.IncompleteExpressionContext; | ||
| 362 | |||
| 363 | const offset: i64 = @intCast((try readOperand(stream, opcode, context)).?.generic); | ||
| 364 | _ = offset; | ||
| 365 | |||
| 366 | switch (context.compile_unit.?.frame_base.?.*) { | ||
| 367 | .ExprLoc => { | ||
| 368 | // TODO: Run this expression in a nested stack machine | ||
| 369 | return error.UnimplementedOpcode; | ||
| 370 | }, | ||
| 371 | .LocListOffset => { | ||
| 372 | // TODO: Read value from .debug_loclists | ||
| 373 | return error.UnimplementedOpcode; | ||
| 374 | }, | ||
| 375 | .SecOffset => { | ||
| 376 | // TODO: Read value from .debug_loclists | ||
| 377 | return error.UnimplementedOpcode; | ||
| 378 | }, | ||
| 379 | else => return error.InvalidFrameBase, | ||
| 380 | } | ||
| 381 | }, | ||
| 382 | OP.breg0...OP.breg31, | ||
| 383 | OP.bregx, | ||
| 384 | => { | ||
| 385 | if (context.thread_context == null) return error.IncompleteExpressionContext; | ||
| 386 | |||
| 387 | const base_register = (try readOperand(stream, opcode, context)).?.base_register; | ||
| 388 | var value: i64 = @intCast(mem.readIntSliceNative(usize, try abi.regBytes( | ||
| 389 | context.thread_context.?, | ||
| 390 | base_register.base_register, | ||
| 391 | context.reg_context, | ||
| 392 | ))); | ||
| 393 | value += base_register.offset; | ||
| 394 | try self.stack.append(allocator, .{ .generic = @intCast(value) }); | ||
| 395 | }, | ||
| 396 | OP.regval_type => { | ||
| 397 | const register_type = (try readOperand(stream, opcode, context)).?.register_type; | ||
| 398 | const value = mem.readIntSliceNative(usize, try abi.regBytes( | ||
| 399 | context.thread_context.?, | ||
| 400 | register_type.register, | ||
| 401 | context.reg_context, | ||
| 402 | )); | ||
| 403 | try self.stack.append(allocator, .{ | ||
| 404 | .regval_type = .{ | ||
| 405 | .type_offset = register_type.type_offset, | ||
| 406 | .type_size = @sizeOf(addr_type), | ||
| 407 | .value = value, | ||
| 408 | }, | ||
| 409 | }); | ||
| 410 | }, | ||
| 411 | |||
| 412 | // 2.5.1.3: Stack Operations | ||
| 413 | OP.dup => { | ||
| 414 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 415 | try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1]); | ||
| 416 | }, | ||
| 417 | OP.drop => { | ||
| 418 | _ = self.stack.pop(); | ||
| 419 | }, | ||
| 420 | OP.pick, OP.over => { | ||
| 421 | const stack_index = if (opcode == OP.over) 1 else (try readOperand(stream, opcode, context)).?.generic; | ||
| 422 | if (stack_index >= self.stack.items.len) return error.InvalidExpression; | ||
| 423 | try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1 - stack_index]); | ||
| 424 | }, | ||
| 425 | OP.swap => { | ||
| 426 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 427 | mem.swap(Value, &self.stack.items[self.stack.items.len - 1], &self.stack.items[self.stack.items.len - 2]); | ||
| 428 | }, | ||
| 429 | OP.rot => { | ||
| 430 | if (self.stack.items.len < 3) return error.InvalidExpression; | ||
| 431 | const first = self.stack.items[self.stack.items.len - 1]; | ||
| 432 | self.stack.items[self.stack.items.len - 1] = self.stack.items[self.stack.items.len - 2]; | ||
| 433 | self.stack.items[self.stack.items.len - 2] = self.stack.items[self.stack.items.len - 3]; | ||
| 434 | self.stack.items[self.stack.items.len - 3] = first; | ||
| 435 | }, | ||
| 436 | OP.deref, | ||
| 437 | OP.xderef, | ||
| 438 | OP.deref_size, | ||
| 439 | OP.xderef_size, | ||
| 440 | OP.deref_type, | ||
| 441 | OP.xderef_type, | ||
| 442 | => { | ||
| 443 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 444 | var addr = try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 445 | const addr_space_identifier: ?usize = switch (opcode) { | ||
| 446 | OP.xderef, | ||
| 447 | OP.xderef_size, | ||
| 448 | OP.xderef_type, | ||
| 449 | => blk: { | ||
| 450 | _ = self.stack.pop(); | ||
| 451 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 452 | break :blk try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 453 | }, | ||
| 454 | else => null, | ||
| 455 | }; | ||
| 456 | |||
| 457 | // Usage of addr_space_identifier in the address calculation is implementation defined. | ||
| 458 | // This code will need to be updated to handle any architectures that utilize this. | ||
| 459 | _ = addr_space_identifier; | ||
| 460 | |||
| 461 | if (context.isValidMemory) |isValidMemory| if (!isValidMemory(addr)) return error.InvalidExpression; | ||
| 462 | |||
| 463 | const operand = try readOperand(stream, opcode, context); | ||
| 464 | const size = switch (opcode) { | ||
| 465 | OP.deref, | ||
| 466 | OP.xderef, | ||
| 467 | => @sizeOf(addr_type), | ||
| 468 | OP.deref_size, | ||
| 469 | OP.xderef_size, | ||
| 470 | => operand.?.type_size, | ||
| 471 | OP.deref_type, | ||
| 472 | OP.xderef_type, | ||
| 473 | => operand.?.deref_type.size, | ||
| 474 | else => unreachable, | ||
| 475 | }; | ||
| 476 | |||
| 477 | const value: addr_type = std.math.cast(addr_type, @as(u64, switch (size) { | ||
| 478 | 1 => @as(*const u8, @ptrFromInt(addr)).*, | ||
| 479 | 2 => @as(*const u16, @ptrFromInt(addr)).*, | ||
| 480 | 4 => @as(*const u32, @ptrFromInt(addr)).*, | ||
| 481 | 8 => @as(*const u64, @ptrFromInt(addr)).*, | ||
| 482 | else => return error.InvalidExpression, | ||
| 483 | })) orelse return error.InvalidExpression; | ||
| 484 | |||
| 485 | switch (opcode) { | ||
| 486 | OP.deref_type, | ||
| 487 | OP.xderef_type, | ||
| 488 | => { | ||
| 489 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 490 | .regval_type = .{ | ||
| 491 | .type_offset = operand.?.deref_type.type_offset, | ||
| 492 | .type_size = operand.?.deref_type.size, | ||
| 493 | .value = value, | ||
| 494 | }, | ||
| 495 | }; | ||
| 496 | }, | ||
| 497 | else => { | ||
| 498 | self.stack.items[self.stack.items.len - 1] = .{ .generic = value }; | ||
| 499 | }, | ||
| 500 | } | ||
| 501 | }, | ||
| 502 | OP.push_object_address => { | ||
| 503 | // In sub-expressions, `push_object_address` is not meaningful (as per the | ||
| 504 | // spec), so treat it like a nop | ||
| 505 | if (!context.entry_value_context) { | ||
| 506 | if (context.object_address == null) return error.IncompleteExpressionContext; | ||
| 507 | try self.stack.append(allocator, .{ .generic = @intFromPtr(context.object_address.?) }); | ||
| 508 | } | ||
| 509 | }, | ||
| 510 | OP.form_tls_address => { | ||
| 511 | return error.UnimplementedOpcode; | ||
| 512 | }, | ||
| 513 | OP.call_frame_cfa => { | ||
| 514 | if (context.cfa) |cfa| { | ||
| 515 | try self.stack.append(allocator, .{ .generic = cfa }); | ||
| 516 | } else return error.IncompleteExpressionContext; | ||
| 517 | }, | ||
| 518 | |||
| 519 | // 2.5.1.4: Arithmetic and Logical Operations | ||
| 520 | OP.abs => { | ||
| 521 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 522 | const value: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 523 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 524 | .generic = std.math.absCast(value), | ||
| 525 | }; | ||
| 526 | }, | ||
| 527 | OP.@"and" => { | ||
| 528 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 529 | const a = try self.stack.pop().asIntegral(); | ||
| 530 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 531 | .generic = a & try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 532 | }; | ||
| 533 | }, | ||
| 534 | OP.div => { | ||
| 535 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 536 | const a: isize = @bitCast(try self.stack.pop().asIntegral()); | ||
| 537 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 538 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 539 | .generic = @bitCast(try std.math.divTrunc(isize, b, a)), | ||
| 540 | }; | ||
| 541 | }, | ||
| 542 | OP.minus => { | ||
| 543 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 544 | const b = try self.stack.pop().asIntegral(); | ||
| 545 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 546 | .generic = try std.math.sub(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b), | ||
| 547 | }; | ||
| 548 | }, | ||
| 549 | OP.mod => { | ||
| 550 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 551 | const a: isize = @bitCast(try self.stack.pop().asIntegral()); | ||
| 552 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 553 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 554 | .generic = @bitCast(@mod(b, a)), | ||
| 555 | }; | ||
| 556 | }, | ||
| 557 | OP.mul => { | ||
| 558 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 559 | const a: isize = @bitCast(try self.stack.pop().asIntegral()); | ||
| 560 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 561 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 562 | .generic = @bitCast(@mulWithOverflow(a, b)[0]), | ||
| 563 | }; | ||
| 564 | }, | ||
| 565 | OP.neg => { | ||
| 566 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 567 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 568 | .generic = @bitCast( | ||
| 569 | try std.math.negate( | ||
| 570 | @as(isize, @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral())), | ||
| 571 | ), | ||
| 572 | ), | ||
| 573 | }; | ||
| 574 | }, | ||
| 575 | OP.not => { | ||
| 576 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 577 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 578 | .generic = ~try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 579 | }; | ||
| 580 | }, | ||
| 581 | OP.@"or" => { | ||
| 582 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 583 | const a = try self.stack.pop().asIntegral(); | ||
| 584 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 585 | .generic = a | try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 586 | }; | ||
| 587 | }, | ||
| 588 | OP.plus => { | ||
| 589 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 590 | const b = try self.stack.pop().asIntegral(); | ||
| 591 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 592 | .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b), | ||
| 593 | }; | ||
| 594 | }, | ||
| 595 | OP.plus_uconst => { | ||
| 596 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 597 | const constant = (try readOperand(stream, opcode, context)).?.generic; | ||
| 598 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 599 | .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), constant), | ||
| 600 | }; | ||
| 601 | }, | ||
| 602 | OP.shl => { | ||
| 603 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 604 | const a = try self.stack.pop().asIntegral(); | ||
| 605 | const b = try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 606 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 607 | .generic = std.math.shl(usize, b, a), | ||
| 608 | }; | ||
| 609 | }, | ||
| 610 | OP.shr => { | ||
| 611 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 612 | const a = try self.stack.pop().asIntegral(); | ||
| 613 | const b = try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 614 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 615 | .generic = std.math.shr(usize, b, a), | ||
| 616 | }; | ||
| 617 | }, | ||
| 618 | OP.shra => { | ||
| 619 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 620 | const a = try self.stack.pop().asIntegral(); | ||
| 621 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 622 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 623 | .generic = @bitCast(std.math.shr(isize, b, a)), | ||
| 624 | }; | ||
| 625 | }, | ||
| 626 | OP.xor => { | ||
| 627 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 628 | const a = try self.stack.pop().asIntegral(); | ||
| 629 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 630 | .generic = a ^ try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 631 | }; | ||
| 632 | }, | ||
| 633 | |||
| 634 | // 2.5.1.5: Control Flow Operations | ||
| 635 | OP.le, | ||
| 636 | OP.ge, | ||
| 637 | OP.eq, | ||
| 638 | OP.lt, | ||
| 639 | OP.gt, | ||
| 640 | OP.ne, | ||
| 641 | => { | ||
| 642 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 643 | const a = self.stack.pop(); | ||
| 644 | const b = self.stack.items[self.stack.items.len - 1]; | ||
| 645 | |||
| 646 | if (a == .generic and b == .generic) { | ||
| 647 | const a_int: isize = @bitCast(a.asIntegral() catch unreachable); | ||
| 648 | const b_int: isize = @bitCast(b.asIntegral() catch unreachable); | ||
| 649 | const result = @intFromBool(switch (opcode) { | ||
| 650 | OP.le => b_int <= a_int, | ||
| 651 | OP.ge => b_int >= a_int, | ||
| 652 | OP.eq => b_int == a_int, | ||
| 653 | OP.lt => b_int < a_int, | ||
| 654 | OP.gt => b_int > a_int, | ||
| 655 | OP.ne => b_int != a_int, | ||
| 656 | else => unreachable, | ||
| 657 | }); | ||
| 658 | |||
| 659 | self.stack.items[self.stack.items.len - 1] = .{ .generic = result }; | ||
| 660 | } else { | ||
| 661 | // TODO: Load the types referenced by these values, find their comparison operator, and run it | ||
| 662 | return error.UnimplementedTypedComparison; | ||
| 663 | } | ||
| 664 | }, | ||
| 665 | OP.skip, OP.bra => { | ||
| 666 | const branch_offset = (try readOperand(stream, opcode, context)).?.branch_offset; | ||
| 667 | const condition = if (opcode == OP.bra) blk: { | ||
| 668 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 669 | break :blk try self.stack.pop().asIntegral() != 0; | ||
| 670 | } else true; | ||
| 671 | |||
| 672 | if (condition) { | ||
| 673 | const new_pos = std.math.cast( | ||
| 674 | usize, | ||
| 675 | try std.math.add(isize, @as(isize, @intCast(stream.pos)), branch_offset), | ||
| 676 | ) orelse return error.InvalidExpression; | ||
| 677 | |||
| 678 | if (new_pos < 0 or new_pos > stream.buffer.len) return error.InvalidExpression; | ||
| 679 | stream.pos = new_pos; | ||
| 680 | } | ||
| 681 | }, | ||
| 682 | OP.call2, | ||
| 683 | OP.call4, | ||
| 684 | OP.call_ref, | ||
| 685 | => { | ||
| 686 | const debug_info_offset = (try readOperand(stream, opcode, context)).?.generic; | ||
| 687 | _ = debug_info_offset; | ||
| 688 | |||
| 689 | // TODO: Load a DIE entry at debug_info_offset in a .debug_info section (the spec says that it | ||
| 690 | // can be in a separate exe / shared object from the one containing this expression). | ||
| 691 | // Transfer control to the DW_AT_location attribute, with the current stack as input. | ||
| 692 | |||
| 693 | return error.UnimplementedExpressionCall; | ||
| 694 | }, | ||
| 695 | |||
| 696 | // 2.5.1.6: Type Conversions | ||
| 697 | OP.convert => { | ||
| 698 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 699 | const type_offset = (try readOperand(stream, opcode, context)).?.generic; | ||
| 700 | |||
| 701 | // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size | ||
| 702 | const value = self.stack.items[self.stack.items.len - 1]; | ||
| 703 | if (type_offset == 0) { | ||
| 704 | self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() }; | ||
| 705 | } else { | ||
| 706 | // TODO: Load the DW_TAG_base_type entry in context.compile_unit, find a conversion operator | ||
| 707 | // from the old type to the new type, run it. | ||
| 708 | return error.UnimplementedTypeConversion; | ||
| 709 | } | ||
| 710 | }, | ||
| 711 | OP.reinterpret => { | ||
| 712 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 713 | const type_offset = (try readOperand(stream, opcode, context)).?.generic; | ||
| 714 | |||
| 715 | // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size | ||
| 716 | const value = self.stack.items[self.stack.items.len - 1]; | ||
| 717 | if (type_offset == 0) { | ||
| 718 | self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() }; | ||
| 719 | } else { | ||
| 720 | self.stack.items[self.stack.items.len - 1] = switch (value) { | ||
| 721 | .generic => |v| .{ | ||
| 722 | .regval_type = .{ | ||
| 723 | .type_offset = type_offset, | ||
| 724 | .type_size = @sizeOf(addr_type), | ||
| 725 | .value = v, | ||
| 726 | }, | ||
| 727 | }, | ||
| 728 | .regval_type => |r| .{ | ||
| 729 | .regval_type = .{ | ||
| 730 | .type_offset = type_offset, | ||
| 731 | .type_size = r.type_size, | ||
| 732 | .value = r.value, | ||
| 733 | }, | ||
| 734 | }, | ||
| 735 | .const_type => |c| .{ | ||
| 736 | .const_type = .{ | ||
| 737 | .type_offset = type_offset, | ||
| 738 | .value_bytes = c.value_bytes, | ||
| 739 | }, | ||
| 740 | }, | ||
| 741 | }; | ||
| 742 | } | ||
| 743 | }, | ||
| 744 | |||
| 745 | // 2.5.1.7: Special Operations | ||
| 746 | OP.nop => {}, | ||
| 747 | OP.entry_value => { | ||
| 748 | const block = (try readOperand(stream, opcode, context)).?.block; | ||
| 749 | if (block.len == 0) return error.InvalidSubExpression; | ||
| 750 | |||
| 751 | // TODO: The spec states that this sub-expression needs to observe the state (ie. registers) | ||
| 752 | // as it was upon entering the current subprogram. If this isn't being called at the | ||
| 753 | // end of a frame unwind operation, an additional ThreadContext with this state will be needed. | ||
| 754 | |||
| 755 | if (isOpcodeRegisterLocation(block[0])) { | ||
| 756 | if (context.thread_context == null) return error.IncompleteExpressionContext; | ||
| 757 | |||
| 758 | var block_stream = std.io.fixedBufferStream(block); | ||
| 759 | const register = (try readOperand(&block_stream, block[0], context)).?.register; | ||
| 760 | const value = mem.readIntSliceNative(usize, try abi.regBytes(context.thread_context.?, register, context.reg_context)); | ||
| 761 | try self.stack.append(allocator, .{ .generic = value }); | ||
| 762 | } else { | ||
| 763 | var stack_machine: Self = .{}; | ||
| 764 | defer stack_machine.deinit(allocator); | ||
| 765 | |||
| 766 | var sub_context = context; | ||
| 767 | sub_context.entry_value_context = true; | ||
| 768 | const result = try stack_machine.run(block, allocator, sub_context, null); | ||
| 769 | try self.stack.append(allocator, result orelse return error.InvalidSubExpression); | ||
| 770 | } | ||
| 771 | }, | ||
| 772 | |||
| 773 | // These have already been handled by readOperand | ||
| 774 | OP.lo_user...OP.hi_user => unreachable, | ||
| 775 | else => { | ||
| 776 | //std.debug.print("Unknown DWARF expression opcode: {x}\n", .{opcode}); | ||
| 777 | return error.UnknownExpressionOpcode; | ||
| 778 | }, | ||
| 779 | } | ||
| 780 | |||
| 781 | return stream.pos < stream.buffer.len; | ||
| 782 | } | ||
| 783 | }; | ||
| 784 | } | ||
| 785 | |||
| 786 | pub fn Builder(comptime options: ExpressionOptions) type { | ||
| 787 | const addr_type = switch (options.addr_size) { | ||
| 788 | 2 => u16, | ||
| 789 | 4 => u32, | ||
| 790 | 8 => u64, | ||
| 791 | else => @compileError("Unsupported address size of " ++ options.addr_size), | ||
| 792 | }; | ||
| 793 | |||
| 794 | return struct { | ||
| 795 | /// Zero-operand instructions | ||
| 796 | pub fn writeOpcode(writer: anytype, comptime opcode: u8) !void { | ||
| 797 | if (options.call_frame_context and !comptime isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; | ||
| 798 | switch (opcode) { | ||
| 799 | OP.dup, | ||
| 800 | OP.drop, | ||
| 801 | OP.over, | ||
| 802 | OP.swap, | ||
| 803 | OP.rot, | ||
| 804 | OP.deref, | ||
| 805 | OP.xderef, | ||
| 806 | OP.push_object_address, | ||
| 807 | OP.form_tls_address, | ||
| 808 | OP.call_frame_cfa, | ||
| 809 | OP.abs, | ||
| 810 | OP.@"and", | ||
| 811 | OP.div, | ||
| 812 | OP.minus, | ||
| 813 | OP.mod, | ||
| 814 | OP.mul, | ||
| 815 | OP.neg, | ||
| 816 | OP.not, | ||
| 817 | OP.@"or", | ||
| 818 | OP.plus, | ||
| 819 | OP.shl, | ||
| 820 | OP.shr, | ||
| 821 | OP.shra, | ||
| 822 | OP.xor, | ||
| 823 | OP.le, | ||
| 824 | OP.ge, | ||
| 825 | OP.eq, | ||
| 826 | OP.lt, | ||
| 827 | OP.gt, | ||
| 828 | OP.ne, | ||
| 829 | OP.nop, | ||
| 830 | OP.stack_value, | ||
| 831 | => try writer.writeByte(opcode), | ||
| 832 | else => @compileError("This opcode requires operands, use `write<Opcode>()` instead"), | ||
| 833 | } | ||
| 834 | } | ||
| 835 | |||
| 836 | // 2.5.1.1: Literal Encodings | ||
| 837 | pub fn writeLiteral(writer: anytype, literal: u8) !void { | ||
| 838 | switch (literal) { | ||
| 839 | 0...31 => |n| try writer.writeByte(n + OP.lit0), | ||
| 840 | else => return error.InvalidLiteral, | ||
| 841 | } | ||
| 842 | } | ||
| 843 | |||
| 844 | pub fn writeConst(writer: anytype, comptime T: type, value: T) !void { | ||
| 845 | if (@typeInfo(T) != .Int) @compileError("Constants must be integers"); | ||
| 846 | |||
| 847 | switch (T) { | ||
| 848 | u8, i8, u16, i16, u32, i32, u64, i64 => { | ||
| 849 | try writer.writeByte(switch (T) { | ||
| 850 | u8 => OP.const1u, | ||
| 851 | i8 => OP.const1s, | ||
| 852 | u16 => OP.const2u, | ||
| 853 | i16 => OP.const2s, | ||
| 854 | u32 => OP.const4u, | ||
| 855 | i32 => OP.const4s, | ||
| 856 | u64 => OP.const8u, | ||
| 857 | i64 => OP.const8s, | ||
| 858 | else => unreachable, | ||
| 859 | }); | ||
| 860 | |||
| 861 | try writer.writeInt(T, value, options.endian); | ||
| 862 | }, | ||
| 863 | else => switch (@typeInfo(T).Int.signedness) { | ||
| 864 | .unsigned => { | ||
| 865 | try writer.writeByte(OP.constu); | ||
| 866 | try leb.writeULEB128(writer, value); | ||
| 867 | }, | ||
| 868 | .signed => { | ||
| 869 | try writer.writeByte(OP.consts); | ||
| 870 | try leb.writeILEB128(writer, value); | ||
| 871 | }, | ||
| 872 | }, | ||
| 873 | } | ||
| 874 | } | ||
| 875 | |||
| 876 | pub fn writeConstx(writer: anytype, debug_addr_offset: anytype) !void { | ||
| 877 | try writer.writeByte(OP.constx); | ||
| 878 | try leb.writeULEB128(writer, debug_addr_offset); | ||
| 879 | } | ||
| 880 | |||
| 881 | pub fn writeConstType(writer: anytype, die_offset: anytype, value_bytes: []const u8) !void { | ||
| 882 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 883 | if (value_bytes.len > 0xff) return error.InvalidTypeLength; | ||
| 884 | try writer.writeByte(OP.const_type); | ||
| 885 | try leb.writeULEB128(writer, die_offset); | ||
| 886 | try writer.writeByte(@intCast(value_bytes.len)); | ||
| 887 | try writer.writeAll(value_bytes); | ||
| 888 | } | ||
| 889 | |||
| 890 | pub fn writeAddr(writer: anytype, value: addr_type) !void { | ||
| 891 | try writer.writeByte(OP.addr); | ||
| 892 | try writer.writeInt(addr_type, value, options.endian); | ||
| 893 | } | ||
| 894 | |||
| 895 | pub fn writeAddrx(writer: anytype, debug_addr_offset: anytype) !void { | ||
| 896 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 897 | try writer.writeByte(OP.addrx); | ||
| 898 | try leb.writeULEB128(writer, debug_addr_offset); | ||
| 899 | } | ||
| 900 | |||
| 901 | // 2.5.1.2: Register Values | ||
| 902 | pub fn writeFbreg(writer: anytype, offset: anytype) !void { | ||
| 903 | try writer.writeByte(OP.fbreg); | ||
| 904 | try leb.writeILEB128(writer, offset); | ||
| 905 | } | ||
| 906 | |||
| 907 | pub fn writeBreg(writer: anytype, register: u8, offset: anytype) !void { | ||
| 908 | if (register > 31) return error.InvalidRegister; | ||
| 909 | try writer.writeByte(OP.breg0 + register); | ||
| 910 | try leb.writeILEB128(writer, offset); | ||
| 911 | } | ||
| 912 | |||
| 913 | pub fn writeBregx(writer: anytype, register: anytype, offset: anytype) !void { | ||
| 914 | try writer.writeByte(OP.bregx); | ||
| 915 | try leb.writeULEB128(writer, register); | ||
| 916 | try leb.writeILEB128(writer, offset); | ||
| 917 | } | ||
| 918 | |||
| 919 | pub fn writeRegvalType(writer: anytype, register: anytype, offset: anytype) !void { | ||
| 920 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 921 | try writer.writeByte(OP.regval_type); | ||
| 922 | try leb.writeULEB128(writer, register); | ||
| 923 | try leb.writeULEB128(writer, offset); | ||
| 924 | } | ||
| 925 | |||
| 926 | // 2.5.1.3: Stack Operations | ||
| 927 | pub fn writePick(writer: anytype, index: u8) !void { | ||
| 928 | try writer.writeByte(OP.pick); | ||
| 929 | try writer.writeByte(index); | ||
| 930 | } | ||
| 931 | |||
| 932 | pub fn writeDerefSize(writer: anytype, size: u8) !void { | ||
| 933 | try writer.writeByte(OP.deref_size); | ||
| 934 | try writer.writeByte(size); | ||
| 935 | } | ||
| 936 | |||
| 937 | pub fn writeXDerefSize(writer: anytype, size: u8) !void { | ||
| 938 | try writer.writeByte(OP.xderef_size); | ||
| 939 | try writer.writeByte(size); | ||
| 940 | } | ||
| 941 | |||
| 942 | pub fn writeDerefType(writer: anytype, size: u8, die_offset: anytype) !void { | ||
| 943 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 944 | try writer.writeByte(OP.deref_type); | ||
| 945 | try writer.writeByte(size); | ||
| 946 | try leb.writeULEB128(writer, die_offset); | ||
| 947 | } | ||
| 948 | |||
| 949 | pub fn writeXDerefType(writer: anytype, size: u8, die_offset: anytype) !void { | ||
| 950 | try writer.writeByte(OP.xderef_type); | ||
| 951 | try writer.writeByte(size); | ||
| 952 | try leb.writeULEB128(writer, die_offset); | ||
| 953 | } | ||
| 954 | |||
| 955 | // 2.5.1.4: Arithmetic and Logical Operations | ||
| 956 | |||
| 957 | pub fn writePlusUconst(writer: anytype, uint_value: anytype) !void { | ||
| 958 | try writer.writeByte(OP.plus_uconst); | ||
| 959 | try leb.writeULEB128(writer, uint_value); | ||
| 960 | } | ||
| 961 | |||
| 962 | // 2.5.1.5: Control Flow Operations | ||
| 963 | |||
| 964 | pub fn writeSkip(writer: anytype, offset: i16) !void { | ||
| 965 | try writer.writeByte(OP.skip); | ||
| 966 | try writer.writeInt(i16, offset, options.endian); | ||
| 967 | } | ||
| 968 | |||
| 969 | pub fn writeBra(writer: anytype, offset: i16) !void { | ||
| 970 | try writer.writeByte(OP.bra); | ||
| 971 | try writer.writeInt(i16, offset, options.endian); | ||
| 972 | } | ||
| 973 | |||
| 974 | pub fn writeCall(writer: anytype, comptime T: type, offset: T) !void { | ||
| 975 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 976 | switch (T) { | ||
| 977 | u16 => try writer.writeByte(OP.call2), | ||
| 978 | u32 => try writer.writeByte(OP.call4), | ||
| 979 | else => @compileError("Call operand must be a 2 or 4 byte offset"), | ||
| 980 | } | ||
| 981 | |||
| 982 | try writer.writeInt(T, offset, options.endian); | ||
| 983 | } | ||
| 984 | |||
| 985 | pub fn writeCallRef(writer: anytype, comptime is_64: bool, value: if (is_64) u64 else u32) !void { | ||
| 986 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 987 | try writer.writeByte(OP.call_ref); | ||
| 988 | try writer.writeInt(if (is_64) u64 else u32, value, options.endian); | ||
| 989 | } | ||
| 990 | |||
| 991 | pub fn writeConvert(writer: anytype, die_offset: anytype) !void { | ||
| 992 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 993 | try writer.writeByte(OP.convert); | ||
| 994 | try leb.writeULEB128(writer, die_offset); | ||
| 995 | } | ||
| 996 | |||
| 997 | pub fn writeReinterpret(writer: anytype, die_offset: anytype) !void { | ||
| 998 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 999 | try writer.writeByte(OP.reinterpret); | ||
| 1000 | try leb.writeULEB128(writer, die_offset); | ||
| 1001 | } | ||
| 1002 | |||
| 1003 | // 2.5.1.7: Special Operations | ||
| 1004 | |||
| 1005 | pub fn writeEntryValue(writer: anytype, expression: []const u8) !void { | ||
| 1006 | try writer.writeByte(OP.entry_value); | ||
| 1007 | try leb.writeULEB128(writer, expression.len); | ||
| 1008 | try writer.writeAll(expression); | ||
| 1009 | } | ||
| 1010 | |||
| 1011 | // 2.6: Location Descriptions | ||
| 1012 | pub fn writeReg(writer: anytype, register: u8) !void { | ||
| 1013 | try writer.writeByte(OP.reg0 + register); | ||
| 1014 | } | ||
| 1015 | |||
| 1016 | pub fn writeRegx(writer: anytype, register: anytype) !void { | ||
| 1017 | try writer.writeByte(OP.regx); | ||
| 1018 | try leb.writeULEB128(writer, register); | ||
| 1019 | } | ||
| 1020 | |||
| 1021 | pub fn writeImplicitValue(writer: anytype, value_bytes: []const u8) !void { | ||
| 1022 | try writer.writeByte(OP.implicit_value); | ||
| 1023 | try leb.writeULEB128(writer, value_bytes.len); | ||
| 1024 | try writer.writeAll(value_bytes); | ||
| 1025 | } | ||
| 1026 | }; | ||
| 1027 | } | ||
| 1028 | |||
| 1029 | // Certain opcodes are not allowed in a CFA context, see 6.4.2 | ||
| 1030 | fn isOpcodeValidInCFA(opcode: u8) bool { | ||
| 1031 | return switch (opcode) { | ||
| 1032 | OP.addrx, | ||
| 1033 | OP.call2, | ||
| 1034 | OP.call4, | ||
| 1035 | OP.call_ref, | ||
| 1036 | OP.const_type, | ||
| 1037 | OP.constx, | ||
| 1038 | OP.convert, | ||
| 1039 | OP.deref_type, | ||
| 1040 | OP.regval_type, | ||
| 1041 | OP.reinterpret, | ||
| 1042 | OP.push_object_address, | ||
| 1043 | OP.call_frame_cfa, | ||
| 1044 | => false, | ||
| 1045 | else => true, | ||
| 1046 | }; | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | fn isOpcodeRegisterLocation(opcode: u8) bool { | ||
| 1050 | return switch (opcode) { | ||
| 1051 | OP.reg0...OP.reg31, OP.regx => true, | ||
| 1052 | else => false, | ||
| 1053 | }; | ||
| 1054 | } | ||
| 1055 | |||
| 1056 | const testing = std.testing; | ||
| 1057 | test "DWARF expressions" { | ||
| 1058 | const allocator = std.testing.allocator; | ||
| 1059 | |||
| 1060 | const options = ExpressionOptions{}; | ||
| 1061 | var stack_machine = StackMachine(options){}; | ||
| 1062 | defer stack_machine.deinit(allocator); | ||
| 1063 | |||
| 1064 | const b = Builder(options); | ||
| 1065 | |||
| 1066 | var program = std.ArrayList(u8).init(allocator); | ||
| 1067 | defer program.deinit(); | ||
| 1068 | |||
| 1069 | const writer = program.writer(); | ||
| 1070 | |||
| 1071 | // Literals | ||
| 1072 | { | ||
| 1073 | const context = ExpressionContext{}; | ||
| 1074 | for (0..32) |i| { | ||
| 1075 | try b.writeLiteral(writer, @intCast(i)); | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | _ = try stack_machine.run(program.items, allocator, context, 0); | ||
| 1079 | |||
| 1080 | for (0..32) |i| { | ||
| 1081 | const expected = 31 - i; | ||
| 1082 | try testing.expectEqual(expected, stack_machine.stack.popOrNull().?.generic); | ||
| 1083 | } | ||
| 1084 | } | ||
| 1085 | |||
| 1086 | // Constants | ||
| 1087 | { | ||
| 1088 | stack_machine.reset(); | ||
| 1089 | program.clearRetainingCapacity(); | ||
| 1090 | |||
| 1091 | const input = [_]comptime_int{ | ||
| 1092 | 1, | ||
| 1093 | -1, | ||
| 1094 | @as(usize, @truncate(0x0fff)), | ||
| 1095 | @as(isize, @truncate(-0x0fff)), | ||
| 1096 | @as(usize, @truncate(0x0fffffff)), | ||
| 1097 | @as(isize, @truncate(-0x0fffffff)), | ||
| 1098 | @as(usize, @truncate(0x0fffffffffffffff)), | ||
| 1099 | @as(isize, @truncate(-0x0fffffffffffffff)), | ||
| 1100 | @as(usize, @truncate(0x8000000)), | ||
| 1101 | @as(isize, @truncate(-0x8000000)), | ||
| 1102 | @as(usize, @truncate(0x12345678_12345678)), | ||
| 1103 | @as(usize, @truncate(0xffffffff_ffffffff)), | ||
| 1104 | @as(usize, @truncate(0xeeeeeeee_eeeeeeee)), | ||
| 1105 | }; | ||
| 1106 | |||
| 1107 | try b.writeConst(writer, u8, input[0]); | ||
| 1108 | try b.writeConst(writer, i8, input[1]); | ||
| 1109 | try b.writeConst(writer, u16, input[2]); | ||
| 1110 | try b.writeConst(writer, i16, input[3]); | ||
| 1111 | try b.writeConst(writer, u32, input[4]); | ||
| 1112 | try b.writeConst(writer, i32, input[5]); | ||
| 1113 | try b.writeConst(writer, u64, input[6]); | ||
| 1114 | try b.writeConst(writer, i64, input[7]); | ||
| 1115 | try b.writeConst(writer, u28, input[8]); | ||
| 1116 | try b.writeConst(writer, i28, input[9]); | ||
| 1117 | try b.writeAddr(writer, input[10]); | ||
| 1118 | |||
| 1119 | var mock_compile_unit: dwarf.CompileUnit = undefined; | ||
| 1120 | mock_compile_unit.addr_base = 1; | ||
| 1121 | |||
| 1122 | var mock_debug_addr = std.ArrayList(u8).init(allocator); | ||
| 1123 | defer mock_debug_addr.deinit(); | ||
| 1124 | |||
| 1125 | try mock_debug_addr.writer().writeIntNative(u16, 0); | ||
| 1126 | try mock_debug_addr.writer().writeIntNative(usize, input[11]); | ||
| 1127 | try mock_debug_addr.writer().writeIntNative(usize, input[12]); | ||
| 1128 | |||
| 1129 | const context = ExpressionContext{ | ||
| 1130 | .compile_unit = &mock_compile_unit, | ||
| 1131 | .debug_addr = mock_debug_addr.items, | ||
| 1132 | }; | ||
| 1133 | |||
| 1134 | try b.writeConstx(writer, @as(usize, 1)); | ||
| 1135 | try b.writeAddrx(writer, @as(usize, 1 + @sizeOf(usize))); | ||
| 1136 | |||
| 1137 | const die_offset: usize = @truncate(0xaabbccdd); | ||
| 1138 | const type_bytes: []const u8 = &.{ 1, 2, 3, 4 }; | ||
| 1139 | try b.writeConstType(writer, die_offset, type_bytes); | ||
| 1140 | |||
| 1141 | _ = try stack_machine.run(program.items, allocator, context, 0); | ||
| 1142 | |||
| 1143 | const const_type = stack_machine.stack.popOrNull().?.const_type; | ||
| 1144 | try testing.expectEqual(die_offset, const_type.type_offset); | ||
| 1145 | try testing.expectEqualSlices(u8, type_bytes, const_type.value_bytes); | ||
| 1146 | |||
| 1147 | const expected = .{ | ||
| 1148 | .{ usize, input[12], usize }, | ||
| 1149 | .{ usize, input[11], usize }, | ||
| 1150 | .{ usize, input[10], usize }, | ||
| 1151 | .{ isize, input[9], isize }, | ||
| 1152 | .{ usize, input[8], usize }, | ||
| 1153 | .{ isize, input[7], isize }, | ||
| 1154 | .{ usize, input[6], usize }, | ||
| 1155 | .{ isize, input[5], isize }, | ||
| 1156 | .{ usize, input[4], usize }, | ||
| 1157 | .{ isize, input[3], isize }, | ||
| 1158 | .{ usize, input[2], usize }, | ||
| 1159 | .{ isize, input[1], isize }, | ||
| 1160 | .{ usize, input[0], usize }, | ||
| 1161 | }; | ||
| 1162 | |||
| 1163 | inline for (expected) |e| { | ||
| 1164 | try testing.expectEqual(@as(e[0], e[1]), @as(e[2], @bitCast(stack_machine.stack.popOrNull().?.generic))); | ||
| 1165 | } | ||
| 1166 | } | ||
| 1167 | |||
| 1168 | // Register values | ||
| 1169 | if (@sizeOf(std.debug.ThreadContext) != 0) { | ||
| 1170 | stack_machine.reset(); | ||
| 1171 | program.clearRetainingCapacity(); | ||
| 1172 | |||
| 1173 | const reg_context = abi.RegisterContext{ | ||
| 1174 | .eh_frame = true, | ||
| 1175 | .is_macho = builtin.os.tag == .macos, | ||
| 1176 | }; | ||
| 1177 | var thread_context: std.debug.ThreadContext = undefined; | ||
| 1178 | std.debug.relocateContext(&thread_context); | ||
| 1179 | const context = ExpressionContext{ | ||
| 1180 | .thread_context = &thread_context, | ||
| 1181 | .reg_context = reg_context, | ||
| 1182 | }; | ||
| 1183 | |||
| 1184 | // Only test register operations on arch / os that have them implemented | ||
| 1185 | if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { | ||
| 1186 | |||
| 1187 | // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it | ||
| 1188 | |||
| 1189 | mem.writeIntSliceNative(usize, reg_bytes, 0xee); | ||
| 1190 | (try abi.regValueNative(usize, &thread_context, abi.fpRegNum(reg_context), reg_context)).* = 1; | ||
| 1191 | (try abi.regValueNative(usize, &thread_context, abi.spRegNum(reg_context), reg_context)).* = 2; | ||
| 1192 | (try abi.regValueNative(usize, &thread_context, abi.ipRegNum(), reg_context)).* = 3; | ||
| 1193 | |||
| 1194 | try b.writeBreg(writer, abi.fpRegNum(reg_context), @as(usize, 100)); | ||
| 1195 | try b.writeBreg(writer, abi.spRegNum(reg_context), @as(usize, 200)); | ||
| 1196 | try b.writeBregx(writer, abi.ipRegNum(), @as(usize, 300)); | ||
| 1197 | try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); | ||
| 1198 | |||
| 1199 | _ = try stack_machine.run(program.items, allocator, context, 0); | ||
| 1200 | |||
| 1201 | const regval_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1202 | try testing.expectEqual(@as(usize, 400), regval_type.type_offset); | ||
| 1203 | try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size); | ||
| 1204 | try testing.expectEqual(@as(usize, 0xee), regval_type.value); | ||
| 1205 | |||
| 1206 | try testing.expectEqual(@as(usize, 303), stack_machine.stack.popOrNull().?.generic); | ||
| 1207 | try testing.expectEqual(@as(usize, 202), stack_machine.stack.popOrNull().?.generic); | ||
| 1208 | try testing.expectEqual(@as(usize, 101), stack_machine.stack.popOrNull().?.generic); | ||
| 1209 | } else |err| { | ||
| 1210 | switch (err) { | ||
| 1211 | error.UnimplementedArch, | ||
| 1212 | error.UnimplementedOs, | ||
| 1213 | error.ThreadContextNotSupported, | ||
| 1214 | => {}, | ||
| 1215 | else => return err, | ||
| 1216 | } | ||
| 1217 | } | ||
| 1218 | } | ||
| 1219 | |||
| 1220 | // Stack operations | ||
| 1221 | { | ||
| 1222 | var context = ExpressionContext{}; | ||
| 1223 | |||
| 1224 | stack_machine.reset(); | ||
| 1225 | program.clearRetainingCapacity(); | ||
| 1226 | try b.writeConst(writer, u8, 1); | ||
| 1227 | try b.writeOpcode(writer, OP.dup); | ||
| 1228 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1229 | try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1230 | try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1231 | |||
| 1232 | stack_machine.reset(); | ||
| 1233 | program.clearRetainingCapacity(); | ||
| 1234 | try b.writeConst(writer, u8, 1); | ||
| 1235 | try b.writeOpcode(writer, OP.drop); | ||
| 1236 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1237 | try testing.expect(stack_machine.stack.popOrNull() == null); | ||
| 1238 | |||
| 1239 | stack_machine.reset(); | ||
| 1240 | program.clearRetainingCapacity(); | ||
| 1241 | try b.writeConst(writer, u8, 4); | ||
| 1242 | try b.writeConst(writer, u8, 5); | ||
| 1243 | try b.writeConst(writer, u8, 6); | ||
| 1244 | try b.writePick(writer, 2); | ||
| 1245 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1246 | try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic); | ||
| 1247 | |||
| 1248 | stack_machine.reset(); | ||
| 1249 | program.clearRetainingCapacity(); | ||
| 1250 | try b.writeConst(writer, u8, 4); | ||
| 1251 | try b.writeConst(writer, u8, 5); | ||
| 1252 | try b.writeConst(writer, u8, 6); | ||
| 1253 | try b.writeOpcode(writer, OP.over); | ||
| 1254 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1255 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1256 | |||
| 1257 | stack_machine.reset(); | ||
| 1258 | program.clearRetainingCapacity(); | ||
| 1259 | try b.writeConst(writer, u8, 5); | ||
| 1260 | try b.writeConst(writer, u8, 6); | ||
| 1261 | try b.writeOpcode(writer, OP.swap); | ||
| 1262 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1263 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1264 | try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic); | ||
| 1265 | |||
| 1266 | stack_machine.reset(); | ||
| 1267 | program.clearRetainingCapacity(); | ||
| 1268 | try b.writeConst(writer, u8, 4); | ||
| 1269 | try b.writeConst(writer, u8, 5); | ||
| 1270 | try b.writeConst(writer, u8, 6); | ||
| 1271 | try b.writeOpcode(writer, OP.rot); | ||
| 1272 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1273 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1274 | try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic); | ||
| 1275 | try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic); | ||
| 1276 | |||
| 1277 | const deref_target: usize = @truncate(0xffeeffee_ffeeffee); | ||
| 1278 | |||
| 1279 | stack_machine.reset(); | ||
| 1280 | program.clearRetainingCapacity(); | ||
| 1281 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1282 | try b.writeOpcode(writer, OP.deref); | ||
| 1283 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1284 | try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic); | ||
| 1285 | |||
| 1286 | stack_machine.reset(); | ||
| 1287 | program.clearRetainingCapacity(); | ||
| 1288 | try b.writeLiteral(writer, 0); | ||
| 1289 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1290 | try b.writeOpcode(writer, OP.xderef); | ||
| 1291 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1292 | try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic); | ||
| 1293 | |||
| 1294 | stack_machine.reset(); | ||
| 1295 | program.clearRetainingCapacity(); | ||
| 1296 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1297 | try b.writeDerefSize(writer, 1); | ||
| 1298 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1299 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic); | ||
| 1300 | |||
| 1301 | stack_machine.reset(); | ||
| 1302 | program.clearRetainingCapacity(); | ||
| 1303 | try b.writeLiteral(writer, 0); | ||
| 1304 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1305 | try b.writeXDerefSize(writer, 1); | ||
| 1306 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1307 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic); | ||
| 1308 | |||
| 1309 | const type_offset: usize = @truncate(0xaabbaabb_aabbaabb); | ||
| 1310 | |||
| 1311 | stack_machine.reset(); | ||
| 1312 | program.clearRetainingCapacity(); | ||
| 1313 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1314 | try b.writeDerefType(writer, 1, type_offset); | ||
| 1315 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1316 | const deref_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1317 | try testing.expectEqual(type_offset, deref_type.type_offset); | ||
| 1318 | try testing.expectEqual(@as(u8, 1), deref_type.type_size); | ||
| 1319 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), deref_type.value); | ||
| 1320 | |||
| 1321 | stack_machine.reset(); | ||
| 1322 | program.clearRetainingCapacity(); | ||
| 1323 | try b.writeLiteral(writer, 0); | ||
| 1324 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1325 | try b.writeXDerefType(writer, 1, type_offset); | ||
| 1326 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1327 | const xderef_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1328 | try testing.expectEqual(type_offset, xderef_type.type_offset); | ||
| 1329 | try testing.expectEqual(@as(u8, 1), xderef_type.type_size); | ||
| 1330 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), xderef_type.value); | ||
| 1331 | |||
| 1332 | context.object_address = &deref_target; | ||
| 1333 | |||
| 1334 | stack_machine.reset(); | ||
| 1335 | program.clearRetainingCapacity(); | ||
| 1336 | try b.writeOpcode(writer, OP.push_object_address); | ||
| 1337 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1338 | try testing.expectEqual(@as(usize, @intFromPtr(context.object_address.?)), stack_machine.stack.popOrNull().?.generic); | ||
| 1339 | |||
| 1340 | // TODO: Test OP.form_tls_address | ||
| 1341 | |||
| 1342 | context.cfa = @truncate(0xccddccdd_ccddccdd); | ||
| 1343 | |||
| 1344 | stack_machine.reset(); | ||
| 1345 | program.clearRetainingCapacity(); | ||
| 1346 | try b.writeOpcode(writer, OP.call_frame_cfa); | ||
| 1347 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1348 | try testing.expectEqual(context.cfa.?, stack_machine.stack.popOrNull().?.generic); | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | // Arithmetic and Logical Operations | ||
| 1352 | { | ||
| 1353 | var context = ExpressionContext{}; | ||
| 1354 | |||
| 1355 | stack_machine.reset(); | ||
| 1356 | program.clearRetainingCapacity(); | ||
| 1357 | try b.writeConst(writer, i16, -4096); | ||
| 1358 | try b.writeOpcode(writer, OP.abs); | ||
| 1359 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1360 | try testing.expectEqual(@as(usize, 4096), stack_machine.stack.popOrNull().?.generic); | ||
| 1361 | |||
| 1362 | stack_machine.reset(); | ||
| 1363 | program.clearRetainingCapacity(); | ||
| 1364 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1365 | try b.writeConst(writer, u16, 0xf0ff); | ||
| 1366 | try b.writeOpcode(writer, OP.@"and"); | ||
| 1367 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1368 | try testing.expectEqual(@as(usize, 0xf00f), stack_machine.stack.popOrNull().?.generic); | ||
| 1369 | |||
| 1370 | stack_machine.reset(); | ||
| 1371 | program.clearRetainingCapacity(); | ||
| 1372 | try b.writeConst(writer, i16, -404); | ||
| 1373 | try b.writeConst(writer, i16, 100); | ||
| 1374 | try b.writeOpcode(writer, OP.div); | ||
| 1375 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1376 | try testing.expectEqual(@as(isize, -404 / 100), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic))); | ||
| 1377 | |||
| 1378 | stack_machine.reset(); | ||
| 1379 | program.clearRetainingCapacity(); | ||
| 1380 | try b.writeConst(writer, u16, 200); | ||
| 1381 | try b.writeConst(writer, u16, 50); | ||
| 1382 | try b.writeOpcode(writer, OP.minus); | ||
| 1383 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1384 | try testing.expectEqual(@as(usize, 150), stack_machine.stack.popOrNull().?.generic); | ||
| 1385 | |||
| 1386 | stack_machine.reset(); | ||
| 1387 | program.clearRetainingCapacity(); | ||
| 1388 | try b.writeConst(writer, u16, 123); | ||
| 1389 | try b.writeConst(writer, u16, 100); | ||
| 1390 | try b.writeOpcode(writer, OP.mod); | ||
| 1391 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1392 | try testing.expectEqual(@as(usize, 23), stack_machine.stack.popOrNull().?.generic); | ||
| 1393 | |||
| 1394 | stack_machine.reset(); | ||
| 1395 | program.clearRetainingCapacity(); | ||
| 1396 | try b.writeConst(writer, u16, 0xff); | ||
| 1397 | try b.writeConst(writer, u16, 0xee); | ||
| 1398 | try b.writeOpcode(writer, OP.mul); | ||
| 1399 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1400 | try testing.expectEqual(@as(usize, 0xed12), stack_machine.stack.popOrNull().?.generic); | ||
| 1401 | |||
| 1402 | stack_machine.reset(); | ||
| 1403 | program.clearRetainingCapacity(); | ||
| 1404 | try b.writeConst(writer, u16, 5); | ||
| 1405 | try b.writeOpcode(writer, OP.neg); | ||
| 1406 | try b.writeConst(writer, i16, -6); | ||
| 1407 | try b.writeOpcode(writer, OP.neg); | ||
| 1408 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1409 | try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic); | ||
| 1410 | try testing.expectEqual(@as(isize, -5), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic))); | ||
| 1411 | |||
| 1412 | stack_machine.reset(); | ||
| 1413 | program.clearRetainingCapacity(); | ||
| 1414 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1415 | try b.writeOpcode(writer, OP.not); | ||
| 1416 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1417 | try testing.expectEqual(~@as(usize, 0xff0f), stack_machine.stack.popOrNull().?.generic); | ||
| 1418 | |||
| 1419 | stack_machine.reset(); | ||
| 1420 | program.clearRetainingCapacity(); | ||
| 1421 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1422 | try b.writeConst(writer, u16, 0xf0ff); | ||
| 1423 | try b.writeOpcode(writer, OP.@"or"); | ||
| 1424 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1425 | try testing.expectEqual(@as(usize, 0xffff), stack_machine.stack.popOrNull().?.generic); | ||
| 1426 | |||
| 1427 | stack_machine.reset(); | ||
| 1428 | program.clearRetainingCapacity(); | ||
| 1429 | try b.writeConst(writer, i16, 402); | ||
| 1430 | try b.writeConst(writer, i16, 100); | ||
| 1431 | try b.writeOpcode(writer, OP.plus); | ||
| 1432 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1433 | try testing.expectEqual(@as(usize, 502), stack_machine.stack.popOrNull().?.generic); | ||
| 1434 | |||
| 1435 | stack_machine.reset(); | ||
| 1436 | program.clearRetainingCapacity(); | ||
| 1437 | try b.writeConst(writer, u16, 4096); | ||
| 1438 | try b.writePlusUconst(writer, @as(usize, 8192)); | ||
| 1439 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1440 | try testing.expectEqual(@as(usize, 4096 + 8192), stack_machine.stack.popOrNull().?.generic); | ||
| 1441 | |||
| 1442 | stack_machine.reset(); | ||
| 1443 | program.clearRetainingCapacity(); | ||
| 1444 | try b.writeConst(writer, u16, 0xfff); | ||
| 1445 | try b.writeConst(writer, u16, 1); | ||
| 1446 | try b.writeOpcode(writer, OP.shl); | ||
| 1447 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1448 | try testing.expectEqual(@as(usize, 0xfff << 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1449 | |||
| 1450 | stack_machine.reset(); | ||
| 1451 | program.clearRetainingCapacity(); | ||
| 1452 | try b.writeConst(writer, u16, 0xfff); | ||
| 1453 | try b.writeConst(writer, u16, 1); | ||
| 1454 | try b.writeOpcode(writer, OP.shr); | ||
| 1455 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1456 | try testing.expectEqual(@as(usize, 0xfff >> 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1457 | |||
| 1458 | stack_machine.reset(); | ||
| 1459 | program.clearRetainingCapacity(); | ||
| 1460 | try b.writeConst(writer, u16, 0xfff); | ||
| 1461 | try b.writeConst(writer, u16, 1); | ||
| 1462 | try b.writeOpcode(writer, OP.shr); | ||
| 1463 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1464 | try testing.expectEqual(@as(usize, @bitCast(@as(isize, 0xfff) >> 1)), stack_machine.stack.popOrNull().?.generic); | ||
| 1465 | |||
| 1466 | stack_machine.reset(); | ||
| 1467 | program.clearRetainingCapacity(); | ||
| 1468 | try b.writeConst(writer, u16, 0xf0ff); | ||
| 1469 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1470 | try b.writeOpcode(writer, OP.xor); | ||
| 1471 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1472 | try testing.expectEqual(@as(usize, 0x0ff0), stack_machine.stack.popOrNull().?.generic); | ||
| 1473 | } | ||
| 1474 | |||
| 1475 | // Control Flow Operations | ||
| 1476 | { | ||
| 1477 | var context = ExpressionContext{}; | ||
| 1478 | const expected = .{ | ||
| 1479 | .{ OP.le, 1, 1, 0 }, | ||
| 1480 | .{ OP.ge, 1, 0, 1 }, | ||
| 1481 | .{ OP.eq, 1, 0, 0 }, | ||
| 1482 | .{ OP.lt, 0, 1, 0 }, | ||
| 1483 | .{ OP.gt, 0, 0, 1 }, | ||
| 1484 | .{ OP.ne, 0, 1, 1 }, | ||
| 1485 | }; | ||
| 1486 | |||
| 1487 | inline for (expected) |e| { | ||
| 1488 | stack_machine.reset(); | ||
| 1489 | program.clearRetainingCapacity(); | ||
| 1490 | |||
| 1491 | try b.writeConst(writer, u16, 0); | ||
| 1492 | try b.writeConst(writer, u16, 0); | ||
| 1493 | try b.writeOpcode(writer, e[0]); | ||
| 1494 | try b.writeConst(writer, u16, 0); | ||
| 1495 | try b.writeConst(writer, u16, 1); | ||
| 1496 | try b.writeOpcode(writer, e[0]); | ||
| 1497 | try b.writeConst(writer, u16, 1); | ||
| 1498 | try b.writeConst(writer, u16, 0); | ||
| 1499 | try b.writeOpcode(writer, e[0]); | ||
| 1500 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1501 | try testing.expectEqual(@as(usize, e[3]), stack_machine.stack.popOrNull().?.generic); | ||
| 1502 | try testing.expectEqual(@as(usize, e[2]), stack_machine.stack.popOrNull().?.generic); | ||
| 1503 | try testing.expectEqual(@as(usize, e[1]), stack_machine.stack.popOrNull().?.generic); | ||
| 1504 | } | ||
| 1505 | |||
| 1506 | stack_machine.reset(); | ||
| 1507 | program.clearRetainingCapacity(); | ||
| 1508 | try b.writeLiteral(writer, 2); | ||
| 1509 | try b.writeSkip(writer, 1); | ||
| 1510 | try b.writeLiteral(writer, 3); | ||
| 1511 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1512 | try testing.expectEqual(@as(usize, 2), stack_machine.stack.popOrNull().?.generic); | ||
| 1513 | |||
| 1514 | stack_machine.reset(); | ||
| 1515 | program.clearRetainingCapacity(); | ||
| 1516 | try b.writeLiteral(writer, 2); | ||
| 1517 | try b.writeBra(writer, 1); | ||
| 1518 | try b.writeLiteral(writer, 3); | ||
| 1519 | try b.writeLiteral(writer, 0); | ||
| 1520 | try b.writeBra(writer, 1); | ||
| 1521 | try b.writeLiteral(writer, 4); | ||
| 1522 | try b.writeLiteral(writer, 5); | ||
| 1523 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1524 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1525 | try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic); | ||
| 1526 | try testing.expect(stack_machine.stack.popOrNull() == null); | ||
| 1527 | |||
| 1528 | // TODO: Test call2, call4, call_ref once implemented | ||
| 1529 | |||
| 1530 | } | ||
| 1531 | |||
| 1532 | // Type conversions | ||
| 1533 | { | ||
| 1534 | var context = ExpressionContext{}; | ||
| 1535 | stack_machine.reset(); | ||
| 1536 | program.clearRetainingCapacity(); | ||
| 1537 | |||
| 1538 | // TODO: Test typed OP.convert once implemented | ||
| 1539 | |||
| 1540 | const value: usize = @truncate(0xffeeffee_ffeeffee); | ||
| 1541 | var value_bytes: [options.addr_size]u8 = undefined; | ||
| 1542 | mem.writeIntSliceNative(usize, &value_bytes, value); | ||
| 1543 | |||
| 1544 | // Convert to generic type | ||
| 1545 | stack_machine.reset(); | ||
| 1546 | program.clearRetainingCapacity(); | ||
| 1547 | try b.writeConstType(writer, @as(usize, 0), &value_bytes); | ||
| 1548 | try b.writeConvert(writer, @as(usize, 0)); | ||
| 1549 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1550 | try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic); | ||
| 1551 | |||
| 1552 | // Reinterpret to generic type | ||
| 1553 | stack_machine.reset(); | ||
| 1554 | program.clearRetainingCapacity(); | ||
| 1555 | try b.writeConstType(writer, @as(usize, 0), &value_bytes); | ||
| 1556 | try b.writeReinterpret(writer, @as(usize, 0)); | ||
| 1557 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1558 | try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic); | ||
| 1559 | |||
| 1560 | // Reinterpret to new type | ||
| 1561 | const die_offset: usize = 0xffee; | ||
| 1562 | |||
| 1563 | stack_machine.reset(); | ||
| 1564 | program.clearRetainingCapacity(); | ||
| 1565 | try b.writeConstType(writer, @as(usize, 0), &value_bytes); | ||
| 1566 | try b.writeReinterpret(writer, die_offset); | ||
| 1567 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1568 | const const_type = stack_machine.stack.popOrNull().?.const_type; | ||
| 1569 | try testing.expectEqual(die_offset, const_type.type_offset); | ||
| 1570 | |||
| 1571 | stack_machine.reset(); | ||
| 1572 | program.clearRetainingCapacity(); | ||
| 1573 | try b.writeLiteral(writer, 0); | ||
| 1574 | try b.writeReinterpret(writer, die_offset); | ||
| 1575 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1576 | const regval_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1577 | try testing.expectEqual(die_offset, regval_type.type_offset); | ||
| 1578 | } | ||
| 1579 | |||
| 1580 | // Special operations | ||
| 1581 | { | ||
| 1582 | var context = ExpressionContext{}; | ||
| 1583 | |||
| 1584 | stack_machine.reset(); | ||
| 1585 | program.clearRetainingCapacity(); | ||
| 1586 | try b.writeOpcode(writer, OP.nop); | ||
| 1587 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1588 | try testing.expect(stack_machine.stack.popOrNull() == null); | ||
| 1589 | |||
| 1590 | // Sub-expression | ||
| 1591 | { | ||
| 1592 | var sub_program = std.ArrayList(u8).init(allocator); | ||
| 1593 | defer sub_program.deinit(); | ||
| 1594 | const sub_writer = sub_program.writer(); | ||
| 1595 | try b.writeLiteral(sub_writer, 3); | ||
| 1596 | |||
| 1597 | stack_machine.reset(); | ||
| 1598 | program.clearRetainingCapacity(); | ||
| 1599 | try b.writeEntryValue(writer, sub_program.items); | ||
| 1600 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1601 | try testing.expectEqual(@as(usize, 3), stack_machine.stack.popOrNull().?.generic); | ||
| 1602 | } | ||
| 1603 | |||
| 1604 | // Register location description | ||
| 1605 | const reg_context = abi.RegisterContext{ | ||
| 1606 | .eh_frame = true, | ||
| 1607 | .is_macho = builtin.os.tag == .macos, | ||
| 1608 | }; | ||
| 1609 | var thread_context: std.debug.ThreadContext = undefined; | ||
| 1610 | std.debug.relocateContext(&thread_context); | ||
| 1611 | context = ExpressionContext{ | ||
| 1612 | .thread_context = &thread_context, | ||
| 1613 | .reg_context = reg_context, | ||
| 1614 | }; | ||
| 1615 | |||
| 1616 | if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { | ||
| 1617 | mem.writeIntSliceNative(usize, reg_bytes, 0xee); | ||
| 1618 | |||
| 1619 | var sub_program = std.ArrayList(u8).init(allocator); | ||
| 1620 | defer sub_program.deinit(); | ||
| 1621 | const sub_writer = sub_program.writer(); | ||
| 1622 | try b.writeReg(sub_writer, 0); | ||
| 1623 | |||
| 1624 | stack_machine.reset(); | ||
| 1625 | program.clearRetainingCapacity(); | ||
| 1626 | try b.writeEntryValue(writer, sub_program.items); | ||
| 1627 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1628 | try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.popOrNull().?.generic); | ||
| 1629 | } else |err| { | ||
| 1630 | switch (err) { | ||
| 1631 | error.UnimplementedArch, | ||
| 1632 | error.UnimplementedOs, | ||
| 1633 | error.ThreadContextNotSupported, | ||
| 1634 | => {}, | ||
| 1635 | else => return err, | ||
| 1636 | } | ||
| 1637 | } | ||
| 1638 | } | ||
| 1639 | } | ||
lib/std/elf.zig+8| ... | @@ -371,6 +371,9 @@ pub const SHT_LOUSER = 0x80000000; | ... | @@ -371,6 +371,9 @@ pub const SHT_LOUSER = 0x80000000; |
| 371 | /// End of application-specific | 371 | /// End of application-specific |
| 372 | pub const SHT_HIUSER = 0xffffffff; | 372 | pub const SHT_HIUSER = 0xffffffff; |
| 373 | 373 | ||
| 374 | // Note type for .note.gnu.build_id | ||
| 375 | pub const NT_GNU_BUILD_ID = 3; | ||
| 376 | |||
| 374 | /// Local symbol | 377 | /// Local symbol |
| 375 | pub const STB_LOCAL = 0; | 378 | pub const STB_LOCAL = 0; |
| 376 | /// Global symbol | 379 | /// Global symbol |
| ... | @@ -1055,6 +1058,11 @@ pub const Shdr = switch (@sizeOf(usize)) { | ... | @@ -1055,6 +1058,11 @@ pub const Shdr = switch (@sizeOf(usize)) { |
| 1055 | 8 => Elf64_Shdr, | 1058 | 8 => Elf64_Shdr, |
| 1056 | else => @compileError("expected pointer size of 32 or 64"), | 1059 | else => @compileError("expected pointer size of 32 or 64"), |
| 1057 | }; | 1060 | }; |
| 1061 | pub const Chdr = switch (@sizeOf(usize)) { | ||
| 1062 | 4 => Elf32_Chdr, | ||
| 1063 | 8 => Elf64_Chdr, | ||
| 1064 | else => @compileError("expected pointer size of 32 or 64"), | ||
| 1065 | }; | ||
| 1058 | pub const Sym = switch (@sizeOf(usize)) { | 1066 | pub const Sym = switch (@sizeOf(usize)) { |
| 1059 | 4 => Elf32_Sym, | 1067 | 4 => Elf32_Sym, |
| 1060 | 8 => Elf64_Sym, | 1068 | 8 => Elf64_Sym, |
lib/std/macho.zig+61| ... | @@ -2064,3 +2064,64 @@ pub const UNWIND_ARM64_FRAME_D14_D15_PAIR: u32 = 0x00000800; | ... | @@ -2064,3 +2064,64 @@ pub const UNWIND_ARM64_FRAME_D14_D15_PAIR: u32 = 0x00000800; |
| 2064 | 2064 | ||
| 2065 | pub const UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK: u32 = 0x00FFF000; | 2065 | pub const UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK: u32 = 0x00FFF000; |
| 2066 | pub const UNWIND_ARM64_DWARF_SECTION_OFFSET: u32 = 0x00FFFFFF; | 2066 | pub const UNWIND_ARM64_DWARF_SECTION_OFFSET: u32 = 0x00FFFFFF; |
| 2067 | |||
| 2068 | pub const CompactUnwindEncoding = packed struct(u32) { | ||
| 2069 | value: packed union { | ||
| 2070 | x86_64: packed union { | ||
| 2071 | frame: packed struct(u24) { | ||
| 2072 | reg4: u3, | ||
| 2073 | reg3: u3, | ||
| 2074 | reg2: u3, | ||
| 2075 | reg1: u3, | ||
| 2076 | reg0: u3, | ||
| 2077 | unused: u1 = 0, | ||
| 2078 | frame_offset: u8, | ||
| 2079 | }, | ||
| 2080 | frameless: packed struct(u24) { | ||
| 2081 | stack_reg_permutation: u10, | ||
| 2082 | stack_reg_count: u3, | ||
| 2083 | stack: packed union { | ||
| 2084 | direct: packed struct(u11) { | ||
| 2085 | _: u3, | ||
| 2086 | stack_size: u8, | ||
| 2087 | }, | ||
| 2088 | indirect: packed struct(u11) { | ||
| 2089 | stack_adjust: u3, | ||
| 2090 | sub_offset: u8, | ||
| 2091 | }, | ||
| 2092 | }, | ||
| 2093 | }, | ||
| 2094 | dwarf: u24, | ||
| 2095 | }, | ||
| 2096 | arm64: packed union { | ||
| 2097 | frame: packed struct(u24) { | ||
| 2098 | x_reg_pairs: packed struct(u5) { | ||
| 2099 | x19_x20: u1, | ||
| 2100 | x21_x22: u1, | ||
| 2101 | x23_x24: u1, | ||
| 2102 | x25_x26: u1, | ||
| 2103 | x27_x28: u1, | ||
| 2104 | }, | ||
| 2105 | d_reg_pairs: packed struct(u4) { | ||
| 2106 | d8_d9: u1, | ||
| 2107 | d10_d11: u1, | ||
| 2108 | d12_d13: u1, | ||
| 2109 | d14_d15: u1, | ||
| 2110 | }, | ||
| 2111 | _: u15, | ||
| 2112 | }, | ||
| 2113 | frameless: packed struct(u24) { | ||
| 2114 | _: u12 = 0, | ||
| 2115 | stack_size: u12, | ||
| 2116 | }, | ||
| 2117 | dwarf: u24, | ||
| 2118 | }, | ||
| 2119 | }, | ||
| 2120 | mode: packed union { | ||
| 2121 | x86_64: UNWIND_X86_64_MODE, | ||
| 2122 | arm64: UNWIND_ARM64_MODE, | ||
| 2123 | }, | ||
| 2124 | personality_index: u2, | ||
| 2125 | has_lsda: u1, | ||
| 2126 | start: u1, | ||
| 2127 | }; |
lib/std/os/linux.zig+1| ... | @@ -86,6 +86,7 @@ pub const timeval = arch_bits.timeval; | ... | @@ -86,6 +86,7 @@ pub const timeval = arch_bits.timeval; |
| 86 | pub const timezone = arch_bits.timezone; | 86 | pub const timezone = arch_bits.timezone; |
| 87 | pub const ucontext_t = arch_bits.ucontext_t; | 87 | pub const ucontext_t = arch_bits.ucontext_t; |
| 88 | pub const user_desc = arch_bits.user_desc; | 88 | pub const user_desc = arch_bits.user_desc; |
| 89 | pub const getcontext = arch_bits.getcontext; | ||
| 89 | 90 | ||
| 90 | pub const tls = @import("linux/tls.zig"); | 91 | pub const tls = @import("linux/tls.zig"); |
| 91 | pub const pie = @import("linux/start_pie.zig"); | 92 | pub const pie = @import("linux/start_pie.zig"); |
lib/std/os/linux/x86.zig+83| ... | @@ -389,3 +389,86 @@ pub const SC = struct { | ... | @@ -389,3 +389,86 @@ pub const SC = struct { |
| 389 | pub const recvmmsg = 19; | 389 | pub const recvmmsg = 19; |
| 390 | pub const sendmmsg = 20; | 390 | pub const sendmmsg = 20; |
| 391 | }; | 391 | }; |
| 392 | |||
| 393 | fn gpRegisterOffset(comptime reg_index: comptime_int) usize { | ||
| 394 | return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; | ||
| 395 | } | ||
| 396 | |||
| 397 | noinline fn getContextReturnAddress() usize { | ||
| 398 | return @returnAddress(); | ||
| 399 | } | ||
| 400 | |||
| 401 | pub fn getContextInternal() callconv(.Naked) void { | ||
| 402 | asm volatile ( | ||
| 403 | \\ movl $0, (%[flags_offset])(%%edx) | ||
| 404 | \\ movl $0, (%[link_offset])(%%edx) | ||
| 405 | \\ movl %%edi, (%[edi_offset])(%%edx) | ||
| 406 | \\ movl %%esi, (%[esi_offset])(%%edx) | ||
| 407 | \\ movl %%ebp, (%[ebp_offset])(%%edx) | ||
| 408 | \\ movl %%ebx, (%[ebx_offset])(%%edx) | ||
| 409 | \\ movl %%edx, (%[edx_offset])(%%edx) | ||
| 410 | \\ movl %%ecx, (%[ecx_offset])(%%edx) | ||
| 411 | \\ movl %%eax, (%[eax_offset])(%%edx) | ||
| 412 | \\ movl (%%esp), %%ecx | ||
| 413 | \\ movl %%ecx, (%[eip_offset])(%%edx) | ||
| 414 | \\ leal 4(%%esp), %%ecx | ||
| 415 | \\ movl %%ecx, (%[esp_offset])(%%edx) | ||
| 416 | \\ xorl %%ecx, %%ecx | ||
| 417 | \\ movw %%fs, %%cx | ||
| 418 | \\ movl %%ecx, (%[fs_offset])(%%edx) | ||
| 419 | \\ leal (%[regspace_offset])(%%edx), %%ecx | ||
| 420 | \\ movl %%ecx, (%[fpregs_offset])(%%edx) | ||
| 421 | \\ fnstenv (%%ecx) | ||
| 422 | \\ fldenv (%%ecx) | ||
| 423 | \\ pushl %%ebx | ||
| 424 | \\ pushl %%esi | ||
| 425 | \\ xorl %%ebx, %%ebx | ||
| 426 | \\ movl %[sigaltstack], %%eax | ||
| 427 | \\ leal (%[stack_offset])(%%edx), %%ecx | ||
| 428 | \\ int $0x80 | ||
| 429 | \\ cmpl $0, %%eax | ||
| 430 | \\ jne return | ||
| 431 | \\ movl %[sigprocmask], %%eax | ||
| 432 | \\ xorl %%ecx, %%ecx | ||
| 433 | \\ leal (%[sigmask_offset])(%%edx), %%edx | ||
| 434 | \\ movl %[sigset_size], %%esi | ||
| 435 | \\ int $0x80 | ||
| 436 | \\ return: | ||
| 437 | \\ popl %%esi | ||
| 438 | \\ popl %%ebx | ||
| 439 | : | ||
| 440 | : [flags_offset] "p" (@offsetOf(ucontext_t, "flags")), | ||
| 441 | [link_offset] "p" (@offsetOf(ucontext_t, "link")), | ||
| 442 | [edi_offset] "p" (comptime gpRegisterOffset(REG.EDI)), | ||
| 443 | [esi_offset] "p" (comptime gpRegisterOffset(REG.ESI)), | ||
| 444 | [ebp_offset] "p" (comptime gpRegisterOffset(REG.EBP)), | ||
| 445 | [esp_offset] "p" (comptime gpRegisterOffset(REG.ESP)), | ||
| 446 | [ebx_offset] "p" (comptime gpRegisterOffset(REG.EBX)), | ||
| 447 | [edx_offset] "p" (comptime gpRegisterOffset(REG.EDX)), | ||
| 448 | [ecx_offset] "p" (comptime gpRegisterOffset(REG.ECX)), | ||
| 449 | [eax_offset] "p" (comptime gpRegisterOffset(REG.EAX)), | ||
| 450 | [eip_offset] "p" (comptime gpRegisterOffset(REG.EIP)), | ||
| 451 | [fs_offset] "p" (comptime gpRegisterOffset(REG.FS)), | ||
| 452 | [fpregs_offset] "p" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")), | ||
| 453 | [regspace_offset] "p" (@offsetOf(ucontext_t, "regspace")), | ||
| 454 | [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)), | ||
| 455 | [stack_offset] "p" (@offsetOf(ucontext_t, "stack")), | ||
| 456 | [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)), | ||
| 457 | [sigmask_offset] "p" (@offsetOf(ucontext_t, "sigmask")), | ||
| 458 | [sigset_size] "i" (linux.NSIG / 8), | ||
| 459 | : "memory", "eax", "ecx", "edx" | ||
| 460 | ); | ||
| 461 | } | ||
| 462 | |||
| 463 | pub inline fn getcontext(context: *ucontext_t) usize { | ||
| 464 | // This method is used so that getContextInternal can control | ||
| 465 | // its prologue in order to read ESP from a constant offset. | ||
| 466 | // The unused &getContextInternal input is required so the function is included in the binary. | ||
| 467 | return asm volatile ( | ||
| 468 | \\ call os.linux.x86.getContextInternal | ||
| 469 | : [ret] "={eax}" (-> usize), | ||
| 470 | : [context] "{edx}" (context), | ||
| 471 | [getContextInternal] "X" (&getContextInternal), | ||
| 472 | : "memory", "ecx" | ||
| 473 | ); | ||
| 474 | } |
lib/std/os/linux/x86_64.zig+94| ... | @@ -395,3 +395,97 @@ pub const ucontext_t = extern struct { | ... | @@ -395,3 +395,97 @@ pub const ucontext_t = extern struct { |
| 395 | sigmask: sigset_t, | 395 | sigmask: sigset_t, |
| 396 | fpregs_mem: [64]usize, | 396 | fpregs_mem: [64]usize, |
| 397 | }; | 397 | }; |
| 398 | |||
| 399 | fn gpRegisterOffset(comptime reg_index: comptime_int) usize { | ||
| 400 | return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; | ||
| 401 | } | ||
| 402 | |||
| 403 | fn getContextInternal() callconv(.Naked) void { | ||
| 404 | // TODO: Read GS/FS registers? | ||
| 405 | asm volatile ( | ||
| 406 | \\ movq $0, (%[flags_offset])(%%rdi) | ||
| 407 | \\ movq $0, (%[link_offset])(%%rdi) | ||
| 408 | \\ movq %%r8, (%[r8_offset])(%%rdi) | ||
| 409 | \\ movq %%r9, (%[r9_offset])(%%rdi) | ||
| 410 | \\ movq %%r10, (%[r10_offset])(%%rdi) | ||
| 411 | \\ movq %%r11, (%[r11_offset])(%%rdi) | ||
| 412 | \\ movq %%r12, (%[r12_offset])(%%rdi) | ||
| 413 | \\ movq %%r13, (%[r13_offset])(%%rdi) | ||
| 414 | \\ movq %%r14, (%[r14_offset])(%%rdi) | ||
| 415 | \\ movq %%r15, (%[r15_offset])(%%rdi) | ||
| 416 | \\ movq %%rdi, (%[rdi_offset])(%%rdi) | ||
| 417 | \\ movq %%rsi, (%[rsi_offset])(%%rdi) | ||
| 418 | \\ movq %%rbp, (%[rbp_offset])(%%rdi) | ||
| 419 | \\ movq %%rbx, (%[rbx_offset])(%%rdi) | ||
| 420 | \\ movq %%rdx, (%[rdx_offset])(%%rdi) | ||
| 421 | \\ movq %%rax, (%[rax_offset])(%%rdi) | ||
| 422 | \\ movq %%rcx, (%[rcx_offset])(%%rdi) | ||
| 423 | \\ movq (%%rsp), %%rcx | ||
| 424 | \\ movq %%rcx, (%[rip_offset])(%%rdi) | ||
| 425 | \\ leaq 8(%%rsp), %%rcx | ||
| 426 | \\ movq %%rcx, (%[rsp_offset])(%%rdi) | ||
| 427 | \\ pushfq | ||
| 428 | \\ popq (%[efl_offset])(%%rdi) | ||
| 429 | \\ leaq (%[fpmem_offset])(%%rdi), %%rcx | ||
| 430 | \\ movq %%rcx, (%[fpstate_offset])(%%rdi) | ||
| 431 | \\ fnstenv (%%rcx) | ||
| 432 | \\ fldenv (%%rcx) | ||
| 433 | \\ stmxcsr (%[mxcsr_offset])(%%rdi) | ||
| 434 | \\ leaq (%[stack_offset])(%%rdi), %%rsi | ||
| 435 | \\ movq %%rdi, %%r8 | ||
| 436 | \\ xorq %%rdi, %%rdi | ||
| 437 | \\ movq %[sigaltstack], %%rax | ||
| 438 | \\ syscall | ||
| 439 | \\ cmpq $0, %%rax | ||
| 440 | \\ jne return | ||
| 441 | \\ movq %[sigprocmask], %%rax | ||
| 442 | \\ xorq %%rsi, %%rsi | ||
| 443 | \\ leaq (%[sigmask_offset])(%%r8), %%rdx | ||
| 444 | \\ movq %[sigset_size], %%r10 | ||
| 445 | \\ syscall | ||
| 446 | \\ return: | ||
| 447 | : | ||
| 448 | : [flags_offset] "p" (@offsetOf(ucontext_t, "flags")), | ||
| 449 | [link_offset] "p" (@offsetOf(ucontext_t, "link")), | ||
| 450 | [r8_offset] "p" (comptime gpRegisterOffset(REG.R8)), | ||
| 451 | [r9_offset] "p" (comptime gpRegisterOffset(REG.R9)), | ||
| 452 | [r10_offset] "p" (comptime gpRegisterOffset(REG.R10)), | ||
| 453 | [r11_offset] "p" (comptime gpRegisterOffset(REG.R11)), | ||
| 454 | [r12_offset] "p" (comptime gpRegisterOffset(REG.R12)), | ||
| 455 | [r13_offset] "p" (comptime gpRegisterOffset(REG.R13)), | ||
| 456 | [r14_offset] "p" (comptime gpRegisterOffset(REG.R14)), | ||
| 457 | [r15_offset] "p" (comptime gpRegisterOffset(REG.R15)), | ||
| 458 | [rdi_offset] "p" (comptime gpRegisterOffset(REG.RDI)), | ||
| 459 | [rsi_offset] "p" (comptime gpRegisterOffset(REG.RSI)), | ||
| 460 | [rbp_offset] "p" (comptime gpRegisterOffset(REG.RBP)), | ||
| 461 | [rbx_offset] "p" (comptime gpRegisterOffset(REG.RBX)), | ||
| 462 | [rdx_offset] "p" (comptime gpRegisterOffset(REG.RDX)), | ||
| 463 | [rax_offset] "p" (comptime gpRegisterOffset(REG.RAX)), | ||
| 464 | [rcx_offset] "p" (comptime gpRegisterOffset(REG.RCX)), | ||
| 465 | [rsp_offset] "p" (comptime gpRegisterOffset(REG.RSP)), | ||
| 466 | [rip_offset] "p" (comptime gpRegisterOffset(REG.RIP)), | ||
| 467 | [efl_offset] "p" (comptime gpRegisterOffset(REG.EFL)), | ||
| 468 | [fpstate_offset] "p" (@offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "fpregs")), | ||
| 469 | [fpmem_offset] "p" (@offsetOf(ucontext_t, "fpregs_mem")), | ||
| 470 | [mxcsr_offset] "p" (@offsetOf(ucontext_t, "fpregs_mem") + @offsetOf(fpstate, "mxcsr")), | ||
| 471 | [sigaltstack] "i" (@intFromEnum(linux.SYS.sigaltstack)), | ||
| 472 | [stack_offset] "p" (@offsetOf(ucontext_t, "stack")), | ||
| 473 | [sigprocmask] "i" (@intFromEnum(linux.SYS.rt_sigprocmask)), | ||
| 474 | [sigmask_offset] "p" (@offsetOf(ucontext_t, "sigmask")), | ||
| 475 | [sigset_size] "i" (linux.NSIG / 8), | ||
| 476 | : "memory", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11" | ||
| 477 | ); | ||
| 478 | } | ||
| 479 | |||
| 480 | pub inline fn getcontext(context: *ucontext_t) usize { | ||
| 481 | // This method is used so that getContextInternal can control | ||
| 482 | // its prologue in order to read RSP from a constant offset | ||
| 483 | // The unused &getContextInternal input is required so the function is included in the binary. | ||
| 484 | return asm volatile ( | ||
| 485 | \\ call os.linux.x86_64.getContextInternal | ||
| 486 | : [ret] "={rax}" (-> usize), | ||
| 487 | : [context] "{rdi}" (context), | ||
| 488 | [getContextInternal] "X" (&getContextInternal), | ||
| 489 | : "memory", "rcx", "rdx", "rdi", "rsi", "r8", "r10", "r11" | ||
| 490 | ); | ||
| 491 | } |
lib/std/os/windows.zig+29| ... | @@ -3301,6 +3301,35 @@ pub const REGSAM = ACCESS_MASK; | ... | @@ -3301,6 +3301,35 @@ pub const REGSAM = ACCESS_MASK; |
| 3301 | pub const ACCESS_MASK = DWORD; | 3301 | pub const ACCESS_MASK = DWORD; |
| 3302 | pub const LSTATUS = LONG; | 3302 | pub const LSTATUS = LONG; |
| 3303 | 3303 | ||
| 3304 | pub const SECTION_INHERIT = enum(c_int) { | ||
| 3305 | ViewShare = 0, | ||
| 3306 | ViewUnmap = 1, | ||
| 3307 | }; | ||
| 3308 | |||
| 3309 | pub const SECTION_QUERY = 0x0001; | ||
| 3310 | pub const SECTION_MAP_WRITE = 0x0002; | ||
| 3311 | pub const SECTION_MAP_READ = 0x0004; | ||
| 3312 | pub const SECTION_MAP_EXECUTE = 0x0008; | ||
| 3313 | pub const SECTION_EXTEND_SIZE = 0x0010; | ||
| 3314 | pub const SECTION_ALL_ACCESS = | ||
| 3315 | STANDARD_RIGHTS_REQUIRED | | ||
| 3316 | SECTION_QUERY | | ||
| 3317 | SECTION_MAP_WRITE | | ||
| 3318 | SECTION_MAP_READ | | ||
| 3319 | SECTION_MAP_EXECUTE | | ||
| 3320 | SECTION_EXTEND_SIZE; | ||
| 3321 | |||
| 3322 | pub const SEC_64K_PAGES = 0x80000; | ||
| 3323 | pub const SEC_FILE = 0x800000; | ||
| 3324 | pub const SEC_IMAGE = 0x1000000; | ||
| 3325 | pub const SEC_PROTECTED_IMAGE = 0x2000000; | ||
| 3326 | pub const SEC_RESERVE = 0x4000000; | ||
| 3327 | pub const SEC_COMMIT = 0x8000000; | ||
| 3328 | pub const SEC_IMAGE_NO_EXECUTE = SEC_IMAGE | SEC_NOCACHE; | ||
| 3329 | pub const SEC_NOCACHE = 0x10000000; | ||
| 3330 | pub const SEC_WRITECOMBINE = 0x40000000; | ||
| 3331 | pub const SEC_LARGE_PAGES = 0x80000000; | ||
| 3332 | |||
| 3304 | pub const HKEY = *opaque {}; | 3333 | pub const HKEY = *opaque {}; |
| 3305 | 3334 | ||
| 3306 | pub const HKEY_LOCAL_MACHINE: HKEY = @as(HKEY, @ptrFromInt(0x80000002)); | 3335 | pub const HKEY_LOCAL_MACHINE: HKEY = @as(HKEY, @ptrFromInt(0x80000002)); |
lib/std/os/windows/ntdll.zig+26| ... | @@ -36,6 +36,7 @@ const THREADINFOCLASS = windows.THREADINFOCLASS; | ... | @@ -36,6 +36,7 @@ const THREADINFOCLASS = windows.THREADINFOCLASS; |
| 36 | const PROCESSINFOCLASS = windows.PROCESSINFOCLASS; | 36 | const PROCESSINFOCLASS = windows.PROCESSINFOCLASS; |
| 37 | const LPVOID = windows.LPVOID; | 37 | const LPVOID = windows.LPVOID; |
| 38 | const LPCVOID = windows.LPCVOID; | 38 | const LPCVOID = windows.LPCVOID; |
| 39 | const SECTION_INHERIT = windows.SECTION_INHERIT; | ||
| 39 | 40 | ||
| 40 | pub extern "ntdll" fn NtQueryInformationProcess( | 41 | pub extern "ntdll" fn NtQueryInformationProcess( |
| 41 | ProcessHandle: HANDLE, | 42 | ProcessHandle: HANDLE, |
| ... | @@ -125,6 +126,31 @@ pub extern "ntdll" fn NtCreateFile( | ... | @@ -125,6 +126,31 @@ pub extern "ntdll" fn NtCreateFile( |
| 125 | EaBuffer: ?*anyopaque, | 126 | EaBuffer: ?*anyopaque, |
| 126 | EaLength: ULONG, | 127 | EaLength: ULONG, |
| 127 | ) callconv(WINAPI) NTSTATUS; | 128 | ) callconv(WINAPI) NTSTATUS; |
| 129 | pub extern "ntdll" fn NtCreateSection( | ||
| 130 | SectionHandle: *HANDLE, | ||
| 131 | DesiredAccess: ACCESS_MASK, | ||
| 132 | ObjectAttributes: ?*OBJECT_ATTRIBUTES, | ||
| 133 | MaximumSize: ?*LARGE_INTEGER, | ||
| 134 | SectionPageProtection: ULONG, | ||
| 135 | AllocationAttributes: ULONG, | ||
| 136 | FileHandle: ?HANDLE, | ||
| 137 | ) callconv(WINAPI) NTSTATUS; | ||
| 138 | pub extern "ntdll" fn NtMapViewOfSection( | ||
| 139 | SectionHandle: HANDLE, | ||
| 140 | ProcessHandle: HANDLE, | ||
| 141 | BaseAddress: *PVOID, | ||
| 142 | ZeroBits: ?*ULONG, | ||
| 143 | CommitSize: SIZE_T, | ||
| 144 | SectionOffset: ?*LARGE_INTEGER, | ||
| 145 | ViewSize: *SIZE_T, | ||
| 146 | InheritDispostion: SECTION_INHERIT, | ||
| 147 | AllocationType: ULONG, | ||
| 148 | Win32Protect: ULONG, | ||
| 149 | ) callconv(WINAPI) NTSTATUS; | ||
| 150 | pub extern "ntdll" fn NtUnmapViewOfSection( | ||
| 151 | ProcessHandle: HANDLE, | ||
| 152 | BaseAddress: PVOID, | ||
| 153 | ) callconv(WINAPI) NTSTATUS; | ||
| 128 | pub extern "ntdll" fn NtDeviceIoControlFile( | 154 | pub extern "ntdll" fn NtDeviceIoControlFile( |
| 129 | FileHandle: HANDLE, | 155 | FileHandle: HANDLE, |
| 130 | Event: ?HANDLE, | 156 | Event: ?HANDLE, |
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/crash_report.zig+12-58| ... | @@ -203,53 +203,11 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any | ... | @@ -203,53 +203,11 @@ fn handleSegfaultPosix(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const any |
| 203 | }; | 203 | }; |
| 204 | 204 | ||
| 205 | const stack_ctx: StackContext = switch (builtin.cpu.arch) { | 205 | const stack_ctx: StackContext = switch (builtin.cpu.arch) { |
| 206 | .x86 => ctx: { | 206 | .x86, |
| 207 | const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | 207 | .x86_64, |
| 208 | const ip = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EIP])); | 208 | .arm, |
| 209 | const bp = @as(usize, @intCast(ctx.mcontext.gregs[os.REG.EBP])); | 209 | .aarch64, |
| 210 | break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } }; | 210 | => StackContext{ .exception = @ptrCast(@alignCast(ctx_ptr)) }, |
| 211 | }, | ||
| 212 | .x86_64 => ctx: { | ||
| 213 | const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | ||
| 214 | const ip = switch (builtin.os.tag) { | ||
| 215 | .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RIP])), | ||
| 216 | .freebsd => @as(usize, @intCast(ctx.mcontext.rip)), | ||
| 217 | .openbsd => @as(usize, @intCast(ctx.sc_rip)), | ||
| 218 | .macos => @as(usize, @intCast(ctx.mcontext.ss.rip)), | ||
| 219 | else => unreachable, | ||
| 220 | }; | ||
| 221 | const bp = switch (builtin.os.tag) { | ||
| 222 | .linux, .netbsd, .solaris => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.RBP])), | ||
| 223 | .openbsd => @as(usize, @intCast(ctx.sc_rbp)), | ||
| 224 | .freebsd => @as(usize, @intCast(ctx.mcontext.rbp)), | ||
| 225 | .macos => @as(usize, @intCast(ctx.mcontext.ss.rbp)), | ||
| 226 | else => unreachable, | ||
| 227 | }; | ||
| 228 | break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } }; | ||
| 229 | }, | ||
| 230 | .arm => ctx: { | ||
| 231 | const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | ||
| 232 | const ip = @as(usize, @intCast(ctx.mcontext.arm_pc)); | ||
| 233 | const bp = @as(usize, @intCast(ctx.mcontext.arm_fp)); | ||
| 234 | break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } }; | ||
| 235 | }, | ||
| 236 | .aarch64 => ctx: { | ||
| 237 | const ctx: *const os.ucontext_t = @ptrCast(@alignCast(ctx_ptr)); | ||
| 238 | const ip = switch (native_os) { | ||
| 239 | .macos => @as(usize, @intCast(ctx.mcontext.ss.pc)), | ||
| 240 | .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.PC])), | ||
| 241 | .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.elr)), | ||
| 242 | else => @as(usize, @intCast(ctx.mcontext.pc)), | ||
| 243 | }; | ||
| 244 | // x29 is the ABI-designated frame pointer | ||
| 245 | const bp = switch (native_os) { | ||
| 246 | .macos => @as(usize, @intCast(ctx.mcontext.ss.fp)), | ||
| 247 | .netbsd => @as(usize, @intCast(ctx.mcontext.gregs[os.REG.FP])), | ||
| 248 | .freebsd => @as(usize, @intCast(ctx.mcontext.gpregs.x[os.REG.FP])), | ||
| 249 | else => @as(usize, @intCast(ctx.mcontext.regs[29])), | ||
| 250 | }; | ||
| 251 | break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } }; | ||
| 252 | }, | ||
| 253 | else => .not_supported, | 211 | else => .not_supported, |
| 254 | }; | 212 | }; |
| 255 | 213 | ||
| ... | @@ -275,10 +233,9 @@ fn handleSegfaultWindows(info: *os.windows.EXCEPTION_POINTERS) callconv(os.windo | ... | @@ -275,10 +233,9 @@ fn handleSegfaultWindows(info: *os.windows.EXCEPTION_POINTERS) callconv(os.windo |
| 275 | fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn { | 233 | fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn { |
| 276 | PanicSwitch.preDispatch(); | 234 | PanicSwitch.preDispatch(); |
| 277 | 235 | ||
| 278 | const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) ctx: { | 236 | const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) |
| 279 | const regs = info.ContextRecord.getRegs(); | 237 | StackContext{ .exception = info.ContextRecord } |
| 280 | break :ctx StackContext{ .exception = .{ .bp = regs.bp, .ip = regs.ip } }; | 238 | else ctx: { |
| 281 | } else ctx: { | ||
| 282 | const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress); | 239 | const addr = @intFromPtr(info.ExceptionRecord.ExceptionAddress); |
| 283 | break :ctx StackContext{ .current = .{ .ret_addr = addr } }; | 240 | break :ctx StackContext{ .current = .{ .ret_addr = addr } }; |
| 284 | }; | 241 | }; |
| ... | @@ -293,7 +250,7 @@ fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg | ... | @@ -293,7 +250,7 @@ fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg |
| 293 | }, | 250 | }, |
| 294 | .illegal_instruction => { | 251 | .illegal_instruction => { |
| 295 | const ip: ?usize = switch (stack_ctx) { | 252 | const ip: ?usize = switch (stack_ctx) { |
| 296 | .exception => |ex| ex.ip, | 253 | .exception => |ex| ex.getRegs().ip, |
| 297 | .current => |cur| cur.ret_addr, | 254 | .current => |cur| cur.ret_addr, |
| 298 | .not_supported => null, | 255 | .not_supported => null, |
| 299 | }; | 256 | }; |
| ... | @@ -314,10 +271,7 @@ const StackContext = union(enum) { | ... | @@ -314,10 +271,7 @@ const StackContext = union(enum) { |
| 314 | current: struct { | 271 | current: struct { |
| 315 | ret_addr: ?usize, | 272 | ret_addr: ?usize, |
| 316 | }, | 273 | }, |
| 317 | exception: struct { | 274 | exception: *const debug.ThreadContext, |
| 318 | bp: usize, | ||
| 319 | ip: usize, | ||
| 320 | }, | ||
| 321 | not_supported: void, | 275 | not_supported: void, |
| 322 | 276 | ||
| 323 | pub fn dumpStackTrace(ctx: @This()) void { | 277 | pub fn dumpStackTrace(ctx: @This()) void { |
| ... | @@ -325,8 +279,8 @@ const StackContext = union(enum) { | ... | @@ -325,8 +279,8 @@ const StackContext = union(enum) { |
| 325 | .current => |ct| { | 279 | .current => |ct| { |
| 326 | debug.dumpCurrentStackTrace(ct.ret_addr); | 280 | debug.dumpCurrentStackTrace(ct.ret_addr); |
| 327 | }, | 281 | }, |
| 328 | .exception => |ex| { | 282 | .exception => |context| { |
| 329 | debug.dumpStackTraceFromBase(ex.bp, ex.ip); | 283 | debug.dumpStackTraceFromBase(context); |
| 330 | }, | 284 | }, |
| 331 | .not_supported => { | 285 | .not_supported => { |
| 332 | const stderr = io.getStdErr().writer(); | 286 | const stderr = io.getStdErr().writer(); |
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; | 513 | return target.os.tag == .windows or target.isDarwin(); |
| 514 | } | 514 | } |
| 515 | 515 | ||
| 516 | pub fn defaultAddressSpace( | 516 | pub fn defaultAddressSpace( |
test/standalone.zig+8| ... | @@ -230,6 +230,14 @@ pub const build_cases = [_]BuildCase{ | ... | @@ -230,6 +230,14 @@ pub const build_cases = [_]BuildCase{ |
| 230 | .build_root = "test/standalone/zerolength_check", | 230 | .build_root = "test/standalone/zerolength_check", |
| 231 | .import = @import("standalone/zerolength_check/build.zig"), | 231 | .import = @import("standalone/zerolength_check/build.zig"), |
| 232 | }, | 232 | }, |
| 233 | .{ | ||
| 234 | .build_root = "test/standalone/stack_iterator", | ||
| 235 | .import = @import("standalone/stack_iterator/build.zig"), | ||
| 236 | }, | ||
| 237 | .{ | ||
| 238 | .build_root = "test/standalone/coff_dwarf", | ||
| 239 | .import = @import("standalone/coff_dwarf/build.zig"), | ||
| 240 | }, | ||
| 233 | }; | 241 | }; |
| 234 | 242 | ||
| 235 | const std = @import("std"); | 243 | const std = @import("std"); |
test/standalone/coff_dwarf/build.zig created+35| ... | @@ -0,0 +1,35 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | |||
| 4 | /// This tests the path where DWARF information is embedded in a COFF binary | ||
| 5 | pub fn build(b: *std.Build) void { | ||
| 6 | const test_step = b.step("test", "Test it"); | ||
| 7 | b.default_step = test_step; | ||
| 8 | |||
| 9 | const optimize: std.builtin.OptimizeMode = .Debug; | ||
| 10 | const target = b.standardTargetOptions(.{}); | ||
| 11 | |||
| 12 | if (builtin.os.tag != .windows) return; | ||
| 13 | |||
| 14 | const exe = b.addExecutable(.{ | ||
| 15 | .name = "main", | ||
| 16 | .root_source_file = .{ .path = "main.zig" }, | ||
| 17 | .optimize = optimize, | ||
| 18 | .target = target, | ||
| 19 | }); | ||
| 20 | |||
| 21 | const lib = b.addSharedLibrary(.{ | ||
| 22 | .name = "shared_lib", | ||
| 23 | .optimize = optimize, | ||
| 24 | .target = target, | ||
| 25 | }); | ||
| 26 | lib.addCSourceFile("shared_lib.c", &.{"-gdwarf"}); | ||
| 27 | lib.linkLibC(); | ||
| 28 | exe.linkLibrary(lib); | ||
| 29 | |||
| 30 | const run = b.addRunArtifact(exe); | ||
| 31 | run.expectExitCode(0); | ||
| 32 | run.skip_foreign_checks = true; | ||
| 33 | |||
| 34 | test_step.dependOn(&run.step); | ||
| 35 | } | ||
test/standalone/coff_dwarf/main.zig created+27| ... | @@ -0,0 +1,27 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const testing = std.testing; | ||
| 4 | |||
| 5 | extern fn add(a: u32, b: u32, addr: *usize) u32; | ||
| 6 | |||
| 7 | pub fn main() !void { | ||
| 8 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | ||
| 9 | defer assert(gpa.deinit() == .ok); | ||
| 10 | const allocator = gpa.allocator(); | ||
| 11 | |||
| 12 | var debug_info = try std.debug.openSelfDebugInfo(allocator); | ||
| 13 | defer debug_info.deinit(); | ||
| 14 | |||
| 15 | var add_addr: usize = undefined; | ||
| 16 | _ = add(1, 2, &add_addr); | ||
| 17 | |||
| 18 | const module = try debug_info.getModuleForAddress(add_addr); | ||
| 19 | const symbol = try module.getSymbolAtAddress(allocator, add_addr); | ||
| 20 | defer symbol.deinit(allocator); | ||
| 21 | |||
| 22 | try testing.expectEqualStrings("add", symbol.symbol_name); | ||
| 23 | try testing.expect(symbol.line_info != null); | ||
| 24 | try testing.expectEqualStrings("shared_lib.c", std.fs.path.basename(symbol.line_info.?.file_name)); | ||
| 25 | try testing.expectEqual(@as(u64, 3), symbol.line_info.?.line); | ||
| 26 | try testing.expectEqual(@as(u64, 0), symbol.line_info.?.column); | ||
| 27 | } | ||
test/standalone/coff_dwarf/shared_lib.c created+6| ... | @@ -0,0 +1,6 @@ | ||
| 1 | #include <stdint.h> | ||
| 2 | |||
| 3 | __declspec(dllexport) uint32_t add(uint32_t a, uint32_t b, uintptr_t* addr) { | ||
| 4 | *addr = (uintptr_t)&add; | ||
| 5 | return a + b; | ||
| 6 | } | ||
test/standalone/stack_iterator/build.zig created+94| ... | @@ -0,0 +1,94 @@ | ||
| 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 with a frame pointer | ||
| 11 | // | ||
| 12 | // getcontext version: zig std | ||
| 13 | // | ||
| 14 | // Unwind info type: | ||
| 15 | // - ELF: DWARF .debug_frame | ||
| 16 | // - MachO: __unwind_info encodings: | ||
| 17 | // - x86_64: RBP_FRAME | ||
| 18 | // - aarch64: FRAME, DWARF | ||
| 19 | { | ||
| 20 | const exe = b.addExecutable(.{ | ||
| 21 | .name = "unwind_fp", | ||
| 22 | .root_source_file = .{ .path = "unwind.zig" }, | ||
| 23 | .target = target, | ||
| 24 | .optimize = optimize, | ||
| 25 | }); | ||
| 26 | |||
| 27 | if (target.isDarwin()) exe.unwind_tables = true; | ||
| 28 | exe.omit_frame_pointer = false; | ||
| 29 | |||
| 30 | const run_cmd = b.addRunArtifact(exe); | ||
| 31 | test_step.dependOn(&run_cmd.step); | ||
| 32 | } | ||
| 33 | |||
| 34 | // Unwinding without a frame pointer | ||
| 35 | // | ||
| 36 | // getcontext version: zig std | ||
| 37 | // | ||
| 38 | // Unwind info type: | ||
| 39 | // - ELF: DWARF .eh_frame_hdr + .eh_frame | ||
| 40 | // - MachO: __unwind_info encodings: | ||
| 41 | // - x86_64: STACK_IMMD, STACK_IND | ||
| 42 | // - aarch64: FRAMELESS, DWARF | ||
| 43 | { | ||
| 44 | const exe = b.addExecutable(.{ | ||
| 45 | .name = "unwind_nofp", | ||
| 46 | .root_source_file = .{ .path = "unwind.zig" }, | ||
| 47 | .target = target, | ||
| 48 | .optimize = optimize, | ||
| 49 | }); | ||
| 50 | |||
| 51 | exe.omit_frame_pointer = true; | ||
| 52 | exe.unwind_tables = true; | ||
| 53 | |||
| 54 | const run_cmd = b.addRunArtifact(exe); | ||
| 55 | test_step.dependOn(&run_cmd.step); | ||
| 56 | } | ||
| 57 | |||
| 58 | // Unwinding through a C shared library without a frame pointer (libc) | ||
| 59 | // | ||
| 60 | // getcontext version: libc | ||
| 61 | // | ||
| 62 | // Unwind info type: | ||
| 63 | // - ELF: DWARF .eh_frame + .debug_frame | ||
| 64 | // - MachO: __unwind_info encodings: | ||
| 65 | // - x86_64: STACK_IMMD, STACK_IND | ||
| 66 | // - aarch64: FRAMELESS, DWARF | ||
| 67 | { | ||
| 68 | const c_shared_lib = b.addSharedLibrary(.{ | ||
| 69 | .name = "c_shared_lib", | ||
| 70 | .target = target, | ||
| 71 | .optimize = optimize, | ||
| 72 | }); | ||
| 73 | |||
| 74 | if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)"); | ||
| 75 | |||
| 76 | c_shared_lib.strip = false; | ||
| 77 | c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"}); | ||
| 78 | c_shared_lib.linkLibC(); | ||
| 79 | |||
| 80 | const exe = b.addExecutable(.{ | ||
| 81 | .name = "shared_lib_unwind", | ||
| 82 | .root_source_file = .{ .path = "shared_lib_unwind.zig" }, | ||
| 83 | .target = target, | ||
| 84 | .optimize = optimize, | ||
| 85 | }); | ||
| 86 | |||
| 87 | if (target.isDarwin()) exe.unwind_tables = true; | ||
| 88 | exe.omit_frame_pointer = true; | ||
| 89 | exe.linkLibrary(c_shared_lib); | ||
| 90 | |||
| 91 | const run_cmd = b.addRunArtifact(exe); | ||
| 92 | test_step.dependOn(&run_cmd.step); | ||
| 93 | } | ||
| 94 | } | ||
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+47| ... | @@ -0,0 +1,47 @@ | ||
| 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 | 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 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.isDarwin() and builtin.cpu.arch == .aarch64) return; | ||
| 40 | |||
| 41 | if (!std.debug.have_ucontext or !std.debug.have_getcontext) return; | ||
| 42 | |||
| 43 | var expected: [5]usize = undefined; | ||
| 44 | var unwound: [5]usize = undefined; | ||
| 45 | frame0(&expected, &unwound, &frame2); | ||
| 46 | try testing.expectEqual(expected, unwound); | ||
| 47 | } | ||
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 | } | ||