| 1 | //! Contains state relevant to stack unwinding through the DWARF `.debug_frame` section, or the |
| 2 | //! `.eh_frame` section which is an extension of the former specified by Linux Standard Base Core. |
| 3 | //! Like `Dwarf`, no assumptions are made about the host's relationship to the target of the unwind |
| 4 | //! information -- unwind data for any target can be read by any host. |
| 5 | //! |
| 6 | //! `Unwind` specifically deals with loading the data from CIEs and FDEs in the section, and with |
| 7 | //! performing fast lookups of a program counter's corresponding FDE. The CFI instructions in the |
| 8 | //! CIEs and FDEs can be interpreted by `VirtualMachine`. |
| 9 | //! |
| 10 | //! The typical usage of `Unwind` is as follows: |
| 11 | //! |
| 12 | //! * Initialize with `initEhFrameHdr` or `initSection`, depending on the available data |
| 13 | //! * Call `prepare` to scan CIEs and, if necessary, construct a search table |
| 14 | //! * Call `lookupPc` to find the section offset of the FDE corresponding to a PC |
| 15 | //! * Call `getFde` to load the corresponding FDE and CIE |
| 16 | //! * Check that the PC does indeed fall in that range (`lookupPc` may return a false positive) |
| 17 | //! * Interpret the embedded CFI instructions using `VirtualMachine` |
| 18 | //! |
| 19 | //! In some cases, such as when using the "compact unwind" data in Mach-O binaries, the FDE offsets |
| 20 | //! may already be known. In that case, no call to `lookupPc` is necessary, which means the call to |
| 21 | //! `prepare` can be optimized to only scan CIEs. |
| 22 | |
| 23 | pub const VirtualMachine = @import("Unwind/VirtualMachine.zig"); |
| 24 | |
| 25 | frame_section: struct { |
| 26 | id: Section, |
| 27 | /// The virtual address of the start of the section. "Virtual address" refers to the address in |
| 28 | /// the binary (e.g. `sh_addr` in an ELF file); the equivalent runtime address may be relocated |
| 29 | /// in position-independent binaries. |
| 30 | vaddr: u64, |
| 31 | /// The full contents of the section. May have imprecise bounds depending on `section`. This |
| 32 | /// memory is externally managed. |
| 33 | /// |
| 34 | /// For `.debug_frame`, the slice length is exactly equal to the section length. This is needed |
| 35 | /// to know the number of CIEs and FDEs. |
| 36 | /// |
| 37 | /// For `.eh_frame`, the slice length may exceed the section length, i.e. the slice may refer to |
| 38 | /// more bytes than are in the second. This restriction exists because `.eh_frame_hdr` only |
| 39 | /// includes the address of the loaded `.eh_frame` data, not its length. It is not a problem |
| 40 | /// because unlike `.debug_frame`, the end of the CIE/FDE list is signaled through a sentinel |
| 41 | /// value. If this slice does have bounds, they will still be checked, preventing crashes when |
| 42 | /// reading potentially-invalid `.eh_frame` data from files. |
| 43 | bytes: []const u8, |
| 44 | }, |
| 45 | |
| 46 | /// A structure allowing fast lookups of the FDE corresponding to a particular PC. We use a binary |
| 47 | /// search table for the lookup; essentially, a list of all FDEs ordered by PC range. `null` means |
| 48 | /// the lookup data is not yet populated, so `prepare` must be called before `lookupPc`. |
| 49 | lookup: ?union(enum) { |
| 50 | /// The `.eh_frame_hdr` section contains a pre-computed search table which we can use. |
| 51 | eh_frame_hdr: struct { |
| 52 | /// Virtual address of the `.eh_frame_hdr` section. |
| 53 | vaddr: u64, |
| 54 | table: EhFrameHeader.SearchTable, |
| 55 | }, |
| 56 | /// There is no pre-computed search table, so we have built one ourselves. |
| 57 | /// Allocated into `gpa` and freed by `deinit`. |
| 58 | sorted_fdes: []SortedFdeEntry, |
| 59 | }, |
| 60 | |
| 61 | /// Initially empty; populated by `prepare`. |
| 62 | cie_list: std.MultiArrayList(struct { |
| 63 | offset: u64, |
| 64 | cie: CommonInformationEntry, |
| 65 | }), |
| 66 | |
| 67 | const SortedFdeEntry = struct { |
| 68 | /// This FDE's value of `pc_begin`. |
| 69 | pc_begin: u64, |
| 70 | /// Offset into the section of the corresponding FDE, including the entry header. |
| 71 | fde_offset: u64, |
| 72 | }; |
| 73 | |
| 74 | pub const Section = enum { debug_frame, eh_frame }; |
| 75 | |
| 76 | /// Initialize with unwind information from a header loaded from an `.eh_frame_hdr` section, and a |
| 77 | /// pointer to the contents of the `.eh_frame` section. |
| 78 | /// |
| 79 | /// `.eh_frame_hdr` may embed a binary search table of FDEs. If it does, we will use that table for |
| 80 | /// PC lookups rather than spending time constructing our own search table. |
| 81 | pub fn initEhFrameHdr(header: EhFrameHeader, section_vaddr: u64, section_bytes_ptr: [*]const u8) Unwind { |
| 82 | return .{ |
| 83 | .frame_section = .{ |
| 84 | .id = .eh_frame, |
| 85 | .bytes = maxSlice(section_bytes_ptr), |
| 86 | .vaddr = header.eh_frame_vaddr, |
| 87 | }, |
| 88 | .lookup = if (header.search_table) |table| .{ .eh_frame_hdr = .{ |
| 89 | .vaddr = section_vaddr, |
| 90 | .table = table, |
| 91 | } } else null, |
| 92 | .cie_list = .empty, |
| 93 | }; |
| 94 | } |
| 95 | |
| 96 | /// Initialize with unwind information from the contents of a `.debug_frame` or `.eh_frame` section. |
| 97 | /// |
| 98 | /// If the `.eh_frame_hdr` section is available, consider instead using `initEhFrameHdr`, which |
| 99 | /// allows the implementation to use a search table embedded in that section if it is available. |
| 100 | pub fn initSection(section: Section, section_vaddr: u64, section_bytes: []const u8) Unwind { |
| 101 | return .{ |
| 102 | .frame_section = .{ |
| 103 | .id = section, |
| 104 | .bytes = section_bytes, |
| 105 | .vaddr = section_vaddr, |
| 106 | }, |
| 107 | .lookup = null, |
| 108 | .cie_list = .empty, |
| 109 | }; |
| 110 | } |
| 111 | |
| 112 | pub fn deinit(unwind: *Unwind, gpa: Allocator) void { |
| 113 | if (unwind.lookup) |lookup| switch (lookup) { |
| 114 | .eh_frame_hdr => {}, |
| 115 | .sorted_fdes => |fdes| gpa.free(fdes), |
| 116 | }; |
| 117 | for (unwind.cie_list.items(.cie)) |*cie| { |
| 118 | if (cie.last_row) |*lr| { |
| 119 | gpa.free(lr.cols); |
| 120 | } |
| 121 | } |
| 122 | unwind.cie_list.deinit(gpa); |
| 123 | } |
| 124 | |
| 125 | /// Decoded version of the `.eh_frame_hdr` section. |
| 126 | pub const EhFrameHeader = struct { |
| 127 | /// The virtual address (i.e. as given in the binary, before relocations) of the `.eh_frame` |
| 128 | /// section. This value is important when using `.eh_frame_hdr` to find debug information for |
| 129 | /// the current binary, because it allows locating where the `.eh_frame` section is loaded in |
| 130 | /// memory (by adding it to the ELF module's base address). |
| 131 | eh_frame_vaddr: u64, |
| 132 | search_table: ?SearchTable, |
| 133 | |
| 134 | pub const SearchTable = struct { |
| 135 | /// The byte offset of the search table into the `.eh_frame_hdr` section. |
| 136 | offset: u8, |
| 137 | encoding: EH.PE, |
| 138 | fde_count: usize, |
| 139 | /// The actual table entries are viewed as a plain byte slice because `encoding` causes the |
| 140 | /// size of entries in the table to vary. |
| 141 | entries: []const u8, |
| 142 | |
| 143 | /// Returns the vaddr of the FDE for `pc`, or `null` if no matching FDE was found. |
| 144 | fn findEntry( |
| 145 | table: *const SearchTable, |
| 146 | eh_frame_hdr_vaddr: u64, |
| 147 | pc: u64, |
| 148 | addr_size_bytes: u8, |
| 149 | endian: Endian, |
| 150 | ) !?u64 { |
| 151 | const table_vaddr = eh_frame_hdr_vaddr + table.offset; |
| 152 | const entry_size = try entrySize(table.encoding, addr_size_bytes); |
| 153 | var left: usize = 0; |
| 154 | var len: usize = table.fde_count; |
| 155 | while (len > 1) { |
| 156 | const mid = left + len / 2; |
| 157 | var entry_reader: Reader = .fixed(table.entries[mid * entry_size ..][0..entry_size]); |
| 158 | const pc_begin = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{ |
| 159 | .pc_rel_base = table_vaddr + left * entry_size, |
| 160 | .data_rel_base = eh_frame_hdr_vaddr, |
| 161 | }, endian); |
| 162 | if (pc < pc_begin) { |
| 163 | len /= 2; |
| 164 | } else { |
| 165 | left = mid; |
| 166 | len -= len / 2; |
| 167 | } |
| 168 | } |
| 169 | if (len == 0) return null; |
| 170 | var entry_reader: Reader = .fixed(table.entries[left * entry_size ..][0..entry_size]); |
| 171 | // Skip past `pc_begin`; we're now interested in the fde offset |
| 172 | _ = try readEhPointerAbs(&entry_reader, table.encoding.type, addr_size_bytes, endian); |
| 173 | const fde_ptr = try readEhPointer(&entry_reader, table.encoding, addr_size_bytes, .{ |
| 174 | .pc_rel_base = table_vaddr + left * entry_size, |
| 175 | .data_rel_base = eh_frame_hdr_vaddr, |
| 176 | }, endian); |
| 177 | return fde_ptr; |
| 178 | } |
| 179 | |
| 180 | fn entrySize(table_enc: EH.PE, addr_size_bytes: u8) !u8 { |
| 181 | return switch (table_enc.type) { |
| 182 | .absptr => 2 * addr_size_bytes, |
| 183 | .udata2, .sdata2 => 4, |
| 184 | .udata4, .sdata4 => 8, |
| 185 | .udata8, .sdata8 => 16, |
| 186 | .uleb128, .sleb128 => return bad(), // this is a binary search table; all entries must be the same size |
| 187 | _ => return bad(), |
| 188 | }; |
| 189 | } |
| 190 | }; |
| 191 | |
| 192 | pub fn parse( |
| 193 | eh_frame_hdr_vaddr: u64, |
| 194 | eh_frame_hdr_bytes: []const u8, |
| 195 | addr_size_bytes: u8, |
| 196 | endian: Endian, |
| 197 | ) !EhFrameHeader { |
| 198 | var r: Reader = .fixed(eh_frame_hdr_bytes); |
| 199 | |
| 200 | const version = try r.takeByte(); |
| 201 | if (version != 1) return bad(); |
| 202 | |
| 203 | const eh_frame_ptr_enc: EH.PE = @bitCast(try r.takeByte()); |
| 204 | const fde_count_enc: EH.PE = @bitCast(try r.takeByte()); |
| 205 | const table_enc: EH.PE = @bitCast(try r.takeByte()); |
| 206 | |
| 207 | const eh_frame_ptr = try readEhPointer(&r, eh_frame_ptr_enc, addr_size_bytes, .{ |
| 208 | .pc_rel_base = eh_frame_hdr_vaddr + r.seek, |
| 209 | }, endian); |
| 210 | |
| 211 | const table: ?SearchTable = table: { |
| 212 | if (fde_count_enc == EH.PE.omit) break :table null; |
| 213 | if (table_enc == EH.PE.omit) break :table null; |
| 214 | const fde_count = try readEhPointer(&r, fde_count_enc, addr_size_bytes, .{ |
| 215 | .pc_rel_base = eh_frame_hdr_vaddr + r.seek, |
| 216 | }, endian); |
| 217 | const entry_size = try SearchTable.entrySize(table_enc, addr_size_bytes); |
| 218 | const bytes_offset = r.seek; |
| 219 | const bytes_len = cast(usize, fde_count * entry_size) orelse return error.EndOfStream; |
| 220 | const bytes = try r.take(bytes_len); |
| 221 | break :table .{ |
| 222 | .encoding = table_enc, |
| 223 | .fde_count = @intCast(fde_count), |
| 224 | .entries = bytes, |
| 225 | .offset = @intCast(bytes_offset), |
| 226 | }; |
| 227 | }; |
| 228 | |
| 229 | return .{ |
| 230 | .eh_frame_vaddr = eh_frame_ptr, |
| 231 | .search_table = table, |
| 232 | }; |
| 233 | } |
| 234 | }; |
| 235 | |
| 236 | /// The shared header of an FDE/CIE, containing a length in bytes (DWARF's "initial length field") |
| 237 | /// and a value which differentiates CIEs from FDEs and maps FDEs to their corresponding CIEs. The |
| 238 | /// `.eh_frame` format also includes a third variation, here called `.terminator`, which acts as a |
| 239 | /// sentinel for the whole section. |
| 240 | /// |
| 241 | /// `CommonInformationEntry.parse` and `FrameDescriptionEntry.parse` expect the `EntryHeader` to |
| 242 | /// have been parsed first: they accept data stored in the `EntryHeader`, and only read the bytes |
| 243 | /// following this header. |
| 244 | const EntryHeader = union(enum) { |
| 245 | cie: struct { |
| 246 | format: Format, |
| 247 | /// Remaining bytes in the CIE. These are parseable by `CommonInformationEntry.parse`. |
| 248 | bytes_len: u64, |
| 249 | }, |
| 250 | fde: struct { |
| 251 | /// Offset into the section of the corresponding CIE, *including* its entry header. |
| 252 | cie_offset: u64, |
| 253 | /// Remaining bytes in the FDE. These are parseable by `FrameDescriptionEntry.parse`. |
| 254 | bytes_len: u64, |
| 255 | }, |
| 256 | /// The `.eh_frame` format includes terminators which indicate that the last CIE/FDE has been |
| 257 | /// reached. However, `.debug_frame` does not include such a terminator, so the caller must |
| 258 | /// keep track of how many section bytes remain when parsing all entries in `.debug_frame`. |
| 259 | terminator, |
| 260 | |
| 261 | fn read(r: *Reader, header_section_offset: u64, section: Section, endian: Endian) !EntryHeader { |
| 262 | const unit_header = try Dwarf.readUnitHeader(r, endian); |
| 263 | if (unit_header.unit_length == 0) return .terminator; |
| 264 | |
| 265 | // Next is a value which will disambiguate CIEs and FDEs. Annoyingly, LSB Core makes this |
| 266 | // value always 4-byte, whereas DWARF makes it depend on the `dwarf.Format`. |
| 267 | const cie_ptr_or_id_size: u8 = switch (section) { |
| 268 | .eh_frame => 4, |
| 269 | .debug_frame => switch (unit_header.format) { |
| 270 | .@"32" => 4, |
| 271 | .@"64" => 8, |
| 272 | }, |
| 273 | }; |
| 274 | const cie_ptr_or_id = switch (cie_ptr_or_id_size) { |
| 275 | 4 => try r.takeInt(u32, endian), |
| 276 | 8 => try r.takeInt(u64, endian), |
| 277 | else => unreachable, |
| 278 | }; |
| 279 | const remaining_bytes = unit_header.unit_length - cie_ptr_or_id_size; |
| 280 | |
| 281 | // If this entry is a CIE, then `cie_ptr_or_id` will have this value, which is different |
| 282 | // between the DWARF `.debug_frame` section and the LSB Core `.eh_frame` section. |
| 283 | const cie_id: u64 = switch (section) { |
| 284 | .eh_frame => 0, |
| 285 | .debug_frame => switch (unit_header.format) { |
| 286 | .@"32" => maxInt(u32), |
| 287 | .@"64" => maxInt(u64), |
| 288 | }, |
| 289 | }; |
| 290 | if (cie_ptr_or_id == cie_id) { |
| 291 | return .{ .cie = .{ |
| 292 | .format = unit_header.format, |
| 293 | .bytes_len = remaining_bytes, |
| 294 | } }; |
| 295 | } |
| 296 | |
| 297 | // This is an FDE -- `cie_ptr_or_id` points to the associated CIE. Unfortunately, the format |
| 298 | // of that pointer again differs between `.debug_frame` and `.eh_frame`. |
| 299 | const cie_offset = switch (section) { |
| 300 | .eh_frame => try std.math.sub(u64, header_section_offset + unit_header.header_length, cie_ptr_or_id), |
| 301 | .debug_frame => cie_ptr_or_id, |
| 302 | }; |
| 303 | return .{ .fde = .{ |
| 304 | .cie_offset = cie_offset, |
| 305 | .bytes_len = remaining_bytes, |
| 306 | } }; |
| 307 | } |
| 308 | }; |
| 309 | |
| 310 | pub const CommonInformationEntry = struct { |
| 311 | version: u8, |
| 312 | format: Format, |
| 313 | |
| 314 | /// In version 4, CIEs can specify the address size used in the CIE and associated FDEs. |
| 315 | /// This value must be used *only* to parse associated FDEs in `FrameDescriptionEntry.parse`. |
| 316 | addr_size_bytes: u8, |
| 317 | |
| 318 | /// Always 0 for versions which do not specify this (currently all versions other than 4). |
| 319 | segment_selector_size: u8, |
| 320 | |
| 321 | code_alignment_factor: u32, |
| 322 | data_alignment_factor: i32, |
| 323 | return_address_register: u8, |
| 324 | |
| 325 | fde_pointer_enc: EH.PE, |
| 326 | is_signal_frame: bool, |
| 327 | |
| 328 | augmentation_kind: AugmentationKind, |
| 329 | |
| 330 | initial_instructions: []const u8, |
| 331 | |
| 332 | last_row: ?struct { |
| 333 | offset: u64, |
| 334 | cfa: VirtualMachine.CfaRule, |
| 335 | cols: []VirtualMachine.Column, |
| 336 | }, |
| 337 | |
| 338 | pub const AugmentationKind = enum { none, gcc_eh, lsb_z }; |
| 339 | |
| 340 | /// This function expects to read the CIE starting with the version field. |
| 341 | /// The returned struct references memory backed by `cie_bytes`. |
| 342 | /// |
| 343 | /// `length_offset` specifies the offset of this CIE's length field in the |
| 344 | /// .eh_frame / .debug_frame section. |
| 345 | fn parse( |
| 346 | format: Format, |
| 347 | cie_bytes: []const u8, |
| 348 | section: Section, |
| 349 | default_addr_size_bytes: u8, |
| 350 | ) !CommonInformationEntry { |
| 351 | // We only read the data through this reader. |
| 352 | var r: Reader = .fixed(cie_bytes); |
| 353 | |
| 354 | const version = try r.takeByte(); |
| 355 | switch (section) { |
| 356 | .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, |
| 357 | .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, |
| 358 | } |
| 359 | |
| 360 | const aug_str = try r.takeSentinel(0); |
| 361 | const aug_kind: AugmentationKind = aug: { |
| 362 | if (aug_str.len == 0) break :aug .none; |
| 363 | if (aug_str[0] == 'z') break :aug .lsb_z; |
| 364 | if (std.mem.eql(u8, aug_str, "eh")) break :aug .gcc_eh; |
| 365 | return error.UnsupportedAugmentation; |
| 366 | }; |
| 367 | |
| 368 | switch (aug_kind) { |
| 369 | .none => {}, // no extra data |
| 370 | .lsb_z => {}, // no extra data yet, but there is a bit later |
| 371 | .gcc_eh => try r.discardAll(default_addr_size_bytes), // unsupported data |
| 372 | } |
| 373 | |
| 374 | const addr_size_bytes = if (version == 4) try r.takeByte() else default_addr_size_bytes; |
| 375 | const segment_selector_size: u8 = if (version == 4) try r.takeByte() else 0; |
| 376 | const code_alignment_factor = try r.takeLeb128(u32); |
| 377 | const data_alignment_factor = try r.takeLeb128(i32); |
| 378 | const return_address_register = if (version == 1) try r.takeByte() else try r.takeLeb128(u8); |
| 379 | |
| 380 | // This is where LSB's augmentation might add some data. |
| 381 | const fde_pointer_enc: EH.PE, const is_signal_frame: bool = aug: { |
| 382 | const default_fde_pointer_enc: EH.PE = .{ .type = .absptr, .rel = .abs }; |
| 383 | if (aug_kind != .lsb_z) break :aug .{ default_fde_pointer_enc, false }; |
| 384 | const aug_data_len = try r.takeLeb128(u32); |
| 385 | var aug_data: Reader = .fixed(try r.take(aug_data_len)); |
| 386 | var fde_pointer_enc: EH.PE = default_fde_pointer_enc; |
| 387 | var is_signal_frame = false; |
| 388 | for (aug_str[1..]) |byte| switch (byte) { |
| 389 | 'L' => _ = try aug_data.takeByte(), // we ignore the LSDA pointer |
| 390 | 'P' => { |
| 391 | const enc: EH.PE = @bitCast(try aug_data.takeByte()); |
| 392 | const endian: Endian = .little; // irrelevant because we're discarding the value anyway |
| 393 | _ = try readEhPointerAbs(&aug_data, enc.type, addr_size_bytes, endian); // we ignore the personality routine; endianness is irrelevant since we're discarding |
| 394 | }, |
| 395 | 'R' => fde_pointer_enc = @bitCast(try aug_data.takeByte()), |
| 396 | 'S' => is_signal_frame = true, |
| 397 | 'B', 'G' => {}, |
| 398 | else => return error.UnsupportedAugmentation, |
| 399 | }; |
| 400 | break :aug .{ fde_pointer_enc, is_signal_frame }; |
| 401 | }; |
| 402 | |
| 403 | return .{ |
| 404 | .format = format, |
| 405 | .version = version, |
| 406 | .addr_size_bytes = addr_size_bytes, |
| 407 | .segment_selector_size = segment_selector_size, |
| 408 | .code_alignment_factor = code_alignment_factor, |
| 409 | .data_alignment_factor = data_alignment_factor, |
| 410 | .return_address_register = return_address_register, |
| 411 | .fde_pointer_enc = fde_pointer_enc, |
| 412 | .is_signal_frame = is_signal_frame, |
| 413 | .augmentation_kind = aug_kind, |
| 414 | .initial_instructions = r.buffered(), |
| 415 | .last_row = null, |
| 416 | }; |
| 417 | } |
| 418 | }; |
| 419 | |
| 420 | pub const FrameDescriptionEntry = struct { |
| 421 | pc_begin: u64, |
| 422 | pc_range: u64, |
| 423 | instructions: []const u8, |
| 424 | |
| 425 | /// This function expects to read the FDE starting at the PC Begin field. |
| 426 | /// The returned struct references memory backed by `fde_bytes`. |
| 427 | fn parse( |
| 428 | /// The virtual address of the FDE we're parsing, *excluding* its entry header (i.e. the |
| 429 | /// address is after the header). If `fde_bytes` is backed by the memory of a loaded |
| 430 | /// module's `.eh_frame` section, this will equal `fde_bytes.ptr`. |
| 431 | fde_vaddr: u64, |
| 432 | fde_bytes: []const u8, |
| 433 | cie: *const CommonInformationEntry, |
| 434 | endian: Endian, |
| 435 | ) !FrameDescriptionEntry { |
| 436 | if (cie.segment_selector_size != 0) return error.UnsupportedAddrSize; |
| 437 | |
| 438 | var r: Reader = .fixed(fde_bytes); |
| 439 | |
| 440 | const pc_begin = try readEhPointer(&r, cie.fde_pointer_enc, cie.addr_size_bytes, .{ |
| 441 | .pc_rel_base = fde_vaddr, |
| 442 | }, endian); |
| 443 | |
| 444 | // I swear I'm not kidding when I say that PC Range is encoded with `cie.fde_pointer_enc`, but ignoring `rel`. |
| 445 | const pc_range = switch (try readEhPointerAbs(&r, cie.fde_pointer_enc.type, cie.addr_size_bytes, endian)) { |
| 446 | .unsigned => |x| x, |
| 447 | .signed => |x| cast(u64, x) orelse return bad(), |
| 448 | }; |
| 449 | |
| 450 | switch (cie.augmentation_kind) { |
| 451 | .none, .gcc_eh => {}, |
| 452 | .lsb_z => { |
| 453 | // There is augmentation data, but it's irrelevant to us -- it |
| 454 | // only contains the LSDA pointer, which we don't care about. |
| 455 | const aug_data_len = try r.takeLeb128(usize); |
| 456 | _ = try r.discardAll(aug_data_len); |
| 457 | }, |
| 458 | } |
| 459 | |
| 460 | return .{ |
| 461 | .pc_begin = pc_begin, |
| 462 | .pc_range = pc_range, |
| 463 | .instructions = r.buffered(), |
| 464 | }; |
| 465 | } |
| 466 | }; |
| 467 | |
| 468 | /// Builds the CIE list and FDE lookup table if they are not already built. It is required to call |
| 469 | /// this function at least once before calling `lookupPc` or `getFde`. If only `getFde` is needed, |
| 470 | /// then `need_lookup` can be set to `false` to make this function more efficient. |
| 471 | pub fn prepare( |
| 472 | unwind: *Unwind, |
| 473 | gpa: Allocator, |
| 474 | addr_size_bytes: u8, |
| 475 | endian: Endian, |
| 476 | need_lookup: bool, |
| 477 | /// The `__eh_frame` section in Mach-O binaries deviates from the standard `.eh_frame` section |
| 478 | /// in one way which this function needs to be aware of. |
| 479 | is_macho: bool, |
| 480 | ) !void { |
| 481 | if (unwind.cie_list.len > 0 and (!need_lookup or unwind.lookup != null)) return; |
| 482 | unwind.cie_list.clearRetainingCapacity(); |
| 483 | |
| 484 | if (is_macho) assert(unwind.lookup == null or unwind.lookup.? != .eh_frame_hdr); |
| 485 | |
| 486 | const section = unwind.frame_section; |
| 487 | |
| 488 | var r: Reader = .fixed(section.bytes); |
| 489 | var fde_list: std.ArrayList(SortedFdeEntry) = .empty; |
| 490 | defer fde_list.deinit(gpa); |
| 491 | |
| 492 | const saw_terminator = while (r.seek < r.buffer.len) { |
| 493 | const entry_offset = r.seek; |
| 494 | switch (try EntryHeader.read(&r, entry_offset, section.id, endian)) { |
| 495 | .cie => |cie_info| { |
| 496 | // We will pre-populate a list of CIEs for efficiency: this avoids work re-parsing |
| 497 | // them every time we look up an FDE. It also lets us cache the result of evaluating |
| 498 | // the CIE's initial CFI instructions, which is useful because in the vast majority |
| 499 | // of cases those instructions will be needed to reach the PC we are unwinding to. |
| 500 | const bytes_len = cast(usize, cie_info.bytes_len) orelse return error.EndOfStream; |
| 501 | const idx = unwind.cie_list.len; |
| 502 | try unwind.cie_list.append(gpa, .{ |
| 503 | .offset = entry_offset, |
| 504 | .cie = CommonInformationEntry.parse(cie_info.format, try r.take(bytes_len), section.id, addr_size_bytes) catch |err| switch (err) { |
| 505 | error.UnsupportedDwarfVersion, |
| 506 | error.UnsupportedAugmentation, |
| 507 | => { |
| 508 | // These are recoverable by just skipping the CIE. |
| 509 | continue; |
| 510 | }, |
| 511 | else => |e| return e, |
| 512 | }, |
| 513 | }); |
| 514 | errdefer _ = unwind.cie_list.pop().?; |
| 515 | try VirtualMachine.populateCieLastRow(gpa, &unwind.cie_list.items(.cie)[idx], addr_size_bytes, endian); |
| 516 | continue; |
| 517 | }, |
| 518 | .fde => |fde_info| { |
| 519 | const bytes_len = cast(usize, fde_info.bytes_len) orelse return error.EndOfStream; |
| 520 | if (!need_lookup) { |
| 521 | try r.discardAll(bytes_len); |
| 522 | continue; |
| 523 | } |
| 524 | const fde_vaddr = section.vaddr + r.seek; |
| 525 | const fde_bytes = try r.take(bytes_len); |
| 526 | const cie = unwind.findCie(fde_info.cie_offset) orelse continue; |
| 527 | const fde: FrameDescriptionEntry = try .parse(fde_vaddr, fde_bytes, cie, endian); |
| 528 | try fde_list.append(gpa, .{ |
| 529 | .pc_begin = fde.pc_begin, |
| 530 | .fde_offset = entry_offset, |
| 531 | }); |
| 532 | }, |
| 533 | .terminator => break true, |
| 534 | } |
| 535 | } else false; |
| 536 | const expect_terminator = switch (section.id) { |
| 537 | .eh_frame => !is_macho, // `.eh_frame` indicates the end of the CIE/FDE list with a sentinel entry, though macOS omits this |
| 538 | .debug_frame => false, // `.debug_frame` uses the section bounds and does not specify a sentinel entry |
| 539 | }; |
| 540 | if (saw_terminator != expect_terminator) return bad(); |
| 541 | |
| 542 | if (need_lookup) { |
| 543 | std.mem.sortUnstable(SortedFdeEntry, fde_list.items, {}, struct { |
| 544 | fn lessThan(ctx: void, a: SortedFdeEntry, b: SortedFdeEntry) bool { |
| 545 | ctx; |
| 546 | return a.pc_begin < b.pc_begin; |
| 547 | } |
| 548 | }.lessThan); |
| 549 | |
| 550 | // This temporary is necessary to avoid an RLS footgun where `lookup` ends up non-null `undefined` on OOM. |
| 551 | const final_fdes = try fde_list.toOwnedSlice(gpa); |
| 552 | unwind.lookup = .{ .sorted_fdes = final_fdes }; |
| 553 | } |
| 554 | } |
| 555 | |
| 556 | fn findCie(unwind: *const Unwind, offset: u64) ?*const CommonInformationEntry { |
| 557 | const offsets = unwind.cie_list.items(.offset); |
| 558 | if (offsets.len == 0) return null; |
| 559 | var start: usize = 0; |
| 560 | var len: usize = offsets.len; |
| 561 | while (len > 1) { |
| 562 | const mid = len / 2; |
| 563 | if (offset < offsets[start + mid]) { |
| 564 | len = mid; |
| 565 | } else { |
| 566 | start += mid; |
| 567 | len -= mid; |
| 568 | } |
| 569 | } |
| 570 | if (offsets[start] != offset) return null; |
| 571 | return &unwind.cie_list.items(.cie)[start]; |
| 572 | } |
| 573 | |
| 574 | /// Given a program counter value, returns the offset of the corresponding FDE, or `null` if no |
| 575 | /// matching FDE was found. The returned offset can be passed to `getFde` to load the data |
| 576 | /// associated with the FDE. |
| 577 | /// |
| 578 | /// Before calling this function, `prepare` must return successfully at least once, to ensure that |
| 579 | /// `unwind.lookup` is populated. |
| 580 | /// |
| 581 | /// The return value may be a false positive. After loading the FDE with `loadFde`, the caller must |
| 582 | /// validate that `pc` is indeed in its range -- if it is not, then no FDE matches `pc`. |
| 583 | pub fn lookupPc(unwind: *const Unwind, pc: u64, addr_size_bytes: u8, endian: Endian) !?u64 { |
| 584 | const sorted_fdes: []const SortedFdeEntry = switch (unwind.lookup.?) { |
| 585 | .eh_frame_hdr => |eh_frame_hdr| { |
| 586 | const fde_vaddr = try eh_frame_hdr.table.findEntry( |
| 587 | eh_frame_hdr.vaddr, |
| 588 | pc, |
| 589 | addr_size_bytes, |
| 590 | endian, |
| 591 | ) orelse return null; |
| 592 | return std.math.sub(u64, fde_vaddr, unwind.frame_section.vaddr) catch bad(); // convert vaddr to offset |
| 593 | }, |
| 594 | .sorted_fdes => |sorted_fdes| sorted_fdes, |
| 595 | }; |
| 596 | if (sorted_fdes.len == 0) return null; |
| 597 | var start: usize = 0; |
| 598 | var len: usize = sorted_fdes.len; |
| 599 | while (len > 1) { |
| 600 | const half = len / 2; |
| 601 | if (pc < sorted_fdes[start + half].pc_begin) { |
| 602 | len = half; |
| 603 | } else { |
| 604 | start += half; |
| 605 | len -= half; |
| 606 | } |
| 607 | } |
| 608 | // If any FDE matches, it'll be the one at `start` (maybe false positive). |
| 609 | return sorted_fdes[start].fde_offset; |
| 610 | } |
| 611 | |
| 612 | /// Get the FDE at a given offset, as well as its associated CIE. This offset typically comes from |
| 613 | /// `lookupPc`. The CFI instructions within can be evaluated with `VirtualMachine`. |
| 614 | pub fn getFde(unwind: *const Unwind, fde_offset: u64, endian: Endian) !struct { *const CommonInformationEntry, FrameDescriptionEntry } { |
| 615 | const section = unwind.frame_section; |
| 616 | |
| 617 | if (fde_offset > section.bytes.len) return error.EndOfStream; |
| 618 | var fde_reader: Reader = .fixed(section.bytes[@intCast(fde_offset)..]); |
| 619 | const fde_info = switch (try EntryHeader.read(&fde_reader, fde_offset, section.id, endian)) { |
| 620 | .fde => |info| info, |
| 621 | .cie, .terminator => return bad(), // This is meant to be an FDE |
| 622 | }; |
| 623 | |
| 624 | const cie = unwind.findCie(fde_info.cie_offset) orelse return bad(); |
| 625 | const fde: FrameDescriptionEntry = try .parse( |
| 626 | section.vaddr + fde_offset + fde_reader.seek, |
| 627 | try fde_reader.take(cast(usize, fde_info.bytes_len) orelse return error.EndOfStream), |
| 628 | cie, |
| 629 | endian, |
| 630 | ); |
| 631 | |
| 632 | return .{ cie, fde }; |
| 633 | } |
| 634 | |
| 635 | const EhPointerContext = struct { |
| 636 | /// The address of the pointer field itself |
| 637 | pc_rel_base: u64, |
| 638 | // These relative addressing modes are only used in specific cases, and |
| 639 | // might not be available / required in all parsing contexts |
| 640 | data_rel_base: ?u64 = null, |
| 641 | text_rel_base: ?u64 = null, |
| 642 | function_rel_base: ?u64 = null, |
| 643 | }; |
| 644 | /// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`. |
| 645 | fn readEhPointerAbs(r: *Reader, enc_ty: EH.PE.Type, addr_size_bytes: u8, endian: Endian) !union(enum) { |
| 646 | signed: i64, |
| 647 | unsigned: u64, |
| 648 | } { |
| 649 | return switch (enc_ty) { |
| 650 | .absptr => .{ |
| 651 | .unsigned = switch (addr_size_bytes) { |
| 652 | 2 => try r.takeInt(u16, endian), |
| 653 | 4 => try r.takeInt(u32, endian), |
| 654 | 8 => try r.takeInt(u64, endian), |
| 655 | else => return error.UnsupportedAddrSize, |
| 656 | }, |
| 657 | }, |
| 658 | .uleb128 => .{ .unsigned = try r.takeLeb128(u64) }, |
| 659 | .udata2 => .{ .unsigned = try r.takeInt(u16, endian) }, |
| 660 | .udata4 => .{ .unsigned = try r.takeInt(u32, endian) }, |
| 661 | .udata8 => .{ .unsigned = try r.takeInt(u64, endian) }, |
| 662 | .sleb128 => .{ .signed = try r.takeLeb128(i64) }, |
| 663 | .sdata2 => .{ .signed = try r.takeInt(i16, endian) }, |
| 664 | .sdata4 => .{ .signed = try r.takeInt(i32, endian) }, |
| 665 | .sdata8 => .{ .signed = try r.takeInt(i64, endian) }, |
| 666 | else => return bad(), |
| 667 | }; |
| 668 | } |
| 669 | /// Returns `error.InvalidDebugInfo` if the encoding is `EH.PE.omit`. |
| 670 | fn readEhPointer(r: *Reader, enc: EH.PE, addr_size_bytes: u8, ctx: EhPointerContext, endian: Endian) !u64 { |
| 671 | const offset = try readEhPointerAbs(r, enc.type, addr_size_bytes, endian); |
| 672 | if (enc.indirect) return bad(); // GCC extension; not supported |
| 673 | const base: u64 = switch (enc.rel) { |
| 674 | .abs, .aligned => 0, |
| 675 | .pcrel => ctx.pc_rel_base, |
| 676 | .textrel => ctx.text_rel_base orelse return bad(), |
| 677 | .datarel => ctx.data_rel_base orelse return bad(), |
| 678 | .funcrel => ctx.function_rel_base orelse return bad(), |
| 679 | _ => return bad(), |
| 680 | }; |
| 681 | return switch (offset) { |
| 682 | .signed => |s| if (s >= 0) |
| 683 | try std.math.add(u64, base, @intCast(s)) |
| 684 | else |
| 685 | try std.math.sub(u64, base, @intCast(-s)), |
| 686 | // absptr can actually contain signed values in some cases (aarch64 MachO) |
| 687 | .unsigned => |u| u +% base, |
| 688 | }; |
| 689 | } |
| 690 | |
| 691 | /// Like `Reader.fixed`, but when the length of the data is unknown and we just want to allow |
| 692 | /// reading indefinitely. |
| 693 | fn maxSlice(ptr: [*]const u8) []const u8 { |
| 694 | const len = std.math.maxInt(usize) - @intFromPtr(ptr); |
| 695 | return ptr[0..len]; |
| 696 | } |
| 697 | |
| 698 | const Allocator = std.mem.Allocator; |
| 699 | const assert = std.debug.assert; |
| 700 | const bad = Dwarf.bad; |
| 701 | const cast = std.math.cast; |
| 702 | const DW = std.dwarf; |
| 703 | const Dwarf = std.debug.Dwarf; |
| 704 | const EH = DW.EH; |
| 705 | const Endian = std.builtin.Endian; |
| 706 | const Format = DW.Format; |
| 707 | const maxInt = std.math.maxInt; |
| 708 | const missing = Dwarf.missing; |
| 709 | const Reader = std.Io.Reader; |
| 710 | const std = @import("std"); |
| 711 | const Unwind = @This(); |