| ... | ... | @@ -2064,3 +2064,315 @@ pub const UNWIND_ARM64_FRAME_D14_D15_PAIR: u32 = 0x00000800; |
| 2064 | 2064 | |
| 2065 | 2065 | pub const UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK: u32 = 0x00FFF000; |
| 2066 | 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_adjust: u3, |
| 2084 | stack_size: u8, |
| 2085 | }, |
| 2086 | dwarf: u24, |
| 2087 | }, |
| 2088 | arm64: packed union { |
| 2089 | frame: packed struct(u24) { |
| 2090 | x_reg_pairs: packed struct { |
| 2091 | x19_x20: u1, |
| 2092 | x21_x22: u1, |
| 2093 | x23_x24: u1, |
| 2094 | x25_x26: u1, |
| 2095 | x27_x28: u1, |
| 2096 | }, |
| 2097 | d_reg_pairs: packed struct { |
| 2098 | d8_d9: u1, |
| 2099 | d10_d11: u1, |
| 2100 | d12_d13: u1, |
| 2101 | d14_d15: u1, |
| 2102 | }, |
| 2103 | unused: u15, |
| 2104 | }, |
| 2105 | frameless: packed struct(u24) { |
| 2106 | unused: u12 = 0, |
| 2107 | stack_size: u12, |
| 2108 | }, |
| 2109 | dwarf: u24, |
| 2110 | }, |
| 2111 | }, |
| 2112 | mode: packed union { |
| 2113 | x86_64: UNWIND_X86_64_MODE, |
| 2114 | arm64: UNWIND_ARM64_MODE, |
| 2115 | }, |
| 2116 | personality_index: u2, |
| 2117 | has_lsda: u1, |
| 2118 | start: u1, |
| 2119 | }; |
| 2120 | |
| 2121 | /// Returns the DWARF register number for an x86_64 register number found in compact unwind info |
| 2122 | fn dwarfRegNumber(unwind_reg_number: u3) !u8 { |
| 2123 | return switch (unwind_reg_number) { |
| 2124 | 1 => 3, // RBX |
| 2125 | 2 => 12, // R12 |
| 2126 | 3 => 13, // R13 |
| 2127 | 4 => 14, // R14 |
| 2128 | 5 => 15, // R15 |
| 2129 | 6 => 6, // RBP |
| 2130 | else => error.InvalidUnwindRegisterNumber, |
| 2131 | }; |
| 2132 | } |
| 2133 | |
| 2134 | const dwarf = std.dwarf; |
| 2135 | const abi = dwarf.abi; |
| 2136 | |
| 2137 | pub fn unwindFrame(context: *dwarf.UnwindContext, unwind_info: []const u8, module_base_address: usize) !usize { |
| 2138 | const header = mem.bytesAsValue( |
| 2139 | unwind_info_section_header, |
| 2140 | unwind_info[0..@sizeOf(unwind_info_section_header)], |
| 2141 | ); |
| 2142 | const indices = mem.bytesAsSlice( |
| 2143 | unwind_info_section_header_index_entry, |
| 2144 | unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(unwind_info_section_header_index_entry)], |
| 2145 | ); |
| 2146 | if (indices.len == 0) return error.MissingUnwindInfo; |
| 2147 | |
| 2148 | const mapped_pc = context.pc - module_base_address; |
| 2149 | const second_level_index = blk: { |
| 2150 | var left: usize = 0; |
| 2151 | var len: usize = indices.len; |
| 2152 | |
| 2153 | while (len > 1) { |
| 2154 | const mid = left + len / 2; |
| 2155 | const offset = indices[mid].functionOffset; |
| 2156 | if (mapped_pc < offset) { |
| 2157 | len /= 2; |
| 2158 | } else { |
| 2159 | left = mid; |
| 2160 | if (mapped_pc == offset) break; |
| 2161 | len -= len / 2; |
| 2162 | } |
| 2163 | } |
| 2164 | |
| 2165 | // Last index is a sentinel containing the highest address as its functionOffset |
| 2166 | if (len == 0 or indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; |
| 2167 | break :blk &indices[left]; |
| 2168 | }; |
| 2169 | |
| 2170 | const common_encodings = mem.bytesAsSlice( |
| 2171 | compact_unwind_encoding_t, |
| 2172 | unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(compact_unwind_encoding_t)], |
| 2173 | ); |
| 2174 | |
| 2175 | const start_offset = second_level_index.secondLevelPagesSectionOffset; |
| 2176 | const kind = mem.bytesAsValue( |
| 2177 | UNWIND_SECOND_LEVEL, |
| 2178 | unwind_info[start_offset..][0..@sizeOf(UNWIND_SECOND_LEVEL)], |
| 2179 | ); |
| 2180 | const raw_encoding = switch (kind.*) { |
| 2181 | .REGULAR => blk: { |
| 2182 | const page_header = mem.bytesAsValue( |
| 2183 | unwind_info_regular_second_level_page_header, |
| 2184 | unwind_info[start_offset..][0..@sizeOf(unwind_info_regular_second_level_page_header)], |
| 2185 | ); |
| 2186 | |
| 2187 | const entries = mem.bytesAsSlice( |
| 2188 | unwind_info_regular_second_level_entry, |
| 2189 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(unwind_info_regular_second_level_entry)], |
| 2190 | ); |
| 2191 | if (entries.len == 0) return error.InvalidUnwindInfo; |
| 2192 | |
| 2193 | var left: usize = 0; |
| 2194 | var len: usize = entries.len; |
| 2195 | while (len > 1) { |
| 2196 | const mid = left + len / 2; |
| 2197 | const offset = entries[mid].functionOffset; |
| 2198 | if (mapped_pc < offset) { |
| 2199 | len /= 2; |
| 2200 | } else { |
| 2201 | left = mid; |
| 2202 | if (mapped_pc == offset) break; |
| 2203 | len -= len / 2; |
| 2204 | } |
| 2205 | } |
| 2206 | |
| 2207 | if (len == 0) return error.InvalidUnwindInfo; |
| 2208 | break :blk entries[left].encoding; |
| 2209 | }, |
| 2210 | .COMPRESSED => blk: { |
| 2211 | const page_header = mem.bytesAsValue( |
| 2212 | unwind_info_compressed_second_level_page_header, |
| 2213 | unwind_info[start_offset..][0..@sizeOf(unwind_info_compressed_second_level_page_header)], |
| 2214 | ); |
| 2215 | |
| 2216 | const entries = mem.bytesAsSlice( |
| 2217 | UnwindInfoCompressedEntry, |
| 2218 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(UnwindInfoCompressedEntry)], |
| 2219 | ); |
| 2220 | if (entries.len == 0) return error.InvalidUnwindInfo; |
| 2221 | |
| 2222 | var left: usize = 0; |
| 2223 | var len: usize = entries.len; |
| 2224 | while (len > 1) { |
| 2225 | const mid = left + len / 2; |
| 2226 | const offset = second_level_index.functionOffset + entries[mid].funcOffset; |
| 2227 | if (mapped_pc < offset) { |
| 2228 | len /= 2; |
| 2229 | } else { |
| 2230 | left = mid; |
| 2231 | if (mapped_pc == offset) break; |
| 2232 | len -= len / 2; |
| 2233 | } |
| 2234 | } |
| 2235 | |
| 2236 | if (len == 0) return error.InvalidUnwindInfo; |
| 2237 | const entry = entries[left]; |
| 2238 | if (entry.encodingIndex < header.commonEncodingsArrayCount) { |
| 2239 | if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; |
| 2240 | break :blk common_encodings[entry.encodingIndex]; |
| 2241 | } else { |
| 2242 | const local_index = try std.math.sub( |
| 2243 | u8, |
| 2244 | entry.encodingIndex, |
| 2245 | std.math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, |
| 2246 | ); |
| 2247 | const local_encodings = mem.bytesAsSlice( |
| 2248 | compact_unwind_encoding_t, |
| 2249 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(compact_unwind_encoding_t)], |
| 2250 | ); |
| 2251 | if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; |
| 2252 | break :blk local_encodings[local_index]; |
| 2253 | } |
| 2254 | }, |
| 2255 | else => return error.InvalidUnwindInfo, |
| 2256 | }; |
| 2257 | |
| 2258 | if (raw_encoding == 0) return error.NoUnwindInfo; |
| 2259 | const reg_context = dwarf.abi.RegisterContext{ |
| 2260 | .eh_frame = false, |
| 2261 | .is_macho = true, |
| 2262 | }; |
| 2263 | |
| 2264 | const encoding: CompactUnwindEncoding = @bitCast(raw_encoding); |
| 2265 | const new_ip = switch (builtin.cpu.arch) { |
| 2266 | .x86_64 => switch (encoding.mode.x86_64) { |
| 2267 | .OLD => return error.UnimplementedUnwindEncoding, |
| 2268 | .RBP_FRAME => blk: { |
| 2269 | const regs: [5]u3 = .{ |
| 2270 | encoding.value.x86_64.frame.reg0, |
| 2271 | encoding.value.x86_64.frame.reg1, |
| 2272 | encoding.value.x86_64.frame.reg2, |
| 2273 | encoding.value.x86_64.frame.reg3, |
| 2274 | encoding.value.x86_64.frame.reg4, |
| 2275 | }; |
| 2276 | |
| 2277 | const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); |
| 2278 | var max_reg: usize = 0; |
| 2279 | inline for (regs, 0..) |reg, i| { |
| 2280 | if (reg > 0) max_reg = i; |
| 2281 | } |
| 2282 | |
| 2283 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; |
| 2284 | const new_sp = fp + 2 * @sizeOf(usize); |
| 2285 | |
| 2286 | // Verify the stack range we're about to read register values from is valid |
| 2287 | if (!context.isValidMemory(new_sp) or !context.isValidMemory(fp - frame_offset + max_reg * @sizeOf(usize))) return error.InvalidUnwindInfo; |
| 2288 | |
| 2289 | const ip_ptr = fp + @sizeOf(usize); |
| 2290 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; |
| 2291 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; |
| 2292 | |
| 2293 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; |
| 2294 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; |
| 2295 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; |
| 2296 | |
| 2297 | for (regs, 0..) |reg, i| { |
| 2298 | if (reg == 0) continue; |
| 2299 | const addr = fp - frame_offset + i * @sizeOf(usize); |
| 2300 | const reg_number = try dwarfRegNumber(reg); |
| 2301 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; |
| 2302 | } |
| 2303 | |
| 2304 | break :blk new_ip; |
| 2305 | }, |
| 2306 | .STACK_IMMD => blk: { |
| 2307 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; |
| 2308 | |
| 2309 | // Decode Lehmer-coded sequence of registers. |
| 2310 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h |
| 2311 | |
| 2312 | // Decode the variable-based permutation number into its digits. Each digit represents |
| 2313 | // an index into the list of register numbers that weren't yet used in the sequence at |
| 2314 | // the time the digit was added. |
| 2315 | const reg_count = encoding.value.x86_64.frameless.stack_reg_count; |
| 2316 | const ip_ptr = if (reg_count > 0) reg_blk: { |
| 2317 | var digits: [6]u3 = undefined; |
| 2318 | var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; |
| 2319 | var base: usize = 2; |
| 2320 | for (0..reg_count) |i| { |
| 2321 | const div = accumulator / base; |
| 2322 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); |
| 2323 | accumulator = div; |
| 2324 | base += 1; |
| 2325 | } |
| 2326 | |
| 2327 | const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; |
| 2328 | var registers: [reg_numbers.len]u3 = undefined; |
| 2329 | var used_indices = [_]bool{false} ** reg_numbers.len; |
| 2330 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { |
| 2331 | var unused_count: u8 = 0; |
| 2332 | const unused_index = for (used_indices, 0..) |used, index| { |
| 2333 | if (!used) { |
| 2334 | if (target_unused_index == unused_count) break index; |
| 2335 | unused_count += 1; |
| 2336 | } |
| 2337 | } else unreachable; |
| 2338 | |
| 2339 | registers[i] = reg_numbers[unused_index]; |
| 2340 | used_indices[unused_index] = true; |
| 2341 | } |
| 2342 | |
| 2343 | var reg_addr = sp + @as(usize, (encoding.value.x86_64.frameless.stack_size - reg_count - 1)) * @sizeOf(usize); |
| 2344 | if (!context.isValidMemory(reg_addr)) return error.InvalidUnwindInfo; |
| 2345 | for (0..reg_count) |i| { |
| 2346 | const reg_number = try dwarfRegNumber(registers[i]); |
| 2347 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; |
| 2348 | reg_addr += @sizeOf(usize); |
| 2349 | } |
| 2350 | |
| 2351 | break :reg_blk reg_addr; |
| 2352 | } else sp + @as(usize, (encoding.value.x86_64.frameless.stack_size - 1)) * @sizeOf(usize); |
| 2353 | |
| 2354 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; |
| 2355 | const new_sp = ip_ptr + @sizeOf(usize); |
| 2356 | if (!context.isValidMemory(new_sp)) return error.InvalidUnwindInfo; |
| 2357 | |
| 2358 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; |
| 2359 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; |
| 2360 | |
| 2361 | break :blk new_ip; |
| 2362 | }, |
| 2363 | .STACK_IND => { |
| 2364 | return error.UnimplementedUnwindEncoding; // TODO |
| 2365 | }, |
| 2366 | .DWARF => return error.RequiresDWARFUnwind, |
| 2367 | }, |
| 2368 | .aarch64 => switch (encoding.mode.x86_64) { |
| 2369 | .DWARF => return error.RequiresDWARFUnwind, |
| 2370 | else => return error.UnimplementedUnwindEncoding, |
| 2371 | }, |
| 2372 | else => return error.UnimplementedArch, |
| 2373 | }; |
| 2374 | |
| 2375 | context.pc = new_ip; |
| 2376 | if (context.pc > 0) context.pc -= 1; |
| 2377 | return new_ip; |
| 2378 | } |