| author | |
| committer | |
| log | e5b46eab3b16a6bf7924ef837fcd6552f430bd58 |
| tree | 8a90dd4323ba9d89305f33013db9d98e13f3e32d |
| parent | 377274ee9ab270886cce1ceaf5ffeddaefd9c239 |
std.debug.Dwarf is the parsing/decoding logic. std.dwarf remains the
unopinionated types and bits alone.
If you look at this diff you can see a lot less redundancy in
namespaces.10 files changed, 5489 insertions(+), 5480 deletions(-)
lib/std/debug.zig+39-37| ... | @@ -18,6 +18,8 @@ const native_arch = builtin.cpu.arch; | ... | @@ -18,6 +18,8 @@ const native_arch = builtin.cpu.arch; |
| 18 | const native_os = builtin.os.tag; | 18 | const native_os = builtin.os.tag; |
| 19 | const native_endian = native_arch.endian(); | 19 | const native_endian = native_arch.endian(); |
| 20 | 20 | ||
| 21 | pub const Dwarf = @import("debug/Dwarf.zig"); | ||
| 22 | |||
| 21 | pub const runtime_safety = switch (builtin.mode) { | 23 | pub const runtime_safety = switch (builtin.mode) { |
| 22 | .Debug, .ReleaseSafe => true, | 24 | .Debug, .ReleaseSafe => true, |
| 23 | .ReleaseFast, .ReleaseSmall => false, | 25 | .ReleaseFast, .ReleaseSmall => false, |
| ... | @@ -67,7 +69,7 @@ pub const SymbolInfo = struct { | ... | @@ -67,7 +69,7 @@ pub const SymbolInfo = struct { |
| 67 | }; | 69 | }; |
| 68 | const PdbOrDwarf = union(enum) { | 70 | const PdbOrDwarf = union(enum) { |
| 69 | pdb: pdb.Pdb, | 71 | pdb: pdb.Pdb, |
| 70 | dwarf: DW.DwarfInfo, | 72 | dwarf: Dwarf, |
| 71 | 73 | ||
| 72 | fn deinit(self: *PdbOrDwarf, allocator: mem.Allocator) void { | 74 | fn deinit(self: *PdbOrDwarf, allocator: mem.Allocator) void { |
| 73 | switch (self.*) { | 75 | switch (self.*) { |
| ... | @@ -566,7 +568,7 @@ pub const StackIterator = struct { | ... | @@ -566,7 +568,7 @@ pub const StackIterator = struct { |
| 566 | // using DWARF and MachO unwind info. | 568 | // using DWARF and MachO unwind info. |
| 567 | unwind_state: if (have_ucontext) ?struct { | 569 | unwind_state: if (have_ucontext) ?struct { |
| 568 | debug_info: *Info, | 570 | debug_info: *Info, |
| 569 | dwarf_context: DW.UnwindContext, | 571 | dwarf_context: Dwarf.UnwindContext, |
| 570 | last_error: ?UnwindError = null, | 572 | last_error: ?UnwindError = null, |
| 571 | failed: bool = false, | 573 | failed: bool = false, |
| 572 | } else void = if (have_ucontext) null else {}, | 574 | } else void = if (have_ucontext) null else {}, |
| ... | @@ -599,7 +601,7 @@ pub const StackIterator = struct { | ... | @@ -599,7 +601,7 @@ pub const StackIterator = struct { |
| 599 | var iterator = init(first_address, null); | 601 | var iterator = init(first_address, null); |
| 600 | iterator.unwind_state = .{ | 602 | iterator.unwind_state = .{ |
| 601 | .debug_info = debug_info, | 603 | .debug_info = debug_info, |
| 602 | .dwarf_context = try DW.UnwindContext.init(debug_info.allocator, context), | 604 | .dwarf_context = try Dwarf.UnwindContext.init(debug_info.allocator, context), |
| 603 | }; | 605 | }; |
| 604 | 606 | ||
| 605 | return iterator; | 607 | return iterator; |
| ... | @@ -783,7 +785,7 @@ pub const StackIterator = struct { | ... | @@ -783,7 +785,7 @@ pub const StackIterator = struct { |
| 783 | // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding | 785 | // __unwind_info is a requirement for unwinding on Darwin. It may fall back to DWARF, but unwinding |
| 784 | // via DWARF before attempting to use the compact unwind info will produce incorrect results. | 786 | // via DWARF before attempting to use the compact unwind info will produce incorrect results. |
| 785 | if (module.unwind_info) |unwind_info| { | 787 | if (module.unwind_info) |unwind_info| { |
| 786 | if (DW.unwindFrameMachO(&unwind_state.dwarf_context, &it.ma, unwind_info, module.eh_frame, module.base_address)) |return_address| { | 788 | if (Dwarf.unwindFrameMachO(&unwind_state.dwarf_context, &it.ma, unwind_info, module.eh_frame, module.base_address)) |return_address| { |
| 787 | return return_address; | 789 | return return_address; |
| 788 | } else |err| { | 790 | } else |err| { |
| 789 | if (err != error.RequiresDWARFUnwind) return err; | 791 | if (err != error.RequiresDWARFUnwind) return err; |
| ... | @@ -1140,10 +1142,10 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebu | ... | @@ -1140,10 +1142,10 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebu |
| 1140 | 1142 | ||
| 1141 | if (coff_obj.getSectionByName(".debug_info")) |_| { | 1143 | if (coff_obj.getSectionByName(".debug_info")) |_| { |
| 1142 | // This coff file has embedded DWARF debug info | 1144 | // This coff file has embedded DWARF debug info |
| 1143 | var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; | 1145 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; |
| 1144 | errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); | 1146 | errdefer for (sections) |section| if (section) |s| if (s.owned) allocator.free(s.data); |
| 1145 | 1147 | ||
| 1146 | inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { | 1148 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { |
| 1147 | sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { | 1149 | sections[i] = if (coff_obj.getSectionByName("." ++ section.name)) |section_header| blk: { |
| 1148 | break :blk .{ | 1150 | break :blk .{ |
| 1149 | .data = try coff_obj.getSectionDataAlloc(section_header, allocator), | 1151 | .data = try coff_obj.getSectionDataAlloc(section_header, allocator), |
| ... | @@ -1153,13 +1155,13 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebu | ... | @@ -1153,13 +1155,13 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebu |
| 1153 | } else null; | 1155 | } else null; |
| 1154 | } | 1156 | } |
| 1155 | 1157 | ||
| 1156 | var dwarf = DW.DwarfInfo{ | 1158 | var dwarf = Dwarf{ |
| 1157 | .endian = native_endian, | 1159 | .endian = native_endian, |
| 1158 | .sections = sections, | 1160 | .sections = sections, |
| 1159 | .is_macho = false, | 1161 | .is_macho = false, |
| 1160 | }; | 1162 | }; |
| 1161 | 1163 | ||
| 1162 | try DW.openDwarfDebugInfo(&dwarf, allocator); | 1164 | try Dwarf.open(&dwarf, allocator); |
| 1163 | di.dwarf = dwarf; | 1165 | di.dwarf = dwarf; |
| 1164 | } | 1166 | } |
| 1165 | 1167 | ||
| ... | @@ -1211,7 +1213,7 @@ pub fn readElfDebugInfo( | ... | @@ -1211,7 +1213,7 @@ pub fn readElfDebugInfo( |
| 1211 | elf_filename: ?[]const u8, | 1213 | elf_filename: ?[]const u8, |
| 1212 | build_id: ?[]const u8, | 1214 | build_id: ?[]const u8, |
| 1213 | expected_crc: ?u32, | 1215 | expected_crc: ?u32, |
| 1214 | parent_sections: *DW.DwarfInfo.SectionArray, | 1216 | parent_sections: *Dwarf.SectionArray, |
| 1215 | parent_mapped_mem: ?[]align(mem.page_size) const u8, | 1217 | parent_mapped_mem: ?[]align(mem.page_size) const u8, |
| 1216 | ) !ModuleDebugInfo { | 1218 | ) !ModuleDebugInfo { |
| 1217 | nosuspend { | 1219 | nosuspend { |
| ... | @@ -1245,7 +1247,7 @@ pub fn readElfDebugInfo( | ... | @@ -1245,7 +1247,7 @@ pub fn readElfDebugInfo( |
| 1245 | @ptrCast(@alignCast(&mapped_mem[shoff])), | 1247 | @ptrCast(@alignCast(&mapped_mem[shoff])), |
| 1246 | )[0..hdr.e_shnum]; | 1248 | )[0..hdr.e_shnum]; |
| 1247 | 1249 | ||
| 1248 | var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; | 1250 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; |
| 1249 | 1251 | ||
| 1250 | // Combine section list. This takes ownership over any owned sections from the parent scope. | 1252 | // Combine section list. This takes ownership over any owned sections from the parent scope. |
| 1251 | for (parent_sections, &sections) |*parent, *section| { | 1253 | for (parent_sections, &sections) |*parent, *section| { |
| ... | @@ -1274,7 +1276,7 @@ pub fn readElfDebugInfo( | ... | @@ -1274,7 +1276,7 @@ pub fn readElfDebugInfo( |
| 1274 | } | 1276 | } |
| 1275 | 1277 | ||
| 1276 | var section_index: ?usize = null; | 1278 | var section_index: ?usize = null; |
| 1277 | inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { | 1279 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { |
| 1278 | if (mem.eql(u8, "." ++ section.name, name)) section_index = i; | 1280 | if (mem.eql(u8, "." ++ section.name, name)) section_index = i; |
| 1279 | } | 1281 | } |
| 1280 | if (section_index == null) continue; | 1282 | if (section_index == null) continue; |
| ... | @@ -1308,10 +1310,10 @@ pub fn readElfDebugInfo( | ... | @@ -1308,10 +1310,10 @@ pub fn readElfDebugInfo( |
| 1308 | } | 1310 | } |
| 1309 | 1311 | ||
| 1310 | const missing_debug_info = | 1312 | const missing_debug_info = |
| 1311 | sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or | 1313 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or |
| 1312 | sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or | 1314 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or |
| 1313 | sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or | 1315 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or |
| 1314 | sections[@intFromEnum(DW.DwarfSection.debug_line)] == null; | 1316 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; |
| 1315 | 1317 | ||
| 1316 | // Attempt to load debug info from an external file | 1318 | // Attempt to load debug info from an external file |
| 1317 | // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html | 1319 | // See: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html |
| ... | @@ -1379,13 +1381,13 @@ pub fn readElfDebugInfo( | ... | @@ -1379,13 +1381,13 @@ pub fn readElfDebugInfo( |
| 1379 | return error.MissingDebugInfo; | 1381 | return error.MissingDebugInfo; |
| 1380 | } | 1382 | } |
| 1381 | 1383 | ||
| 1382 | var di = DW.DwarfInfo{ | 1384 | var di = Dwarf{ |
| 1383 | .endian = endian, | 1385 | .endian = endian, |
| 1384 | .sections = sections, | 1386 | .sections = sections, |
| 1385 | .is_macho = false, | 1387 | .is_macho = false, |
| 1386 | }; | 1388 | }; |
| 1387 | 1389 | ||
| 1388 | try DW.openDwarfDebugInfo(&di, allocator); | 1390 | try Dwarf.open(&di, allocator); |
| 1389 | 1391 | ||
| 1390 | return ModuleDebugInfo{ | 1392 | return ModuleDebugInfo{ |
| 1391 | .base_address = undefined, | 1393 | .base_address = undefined, |
| ... | @@ -2168,13 +2170,13 @@ pub const Info = struct { | ... | @@ -2168,13 +2170,13 @@ pub const Info = struct { |
| 2168 | const obj_di = try self.allocator.create(ModuleDebugInfo); | 2170 | const obj_di = try self.allocator.create(ModuleDebugInfo); |
| 2169 | errdefer self.allocator.destroy(obj_di); | 2171 | errdefer self.allocator.destroy(obj_di); |
| 2170 | 2172 | ||
| 2171 | var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; | 2173 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; |
| 2172 | if (ctx.gnu_eh_frame) |eh_frame_hdr| { | 2174 | if (ctx.gnu_eh_frame) |eh_frame_hdr| { |
| 2173 | // This is a special case - pointer offsets inside .eh_frame_hdr | 2175 | // This is a special case - pointer offsets inside .eh_frame_hdr |
| 2174 | // are encoded relative to its base address, so we must use the | 2176 | // are encoded relative to its base address, so we must use the |
| 2175 | // version that is already memory mapped, and not the one that | 2177 | // version that is already memory mapped, and not the one that |
| 2176 | // will be mapped separately from the ELF file. | 2178 | // will be mapped separately from the ELF file. |
| 2177 | sections[@intFromEnum(DW.DwarfSection.eh_frame_hdr)] = .{ | 2179 | sections[@intFromEnum(Dwarf.Section.Id.eh_frame_hdr)] = .{ |
| 2178 | .data = eh_frame_hdr, | 2180 | .data = eh_frame_hdr, |
| 2179 | .owned = false, | 2181 | .owned = false, |
| 2180 | }; | 2182 | }; |
| ... | @@ -2219,7 +2221,7 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -2219,7 +2221,7 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 2219 | 2221 | ||
| 2220 | const OFileTable = std.StringHashMap(OFileInfo); | 2222 | const OFileTable = std.StringHashMap(OFileInfo); |
| 2221 | const OFileInfo = struct { | 2223 | const OFileInfo = struct { |
| 2222 | di: DW.DwarfInfo, | 2224 | di: Dwarf, |
| 2223 | addr_table: std.StringHashMap(u64), | 2225 | addr_table: std.StringHashMap(u64), |
| 2224 | }; | 2226 | }; |
| 2225 | 2227 | ||
| ... | @@ -2278,8 +2280,8 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -2278,8 +2280,8 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 2278 | addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); | 2280 | addr_table.putAssumeCapacityNoClobber(sym_name, sym.n_value); |
| 2279 | } | 2281 | } |
| 2280 | 2282 | ||
| 2281 | var sections: DW.DwarfInfo.SectionArray = DW.DwarfInfo.null_section_array; | 2283 | var sections: Dwarf.SectionArray = Dwarf.null_section_array; |
| 2282 | if (self.eh_frame) |eh_frame| sections[@intFromEnum(DW.DwarfSection.eh_frame)] = .{ | 2284 | if (self.eh_frame) |eh_frame| sections[@intFromEnum(Dwarf.Section.Id.eh_frame)] = .{ |
| 2283 | .data = eh_frame, | 2285 | .data = eh_frame, |
| 2284 | .owned = false, | 2286 | .owned = false, |
| 2285 | }; | 2287 | }; |
| ... | @@ -2288,7 +2290,7 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -2288,7 +2290,7 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 2288 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; | 2290 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; |
| 2289 | 2291 | ||
| 2290 | var section_index: ?usize = null; | 2292 | var section_index: ?usize = null; |
| 2291 | inline for (@typeInfo(DW.DwarfSection).Enum.fields, 0..) |section, i| { | 2293 | inline for (@typeInfo(Dwarf.Section.Id).Enum.fields, 0..) |section, i| { |
| 2292 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; | 2294 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) section_index = i; |
| 2293 | } | 2295 | } |
| 2294 | if (section_index == null) continue; | 2296 | if (section_index == null) continue; |
| ... | @@ -2302,19 +2304,19 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -2302,19 +2304,19 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 2302 | } | 2304 | } |
| 2303 | 2305 | ||
| 2304 | const missing_debug_info = | 2306 | const missing_debug_info = |
| 2305 | sections[@intFromEnum(DW.DwarfSection.debug_info)] == null or | 2307 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or |
| 2306 | sections[@intFromEnum(DW.DwarfSection.debug_abbrev)] == null or | 2308 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or |
| 2307 | sections[@intFromEnum(DW.DwarfSection.debug_str)] == null or | 2309 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or |
| 2308 | sections[@intFromEnum(DW.DwarfSection.debug_line)] == null; | 2310 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; |
| 2309 | if (missing_debug_info) return error.MissingDebugInfo; | 2311 | if (missing_debug_info) return error.MissingDebugInfo; |
| 2310 | 2312 | ||
| 2311 | var di = DW.DwarfInfo{ | 2313 | var di = Dwarf{ |
| 2312 | .endian = .little, | 2314 | .endian = .little, |
| 2313 | .sections = sections, | 2315 | .sections = sections, |
| 2314 | .is_macho = true, | 2316 | .is_macho = true, |
| 2315 | }; | 2317 | }; |
| 2316 | 2318 | ||
| 2317 | try DW.openDwarfDebugInfo(&di, allocator); | 2319 | try Dwarf.open(&di, allocator); |
| 2318 | const info = OFileInfo{ | 2320 | const info = OFileInfo{ |
| 2319 | .di = di, | 2321 | .di = di, |
| 2320 | .addr_table = addr_table, | 2322 | .addr_table = addr_table, |
| ... | @@ -2411,14 +2413,14 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -2411,14 +2413,14 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 2411 | } | 2413 | } |
| 2412 | } | 2414 | } |
| 2413 | 2415 | ||
| 2414 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { | 2416 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { |
| 2415 | return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; | 2417 | return if ((try self.getOFileInfoForAddress(allocator, address)).o_file_info) |o_file_info| &o_file_info.di else null; |
| 2416 | } | 2418 | } |
| 2417 | }, | 2419 | }, |
| 2418 | .uefi, .windows => struct { | 2420 | .uefi, .windows => struct { |
| 2419 | base_address: usize, | 2421 | base_address: usize, |
| 2420 | pdb: ?pdb.Pdb = null, | 2422 | pdb: ?pdb.Pdb = null, |
| 2421 | dwarf: ?DW.DwarfInfo = null, | 2423 | dwarf: ?Dwarf = null, |
| 2422 | coff_image_base: u64, | 2424 | coff_image_base: u64, |
| 2423 | 2425 | ||
| 2424 | /// Only used if pdb is non-null | 2426 | /// Only used if pdb is non-null |
| ... | @@ -2488,7 +2490,7 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -2488,7 +2490,7 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 2488 | return SymbolInfo{}; | 2490 | return SymbolInfo{}; |
| 2489 | } | 2491 | } |
| 2490 | 2492 | ||
| 2491 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { | 2493 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { |
| 2492 | _ = allocator; | 2494 | _ = allocator; |
| 2493 | _ = address; | 2495 | _ = address; |
| 2494 | 2496 | ||
| ... | @@ -2500,7 +2502,7 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -2500,7 +2502,7 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 2500 | }, | 2502 | }, |
| 2501 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { | 2503 | .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { |
| 2502 | base_address: usize, | 2504 | base_address: usize, |
| 2503 | dwarf: DW.DwarfInfo, | 2505 | dwarf: Dwarf, |
| 2504 | mapped_memory: []align(mem.page_size) const u8, | 2506 | mapped_memory: []align(mem.page_size) const u8, |
| 2505 | external_mapped_memory: ?[]align(mem.page_size) const u8, | 2507 | external_mapped_memory: ?[]align(mem.page_size) const u8, |
| 2506 | 2508 | ||
| ... | @@ -2516,7 +2518,7 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -2516,7 +2518,7 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 2516 | return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); | 2518 | return getSymbolFromDwarf(allocator, relocated_address, &self.dwarf); |
| 2517 | } | 2519 | } |
| 2518 | 2520 | ||
| 2519 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { | 2521 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { |
| 2520 | _ = allocator; | 2522 | _ = allocator; |
| 2521 | _ = address; | 2523 | _ = address; |
| 2522 | return &self.dwarf; | 2524 | return &self.dwarf; |
| ... | @@ -2535,17 +2537,17 @@ pub const ModuleDebugInfo = switch (native_os) { | ... | @@ -2535,17 +2537,17 @@ pub const ModuleDebugInfo = switch (native_os) { |
| 2535 | return SymbolInfo{}; | 2537 | return SymbolInfo{}; |
| 2536 | } | 2538 | } |
| 2537 | 2539 | ||
| 2538 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const DW.DwarfInfo { | 2540 | pub fn getDwarfInfoForAddress(self: *@This(), allocator: mem.Allocator, address: usize) !?*const Dwarf { |
| 2539 | _ = self; | 2541 | _ = self; |
| 2540 | _ = allocator; | 2542 | _ = allocator; |
| 2541 | _ = address; | 2543 | _ = address; |
| 2542 | return null; | 2544 | return null; |
| 2543 | } | 2545 | } |
| 2544 | }, | 2546 | }, |
| 2545 | else => DW.DwarfInfo, | 2547 | else => Dwarf, |
| 2546 | }; | 2548 | }; |
| 2547 | 2549 | ||
| 2548 | fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *DW.DwarfInfo) !SymbolInfo { | 2550 | fn getSymbolFromDwarf(allocator: mem.Allocator, address: u64, di: *Dwarf) !SymbolInfo { |
| 2549 | if (nosuspend di.findCompileUnit(address)) |compile_unit| { | 2551 | if (nosuspend di.findCompileUnit(address)) |compile_unit| { |
| 2550 | return SymbolInfo{ | 2552 | return SymbolInfo{ |
| 2551 | .symbol_name = nosuspend di.getSymbolName(address) orelse "???", | 2553 | .symbol_name = nosuspend di.getSymbolName(address) orelse "???", |
lib/std/debug/Dwarf.zig created+2709| ... | @@ -0,0 +1,2709 @@ | ||
| 1 | //! Implements parsing, decoding, and caching of DWARF information. | ||
| 2 | //! | ||
| 3 | //! For unopinionated types and bits, see `std.dwarf`. | ||
| 4 | |||
| 5 | const builtin = @import("builtin"); | ||
| 6 | const std = @import("../std.zig"); | ||
| 7 | const AT = DW.AT; | ||
| 8 | const Allocator = std.mem.Allocator; | ||
| 9 | const DW = std.dwarf; | ||
| 10 | const EH = DW.EH; | ||
| 11 | const FORM = DW.FORM; | ||
| 12 | const Format = DW.Format; | ||
| 13 | const RLE = DW.RLE; | ||
| 14 | const StackIterator = std.debug.StackIterator; | ||
| 15 | const UT = DW.UT; | ||
| 16 | const assert = std.debug.assert; | ||
| 17 | const cast = std.math.cast; | ||
| 18 | const maxInt = std.math.maxInt; | ||
| 19 | const native_endian = builtin.cpu.arch.endian(); | ||
| 20 | const readInt = std.mem.readInt; | ||
| 21 | |||
| 22 | const Dwarf = @This(); | ||
| 23 | |||
| 24 | pub const expression = @import("Dwarf/expression.zig"); | ||
| 25 | pub const abi = @import("Dwarf/abi.zig"); | ||
| 26 | pub const call_frame = @import("Dwarf/call_frame.zig"); | ||
| 27 | |||
| 28 | endian: std.builtin.Endian, | ||
| 29 | sections: SectionArray = null_section_array, | ||
| 30 | is_macho: bool, | ||
| 31 | |||
| 32 | // Filled later by the initializer | ||
| 33 | abbrev_table_list: std.ArrayListUnmanaged(Abbrev.Table) = .{}, | ||
| 34 | compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{}, | ||
| 35 | func_list: std.ArrayListUnmanaged(Func) = .{}, | ||
| 36 | |||
| 37 | eh_frame_hdr: ?ExceptionFrameHeader = null, | ||
| 38 | // These lookup tables are only used if `eh_frame_hdr` is null | ||
| 39 | cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{}, | ||
| 40 | // Sorted by start_pc | ||
| 41 | fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{}, | ||
| 42 | |||
| 43 | pub const Section = struct { | ||
| 44 | data: []const u8, | ||
| 45 | // Module-relative virtual address. | ||
| 46 | // Only set if the section data was loaded from disk. | ||
| 47 | virtual_address: ?usize = null, | ||
| 48 | // If `data` is owned by this Dwarf. | ||
| 49 | owned: bool, | ||
| 50 | |||
| 51 | pub const Id = enum { | ||
| 52 | debug_info, | ||
| 53 | debug_abbrev, | ||
| 54 | debug_str, | ||
| 55 | debug_str_offsets, | ||
| 56 | debug_line, | ||
| 57 | debug_line_str, | ||
| 58 | debug_ranges, | ||
| 59 | debug_loclists, | ||
| 60 | debug_rnglists, | ||
| 61 | debug_addr, | ||
| 62 | debug_names, | ||
| 63 | debug_frame, | ||
| 64 | eh_frame, | ||
| 65 | eh_frame_hdr, | ||
| 66 | }; | ||
| 67 | |||
| 68 | // For sections that are not memory mapped by the loader, this is an offset | ||
| 69 | // from `data.ptr` to where the section would have been mapped. Otherwise, | ||
| 70 | // `data` is directly backed by the section and the offset is zero. | ||
| 71 | pub fn virtualOffset(self: Section, base_address: usize) i64 { | ||
| 72 | return if (self.virtual_address) |va| | ||
| 73 | @as(i64, @intCast(base_address + va)) - | ||
| 74 | @as(i64, @intCast(@intFromPtr(self.data.ptr))) | ||
| 75 | else | ||
| 76 | 0; | ||
| 77 | } | ||
| 78 | }; | ||
| 79 | |||
| 80 | pub const Abbrev = struct { | ||
| 81 | code: u64, | ||
| 82 | tag_id: u64, | ||
| 83 | has_children: bool, | ||
| 84 | attrs: []Attr, | ||
| 85 | |||
| 86 | fn deinit(abbrev: *Abbrev, allocator: Allocator) void { | ||
| 87 | allocator.free(abbrev.attrs); | ||
| 88 | abbrev.* = undefined; | ||
| 89 | } | ||
| 90 | |||
| 91 | const Attr = struct { | ||
| 92 | id: u64, | ||
| 93 | form_id: u64, | ||
| 94 | /// Only valid if form_id is .implicit_const | ||
| 95 | payload: i64, | ||
| 96 | }; | ||
| 97 | |||
| 98 | const Table = struct { | ||
| 99 | // offset from .debug_abbrev | ||
| 100 | offset: u64, | ||
| 101 | abbrevs: []Abbrev, | ||
| 102 | |||
| 103 | fn deinit(table: *Table, allocator: Allocator) void { | ||
| 104 | for (table.abbrevs) |*abbrev| { | ||
| 105 | abbrev.deinit(allocator); | ||
| 106 | } | ||
| 107 | allocator.free(table.abbrevs); | ||
| 108 | table.* = undefined; | ||
| 109 | } | ||
| 110 | |||
| 111 | fn get(table: *const Table, abbrev_code: u64) ?*const Abbrev { | ||
| 112 | return for (table.abbrevs) |*abbrev| { | ||
| 113 | if (abbrev.code == abbrev_code) break abbrev; | ||
| 114 | } else null; | ||
| 115 | } | ||
| 116 | }; | ||
| 117 | }; | ||
| 118 | |||
| 119 | pub const CompileUnit = struct { | ||
| 120 | version: u16, | ||
| 121 | format: Format, | ||
| 122 | die: Die, | ||
| 123 | pc_range: ?PcRange, | ||
| 124 | |||
| 125 | str_offsets_base: usize, | ||
| 126 | addr_base: usize, | ||
| 127 | rnglists_base: usize, | ||
| 128 | loclists_base: usize, | ||
| 129 | frame_base: ?*const FormValue, | ||
| 130 | }; | ||
| 131 | |||
| 132 | pub const FormValue = union(enum) { | ||
| 133 | addr: u64, | ||
| 134 | addrx: usize, | ||
| 135 | block: []const u8, | ||
| 136 | udata: u64, | ||
| 137 | data16: *const [16]u8, | ||
| 138 | sdata: i64, | ||
| 139 | exprloc: []const u8, | ||
| 140 | flag: bool, | ||
| 141 | sec_offset: u64, | ||
| 142 | ref: u64, | ||
| 143 | ref_addr: u64, | ||
| 144 | string: [:0]const u8, | ||
| 145 | strp: u64, | ||
| 146 | strx: usize, | ||
| 147 | line_strp: u64, | ||
| 148 | loclistx: u64, | ||
| 149 | rnglistx: u64, | ||
| 150 | |||
| 151 | fn getString(fv: FormValue, di: Dwarf) ![:0]const u8 { | ||
| 152 | switch (fv) { | ||
| 153 | .string => |s| return s, | ||
| 154 | .strp => |off| return di.getString(off), | ||
| 155 | .line_strp => |off| return di.getLineString(off), | ||
| 156 | else => return badDwarf(), | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | fn getUInt(fv: FormValue, comptime U: type) !U { | ||
| 161 | return switch (fv) { | ||
| 162 | inline .udata, | ||
| 163 | .sdata, | ||
| 164 | .sec_offset, | ||
| 165 | => |c| cast(U, c) orelse badDwarf(), | ||
| 166 | else => badDwarf(), | ||
| 167 | }; | ||
| 168 | } | ||
| 169 | }; | ||
| 170 | |||
| 171 | pub const Die = struct { | ||
| 172 | tag_id: u64, | ||
| 173 | has_children: bool, | ||
| 174 | attrs: []Attr, | ||
| 175 | |||
| 176 | const Attr = struct { | ||
| 177 | id: u64, | ||
| 178 | value: FormValue, | ||
| 179 | }; | ||
| 180 | |||
| 181 | fn deinit(self: *Die, allocator: Allocator) void { | ||
| 182 | allocator.free(self.attrs); | ||
| 183 | self.* = undefined; | ||
| 184 | } | ||
| 185 | |||
| 186 | fn getAttr(self: *const Die, id: u64) ?*const FormValue { | ||
| 187 | for (self.attrs) |*attr| { | ||
| 188 | if (attr.id == id) return &attr.value; | ||
| 189 | } | ||
| 190 | return null; | ||
| 191 | } | ||
| 192 | |||
| 193 | fn getAttrAddr( | ||
| 194 | self: *const Die, | ||
| 195 | di: *const Dwarf, | ||
| 196 | id: u64, | ||
| 197 | compile_unit: CompileUnit, | ||
| 198 | ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 { | ||
| 199 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 200 | return switch (form_value.*) { | ||
| 201 | .addr => |value| value, | ||
| 202 | .addrx => |index| di.readDebugAddr(compile_unit, index), | ||
| 203 | else => error.InvalidDebugInfo, | ||
| 204 | }; | ||
| 205 | } | ||
| 206 | |||
| 207 | fn getAttrSecOffset(self: *const Die, id: u64) !u64 { | ||
| 208 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 209 | return form_value.getUInt(u64); | ||
| 210 | } | ||
| 211 | |||
| 212 | fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 { | ||
| 213 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 214 | return switch (form_value.*) { | ||
| 215 | .Const => |value| value.asUnsignedLe(), | ||
| 216 | else => error.InvalidDebugInfo, | ||
| 217 | }; | ||
| 218 | } | ||
| 219 | |||
| 220 | fn getAttrRef(self: *const Die, id: u64) !u64 { | ||
| 221 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 222 | return switch (form_value.*) { | ||
| 223 | .ref => |value| value, | ||
| 224 | else => error.InvalidDebugInfo, | ||
| 225 | }; | ||
| 226 | } | ||
| 227 | |||
| 228 | pub fn getAttrString( | ||
| 229 | self: *const Die, | ||
| 230 | di: *Dwarf, | ||
| 231 | id: u64, | ||
| 232 | opt_str: ?[]const u8, | ||
| 233 | compile_unit: CompileUnit, | ||
| 234 | ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 { | ||
| 235 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 236 | switch (form_value.*) { | ||
| 237 | .string => |value| return value, | ||
| 238 | .strp => |offset| return di.getString(offset), | ||
| 239 | .strx => |index| { | ||
| 240 | const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf(); | ||
| 241 | if (compile_unit.str_offsets_base == 0) return badDwarf(); | ||
| 242 | switch (compile_unit.format) { | ||
| 243 | .@"32" => { | ||
| 244 | const byte_offset = compile_unit.str_offsets_base + 4 * index; | ||
| 245 | if (byte_offset + 4 > debug_str_offsets.len) return badDwarf(); | ||
| 246 | const offset = readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); | ||
| 247 | return getStringGeneric(opt_str, offset); | ||
| 248 | }, | ||
| 249 | .@"64" => { | ||
| 250 | const byte_offset = compile_unit.str_offsets_base + 8 * index; | ||
| 251 | if (byte_offset + 8 > debug_str_offsets.len) return badDwarf(); | ||
| 252 | const offset = readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); | ||
| 253 | return getStringGeneric(opt_str, offset); | ||
| 254 | }, | ||
| 255 | } | ||
| 256 | }, | ||
| 257 | .line_strp => |offset| return di.getLineString(offset), | ||
| 258 | else => return badDwarf(), | ||
| 259 | } | ||
| 260 | } | ||
| 261 | }; | ||
| 262 | |||
| 263 | /// This represents the decoded .eh_frame_hdr header | ||
| 264 | pub const ExceptionFrameHeader = struct { | ||
| 265 | eh_frame_ptr: usize, | ||
| 266 | table_enc: u8, | ||
| 267 | fde_count: usize, | ||
| 268 | entries: []const u8, | ||
| 269 | |||
| 270 | pub fn entrySize(table_enc: u8) !u8 { | ||
| 271 | return switch (table_enc & EH.PE.type_mask) { | ||
| 272 | EH.PE.udata2, | ||
| 273 | EH.PE.sdata2, | ||
| 274 | => 4, | ||
| 275 | EH.PE.udata4, | ||
| 276 | EH.PE.sdata4, | ||
| 277 | => 8, | ||
| 278 | EH.PE.udata8, | ||
| 279 | EH.PE.sdata8, | ||
| 280 | => 16, | ||
| 281 | // This is a binary search table, so all entries must be the same length | ||
| 282 | else => return badDwarf(), | ||
| 283 | }; | ||
| 284 | } | ||
| 285 | |||
| 286 | fn isValidPtr( | ||
| 287 | self: ExceptionFrameHeader, | ||
| 288 | comptime T: type, | ||
| 289 | ptr: usize, | ||
| 290 | ma: *StackIterator.MemoryAccessor, | ||
| 291 | eh_frame_len: ?usize, | ||
| 292 | ) bool { | ||
| 293 | if (eh_frame_len) |len| { | ||
| 294 | return ptr >= self.eh_frame_ptr and ptr <= self.eh_frame_ptr + len - @sizeOf(T); | ||
| 295 | } else { | ||
| 296 | return ma.load(T, ptr) != null; | ||
| 297 | } | ||
| 298 | } | ||
| 299 | |||
| 300 | /// Find an entry by binary searching the eh_frame_hdr section. | ||
| 301 | /// | ||
| 302 | /// Since the length of the eh_frame section (`eh_frame_len`) may not be known by the caller, | ||
| 303 | /// MemoryAccessor will be used to verify readability of the header entries. | ||
| 304 | /// If `eh_frame_len` is provided, then these checks can be skipped. | ||
| 305 | pub fn findEntry( | ||
| 306 | self: ExceptionFrameHeader, | ||
| 307 | ma: *StackIterator.MemoryAccessor, | ||
| 308 | eh_frame_len: ?usize, | ||
| 309 | eh_frame_hdr_ptr: usize, | ||
| 310 | pc: usize, | ||
| 311 | cie: *CommonInformationEntry, | ||
| 312 | fde: *FrameDescriptionEntry, | ||
| 313 | ) !void { | ||
| 314 | const entry_size = try entrySize(self.table_enc); | ||
| 315 | |||
| 316 | var left: usize = 0; | ||
| 317 | var len: usize = self.fde_count; | ||
| 318 | |||
| 319 | var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; | ||
| 320 | |||
| 321 | while (len > 1) { | ||
| 322 | const mid = left + len / 2; | ||
| 323 | |||
| 324 | fbr.pos = mid * entry_size; | ||
| 325 | const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | ||
| 326 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), | ||
| 327 | .follow_indirect = true, | ||
| 328 | .data_rel_base = eh_frame_hdr_ptr, | ||
| 329 | }) orelse return badDwarf(); | ||
| 330 | |||
| 331 | if (pc < pc_begin) { | ||
| 332 | len /= 2; | ||
| 333 | } else { | ||
| 334 | left = mid; | ||
| 335 | if (pc == pc_begin) break; | ||
| 336 | len -= len / 2; | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 340 | if (len == 0) return badDwarf(); | ||
| 341 | fbr.pos = left * entry_size; | ||
| 342 | |||
| 343 | // Read past the pc_begin field of the entry | ||
| 344 | _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | ||
| 345 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), | ||
| 346 | .follow_indirect = true, | ||
| 347 | .data_rel_base = eh_frame_hdr_ptr, | ||
| 348 | }) orelse return badDwarf(); | ||
| 349 | |||
| 350 | const fde_ptr = cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | ||
| 351 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), | ||
| 352 | .follow_indirect = true, | ||
| 353 | .data_rel_base = eh_frame_hdr_ptr, | ||
| 354 | }) orelse return badDwarf()) orelse return badDwarf(); | ||
| 355 | |||
| 356 | if (fde_ptr < self.eh_frame_ptr) return badDwarf(); | ||
| 357 | |||
| 358 | // Even if eh_frame_len is not specified, all ranges accssed are checked via MemoryAccessor | ||
| 359 | const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse maxInt(u32)]; | ||
| 360 | |||
| 361 | const fde_offset = fde_ptr - self.eh_frame_ptr; | ||
| 362 | var eh_frame_fbr: FixedBufferReader = .{ | ||
| 363 | .buf = eh_frame, | ||
| 364 | .pos = fde_offset, | ||
| 365 | .endian = native_endian, | ||
| 366 | }; | ||
| 367 | |||
| 368 | const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); | ||
| 369 | if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); | ||
| 370 | if (fde_entry_header.type != .fde) return badDwarf(); | ||
| 371 | |||
| 372 | // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable | ||
| 373 | const cie_offset = fde_entry_header.type.fde; | ||
| 374 | try eh_frame_fbr.seekTo(cie_offset); | ||
| 375 | const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); | ||
| 376 | if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); | ||
| 377 | if (cie_entry_header.type != .cie) return badDwarf(); | ||
| 378 | |||
| 379 | cie.* = try CommonInformationEntry.parse( | ||
| 380 | cie_entry_header.entry_bytes, | ||
| 381 | 0, | ||
| 382 | true, | ||
| 383 | cie_entry_header.format, | ||
| 384 | .eh_frame, | ||
| 385 | cie_entry_header.length_offset, | ||
| 386 | @sizeOf(usize), | ||
| 387 | native_endian, | ||
| 388 | ); | ||
| 389 | |||
| 390 | fde.* = try FrameDescriptionEntry.parse( | ||
| 391 | fde_entry_header.entry_bytes, | ||
| 392 | 0, | ||
| 393 | true, | ||
| 394 | cie.*, | ||
| 395 | @sizeOf(usize), | ||
| 396 | native_endian, | ||
| 397 | ); | ||
| 398 | } | ||
| 399 | }; | ||
| 400 | |||
| 401 | pub const EntryHeader = struct { | ||
| 402 | /// Offset of the length field in the backing buffer | ||
| 403 | length_offset: usize, | ||
| 404 | format: Format, | ||
| 405 | type: union(enum) { | ||
| 406 | cie, | ||
| 407 | /// Value is the offset of the corresponding CIE | ||
| 408 | fde: u64, | ||
| 409 | terminator, | ||
| 410 | }, | ||
| 411 | /// The entry's contents, not including the ID field | ||
| 412 | entry_bytes: []const u8, | ||
| 413 | |||
| 414 | /// The length of the entry including the ID field, but not the length field itself | ||
| 415 | pub fn entryLength(self: EntryHeader) usize { | ||
| 416 | return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4); | ||
| 417 | } | ||
| 418 | |||
| 419 | /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure. | ||
| 420 | /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections. | ||
| 421 | pub fn read( | ||
| 422 | fbr: *FixedBufferReader, | ||
| 423 | opt_ma: ?*StackIterator.MemoryAccessor, | ||
| 424 | dwarf_section: Section.Id, | ||
| 425 | ) !EntryHeader { | ||
| 426 | assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); | ||
| 427 | |||
| 428 | const length_offset = fbr.pos; | ||
| 429 | const unit_header = try readUnitHeader(fbr, opt_ma); | ||
| 430 | const unit_length = cast(usize, unit_header.unit_length) orelse return badDwarf(); | ||
| 431 | if (unit_length == 0) return .{ | ||
| 432 | .length_offset = length_offset, | ||
| 433 | .format = unit_header.format, | ||
| 434 | .type = .terminator, | ||
| 435 | .entry_bytes = &.{}, | ||
| 436 | }; | ||
| 437 | const start_offset = fbr.pos; | ||
| 438 | const end_offset = start_offset + unit_length; | ||
| 439 | defer fbr.pos = end_offset; | ||
| 440 | |||
| 441 | const id = try if (opt_ma) |ma| | ||
| 442 | fbr.readAddressChecked(unit_header.format, ma) | ||
| 443 | else | ||
| 444 | fbr.readAddress(unit_header.format); | ||
| 445 | const entry_bytes = fbr.buf[fbr.pos..end_offset]; | ||
| 446 | const cie_id: u64 = switch (dwarf_section) { | ||
| 447 | .eh_frame => CommonInformationEntry.eh_id, | ||
| 448 | .debug_frame => switch (unit_header.format) { | ||
| 449 | .@"32" => CommonInformationEntry.dwarf32_id, | ||
| 450 | .@"64" => CommonInformationEntry.dwarf64_id, | ||
| 451 | }, | ||
| 452 | else => unreachable, | ||
| 453 | }; | ||
| 454 | |||
| 455 | return .{ | ||
| 456 | .length_offset = length_offset, | ||
| 457 | .format = unit_header.format, | ||
| 458 | .type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) { | ||
| 459 | .eh_frame => try std.math.sub(u64, start_offset, id), | ||
| 460 | .debug_frame => id, | ||
| 461 | else => unreachable, | ||
| 462 | } }, | ||
| 463 | .entry_bytes = entry_bytes, | ||
| 464 | }; | ||
| 465 | } | ||
| 466 | }; | ||
| 467 | |||
| 468 | pub const CommonInformationEntry = struct { | ||
| 469 | // Used in .eh_frame | ||
| 470 | pub const eh_id = 0; | ||
| 471 | |||
| 472 | // Used in .debug_frame (DWARF32) | ||
| 473 | pub const dwarf32_id = maxInt(u32); | ||
| 474 | |||
| 475 | // Used in .debug_frame (DWARF64) | ||
| 476 | pub const dwarf64_id = maxInt(u64); | ||
| 477 | |||
| 478 | // Offset of the length field of this entry in the eh_frame section. | ||
| 479 | // This is the key that FDEs use to reference CIEs. | ||
| 480 | length_offset: u64, | ||
| 481 | version: u8, | ||
| 482 | address_size: u8, | ||
| 483 | format: Format, | ||
| 484 | |||
| 485 | // Only present in version 4 | ||
| 486 | segment_selector_size: ?u8, | ||
| 487 | |||
| 488 | code_alignment_factor: u32, | ||
| 489 | data_alignment_factor: i32, | ||
| 490 | return_address_register: u8, | ||
| 491 | |||
| 492 | aug_str: []const u8, | ||
| 493 | aug_data: []const u8, | ||
| 494 | lsda_pointer_enc: u8, | ||
| 495 | personality_enc: ?u8, | ||
| 496 | personality_routine_pointer: ?u64, | ||
| 497 | fde_pointer_enc: u8, | ||
| 498 | initial_instructions: []const u8, | ||
| 499 | |||
| 500 | pub fn isSignalFrame(self: CommonInformationEntry) bool { | ||
| 501 | for (self.aug_str) |c| if (c == 'S') return true; | ||
| 502 | return false; | ||
| 503 | } | ||
| 504 | |||
| 505 | pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool { | ||
| 506 | for (self.aug_str) |c| if (c == 'B') return true; | ||
| 507 | return false; | ||
| 508 | } | ||
| 509 | |||
| 510 | pub fn mteTaggedFrame(self: CommonInformationEntry) bool { | ||
| 511 | for (self.aug_str) |c| if (c == 'G') return true; | ||
| 512 | return false; | ||
| 513 | } | ||
| 514 | |||
| 515 | /// This function expects to read the CIE starting with the version field. | ||
| 516 | /// The returned struct references memory backed by cie_bytes. | ||
| 517 | /// | ||
| 518 | /// See the FrameDescriptionEntry.parse documentation for the description | ||
| 519 | /// of `pc_rel_offset` and `is_runtime`. | ||
| 520 | /// | ||
| 521 | /// `length_offset` specifies the offset of this CIE's length field in the | ||
| 522 | /// .eh_frame / .debug_frame section. | ||
| 523 | pub fn parse( | ||
| 524 | cie_bytes: []const u8, | ||
| 525 | pc_rel_offset: i64, | ||
| 526 | is_runtime: bool, | ||
| 527 | format: Format, | ||
| 528 | dwarf_section: Section.Id, | ||
| 529 | length_offset: u64, | ||
| 530 | addr_size_bytes: u8, | ||
| 531 | endian: std.builtin.Endian, | ||
| 532 | ) !CommonInformationEntry { | ||
| 533 | if (addr_size_bytes > 8) return error.UnsupportedAddrSize; | ||
| 534 | |||
| 535 | var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; | ||
| 536 | |||
| 537 | const version = try fbr.readByte(); | ||
| 538 | switch (dwarf_section) { | ||
| 539 | .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, | ||
| 540 | .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, | ||
| 541 | else => return error.UnsupportedDwarfSection, | ||
| 542 | } | ||
| 543 | |||
| 544 | var has_eh_data = false; | ||
| 545 | var has_aug_data = false; | ||
| 546 | |||
| 547 | var aug_str_len: usize = 0; | ||
| 548 | const aug_str_start = fbr.pos; | ||
| 549 | var aug_byte = try fbr.readByte(); | ||
| 550 | while (aug_byte != 0) : (aug_byte = try fbr.readByte()) { | ||
| 551 | switch (aug_byte) { | ||
| 552 | 'z' => { | ||
| 553 | if (aug_str_len != 0) return badDwarf(); | ||
| 554 | has_aug_data = true; | ||
| 555 | }, | ||
| 556 | 'e' => { | ||
| 557 | if (has_aug_data or aug_str_len != 0) return badDwarf(); | ||
| 558 | if (try fbr.readByte() != 'h') return badDwarf(); | ||
| 559 | has_eh_data = true; | ||
| 560 | }, | ||
| 561 | else => if (has_eh_data) return badDwarf(), | ||
| 562 | } | ||
| 563 | |||
| 564 | aug_str_len += 1; | ||
| 565 | } | ||
| 566 | |||
| 567 | if (has_eh_data) { | ||
| 568 | // legacy data created by older versions of gcc - unsupported here | ||
| 569 | for (0..addr_size_bytes) |_| _ = try fbr.readByte(); | ||
| 570 | } | ||
| 571 | |||
| 572 | const address_size = if (version == 4) try fbr.readByte() else addr_size_bytes; | ||
| 573 | const segment_selector_size = if (version == 4) try fbr.readByte() else null; | ||
| 574 | |||
| 575 | const code_alignment_factor = try fbr.readUleb128(u32); | ||
| 576 | const data_alignment_factor = try fbr.readIleb128(i32); | ||
| 577 | const return_address_register = if (version == 1) try fbr.readByte() else try fbr.readUleb128(u8); | ||
| 578 | |||
| 579 | var lsda_pointer_enc: u8 = EH.PE.omit; | ||
| 580 | var personality_enc: ?u8 = null; | ||
| 581 | var personality_routine_pointer: ?u64 = null; | ||
| 582 | var fde_pointer_enc: u8 = EH.PE.absptr; | ||
| 583 | |||
| 584 | var aug_data: []const u8 = &[_]u8{}; | ||
| 585 | const aug_str = if (has_aug_data) blk: { | ||
| 586 | const aug_data_len = try fbr.readUleb128(usize); | ||
| 587 | const aug_data_start = fbr.pos; | ||
| 588 | aug_data = cie_bytes[aug_data_start..][0..aug_data_len]; | ||
| 589 | |||
| 590 | const aug_str = cie_bytes[aug_str_start..][0..aug_str_len]; | ||
| 591 | for (aug_str[1..]) |byte| { | ||
| 592 | switch (byte) { | ||
| 593 | 'L' => { | ||
| 594 | lsda_pointer_enc = try fbr.readByte(); | ||
| 595 | }, | ||
| 596 | 'P' => { | ||
| 597 | personality_enc = try fbr.readByte(); | ||
| 598 | personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{ | ||
| 599 | .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.pos]), pc_rel_offset), | ||
| 600 | .follow_indirect = is_runtime, | ||
| 601 | }); | ||
| 602 | }, | ||
| 603 | 'R' => { | ||
| 604 | fde_pointer_enc = try fbr.readByte(); | ||
| 605 | }, | ||
| 606 | 'S', 'B', 'G' => {}, | ||
| 607 | else => return badDwarf(), | ||
| 608 | } | ||
| 609 | } | ||
| 610 | |||
| 611 | // aug_data_len can include padding so the CIE ends on an address boundary | ||
| 612 | fbr.pos = aug_data_start + aug_data_len; | ||
| 613 | break :blk aug_str; | ||
| 614 | } else &[_]u8{}; | ||
| 615 | |||
| 616 | const initial_instructions = cie_bytes[fbr.pos..]; | ||
| 617 | return .{ | ||
| 618 | .length_offset = length_offset, | ||
| 619 | .version = version, | ||
| 620 | .address_size = address_size, | ||
| 621 | .format = format, | ||
| 622 | .segment_selector_size = segment_selector_size, | ||
| 623 | .code_alignment_factor = code_alignment_factor, | ||
| 624 | .data_alignment_factor = data_alignment_factor, | ||
| 625 | .return_address_register = return_address_register, | ||
| 626 | .aug_str = aug_str, | ||
| 627 | .aug_data = aug_data, | ||
| 628 | .lsda_pointer_enc = lsda_pointer_enc, | ||
| 629 | .personality_enc = personality_enc, | ||
| 630 | .personality_routine_pointer = personality_routine_pointer, | ||
| 631 | .fde_pointer_enc = fde_pointer_enc, | ||
| 632 | .initial_instructions = initial_instructions, | ||
| 633 | }; | ||
| 634 | } | ||
| 635 | }; | ||
| 636 | |||
| 637 | pub const FrameDescriptionEntry = struct { | ||
| 638 | // Offset into eh_frame where the CIE for this FDE is stored | ||
| 639 | cie_length_offset: u64, | ||
| 640 | |||
| 641 | pc_begin: u64, | ||
| 642 | pc_range: u64, | ||
| 643 | lsda_pointer: ?u64, | ||
| 644 | aug_data: []const u8, | ||
| 645 | instructions: []const u8, | ||
| 646 | |||
| 647 | /// This function expects to read the FDE starting at the PC Begin field. | ||
| 648 | /// The returned struct references memory backed by `fde_bytes`. | ||
| 649 | /// | ||
| 650 | /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values | ||
| 651 | /// used when decoding pointers. This should be set to zero if fde_bytes is | ||
| 652 | /// backed by the memory of a .eh_frame / .debug_frame section in the running executable. | ||
| 653 | /// Otherwise, it should be the relative offset to translate addresses from | ||
| 654 | /// where the section is currently stored in memory, to where it *would* be | ||
| 655 | /// stored at runtime: section base addr - backing data base ptr. | ||
| 656 | /// | ||
| 657 | /// Similarly, `is_runtime` specifies this function is being called on a runtime | ||
| 658 | /// section, and so indirect pointers can be followed. | ||
| 659 | pub fn parse( | ||
| 660 | fde_bytes: []const u8, | ||
| 661 | pc_rel_offset: i64, | ||
| 662 | is_runtime: bool, | ||
| 663 | cie: CommonInformationEntry, | ||
| 664 | addr_size_bytes: u8, | ||
| 665 | endian: std.builtin.Endian, | ||
| 666 | ) !FrameDescriptionEntry { | ||
| 667 | if (addr_size_bytes > 8) return error.InvalidAddrSize; | ||
| 668 | |||
| 669 | var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; | ||
| 670 | |||
| 671 | const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ | ||
| 672 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), | ||
| 673 | .follow_indirect = is_runtime, | ||
| 674 | }) orelse return badDwarf(); | ||
| 675 | |||
| 676 | const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ | ||
| 677 | .pc_rel_base = 0, | ||
| 678 | .follow_indirect = false, | ||
| 679 | }) orelse return badDwarf(); | ||
| 680 | |||
| 681 | var aug_data: []const u8 = &[_]u8{}; | ||
| 682 | const lsda_pointer = if (cie.aug_str.len > 0) blk: { | ||
| 683 | const aug_data_len = try fbr.readUleb128(usize); | ||
| 684 | const aug_data_start = fbr.pos; | ||
| 685 | aug_data = fde_bytes[aug_data_start..][0..aug_data_len]; | ||
| 686 | |||
| 687 | const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit) | ||
| 688 | try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{ | ||
| 689 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), | ||
| 690 | .follow_indirect = is_runtime, | ||
| 691 | }) | ||
| 692 | else | ||
| 693 | null; | ||
| 694 | |||
| 695 | fbr.pos = aug_data_start + aug_data_len; | ||
| 696 | break :blk lsda_pointer; | ||
| 697 | } else null; | ||
| 698 | |||
| 699 | const instructions = fde_bytes[fbr.pos..]; | ||
| 700 | return .{ | ||
| 701 | .cie_length_offset = cie.length_offset, | ||
| 702 | .pc_begin = pc_begin, | ||
| 703 | .pc_range = pc_range, | ||
| 704 | .lsda_pointer = lsda_pointer, | ||
| 705 | .aug_data = aug_data, | ||
| 706 | .instructions = instructions, | ||
| 707 | }; | ||
| 708 | } | ||
| 709 | }; | ||
| 710 | |||
| 711 | pub const UnwindContext = struct { | ||
| 712 | allocator: Allocator, | ||
| 713 | cfa: ?usize, | ||
| 714 | pc: usize, | ||
| 715 | thread_context: *std.debug.ThreadContext, | ||
| 716 | reg_context: abi.RegisterContext, | ||
| 717 | vm: call_frame.VirtualMachine, | ||
| 718 | stack_machine: expression.StackMachine(.{ .call_frame_context = true }), | ||
| 719 | |||
| 720 | pub fn init( | ||
| 721 | allocator: Allocator, | ||
| 722 | thread_context: *const std.debug.ThreadContext, | ||
| 723 | ) !UnwindContext { | ||
| 724 | const pc = abi.stripInstructionPtrAuthCode( | ||
| 725 | (try abi.regValueNative( | ||
| 726 | usize, | ||
| 727 | thread_context, | ||
| 728 | abi.ipRegNum(), | ||
| 729 | null, | ||
| 730 | )).*, | ||
| 731 | ); | ||
| 732 | |||
| 733 | const context_copy = try allocator.create(std.debug.ThreadContext); | ||
| 734 | std.debug.copyContext(thread_context, context_copy); | ||
| 735 | |||
| 736 | return .{ | ||
| 737 | .allocator = allocator, | ||
| 738 | .cfa = null, | ||
| 739 | .pc = pc, | ||
| 740 | .thread_context = context_copy, | ||
| 741 | .reg_context = undefined, | ||
| 742 | .vm = .{}, | ||
| 743 | .stack_machine = .{}, | ||
| 744 | }; | ||
| 745 | } | ||
| 746 | |||
| 747 | pub fn deinit(self: *UnwindContext) void { | ||
| 748 | self.vm.deinit(self.allocator); | ||
| 749 | self.stack_machine.deinit(self.allocator); | ||
| 750 | self.allocator.destroy(self.thread_context); | ||
| 751 | self.* = undefined; | ||
| 752 | } | ||
| 753 | |||
| 754 | pub fn getFp(self: *const UnwindContext) !usize { | ||
| 755 | return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*; | ||
| 756 | } | ||
| 757 | }; | ||
| 758 | |||
| 759 | const num_sections = std.enums.directEnumArrayLen(Section.Id, 0); | ||
| 760 | pub const SectionArray = [num_sections]?Section; | ||
| 761 | pub const null_section_array = [_]?Section{null} ** num_sections; | ||
| 762 | |||
| 763 | /// Initialize DWARF info. The caller has the responsibility to initialize most | ||
| 764 | /// the `Dwarf` fields before calling. `binary_mem` is the raw bytes of the | ||
| 765 | /// main binary file (not the secondary debug info file). | ||
| 766 | pub fn open(di: *Dwarf, allocator: Allocator) !void { | ||
| 767 | try di.scanAllFunctions(allocator); | ||
| 768 | try di.scanAllCompileUnits(allocator); | ||
| 769 | } | ||
| 770 | |||
| 771 | const PcRange = struct { | ||
| 772 | start: u64, | ||
| 773 | end: u64, | ||
| 774 | }; | ||
| 775 | |||
| 776 | const Func = struct { | ||
| 777 | pc_range: ?PcRange, | ||
| 778 | name: ?[]const u8, | ||
| 779 | }; | ||
| 780 | |||
| 781 | pub fn section(di: Dwarf, dwarf_section: Section.Id) ?[]const u8 { | ||
| 782 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null; | ||
| 783 | } | ||
| 784 | |||
| 785 | pub fn sectionVirtualOffset(di: Dwarf, dwarf_section: Section.Id, base_address: usize) ?i64 { | ||
| 786 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; | ||
| 787 | } | ||
| 788 | |||
| 789 | pub fn deinit(di: *Dwarf, allocator: Allocator) void { | ||
| 790 | for (di.sections) |opt_section| { | ||
| 791 | if (opt_section) |s| if (s.owned) allocator.free(s.data); | ||
| 792 | } | ||
| 793 | for (di.abbrev_table_list.items) |*abbrev| { | ||
| 794 | abbrev.deinit(allocator); | ||
| 795 | } | ||
| 796 | di.abbrev_table_list.deinit(allocator); | ||
| 797 | for (di.compile_unit_list.items) |*cu| { | ||
| 798 | cu.die.deinit(allocator); | ||
| 799 | } | ||
| 800 | di.compile_unit_list.deinit(allocator); | ||
| 801 | di.func_list.deinit(allocator); | ||
| 802 | di.cie_map.deinit(allocator); | ||
| 803 | di.fde_list.deinit(allocator); | ||
| 804 | di.* = undefined; | ||
| 805 | } | ||
| 806 | |||
| 807 | pub fn getSymbolName(di: *Dwarf, address: u64) ?[]const u8 { | ||
| 808 | for (di.func_list.items) |*func| { | ||
| 809 | if (func.pc_range) |range| { | ||
| 810 | if (address >= range.start and address < range.end) { | ||
| 811 | return func.name; | ||
| 812 | } | ||
| 813 | } | ||
| 814 | } | ||
| 815 | |||
| 816 | return null; | ||
| 817 | } | ||
| 818 | |||
| 819 | fn scanAllFunctions(di: *Dwarf, allocator: Allocator) !void { | ||
| 820 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | ||
| 821 | var this_unit_offset: u64 = 0; | ||
| 822 | |||
| 823 | while (this_unit_offset < fbr.buf.len) { | ||
| 824 | try fbr.seekTo(this_unit_offset); | ||
| 825 | |||
| 826 | const unit_header = try readUnitHeader(&fbr, null); | ||
| 827 | if (unit_header.unit_length == 0) return; | ||
| 828 | const next_offset = unit_header.header_length + unit_header.unit_length; | ||
| 829 | |||
| 830 | const version = try fbr.readInt(u16); | ||
| 831 | if (version < 2 or version > 5) return badDwarf(); | ||
| 832 | |||
| 833 | var address_size: u8 = undefined; | ||
| 834 | var debug_abbrev_offset: u64 = undefined; | ||
| 835 | if (version >= 5) { | ||
| 836 | const unit_type = try fbr.readInt(u8); | ||
| 837 | if (unit_type != DW.UT.compile) return badDwarf(); | ||
| 838 | address_size = try fbr.readByte(); | ||
| 839 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | ||
| 840 | } else { | ||
| 841 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | ||
| 842 | address_size = try fbr.readByte(); | ||
| 843 | } | ||
| 844 | if (address_size != @sizeOf(usize)) return badDwarf(); | ||
| 845 | |||
| 846 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); | ||
| 847 | |||
| 848 | var max_attrs: usize = 0; | ||
| 849 | var zig_padding_abbrev_code: u7 = 0; | ||
| 850 | for (abbrev_table.abbrevs) |abbrev| { | ||
| 851 | max_attrs = @max(max_attrs, abbrev.attrs.len); | ||
| 852 | if (cast(u7, abbrev.code)) |code| { | ||
| 853 | if (abbrev.tag_id == DW.TAG.ZIG_padding and | ||
| 854 | !abbrev.has_children and | ||
| 855 | abbrev.attrs.len == 0) | ||
| 856 | { | ||
| 857 | zig_padding_abbrev_code = code; | ||
| 858 | } | ||
| 859 | } | ||
| 860 | } | ||
| 861 | const attrs_buf = try allocator.alloc(Die.Attr, max_attrs * 3); | ||
| 862 | defer allocator.free(attrs_buf); | ||
| 863 | var attrs_bufs: [3][]Die.Attr = undefined; | ||
| 864 | for (&attrs_bufs, 0..) |*buf, index| buf.* = attrs_buf[index * max_attrs ..][0..max_attrs]; | ||
| 865 | |||
| 866 | const next_unit_pos = this_unit_offset + next_offset; | ||
| 867 | |||
| 868 | var compile_unit: CompileUnit = .{ | ||
| 869 | .version = version, | ||
| 870 | .format = unit_header.format, | ||
| 871 | .die = undefined, | ||
| 872 | .pc_range = null, | ||
| 873 | |||
| 874 | .str_offsets_base = 0, | ||
| 875 | .addr_base = 0, | ||
| 876 | .rnglists_base = 0, | ||
| 877 | .loclists_base = 0, | ||
| 878 | .frame_base = null, | ||
| 879 | }; | ||
| 880 | |||
| 881 | while (true) { | ||
| 882 | fbr.pos = std.mem.indexOfNonePos(u8, fbr.buf, fbr.pos, &.{ | ||
| 883 | zig_padding_abbrev_code, 0, | ||
| 884 | }) orelse fbr.buf.len; | ||
| 885 | if (fbr.pos >= next_unit_pos) break; | ||
| 886 | var die_obj = (try parseDie( | ||
| 887 | &fbr, | ||
| 888 | attrs_bufs[0], | ||
| 889 | abbrev_table, | ||
| 890 | unit_header.format, | ||
| 891 | )) orelse continue; | ||
| 892 | |||
| 893 | switch (die_obj.tag_id) { | ||
| 894 | DW.TAG.compile_unit => { | ||
| 895 | compile_unit.die = die_obj; | ||
| 896 | compile_unit.die.attrs = attrs_bufs[1][0..die_obj.attrs.len]; | ||
| 897 | @memcpy(compile_unit.die.attrs, die_obj.attrs); | ||
| 898 | |||
| 899 | compile_unit.str_offsets_base = if (die_obj.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0; | ||
| 900 | compile_unit.addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0; | ||
| 901 | compile_unit.rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0; | ||
| 902 | compile_unit.loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0; | ||
| 903 | compile_unit.frame_base = die_obj.getAttr(AT.frame_base); | ||
| 904 | }, | ||
| 905 | DW.TAG.subprogram, DW.TAG.inlined_subroutine, DW.TAG.subroutine, DW.TAG.entry_point => { | ||
| 906 | const fn_name = x: { | ||
| 907 | var this_die_obj = die_obj; | ||
| 908 | // Prevent endless loops | ||
| 909 | for (0..3) |_| { | ||
| 910 | if (this_die_obj.getAttr(AT.name)) |_| { | ||
| 911 | break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit); | ||
| 912 | } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| { | ||
| 913 | const after_die_offset = fbr.pos; | ||
| 914 | defer fbr.pos = after_die_offset; | ||
| 915 | |||
| 916 | // Follow the DIE it points to and repeat | ||
| 917 | const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); | ||
| 918 | if (ref_offset > next_offset) return badDwarf(); | ||
| 919 | try fbr.seekTo(this_unit_offset + ref_offset); | ||
| 920 | this_die_obj = (try parseDie( | ||
| 921 | &fbr, | ||
| 922 | attrs_bufs[2], | ||
| 923 | abbrev_table, | ||
| 924 | unit_header.format, | ||
| 925 | )) orelse return badDwarf(); | ||
| 926 | } else if (this_die_obj.getAttr(AT.specification)) |_| { | ||
| 927 | const after_die_offset = fbr.pos; | ||
| 928 | defer fbr.pos = after_die_offset; | ||
| 929 | |||
| 930 | // Follow the DIE it points to and repeat | ||
| 931 | const ref_offset = try this_die_obj.getAttrRef(AT.specification); | ||
| 932 | if (ref_offset > next_offset) return badDwarf(); | ||
| 933 | try fbr.seekTo(this_unit_offset + ref_offset); | ||
| 934 | this_die_obj = (try parseDie( | ||
| 935 | &fbr, | ||
| 936 | attrs_bufs[2], | ||
| 937 | abbrev_table, | ||
| 938 | unit_header.format, | ||
| 939 | )) orelse return badDwarf(); | ||
| 940 | } else { | ||
| 941 | break :x null; | ||
| 942 | } | ||
| 943 | } | ||
| 944 | |||
| 945 | break :x null; | ||
| 946 | }; | ||
| 947 | |||
| 948 | var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: { | ||
| 949 | if (die_obj.getAttr(AT.high_pc)) |high_pc_value| { | ||
| 950 | const pc_end = switch (high_pc_value.*) { | ||
| 951 | .addr => |value| value, | ||
| 952 | .udata => |offset| low_pc + offset, | ||
| 953 | else => return badDwarf(), | ||
| 954 | }; | ||
| 955 | |||
| 956 | try di.func_list.append(allocator, .{ | ||
| 957 | .name = fn_name, | ||
| 958 | .pc_range = .{ | ||
| 959 | .start = low_pc, | ||
| 960 | .end = pc_end, | ||
| 961 | }, | ||
| 962 | }); | ||
| 963 | |||
| 964 | break :blk true; | ||
| 965 | } | ||
| 966 | |||
| 967 | break :blk false; | ||
| 968 | } else |err| blk: { | ||
| 969 | if (err != error.MissingDebugInfo) return err; | ||
| 970 | break :blk false; | ||
| 971 | }; | ||
| 972 | |||
| 973 | if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: { | ||
| 974 | var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| { | ||
| 975 | if (err != error.MissingDebugInfo) return err; | ||
| 976 | break :blk; | ||
| 977 | }; | ||
| 978 | |||
| 979 | while (try iter.next()) |range| { | ||
| 980 | range_added = true; | ||
| 981 | try di.func_list.append(allocator, .{ | ||
| 982 | .name = fn_name, | ||
| 983 | .pc_range = .{ | ||
| 984 | .start = range.start_addr, | ||
| 985 | .end = range.end_addr, | ||
| 986 | }, | ||
| 987 | }); | ||
| 988 | } | ||
| 989 | } | ||
| 990 | |||
| 991 | if (fn_name != null and !range_added) { | ||
| 992 | try di.func_list.append(allocator, .{ | ||
| 993 | .name = fn_name, | ||
| 994 | .pc_range = null, | ||
| 995 | }); | ||
| 996 | } | ||
| 997 | }, | ||
| 998 | else => {}, | ||
| 999 | } | ||
| 1000 | } | ||
| 1001 | |||
| 1002 | this_unit_offset += next_offset; | ||
| 1003 | } | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | fn scanAllCompileUnits(di: *Dwarf, allocator: Allocator) !void { | ||
| 1007 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | ||
| 1008 | var this_unit_offset: u64 = 0; | ||
| 1009 | |||
| 1010 | var attrs_buf = std.ArrayList(Die.Attr).init(allocator); | ||
| 1011 | defer attrs_buf.deinit(); | ||
| 1012 | |||
| 1013 | while (this_unit_offset < fbr.buf.len) { | ||
| 1014 | try fbr.seekTo(this_unit_offset); | ||
| 1015 | |||
| 1016 | const unit_header = try readUnitHeader(&fbr, null); | ||
| 1017 | if (unit_header.unit_length == 0) return; | ||
| 1018 | const next_offset = unit_header.header_length + unit_header.unit_length; | ||
| 1019 | |||
| 1020 | const version = try fbr.readInt(u16); | ||
| 1021 | if (version < 2 or version > 5) return badDwarf(); | ||
| 1022 | |||
| 1023 | var address_size: u8 = undefined; | ||
| 1024 | var debug_abbrev_offset: u64 = undefined; | ||
| 1025 | if (version >= 5) { | ||
| 1026 | const unit_type = try fbr.readInt(u8); | ||
| 1027 | if (unit_type != UT.compile) return badDwarf(); | ||
| 1028 | address_size = try fbr.readByte(); | ||
| 1029 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | ||
| 1030 | } else { | ||
| 1031 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | ||
| 1032 | address_size = try fbr.readByte(); | ||
| 1033 | } | ||
| 1034 | if (address_size != @sizeOf(usize)) return badDwarf(); | ||
| 1035 | |||
| 1036 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); | ||
| 1037 | |||
| 1038 | var max_attrs: usize = 0; | ||
| 1039 | for (abbrev_table.abbrevs) |abbrev| { | ||
| 1040 | max_attrs = @max(max_attrs, abbrev.attrs.len); | ||
| 1041 | } | ||
| 1042 | try attrs_buf.resize(max_attrs); | ||
| 1043 | |||
| 1044 | var compile_unit_die = (try parseDie( | ||
| 1045 | &fbr, | ||
| 1046 | attrs_buf.items, | ||
| 1047 | abbrev_table, | ||
| 1048 | unit_header.format, | ||
| 1049 | )) orelse return badDwarf(); | ||
| 1050 | |||
| 1051 | if (compile_unit_die.tag_id != DW.TAG.compile_unit) return badDwarf(); | ||
| 1052 | |||
| 1053 | compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs); | ||
| 1054 | |||
| 1055 | var compile_unit: CompileUnit = .{ | ||
| 1056 | .version = version, | ||
| 1057 | .format = unit_header.format, | ||
| 1058 | .pc_range = null, | ||
| 1059 | .die = compile_unit_die, | ||
| 1060 | .str_offsets_base = if (compile_unit_die.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0, | ||
| 1061 | .addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0, | ||
| 1062 | .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, | ||
| 1063 | .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, | ||
| 1064 | .frame_base = compile_unit_die.getAttr(AT.frame_base), | ||
| 1065 | }; | ||
| 1066 | |||
| 1067 | compile_unit.pc_range = x: { | ||
| 1068 | if (compile_unit_die.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| { | ||
| 1069 | if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| { | ||
| 1070 | const pc_end = switch (high_pc_value.*) { | ||
| 1071 | .addr => |value| value, | ||
| 1072 | .udata => |offset| low_pc + offset, | ||
| 1073 | else => return badDwarf(), | ||
| 1074 | }; | ||
| 1075 | break :x PcRange{ | ||
| 1076 | .start = low_pc, | ||
| 1077 | .end = pc_end, | ||
| 1078 | }; | ||
| 1079 | } else { | ||
| 1080 | break :x null; | ||
| 1081 | } | ||
| 1082 | } else |err| { | ||
| 1083 | if (err != error.MissingDebugInfo) return err; | ||
| 1084 | break :x null; | ||
| 1085 | } | ||
| 1086 | }; | ||
| 1087 | |||
| 1088 | try di.compile_unit_list.append(allocator, compile_unit); | ||
| 1089 | |||
| 1090 | this_unit_offset += next_offset; | ||
| 1091 | } | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | const DebugRangeIterator = struct { | ||
| 1095 | base_address: u64, | ||
| 1096 | section_type: Section.Id, | ||
| 1097 | di: *const Dwarf, | ||
| 1098 | compile_unit: *const CompileUnit, | ||
| 1099 | fbr: FixedBufferReader, | ||
| 1100 | |||
| 1101 | pub fn init(ranges_value: *const FormValue, di: *const Dwarf, compile_unit: *const CompileUnit) !@This() { | ||
| 1102 | const section_type = if (compile_unit.version >= 5) Section.Id.debug_rnglists else Section.Id.debug_ranges; | ||
| 1103 | const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo; | ||
| 1104 | |||
| 1105 | const ranges_offset = switch (ranges_value.*) { | ||
| 1106 | .sec_offset, .udata => |off| off, | ||
| 1107 | .rnglistx => |idx| off: { | ||
| 1108 | switch (compile_unit.format) { | ||
| 1109 | .@"32" => { | ||
| 1110 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); | ||
| 1111 | if (offset_loc + 4 > debug_ranges.len) return badDwarf(); | ||
| 1112 | const offset = readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); | ||
| 1113 | break :off compile_unit.rnglists_base + offset; | ||
| 1114 | }, | ||
| 1115 | .@"64" => { | ||
| 1116 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); | ||
| 1117 | if (offset_loc + 8 > debug_ranges.len) return badDwarf(); | ||
| 1118 | const offset = readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); | ||
| 1119 | break :off compile_unit.rnglists_base + offset; | ||
| 1120 | }, | ||
| 1121 | } | ||
| 1122 | }, | ||
| 1123 | else => return badDwarf(), | ||
| 1124 | }; | ||
| 1125 | |||
| 1126 | // All the addresses in the list are relative to the value | ||
| 1127 | // specified by DW_AT.low_pc or to some other value encoded | ||
| 1128 | // in the list itself. | ||
| 1129 | // If no starting value is specified use zero. | ||
| 1130 | const base_address = compile_unit.die.getAttrAddr(di, AT.low_pc, compile_unit.*) catch |err| switch (err) { | ||
| 1131 | error.MissingDebugInfo => 0, | ||
| 1132 | else => return err, | ||
| 1133 | }; | ||
| 1134 | |||
| 1135 | return .{ | ||
| 1136 | .base_address = base_address, | ||
| 1137 | .section_type = section_type, | ||
| 1138 | .di = di, | ||
| 1139 | .compile_unit = compile_unit, | ||
| 1140 | .fbr = .{ | ||
| 1141 | .buf = debug_ranges, | ||
| 1142 | .pos = cast(usize, ranges_offset) orelse return badDwarf(), | ||
| 1143 | .endian = di.endian, | ||
| 1144 | }, | ||
| 1145 | }; | ||
| 1146 | } | ||
| 1147 | |||
| 1148 | // Returns the next range in the list, or null if the end was reached. | ||
| 1149 | pub fn next(self: *@This()) !?struct { start_addr: u64, end_addr: u64 } { | ||
| 1150 | switch (self.section_type) { | ||
| 1151 | .debug_rnglists => { | ||
| 1152 | const kind = try self.fbr.readByte(); | ||
| 1153 | switch (kind) { | ||
| 1154 | RLE.end_of_list => return null, | ||
| 1155 | RLE.base_addressx => { | ||
| 1156 | const index = try self.fbr.readUleb128(usize); | ||
| 1157 | self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index); | ||
| 1158 | return try self.next(); | ||
| 1159 | }, | ||
| 1160 | RLE.startx_endx => { | ||
| 1161 | const start_index = try self.fbr.readUleb128(usize); | ||
| 1162 | const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); | ||
| 1163 | |||
| 1164 | const end_index = try self.fbr.readUleb128(usize); | ||
| 1165 | const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index); | ||
| 1166 | |||
| 1167 | return .{ | ||
| 1168 | .start_addr = start_addr, | ||
| 1169 | .end_addr = end_addr, | ||
| 1170 | }; | ||
| 1171 | }, | ||
| 1172 | RLE.startx_length => { | ||
| 1173 | const start_index = try self.fbr.readUleb128(usize); | ||
| 1174 | const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); | ||
| 1175 | |||
| 1176 | const len = try self.fbr.readUleb128(usize); | ||
| 1177 | const end_addr = start_addr + len; | ||
| 1178 | |||
| 1179 | return .{ | ||
| 1180 | .start_addr = start_addr, | ||
| 1181 | .end_addr = end_addr, | ||
| 1182 | }; | ||
| 1183 | }, | ||
| 1184 | RLE.offset_pair => { | ||
| 1185 | const start_addr = try self.fbr.readUleb128(usize); | ||
| 1186 | const end_addr = try self.fbr.readUleb128(usize); | ||
| 1187 | |||
| 1188 | // This is the only kind that uses the base address | ||
| 1189 | return .{ | ||
| 1190 | .start_addr = self.base_address + start_addr, | ||
| 1191 | .end_addr = self.base_address + end_addr, | ||
| 1192 | }; | ||
| 1193 | }, | ||
| 1194 | RLE.base_address => { | ||
| 1195 | self.base_address = try self.fbr.readInt(usize); | ||
| 1196 | return try self.next(); | ||
| 1197 | }, | ||
| 1198 | RLE.start_end => { | ||
| 1199 | const start_addr = try self.fbr.readInt(usize); | ||
| 1200 | const end_addr = try self.fbr.readInt(usize); | ||
| 1201 | |||
| 1202 | return .{ | ||
| 1203 | .start_addr = start_addr, | ||
| 1204 | .end_addr = end_addr, | ||
| 1205 | }; | ||
| 1206 | }, | ||
| 1207 | RLE.start_length => { | ||
| 1208 | const start_addr = try self.fbr.readInt(usize); | ||
| 1209 | const len = try self.fbr.readUleb128(usize); | ||
| 1210 | const end_addr = start_addr + len; | ||
| 1211 | |||
| 1212 | return .{ | ||
| 1213 | .start_addr = start_addr, | ||
| 1214 | .end_addr = end_addr, | ||
| 1215 | }; | ||
| 1216 | }, | ||
| 1217 | else => return badDwarf(), | ||
| 1218 | } | ||
| 1219 | }, | ||
| 1220 | .debug_ranges => { | ||
| 1221 | const start_addr = try self.fbr.readInt(usize); | ||
| 1222 | const end_addr = try self.fbr.readInt(usize); | ||
| 1223 | if (start_addr == 0 and end_addr == 0) return null; | ||
| 1224 | |||
| 1225 | // This entry selects a new value for the base address | ||
| 1226 | if (start_addr == maxInt(usize)) { | ||
| 1227 | self.base_address = end_addr; | ||
| 1228 | return try self.next(); | ||
| 1229 | } | ||
| 1230 | |||
| 1231 | return .{ | ||
| 1232 | .start_addr = self.base_address + start_addr, | ||
| 1233 | .end_addr = self.base_address + end_addr, | ||
| 1234 | }; | ||
| 1235 | }, | ||
| 1236 | else => unreachable, | ||
| 1237 | } | ||
| 1238 | } | ||
| 1239 | }; | ||
| 1240 | |||
| 1241 | pub fn findCompileUnit(di: *const Dwarf, target_address: u64) !*const CompileUnit { | ||
| 1242 | for (di.compile_unit_list.items) |*compile_unit| { | ||
| 1243 | if (compile_unit.pc_range) |range| { | ||
| 1244 | if (target_address >= range.start and target_address < range.end) return compile_unit; | ||
| 1245 | } | ||
| 1246 | |||
| 1247 | const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue; | ||
| 1248 | var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue; | ||
| 1249 | while (try iter.next()) |range| { | ||
| 1250 | if (target_address >= range.start_addr and target_address < range.end_addr) return compile_unit; | ||
| 1251 | } | ||
| 1252 | } | ||
| 1253 | |||
| 1254 | return missingDwarf(); | ||
| 1255 | } | ||
| 1256 | |||
| 1257 | /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, | ||
| 1258 | /// seeks in the stream and parses it. | ||
| 1259 | fn getAbbrevTable(di: *Dwarf, allocator: Allocator, abbrev_offset: u64) !*const Abbrev.Table { | ||
| 1260 | for (di.abbrev_table_list.items) |*table| { | ||
| 1261 | if (table.offset == abbrev_offset) { | ||
| 1262 | return table; | ||
| 1263 | } | ||
| 1264 | } | ||
| 1265 | try di.abbrev_table_list.append( | ||
| 1266 | allocator, | ||
| 1267 | try di.parseAbbrevTable(allocator, abbrev_offset), | ||
| 1268 | ); | ||
| 1269 | return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1]; | ||
| 1270 | } | ||
| 1271 | |||
| 1272 | fn parseAbbrevTable(di: *Dwarf, allocator: Allocator, offset: u64) !Abbrev.Table { | ||
| 1273 | var fbr: FixedBufferReader = .{ | ||
| 1274 | .buf = di.section(.debug_abbrev).?, | ||
| 1275 | .pos = cast(usize, offset) orelse return badDwarf(), | ||
| 1276 | .endian = di.endian, | ||
| 1277 | }; | ||
| 1278 | |||
| 1279 | var abbrevs = std.ArrayList(Abbrev).init(allocator); | ||
| 1280 | defer { | ||
| 1281 | for (abbrevs.items) |*abbrev| { | ||
| 1282 | abbrev.deinit(allocator); | ||
| 1283 | } | ||
| 1284 | abbrevs.deinit(); | ||
| 1285 | } | ||
| 1286 | |||
| 1287 | var attrs = std.ArrayList(Abbrev.Attr).init(allocator); | ||
| 1288 | defer attrs.deinit(); | ||
| 1289 | |||
| 1290 | while (true) { | ||
| 1291 | const code = try fbr.readUleb128(u64); | ||
| 1292 | if (code == 0) break; | ||
| 1293 | const tag_id = try fbr.readUleb128(u64); | ||
| 1294 | const has_children = (try fbr.readByte()) == DW.CHILDREN.yes; | ||
| 1295 | |||
| 1296 | while (true) { | ||
| 1297 | const attr_id = try fbr.readUleb128(u64); | ||
| 1298 | const form_id = try fbr.readUleb128(u64); | ||
| 1299 | if (attr_id == 0 and form_id == 0) break; | ||
| 1300 | try attrs.append(.{ | ||
| 1301 | .id = attr_id, | ||
| 1302 | .form_id = form_id, | ||
| 1303 | .payload = switch (form_id) { | ||
| 1304 | FORM.implicit_const => try fbr.readIleb128(i64), | ||
| 1305 | else => undefined, | ||
| 1306 | }, | ||
| 1307 | }); | ||
| 1308 | } | ||
| 1309 | |||
| 1310 | try abbrevs.append(.{ | ||
| 1311 | .code = code, | ||
| 1312 | .tag_id = tag_id, | ||
| 1313 | .has_children = has_children, | ||
| 1314 | .attrs = try attrs.toOwnedSlice(), | ||
| 1315 | }); | ||
| 1316 | } | ||
| 1317 | |||
| 1318 | return .{ | ||
| 1319 | .offset = offset, | ||
| 1320 | .abbrevs = try abbrevs.toOwnedSlice(), | ||
| 1321 | }; | ||
| 1322 | } | ||
| 1323 | |||
| 1324 | fn parseDie( | ||
| 1325 | fbr: *FixedBufferReader, | ||
| 1326 | attrs_buf: []Die.Attr, | ||
| 1327 | abbrev_table: *const Abbrev.Table, | ||
| 1328 | format: Format, | ||
| 1329 | ) !?Die { | ||
| 1330 | const abbrev_code = try fbr.readUleb128(u64); | ||
| 1331 | if (abbrev_code == 0) return null; | ||
| 1332 | const table_entry = abbrev_table.get(abbrev_code) orelse return badDwarf(); | ||
| 1333 | |||
| 1334 | const attrs = attrs_buf[0..table_entry.attrs.len]; | ||
| 1335 | for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = Die.Attr{ | ||
| 1336 | .id = attr.id, | ||
| 1337 | .value = try parseFormValue( | ||
| 1338 | fbr, | ||
| 1339 | attr.form_id, | ||
| 1340 | format, | ||
| 1341 | attr.payload, | ||
| 1342 | ), | ||
| 1343 | }; | ||
| 1344 | return .{ | ||
| 1345 | .tag_id = table_entry.tag_id, | ||
| 1346 | .has_children = table_entry.has_children, | ||
| 1347 | .attrs = attrs, | ||
| 1348 | }; | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | pub fn getLineNumberInfo( | ||
| 1352 | di: *Dwarf, | ||
| 1353 | allocator: Allocator, | ||
| 1354 | compile_unit: CompileUnit, | ||
| 1355 | target_address: u64, | ||
| 1356 | ) !std.debug.LineInfo { | ||
| 1357 | const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); | ||
| 1358 | const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); | ||
| 1359 | |||
| 1360 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; | ||
| 1361 | try fbr.seekTo(line_info_offset); | ||
| 1362 | |||
| 1363 | const unit_header = try readUnitHeader(&fbr, null); | ||
| 1364 | if (unit_header.unit_length == 0) return missingDwarf(); | ||
| 1365 | const next_offset = unit_header.header_length + unit_header.unit_length; | ||
| 1366 | |||
| 1367 | const version = try fbr.readInt(u16); | ||
| 1368 | if (version < 2) return badDwarf(); | ||
| 1369 | |||
| 1370 | var addr_size: u8 = switch (unit_header.format) { | ||
| 1371 | .@"32" => 4, | ||
| 1372 | .@"64" => 8, | ||
| 1373 | }; | ||
| 1374 | var seg_size: u8 = 0; | ||
| 1375 | if (version >= 5) { | ||
| 1376 | addr_size = try fbr.readByte(); | ||
| 1377 | seg_size = try fbr.readByte(); | ||
| 1378 | } | ||
| 1379 | |||
| 1380 | const prologue_length = try fbr.readAddress(unit_header.format); | ||
| 1381 | const prog_start_offset = fbr.pos + prologue_length; | ||
| 1382 | |||
| 1383 | const minimum_instruction_length = try fbr.readByte(); | ||
| 1384 | if (minimum_instruction_length == 0) return badDwarf(); | ||
| 1385 | |||
| 1386 | if (version >= 4) { | ||
| 1387 | // maximum_operations_per_instruction | ||
| 1388 | _ = try fbr.readByte(); | ||
| 1389 | } | ||
| 1390 | |||
| 1391 | const default_is_stmt = (try fbr.readByte()) != 0; | ||
| 1392 | const line_base = try fbr.readByteSigned(); | ||
| 1393 | |||
| 1394 | const line_range = try fbr.readByte(); | ||
| 1395 | if (line_range == 0) return badDwarf(); | ||
| 1396 | |||
| 1397 | const opcode_base = try fbr.readByte(); | ||
| 1398 | |||
| 1399 | const standard_opcode_lengths = try fbr.readBytes(opcode_base - 1); | ||
| 1400 | |||
| 1401 | var include_directories = std.ArrayList(FileEntry).init(allocator); | ||
| 1402 | defer include_directories.deinit(); | ||
| 1403 | var file_entries = std.ArrayList(FileEntry).init(allocator); | ||
| 1404 | defer file_entries.deinit(); | ||
| 1405 | |||
| 1406 | if (version < 5) { | ||
| 1407 | try include_directories.append(.{ .path = compile_unit_cwd }); | ||
| 1408 | |||
| 1409 | while (true) { | ||
| 1410 | const dir = try fbr.readBytesTo(0); | ||
| 1411 | if (dir.len == 0) break; | ||
| 1412 | try include_directories.append(.{ .path = dir }); | ||
| 1413 | } | ||
| 1414 | |||
| 1415 | while (true) { | ||
| 1416 | const file_name = try fbr.readBytesTo(0); | ||
| 1417 | if (file_name.len == 0) break; | ||
| 1418 | const dir_index = try fbr.readUleb128(u32); | ||
| 1419 | const mtime = try fbr.readUleb128(u64); | ||
| 1420 | const size = try fbr.readUleb128(u64); | ||
| 1421 | try file_entries.append(.{ | ||
| 1422 | .path = file_name, | ||
| 1423 | .dir_index = dir_index, | ||
| 1424 | .mtime = mtime, | ||
| 1425 | .size = size, | ||
| 1426 | }); | ||
| 1427 | } | ||
| 1428 | } else { | ||
| 1429 | const FileEntFmt = struct { | ||
| 1430 | content_type_code: u8, | ||
| 1431 | form_code: u16, | ||
| 1432 | }; | ||
| 1433 | { | ||
| 1434 | var dir_ent_fmt_buf: [10]FileEntFmt = undefined; | ||
| 1435 | const directory_entry_format_count = try fbr.readByte(); | ||
| 1436 | if (directory_entry_format_count > dir_ent_fmt_buf.len) return badDwarf(); | ||
| 1437 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { | ||
| 1438 | ent_fmt.* = .{ | ||
| 1439 | .content_type_code = try fbr.readUleb128(u8), | ||
| 1440 | .form_code = try fbr.readUleb128(u16), | ||
| 1441 | }; | ||
| 1442 | } | ||
| 1443 | |||
| 1444 | const directories_count = try fbr.readUleb128(usize); | ||
| 1445 | try include_directories.ensureUnusedCapacity(directories_count); | ||
| 1446 | { | ||
| 1447 | var i: usize = 0; | ||
| 1448 | while (i < directories_count) : (i += 1) { | ||
| 1449 | var e: FileEntry = .{ .path = &.{} }; | ||
| 1450 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { | ||
| 1451 | const form_value = try parseFormValue( | ||
| 1452 | &fbr, | ||
| 1453 | ent_fmt.form_code, | ||
| 1454 | unit_header.format, | ||
| 1455 | null, | ||
| 1456 | ); | ||
| 1457 | switch (ent_fmt.content_type_code) { | ||
| 1458 | DW.LNCT.path => e.path = try form_value.getString(di.*), | ||
| 1459 | DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), | ||
| 1460 | DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), | ||
| 1461 | DW.LNCT.size => e.size = try form_value.getUInt(u64), | ||
| 1462 | DW.LNCT.MD5 => e.md5 = switch (form_value) { | ||
| 1463 | .data16 => |data16| data16.*, | ||
| 1464 | else => return badDwarf(), | ||
| 1465 | }, | ||
| 1466 | else => continue, | ||
| 1467 | } | ||
| 1468 | } | ||
| 1469 | include_directories.appendAssumeCapacity(e); | ||
| 1470 | } | ||
| 1471 | } | ||
| 1472 | } | ||
| 1473 | |||
| 1474 | var file_ent_fmt_buf: [10]FileEntFmt = undefined; | ||
| 1475 | const file_name_entry_format_count = try fbr.readByte(); | ||
| 1476 | if (file_name_entry_format_count > file_ent_fmt_buf.len) return badDwarf(); | ||
| 1477 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { | ||
| 1478 | ent_fmt.* = .{ | ||
| 1479 | .content_type_code = try fbr.readUleb128(u8), | ||
| 1480 | .form_code = try fbr.readUleb128(u16), | ||
| 1481 | }; | ||
| 1482 | } | ||
| 1483 | |||
| 1484 | const file_names_count = try fbr.readUleb128(usize); | ||
| 1485 | try file_entries.ensureUnusedCapacity(file_names_count); | ||
| 1486 | { | ||
| 1487 | var i: usize = 0; | ||
| 1488 | while (i < file_names_count) : (i += 1) { | ||
| 1489 | var e: FileEntry = .{ .path = &.{} }; | ||
| 1490 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { | ||
| 1491 | const form_value = try parseFormValue( | ||
| 1492 | &fbr, | ||
| 1493 | ent_fmt.form_code, | ||
| 1494 | unit_header.format, | ||
| 1495 | null, | ||
| 1496 | ); | ||
| 1497 | switch (ent_fmt.content_type_code) { | ||
| 1498 | DW.LNCT.path => e.path = try form_value.getString(di.*), | ||
| 1499 | DW.LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), | ||
| 1500 | DW.LNCT.timestamp => e.mtime = try form_value.getUInt(u64), | ||
| 1501 | DW.LNCT.size => e.size = try form_value.getUInt(u64), | ||
| 1502 | DW.LNCT.MD5 => e.md5 = switch (form_value) { | ||
| 1503 | .data16 => |data16| data16.*, | ||
| 1504 | else => return badDwarf(), | ||
| 1505 | }, | ||
| 1506 | else => continue, | ||
| 1507 | } | ||
| 1508 | } | ||
| 1509 | file_entries.appendAssumeCapacity(e); | ||
| 1510 | } | ||
| 1511 | } | ||
| 1512 | } | ||
| 1513 | |||
| 1514 | var prog = LineNumberProgram.init( | ||
| 1515 | default_is_stmt, | ||
| 1516 | include_directories.items, | ||
| 1517 | target_address, | ||
| 1518 | version, | ||
| 1519 | ); | ||
| 1520 | |||
| 1521 | try fbr.seekTo(prog_start_offset); | ||
| 1522 | |||
| 1523 | const next_unit_pos = line_info_offset + next_offset; | ||
| 1524 | |||
| 1525 | while (fbr.pos < next_unit_pos) { | ||
| 1526 | const opcode = try fbr.readByte(); | ||
| 1527 | |||
| 1528 | if (opcode == DW.LNS.extended_op) { | ||
| 1529 | const op_size = try fbr.readUleb128(u64); | ||
| 1530 | if (op_size < 1) return badDwarf(); | ||
| 1531 | const sub_op = try fbr.readByte(); | ||
| 1532 | switch (sub_op) { | ||
| 1533 | DW.LNE.end_sequence => { | ||
| 1534 | prog.end_sequence = true; | ||
| 1535 | if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; | ||
| 1536 | prog.reset(); | ||
| 1537 | }, | ||
| 1538 | DW.LNE.set_address => { | ||
| 1539 | const addr = try fbr.readInt(usize); | ||
| 1540 | prog.address = addr; | ||
| 1541 | }, | ||
| 1542 | DW.LNE.define_file => { | ||
| 1543 | const path = try fbr.readBytesTo(0); | ||
| 1544 | const dir_index = try fbr.readUleb128(u32); | ||
| 1545 | const mtime = try fbr.readUleb128(u64); | ||
| 1546 | const size = try fbr.readUleb128(u64); | ||
| 1547 | try file_entries.append(.{ | ||
| 1548 | .path = path, | ||
| 1549 | .dir_index = dir_index, | ||
| 1550 | .mtime = mtime, | ||
| 1551 | .size = size, | ||
| 1552 | }); | ||
| 1553 | }, | ||
| 1554 | else => try fbr.seekForward(op_size - 1), | ||
| 1555 | } | ||
| 1556 | } else if (opcode >= opcode_base) { | ||
| 1557 | // special opcodes | ||
| 1558 | const adjusted_opcode = opcode - opcode_base; | ||
| 1559 | const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); | ||
| 1560 | const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range); | ||
| 1561 | prog.line += inc_line; | ||
| 1562 | prog.address += inc_addr; | ||
| 1563 | if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; | ||
| 1564 | prog.basic_block = false; | ||
| 1565 | } else { | ||
| 1566 | switch (opcode) { | ||
| 1567 | DW.LNS.copy => { | ||
| 1568 | if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; | ||
| 1569 | prog.basic_block = false; | ||
| 1570 | }, | ||
| 1571 | DW.LNS.advance_pc => { | ||
| 1572 | const arg = try fbr.readUleb128(usize); | ||
| 1573 | prog.address += arg * minimum_instruction_length; | ||
| 1574 | }, | ||
| 1575 | DW.LNS.advance_line => { | ||
| 1576 | const arg = try fbr.readIleb128(i64); | ||
| 1577 | prog.line += arg; | ||
| 1578 | }, | ||
| 1579 | DW.LNS.set_file => { | ||
| 1580 | const arg = try fbr.readUleb128(usize); | ||
| 1581 | prog.file = arg; | ||
| 1582 | }, | ||
| 1583 | DW.LNS.set_column => { | ||
| 1584 | const arg = try fbr.readUleb128(u64); | ||
| 1585 | prog.column = arg; | ||
| 1586 | }, | ||
| 1587 | DW.LNS.negate_stmt => { | ||
| 1588 | prog.is_stmt = !prog.is_stmt; | ||
| 1589 | }, | ||
| 1590 | DW.LNS.set_basic_block => { | ||
| 1591 | prog.basic_block = true; | ||
| 1592 | }, | ||
| 1593 | DW.LNS.const_add_pc => { | ||
| 1594 | const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); | ||
| 1595 | prog.address += inc_addr; | ||
| 1596 | }, | ||
| 1597 | DW.LNS.fixed_advance_pc => { | ||
| 1598 | const arg = try fbr.readInt(u16); | ||
| 1599 | prog.address += arg; | ||
| 1600 | }, | ||
| 1601 | DW.LNS.set_prologue_end => {}, | ||
| 1602 | else => { | ||
| 1603 | if (opcode - 1 >= standard_opcode_lengths.len) return badDwarf(); | ||
| 1604 | try fbr.seekForward(standard_opcode_lengths[opcode - 1]); | ||
| 1605 | }, | ||
| 1606 | } | ||
| 1607 | } | ||
| 1608 | } | ||
| 1609 | |||
| 1610 | return missingDwarf(); | ||
| 1611 | } | ||
| 1612 | |||
| 1613 | fn getString(di: Dwarf, offset: u64) ![:0]const u8 { | ||
| 1614 | return getStringGeneric(di.section(.debug_str), offset); | ||
| 1615 | } | ||
| 1616 | |||
| 1617 | fn getLineString(di: Dwarf, offset: u64) ![:0]const u8 { | ||
| 1618 | return getStringGeneric(di.section(.debug_line_str), offset); | ||
| 1619 | } | ||
| 1620 | |||
| 1621 | fn readDebugAddr(di: Dwarf, compile_unit: CompileUnit, index: u64) !u64 { | ||
| 1622 | const debug_addr = di.section(.debug_addr) orelse return badDwarf(); | ||
| 1623 | |||
| 1624 | // addr_base points to the first item after the header, however we | ||
| 1625 | // need to read the header to know the size of each item. Empirically, | ||
| 1626 | // it may disagree with is_64 on the compile unit. | ||
| 1627 | // The header is 8 or 12 bytes depending on is_64. | ||
| 1628 | if (compile_unit.addr_base < 8) return badDwarf(); | ||
| 1629 | |||
| 1630 | const version = readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); | ||
| 1631 | if (version != 5) return badDwarf(); | ||
| 1632 | |||
| 1633 | const addr_size = debug_addr[compile_unit.addr_base - 2]; | ||
| 1634 | const seg_size = debug_addr[compile_unit.addr_base - 1]; | ||
| 1635 | |||
| 1636 | const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index)); | ||
| 1637 | if (byte_offset + addr_size > debug_addr.len) return badDwarf(); | ||
| 1638 | return switch (addr_size) { | ||
| 1639 | 1 => debug_addr[byte_offset], | ||
| 1640 | 2 => readInt(u16, debug_addr[byte_offset..][0..2], di.endian), | ||
| 1641 | 4 => readInt(u32, debug_addr[byte_offset..][0..4], di.endian), | ||
| 1642 | 8 => readInt(u64, debug_addr[byte_offset..][0..8], di.endian), | ||
| 1643 | else => badDwarf(), | ||
| 1644 | }; | ||
| 1645 | } | ||
| 1646 | |||
| 1647 | /// If .eh_frame_hdr is present, then only the header needs to be parsed. | ||
| 1648 | /// | ||
| 1649 | /// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list | ||
| 1650 | /// of FDEs is built for binary searching during unwinding. | ||
| 1651 | pub fn scanAllUnwindInfo(di: *Dwarf, allocator: Allocator, base_address: usize) !void { | ||
| 1652 | if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { | ||
| 1653 | var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; | ||
| 1654 | |||
| 1655 | const version = try fbr.readByte(); | ||
| 1656 | if (version != 1) break :blk; | ||
| 1657 | |||
| 1658 | const eh_frame_ptr_enc = try fbr.readByte(); | ||
| 1659 | if (eh_frame_ptr_enc == EH.PE.omit) break :blk; | ||
| 1660 | const fde_count_enc = try fbr.readByte(); | ||
| 1661 | if (fde_count_enc == EH.PE.omit) break :blk; | ||
| 1662 | const table_enc = try fbr.readByte(); | ||
| 1663 | if (table_enc == EH.PE.omit) break :blk; | ||
| 1664 | |||
| 1665 | const eh_frame_ptr = cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ | ||
| 1666 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), | ||
| 1667 | .follow_indirect = true, | ||
| 1668 | }) orelse return badDwarf()) orelse return badDwarf(); | ||
| 1669 | |||
| 1670 | const fde_count = cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ | ||
| 1671 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), | ||
| 1672 | .follow_indirect = true, | ||
| 1673 | }) orelse return badDwarf()) orelse return badDwarf(); | ||
| 1674 | |||
| 1675 | const entry_size = try ExceptionFrameHeader.entrySize(table_enc); | ||
| 1676 | const entries_len = fde_count * entry_size; | ||
| 1677 | if (entries_len > eh_frame_hdr.len - fbr.pos) return badDwarf(); | ||
| 1678 | |||
| 1679 | di.eh_frame_hdr = .{ | ||
| 1680 | .eh_frame_ptr = eh_frame_ptr, | ||
| 1681 | .table_enc = table_enc, | ||
| 1682 | .fde_count = fde_count, | ||
| 1683 | .entries = eh_frame_hdr[fbr.pos..][0..entries_len], | ||
| 1684 | }; | ||
| 1685 | |||
| 1686 | // No need to scan .eh_frame, we have a binary search table already | ||
| 1687 | return; | ||
| 1688 | } | ||
| 1689 | |||
| 1690 | const frame_sections = [2]Section.Id{ .eh_frame, .debug_frame }; | ||
| 1691 | for (frame_sections) |frame_section| { | ||
| 1692 | if (di.section(frame_section)) |section_data| { | ||
| 1693 | var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian }; | ||
| 1694 | while (fbr.pos < fbr.buf.len) { | ||
| 1695 | const entry_header = try EntryHeader.read(&fbr, null, frame_section); | ||
| 1696 | switch (entry_header.type) { | ||
| 1697 | .cie => { | ||
| 1698 | const cie = try CommonInformationEntry.parse( | ||
| 1699 | entry_header.entry_bytes, | ||
| 1700 | di.sectionVirtualOffset(frame_section, base_address).?, | ||
| 1701 | true, | ||
| 1702 | entry_header.format, | ||
| 1703 | frame_section, | ||
| 1704 | entry_header.length_offset, | ||
| 1705 | @sizeOf(usize), | ||
| 1706 | di.endian, | ||
| 1707 | ); | ||
| 1708 | try di.cie_map.put(allocator, entry_header.length_offset, cie); | ||
| 1709 | }, | ||
| 1710 | .fde => |cie_offset| { | ||
| 1711 | const cie = di.cie_map.get(cie_offset) orelse return badDwarf(); | ||
| 1712 | const fde = try FrameDescriptionEntry.parse( | ||
| 1713 | entry_header.entry_bytes, | ||
| 1714 | di.sectionVirtualOffset(frame_section, base_address).?, | ||
| 1715 | true, | ||
| 1716 | cie, | ||
| 1717 | @sizeOf(usize), | ||
| 1718 | di.endian, | ||
| 1719 | ); | ||
| 1720 | try di.fde_list.append(allocator, fde); | ||
| 1721 | }, | ||
| 1722 | .terminator => break, | ||
| 1723 | } | ||
| 1724 | } | ||
| 1725 | |||
| 1726 | std.mem.sortUnstable(FrameDescriptionEntry, di.fde_list.items, {}, struct { | ||
| 1727 | fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool { | ||
| 1728 | _ = ctx; | ||
| 1729 | return a.pc_begin < b.pc_begin; | ||
| 1730 | } | ||
| 1731 | }.lessThan); | ||
| 1732 | } | ||
| 1733 | } | ||
| 1734 | } | ||
| 1735 | |||
| 1736 | /// Unwind a stack frame using DWARF unwinding info, updating the register context. | ||
| 1737 | /// | ||
| 1738 | /// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE. | ||
| 1739 | /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. | ||
| 1740 | /// | ||
| 1741 | /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info | ||
| 1742 | /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. | ||
| 1743 | pub fn unwindFrame(di: *const Dwarf, context: *UnwindContext, ma: *StackIterator.MemoryAccessor, explicit_fde_offset: ?usize) !usize { | ||
| 1744 | if (!comptime abi.supportsUnwinding(builtin.target)) return error.UnsupportedCpuArchitecture; | ||
| 1745 | if (context.pc == 0) return 0; | ||
| 1746 | |||
| 1747 | // Find the FDE and CIE | ||
| 1748 | var cie: CommonInformationEntry = undefined; | ||
| 1749 | var fde: FrameDescriptionEntry = undefined; | ||
| 1750 | |||
| 1751 | if (explicit_fde_offset) |fde_offset| { | ||
| 1752 | const dwarf_section: Section.Id = .eh_frame; | ||
| 1753 | const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; | ||
| 1754 | if (fde_offset >= frame_section.len) return error.MissingFDE; | ||
| 1755 | |||
| 1756 | var fbr: FixedBufferReader = .{ | ||
| 1757 | .buf = frame_section, | ||
| 1758 | .pos = fde_offset, | ||
| 1759 | .endian = di.endian, | ||
| 1760 | }; | ||
| 1761 | |||
| 1762 | const fde_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); | ||
| 1763 | if (fde_entry_header.type != .fde) return error.MissingFDE; | ||
| 1764 | |||
| 1765 | const cie_offset = fde_entry_header.type.fde; | ||
| 1766 | try fbr.seekTo(cie_offset); | ||
| 1767 | |||
| 1768 | fbr.endian = native_endian; | ||
| 1769 | const cie_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); | ||
| 1770 | if (cie_entry_header.type != .cie) return badDwarf(); | ||
| 1771 | |||
| 1772 | cie = try CommonInformationEntry.parse( | ||
| 1773 | cie_entry_header.entry_bytes, | ||
| 1774 | 0, | ||
| 1775 | true, | ||
| 1776 | cie_entry_header.format, | ||
| 1777 | dwarf_section, | ||
| 1778 | cie_entry_header.length_offset, | ||
| 1779 | @sizeOf(usize), | ||
| 1780 | native_endian, | ||
| 1781 | ); | ||
| 1782 | |||
| 1783 | fde = try FrameDescriptionEntry.parse( | ||
| 1784 | fde_entry_header.entry_bytes, | ||
| 1785 | 0, | ||
| 1786 | true, | ||
| 1787 | cie, | ||
| 1788 | @sizeOf(usize), | ||
| 1789 | native_endian, | ||
| 1790 | ); | ||
| 1791 | } else if (di.eh_frame_hdr) |header| { | ||
| 1792 | const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; | ||
| 1793 | try header.findEntry( | ||
| 1794 | ma, | ||
| 1795 | eh_frame_len, | ||
| 1796 | @intFromPtr(di.section(.eh_frame_hdr).?.ptr), | ||
| 1797 | context.pc, | ||
| 1798 | &cie, | ||
| 1799 | &fde, | ||
| 1800 | ); | ||
| 1801 | } else { | ||
| 1802 | const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { | ||
| 1803 | pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) std.math.Order { | ||
| 1804 | if (pc < mid_item.pc_begin) return .lt; | ||
| 1805 | |||
| 1806 | const range_end = mid_item.pc_begin + mid_item.pc_range; | ||
| 1807 | if (pc < range_end) return .eq; | ||
| 1808 | |||
| 1809 | return .gt; | ||
| 1810 | } | ||
| 1811 | }.compareFn); | ||
| 1812 | |||
| 1813 | fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; | ||
| 1814 | cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; | ||
| 1815 | } | ||
| 1816 | |||
| 1817 | var expression_context: expression.Context = .{ | ||
| 1818 | .format = cie.format, | ||
| 1819 | .memory_accessor = ma, | ||
| 1820 | .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, | ||
| 1821 | .thread_context = context.thread_context, | ||
| 1822 | .reg_context = context.reg_context, | ||
| 1823 | .cfa = context.cfa, | ||
| 1824 | }; | ||
| 1825 | |||
| 1826 | context.vm.reset(); | ||
| 1827 | context.reg_context.eh_frame = cie.version != 4; | ||
| 1828 | context.reg_context.is_macho = di.is_macho; | ||
| 1829 | |||
| 1830 | const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); | ||
| 1831 | context.cfa = switch (row.cfa.rule) { | ||
| 1832 | .val_offset => |offset| blk: { | ||
| 1833 | const register = row.cfa.register orelse return error.InvalidCFARule; | ||
| 1834 | const value = readInt(usize, (try abi.regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); | ||
| 1835 | break :blk try call_frame.applyOffset(value, offset); | ||
| 1836 | }, | ||
| 1837 | .expression => |expr| blk: { | ||
| 1838 | context.stack_machine.reset(); | ||
| 1839 | const value = try context.stack_machine.run( | ||
| 1840 | expr, | ||
| 1841 | context.allocator, | ||
| 1842 | expression_context, | ||
| 1843 | context.cfa, | ||
| 1844 | ); | ||
| 1845 | |||
| 1846 | if (value) |v| { | ||
| 1847 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 1848 | break :blk v.generic; | ||
| 1849 | } else return error.NoExpressionValue; | ||
| 1850 | }, | ||
| 1851 | else => return error.InvalidCFARule, | ||
| 1852 | }; | ||
| 1853 | |||
| 1854 | if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA; | ||
| 1855 | expression_context.cfa = context.cfa; | ||
| 1856 | |||
| 1857 | // Buffering the modifications is done because copying the thread context is not portable, | ||
| 1858 | // some implementations (ie. darwin) use internal pointers to the mcontext. | ||
| 1859 | var arena = std.heap.ArenaAllocator.init(context.allocator); | ||
| 1860 | defer arena.deinit(); | ||
| 1861 | const update_allocator = arena.allocator(); | ||
| 1862 | |||
| 1863 | const RegisterUpdate = struct { | ||
| 1864 | // Backed by thread_context | ||
| 1865 | dest: []u8, | ||
| 1866 | // Backed by arena | ||
| 1867 | src: []const u8, | ||
| 1868 | prev: ?*@This(), | ||
| 1869 | }; | ||
| 1870 | |||
| 1871 | var update_tail: ?*RegisterUpdate = null; | ||
| 1872 | var has_return_address = true; | ||
| 1873 | for (context.vm.rowColumns(row)) |column| { | ||
| 1874 | if (column.register) |register| { | ||
| 1875 | if (register == cie.return_address_register) { | ||
| 1876 | has_return_address = column.rule != .undefined; | ||
| 1877 | } | ||
| 1878 | |||
| 1879 | const dest = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 1880 | const src = try update_allocator.alloc(u8, dest.len); | ||
| 1881 | |||
| 1882 | const prev = update_tail; | ||
| 1883 | update_tail = try update_allocator.create(RegisterUpdate); | ||
| 1884 | update_tail.?.* = .{ | ||
| 1885 | .dest = dest, | ||
| 1886 | .src = src, | ||
| 1887 | .prev = prev, | ||
| 1888 | }; | ||
| 1889 | |||
| 1890 | try column.resolveValue( | ||
| 1891 | context, | ||
| 1892 | expression_context, | ||
| 1893 | ma, | ||
| 1894 | src, | ||
| 1895 | ); | ||
| 1896 | } | ||
| 1897 | } | ||
| 1898 | |||
| 1899 | // On all implemented architectures, the CFA is defined as being the previous frame's SP | ||
| 1900 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; | ||
| 1901 | |||
| 1902 | while (update_tail) |tail| { | ||
| 1903 | @memcpy(tail.dest, tail.src); | ||
| 1904 | update_tail = tail.prev; | ||
| 1905 | } | ||
| 1906 | |||
| 1907 | if (has_return_address) { | ||
| 1908 | context.pc = abi.stripInstructionPtrAuthCode(readInt(usize, (try abi.regBytes( | ||
| 1909 | context.thread_context, | ||
| 1910 | cie.return_address_register, | ||
| 1911 | context.reg_context, | ||
| 1912 | ))[0..@sizeOf(usize)], native_endian)); | ||
| 1913 | } else { | ||
| 1914 | context.pc = 0; | ||
| 1915 | } | ||
| 1916 | |||
| 1917 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc; | ||
| 1918 | |||
| 1919 | // The call instruction will have pushed the address of the instruction that follows the call as the return address. | ||
| 1920 | // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in | ||
| 1921 | // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up | ||
| 1922 | // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, | ||
| 1923 | // we subtract one so that the next lookup is guaranteed to land inside the | ||
| 1924 | // | ||
| 1925 | // The exception to this rule is signal frames, where we return execution would be returned to the instruction | ||
| 1926 | // that triggered the handler. | ||
| 1927 | const return_address = context.pc; | ||
| 1928 | if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; | ||
| 1929 | |||
| 1930 | return return_address; | ||
| 1931 | } | ||
| 1932 | |||
| 1933 | fn parseFormValue( | ||
| 1934 | fbr: *FixedBufferReader, | ||
| 1935 | form_id: u64, | ||
| 1936 | format: Format, | ||
| 1937 | implicit_const: ?i64, | ||
| 1938 | ) anyerror!FormValue { | ||
| 1939 | return switch (form_id) { | ||
| 1940 | FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) { | ||
| 1941 | 32 => .@"32", | ||
| 1942 | 64 => .@"64", | ||
| 1943 | else => @compileError("unsupported @sizeOf(usize)"), | ||
| 1944 | }) }, | ||
| 1945 | FORM.addrx1 => .{ .addrx = try fbr.readInt(u8) }, | ||
| 1946 | FORM.addrx2 => .{ .addrx = try fbr.readInt(u16) }, | ||
| 1947 | FORM.addrx3 => .{ .addrx = try fbr.readInt(u24) }, | ||
| 1948 | FORM.addrx4 => .{ .addrx = try fbr.readInt(u32) }, | ||
| 1949 | FORM.addrx => .{ .addrx = try fbr.readUleb128(usize) }, | ||
| 1950 | |||
| 1951 | FORM.block1, | ||
| 1952 | FORM.block2, | ||
| 1953 | FORM.block4, | ||
| 1954 | FORM.block, | ||
| 1955 | => .{ .block = try fbr.readBytes(switch (form_id) { | ||
| 1956 | FORM.block1 => try fbr.readInt(u8), | ||
| 1957 | FORM.block2 => try fbr.readInt(u16), | ||
| 1958 | FORM.block4 => try fbr.readInt(u32), | ||
| 1959 | FORM.block => try fbr.readUleb128(usize), | ||
| 1960 | else => unreachable, | ||
| 1961 | }) }, | ||
| 1962 | |||
| 1963 | FORM.data1 => .{ .udata = try fbr.readInt(u8) }, | ||
| 1964 | FORM.data2 => .{ .udata = try fbr.readInt(u16) }, | ||
| 1965 | FORM.data4 => .{ .udata = try fbr.readInt(u32) }, | ||
| 1966 | FORM.data8 => .{ .udata = try fbr.readInt(u64) }, | ||
| 1967 | FORM.data16 => .{ .data16 = (try fbr.readBytes(16))[0..16] }, | ||
| 1968 | FORM.udata => .{ .udata = try fbr.readUleb128(u64) }, | ||
| 1969 | FORM.sdata => .{ .sdata = try fbr.readIleb128(i64) }, | ||
| 1970 | FORM.exprloc => .{ .exprloc = try fbr.readBytes(try fbr.readUleb128(usize)) }, | ||
| 1971 | FORM.flag => .{ .flag = (try fbr.readByte()) != 0 }, | ||
| 1972 | FORM.flag_present => .{ .flag = true }, | ||
| 1973 | FORM.sec_offset => .{ .sec_offset = try fbr.readAddress(format) }, | ||
| 1974 | |||
| 1975 | FORM.ref1 => .{ .ref = try fbr.readInt(u8) }, | ||
| 1976 | FORM.ref2 => .{ .ref = try fbr.readInt(u16) }, | ||
| 1977 | FORM.ref4 => .{ .ref = try fbr.readInt(u32) }, | ||
| 1978 | FORM.ref8 => .{ .ref = try fbr.readInt(u64) }, | ||
| 1979 | FORM.ref_udata => .{ .ref = try fbr.readUleb128(u64) }, | ||
| 1980 | |||
| 1981 | FORM.ref_addr => .{ .ref_addr = try fbr.readAddress(format) }, | ||
| 1982 | FORM.ref_sig8 => .{ .ref = try fbr.readInt(u64) }, | ||
| 1983 | |||
| 1984 | FORM.string => .{ .string = try fbr.readBytesTo(0) }, | ||
| 1985 | FORM.strp => .{ .strp = try fbr.readAddress(format) }, | ||
| 1986 | FORM.strx1 => .{ .strx = try fbr.readInt(u8) }, | ||
| 1987 | FORM.strx2 => .{ .strx = try fbr.readInt(u16) }, | ||
| 1988 | FORM.strx3 => .{ .strx = try fbr.readInt(u24) }, | ||
| 1989 | FORM.strx4 => .{ .strx = try fbr.readInt(u32) }, | ||
| 1990 | FORM.strx => .{ .strx = try fbr.readUleb128(usize) }, | ||
| 1991 | FORM.line_strp => .{ .line_strp = try fbr.readAddress(format) }, | ||
| 1992 | FORM.indirect => parseFormValue(fbr, try fbr.readUleb128(u64), format, implicit_const), | ||
| 1993 | FORM.implicit_const => .{ .sdata = implicit_const orelse return badDwarf() }, | ||
| 1994 | FORM.loclistx => .{ .loclistx = try fbr.readUleb128(u64) }, | ||
| 1995 | FORM.rnglistx => .{ .rnglistx = try fbr.readUleb128(u64) }, | ||
| 1996 | else => { | ||
| 1997 | //debug.print("unrecognized form id: {x}\n", .{form_id}); | ||
| 1998 | return badDwarf(); | ||
| 1999 | }, | ||
| 2000 | }; | ||
| 2001 | } | ||
| 2002 | |||
| 2003 | const FileEntry = struct { | ||
| 2004 | path: []const u8, | ||
| 2005 | dir_index: u32 = 0, | ||
| 2006 | mtime: u64 = 0, | ||
| 2007 | size: u64 = 0, | ||
| 2008 | md5: [16]u8 = [1]u8{0} ** 16, | ||
| 2009 | }; | ||
| 2010 | |||
| 2011 | const LineNumberProgram = struct { | ||
| 2012 | address: u64, | ||
| 2013 | file: usize, | ||
| 2014 | line: i64, | ||
| 2015 | column: u64, | ||
| 2016 | version: u16, | ||
| 2017 | is_stmt: bool, | ||
| 2018 | basic_block: bool, | ||
| 2019 | end_sequence: bool, | ||
| 2020 | |||
| 2021 | default_is_stmt: bool, | ||
| 2022 | target_address: u64, | ||
| 2023 | include_dirs: []const FileEntry, | ||
| 2024 | |||
| 2025 | prev_valid: bool, | ||
| 2026 | prev_address: u64, | ||
| 2027 | prev_file: usize, | ||
| 2028 | prev_line: i64, | ||
| 2029 | prev_column: u64, | ||
| 2030 | prev_is_stmt: bool, | ||
| 2031 | prev_basic_block: bool, | ||
| 2032 | prev_end_sequence: bool, | ||
| 2033 | |||
| 2034 | // Reset the state machine following the DWARF specification | ||
| 2035 | pub fn reset(self: *LineNumberProgram) void { | ||
| 2036 | self.address = 0; | ||
| 2037 | self.file = 1; | ||
| 2038 | self.line = 1; | ||
| 2039 | self.column = 0; | ||
| 2040 | self.is_stmt = self.default_is_stmt; | ||
| 2041 | self.basic_block = false; | ||
| 2042 | self.end_sequence = false; | ||
| 2043 | // Invalidate all the remaining fields | ||
| 2044 | self.prev_valid = false; | ||
| 2045 | self.prev_address = 0; | ||
| 2046 | self.prev_file = undefined; | ||
| 2047 | self.prev_line = undefined; | ||
| 2048 | self.prev_column = undefined; | ||
| 2049 | self.prev_is_stmt = undefined; | ||
| 2050 | self.prev_basic_block = undefined; | ||
| 2051 | self.prev_end_sequence = undefined; | ||
| 2052 | } | ||
| 2053 | |||
| 2054 | pub fn init( | ||
| 2055 | is_stmt: bool, | ||
| 2056 | include_dirs: []const FileEntry, | ||
| 2057 | target_address: u64, | ||
| 2058 | version: u16, | ||
| 2059 | ) LineNumberProgram { | ||
| 2060 | return LineNumberProgram{ | ||
| 2061 | .address = 0, | ||
| 2062 | .file = 1, | ||
| 2063 | .line = 1, | ||
| 2064 | .column = 0, | ||
| 2065 | .version = version, | ||
| 2066 | .is_stmt = is_stmt, | ||
| 2067 | .basic_block = false, | ||
| 2068 | .end_sequence = false, | ||
| 2069 | .include_dirs = include_dirs, | ||
| 2070 | .default_is_stmt = is_stmt, | ||
| 2071 | .target_address = target_address, | ||
| 2072 | .prev_valid = false, | ||
| 2073 | .prev_address = 0, | ||
| 2074 | .prev_file = undefined, | ||
| 2075 | .prev_line = undefined, | ||
| 2076 | .prev_column = undefined, | ||
| 2077 | .prev_is_stmt = undefined, | ||
| 2078 | .prev_basic_block = undefined, | ||
| 2079 | .prev_end_sequence = undefined, | ||
| 2080 | }; | ||
| 2081 | } | ||
| 2082 | |||
| 2083 | pub fn checkLineMatch( | ||
| 2084 | self: *LineNumberProgram, | ||
| 2085 | allocator: Allocator, | ||
| 2086 | file_entries: []const FileEntry, | ||
| 2087 | ) !?std.debug.LineInfo { | ||
| 2088 | if (self.prev_valid and | ||
| 2089 | self.target_address >= self.prev_address and | ||
| 2090 | self.target_address < self.address) | ||
| 2091 | { | ||
| 2092 | const file_index = if (self.version >= 5) self.prev_file else i: { | ||
| 2093 | if (self.prev_file == 0) return missingDwarf(); | ||
| 2094 | break :i self.prev_file - 1; | ||
| 2095 | }; | ||
| 2096 | |||
| 2097 | if (file_index >= file_entries.len) return badDwarf(); | ||
| 2098 | const file_entry = &file_entries[file_index]; | ||
| 2099 | |||
| 2100 | if (file_entry.dir_index >= self.include_dirs.len) return badDwarf(); | ||
| 2101 | const dir_name = self.include_dirs[file_entry.dir_index].path; | ||
| 2102 | |||
| 2103 | const file_name = try std.fs.path.join(allocator, &[_][]const u8{ | ||
| 2104 | dir_name, file_entry.path, | ||
| 2105 | }); | ||
| 2106 | |||
| 2107 | return std.debug.LineInfo{ | ||
| 2108 | .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, | ||
| 2109 | .column = self.prev_column, | ||
| 2110 | .file_name = file_name, | ||
| 2111 | }; | ||
| 2112 | } | ||
| 2113 | |||
| 2114 | self.prev_valid = true; | ||
| 2115 | self.prev_address = self.address; | ||
| 2116 | self.prev_file = self.file; | ||
| 2117 | self.prev_line = self.line; | ||
| 2118 | self.prev_column = self.column; | ||
| 2119 | self.prev_is_stmt = self.is_stmt; | ||
| 2120 | self.prev_basic_block = self.basic_block; | ||
| 2121 | self.prev_end_sequence = self.end_sequence; | ||
| 2122 | return null; | ||
| 2123 | } | ||
| 2124 | }; | ||
| 2125 | |||
| 2126 | const UnitHeader = struct { | ||
| 2127 | format: Format, | ||
| 2128 | header_length: u4, | ||
| 2129 | unit_length: u64, | ||
| 2130 | }; | ||
| 2131 | fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*StackIterator.MemoryAccessor) !UnitHeader { | ||
| 2132 | return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { | ||
| 2133 | 0...0xfffffff0 - 1 => |unit_length| .{ | ||
| 2134 | .format = .@"32", | ||
| 2135 | .header_length = 4, | ||
| 2136 | .unit_length = unit_length, | ||
| 2137 | }, | ||
| 2138 | 0xfffffff0...0xffffffff - 1 => badDwarf(), | ||
| 2139 | 0xffffffff => .{ | ||
| 2140 | .format = .@"64", | ||
| 2141 | .header_length = 12, | ||
| 2142 | .unit_length = try if (opt_ma) |ma| fbr.readIntChecked(u64, ma) else fbr.readInt(u64), | ||
| 2143 | }, | ||
| 2144 | }; | ||
| 2145 | } | ||
| 2146 | |||
| 2147 | /// Returns the DWARF register number for an x86_64 register number found in compact unwind info | ||
| 2148 | fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { | ||
| 2149 | return switch (unwind_reg_number) { | ||
| 2150 | 1 => 3, // RBX | ||
| 2151 | 2 => 12, // R12 | ||
| 2152 | 3 => 13, // R13 | ||
| 2153 | 4 => 14, // R14 | ||
| 2154 | 5 => 15, // R15 | ||
| 2155 | 6 => 6, // RBP | ||
| 2156 | else => error.InvalidUnwindRegisterNumber, | ||
| 2157 | }; | ||
| 2158 | } | ||
| 2159 | |||
| 2160 | /// This function is to make it handy to comment out the return and make it | ||
| 2161 | /// into a crash when working on this file. | ||
| 2162 | fn badDwarf() error{InvalidDebugInfo} { | ||
| 2163 | //if (true) @panic("badDwarf"); // can be handy to uncomment when working on this file | ||
| 2164 | return error.InvalidDebugInfo; | ||
| 2165 | } | ||
| 2166 | |||
| 2167 | fn missingDwarf() error{MissingDebugInfo} { | ||
| 2168 | //if (true) @panic("missingDwarf"); // can be handy to uncomment when working on this file | ||
| 2169 | return error.MissingDebugInfo; | ||
| 2170 | } | ||
| 2171 | |||
| 2172 | fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { | ||
| 2173 | const str = opt_str orelse return badDwarf(); | ||
| 2174 | if (offset > str.len) return badDwarf(); | ||
| 2175 | const casted_offset = cast(usize, offset) orelse return badDwarf(); | ||
| 2176 | // Valid strings always have a terminating zero byte | ||
| 2177 | const last = std.mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf(); | ||
| 2178 | return str[casted_offset..last :0]; | ||
| 2179 | } | ||
| 2180 | |||
| 2181 | // Reading debug info needs to be fast, even when compiled in debug mode, | ||
| 2182 | // so avoid using a `std.io.FixedBufferStream` which is too slow. | ||
| 2183 | pub const FixedBufferReader = struct { | ||
| 2184 | buf: []const u8, | ||
| 2185 | pos: usize = 0, | ||
| 2186 | endian: std.builtin.Endian, | ||
| 2187 | |||
| 2188 | pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; | ||
| 2189 | |||
| 2190 | fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void { | ||
| 2191 | if (pos > fbr.buf.len) return error.EndOfBuffer; | ||
| 2192 | fbr.pos = @intCast(pos); | ||
| 2193 | } | ||
| 2194 | |||
| 2195 | fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void { | ||
| 2196 | if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; | ||
| 2197 | fbr.pos += @intCast(amount); | ||
| 2198 | } | ||
| 2199 | |||
| 2200 | pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 { | ||
| 2201 | if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; | ||
| 2202 | defer fbr.pos += 1; | ||
| 2203 | return fbr.buf[fbr.pos]; | ||
| 2204 | } | ||
| 2205 | |||
| 2206 | fn readByteSigned(fbr: *FixedBufferReader) Error!i8 { | ||
| 2207 | return @bitCast(try fbr.readByte()); | ||
| 2208 | } | ||
| 2209 | |||
| 2210 | fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T { | ||
| 2211 | const size = @divExact(@typeInfo(T).Int.bits, 8); | ||
| 2212 | if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; | ||
| 2213 | defer fbr.pos += size; | ||
| 2214 | return std.mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); | ||
| 2215 | } | ||
| 2216 | |||
| 2217 | fn readIntChecked( | ||
| 2218 | fbr: *FixedBufferReader, | ||
| 2219 | comptime T: type, | ||
| 2220 | ma: *std.debug.StackIterator.MemoryAccessor, | ||
| 2221 | ) Error!T { | ||
| 2222 | if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) | ||
| 2223 | return error.InvalidBuffer; | ||
| 2224 | |||
| 2225 | return fbr.readInt(T); | ||
| 2226 | } | ||
| 2227 | |||
| 2228 | fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { | ||
| 2229 | return std.leb.readUleb128(T, fbr); | ||
| 2230 | } | ||
| 2231 | |||
| 2232 | fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { | ||
| 2233 | return std.leb.readIleb128(T, fbr); | ||
| 2234 | } | ||
| 2235 | |||
| 2236 | fn readAddress(fbr: *FixedBufferReader, format: Format) Error!u64 { | ||
| 2237 | return switch (format) { | ||
| 2238 | .@"32" => try fbr.readInt(u32), | ||
| 2239 | .@"64" => try fbr.readInt(u64), | ||
| 2240 | }; | ||
| 2241 | } | ||
| 2242 | |||
| 2243 | fn readAddressChecked( | ||
| 2244 | fbr: *FixedBufferReader, | ||
| 2245 | format: Format, | ||
| 2246 | ma: *std.debug.StackIterator.MemoryAccessor, | ||
| 2247 | ) Error!u64 { | ||
| 2248 | return switch (format) { | ||
| 2249 | .@"32" => try fbr.readIntChecked(u32, ma), | ||
| 2250 | .@"64" => try fbr.readIntChecked(u64, ma), | ||
| 2251 | }; | ||
| 2252 | } | ||
| 2253 | |||
| 2254 | fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 { | ||
| 2255 | if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; | ||
| 2256 | defer fbr.pos += len; | ||
| 2257 | return fbr.buf[fbr.pos..][0..len]; | ||
| 2258 | } | ||
| 2259 | |||
| 2260 | fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { | ||
| 2261 | const end = @call(.always_inline, std.mem.indexOfScalarPos, .{ | ||
| 2262 | u8, | ||
| 2263 | fbr.buf, | ||
| 2264 | fbr.pos, | ||
| 2265 | sentinel, | ||
| 2266 | }) orelse return error.EndOfBuffer; | ||
| 2267 | defer fbr.pos = end + 1; | ||
| 2268 | return fbr.buf[fbr.pos..end :sentinel]; | ||
| 2269 | } | ||
| 2270 | }; | ||
| 2271 | |||
| 2272 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). | ||
| 2273 | /// If the compact encoding can't encode a way to unwind a frame, it will | ||
| 2274 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. | ||
| 2275 | pub fn unwindFrameMachO( | ||
| 2276 | context: *UnwindContext, | ||
| 2277 | ma: *StackIterator.MemoryAccessor, | ||
| 2278 | unwind_info: []const u8, | ||
| 2279 | eh_frame: ?[]const u8, | ||
| 2280 | module_base_address: usize, | ||
| 2281 | ) !usize { | ||
| 2282 | const macho = std.macho; | ||
| 2283 | |||
| 2284 | const header = std.mem.bytesAsValue( | ||
| 2285 | macho.unwind_info_section_header, | ||
| 2286 | unwind_info[0..@sizeOf(macho.unwind_info_section_header)], | ||
| 2287 | ); | ||
| 2288 | const indices = std.mem.bytesAsSlice( | ||
| 2289 | macho.unwind_info_section_header_index_entry, | ||
| 2290 | unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], | ||
| 2291 | ); | ||
| 2292 | if (indices.len == 0) return error.MissingUnwindInfo; | ||
| 2293 | |||
| 2294 | const mapped_pc = context.pc - module_base_address; | ||
| 2295 | const second_level_index = blk: { | ||
| 2296 | var left: usize = 0; | ||
| 2297 | var len: usize = indices.len; | ||
| 2298 | |||
| 2299 | while (len > 1) { | ||
| 2300 | const mid = left + len / 2; | ||
| 2301 | const offset = indices[mid].functionOffset; | ||
| 2302 | if (mapped_pc < offset) { | ||
| 2303 | len /= 2; | ||
| 2304 | } else { | ||
| 2305 | left = mid; | ||
| 2306 | if (mapped_pc == offset) break; | ||
| 2307 | len -= len / 2; | ||
| 2308 | } | ||
| 2309 | } | ||
| 2310 | |||
| 2311 | // Last index is a sentinel containing the highest address as its functionOffset | ||
| 2312 | if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; | ||
| 2313 | break :blk &indices[left]; | ||
| 2314 | }; | ||
| 2315 | |||
| 2316 | const common_encodings = std.mem.bytesAsSlice( | ||
| 2317 | macho.compact_unwind_encoding_t, | ||
| 2318 | unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 2319 | ); | ||
| 2320 | |||
| 2321 | const start_offset = second_level_index.secondLevelPagesSectionOffset; | ||
| 2322 | const kind = std.mem.bytesAsValue( | ||
| 2323 | macho.UNWIND_SECOND_LEVEL, | ||
| 2324 | unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], | ||
| 2325 | ); | ||
| 2326 | |||
| 2327 | const entry: struct { | ||
| 2328 | function_offset: usize, | ||
| 2329 | raw_encoding: u32, | ||
| 2330 | } = switch (kind.*) { | ||
| 2331 | .REGULAR => blk: { | ||
| 2332 | const page_header = std.mem.bytesAsValue( | ||
| 2333 | macho.unwind_info_regular_second_level_page_header, | ||
| 2334 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], | ||
| 2335 | ); | ||
| 2336 | |||
| 2337 | const entries = std.mem.bytesAsSlice( | ||
| 2338 | macho.unwind_info_regular_second_level_entry, | ||
| 2339 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], | ||
| 2340 | ); | ||
| 2341 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 2342 | |||
| 2343 | var left: usize = 0; | ||
| 2344 | var len: usize = entries.len; | ||
| 2345 | while (len > 1) { | ||
| 2346 | const mid = left + len / 2; | ||
| 2347 | const offset = entries[mid].functionOffset; | ||
| 2348 | if (mapped_pc < offset) { | ||
| 2349 | len /= 2; | ||
| 2350 | } else { | ||
| 2351 | left = mid; | ||
| 2352 | if (mapped_pc == offset) break; | ||
| 2353 | len -= len / 2; | ||
| 2354 | } | ||
| 2355 | } | ||
| 2356 | |||
| 2357 | break :blk .{ | ||
| 2358 | .function_offset = entries[left].functionOffset, | ||
| 2359 | .raw_encoding = entries[left].encoding, | ||
| 2360 | }; | ||
| 2361 | }, | ||
| 2362 | .COMPRESSED => blk: { | ||
| 2363 | const page_header = std.mem.bytesAsValue( | ||
| 2364 | macho.unwind_info_compressed_second_level_page_header, | ||
| 2365 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], | ||
| 2366 | ); | ||
| 2367 | |||
| 2368 | const entries = std.mem.bytesAsSlice( | ||
| 2369 | macho.UnwindInfoCompressedEntry, | ||
| 2370 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], | ||
| 2371 | ); | ||
| 2372 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 2373 | |||
| 2374 | var left: usize = 0; | ||
| 2375 | var len: usize = entries.len; | ||
| 2376 | while (len > 1) { | ||
| 2377 | const mid = left + len / 2; | ||
| 2378 | const offset = second_level_index.functionOffset + entries[mid].funcOffset; | ||
| 2379 | if (mapped_pc < offset) { | ||
| 2380 | len /= 2; | ||
| 2381 | } else { | ||
| 2382 | left = mid; | ||
| 2383 | if (mapped_pc == offset) break; | ||
| 2384 | len -= len / 2; | ||
| 2385 | } | ||
| 2386 | } | ||
| 2387 | |||
| 2388 | const entry = entries[left]; | ||
| 2389 | const function_offset = second_level_index.functionOffset + entry.funcOffset; | ||
| 2390 | if (entry.encodingIndex < header.commonEncodingsArrayCount) { | ||
| 2391 | if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; | ||
| 2392 | break :blk .{ | ||
| 2393 | .function_offset = function_offset, | ||
| 2394 | .raw_encoding = common_encodings[entry.encodingIndex], | ||
| 2395 | }; | ||
| 2396 | } else { | ||
| 2397 | const local_index = try std.math.sub( | ||
| 2398 | u8, | ||
| 2399 | entry.encodingIndex, | ||
| 2400 | cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, | ||
| 2401 | ); | ||
| 2402 | const local_encodings = std.mem.bytesAsSlice( | ||
| 2403 | macho.compact_unwind_encoding_t, | ||
| 2404 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 2405 | ); | ||
| 2406 | if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; | ||
| 2407 | break :blk .{ | ||
| 2408 | .function_offset = function_offset, | ||
| 2409 | .raw_encoding = local_encodings[local_index], | ||
| 2410 | }; | ||
| 2411 | } | ||
| 2412 | }, | ||
| 2413 | else => return error.InvalidUnwindInfo, | ||
| 2414 | }; | ||
| 2415 | |||
| 2416 | if (entry.raw_encoding == 0) return error.NoUnwindInfo; | ||
| 2417 | const reg_context = abi.RegisterContext{ | ||
| 2418 | .eh_frame = false, | ||
| 2419 | .is_macho = true, | ||
| 2420 | }; | ||
| 2421 | |||
| 2422 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | ||
| 2423 | const new_ip = switch (builtin.cpu.arch) { | ||
| 2424 | .x86_64 => switch (encoding.mode.x86_64) { | ||
| 2425 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 2426 | .RBP_FRAME => blk: { | ||
| 2427 | const regs: [5]u3 = .{ | ||
| 2428 | encoding.value.x86_64.frame.reg0, | ||
| 2429 | encoding.value.x86_64.frame.reg1, | ||
| 2430 | encoding.value.x86_64.frame.reg2, | ||
| 2431 | encoding.value.x86_64.frame.reg3, | ||
| 2432 | encoding.value.x86_64.frame.reg4, | ||
| 2433 | }; | ||
| 2434 | |||
| 2435 | const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); | ||
| 2436 | var max_reg: usize = 0; | ||
| 2437 | inline for (regs, 0..) |reg, i| { | ||
| 2438 | if (reg > 0) max_reg = i; | ||
| 2439 | } | ||
| 2440 | |||
| 2441 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | ||
| 2442 | const new_sp = fp + 2 * @sizeOf(usize); | ||
| 2443 | |||
| 2444 | // Verify the stack range we're about to read register values from | ||
| 2445 | if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo; | ||
| 2446 | |||
| 2447 | const ip_ptr = fp + @sizeOf(usize); | ||
| 2448 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2449 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 2450 | |||
| 2451 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 2452 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2453 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2454 | |||
| 2455 | for (regs, 0..) |reg, i| { | ||
| 2456 | if (reg == 0) continue; | ||
| 2457 | const addr = fp - frame_offset + i * @sizeOf(usize); | ||
| 2458 | const reg_number = try compactUnwindToDwarfRegNumber(reg); | ||
| 2459 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; | ||
| 2460 | } | ||
| 2461 | |||
| 2462 | break :blk new_ip; | ||
| 2463 | }, | ||
| 2464 | .STACK_IMMD, | ||
| 2465 | .STACK_IND, | ||
| 2466 | => blk: { | ||
| 2467 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | ||
| 2468 | const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) | ||
| 2469 | @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) | ||
| 2470 | else stack_size: { | ||
| 2471 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | ||
| 2472 | const sub_offset_addr = | ||
| 2473 | module_base_address + | ||
| 2474 | entry.function_offset + | ||
| 2475 | encoding.value.x86_64.frameless.stack.indirect.sub_offset; | ||
| 2476 | if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo; | ||
| 2477 | |||
| 2478 | // `sub_offset_addr` points to the offset of the literal within the instruction | ||
| 2479 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | ||
| 2480 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); | ||
| 2481 | }; | ||
| 2482 | |||
| 2483 | // Decode the Lehmer-coded sequence of registers. | ||
| 2484 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | ||
| 2485 | |||
| 2486 | // Decode the variable-based permutation number into its digits. Each digit represents | ||
| 2487 | // an index into the list of register numbers that weren't yet used in the sequence at | ||
| 2488 | // the time the digit was added. | ||
| 2489 | const reg_count = encoding.value.x86_64.frameless.stack_reg_count; | ||
| 2490 | const ip_ptr = if (reg_count > 0) reg_blk: { | ||
| 2491 | var digits: [6]u3 = undefined; | ||
| 2492 | var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; | ||
| 2493 | var base: usize = 2; | ||
| 2494 | for (0..reg_count) |i| { | ||
| 2495 | const div = accumulator / base; | ||
| 2496 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | ||
| 2497 | accumulator = div; | ||
| 2498 | base += 1; | ||
| 2499 | } | ||
| 2500 | |||
| 2501 | const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; | ||
| 2502 | var registers: [reg_numbers.len]u3 = undefined; | ||
| 2503 | var used_indices = [_]bool{false} ** reg_numbers.len; | ||
| 2504 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | ||
| 2505 | var unused_count: u8 = 0; | ||
| 2506 | const unused_index = for (used_indices, 0..) |used, index| { | ||
| 2507 | if (!used) { | ||
| 2508 | if (target_unused_index == unused_count) break index; | ||
| 2509 | unused_count += 1; | ||
| 2510 | } | ||
| 2511 | } else unreachable; | ||
| 2512 | |||
| 2513 | registers[i] = reg_numbers[unused_index]; | ||
| 2514 | used_indices[unused_index] = true; | ||
| 2515 | } | ||
| 2516 | |||
| 2517 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | ||
| 2518 | if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo; | ||
| 2519 | for (0..reg_count) |i| { | ||
| 2520 | const reg_number = try compactUnwindToDwarfRegNumber(registers[i]); | ||
| 2521 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2522 | reg_addr += @sizeOf(usize); | ||
| 2523 | } | ||
| 2524 | |||
| 2525 | break :reg_blk reg_addr; | ||
| 2526 | } else sp + stack_size - @sizeOf(usize); | ||
| 2527 | |||
| 2528 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2529 | const new_sp = ip_ptr + @sizeOf(usize); | ||
| 2530 | if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; | ||
| 2531 | |||
| 2532 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2533 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2534 | |||
| 2535 | break :blk new_ip; | ||
| 2536 | }, | ||
| 2537 | .DWARF => { | ||
| 2538 | return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); | ||
| 2539 | }, | ||
| 2540 | }, | ||
| 2541 | .aarch64 => switch (encoding.mode.arm64) { | ||
| 2542 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 2543 | .FRAMELESS => blk: { | ||
| 2544 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | ||
| 2545 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | ||
| 2546 | const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*; | ||
| 2547 | if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; | ||
| 2548 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2549 | break :blk new_ip; | ||
| 2550 | }, | ||
| 2551 | .DWARF => { | ||
| 2552 | return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); | ||
| 2553 | }, | ||
| 2554 | .FRAME => blk: { | ||
| 2555 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | ||
| 2556 | const new_sp = fp + 16; | ||
| 2557 | const ip_ptr = fp + @sizeOf(usize); | ||
| 2558 | |||
| 2559 | const num_restored_pairs: usize = | ||
| 2560 | @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + | ||
| 2561 | @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); | ||
| 2562 | const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); | ||
| 2563 | |||
| 2564 | if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo; | ||
| 2565 | |||
| 2566 | var reg_addr = fp - @sizeOf(usize); | ||
| 2567 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 2568 | if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { | ||
| 2569 | (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2570 | reg_addr += @sizeOf(usize); | ||
| 2571 | (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2572 | reg_addr += @sizeOf(usize); | ||
| 2573 | } | ||
| 2574 | } | ||
| 2575 | |||
| 2576 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 2577 | if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { | ||
| 2578 | // Only the lower half of the 128-bit V registers are restored during unwinding | ||
| 2579 | @memcpy( | ||
| 2580 | try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context), | ||
| 2581 | std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 2582 | ); | ||
| 2583 | reg_addr += @sizeOf(usize); | ||
| 2584 | @memcpy( | ||
| 2585 | try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context), | ||
| 2586 | std.mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 2587 | ); | ||
| 2588 | reg_addr += @sizeOf(usize); | ||
| 2589 | } | ||
| 2590 | } | ||
| 2591 | |||
| 2592 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2593 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 2594 | |||
| 2595 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 2596 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2597 | |||
| 2598 | break :blk new_ip; | ||
| 2599 | }, | ||
| 2600 | }, | ||
| 2601 | else => return error.UnimplementedArch, | ||
| 2602 | }; | ||
| 2603 | |||
| 2604 | context.pc = abi.stripInstructionPtrAuthCode(new_ip); | ||
| 2605 | if (context.pc > 0) context.pc -= 1; | ||
| 2606 | return new_ip; | ||
| 2607 | } | ||
| 2608 | |||
| 2609 | fn unwindFrameMachODwarf( | ||
| 2610 | context: *UnwindContext, | ||
| 2611 | ma: *std.debug.StackIterator.MemoryAccessor, | ||
| 2612 | eh_frame: []const u8, | ||
| 2613 | fde_offset: usize, | ||
| 2614 | ) !usize { | ||
| 2615 | var di = Dwarf{ | ||
| 2616 | .endian = native_endian, | ||
| 2617 | .is_macho = true, | ||
| 2618 | }; | ||
| 2619 | defer di.deinit(context.allocator); | ||
| 2620 | |||
| 2621 | di.sections[@intFromEnum(Section.Id.eh_frame)] = .{ | ||
| 2622 | .data = eh_frame, | ||
| 2623 | .owned = false, | ||
| 2624 | }; | ||
| 2625 | |||
| 2626 | return di.unwindFrame(context, ma, fde_offset); | ||
| 2627 | } | ||
| 2628 | |||
| 2629 | const EhPointerContext = struct { | ||
| 2630 | // The address of the pointer field itself | ||
| 2631 | pc_rel_base: u64, | ||
| 2632 | |||
| 2633 | // Whether or not to follow indirect pointers. This should only be | ||
| 2634 | // used when decoding pointers at runtime using the current process's | ||
| 2635 | // debug info | ||
| 2636 | follow_indirect: bool, | ||
| 2637 | |||
| 2638 | // These relative addressing modes are only used in specific cases, and | ||
| 2639 | // might not be available / required in all parsing contexts | ||
| 2640 | data_rel_base: ?u64 = null, | ||
| 2641 | text_rel_base: ?u64 = null, | ||
| 2642 | function_rel_base: ?u64 = null, | ||
| 2643 | }; | ||
| 2644 | fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { | ||
| 2645 | if (enc == EH.PE.omit) return null; | ||
| 2646 | |||
| 2647 | const value: union(enum) { | ||
| 2648 | signed: i64, | ||
| 2649 | unsigned: u64, | ||
| 2650 | } = switch (enc & EH.PE.type_mask) { | ||
| 2651 | EH.PE.absptr => .{ | ||
| 2652 | .unsigned = switch (addr_size_bytes) { | ||
| 2653 | 2 => try fbr.readInt(u16), | ||
| 2654 | 4 => try fbr.readInt(u32), | ||
| 2655 | 8 => try fbr.readInt(u64), | ||
| 2656 | else => return error.InvalidAddrSize, | ||
| 2657 | }, | ||
| 2658 | }, | ||
| 2659 | EH.PE.uleb128 => .{ .unsigned = try fbr.readUleb128(u64) }, | ||
| 2660 | EH.PE.udata2 => .{ .unsigned = try fbr.readInt(u16) }, | ||
| 2661 | EH.PE.udata4 => .{ .unsigned = try fbr.readInt(u32) }, | ||
| 2662 | EH.PE.udata8 => .{ .unsigned = try fbr.readInt(u64) }, | ||
| 2663 | EH.PE.sleb128 => .{ .signed = try fbr.readIleb128(i64) }, | ||
| 2664 | EH.PE.sdata2 => .{ .signed = try fbr.readInt(i16) }, | ||
| 2665 | EH.PE.sdata4 => .{ .signed = try fbr.readInt(i32) }, | ||
| 2666 | EH.PE.sdata8 => .{ .signed = try fbr.readInt(i64) }, | ||
| 2667 | else => return badDwarf(), | ||
| 2668 | }; | ||
| 2669 | |||
| 2670 | const base = switch (enc & EH.PE.rel_mask) { | ||
| 2671 | EH.PE.pcrel => ctx.pc_rel_base, | ||
| 2672 | EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified, | ||
| 2673 | EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified, | ||
| 2674 | EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified, | ||
| 2675 | else => null, | ||
| 2676 | }; | ||
| 2677 | |||
| 2678 | const ptr: u64 = if (base) |b| switch (value) { | ||
| 2679 | .signed => |s| @intCast(try std.math.add(i64, s, @as(i64, @intCast(b)))), | ||
| 2680 | // absptr can actually contain signed values in some cases (aarch64 MachO) | ||
| 2681 | .unsigned => |u| u +% b, | ||
| 2682 | } else switch (value) { | ||
| 2683 | .signed => |s| @as(u64, @intCast(s)), | ||
| 2684 | .unsigned => |u| u, | ||
| 2685 | }; | ||
| 2686 | |||
| 2687 | if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) { | ||
| 2688 | if (@sizeOf(usize) != addr_size_bytes) { | ||
| 2689 | // See the documentation for `follow_indirect` | ||
| 2690 | return error.NonNativeIndirection; | ||
| 2691 | } | ||
| 2692 | |||
| 2693 | const native_ptr = cast(usize, ptr) orelse return error.PointerOverflow; | ||
| 2694 | return switch (addr_size_bytes) { | ||
| 2695 | 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*, | ||
| 2696 | else => return error.UnsupportedAddrSize, | ||
| 2697 | }; | ||
| 2698 | } else { | ||
| 2699 | return ptr; | ||
| 2700 | } | ||
| 2701 | } | ||
| 2702 | |||
| 2703 | fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { | ||
| 2704 | if (pc_rel_offset < 0) { | ||
| 2705 | return std.math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset))); | ||
| 2706 | } else { | ||
| 2707 | return std.math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); | ||
| 2708 | } | ||
| 2709 | } | ||
lib/std/debug/Dwarf/abi.zig created+410| ... | @@ -0,0 +1,410 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const std = @import("../../std.zig"); | ||
| 3 | const mem = std.mem; | ||
| 4 | const native_os = builtin.os.tag; | ||
| 5 | const posix = std.posix; | ||
| 6 | |||
| 7 | pub fn supportsUnwinding(target: std.Target) bool { | ||
| 8 | return switch (target.cpu.arch) { | ||
| 9 | .x86 => switch (target.os.tag) { | ||
| 10 | .linux, .netbsd, .solaris, .illumos => true, | ||
| 11 | else => false, | ||
| 12 | }, | ||
| 13 | .x86_64 => switch (target.os.tag) { | ||
| 14 | .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, | ||
| 15 | else => false, | ||
| 16 | }, | ||
| 17 | .arm => switch (target.os.tag) { | ||
| 18 | .linux => true, | ||
| 19 | else => false, | ||
| 20 | }, | ||
| 21 | .aarch64 => switch (target.os.tag) { | ||
| 22 | .linux, .netbsd, .freebsd, .macos, .ios => true, | ||
| 23 | else => false, | ||
| 24 | }, | ||
| 25 | else => false, | ||
| 26 | }; | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn ipRegNum() u8 { | ||
| 30 | return switch (builtin.cpu.arch) { | ||
| 31 | .x86 => 8, | ||
| 32 | .x86_64 => 16, | ||
| 33 | .arm => 15, | ||
| 34 | .aarch64 => 32, | ||
| 35 | else => unreachable, | ||
| 36 | }; | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn fpRegNum(reg_context: RegisterContext) u8 { | ||
| 40 | return switch (builtin.cpu.arch) { | ||
| 41 | // GCC on OS X historically did the opposite of ELF for these registers (only in .eh_frame), and that is now the convention for MachO | ||
| 42 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5, | ||
| 43 | .x86_64 => 6, | ||
| 44 | .arm => 11, | ||
| 45 | .aarch64 => 29, | ||
| 46 | else => unreachable, | ||
| 47 | }; | ||
| 48 | } | ||
| 49 | |||
| 50 | pub fn spRegNum(reg_context: RegisterContext) u8 { | ||
| 51 | return switch (builtin.cpu.arch) { | ||
| 52 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4, | ||
| 53 | .x86_64 => 7, | ||
| 54 | .arm => 13, | ||
| 55 | .aarch64 => 31, | ||
| 56 | else => unreachable, | ||
| 57 | }; | ||
| 58 | } | ||
| 59 | |||
| 60 | /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. | ||
| 61 | /// This function clears these signature bits to make the pointer usable. | ||
| 62 | pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { | ||
| 63 | if (builtin.cpu.arch == .aarch64) { | ||
| 64 | // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) | ||
| 65 | // The save / restore is because `xpaclri` operates on x30 (LR) | ||
| 66 | return asm ( | ||
| 67 | \\mov x16, x30 | ||
| 68 | \\mov x30, x15 | ||
| 69 | \\hint 0x07 | ||
| 70 | \\mov x15, x30 | ||
| 71 | \\mov x30, x16 | ||
| 72 | : [ret] "={x15}" (-> usize), | ||
| 73 | : [ptr] "{x15}" (ptr), | ||
| 74 | : "x16" | ||
| 75 | ); | ||
| 76 | } | ||
| 77 | |||
| 78 | return ptr; | ||
| 79 | } | ||
| 80 | |||
| 81 | pub const RegisterContext = struct { | ||
| 82 | eh_frame: bool, | ||
| 83 | is_macho: bool, | ||
| 84 | }; | ||
| 85 | |||
| 86 | pub const AbiError = error{ | ||
| 87 | InvalidRegister, | ||
| 88 | UnimplementedArch, | ||
| 89 | UnimplementedOs, | ||
| 90 | RegisterContextRequired, | ||
| 91 | ThreadContextNotSupported, | ||
| 92 | }; | ||
| 93 | |||
| 94 | fn RegValueReturnType(comptime ContextPtrType: type, comptime T: type) type { | ||
| 95 | const reg_bytes_type = comptime RegBytesReturnType(ContextPtrType); | ||
| 96 | const info = @typeInfo(reg_bytes_type).Pointer; | ||
| 97 | return @Type(.{ | ||
| 98 | .Pointer = .{ | ||
| 99 | .size = .One, | ||
| 100 | .is_const = info.is_const, | ||
| 101 | .is_volatile = info.is_volatile, | ||
| 102 | .is_allowzero = info.is_allowzero, | ||
| 103 | .alignment = info.alignment, | ||
| 104 | .address_space = info.address_space, | ||
| 105 | .child = T, | ||
| 106 | .sentinel = null, | ||
| 107 | }, | ||
| 108 | }); | ||
| 109 | } | ||
| 110 | |||
| 111 | /// Returns a pointer to a register stored in a ThreadContext, preserving the pointer attributes of the context. | ||
| 112 | pub fn regValueNative( | ||
| 113 | comptime T: type, | ||
| 114 | thread_context_ptr: anytype, | ||
| 115 | reg_number: u8, | ||
| 116 | reg_context: ?RegisterContext, | ||
| 117 | ) !RegValueReturnType(@TypeOf(thread_context_ptr), T) { | ||
| 118 | const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context); | ||
| 119 | if (@sizeOf(T) != reg_bytes.len) return error.IncompatibleRegisterSize; | ||
| 120 | return mem.bytesAsValue(T, reg_bytes[0..@sizeOf(T)]); | ||
| 121 | } | ||
| 122 | |||
| 123 | fn RegBytesReturnType(comptime ContextPtrType: type) type { | ||
| 124 | const info = @typeInfo(ContextPtrType); | ||
| 125 | if (info != .Pointer or info.Pointer.child != std.debug.ThreadContext) { | ||
| 126 | @compileError("Expected a pointer to std.debug.ThreadContext, got " ++ @typeName(@TypeOf(ContextPtrType))); | ||
| 127 | } | ||
| 128 | |||
| 129 | return if (info.Pointer.is_const) return []const u8 else []u8; | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Returns a slice containing the backing storage for `reg_number`. | ||
| 133 | /// | ||
| 134 | /// `reg_context` describes in what context the register number is used, as it can have different | ||
| 135 | /// meanings depending on the DWARF container. It is only required when getting the stack or | ||
| 136 | /// frame pointer register on some architectures. | ||
| 137 | pub fn regBytes( | ||
| 138 | thread_context_ptr: anytype, | ||
| 139 | reg_number: u8, | ||
| 140 | reg_context: ?RegisterContext, | ||
| 141 | ) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) { | ||
| 142 | if (native_os == .windows) { | ||
| 143 | return switch (builtin.cpu.arch) { | ||
| 144 | .x86 => switch (reg_number) { | ||
| 145 | 0 => mem.asBytes(&thread_context_ptr.Eax), | ||
| 146 | 1 => mem.asBytes(&thread_context_ptr.Ecx), | ||
| 147 | 2 => mem.asBytes(&thread_context_ptr.Edx), | ||
| 148 | 3 => mem.asBytes(&thread_context_ptr.Ebx), | ||
| 149 | 4 => mem.asBytes(&thread_context_ptr.Esp), | ||
| 150 | 5 => mem.asBytes(&thread_context_ptr.Ebp), | ||
| 151 | 6 => mem.asBytes(&thread_context_ptr.Esi), | ||
| 152 | 7 => mem.asBytes(&thread_context_ptr.Edi), | ||
| 153 | 8 => mem.asBytes(&thread_context_ptr.Eip), | ||
| 154 | 9 => mem.asBytes(&thread_context_ptr.EFlags), | ||
| 155 | 10 => mem.asBytes(&thread_context_ptr.SegCs), | ||
| 156 | 11 => mem.asBytes(&thread_context_ptr.SegSs), | ||
| 157 | 12 => mem.asBytes(&thread_context_ptr.SegDs), | ||
| 158 | 13 => mem.asBytes(&thread_context_ptr.SegEs), | ||
| 159 | 14 => mem.asBytes(&thread_context_ptr.SegFs), | ||
| 160 | 15 => mem.asBytes(&thread_context_ptr.SegGs), | ||
| 161 | else => error.InvalidRegister, | ||
| 162 | }, | ||
| 163 | .x86_64 => switch (reg_number) { | ||
| 164 | 0 => mem.asBytes(&thread_context_ptr.Rax), | ||
| 165 | 1 => mem.asBytes(&thread_context_ptr.Rdx), | ||
| 166 | 2 => mem.asBytes(&thread_context_ptr.Rcx), | ||
| 167 | 3 => mem.asBytes(&thread_context_ptr.Rbx), | ||
| 168 | 4 => mem.asBytes(&thread_context_ptr.Rsi), | ||
| 169 | 5 => mem.asBytes(&thread_context_ptr.Rdi), | ||
| 170 | 6 => mem.asBytes(&thread_context_ptr.Rbp), | ||
| 171 | 7 => mem.asBytes(&thread_context_ptr.Rsp), | ||
| 172 | 8 => mem.asBytes(&thread_context_ptr.R8), | ||
| 173 | 9 => mem.asBytes(&thread_context_ptr.R9), | ||
| 174 | 10 => mem.asBytes(&thread_context_ptr.R10), | ||
| 175 | 11 => mem.asBytes(&thread_context_ptr.R11), | ||
| 176 | 12 => mem.asBytes(&thread_context_ptr.R12), | ||
| 177 | 13 => mem.asBytes(&thread_context_ptr.R13), | ||
| 178 | 14 => mem.asBytes(&thread_context_ptr.R14), | ||
| 179 | 15 => mem.asBytes(&thread_context_ptr.R15), | ||
| 180 | 16 => mem.asBytes(&thread_context_ptr.Rip), | ||
| 181 | else => error.InvalidRegister, | ||
| 182 | }, | ||
| 183 | .aarch64 => switch (reg_number) { | ||
| 184 | 0...30 => mem.asBytes(&thread_context_ptr.DUMMYUNIONNAME.X[reg_number]), | ||
| 185 | 31 => mem.asBytes(&thread_context_ptr.Sp), | ||
| 186 | 32 => mem.asBytes(&thread_context_ptr.Pc), | ||
| 187 | else => error.InvalidRegister, | ||
| 188 | }, | ||
| 189 | else => error.UnimplementedArch, | ||
| 190 | }; | ||
| 191 | } | ||
| 192 | |||
| 193 | if (!std.debug.have_ucontext) return error.ThreadContextNotSupported; | ||
| 194 | |||
| 195 | const ucontext_ptr = thread_context_ptr; | ||
| 196 | return switch (builtin.cpu.arch) { | ||
| 197 | .x86 => switch (native_os) { | ||
| 198 | .linux, .netbsd, .solaris, .illumos => switch (reg_number) { | ||
| 199 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]), | ||
| 200 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]), | ||
| 201 | 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDX]), | ||
| 202 | 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBX]), | ||
| 203 | 4...5 => if (reg_context) |r| bytes: { | ||
| 204 | if (reg_number == 4) { | ||
| 205 | break :bytes if (r.eh_frame and r.is_macho) | ||
| 206 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]) | ||
| 207 | else | ||
| 208 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]); | ||
| 209 | } else { | ||
| 210 | break :bytes if (r.eh_frame and r.is_macho) | ||
| 211 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]) | ||
| 212 | else | ||
| 213 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]); | ||
| 214 | } | ||
| 215 | } else error.RegisterContextRequired, | ||
| 216 | 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESI]), | ||
| 217 | 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDI]), | ||
| 218 | 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EIP]), | ||
| 219 | 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EFL]), | ||
| 220 | 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.CS]), | ||
| 221 | 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.SS]), | ||
| 222 | 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.DS]), | ||
| 223 | 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ES]), | ||
| 224 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.FS]), | ||
| 225 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.GS]), | ||
| 226 | 16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs | ||
| 227 | 32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs | ||
| 228 | else => error.InvalidRegister, | ||
| 229 | }, | ||
| 230 | else => error.UnimplementedOs, | ||
| 231 | }, | ||
| 232 | .x86_64 => switch (native_os) { | ||
| 233 | .linux, .solaris, .illumos => switch (reg_number) { | ||
| 234 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]), | ||
| 235 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]), | ||
| 236 | 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RCX]), | ||
| 237 | 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBX]), | ||
| 238 | 4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSI]), | ||
| 239 | 5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDI]), | ||
| 240 | 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBP]), | ||
| 241 | 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSP]), | ||
| 242 | 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R8]), | ||
| 243 | 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R9]), | ||
| 244 | 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R10]), | ||
| 245 | 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R11]), | ||
| 246 | 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R12]), | ||
| 247 | 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R13]), | ||
| 248 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]), | ||
| 249 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]), | ||
| 250 | 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]), | ||
| 251 | 17...32 => |i| if (native_os.isSolarish()) | ||
| 252 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17]) | ||
| 253 | else | ||
| 254 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]), | ||
| 255 | else => error.InvalidRegister, | ||
| 256 | }, | ||
| 257 | .freebsd => switch (reg_number) { | ||
| 258 | 0 => mem.asBytes(&ucontext_ptr.mcontext.rax), | ||
| 259 | 1 => mem.asBytes(&ucontext_ptr.mcontext.rdx), | ||
| 260 | 2 => mem.asBytes(&ucontext_ptr.mcontext.rcx), | ||
| 261 | 3 => mem.asBytes(&ucontext_ptr.mcontext.rbx), | ||
| 262 | 4 => mem.asBytes(&ucontext_ptr.mcontext.rsi), | ||
| 263 | 5 => mem.asBytes(&ucontext_ptr.mcontext.rdi), | ||
| 264 | 6 => mem.asBytes(&ucontext_ptr.mcontext.rbp), | ||
| 265 | 7 => mem.asBytes(&ucontext_ptr.mcontext.rsp), | ||
| 266 | 8 => mem.asBytes(&ucontext_ptr.mcontext.r8), | ||
| 267 | 9 => mem.asBytes(&ucontext_ptr.mcontext.r9), | ||
| 268 | 10 => mem.asBytes(&ucontext_ptr.mcontext.r10), | ||
| 269 | 11 => mem.asBytes(&ucontext_ptr.mcontext.r11), | ||
| 270 | 12 => mem.asBytes(&ucontext_ptr.mcontext.r12), | ||
| 271 | 13 => mem.asBytes(&ucontext_ptr.mcontext.r13), | ||
| 272 | 14 => mem.asBytes(&ucontext_ptr.mcontext.r14), | ||
| 273 | 15 => mem.asBytes(&ucontext_ptr.mcontext.r15), | ||
| 274 | 16 => mem.asBytes(&ucontext_ptr.mcontext.rip), | ||
| 275 | // TODO: Extract xmm state from mcontext.fpstate? | ||
| 276 | else => error.InvalidRegister, | ||
| 277 | }, | ||
| 278 | .openbsd => switch (reg_number) { | ||
| 279 | 0 => mem.asBytes(&ucontext_ptr.sc_rax), | ||
| 280 | 1 => mem.asBytes(&ucontext_ptr.sc_rdx), | ||
| 281 | 2 => mem.asBytes(&ucontext_ptr.sc_rcx), | ||
| 282 | 3 => mem.asBytes(&ucontext_ptr.sc_rbx), | ||
| 283 | 4 => mem.asBytes(&ucontext_ptr.sc_rsi), | ||
| 284 | 5 => mem.asBytes(&ucontext_ptr.sc_rdi), | ||
| 285 | 6 => mem.asBytes(&ucontext_ptr.sc_rbp), | ||
| 286 | 7 => mem.asBytes(&ucontext_ptr.sc_rsp), | ||
| 287 | 8 => mem.asBytes(&ucontext_ptr.sc_r8), | ||
| 288 | 9 => mem.asBytes(&ucontext_ptr.sc_r9), | ||
| 289 | 10 => mem.asBytes(&ucontext_ptr.sc_r10), | ||
| 290 | 11 => mem.asBytes(&ucontext_ptr.sc_r11), | ||
| 291 | 12 => mem.asBytes(&ucontext_ptr.sc_r12), | ||
| 292 | 13 => mem.asBytes(&ucontext_ptr.sc_r13), | ||
| 293 | 14 => mem.asBytes(&ucontext_ptr.sc_r14), | ||
| 294 | 15 => mem.asBytes(&ucontext_ptr.sc_r15), | ||
| 295 | 16 => mem.asBytes(&ucontext_ptr.sc_rip), | ||
| 296 | // TODO: Extract xmm state from sc_fpstate? | ||
| 297 | else => error.InvalidRegister, | ||
| 298 | }, | ||
| 299 | .macos, .ios => switch (reg_number) { | ||
| 300 | 0 => mem.asBytes(&ucontext_ptr.mcontext.ss.rax), | ||
| 301 | 1 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdx), | ||
| 302 | 2 => mem.asBytes(&ucontext_ptr.mcontext.ss.rcx), | ||
| 303 | 3 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbx), | ||
| 304 | 4 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsi), | ||
| 305 | 5 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdi), | ||
| 306 | 6 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbp), | ||
| 307 | 7 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsp), | ||
| 308 | 8 => mem.asBytes(&ucontext_ptr.mcontext.ss.r8), | ||
| 309 | 9 => mem.asBytes(&ucontext_ptr.mcontext.ss.r9), | ||
| 310 | 10 => mem.asBytes(&ucontext_ptr.mcontext.ss.r10), | ||
| 311 | 11 => mem.asBytes(&ucontext_ptr.mcontext.ss.r11), | ||
| 312 | 12 => mem.asBytes(&ucontext_ptr.mcontext.ss.r12), | ||
| 313 | 13 => mem.asBytes(&ucontext_ptr.mcontext.ss.r13), | ||
| 314 | 14 => mem.asBytes(&ucontext_ptr.mcontext.ss.r14), | ||
| 315 | 15 => mem.asBytes(&ucontext_ptr.mcontext.ss.r15), | ||
| 316 | 16 => mem.asBytes(&ucontext_ptr.mcontext.ss.rip), | ||
| 317 | else => error.InvalidRegister, | ||
| 318 | }, | ||
| 319 | else => error.UnimplementedOs, | ||
| 320 | }, | ||
| 321 | .arm => switch (native_os) { | ||
| 322 | .linux => switch (reg_number) { | ||
| 323 | 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0), | ||
| 324 | 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1), | ||
| 325 | 2 => mem.asBytes(&ucontext_ptr.mcontext.arm_r2), | ||
| 326 | 3 => mem.asBytes(&ucontext_ptr.mcontext.arm_r3), | ||
| 327 | 4 => mem.asBytes(&ucontext_ptr.mcontext.arm_r4), | ||
| 328 | 5 => mem.asBytes(&ucontext_ptr.mcontext.arm_r5), | ||
| 329 | 6 => mem.asBytes(&ucontext_ptr.mcontext.arm_r6), | ||
| 330 | 7 => mem.asBytes(&ucontext_ptr.mcontext.arm_r7), | ||
| 331 | 8 => mem.asBytes(&ucontext_ptr.mcontext.arm_r8), | ||
| 332 | 9 => mem.asBytes(&ucontext_ptr.mcontext.arm_r9), | ||
| 333 | 10 => mem.asBytes(&ucontext_ptr.mcontext.arm_r10), | ||
| 334 | 11 => mem.asBytes(&ucontext_ptr.mcontext.arm_fp), | ||
| 335 | 12 => mem.asBytes(&ucontext_ptr.mcontext.arm_ip), | ||
| 336 | 13 => mem.asBytes(&ucontext_ptr.mcontext.arm_sp), | ||
| 337 | 14 => mem.asBytes(&ucontext_ptr.mcontext.arm_lr), | ||
| 338 | 15 => mem.asBytes(&ucontext_ptr.mcontext.arm_pc), | ||
| 339 | // CPSR is not allocated a register number (See: https://github.com/ARM-software/abi-aa/blob/main/aadwarf32/aadwarf32.rst, Section 4.1) | ||
| 340 | else => error.InvalidRegister, | ||
| 341 | }, | ||
| 342 | else => error.UnimplementedOs, | ||
| 343 | }, | ||
| 344 | .aarch64 => switch (native_os) { | ||
| 345 | .macos, .ios => switch (reg_number) { | ||
| 346 | 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]), | ||
| 347 | 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp), | ||
| 348 | 30 => mem.asBytes(&ucontext_ptr.mcontext.ss.lr), | ||
| 349 | 31 => mem.asBytes(&ucontext_ptr.mcontext.ss.sp), | ||
| 350 | 32 => mem.asBytes(&ucontext_ptr.mcontext.ss.pc), | ||
| 351 | |||
| 352 | // TODO: Find storage for this state | ||
| 353 | //34 => mem.asBytes(&ucontext_ptr.ra_sign_state), | ||
| 354 | |||
| 355 | // V0-V31 | ||
| 356 | 64...95 => mem.asBytes(&ucontext_ptr.mcontext.ns.q[reg_number - 64]), | ||
| 357 | else => error.InvalidRegister, | ||
| 358 | }, | ||
| 359 | .netbsd => switch (reg_number) { | ||
| 360 | 0...34 => mem.asBytes(&ucontext_ptr.mcontext.gregs[reg_number]), | ||
| 361 | else => error.InvalidRegister, | ||
| 362 | }, | ||
| 363 | .freebsd => switch (reg_number) { | ||
| 364 | 0...29 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.x[reg_number]), | ||
| 365 | 30 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.lr), | ||
| 366 | 31 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.sp), | ||
| 367 | |||
| 368 | // TODO: This seems wrong, but it was in the previous debug.zig code for mapping PC, check this | ||
| 369 | 32 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.elr), | ||
| 370 | |||
| 371 | else => error.InvalidRegister, | ||
| 372 | }, | ||
| 373 | .openbsd => switch (reg_number) { | ||
| 374 | 0...30 => mem.asBytes(&ucontext_ptr.sc_x[reg_number]), | ||
| 375 | 31 => mem.asBytes(&ucontext_ptr.sc_sp), | ||
| 376 | 32 => mem.asBytes(&ucontext_ptr.sc_lr), | ||
| 377 | 33 => mem.asBytes(&ucontext_ptr.sc_elr), | ||
| 378 | 34 => mem.asBytes(&ucontext_ptr.sc_spsr), | ||
| 379 | else => error.InvalidRegister, | ||
| 380 | }, | ||
| 381 | else => switch (reg_number) { | ||
| 382 | 0...30 => mem.asBytes(&ucontext_ptr.mcontext.regs[reg_number]), | ||
| 383 | 31 => mem.asBytes(&ucontext_ptr.mcontext.sp), | ||
| 384 | 32 => mem.asBytes(&ucontext_ptr.mcontext.pc), | ||
| 385 | else => error.InvalidRegister, | ||
| 386 | }, | ||
| 387 | }, | ||
| 388 | else => error.UnimplementedArch, | ||
| 389 | }; | ||
| 390 | } | ||
| 391 | |||
| 392 | /// Returns the ABI-defined default value this register has in the unwinding table | ||
| 393 | /// before running any of the CIE instructions. The DWARF spec defines these as having | ||
| 394 | /// the .undefined rule by default, but allows ABI authors to override that. | ||
| 395 | pub fn getRegDefaultValue(reg_number: u8, context: *std.debug.Dwarf.UnwindContext, out: []u8) !void { | ||
| 396 | switch (builtin.cpu.arch) { | ||
| 397 | .aarch64 => { | ||
| 398 | // Callee-saved registers are initialized as if they had the .same_value rule | ||
| 399 | if (reg_number >= 19 and reg_number <= 28) { | ||
| 400 | const src = try regBytes(context.thread_context, reg_number, context.reg_context); | ||
| 401 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 402 | @memcpy(out, src); | ||
| 403 | return; | ||
| 404 | } | ||
| 405 | }, | ||
| 406 | else => {}, | ||
| 407 | } | ||
| 408 | |||
| 409 | @memset(out, undefined); | ||
| 410 | } | ||
lib/std/debug/Dwarf/call_frame.zig created+687| ... | @@ -0,0 +1,687 @@ | ||
| 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 DW = std.dwarf; | ||
| 7 | const abi = std.debug.Dwarf.abi; | ||
| 8 | const assert = std.debug.assert; | ||
| 9 | const native_endian = builtin.cpu.arch.endian(); | ||
| 10 | |||
| 11 | /// TODO merge with std.dwarf.CFA | ||
| 12 | const Opcode = enum(u8) { | ||
| 13 | advance_loc = 0x1 << 6, | ||
| 14 | offset = 0x2 << 6, | ||
| 15 | restore = 0x3 << 6, | ||
| 16 | |||
| 17 | nop = 0x00, | ||
| 18 | set_loc = 0x01, | ||
| 19 | advance_loc1 = 0x02, | ||
| 20 | advance_loc2 = 0x03, | ||
| 21 | advance_loc4 = 0x04, | ||
| 22 | offset_extended = 0x05, | ||
| 23 | restore_extended = 0x06, | ||
| 24 | undefined = 0x07, | ||
| 25 | same_value = 0x08, | ||
| 26 | register = 0x09, | ||
| 27 | remember_state = 0x0a, | ||
| 28 | restore_state = 0x0b, | ||
| 29 | def_cfa = 0x0c, | ||
| 30 | def_cfa_register = 0x0d, | ||
| 31 | def_cfa_offset = 0x0e, | ||
| 32 | def_cfa_expression = 0x0f, | ||
| 33 | expression = 0x10, | ||
| 34 | offset_extended_sf = 0x11, | ||
| 35 | def_cfa_sf = 0x12, | ||
| 36 | def_cfa_offset_sf = 0x13, | ||
| 37 | val_offset = 0x14, | ||
| 38 | val_offset_sf = 0x15, | ||
| 39 | val_expression = 0x16, | ||
| 40 | |||
| 41 | // These opcodes encode an operand in the lower 6 bits of the opcode itself | ||
| 42 | pub const lo_inline = @intFromEnum(Opcode.advance_loc); | ||
| 43 | pub const hi_inline = @intFromEnum(Opcode.restore) | 0b111111; | ||
| 44 | |||
| 45 | // These opcodes are trailed by zero or more operands | ||
| 46 | pub const lo_reserved = @intFromEnum(Opcode.nop); | ||
| 47 | pub const hi_reserved = @intFromEnum(Opcode.val_expression); | ||
| 48 | |||
| 49 | // Vendor-specific opcodes | ||
| 50 | pub const lo_user = 0x1c; | ||
| 51 | pub const hi_user = 0x3f; | ||
| 52 | }; | ||
| 53 | |||
| 54 | fn readBlock(stream: *std.io.FixedBufferStream([]const u8)) ![]const u8 { | ||
| 55 | const reader = stream.reader(); | ||
| 56 | const block_len = try leb.readUleb128(usize, reader); | ||
| 57 | if (stream.pos + block_len > stream.buffer.len) return error.InvalidOperand; | ||
| 58 | |||
| 59 | const block = stream.buffer[stream.pos..][0..block_len]; | ||
| 60 | reader.context.pos += block_len; | ||
| 61 | |||
| 62 | return block; | ||
| 63 | } | ||
| 64 | |||
| 65 | pub const Instruction = union(Opcode) { | ||
| 66 | advance_loc: struct { | ||
| 67 | delta: u8, | ||
| 68 | }, | ||
| 69 | offset: struct { | ||
| 70 | register: u8, | ||
| 71 | offset: u64, | ||
| 72 | }, | ||
| 73 | restore: struct { | ||
| 74 | register: u8, | ||
| 75 | }, | ||
| 76 | nop: void, | ||
| 77 | set_loc: struct { | ||
| 78 | address: u64, | ||
| 79 | }, | ||
| 80 | advance_loc1: struct { | ||
| 81 | delta: u8, | ||
| 82 | }, | ||
| 83 | advance_loc2: struct { | ||
| 84 | delta: u16, | ||
| 85 | }, | ||
| 86 | advance_loc4: struct { | ||
| 87 | delta: u32, | ||
| 88 | }, | ||
| 89 | offset_extended: struct { | ||
| 90 | register: u8, | ||
| 91 | offset: u64, | ||
| 92 | }, | ||
| 93 | restore_extended: struct { | ||
| 94 | register: u8, | ||
| 95 | }, | ||
| 96 | undefined: struct { | ||
| 97 | register: u8, | ||
| 98 | }, | ||
| 99 | same_value: struct { | ||
| 100 | register: u8, | ||
| 101 | }, | ||
| 102 | register: struct { | ||
| 103 | register: u8, | ||
| 104 | target_register: u8, | ||
| 105 | }, | ||
| 106 | remember_state: void, | ||
| 107 | restore_state: void, | ||
| 108 | def_cfa: struct { | ||
| 109 | register: u8, | ||
| 110 | offset: u64, | ||
| 111 | }, | ||
| 112 | def_cfa_register: struct { | ||
| 113 | register: u8, | ||
| 114 | }, | ||
| 115 | def_cfa_offset: struct { | ||
| 116 | offset: u64, | ||
| 117 | }, | ||
| 118 | def_cfa_expression: struct { | ||
| 119 | block: []const u8, | ||
| 120 | }, | ||
| 121 | expression: struct { | ||
| 122 | register: u8, | ||
| 123 | block: []const u8, | ||
| 124 | }, | ||
| 125 | offset_extended_sf: struct { | ||
| 126 | register: u8, | ||
| 127 | offset: i64, | ||
| 128 | }, | ||
| 129 | def_cfa_sf: struct { | ||
| 130 | register: u8, | ||
| 131 | offset: i64, | ||
| 132 | }, | ||
| 133 | def_cfa_offset_sf: struct { | ||
| 134 | offset: i64, | ||
| 135 | }, | ||
| 136 | val_offset: struct { | ||
| 137 | register: u8, | ||
| 138 | offset: u64, | ||
| 139 | }, | ||
| 140 | val_offset_sf: struct { | ||
| 141 | register: u8, | ||
| 142 | offset: i64, | ||
| 143 | }, | ||
| 144 | val_expression: struct { | ||
| 145 | register: u8, | ||
| 146 | block: []const u8, | ||
| 147 | }, | ||
| 148 | |||
| 149 | pub fn read( | ||
| 150 | stream: *std.io.FixedBufferStream([]const u8), | ||
| 151 | addr_size_bytes: u8, | ||
| 152 | endian: std.builtin.Endian, | ||
| 153 | ) !Instruction { | ||
| 154 | const reader = stream.reader(); | ||
| 155 | switch (try reader.readByte()) { | ||
| 156 | Opcode.lo_inline...Opcode.hi_inline => |opcode| { | ||
| 157 | const e: Opcode = @enumFromInt(opcode & 0b11000000); | ||
| 158 | const value: u6 = @intCast(opcode & 0b111111); | ||
| 159 | return switch (e) { | ||
| 160 | .advance_loc => .{ | ||
| 161 | .advance_loc = .{ .delta = value }, | ||
| 162 | }, | ||
| 163 | .offset => .{ | ||
| 164 | .offset = .{ | ||
| 165 | .register = value, | ||
| 166 | .offset = try leb.readUleb128(u64, reader), | ||
| 167 | }, | ||
| 168 | }, | ||
| 169 | .restore => .{ | ||
| 170 | .restore = .{ .register = value }, | ||
| 171 | }, | ||
| 172 | else => unreachable, | ||
| 173 | }; | ||
| 174 | }, | ||
| 175 | Opcode.lo_reserved...Opcode.hi_reserved => |opcode| { | ||
| 176 | const e: Opcode = @enumFromInt(opcode); | ||
| 177 | return switch (e) { | ||
| 178 | .advance_loc, | ||
| 179 | .offset, | ||
| 180 | .restore, | ||
| 181 | => unreachable, | ||
| 182 | .nop => .{ .nop = {} }, | ||
| 183 | .set_loc => .{ | ||
| 184 | .set_loc = .{ | ||
| 185 | .address = switch (addr_size_bytes) { | ||
| 186 | 2 => try reader.readInt(u16, endian), | ||
| 187 | 4 => try reader.readInt(u32, endian), | ||
| 188 | 8 => try reader.readInt(u64, endian), | ||
| 189 | else => return error.InvalidAddrSize, | ||
| 190 | }, | ||
| 191 | }, | ||
| 192 | }, | ||
| 193 | .advance_loc1 => .{ | ||
| 194 | .advance_loc1 = .{ .delta = try reader.readByte() }, | ||
| 195 | }, | ||
| 196 | .advance_loc2 => .{ | ||
| 197 | .advance_loc2 = .{ .delta = try reader.readInt(u16, endian) }, | ||
| 198 | }, | ||
| 199 | .advance_loc4 => .{ | ||
| 200 | .advance_loc4 = .{ .delta = try reader.readInt(u32, endian) }, | ||
| 201 | }, | ||
| 202 | .offset_extended => .{ | ||
| 203 | .offset_extended = .{ | ||
| 204 | .register = try leb.readUleb128(u8, reader), | ||
| 205 | .offset = try leb.readUleb128(u64, reader), | ||
| 206 | }, | ||
| 207 | }, | ||
| 208 | .restore_extended => .{ | ||
| 209 | .restore_extended = .{ | ||
| 210 | .register = try leb.readUleb128(u8, reader), | ||
| 211 | }, | ||
| 212 | }, | ||
| 213 | .undefined => .{ | ||
| 214 | .undefined = .{ | ||
| 215 | .register = try leb.readUleb128(u8, reader), | ||
| 216 | }, | ||
| 217 | }, | ||
| 218 | .same_value => .{ | ||
| 219 | .same_value = .{ | ||
| 220 | .register = try leb.readUleb128(u8, reader), | ||
| 221 | }, | ||
| 222 | }, | ||
| 223 | .register => .{ | ||
| 224 | .register = .{ | ||
| 225 | .register = try leb.readUleb128(u8, reader), | ||
| 226 | .target_register = try leb.readUleb128(u8, reader), | ||
| 227 | }, | ||
| 228 | }, | ||
| 229 | .remember_state => .{ .remember_state = {} }, | ||
| 230 | .restore_state => .{ .restore_state = {} }, | ||
| 231 | .def_cfa => .{ | ||
| 232 | .def_cfa = .{ | ||
| 233 | .register = try leb.readUleb128(u8, reader), | ||
| 234 | .offset = try leb.readUleb128(u64, reader), | ||
| 235 | }, | ||
| 236 | }, | ||
| 237 | .def_cfa_register => .{ | ||
| 238 | .def_cfa_register = .{ | ||
| 239 | .register = try leb.readUleb128(u8, reader), | ||
| 240 | }, | ||
| 241 | }, | ||
| 242 | .def_cfa_offset => .{ | ||
| 243 | .def_cfa_offset = .{ | ||
| 244 | .offset = try leb.readUleb128(u64, reader), | ||
| 245 | }, | ||
| 246 | }, | ||
| 247 | .def_cfa_expression => .{ | ||
| 248 | .def_cfa_expression = .{ | ||
| 249 | .block = try readBlock(stream), | ||
| 250 | }, | ||
| 251 | }, | ||
| 252 | .expression => .{ | ||
| 253 | .expression = .{ | ||
| 254 | .register = try leb.readUleb128(u8, reader), | ||
| 255 | .block = try readBlock(stream), | ||
| 256 | }, | ||
| 257 | }, | ||
| 258 | .offset_extended_sf => .{ | ||
| 259 | .offset_extended_sf = .{ | ||
| 260 | .register = try leb.readUleb128(u8, reader), | ||
| 261 | .offset = try leb.readIleb128(i64, reader), | ||
| 262 | }, | ||
| 263 | }, | ||
| 264 | .def_cfa_sf => .{ | ||
| 265 | .def_cfa_sf = .{ | ||
| 266 | .register = try leb.readUleb128(u8, reader), | ||
| 267 | .offset = try leb.readIleb128(i64, reader), | ||
| 268 | }, | ||
| 269 | }, | ||
| 270 | .def_cfa_offset_sf => .{ | ||
| 271 | .def_cfa_offset_sf = .{ | ||
| 272 | .offset = try leb.readIleb128(i64, reader), | ||
| 273 | }, | ||
| 274 | }, | ||
| 275 | .val_offset => .{ | ||
| 276 | .val_offset = .{ | ||
| 277 | .register = try leb.readUleb128(u8, reader), | ||
| 278 | .offset = try leb.readUleb128(u64, reader), | ||
| 279 | }, | ||
| 280 | }, | ||
| 281 | .val_offset_sf => .{ | ||
| 282 | .val_offset_sf = .{ | ||
| 283 | .register = try leb.readUleb128(u8, reader), | ||
| 284 | .offset = try leb.readIleb128(i64, reader), | ||
| 285 | }, | ||
| 286 | }, | ||
| 287 | .val_expression => .{ | ||
| 288 | .val_expression = .{ | ||
| 289 | .register = try leb.readUleb128(u8, reader), | ||
| 290 | .block = try readBlock(stream), | ||
| 291 | }, | ||
| 292 | }, | ||
| 293 | }; | ||
| 294 | }, | ||
| 295 | Opcode.lo_user...Opcode.hi_user => return error.UnimplementedUserOpcode, | ||
| 296 | else => return error.InvalidOpcode, | ||
| 297 | } | ||
| 298 | } | ||
| 299 | }; | ||
| 300 | |||
| 301 | /// Since register rules are applied (usually) during a panic, | ||
| 302 | /// checked addition / subtraction is used so that we can return | ||
| 303 | /// an error and fall back to FP-based unwinding. | ||
| 304 | pub fn applyOffset(base: usize, offset: i64) !usize { | ||
| 305 | return if (offset >= 0) | ||
| 306 | try std.math.add(usize, base, @as(usize, @intCast(offset))) | ||
| 307 | else | ||
| 308 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); | ||
| 309 | } | ||
| 310 | |||
| 311 | /// This is a virtual machine that runs DWARF call frame instructions. | ||
| 312 | pub const VirtualMachine = struct { | ||
| 313 | /// See section 6.4.1 of the DWARF5 specification for details on each | ||
| 314 | const RegisterRule = union(enum) { | ||
| 315 | // The spec says that the default rule for each column is the undefined rule. | ||
| 316 | // However, it also allows ABI / compiler authors to specify alternate defaults, so | ||
| 317 | // there is a distinction made here. | ||
| 318 | default: void, | ||
| 319 | |||
| 320 | undefined: void, | ||
| 321 | same_value: void, | ||
| 322 | |||
| 323 | // offset(N) | ||
| 324 | offset: i64, | ||
| 325 | |||
| 326 | // val_offset(N) | ||
| 327 | val_offset: i64, | ||
| 328 | |||
| 329 | // register(R) | ||
| 330 | register: u8, | ||
| 331 | |||
| 332 | // expression(E) | ||
| 333 | expression: []const u8, | ||
| 334 | |||
| 335 | // val_expression(E) | ||
| 336 | val_expression: []const u8, | ||
| 337 | |||
| 338 | // Augmenter-defined rule | ||
| 339 | architectural: void, | ||
| 340 | }; | ||
| 341 | |||
| 342 | /// Each row contains unwinding rules for a set of registers. | ||
| 343 | pub const Row = struct { | ||
| 344 | /// Offset from `FrameDescriptionEntry.pc_begin` | ||
| 345 | offset: u64 = 0, | ||
| 346 | |||
| 347 | /// Special-case column that defines the CFA (Canonical Frame Address) rule. | ||
| 348 | /// The register field of this column defines the register that CFA is derived from. | ||
| 349 | cfa: Column = .{}, | ||
| 350 | |||
| 351 | /// The register fields in these columns define the register the rule applies to. | ||
| 352 | columns: ColumnRange = .{}, | ||
| 353 | |||
| 354 | /// Indicates that the next write to any column in this row needs to copy | ||
| 355 | /// the backing column storage first, as it may be referenced by previous rows. | ||
| 356 | copy_on_write: bool = false, | ||
| 357 | }; | ||
| 358 | |||
| 359 | pub const Column = struct { | ||
| 360 | register: ?u8 = null, | ||
| 361 | rule: RegisterRule = .{ .default = {} }, | ||
| 362 | |||
| 363 | /// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes) | ||
| 364 | pub fn resolveValue( | ||
| 365 | self: Column, | ||
| 366 | context: *std.debug.Dwarf.UnwindContext, | ||
| 367 | expression_context: std.debug.Dwarf.expression.Context, | ||
| 368 | ma: *debug.StackIterator.MemoryAccessor, | ||
| 369 | out: []u8, | ||
| 370 | ) !void { | ||
| 371 | switch (self.rule) { | ||
| 372 | .default => { | ||
| 373 | const register = self.register orelse return error.InvalidRegister; | ||
| 374 | try abi.getRegDefaultValue(register, context, out); | ||
| 375 | }, | ||
| 376 | .undefined => { | ||
| 377 | @memset(out, undefined); | ||
| 378 | }, | ||
| 379 | .same_value => { | ||
| 380 | // TODO: This copy could be eliminated if callers always copy the state then call this function to update it | ||
| 381 | const register = self.register orelse return error.InvalidRegister; | ||
| 382 | const src = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 383 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 384 | @memcpy(out, src); | ||
| 385 | }, | ||
| 386 | .offset => |offset| { | ||
| 387 | if (context.cfa) |cfa| { | ||
| 388 | const addr = try applyOffset(cfa, offset); | ||
| 389 | if (ma.load(usize, addr) == null) return error.InvalidAddress; | ||
| 390 | const ptr: *const usize = @ptrFromInt(addr); | ||
| 391 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | ||
| 392 | } else return error.InvalidCFA; | ||
| 393 | }, | ||
| 394 | .val_offset => |offset| { | ||
| 395 | if (context.cfa) |cfa| { | ||
| 396 | mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); | ||
| 397 | } else return error.InvalidCFA; | ||
| 398 | }, | ||
| 399 | .register => |register| { | ||
| 400 | const src = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 401 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 402 | @memcpy(out, try abi.regBytes(context.thread_context, register, context.reg_context)); | ||
| 403 | }, | ||
| 404 | .expression => |expression| { | ||
| 405 | context.stack_machine.reset(); | ||
| 406 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 407 | const addr = if (value) |v| blk: { | ||
| 408 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 409 | break :blk v.generic; | ||
| 410 | } else return error.NoExpressionValue; | ||
| 411 | |||
| 412 | if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress; | ||
| 413 | const ptr: *usize = @ptrFromInt(addr); | ||
| 414 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | ||
| 415 | }, | ||
| 416 | .val_expression => |expression| { | ||
| 417 | context.stack_machine.reset(); | ||
| 418 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 419 | if (value) |v| { | ||
| 420 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 421 | mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); | ||
| 422 | } else return error.NoExpressionValue; | ||
| 423 | }, | ||
| 424 | .architectural => return error.UnimplementedRegisterRule, | ||
| 425 | } | ||
| 426 | } | ||
| 427 | }; | ||
| 428 | |||
| 429 | const ColumnRange = struct { | ||
| 430 | /// Index into `columns` of the first column in this row. | ||
| 431 | start: usize = undefined, | ||
| 432 | len: u8 = 0, | ||
| 433 | }; | ||
| 434 | |||
| 435 | columns: std.ArrayListUnmanaged(Column) = .{}, | ||
| 436 | stack: std.ArrayListUnmanaged(ColumnRange) = .{}, | ||
| 437 | current_row: Row = .{}, | ||
| 438 | |||
| 439 | /// The result of executing the CIE's initial_instructions | ||
| 440 | cie_row: ?Row = null, | ||
| 441 | |||
| 442 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { | ||
| 443 | self.stack.deinit(allocator); | ||
| 444 | self.columns.deinit(allocator); | ||
| 445 | self.* = undefined; | ||
| 446 | } | ||
| 447 | |||
| 448 | pub fn reset(self: *VirtualMachine) void { | ||
| 449 | self.stack.clearRetainingCapacity(); | ||
| 450 | self.columns.clearRetainingCapacity(); | ||
| 451 | self.current_row = .{}; | ||
| 452 | self.cie_row = null; | ||
| 453 | } | ||
| 454 | |||
| 455 | /// Return a slice backed by the row's non-CFA columns | ||
| 456 | pub fn rowColumns(self: VirtualMachine, row: Row) []Column { | ||
| 457 | if (row.columns.len == 0) return &.{}; | ||
| 458 | return self.columns.items[row.columns.start..][0..row.columns.len]; | ||
| 459 | } | ||
| 460 | |||
| 461 | /// Either retrieves or adds a column for `register` (non-CFA) in the current row. | ||
| 462 | fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { | ||
| 463 | for (self.rowColumns(self.current_row)) |*c| { | ||
| 464 | if (c.register == register) return c; | ||
| 465 | } | ||
| 466 | |||
| 467 | if (self.current_row.columns.len == 0) { | ||
| 468 | self.current_row.columns.start = self.columns.items.len; | ||
| 469 | } | ||
| 470 | self.current_row.columns.len += 1; | ||
| 471 | |||
| 472 | const column = try self.columns.addOne(allocator); | ||
| 473 | column.* = .{ | ||
| 474 | .register = register, | ||
| 475 | }; | ||
| 476 | |||
| 477 | return column; | ||
| 478 | } | ||
| 479 | |||
| 480 | /// Runs the CIE instructions, then the FDE instructions. Execution halts | ||
| 481 | /// once the row that corresponds to `pc` is known, and the row is returned. | ||
| 482 | pub fn runTo( | ||
| 483 | self: *VirtualMachine, | ||
| 484 | allocator: std.mem.Allocator, | ||
| 485 | pc: u64, | ||
| 486 | cie: std.debug.Dwarf.CommonInformationEntry, | ||
| 487 | fde: std.debug.Dwarf.FrameDescriptionEntry, | ||
| 488 | addr_size_bytes: u8, | ||
| 489 | endian: std.builtin.Endian, | ||
| 490 | ) !Row { | ||
| 491 | assert(self.cie_row == null); | ||
| 492 | if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; | ||
| 493 | |||
| 494 | var prev_row: Row = self.current_row; | ||
| 495 | |||
| 496 | var cie_stream = std.io.fixedBufferStream(cie.initial_instructions); | ||
| 497 | var fde_stream = std.io.fixedBufferStream(fde.instructions); | ||
| 498 | var streams = [_]*std.io.FixedBufferStream([]const u8){ | ||
| 499 | &cie_stream, | ||
| 500 | &fde_stream, | ||
| 501 | }; | ||
| 502 | |||
| 503 | for (&streams, 0..) |stream, i| { | ||
| 504 | while (stream.pos < stream.buffer.len) { | ||
| 505 | const instruction = try std.debug.Dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); | ||
| 506 | prev_row = try self.step(allocator, cie, i == 0, instruction); | ||
| 507 | if (pc < fde.pc_begin + self.current_row.offset) return prev_row; | ||
| 508 | } | ||
| 509 | } | ||
| 510 | |||
| 511 | return self.current_row; | ||
| 512 | } | ||
| 513 | |||
| 514 | pub fn runToNative( | ||
| 515 | self: *VirtualMachine, | ||
| 516 | allocator: std.mem.Allocator, | ||
| 517 | pc: u64, | ||
| 518 | cie: std.debug.Dwarf.CommonInformationEntry, | ||
| 519 | fde: std.debug.Dwarf.FrameDescriptionEntry, | ||
| 520 | ) !Row { | ||
| 521 | return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian()); | ||
| 522 | } | ||
| 523 | |||
| 524 | fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { | ||
| 525 | if (!self.current_row.copy_on_write) return; | ||
| 526 | |||
| 527 | const new_start = self.columns.items.len; | ||
| 528 | if (self.current_row.columns.len > 0) { | ||
| 529 | try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); | ||
| 530 | self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); | ||
| 531 | self.current_row.columns.start = new_start; | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | /// Executes a single instruction. | ||
| 536 | /// If this instruction is from the CIE, `is_initial` should be set. | ||
| 537 | /// Returns the value of `current_row` before executing this instruction. | ||
| 538 | pub fn step( | ||
| 539 | self: *VirtualMachine, | ||
| 540 | allocator: std.mem.Allocator, | ||
| 541 | cie: std.debug.Dwarf.CommonInformationEntry, | ||
| 542 | is_initial: bool, | ||
| 543 | instruction: Instruction, | ||
| 544 | ) !Row { | ||
| 545 | // CIE instructions must be run before FDE instructions | ||
| 546 | assert(!is_initial or self.cie_row == null); | ||
| 547 | if (!is_initial and self.cie_row == null) { | ||
| 548 | self.cie_row = self.current_row; | ||
| 549 | self.current_row.copy_on_write = true; | ||
| 550 | } | ||
| 551 | |||
| 552 | const prev_row = self.current_row; | ||
| 553 | switch (instruction) { | ||
| 554 | .set_loc => |i| { | ||
| 555 | if (i.address <= self.current_row.offset) return error.InvalidOperation; | ||
| 556 | // TODO: Check cie.segment_selector_size != 0 for DWARFV4 | ||
| 557 | self.current_row.offset = i.address; | ||
| 558 | }, | ||
| 559 | inline .advance_loc, | ||
| 560 | .advance_loc1, | ||
| 561 | .advance_loc2, | ||
| 562 | .advance_loc4, | ||
| 563 | => |i| { | ||
| 564 | self.current_row.offset += i.delta * cie.code_alignment_factor; | ||
| 565 | self.current_row.copy_on_write = true; | ||
| 566 | }, | ||
| 567 | inline .offset, | ||
| 568 | .offset_extended, | ||
| 569 | .offset_extended_sf, | ||
| 570 | => |i| { | ||
| 571 | try self.resolveCopyOnWrite(allocator); | ||
| 572 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 573 | column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor }; | ||
| 574 | }, | ||
| 575 | inline .restore, | ||
| 576 | .restore_extended, | ||
| 577 | => |i| { | ||
| 578 | try self.resolveCopyOnWrite(allocator); | ||
| 579 | if (self.cie_row) |cie_row| { | ||
| 580 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 581 | column.rule = for (self.rowColumns(cie_row)) |cie_column| { | ||
| 582 | if (cie_column.register == i.register) break cie_column.rule; | ||
| 583 | } else .{ .default = {} }; | ||
| 584 | } else return error.InvalidOperation; | ||
| 585 | }, | ||
| 586 | .nop => {}, | ||
| 587 | .undefined => |i| { | ||
| 588 | try self.resolveCopyOnWrite(allocator); | ||
| 589 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 590 | column.rule = .{ .undefined = {} }; | ||
| 591 | }, | ||
| 592 | .same_value => |i| { | ||
| 593 | try self.resolveCopyOnWrite(allocator); | ||
| 594 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 595 | column.rule = .{ .same_value = {} }; | ||
| 596 | }, | ||
| 597 | .register => |i| { | ||
| 598 | try self.resolveCopyOnWrite(allocator); | ||
| 599 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 600 | column.rule = .{ .register = i.target_register }; | ||
| 601 | }, | ||
| 602 | .remember_state => { | ||
| 603 | try self.stack.append(allocator, self.current_row.columns); | ||
| 604 | self.current_row.copy_on_write = true; | ||
| 605 | }, | ||
| 606 | .restore_state => { | ||
| 607 | const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; | ||
| 608 | self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); | ||
| 609 | try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); | ||
| 610 | |||
| 611 | self.current_row.columns.start = self.columns.items.len; | ||
| 612 | self.current_row.columns.len = restored_columns.len; | ||
| 613 | self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); | ||
| 614 | }, | ||
| 615 | .def_cfa => |i| { | ||
| 616 | try self.resolveCopyOnWrite(allocator); | ||
| 617 | self.current_row.cfa = .{ | ||
| 618 | .register = i.register, | ||
| 619 | .rule = .{ .val_offset = @intCast(i.offset) }, | ||
| 620 | }; | ||
| 621 | }, | ||
| 622 | .def_cfa_sf => |i| { | ||
| 623 | try self.resolveCopyOnWrite(allocator); | ||
| 624 | self.current_row.cfa = .{ | ||
| 625 | .register = i.register, | ||
| 626 | .rule = .{ .val_offset = i.offset * cie.data_alignment_factor }, | ||
| 627 | }; | ||
| 628 | }, | ||
| 629 | .def_cfa_register => |i| { | ||
| 630 | try self.resolveCopyOnWrite(allocator); | ||
| 631 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 632 | self.current_row.cfa.register = i.register; | ||
| 633 | }, | ||
| 634 | .def_cfa_offset => |i| { | ||
| 635 | try self.resolveCopyOnWrite(allocator); | ||
| 636 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 637 | self.current_row.cfa.rule = .{ | ||
| 638 | .val_offset = @intCast(i.offset), | ||
| 639 | }; | ||
| 640 | }, | ||
| 641 | .def_cfa_offset_sf => |i| { | ||
| 642 | try self.resolveCopyOnWrite(allocator); | ||
| 643 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 644 | self.current_row.cfa.rule = .{ | ||
| 645 | .val_offset = i.offset * cie.data_alignment_factor, | ||
| 646 | }; | ||
| 647 | }, | ||
| 648 | .def_cfa_expression => |i| { | ||
| 649 | try self.resolveCopyOnWrite(allocator); | ||
| 650 | self.current_row.cfa.register = undefined; | ||
| 651 | self.current_row.cfa.rule = .{ | ||
| 652 | .expression = i.block, | ||
| 653 | }; | ||
| 654 | }, | ||
| 655 | .expression => |i| { | ||
| 656 | try self.resolveCopyOnWrite(allocator); | ||
| 657 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 658 | column.rule = .{ | ||
| 659 | .expression = i.block, | ||
| 660 | }; | ||
| 661 | }, | ||
| 662 | .val_offset => |i| { | ||
| 663 | try self.resolveCopyOnWrite(allocator); | ||
| 664 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 665 | column.rule = .{ | ||
| 666 | .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, | ||
| 667 | }; | ||
| 668 | }, | ||
| 669 | .val_offset_sf => |i| { | ||
| 670 | try self.resolveCopyOnWrite(allocator); | ||
| 671 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 672 | column.rule = .{ | ||
| 673 | .val_offset = i.offset * cie.data_alignment_factor, | ||
| 674 | }; | ||
| 675 | }, | ||
| 676 | .val_expression => |i| { | ||
| 677 | try self.resolveCopyOnWrite(allocator); | ||
| 678 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 679 | column.rule = .{ | ||
| 680 | .val_expression = i.block, | ||
| 681 | }; | ||
| 682 | }, | ||
| 683 | } | ||
| 684 | |||
| 685 | return prev_row; | ||
| 686 | } | ||
| 687 | }; | ||
lib/std/debug/Dwarf/expression.zig created+1638| ... | @@ -0,0 +1,1638 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const leb = std.leb; | ||
| 4 | const OP = std.dwarf.OP; | ||
| 5 | const abi = std.debug.Dwarf.abi; | ||
| 6 | const mem = std.mem; | ||
| 7 | const assert = std.debug.assert; | ||
| 8 | const native_endian = builtin.cpu.arch.endian(); | ||
| 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 Context = struct { | ||
| 14 | /// The dwarf format of the section this expression is in | ||
| 15 | format: std.dwarf.Format = .@"32", | ||
| 16 | /// If specified, any addresses will pass through before being accessed | ||
| 17 | memory_accessor: ?*std.debug.StackIterator.MemoryAccessor = null, | ||
| 18 | /// The compilation unit this expression relates to, if any | ||
| 19 | compile_unit: ?*const std.debug.Dwarf.CompileUnit = null, | ||
| 20 | /// When evaluating a user-presented expression, this is the address of the object being evaluated | ||
| 21 | object_address: ?*const anyopaque = null, | ||
| 22 | /// .debug_addr section | ||
| 23 | debug_addr: ?[]const u8 = null, | ||
| 24 | /// Thread context | ||
| 25 | thread_context: ?*std.debug.ThreadContext = null, | ||
| 26 | reg_context: ?abi.RegisterContext = null, | ||
| 27 | /// Call frame address, if in a CFI context | ||
| 28 | cfa: ?usize = null, | ||
| 29 | /// This expression is a sub-expression from an OP.entry_value instruction | ||
| 30 | entry_value_context: bool = false, | ||
| 31 | }; | ||
| 32 | |||
| 33 | pub const Options = struct { | ||
| 34 | /// The address size of the target architecture | ||
| 35 | addr_size: u8 = @sizeOf(usize), | ||
| 36 | /// Endianness of the target architecture | ||
| 37 | endian: std.builtin.Endian = builtin.target.cpu.arch.endian(), | ||
| 38 | /// Restrict the stack machine to a subset of opcodes used in call frame instructions | ||
| 39 | call_frame_context: bool = false, | ||
| 40 | }; | ||
| 41 | |||
| 42 | // Explicitly defined to support executing sub-expressions | ||
| 43 | pub const Error = error{ | ||
| 44 | UnimplementedExpressionCall, | ||
| 45 | UnimplementedOpcode, | ||
| 46 | UnimplementedUserOpcode, | ||
| 47 | UnimplementedTypedComparison, | ||
| 48 | UnimplementedTypeConversion, | ||
| 49 | |||
| 50 | UnknownExpressionOpcode, | ||
| 51 | |||
| 52 | IncompleteExpressionContext, | ||
| 53 | |||
| 54 | InvalidCFAOpcode, | ||
| 55 | InvalidExpression, | ||
| 56 | InvalidFrameBase, | ||
| 57 | InvalidIntegralTypeSize, | ||
| 58 | InvalidRegister, | ||
| 59 | InvalidSubExpression, | ||
| 60 | InvalidTypeLength, | ||
| 61 | |||
| 62 | TruncatedIntegralType, | ||
| 63 | } || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; | ||
| 64 | |||
| 65 | /// A stack machine that can decode and run DWARF expressions. | ||
| 66 | /// Expressions can be decoded for non-native address size and endianness, | ||
| 67 | /// but can only be executed if the current target matches the configuration. | ||
| 68 | pub fn StackMachine(comptime options: Options) type { | ||
| 69 | const addr_type = switch (options.addr_size) { | ||
| 70 | 2 => u16, | ||
| 71 | 4 => u32, | ||
| 72 | 8 => u64, | ||
| 73 | else => @compileError("Unsupported address size of " ++ options.addr_size), | ||
| 74 | }; | ||
| 75 | |||
| 76 | const addr_type_signed = switch (options.addr_size) { | ||
| 77 | 2 => i16, | ||
| 78 | 4 => i32, | ||
| 79 | 8 => i64, | ||
| 80 | else => @compileError("Unsupported address size of " ++ options.addr_size), | ||
| 81 | }; | ||
| 82 | |||
| 83 | return struct { | ||
| 84 | const Self = @This(); | ||
| 85 | |||
| 86 | const Operand = union(enum) { | ||
| 87 | generic: addr_type, | ||
| 88 | register: u8, | ||
| 89 | type_size: u8, | ||
| 90 | branch_offset: i16, | ||
| 91 | base_register: struct { | ||
| 92 | base_register: u8, | ||
| 93 | offset: i64, | ||
| 94 | }, | ||
| 95 | composite_location: struct { | ||
| 96 | size: u64, | ||
| 97 | offset: i64, | ||
| 98 | }, | ||
| 99 | block: []const u8, | ||
| 100 | register_type: struct { | ||
| 101 | register: u8, | ||
| 102 | type_offset: addr_type, | ||
| 103 | }, | ||
| 104 | const_type: struct { | ||
| 105 | type_offset: addr_type, | ||
| 106 | value_bytes: []const u8, | ||
| 107 | }, | ||
| 108 | deref_type: struct { | ||
| 109 | size: u8, | ||
| 110 | type_offset: addr_type, | ||
| 111 | }, | ||
| 112 | }; | ||
| 113 | |||
| 114 | const Value = union(enum) { | ||
| 115 | generic: addr_type, | ||
| 116 | |||
| 117 | // Typed value with a maximum size of a register | ||
| 118 | regval_type: struct { | ||
| 119 | // Offset of DW_TAG_base_type DIE | ||
| 120 | type_offset: addr_type, | ||
| 121 | type_size: u8, | ||
| 122 | value: addr_type, | ||
| 123 | }, | ||
| 124 | |||
| 125 | // Typed value specified directly in the instruction stream | ||
| 126 | const_type: struct { | ||
| 127 | // Offset of DW_TAG_base_type DIE | ||
| 128 | type_offset: addr_type, | ||
| 129 | // Backed by the instruction stream | ||
| 130 | value_bytes: []const u8, | ||
| 131 | }, | ||
| 132 | |||
| 133 | pub fn asIntegral(self: Value) !addr_type { | ||
| 134 | return switch (self) { | ||
| 135 | .generic => |v| v, | ||
| 136 | |||
| 137 | // TODO: For these two prongs, look up the type and assert it's integral? | ||
| 138 | .regval_type => |regval_type| regval_type.value, | ||
| 139 | .const_type => |const_type| { | ||
| 140 | const value: u64 = switch (const_type.value_bytes.len) { | ||
| 141 | 1 => mem.readInt(u8, const_type.value_bytes[0..1], native_endian), | ||
| 142 | 2 => mem.readInt(u16, const_type.value_bytes[0..2], native_endian), | ||
| 143 | 4 => mem.readInt(u32, const_type.value_bytes[0..4], native_endian), | ||
| 144 | 8 => mem.readInt(u64, const_type.value_bytes[0..8], native_endian), | ||
| 145 | else => return error.InvalidIntegralTypeSize, | ||
| 146 | }; | ||
| 147 | |||
| 148 | return std.math.cast(addr_type, value) orelse error.TruncatedIntegralType; | ||
| 149 | }, | ||
| 150 | }; | ||
| 151 | } | ||
| 152 | }; | ||
| 153 | |||
| 154 | stack: std.ArrayListUnmanaged(Value) = .{}, | ||
| 155 | |||
| 156 | pub fn reset(self: *Self) void { | ||
| 157 | self.stack.clearRetainingCapacity(); | ||
| 158 | } | ||
| 159 | |||
| 160 | pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { | ||
| 161 | self.stack.deinit(allocator); | ||
| 162 | } | ||
| 163 | |||
| 164 | fn generic(value: anytype) Operand { | ||
| 165 | const int_info = @typeInfo(@TypeOf(value)).Int; | ||
| 166 | if (@sizeOf(@TypeOf(value)) > options.addr_size) { | ||
| 167 | return .{ .generic = switch (int_info.signedness) { | ||
| 168 | .signed => @bitCast(@as(addr_type_signed, @truncate(value))), | ||
| 169 | .unsigned => @truncate(value), | ||
| 170 | } }; | ||
| 171 | } else { | ||
| 172 | return .{ .generic = switch (int_info.signedness) { | ||
| 173 | .signed => @bitCast(@as(addr_type_signed, @intCast(value))), | ||
| 174 | .unsigned => @intCast(value), | ||
| 175 | } }; | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8, context: Context) !?Operand { | ||
| 180 | const reader = stream.reader(); | ||
| 181 | return switch (opcode) { | ||
| 182 | OP.addr => generic(try reader.readInt(addr_type, options.endian)), | ||
| 183 | OP.call_ref => switch (context.format) { | ||
| 184 | .@"32" => generic(try reader.readInt(u32, options.endian)), | ||
| 185 | .@"64" => generic(try reader.readInt(u64, options.endian)), | ||
| 186 | }, | ||
| 187 | OP.const1u, | ||
| 188 | OP.pick, | ||
| 189 | => generic(try reader.readByte()), | ||
| 190 | OP.deref_size, | ||
| 191 | OP.xderef_size, | ||
| 192 | => .{ .type_size = try reader.readByte() }, | ||
| 193 | OP.const1s => generic(try reader.readByteSigned()), | ||
| 194 | OP.const2u, | ||
| 195 | OP.call2, | ||
| 196 | => generic(try reader.readInt(u16, options.endian)), | ||
| 197 | OP.call4 => generic(try reader.readInt(u32, options.endian)), | ||
| 198 | OP.const2s => generic(try reader.readInt(i16, options.endian)), | ||
| 199 | OP.bra, | ||
| 200 | OP.skip, | ||
| 201 | => .{ .branch_offset = try reader.readInt(i16, options.endian) }, | ||
| 202 | OP.const4u => generic(try reader.readInt(u32, options.endian)), | ||
| 203 | OP.const4s => generic(try reader.readInt(i32, options.endian)), | ||
| 204 | OP.const8u => generic(try reader.readInt(u64, options.endian)), | ||
| 205 | OP.const8s => generic(try reader.readInt(i64, options.endian)), | ||
| 206 | OP.constu, | ||
| 207 | OP.plus_uconst, | ||
| 208 | OP.addrx, | ||
| 209 | OP.constx, | ||
| 210 | OP.convert, | ||
| 211 | OP.reinterpret, | ||
| 212 | => generic(try leb.readUleb128(u64, reader)), | ||
| 213 | OP.consts, | ||
| 214 | OP.fbreg, | ||
| 215 | => generic(try leb.readIleb128(i64, reader)), | ||
| 216 | OP.lit0...OP.lit31 => |n| generic(n - OP.lit0), | ||
| 217 | OP.reg0...OP.reg31 => |n| .{ .register = n - OP.reg0 }, | ||
| 218 | OP.breg0...OP.breg31 => |n| .{ .base_register = .{ | ||
| 219 | .base_register = n - OP.breg0, | ||
| 220 | .offset = try leb.readIleb128(i64, reader), | ||
| 221 | } }, | ||
| 222 | OP.regx => .{ .register = try leb.readUleb128(u8, reader) }, | ||
| 223 | OP.bregx => blk: { | ||
| 224 | const base_register = try leb.readUleb128(u8, reader); | ||
| 225 | const offset = try leb.readIleb128(i64, reader); | ||
| 226 | break :blk .{ .base_register = .{ | ||
| 227 | .base_register = base_register, | ||
| 228 | .offset = offset, | ||
| 229 | } }; | ||
| 230 | }, | ||
| 231 | OP.regval_type => blk: { | ||
| 232 | const register = try leb.readUleb128(u8, reader); | ||
| 233 | const type_offset = try leb.readUleb128(addr_type, reader); | ||
| 234 | break :blk .{ .register_type = .{ | ||
| 235 | .register = register, | ||
| 236 | .type_offset = type_offset, | ||
| 237 | } }; | ||
| 238 | }, | ||
| 239 | OP.piece => .{ | ||
| 240 | .composite_location = .{ | ||
| 241 | .size = try leb.readUleb128(u8, reader), | ||
| 242 | .offset = 0, | ||
| 243 | }, | ||
| 244 | }, | ||
| 245 | OP.bit_piece => blk: { | ||
| 246 | const size = try leb.readUleb128(u8, reader); | ||
| 247 | const offset = try leb.readIleb128(i64, reader); | ||
| 248 | break :blk .{ .composite_location = .{ | ||
| 249 | .size = size, | ||
| 250 | .offset = offset, | ||
| 251 | } }; | ||
| 252 | }, | ||
| 253 | OP.implicit_value, OP.entry_value => blk: { | ||
| 254 | const size = try leb.readUleb128(u8, reader); | ||
| 255 | if (stream.pos + size > stream.buffer.len) return error.InvalidExpression; | ||
| 256 | const block = stream.buffer[stream.pos..][0..size]; | ||
| 257 | stream.pos += size; | ||
| 258 | break :blk .{ | ||
| 259 | .block = block, | ||
| 260 | }; | ||
| 261 | }, | ||
| 262 | OP.const_type => blk: { | ||
| 263 | const type_offset = try leb.readUleb128(addr_type, reader); | ||
| 264 | const size = try reader.readByte(); | ||
| 265 | if (stream.pos + size > stream.buffer.len) return error.InvalidExpression; | ||
| 266 | const value_bytes = stream.buffer[stream.pos..][0..size]; | ||
| 267 | stream.pos += size; | ||
| 268 | break :blk .{ .const_type = .{ | ||
| 269 | .type_offset = type_offset, | ||
| 270 | .value_bytes = value_bytes, | ||
| 271 | } }; | ||
| 272 | }, | ||
| 273 | OP.deref_type, | ||
| 274 | OP.xderef_type, | ||
| 275 | => .{ | ||
| 276 | .deref_type = .{ | ||
| 277 | .size = try reader.readByte(), | ||
| 278 | .type_offset = try leb.readUleb128(addr_type, reader), | ||
| 279 | }, | ||
| 280 | }, | ||
| 281 | OP.lo_user...OP.hi_user => return error.UnimplementedUserOpcode, | ||
| 282 | else => null, | ||
| 283 | }; | ||
| 284 | } | ||
| 285 | |||
| 286 | pub fn run( | ||
| 287 | self: *Self, | ||
| 288 | expression: []const u8, | ||
| 289 | allocator: std.mem.Allocator, | ||
| 290 | context: Context, | ||
| 291 | initial_value: ?usize, | ||
| 292 | ) Error!?Value { | ||
| 293 | if (initial_value) |i| try self.stack.append(allocator, .{ .generic = i }); | ||
| 294 | var stream = std.io.fixedBufferStream(expression); | ||
| 295 | while (try self.step(&stream, allocator, context)) {} | ||
| 296 | if (self.stack.items.len == 0) return null; | ||
| 297 | return self.stack.items[self.stack.items.len - 1]; | ||
| 298 | } | ||
| 299 | |||
| 300 | /// Reads an opcode and its operands from `stream`, then executes it | ||
| 301 | pub fn step( | ||
| 302 | self: *Self, | ||
| 303 | stream: *std.io.FixedBufferStream([]const u8), | ||
| 304 | allocator: std.mem.Allocator, | ||
| 305 | context: Context, | ||
| 306 | ) Error!bool { | ||
| 307 | if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian()) | ||
| 308 | @compileError("Execution of non-native address sizes / endianness is not supported"); | ||
| 309 | |||
| 310 | const opcode = try stream.reader().readByte(); | ||
| 311 | if (options.call_frame_context and !isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; | ||
| 312 | const operand = try readOperand(stream, opcode, context); | ||
| 313 | switch (opcode) { | ||
| 314 | |||
| 315 | // 2.5.1.1: Literal Encodings | ||
| 316 | OP.lit0...OP.lit31, | ||
| 317 | OP.addr, | ||
| 318 | OP.const1u, | ||
| 319 | OP.const2u, | ||
| 320 | OP.const4u, | ||
| 321 | OP.const8u, | ||
| 322 | OP.const1s, | ||
| 323 | OP.const2s, | ||
| 324 | OP.const4s, | ||
| 325 | OP.const8s, | ||
| 326 | OP.constu, | ||
| 327 | OP.consts, | ||
| 328 | => try self.stack.append(allocator, .{ .generic = operand.?.generic }), | ||
| 329 | |||
| 330 | OP.const_type => { | ||
| 331 | const const_type = operand.?.const_type; | ||
| 332 | try self.stack.append(allocator, .{ .const_type = .{ | ||
| 333 | .type_offset = const_type.type_offset, | ||
| 334 | .value_bytes = const_type.value_bytes, | ||
| 335 | } }); | ||
| 336 | }, | ||
| 337 | |||
| 338 | OP.addrx, | ||
| 339 | OP.constx, | ||
| 340 | => { | ||
| 341 | if (context.compile_unit == null) return error.IncompleteExpressionContext; | ||
| 342 | if (context.debug_addr == null) return error.IncompleteExpressionContext; | ||
| 343 | const debug_addr_index = operand.?.generic; | ||
| 344 | const offset = context.compile_unit.?.addr_base + debug_addr_index; | ||
| 345 | if (offset >= context.debug_addr.?.len) return error.InvalidExpression; | ||
| 346 | const value = mem.readInt(usize, context.debug_addr.?[offset..][0..@sizeOf(usize)], native_endian); | ||
| 347 | try self.stack.append(allocator, .{ .generic = value }); | ||
| 348 | }, | ||
| 349 | |||
| 350 | // 2.5.1.2: Register Values | ||
| 351 | OP.fbreg => { | ||
| 352 | if (context.compile_unit == null) return error.IncompleteExpressionContext; | ||
| 353 | if (context.compile_unit.?.frame_base == null) return error.IncompleteExpressionContext; | ||
| 354 | |||
| 355 | const offset: i64 = @intCast(operand.?.generic); | ||
| 356 | _ = offset; | ||
| 357 | |||
| 358 | switch (context.compile_unit.?.frame_base.?.*) { | ||
| 359 | .exprloc => { | ||
| 360 | // TODO: Run this expression in a nested stack machine | ||
| 361 | return error.UnimplementedOpcode; | ||
| 362 | }, | ||
| 363 | .loclistx => { | ||
| 364 | // TODO: Read value from .debug_loclists | ||
| 365 | return error.UnimplementedOpcode; | ||
| 366 | }, | ||
| 367 | .sec_offset => { | ||
| 368 | // TODO: Read value from .debug_loclists | ||
| 369 | return error.UnimplementedOpcode; | ||
| 370 | }, | ||
| 371 | else => return error.InvalidFrameBase, | ||
| 372 | } | ||
| 373 | }, | ||
| 374 | OP.breg0...OP.breg31, | ||
| 375 | OP.bregx, | ||
| 376 | => { | ||
| 377 | if (context.thread_context == null) return error.IncompleteExpressionContext; | ||
| 378 | |||
| 379 | const base_register = operand.?.base_register; | ||
| 380 | var value: i64 = @intCast(mem.readInt(usize, (try abi.regBytes( | ||
| 381 | context.thread_context.?, | ||
| 382 | base_register.base_register, | ||
| 383 | context.reg_context, | ||
| 384 | ))[0..@sizeOf(usize)], native_endian)); | ||
| 385 | value += base_register.offset; | ||
| 386 | try self.stack.append(allocator, .{ .generic = @intCast(value) }); | ||
| 387 | }, | ||
| 388 | OP.regval_type => { | ||
| 389 | const register_type = operand.?.register_type; | ||
| 390 | const value = mem.readInt(usize, (try abi.regBytes( | ||
| 391 | context.thread_context.?, | ||
| 392 | register_type.register, | ||
| 393 | context.reg_context, | ||
| 394 | ))[0..@sizeOf(usize)], native_endian); | ||
| 395 | try self.stack.append(allocator, .{ | ||
| 396 | .regval_type = .{ | ||
| 397 | .type_offset = register_type.type_offset, | ||
| 398 | .type_size = @sizeOf(addr_type), | ||
| 399 | .value = value, | ||
| 400 | }, | ||
| 401 | }); | ||
| 402 | }, | ||
| 403 | |||
| 404 | // 2.5.1.3: Stack Operations | ||
| 405 | OP.dup => { | ||
| 406 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 407 | try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1]); | ||
| 408 | }, | ||
| 409 | OP.drop => { | ||
| 410 | _ = self.stack.pop(); | ||
| 411 | }, | ||
| 412 | OP.pick, OP.over => { | ||
| 413 | const stack_index = if (opcode == OP.over) 1 else operand.?.generic; | ||
| 414 | if (stack_index >= self.stack.items.len) return error.InvalidExpression; | ||
| 415 | try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1 - stack_index]); | ||
| 416 | }, | ||
| 417 | OP.swap => { | ||
| 418 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 419 | mem.swap(Value, &self.stack.items[self.stack.items.len - 1], &self.stack.items[self.stack.items.len - 2]); | ||
| 420 | }, | ||
| 421 | OP.rot => { | ||
| 422 | if (self.stack.items.len < 3) return error.InvalidExpression; | ||
| 423 | const first = self.stack.items[self.stack.items.len - 1]; | ||
| 424 | self.stack.items[self.stack.items.len - 1] = self.stack.items[self.stack.items.len - 2]; | ||
| 425 | self.stack.items[self.stack.items.len - 2] = self.stack.items[self.stack.items.len - 3]; | ||
| 426 | self.stack.items[self.stack.items.len - 3] = first; | ||
| 427 | }, | ||
| 428 | OP.deref, | ||
| 429 | OP.xderef, | ||
| 430 | OP.deref_size, | ||
| 431 | OP.xderef_size, | ||
| 432 | OP.deref_type, | ||
| 433 | OP.xderef_type, | ||
| 434 | => { | ||
| 435 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 436 | const addr = try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 437 | const addr_space_identifier: ?usize = switch (opcode) { | ||
| 438 | OP.xderef, | ||
| 439 | OP.xderef_size, | ||
| 440 | OP.xderef_type, | ||
| 441 | => blk: { | ||
| 442 | _ = self.stack.pop(); | ||
| 443 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 444 | break :blk try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 445 | }, | ||
| 446 | else => null, | ||
| 447 | }; | ||
| 448 | |||
| 449 | // Usage of addr_space_identifier in the address calculation is implementation defined. | ||
| 450 | // This code will need to be updated to handle any architectures that utilize this. | ||
| 451 | _ = addr_space_identifier; | ||
| 452 | |||
| 453 | const size = switch (opcode) { | ||
| 454 | OP.deref, | ||
| 455 | OP.xderef, | ||
| 456 | => @sizeOf(addr_type), | ||
| 457 | OP.deref_size, | ||
| 458 | OP.xderef_size, | ||
| 459 | => operand.?.type_size, | ||
| 460 | OP.deref_type, | ||
| 461 | OP.xderef_type, | ||
| 462 | => operand.?.deref_type.size, | ||
| 463 | else => unreachable, | ||
| 464 | }; | ||
| 465 | |||
| 466 | if (context.memory_accessor) |memory_accessor| { | ||
| 467 | if (!switch (size) { | ||
| 468 | 1 => memory_accessor.load(u8, addr) != null, | ||
| 469 | 2 => memory_accessor.load(u16, addr) != null, | ||
| 470 | 4 => memory_accessor.load(u32, addr) != null, | ||
| 471 | 8 => memory_accessor.load(u64, addr) != null, | ||
| 472 | else => return error.InvalidExpression, | ||
| 473 | }) return error.InvalidExpression; | ||
| 474 | } | ||
| 475 | |||
| 476 | const value: addr_type = std.math.cast(addr_type, @as(u64, switch (size) { | ||
| 477 | 1 => @as(*const u8, @ptrFromInt(addr)).*, | ||
| 478 | 2 => @as(*const u16, @ptrFromInt(addr)).*, | ||
| 479 | 4 => @as(*const u32, @ptrFromInt(addr)).*, | ||
| 480 | 8 => @as(*const u64, @ptrFromInt(addr)).*, | ||
| 481 | else => return error.InvalidExpression, | ||
| 482 | })) orelse return error.InvalidExpression; | ||
| 483 | |||
| 484 | switch (opcode) { | ||
| 485 | OP.deref_type, | ||
| 486 | OP.xderef_type, | ||
| 487 | => { | ||
| 488 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 489 | .regval_type = .{ | ||
| 490 | .type_offset = operand.?.deref_type.type_offset, | ||
| 491 | .type_size = operand.?.deref_type.size, | ||
| 492 | .value = value, | ||
| 493 | }, | ||
| 494 | }; | ||
| 495 | }, | ||
| 496 | else => { | ||
| 497 | self.stack.items[self.stack.items.len - 1] = .{ .generic = value }; | ||
| 498 | }, | ||
| 499 | } | ||
| 500 | }, | ||
| 501 | OP.push_object_address => { | ||
| 502 | // In sub-expressions, `push_object_address` is not meaningful (as per the | ||
| 503 | // spec), so treat it like a nop | ||
| 504 | if (!context.entry_value_context) { | ||
| 505 | if (context.object_address == null) return error.IncompleteExpressionContext; | ||
| 506 | try self.stack.append(allocator, .{ .generic = @intFromPtr(context.object_address.?) }); | ||
| 507 | } | ||
| 508 | }, | ||
| 509 | OP.form_tls_address => { | ||
| 510 | return error.UnimplementedOpcode; | ||
| 511 | }, | ||
| 512 | OP.call_frame_cfa => { | ||
| 513 | if (context.cfa) |cfa| { | ||
| 514 | try self.stack.append(allocator, .{ .generic = cfa }); | ||
| 515 | } else return error.IncompleteExpressionContext; | ||
| 516 | }, | ||
| 517 | |||
| 518 | // 2.5.1.4: Arithmetic and Logical Operations | ||
| 519 | OP.abs => { | ||
| 520 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 521 | const value: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 522 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 523 | .generic = @abs(value), | ||
| 524 | }; | ||
| 525 | }, | ||
| 526 | OP.@"and" => { | ||
| 527 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 528 | const a = try self.stack.pop().asIntegral(); | ||
| 529 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 530 | .generic = a & try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 531 | }; | ||
| 532 | }, | ||
| 533 | OP.div => { | ||
| 534 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 535 | const a: isize = @bitCast(try self.stack.pop().asIntegral()); | ||
| 536 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 537 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 538 | .generic = @bitCast(try std.math.divTrunc(isize, b, a)), | ||
| 539 | }; | ||
| 540 | }, | ||
| 541 | OP.minus => { | ||
| 542 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 543 | const b = try self.stack.pop().asIntegral(); | ||
| 544 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 545 | .generic = try std.math.sub(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b), | ||
| 546 | }; | ||
| 547 | }, | ||
| 548 | OP.mod => { | ||
| 549 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 550 | const a: isize = @bitCast(try self.stack.pop().asIntegral()); | ||
| 551 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 552 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 553 | .generic = @bitCast(@mod(b, a)), | ||
| 554 | }; | ||
| 555 | }, | ||
| 556 | OP.mul => { | ||
| 557 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 558 | const a: isize = @bitCast(try self.stack.pop().asIntegral()); | ||
| 559 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 560 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 561 | .generic = @bitCast(@mulWithOverflow(a, b)[0]), | ||
| 562 | }; | ||
| 563 | }, | ||
| 564 | OP.neg => { | ||
| 565 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 566 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 567 | .generic = @bitCast( | ||
| 568 | try std.math.negate( | ||
| 569 | @as(isize, @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral())), | ||
| 570 | ), | ||
| 571 | ), | ||
| 572 | }; | ||
| 573 | }, | ||
| 574 | OP.not => { | ||
| 575 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 576 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 577 | .generic = ~try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 578 | }; | ||
| 579 | }, | ||
| 580 | OP.@"or" => { | ||
| 581 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 582 | const a = try self.stack.pop().asIntegral(); | ||
| 583 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 584 | .generic = a | try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 585 | }; | ||
| 586 | }, | ||
| 587 | OP.plus => { | ||
| 588 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 589 | const b = try self.stack.pop().asIntegral(); | ||
| 590 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 591 | .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b), | ||
| 592 | }; | ||
| 593 | }, | ||
| 594 | OP.plus_uconst => { | ||
| 595 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 596 | const constant = operand.?.generic; | ||
| 597 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 598 | .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), constant), | ||
| 599 | }; | ||
| 600 | }, | ||
| 601 | OP.shl => { | ||
| 602 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 603 | const a = try self.stack.pop().asIntegral(); | ||
| 604 | const b = try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 605 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 606 | .generic = std.math.shl(usize, b, a), | ||
| 607 | }; | ||
| 608 | }, | ||
| 609 | OP.shr => { | ||
| 610 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 611 | const a = try self.stack.pop().asIntegral(); | ||
| 612 | const b = try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 613 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 614 | .generic = std.math.shr(usize, b, a), | ||
| 615 | }; | ||
| 616 | }, | ||
| 617 | OP.shra => { | ||
| 618 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 619 | const a = try self.stack.pop().asIntegral(); | ||
| 620 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 621 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 622 | .generic = @bitCast(std.math.shr(isize, b, a)), | ||
| 623 | }; | ||
| 624 | }, | ||
| 625 | OP.xor => { | ||
| 626 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 627 | const a = try self.stack.pop().asIntegral(); | ||
| 628 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 629 | .generic = a ^ try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 630 | }; | ||
| 631 | }, | ||
| 632 | |||
| 633 | // 2.5.1.5: Control Flow Operations | ||
| 634 | OP.le, | ||
| 635 | OP.ge, | ||
| 636 | OP.eq, | ||
| 637 | OP.lt, | ||
| 638 | OP.gt, | ||
| 639 | OP.ne, | ||
| 640 | => { | ||
| 641 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 642 | const a = self.stack.pop(); | ||
| 643 | const b = self.stack.items[self.stack.items.len - 1]; | ||
| 644 | |||
| 645 | if (a == .generic and b == .generic) { | ||
| 646 | const a_int: isize = @bitCast(a.asIntegral() catch unreachable); | ||
| 647 | const b_int: isize = @bitCast(b.asIntegral() catch unreachable); | ||
| 648 | const result = @intFromBool(switch (opcode) { | ||
| 649 | OP.le => b_int <= a_int, | ||
| 650 | OP.ge => b_int >= a_int, | ||
| 651 | OP.eq => b_int == a_int, | ||
| 652 | OP.lt => b_int < a_int, | ||
| 653 | OP.gt => b_int > a_int, | ||
| 654 | OP.ne => b_int != a_int, | ||
| 655 | else => unreachable, | ||
| 656 | }); | ||
| 657 | |||
| 658 | self.stack.items[self.stack.items.len - 1] = .{ .generic = result }; | ||
| 659 | } else { | ||
| 660 | // TODO: Load the types referenced by these values, find their comparison operator, and run it | ||
| 661 | return error.UnimplementedTypedComparison; | ||
| 662 | } | ||
| 663 | }, | ||
| 664 | OP.skip, OP.bra => { | ||
| 665 | const branch_offset = operand.?.branch_offset; | ||
| 666 | const condition = if (opcode == OP.bra) blk: { | ||
| 667 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 668 | break :blk try self.stack.pop().asIntegral() != 0; | ||
| 669 | } else true; | ||
| 670 | |||
| 671 | if (condition) { | ||
| 672 | const new_pos = std.math.cast( | ||
| 673 | usize, | ||
| 674 | try std.math.add(isize, @as(isize, @intCast(stream.pos)), branch_offset), | ||
| 675 | ) orelse return error.InvalidExpression; | ||
| 676 | |||
| 677 | if (new_pos < 0 or new_pos > stream.buffer.len) return error.InvalidExpression; | ||
| 678 | stream.pos = new_pos; | ||
| 679 | } | ||
| 680 | }, | ||
| 681 | OP.call2, | ||
| 682 | OP.call4, | ||
| 683 | OP.call_ref, | ||
| 684 | => { | ||
| 685 | const debug_info_offset = operand.?.generic; | ||
| 686 | _ = debug_info_offset; | ||
| 687 | |||
| 688 | // TODO: Load a DIE entry at debug_info_offset in a .debug_info section (the spec says that it | ||
| 689 | // can be in a separate exe / shared object from the one containing this expression). | ||
| 690 | // Transfer control to the DW_AT_location attribute, with the current stack as input. | ||
| 691 | |||
| 692 | return error.UnimplementedExpressionCall; | ||
| 693 | }, | ||
| 694 | |||
| 695 | // 2.5.1.6: Type Conversions | ||
| 696 | OP.convert => { | ||
| 697 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 698 | const type_offset = operand.?.generic; | ||
| 699 | |||
| 700 | // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size | ||
| 701 | const value = self.stack.items[self.stack.items.len - 1]; | ||
| 702 | if (type_offset == 0) { | ||
| 703 | self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() }; | ||
| 704 | } else { | ||
| 705 | // TODO: Load the DW_TAG_base_type entry in context.compile_unit, find a conversion operator | ||
| 706 | // from the old type to the new type, run it. | ||
| 707 | return error.UnimplementedTypeConversion; | ||
| 708 | } | ||
| 709 | }, | ||
| 710 | OP.reinterpret => { | ||
| 711 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 712 | const type_offset = operand.?.generic; | ||
| 713 | |||
| 714 | // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size | ||
| 715 | const value = self.stack.items[self.stack.items.len - 1]; | ||
| 716 | if (type_offset == 0) { | ||
| 717 | self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() }; | ||
| 718 | } else { | ||
| 719 | self.stack.items[self.stack.items.len - 1] = switch (value) { | ||
| 720 | .generic => |v| .{ | ||
| 721 | .regval_type = .{ | ||
| 722 | .type_offset = type_offset, | ||
| 723 | .type_size = @sizeOf(addr_type), | ||
| 724 | .value = v, | ||
| 725 | }, | ||
| 726 | }, | ||
| 727 | .regval_type => |r| .{ | ||
| 728 | .regval_type = .{ | ||
| 729 | .type_offset = type_offset, | ||
| 730 | .type_size = r.type_size, | ||
| 731 | .value = r.value, | ||
| 732 | }, | ||
| 733 | }, | ||
| 734 | .const_type => |c| .{ | ||
| 735 | .const_type = .{ | ||
| 736 | .type_offset = type_offset, | ||
| 737 | .value_bytes = c.value_bytes, | ||
| 738 | }, | ||
| 739 | }, | ||
| 740 | }; | ||
| 741 | } | ||
| 742 | }, | ||
| 743 | |||
| 744 | // 2.5.1.7: Special Operations | ||
| 745 | OP.nop => {}, | ||
| 746 | OP.entry_value => { | ||
| 747 | const block = operand.?.block; | ||
| 748 | if (block.len == 0) return error.InvalidSubExpression; | ||
| 749 | |||
| 750 | // TODO: The spec states that this sub-expression needs to observe the state (ie. registers) | ||
| 751 | // as it was upon entering the current subprogram. If this isn't being called at the | ||
| 752 | // end of a frame unwind operation, an additional ThreadContext with this state will be needed. | ||
| 753 | |||
| 754 | if (isOpcodeRegisterLocation(block[0])) { | ||
| 755 | if (context.thread_context == null) return error.IncompleteExpressionContext; | ||
| 756 | |||
| 757 | var block_stream = std.io.fixedBufferStream(block); | ||
| 758 | const register = (try readOperand(&block_stream, block[0], context)).?.register; | ||
| 759 | const value = mem.readInt(usize, (try abi.regBytes(context.thread_context.?, register, context.reg_context))[0..@sizeOf(usize)], native_endian); | ||
| 760 | try self.stack.append(allocator, .{ .generic = value }); | ||
| 761 | } else { | ||
| 762 | var stack_machine: Self = .{}; | ||
| 763 | defer stack_machine.deinit(allocator); | ||
| 764 | |||
| 765 | var sub_context = context; | ||
| 766 | sub_context.entry_value_context = true; | ||
| 767 | const result = try stack_machine.run(block, allocator, sub_context, null); | ||
| 768 | try self.stack.append(allocator, result orelse return error.InvalidSubExpression); | ||
| 769 | } | ||
| 770 | }, | ||
| 771 | |||
| 772 | // These have already been handled by readOperand | ||
| 773 | OP.lo_user...OP.hi_user => unreachable, | ||
| 774 | else => { | ||
| 775 | //std.debug.print("Unknown DWARF expression opcode: {x}\n", .{opcode}); | ||
| 776 | return error.UnknownExpressionOpcode; | ||
| 777 | }, | ||
| 778 | } | ||
| 779 | |||
| 780 | return stream.pos < stream.buffer.len; | ||
| 781 | } | ||
| 782 | }; | ||
| 783 | } | ||
| 784 | |||
| 785 | pub fn Builder(comptime options: Options) type { | ||
| 786 | const addr_type = switch (options.addr_size) { | ||
| 787 | 2 => u16, | ||
| 788 | 4 => u32, | ||
| 789 | 8 => u64, | ||
| 790 | else => @compileError("Unsupported address size of " ++ options.addr_size), | ||
| 791 | }; | ||
| 792 | |||
| 793 | return struct { | ||
| 794 | /// Zero-operand instructions | ||
| 795 | pub fn writeOpcode(writer: anytype, comptime opcode: u8) !void { | ||
| 796 | if (options.call_frame_context and !comptime isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; | ||
| 797 | switch (opcode) { | ||
| 798 | OP.dup, | ||
| 799 | OP.drop, | ||
| 800 | OP.over, | ||
| 801 | OP.swap, | ||
| 802 | OP.rot, | ||
| 803 | OP.deref, | ||
| 804 | OP.xderef, | ||
| 805 | OP.push_object_address, | ||
| 806 | OP.form_tls_address, | ||
| 807 | OP.call_frame_cfa, | ||
| 808 | OP.abs, | ||
| 809 | OP.@"and", | ||
| 810 | OP.div, | ||
| 811 | OP.minus, | ||
| 812 | OP.mod, | ||
| 813 | OP.mul, | ||
| 814 | OP.neg, | ||
| 815 | OP.not, | ||
| 816 | OP.@"or", | ||
| 817 | OP.plus, | ||
| 818 | OP.shl, | ||
| 819 | OP.shr, | ||
| 820 | OP.shra, | ||
| 821 | OP.xor, | ||
| 822 | OP.le, | ||
| 823 | OP.ge, | ||
| 824 | OP.eq, | ||
| 825 | OP.lt, | ||
| 826 | OP.gt, | ||
| 827 | OP.ne, | ||
| 828 | OP.nop, | ||
| 829 | OP.stack_value, | ||
| 830 | => try writer.writeByte(opcode), | ||
| 831 | else => @compileError("This opcode requires operands, use `write<Opcode>()` instead"), | ||
| 832 | } | ||
| 833 | } | ||
| 834 | |||
| 835 | // 2.5.1.1: Literal Encodings | ||
| 836 | pub fn writeLiteral(writer: anytype, literal: u8) !void { | ||
| 837 | switch (literal) { | ||
| 838 | 0...31 => |n| try writer.writeByte(n + OP.lit0), | ||
| 839 | else => return error.InvalidLiteral, | ||
| 840 | } | ||
| 841 | } | ||
| 842 | |||
| 843 | pub fn writeConst(writer: anytype, comptime T: type, value: T) !void { | ||
| 844 | if (@typeInfo(T) != .Int) @compileError("Constants must be integers"); | ||
| 845 | |||
| 846 | switch (T) { | ||
| 847 | u8, i8, u16, i16, u32, i32, u64, i64 => { | ||
| 848 | try writer.writeByte(switch (T) { | ||
| 849 | u8 => OP.const1u, | ||
| 850 | i8 => OP.const1s, | ||
| 851 | u16 => OP.const2u, | ||
| 852 | i16 => OP.const2s, | ||
| 853 | u32 => OP.const4u, | ||
| 854 | i32 => OP.const4s, | ||
| 855 | u64 => OP.const8u, | ||
| 856 | i64 => OP.const8s, | ||
| 857 | else => unreachable, | ||
| 858 | }); | ||
| 859 | |||
| 860 | try writer.writeInt(T, value, options.endian); | ||
| 861 | }, | ||
| 862 | else => switch (@typeInfo(T).Int.signedness) { | ||
| 863 | .unsigned => { | ||
| 864 | try writer.writeByte(OP.constu); | ||
| 865 | try leb.writeUleb128(writer, value); | ||
| 866 | }, | ||
| 867 | .signed => { | ||
| 868 | try writer.writeByte(OP.consts); | ||
| 869 | try leb.writeIleb128(writer, value); | ||
| 870 | }, | ||
| 871 | }, | ||
| 872 | } | ||
| 873 | } | ||
| 874 | |||
| 875 | pub fn writeConstx(writer: anytype, debug_addr_offset: anytype) !void { | ||
| 876 | try writer.writeByte(OP.constx); | ||
| 877 | try leb.writeUleb128(writer, debug_addr_offset); | ||
| 878 | } | ||
| 879 | |||
| 880 | pub fn writeConstType(writer: anytype, die_offset: anytype, value_bytes: []const u8) !void { | ||
| 881 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 882 | if (value_bytes.len > 0xff) return error.InvalidTypeLength; | ||
| 883 | try writer.writeByte(OP.const_type); | ||
| 884 | try leb.writeUleb128(writer, die_offset); | ||
| 885 | try writer.writeByte(@intCast(value_bytes.len)); | ||
| 886 | try writer.writeAll(value_bytes); | ||
| 887 | } | ||
| 888 | |||
| 889 | pub fn writeAddr(writer: anytype, value: addr_type) !void { | ||
| 890 | try writer.writeByte(OP.addr); | ||
| 891 | try writer.writeInt(addr_type, value, options.endian); | ||
| 892 | } | ||
| 893 | |||
| 894 | pub fn writeAddrx(writer: anytype, debug_addr_offset: anytype) !void { | ||
| 895 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 896 | try writer.writeByte(OP.addrx); | ||
| 897 | try leb.writeUleb128(writer, debug_addr_offset); | ||
| 898 | } | ||
| 899 | |||
| 900 | // 2.5.1.2: Register Values | ||
| 901 | pub fn writeFbreg(writer: anytype, offset: anytype) !void { | ||
| 902 | try writer.writeByte(OP.fbreg); | ||
| 903 | try leb.writeIleb128(writer, offset); | ||
| 904 | } | ||
| 905 | |||
| 906 | pub fn writeBreg(writer: anytype, register: u8, offset: anytype) !void { | ||
| 907 | if (register > 31) return error.InvalidRegister; | ||
| 908 | try writer.writeByte(OP.breg0 + register); | ||
| 909 | try leb.writeIleb128(writer, offset); | ||
| 910 | } | ||
| 911 | |||
| 912 | pub fn writeBregx(writer: anytype, register: anytype, offset: anytype) !void { | ||
| 913 | try writer.writeByte(OP.bregx); | ||
| 914 | try leb.writeUleb128(writer, register); | ||
| 915 | try leb.writeIleb128(writer, offset); | ||
| 916 | } | ||
| 917 | |||
| 918 | pub fn writeRegvalType(writer: anytype, register: anytype, offset: anytype) !void { | ||
| 919 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 920 | try writer.writeByte(OP.regval_type); | ||
| 921 | try leb.writeUleb128(writer, register); | ||
| 922 | try leb.writeUleb128(writer, offset); | ||
| 923 | } | ||
| 924 | |||
| 925 | // 2.5.1.3: Stack Operations | ||
| 926 | pub fn writePick(writer: anytype, index: u8) !void { | ||
| 927 | try writer.writeByte(OP.pick); | ||
| 928 | try writer.writeByte(index); | ||
| 929 | } | ||
| 930 | |||
| 931 | pub fn writeDerefSize(writer: anytype, size: u8) !void { | ||
| 932 | try writer.writeByte(OP.deref_size); | ||
| 933 | try writer.writeByte(size); | ||
| 934 | } | ||
| 935 | |||
| 936 | pub fn writeXDerefSize(writer: anytype, size: u8) !void { | ||
| 937 | try writer.writeByte(OP.xderef_size); | ||
| 938 | try writer.writeByte(size); | ||
| 939 | } | ||
| 940 | |||
| 941 | pub fn writeDerefType(writer: anytype, size: u8, die_offset: anytype) !void { | ||
| 942 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 943 | try writer.writeByte(OP.deref_type); | ||
| 944 | try writer.writeByte(size); | ||
| 945 | try leb.writeUleb128(writer, die_offset); | ||
| 946 | } | ||
| 947 | |||
| 948 | pub fn writeXDerefType(writer: anytype, size: u8, die_offset: anytype) !void { | ||
| 949 | try writer.writeByte(OP.xderef_type); | ||
| 950 | try writer.writeByte(size); | ||
| 951 | try leb.writeUleb128(writer, die_offset); | ||
| 952 | } | ||
| 953 | |||
| 954 | // 2.5.1.4: Arithmetic and Logical Operations | ||
| 955 | |||
| 956 | pub fn writePlusUconst(writer: anytype, uint_value: anytype) !void { | ||
| 957 | try writer.writeByte(OP.plus_uconst); | ||
| 958 | try leb.writeUleb128(writer, uint_value); | ||
| 959 | } | ||
| 960 | |||
| 961 | // 2.5.1.5: Control Flow Operations | ||
| 962 | |||
| 963 | pub fn writeSkip(writer: anytype, offset: i16) !void { | ||
| 964 | try writer.writeByte(OP.skip); | ||
| 965 | try writer.writeInt(i16, offset, options.endian); | ||
| 966 | } | ||
| 967 | |||
| 968 | pub fn writeBra(writer: anytype, offset: i16) !void { | ||
| 969 | try writer.writeByte(OP.bra); | ||
| 970 | try writer.writeInt(i16, offset, options.endian); | ||
| 971 | } | ||
| 972 | |||
| 973 | pub fn writeCall(writer: anytype, comptime T: type, offset: T) !void { | ||
| 974 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 975 | switch (T) { | ||
| 976 | u16 => try writer.writeByte(OP.call2), | ||
| 977 | u32 => try writer.writeByte(OP.call4), | ||
| 978 | else => @compileError("Call operand must be a 2 or 4 byte offset"), | ||
| 979 | } | ||
| 980 | |||
| 981 | try writer.writeInt(T, offset, options.endian); | ||
| 982 | } | ||
| 983 | |||
| 984 | pub fn writeCallRef(writer: anytype, comptime is_64: bool, value: if (is_64) u64 else u32) !void { | ||
| 985 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 986 | try writer.writeByte(OP.call_ref); | ||
| 987 | try writer.writeInt(if (is_64) u64 else u32, value, options.endian); | ||
| 988 | } | ||
| 989 | |||
| 990 | pub fn writeConvert(writer: anytype, die_offset: anytype) !void { | ||
| 991 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 992 | try writer.writeByte(OP.convert); | ||
| 993 | try leb.writeUleb128(writer, die_offset); | ||
| 994 | } | ||
| 995 | |||
| 996 | pub fn writeReinterpret(writer: anytype, die_offset: anytype) !void { | ||
| 997 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 998 | try writer.writeByte(OP.reinterpret); | ||
| 999 | try leb.writeUleb128(writer, die_offset); | ||
| 1000 | } | ||
| 1001 | |||
| 1002 | // 2.5.1.7: Special Operations | ||
| 1003 | |||
| 1004 | pub fn writeEntryValue(writer: anytype, expression: []const u8) !void { | ||
| 1005 | try writer.writeByte(OP.entry_value); | ||
| 1006 | try leb.writeUleb128(writer, expression.len); | ||
| 1007 | try writer.writeAll(expression); | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | // 2.6: Location Descriptions | ||
| 1011 | pub fn writeReg(writer: anytype, register: u8) !void { | ||
| 1012 | try writer.writeByte(OP.reg0 + register); | ||
| 1013 | } | ||
| 1014 | |||
| 1015 | pub fn writeRegx(writer: anytype, register: anytype) !void { | ||
| 1016 | try writer.writeByte(OP.regx); | ||
| 1017 | try leb.writeUleb128(writer, register); | ||
| 1018 | } | ||
| 1019 | |||
| 1020 | pub fn writeImplicitValue(writer: anytype, value_bytes: []const u8) !void { | ||
| 1021 | try writer.writeByte(OP.implicit_value); | ||
| 1022 | try leb.writeUleb128(writer, value_bytes.len); | ||
| 1023 | try writer.writeAll(value_bytes); | ||
| 1024 | } | ||
| 1025 | }; | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | // Certain opcodes are not allowed in a CFA context, see 6.4.2 | ||
| 1029 | fn isOpcodeValidInCFA(opcode: u8) bool { | ||
| 1030 | return switch (opcode) { | ||
| 1031 | OP.addrx, | ||
| 1032 | OP.call2, | ||
| 1033 | OP.call4, | ||
| 1034 | OP.call_ref, | ||
| 1035 | OP.const_type, | ||
| 1036 | OP.constx, | ||
| 1037 | OP.convert, | ||
| 1038 | OP.deref_type, | ||
| 1039 | OP.regval_type, | ||
| 1040 | OP.reinterpret, | ||
| 1041 | OP.push_object_address, | ||
| 1042 | OP.call_frame_cfa, | ||
| 1043 | => false, | ||
| 1044 | else => true, | ||
| 1045 | }; | ||
| 1046 | } | ||
| 1047 | |||
| 1048 | fn isOpcodeRegisterLocation(opcode: u8) bool { | ||
| 1049 | return switch (opcode) { | ||
| 1050 | OP.reg0...OP.reg31, OP.regx => true, | ||
| 1051 | else => false, | ||
| 1052 | }; | ||
| 1053 | } | ||
| 1054 | |||
| 1055 | const testing = std.testing; | ||
| 1056 | test "DWARF expressions" { | ||
| 1057 | const allocator = std.testing.allocator; | ||
| 1058 | |||
| 1059 | const options = Options{}; | ||
| 1060 | var stack_machine = StackMachine(options){}; | ||
| 1061 | defer stack_machine.deinit(allocator); | ||
| 1062 | |||
| 1063 | const b = Builder(options); | ||
| 1064 | |||
| 1065 | var program = std.ArrayList(u8).init(allocator); | ||
| 1066 | defer program.deinit(); | ||
| 1067 | |||
| 1068 | const writer = program.writer(); | ||
| 1069 | |||
| 1070 | // Literals | ||
| 1071 | { | ||
| 1072 | const context = Context{}; | ||
| 1073 | for (0..32) |i| { | ||
| 1074 | try b.writeLiteral(writer, @intCast(i)); | ||
| 1075 | } | ||
| 1076 | |||
| 1077 | _ = try stack_machine.run(program.items, allocator, context, 0); | ||
| 1078 | |||
| 1079 | for (0..32) |i| { | ||
| 1080 | const expected = 31 - i; | ||
| 1081 | try testing.expectEqual(expected, stack_machine.stack.popOrNull().?.generic); | ||
| 1082 | } | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | // Constants | ||
| 1086 | { | ||
| 1087 | stack_machine.reset(); | ||
| 1088 | program.clearRetainingCapacity(); | ||
| 1089 | |||
| 1090 | const input = [_]comptime_int{ | ||
| 1091 | 1, | ||
| 1092 | -1, | ||
| 1093 | @as(usize, @truncate(0x0fff)), | ||
| 1094 | @as(isize, @truncate(-0x0fff)), | ||
| 1095 | @as(usize, @truncate(0x0fffffff)), | ||
| 1096 | @as(isize, @truncate(-0x0fffffff)), | ||
| 1097 | @as(usize, @truncate(0x0fffffffffffffff)), | ||
| 1098 | @as(isize, @truncate(-0x0fffffffffffffff)), | ||
| 1099 | @as(usize, @truncate(0x8000000)), | ||
| 1100 | @as(isize, @truncate(-0x8000000)), | ||
| 1101 | @as(usize, @truncate(0x12345678_12345678)), | ||
| 1102 | @as(usize, @truncate(0xffffffff_ffffffff)), | ||
| 1103 | @as(usize, @truncate(0xeeeeeeee_eeeeeeee)), | ||
| 1104 | }; | ||
| 1105 | |||
| 1106 | try b.writeConst(writer, u8, input[0]); | ||
| 1107 | try b.writeConst(writer, i8, input[1]); | ||
| 1108 | try b.writeConst(writer, u16, input[2]); | ||
| 1109 | try b.writeConst(writer, i16, input[3]); | ||
| 1110 | try b.writeConst(writer, u32, input[4]); | ||
| 1111 | try b.writeConst(writer, i32, input[5]); | ||
| 1112 | try b.writeConst(writer, u64, input[6]); | ||
| 1113 | try b.writeConst(writer, i64, input[7]); | ||
| 1114 | try b.writeConst(writer, u28, input[8]); | ||
| 1115 | try b.writeConst(writer, i28, input[9]); | ||
| 1116 | try b.writeAddr(writer, input[10]); | ||
| 1117 | |||
| 1118 | var mock_compile_unit: std.debug.Dwarf.CompileUnit = undefined; | ||
| 1119 | mock_compile_unit.addr_base = 1; | ||
| 1120 | |||
| 1121 | var mock_debug_addr = std.ArrayList(u8).init(allocator); | ||
| 1122 | defer mock_debug_addr.deinit(); | ||
| 1123 | |||
| 1124 | try mock_debug_addr.writer().writeInt(u16, 0, native_endian); | ||
| 1125 | try mock_debug_addr.writer().writeInt(usize, input[11], native_endian); | ||
| 1126 | try mock_debug_addr.writer().writeInt(usize, input[12], native_endian); | ||
| 1127 | |||
| 1128 | const context = Context{ | ||
| 1129 | .compile_unit = &mock_compile_unit, | ||
| 1130 | .debug_addr = mock_debug_addr.items, | ||
| 1131 | }; | ||
| 1132 | |||
| 1133 | try b.writeConstx(writer, @as(usize, 1)); | ||
| 1134 | try b.writeAddrx(writer, @as(usize, 1 + @sizeOf(usize))); | ||
| 1135 | |||
| 1136 | const die_offset: usize = @truncate(0xaabbccdd); | ||
| 1137 | const type_bytes: []const u8 = &.{ 1, 2, 3, 4 }; | ||
| 1138 | try b.writeConstType(writer, die_offset, type_bytes); | ||
| 1139 | |||
| 1140 | _ = try stack_machine.run(program.items, allocator, context, 0); | ||
| 1141 | |||
| 1142 | const const_type = stack_machine.stack.popOrNull().?.const_type; | ||
| 1143 | try testing.expectEqual(die_offset, const_type.type_offset); | ||
| 1144 | try testing.expectEqualSlices(u8, type_bytes, const_type.value_bytes); | ||
| 1145 | |||
| 1146 | const expected = .{ | ||
| 1147 | .{ usize, input[12], usize }, | ||
| 1148 | .{ usize, input[11], usize }, | ||
| 1149 | .{ usize, input[10], usize }, | ||
| 1150 | .{ isize, input[9], isize }, | ||
| 1151 | .{ usize, input[8], usize }, | ||
| 1152 | .{ isize, input[7], isize }, | ||
| 1153 | .{ usize, input[6], usize }, | ||
| 1154 | .{ isize, input[5], isize }, | ||
| 1155 | .{ usize, input[4], usize }, | ||
| 1156 | .{ isize, input[3], isize }, | ||
| 1157 | .{ usize, input[2], usize }, | ||
| 1158 | .{ isize, input[1], isize }, | ||
| 1159 | .{ usize, input[0], usize }, | ||
| 1160 | }; | ||
| 1161 | |||
| 1162 | inline for (expected) |e| { | ||
| 1163 | try testing.expectEqual(@as(e[0], e[1]), @as(e[2], @bitCast(stack_machine.stack.popOrNull().?.generic))); | ||
| 1164 | } | ||
| 1165 | } | ||
| 1166 | |||
| 1167 | // Register values | ||
| 1168 | if (@sizeOf(std.debug.ThreadContext) != 0) { | ||
| 1169 | stack_machine.reset(); | ||
| 1170 | program.clearRetainingCapacity(); | ||
| 1171 | |||
| 1172 | const reg_context = abi.RegisterContext{ | ||
| 1173 | .eh_frame = true, | ||
| 1174 | .is_macho = builtin.os.tag == .macos, | ||
| 1175 | }; | ||
| 1176 | var thread_context: std.debug.ThreadContext = undefined; | ||
| 1177 | std.debug.relocateContext(&thread_context); | ||
| 1178 | const context = Context{ | ||
| 1179 | .thread_context = &thread_context, | ||
| 1180 | .reg_context = reg_context, | ||
| 1181 | }; | ||
| 1182 | |||
| 1183 | // Only test register operations on arch / os that have them implemented | ||
| 1184 | if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { | ||
| 1185 | |||
| 1186 | // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it | ||
| 1187 | |||
| 1188 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); | ||
| 1189 | (try abi.regValueNative(usize, &thread_context, abi.fpRegNum(reg_context), reg_context)).* = 1; | ||
| 1190 | (try abi.regValueNative(usize, &thread_context, abi.spRegNum(reg_context), reg_context)).* = 2; | ||
| 1191 | (try abi.regValueNative(usize, &thread_context, abi.ipRegNum(), reg_context)).* = 3; | ||
| 1192 | |||
| 1193 | try b.writeBreg(writer, abi.fpRegNum(reg_context), @as(usize, 100)); | ||
| 1194 | try b.writeBreg(writer, abi.spRegNum(reg_context), @as(usize, 200)); | ||
| 1195 | try b.writeBregx(writer, abi.ipRegNum(), @as(usize, 300)); | ||
| 1196 | try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); | ||
| 1197 | |||
| 1198 | _ = try stack_machine.run(program.items, allocator, context, 0); | ||
| 1199 | |||
| 1200 | const regval_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1201 | try testing.expectEqual(@as(usize, 400), regval_type.type_offset); | ||
| 1202 | try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size); | ||
| 1203 | try testing.expectEqual(@as(usize, 0xee), regval_type.value); | ||
| 1204 | |||
| 1205 | try testing.expectEqual(@as(usize, 303), stack_machine.stack.popOrNull().?.generic); | ||
| 1206 | try testing.expectEqual(@as(usize, 202), stack_machine.stack.popOrNull().?.generic); | ||
| 1207 | try testing.expectEqual(@as(usize, 101), stack_machine.stack.popOrNull().?.generic); | ||
| 1208 | } else |err| { | ||
| 1209 | switch (err) { | ||
| 1210 | error.UnimplementedArch, | ||
| 1211 | error.UnimplementedOs, | ||
| 1212 | error.ThreadContextNotSupported, | ||
| 1213 | => {}, | ||
| 1214 | else => return err, | ||
| 1215 | } | ||
| 1216 | } | ||
| 1217 | } | ||
| 1218 | |||
| 1219 | // Stack operations | ||
| 1220 | { | ||
| 1221 | var context = Context{}; | ||
| 1222 | |||
| 1223 | stack_machine.reset(); | ||
| 1224 | program.clearRetainingCapacity(); | ||
| 1225 | try b.writeConst(writer, u8, 1); | ||
| 1226 | try b.writeOpcode(writer, OP.dup); | ||
| 1227 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1228 | try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1229 | try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1230 | |||
| 1231 | stack_machine.reset(); | ||
| 1232 | program.clearRetainingCapacity(); | ||
| 1233 | try b.writeConst(writer, u8, 1); | ||
| 1234 | try b.writeOpcode(writer, OP.drop); | ||
| 1235 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1236 | try testing.expect(stack_machine.stack.popOrNull() == null); | ||
| 1237 | |||
| 1238 | stack_machine.reset(); | ||
| 1239 | program.clearRetainingCapacity(); | ||
| 1240 | try b.writeConst(writer, u8, 4); | ||
| 1241 | try b.writeConst(writer, u8, 5); | ||
| 1242 | try b.writeConst(writer, u8, 6); | ||
| 1243 | try b.writePick(writer, 2); | ||
| 1244 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1245 | try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic); | ||
| 1246 | |||
| 1247 | stack_machine.reset(); | ||
| 1248 | program.clearRetainingCapacity(); | ||
| 1249 | try b.writeConst(writer, u8, 4); | ||
| 1250 | try b.writeConst(writer, u8, 5); | ||
| 1251 | try b.writeConst(writer, u8, 6); | ||
| 1252 | try b.writeOpcode(writer, OP.over); | ||
| 1253 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1254 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1255 | |||
| 1256 | stack_machine.reset(); | ||
| 1257 | program.clearRetainingCapacity(); | ||
| 1258 | try b.writeConst(writer, u8, 5); | ||
| 1259 | try b.writeConst(writer, u8, 6); | ||
| 1260 | try b.writeOpcode(writer, OP.swap); | ||
| 1261 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1262 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1263 | try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic); | ||
| 1264 | |||
| 1265 | stack_machine.reset(); | ||
| 1266 | program.clearRetainingCapacity(); | ||
| 1267 | try b.writeConst(writer, u8, 4); | ||
| 1268 | try b.writeConst(writer, u8, 5); | ||
| 1269 | try b.writeConst(writer, u8, 6); | ||
| 1270 | try b.writeOpcode(writer, OP.rot); | ||
| 1271 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1272 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1273 | try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic); | ||
| 1274 | try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic); | ||
| 1275 | |||
| 1276 | const deref_target: usize = @truncate(0xffeeffee_ffeeffee); | ||
| 1277 | |||
| 1278 | stack_machine.reset(); | ||
| 1279 | program.clearRetainingCapacity(); | ||
| 1280 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1281 | try b.writeOpcode(writer, OP.deref); | ||
| 1282 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1283 | try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic); | ||
| 1284 | |||
| 1285 | stack_machine.reset(); | ||
| 1286 | program.clearRetainingCapacity(); | ||
| 1287 | try b.writeLiteral(writer, 0); | ||
| 1288 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1289 | try b.writeOpcode(writer, OP.xderef); | ||
| 1290 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1291 | try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic); | ||
| 1292 | |||
| 1293 | stack_machine.reset(); | ||
| 1294 | program.clearRetainingCapacity(); | ||
| 1295 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1296 | try b.writeDerefSize(writer, 1); | ||
| 1297 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1298 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic); | ||
| 1299 | |||
| 1300 | stack_machine.reset(); | ||
| 1301 | program.clearRetainingCapacity(); | ||
| 1302 | try b.writeLiteral(writer, 0); | ||
| 1303 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1304 | try b.writeXDerefSize(writer, 1); | ||
| 1305 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1306 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic); | ||
| 1307 | |||
| 1308 | const type_offset: usize = @truncate(0xaabbaabb_aabbaabb); | ||
| 1309 | |||
| 1310 | stack_machine.reset(); | ||
| 1311 | program.clearRetainingCapacity(); | ||
| 1312 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1313 | try b.writeDerefType(writer, 1, type_offset); | ||
| 1314 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1315 | const deref_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1316 | try testing.expectEqual(type_offset, deref_type.type_offset); | ||
| 1317 | try testing.expectEqual(@as(u8, 1), deref_type.type_size); | ||
| 1318 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), deref_type.value); | ||
| 1319 | |||
| 1320 | stack_machine.reset(); | ||
| 1321 | program.clearRetainingCapacity(); | ||
| 1322 | try b.writeLiteral(writer, 0); | ||
| 1323 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1324 | try b.writeXDerefType(writer, 1, type_offset); | ||
| 1325 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1326 | const xderef_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1327 | try testing.expectEqual(type_offset, xderef_type.type_offset); | ||
| 1328 | try testing.expectEqual(@as(u8, 1), xderef_type.type_size); | ||
| 1329 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), xderef_type.value); | ||
| 1330 | |||
| 1331 | context.object_address = &deref_target; | ||
| 1332 | |||
| 1333 | stack_machine.reset(); | ||
| 1334 | program.clearRetainingCapacity(); | ||
| 1335 | try b.writeOpcode(writer, OP.push_object_address); | ||
| 1336 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1337 | try testing.expectEqual(@as(usize, @intFromPtr(context.object_address.?)), stack_machine.stack.popOrNull().?.generic); | ||
| 1338 | |||
| 1339 | // TODO: Test OP.form_tls_address | ||
| 1340 | |||
| 1341 | context.cfa = @truncate(0xccddccdd_ccddccdd); | ||
| 1342 | |||
| 1343 | stack_machine.reset(); | ||
| 1344 | program.clearRetainingCapacity(); | ||
| 1345 | try b.writeOpcode(writer, OP.call_frame_cfa); | ||
| 1346 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1347 | try testing.expectEqual(context.cfa.?, stack_machine.stack.popOrNull().?.generic); | ||
| 1348 | } | ||
| 1349 | |||
| 1350 | // Arithmetic and Logical Operations | ||
| 1351 | { | ||
| 1352 | const context = Context{}; | ||
| 1353 | |||
| 1354 | stack_machine.reset(); | ||
| 1355 | program.clearRetainingCapacity(); | ||
| 1356 | try b.writeConst(writer, i16, -4096); | ||
| 1357 | try b.writeOpcode(writer, OP.abs); | ||
| 1358 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1359 | try testing.expectEqual(@as(usize, 4096), stack_machine.stack.popOrNull().?.generic); | ||
| 1360 | |||
| 1361 | stack_machine.reset(); | ||
| 1362 | program.clearRetainingCapacity(); | ||
| 1363 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1364 | try b.writeConst(writer, u16, 0xf0ff); | ||
| 1365 | try b.writeOpcode(writer, OP.@"and"); | ||
| 1366 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1367 | try testing.expectEqual(@as(usize, 0xf00f), stack_machine.stack.popOrNull().?.generic); | ||
| 1368 | |||
| 1369 | stack_machine.reset(); | ||
| 1370 | program.clearRetainingCapacity(); | ||
| 1371 | try b.writeConst(writer, i16, -404); | ||
| 1372 | try b.writeConst(writer, i16, 100); | ||
| 1373 | try b.writeOpcode(writer, OP.div); | ||
| 1374 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1375 | try testing.expectEqual(@as(isize, -404 / 100), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic))); | ||
| 1376 | |||
| 1377 | stack_machine.reset(); | ||
| 1378 | program.clearRetainingCapacity(); | ||
| 1379 | try b.writeConst(writer, u16, 200); | ||
| 1380 | try b.writeConst(writer, u16, 50); | ||
| 1381 | try b.writeOpcode(writer, OP.minus); | ||
| 1382 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1383 | try testing.expectEqual(@as(usize, 150), stack_machine.stack.popOrNull().?.generic); | ||
| 1384 | |||
| 1385 | stack_machine.reset(); | ||
| 1386 | program.clearRetainingCapacity(); | ||
| 1387 | try b.writeConst(writer, u16, 123); | ||
| 1388 | try b.writeConst(writer, u16, 100); | ||
| 1389 | try b.writeOpcode(writer, OP.mod); | ||
| 1390 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1391 | try testing.expectEqual(@as(usize, 23), stack_machine.stack.popOrNull().?.generic); | ||
| 1392 | |||
| 1393 | stack_machine.reset(); | ||
| 1394 | program.clearRetainingCapacity(); | ||
| 1395 | try b.writeConst(writer, u16, 0xff); | ||
| 1396 | try b.writeConst(writer, u16, 0xee); | ||
| 1397 | try b.writeOpcode(writer, OP.mul); | ||
| 1398 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1399 | try testing.expectEqual(@as(usize, 0xed12), stack_machine.stack.popOrNull().?.generic); | ||
| 1400 | |||
| 1401 | stack_machine.reset(); | ||
| 1402 | program.clearRetainingCapacity(); | ||
| 1403 | try b.writeConst(writer, u16, 5); | ||
| 1404 | try b.writeOpcode(writer, OP.neg); | ||
| 1405 | try b.writeConst(writer, i16, -6); | ||
| 1406 | try b.writeOpcode(writer, OP.neg); | ||
| 1407 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1408 | try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic); | ||
| 1409 | try testing.expectEqual(@as(isize, -5), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic))); | ||
| 1410 | |||
| 1411 | stack_machine.reset(); | ||
| 1412 | program.clearRetainingCapacity(); | ||
| 1413 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1414 | try b.writeOpcode(writer, OP.not); | ||
| 1415 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1416 | try testing.expectEqual(~@as(usize, 0xff0f), stack_machine.stack.popOrNull().?.generic); | ||
| 1417 | |||
| 1418 | stack_machine.reset(); | ||
| 1419 | program.clearRetainingCapacity(); | ||
| 1420 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1421 | try b.writeConst(writer, u16, 0xf0ff); | ||
| 1422 | try b.writeOpcode(writer, OP.@"or"); | ||
| 1423 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1424 | try testing.expectEqual(@as(usize, 0xffff), stack_machine.stack.popOrNull().?.generic); | ||
| 1425 | |||
| 1426 | stack_machine.reset(); | ||
| 1427 | program.clearRetainingCapacity(); | ||
| 1428 | try b.writeConst(writer, i16, 402); | ||
| 1429 | try b.writeConst(writer, i16, 100); | ||
| 1430 | try b.writeOpcode(writer, OP.plus); | ||
| 1431 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1432 | try testing.expectEqual(@as(usize, 502), stack_machine.stack.popOrNull().?.generic); | ||
| 1433 | |||
| 1434 | stack_machine.reset(); | ||
| 1435 | program.clearRetainingCapacity(); | ||
| 1436 | try b.writeConst(writer, u16, 4096); | ||
| 1437 | try b.writePlusUconst(writer, @as(usize, 8192)); | ||
| 1438 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1439 | try testing.expectEqual(@as(usize, 4096 + 8192), stack_machine.stack.popOrNull().?.generic); | ||
| 1440 | |||
| 1441 | stack_machine.reset(); | ||
| 1442 | program.clearRetainingCapacity(); | ||
| 1443 | try b.writeConst(writer, u16, 0xfff); | ||
| 1444 | try b.writeConst(writer, u16, 1); | ||
| 1445 | try b.writeOpcode(writer, OP.shl); | ||
| 1446 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1447 | try testing.expectEqual(@as(usize, 0xfff << 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1448 | |||
| 1449 | stack_machine.reset(); | ||
| 1450 | program.clearRetainingCapacity(); | ||
| 1451 | try b.writeConst(writer, u16, 0xfff); | ||
| 1452 | try b.writeConst(writer, u16, 1); | ||
| 1453 | try b.writeOpcode(writer, OP.shr); | ||
| 1454 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1455 | try testing.expectEqual(@as(usize, 0xfff >> 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1456 | |||
| 1457 | stack_machine.reset(); | ||
| 1458 | program.clearRetainingCapacity(); | ||
| 1459 | try b.writeConst(writer, u16, 0xfff); | ||
| 1460 | try b.writeConst(writer, u16, 1); | ||
| 1461 | try b.writeOpcode(writer, OP.shr); | ||
| 1462 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1463 | try testing.expectEqual(@as(usize, @bitCast(@as(isize, 0xfff) >> 1)), stack_machine.stack.popOrNull().?.generic); | ||
| 1464 | |||
| 1465 | stack_machine.reset(); | ||
| 1466 | program.clearRetainingCapacity(); | ||
| 1467 | try b.writeConst(writer, u16, 0xf0ff); | ||
| 1468 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1469 | try b.writeOpcode(writer, OP.xor); | ||
| 1470 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1471 | try testing.expectEqual(@as(usize, 0x0ff0), stack_machine.stack.popOrNull().?.generic); | ||
| 1472 | } | ||
| 1473 | |||
| 1474 | // Control Flow Operations | ||
| 1475 | { | ||
| 1476 | const context = Context{}; | ||
| 1477 | const expected = .{ | ||
| 1478 | .{ OP.le, 1, 1, 0 }, | ||
| 1479 | .{ OP.ge, 1, 0, 1 }, | ||
| 1480 | .{ OP.eq, 1, 0, 0 }, | ||
| 1481 | .{ OP.lt, 0, 1, 0 }, | ||
| 1482 | .{ OP.gt, 0, 0, 1 }, | ||
| 1483 | .{ OP.ne, 0, 1, 1 }, | ||
| 1484 | }; | ||
| 1485 | |||
| 1486 | inline for (expected) |e| { | ||
| 1487 | stack_machine.reset(); | ||
| 1488 | program.clearRetainingCapacity(); | ||
| 1489 | |||
| 1490 | try b.writeConst(writer, u16, 0); | ||
| 1491 | try b.writeConst(writer, u16, 0); | ||
| 1492 | try b.writeOpcode(writer, e[0]); | ||
| 1493 | try b.writeConst(writer, u16, 0); | ||
| 1494 | try b.writeConst(writer, u16, 1); | ||
| 1495 | try b.writeOpcode(writer, e[0]); | ||
| 1496 | try b.writeConst(writer, u16, 1); | ||
| 1497 | try b.writeConst(writer, u16, 0); | ||
| 1498 | try b.writeOpcode(writer, e[0]); | ||
| 1499 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1500 | try testing.expectEqual(@as(usize, e[3]), stack_machine.stack.popOrNull().?.generic); | ||
| 1501 | try testing.expectEqual(@as(usize, e[2]), stack_machine.stack.popOrNull().?.generic); | ||
| 1502 | try testing.expectEqual(@as(usize, e[1]), stack_machine.stack.popOrNull().?.generic); | ||
| 1503 | } | ||
| 1504 | |||
| 1505 | stack_machine.reset(); | ||
| 1506 | program.clearRetainingCapacity(); | ||
| 1507 | try b.writeLiteral(writer, 2); | ||
| 1508 | try b.writeSkip(writer, 1); | ||
| 1509 | try b.writeLiteral(writer, 3); | ||
| 1510 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1511 | try testing.expectEqual(@as(usize, 2), stack_machine.stack.popOrNull().?.generic); | ||
| 1512 | |||
| 1513 | stack_machine.reset(); | ||
| 1514 | program.clearRetainingCapacity(); | ||
| 1515 | try b.writeLiteral(writer, 2); | ||
| 1516 | try b.writeBra(writer, 1); | ||
| 1517 | try b.writeLiteral(writer, 3); | ||
| 1518 | try b.writeLiteral(writer, 0); | ||
| 1519 | try b.writeBra(writer, 1); | ||
| 1520 | try b.writeLiteral(writer, 4); | ||
| 1521 | try b.writeLiteral(writer, 5); | ||
| 1522 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1523 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1524 | try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic); | ||
| 1525 | try testing.expect(stack_machine.stack.popOrNull() == null); | ||
| 1526 | |||
| 1527 | // TODO: Test call2, call4, call_ref once implemented | ||
| 1528 | |||
| 1529 | } | ||
| 1530 | |||
| 1531 | // Type conversions | ||
| 1532 | { | ||
| 1533 | const context = Context{}; | ||
| 1534 | stack_machine.reset(); | ||
| 1535 | program.clearRetainingCapacity(); | ||
| 1536 | |||
| 1537 | // TODO: Test typed OP.convert once implemented | ||
| 1538 | |||
| 1539 | const value: usize = @truncate(0xffeeffee_ffeeffee); | ||
| 1540 | var value_bytes: [options.addr_size]u8 = undefined; | ||
| 1541 | mem.writeInt(usize, &value_bytes, value, native_endian); | ||
| 1542 | |||
| 1543 | // Convert to generic type | ||
| 1544 | stack_machine.reset(); | ||
| 1545 | program.clearRetainingCapacity(); | ||
| 1546 | try b.writeConstType(writer, @as(usize, 0), &value_bytes); | ||
| 1547 | try b.writeConvert(writer, @as(usize, 0)); | ||
| 1548 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1549 | try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic); | ||
| 1550 | |||
| 1551 | // Reinterpret to generic type | ||
| 1552 | stack_machine.reset(); | ||
| 1553 | program.clearRetainingCapacity(); | ||
| 1554 | try b.writeConstType(writer, @as(usize, 0), &value_bytes); | ||
| 1555 | try b.writeReinterpret(writer, @as(usize, 0)); | ||
| 1556 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1557 | try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic); | ||
| 1558 | |||
| 1559 | // Reinterpret to new type | ||
| 1560 | const die_offset: usize = 0xffee; | ||
| 1561 | |||
| 1562 | stack_machine.reset(); | ||
| 1563 | program.clearRetainingCapacity(); | ||
| 1564 | try b.writeConstType(writer, @as(usize, 0), &value_bytes); | ||
| 1565 | try b.writeReinterpret(writer, die_offset); | ||
| 1566 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1567 | const const_type = stack_machine.stack.popOrNull().?.const_type; | ||
| 1568 | try testing.expectEqual(die_offset, const_type.type_offset); | ||
| 1569 | |||
| 1570 | stack_machine.reset(); | ||
| 1571 | program.clearRetainingCapacity(); | ||
| 1572 | try b.writeLiteral(writer, 0); | ||
| 1573 | try b.writeReinterpret(writer, die_offset); | ||
| 1574 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1575 | const regval_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1576 | try testing.expectEqual(die_offset, regval_type.type_offset); | ||
| 1577 | } | ||
| 1578 | |||
| 1579 | // Special operations | ||
| 1580 | { | ||
| 1581 | var context = Context{}; | ||
| 1582 | |||
| 1583 | stack_machine.reset(); | ||
| 1584 | program.clearRetainingCapacity(); | ||
| 1585 | try b.writeOpcode(writer, OP.nop); | ||
| 1586 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1587 | try testing.expect(stack_machine.stack.popOrNull() == null); | ||
| 1588 | |||
| 1589 | // Sub-expression | ||
| 1590 | { | ||
| 1591 | var sub_program = std.ArrayList(u8).init(allocator); | ||
| 1592 | defer sub_program.deinit(); | ||
| 1593 | const sub_writer = sub_program.writer(); | ||
| 1594 | try b.writeLiteral(sub_writer, 3); | ||
| 1595 | |||
| 1596 | stack_machine.reset(); | ||
| 1597 | program.clearRetainingCapacity(); | ||
| 1598 | try b.writeEntryValue(writer, sub_program.items); | ||
| 1599 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1600 | try testing.expectEqual(@as(usize, 3), stack_machine.stack.popOrNull().?.generic); | ||
| 1601 | } | ||
| 1602 | |||
| 1603 | // Register location description | ||
| 1604 | const reg_context = abi.RegisterContext{ | ||
| 1605 | .eh_frame = true, | ||
| 1606 | .is_macho = builtin.os.tag == .macos, | ||
| 1607 | }; | ||
| 1608 | var thread_context: std.debug.ThreadContext = undefined; | ||
| 1609 | std.debug.relocateContext(&thread_context); | ||
| 1610 | context = Context{ | ||
| 1611 | .thread_context = &thread_context, | ||
| 1612 | .reg_context = reg_context, | ||
| 1613 | }; | ||
| 1614 | |||
| 1615 | if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { | ||
| 1616 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); | ||
| 1617 | |||
| 1618 | var sub_program = std.ArrayList(u8).init(allocator); | ||
| 1619 | defer sub_program.deinit(); | ||
| 1620 | const sub_writer = sub_program.writer(); | ||
| 1621 | try b.writeReg(sub_writer, 0); | ||
| 1622 | |||
| 1623 | stack_machine.reset(); | ||
| 1624 | program.clearRetainingCapacity(); | ||
| 1625 | try b.writeEntryValue(writer, sub_program.items); | ||
| 1626 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1627 | try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.popOrNull().?.generic); | ||
| 1628 | } else |err| { | ||
| 1629 | switch (err) { | ||
| 1630 | error.UnimplementedArch, | ||
| 1631 | error.UnimplementedOs, | ||
| 1632 | error.ThreadContextNotSupported, | ||
| 1633 | => {}, | ||
| 1634 | else => return err, | ||
| 1635 | } | ||
| 1636 | } | ||
| 1637 | } | ||
| 1638 | } | ||
lib/std/dwarf.zig+5-2697| ... | @@ -1,12 +1,8 @@ | ... | @@ -1,12 +1,8 @@ |
| 1 | //! DWARF debugging data format. | 1 | //! DWARF debugging data format. |
| 2 | 2 | //! | |
| 3 | const builtin = @import("builtin"); | 3 | //! This namespace contains unopinionated types and data definitions only. For |
| 4 | const std = @import("std.zig"); | 4 | //! an implementation of parsing and caching DWARF information, see |
| 5 | const debug = std.debug; | 5 | //! `std.debug.Dwarf`. |
| 6 | const mem = std.mem; | ||
| 7 | const math = std.math; | ||
| 8 | const assert = debug.assert; | ||
| 9 | const native_endian = builtin.cpu.arch.endian(); | ||
| 10 | 6 | ||
| 11 | pub const TAG = @import("dwarf/TAG.zig"); | 7 | pub const TAG = @import("dwarf/TAG.zig"); |
| 12 | pub const AT = @import("dwarf/AT.zig"); | 8 | pub const AT = @import("dwarf/AT.zig"); |
| ... | @@ -15,9 +11,7 @@ pub const LANG = @import("dwarf/LANG.zig"); | ... | @@ -15,9 +11,7 @@ pub const LANG = @import("dwarf/LANG.zig"); |
| 15 | pub const FORM = @import("dwarf/FORM.zig"); | 11 | pub const FORM = @import("dwarf/FORM.zig"); |
| 16 | pub const ATE = @import("dwarf/ATE.zig"); | 12 | pub const ATE = @import("dwarf/ATE.zig"); |
| 17 | pub const EH = @import("dwarf/EH.zig"); | 13 | pub const EH = @import("dwarf/EH.zig"); |
| 18 | pub const abi = @import("dwarf/abi.zig"); | 14 | pub const Format = enum { @"32", @"64" }; |
| 19 | pub const call_frame = @import("dwarf/call_frame.zig"); | ||
| 20 | pub const expressions = @import("dwarf/expressions.zig"); | ||
| 21 | 15 | ||
| 22 | pub const LLE = struct { | 16 | pub const LLE = struct { |
| 23 | pub const end_of_list = 0x00; | 17 | pub const end_of_list = 0x00; |
| ... | @@ -151,2689 +145,3 @@ pub const CC = enum(u8) { | ... | @@ -151,2689 +145,3 @@ pub const CC = enum(u8) { |
| 151 | pub const lo_user = 0x40; | 145 | pub const lo_user = 0x40; |
| 152 | pub const hi_user = 0xff; | 146 | pub const hi_user = 0xff; |
| 153 | }; | 147 | }; |
| 154 | |||
| 155 | pub const Format = enum { @"32", @"64" }; | ||
| 156 | |||
| 157 | const PcRange = struct { | ||
| 158 | start: u64, | ||
| 159 | end: u64, | ||
| 160 | }; | ||
| 161 | |||
| 162 | const Func = struct { | ||
| 163 | pc_range: ?PcRange, | ||
| 164 | name: ?[]const u8, | ||
| 165 | }; | ||
| 166 | |||
| 167 | pub const CompileUnit = struct { | ||
| 168 | version: u16, | ||
| 169 | format: Format, | ||
| 170 | die: Die, | ||
| 171 | pc_range: ?PcRange, | ||
| 172 | |||
| 173 | str_offsets_base: usize, | ||
| 174 | addr_base: usize, | ||
| 175 | rnglists_base: usize, | ||
| 176 | loclists_base: usize, | ||
| 177 | frame_base: ?*const FormValue, | ||
| 178 | }; | ||
| 179 | |||
| 180 | const Abbrev = struct { | ||
| 181 | code: u64, | ||
| 182 | tag_id: u64, | ||
| 183 | has_children: bool, | ||
| 184 | attrs: []Attr, | ||
| 185 | |||
| 186 | fn deinit(abbrev: *Abbrev, allocator: mem.Allocator) void { | ||
| 187 | allocator.free(abbrev.attrs); | ||
| 188 | abbrev.* = undefined; | ||
| 189 | } | ||
| 190 | |||
| 191 | const Attr = struct { | ||
| 192 | id: u64, | ||
| 193 | form_id: u64, | ||
| 194 | /// Only valid if form_id is .implicit_const | ||
| 195 | payload: i64, | ||
| 196 | }; | ||
| 197 | |||
| 198 | const Table = struct { | ||
| 199 | // offset from .debug_abbrev | ||
| 200 | offset: u64, | ||
| 201 | abbrevs: []Abbrev, | ||
| 202 | |||
| 203 | fn deinit(table: *Table, allocator: mem.Allocator) void { | ||
| 204 | for (table.abbrevs) |*abbrev| { | ||
| 205 | abbrev.deinit(allocator); | ||
| 206 | } | ||
| 207 | allocator.free(table.abbrevs); | ||
| 208 | table.* = undefined; | ||
| 209 | } | ||
| 210 | |||
| 211 | fn get(table: *const Table, abbrev_code: u64) ?*const Abbrev { | ||
| 212 | return for (table.abbrevs) |*abbrev| { | ||
| 213 | if (abbrev.code == abbrev_code) break abbrev; | ||
| 214 | } else null; | ||
| 215 | } | ||
| 216 | }; | ||
| 217 | }; | ||
| 218 | |||
| 219 | pub const FormValue = union(enum) { | ||
| 220 | addr: u64, | ||
| 221 | addrx: usize, | ||
| 222 | block: []const u8, | ||
| 223 | udata: u64, | ||
| 224 | data16: *const [16]u8, | ||
| 225 | sdata: i64, | ||
| 226 | exprloc: []const u8, | ||
| 227 | flag: bool, | ||
| 228 | sec_offset: u64, | ||
| 229 | ref: u64, | ||
| 230 | ref_addr: u64, | ||
| 231 | string: [:0]const u8, | ||
| 232 | strp: u64, | ||
| 233 | strx: usize, | ||
| 234 | line_strp: u64, | ||
| 235 | loclistx: u64, | ||
| 236 | rnglistx: u64, | ||
| 237 | |||
| 238 | fn getString(fv: FormValue, di: DwarfInfo) ![:0]const u8 { | ||
| 239 | switch (fv) { | ||
| 240 | .string => |s| return s, | ||
| 241 | .strp => |off| return di.getString(off), | ||
| 242 | .line_strp => |off| return di.getLineString(off), | ||
| 243 | else => return badDwarf(), | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | fn getUInt(fv: FormValue, comptime U: type) !U { | ||
| 248 | return switch (fv) { | ||
| 249 | inline .udata, | ||
| 250 | .sdata, | ||
| 251 | .sec_offset, | ||
| 252 | => |c| math.cast(U, c) orelse badDwarf(), | ||
| 253 | else => badDwarf(), | ||
| 254 | }; | ||
| 255 | } | ||
| 256 | }; | ||
| 257 | |||
| 258 | const Die = struct { | ||
| 259 | tag_id: u64, | ||
| 260 | has_children: bool, | ||
| 261 | attrs: []Attr, | ||
| 262 | |||
| 263 | const Attr = struct { | ||
| 264 | id: u64, | ||
| 265 | value: FormValue, | ||
| 266 | }; | ||
| 267 | |||
| 268 | fn deinit(self: *Die, allocator: mem.Allocator) void { | ||
| 269 | allocator.free(self.attrs); | ||
| 270 | self.* = undefined; | ||
| 271 | } | ||
| 272 | |||
| 273 | fn getAttr(self: *const Die, id: u64) ?*const FormValue { | ||
| 274 | for (self.attrs) |*attr| { | ||
| 275 | if (attr.id == id) return &attr.value; | ||
| 276 | } | ||
| 277 | return null; | ||
| 278 | } | ||
| 279 | |||
| 280 | fn getAttrAddr( | ||
| 281 | self: *const Die, | ||
| 282 | di: *const DwarfInfo, | ||
| 283 | id: u64, | ||
| 284 | compile_unit: CompileUnit, | ||
| 285 | ) error{ InvalidDebugInfo, MissingDebugInfo }!u64 { | ||
| 286 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 287 | return switch (form_value.*) { | ||
| 288 | .addr => |value| value, | ||
| 289 | .addrx => |index| di.readDebugAddr(compile_unit, index), | ||
| 290 | else => error.InvalidDebugInfo, | ||
| 291 | }; | ||
| 292 | } | ||
| 293 | |||
| 294 | fn getAttrSecOffset(self: *const Die, id: u64) !u64 { | ||
| 295 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 296 | return form_value.getUInt(u64); | ||
| 297 | } | ||
| 298 | |||
| 299 | fn getAttrUnsignedLe(self: *const Die, id: u64) !u64 { | ||
| 300 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 301 | return switch (form_value.*) { | ||
| 302 | .Const => |value| value.asUnsignedLe(), | ||
| 303 | else => error.InvalidDebugInfo, | ||
| 304 | }; | ||
| 305 | } | ||
| 306 | |||
| 307 | fn getAttrRef(self: *const Die, id: u64) !u64 { | ||
| 308 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 309 | return switch (form_value.*) { | ||
| 310 | .ref => |value| value, | ||
| 311 | else => error.InvalidDebugInfo, | ||
| 312 | }; | ||
| 313 | } | ||
| 314 | |||
| 315 | pub fn getAttrString( | ||
| 316 | self: *const Die, | ||
| 317 | di: *DwarfInfo, | ||
| 318 | id: u64, | ||
| 319 | opt_str: ?[]const u8, | ||
| 320 | compile_unit: CompileUnit, | ||
| 321 | ) error{ InvalidDebugInfo, MissingDebugInfo }![]const u8 { | ||
| 322 | const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; | ||
| 323 | switch (form_value.*) { | ||
| 324 | .string => |value| return value, | ||
| 325 | .strp => |offset| return di.getString(offset), | ||
| 326 | .strx => |index| { | ||
| 327 | const debug_str_offsets = di.section(.debug_str_offsets) orelse return badDwarf(); | ||
| 328 | if (compile_unit.str_offsets_base == 0) return badDwarf(); | ||
| 329 | switch (compile_unit.format) { | ||
| 330 | .@"32" => { | ||
| 331 | const byte_offset = compile_unit.str_offsets_base + 4 * index; | ||
| 332 | if (byte_offset + 4 > debug_str_offsets.len) return badDwarf(); | ||
| 333 | const offset = mem.readInt(u32, debug_str_offsets[byte_offset..][0..4], di.endian); | ||
| 334 | return getStringGeneric(opt_str, offset); | ||
| 335 | }, | ||
| 336 | .@"64" => { | ||
| 337 | const byte_offset = compile_unit.str_offsets_base + 8 * index; | ||
| 338 | if (byte_offset + 8 > debug_str_offsets.len) return badDwarf(); | ||
| 339 | const offset = mem.readInt(u64, debug_str_offsets[byte_offset..][0..8], di.endian); | ||
| 340 | return getStringGeneric(opt_str, offset); | ||
| 341 | }, | ||
| 342 | } | ||
| 343 | }, | ||
| 344 | .line_strp => |offset| return di.getLineString(offset), | ||
| 345 | else => return badDwarf(), | ||
| 346 | } | ||
| 347 | } | ||
| 348 | }; | ||
| 349 | |||
| 350 | const FileEntry = struct { | ||
| 351 | path: []const u8, | ||
| 352 | dir_index: u32 = 0, | ||
| 353 | mtime: u64 = 0, | ||
| 354 | size: u64 = 0, | ||
| 355 | md5: [16]u8 = [1]u8{0} ** 16, | ||
| 356 | }; | ||
| 357 | |||
| 358 | const LineNumberProgram = struct { | ||
| 359 | address: u64, | ||
| 360 | file: usize, | ||
| 361 | line: i64, | ||
| 362 | column: u64, | ||
| 363 | version: u16, | ||
| 364 | is_stmt: bool, | ||
| 365 | basic_block: bool, | ||
| 366 | end_sequence: bool, | ||
| 367 | |||
| 368 | default_is_stmt: bool, | ||
| 369 | target_address: u64, | ||
| 370 | include_dirs: []const FileEntry, | ||
| 371 | |||
| 372 | prev_valid: bool, | ||
| 373 | prev_address: u64, | ||
| 374 | prev_file: usize, | ||
| 375 | prev_line: i64, | ||
| 376 | prev_column: u64, | ||
| 377 | prev_is_stmt: bool, | ||
| 378 | prev_basic_block: bool, | ||
| 379 | prev_end_sequence: bool, | ||
| 380 | |||
| 381 | // Reset the state machine following the DWARF specification | ||
| 382 | pub fn reset(self: *LineNumberProgram) void { | ||
| 383 | self.address = 0; | ||
| 384 | self.file = 1; | ||
| 385 | self.line = 1; | ||
| 386 | self.column = 0; | ||
| 387 | self.is_stmt = self.default_is_stmt; | ||
| 388 | self.basic_block = false; | ||
| 389 | self.end_sequence = false; | ||
| 390 | // Invalidate all the remaining fields | ||
| 391 | self.prev_valid = false; | ||
| 392 | self.prev_address = 0; | ||
| 393 | self.prev_file = undefined; | ||
| 394 | self.prev_line = undefined; | ||
| 395 | self.prev_column = undefined; | ||
| 396 | self.prev_is_stmt = undefined; | ||
| 397 | self.prev_basic_block = undefined; | ||
| 398 | self.prev_end_sequence = undefined; | ||
| 399 | } | ||
| 400 | |||
| 401 | pub fn init( | ||
| 402 | is_stmt: bool, | ||
| 403 | include_dirs: []const FileEntry, | ||
| 404 | target_address: u64, | ||
| 405 | version: u16, | ||
| 406 | ) LineNumberProgram { | ||
| 407 | return LineNumberProgram{ | ||
| 408 | .address = 0, | ||
| 409 | .file = 1, | ||
| 410 | .line = 1, | ||
| 411 | .column = 0, | ||
| 412 | .version = version, | ||
| 413 | .is_stmt = is_stmt, | ||
| 414 | .basic_block = false, | ||
| 415 | .end_sequence = false, | ||
| 416 | .include_dirs = include_dirs, | ||
| 417 | .default_is_stmt = is_stmt, | ||
| 418 | .target_address = target_address, | ||
| 419 | .prev_valid = false, | ||
| 420 | .prev_address = 0, | ||
| 421 | .prev_file = undefined, | ||
| 422 | .prev_line = undefined, | ||
| 423 | .prev_column = undefined, | ||
| 424 | .prev_is_stmt = undefined, | ||
| 425 | .prev_basic_block = undefined, | ||
| 426 | .prev_end_sequence = undefined, | ||
| 427 | }; | ||
| 428 | } | ||
| 429 | |||
| 430 | pub fn checkLineMatch( | ||
| 431 | self: *LineNumberProgram, | ||
| 432 | allocator: mem.Allocator, | ||
| 433 | file_entries: []const FileEntry, | ||
| 434 | ) !?debug.LineInfo { | ||
| 435 | if (self.prev_valid and | ||
| 436 | self.target_address >= self.prev_address and | ||
| 437 | self.target_address < self.address) | ||
| 438 | { | ||
| 439 | const file_index = if (self.version >= 5) self.prev_file else i: { | ||
| 440 | if (self.prev_file == 0) return missingDwarf(); | ||
| 441 | break :i self.prev_file - 1; | ||
| 442 | }; | ||
| 443 | |||
| 444 | if (file_index >= file_entries.len) return badDwarf(); | ||
| 445 | const file_entry = &file_entries[file_index]; | ||
| 446 | |||
| 447 | if (file_entry.dir_index >= self.include_dirs.len) return badDwarf(); | ||
| 448 | const dir_name = self.include_dirs[file_entry.dir_index].path; | ||
| 449 | |||
| 450 | const file_name = try std.fs.path.join(allocator, &[_][]const u8{ | ||
| 451 | dir_name, file_entry.path, | ||
| 452 | }); | ||
| 453 | |||
| 454 | return debug.LineInfo{ | ||
| 455 | .line = if (self.prev_line >= 0) @as(u64, @intCast(self.prev_line)) else 0, | ||
| 456 | .column = self.prev_column, | ||
| 457 | .file_name = file_name, | ||
| 458 | }; | ||
| 459 | } | ||
| 460 | |||
| 461 | self.prev_valid = true; | ||
| 462 | self.prev_address = self.address; | ||
| 463 | self.prev_file = self.file; | ||
| 464 | self.prev_line = self.line; | ||
| 465 | self.prev_column = self.column; | ||
| 466 | self.prev_is_stmt = self.is_stmt; | ||
| 467 | self.prev_basic_block = self.basic_block; | ||
| 468 | self.prev_end_sequence = self.end_sequence; | ||
| 469 | return null; | ||
| 470 | } | ||
| 471 | }; | ||
| 472 | |||
| 473 | const UnitHeader = struct { | ||
| 474 | format: Format, | ||
| 475 | header_length: u4, | ||
| 476 | unit_length: u64, | ||
| 477 | }; | ||
| 478 | fn readUnitHeader(fbr: *FixedBufferReader, opt_ma: ?*debug.StackIterator.MemoryAccessor) !UnitHeader { | ||
| 479 | return switch (try if (opt_ma) |ma| fbr.readIntChecked(u32, ma) else fbr.readInt(u32)) { | ||
| 480 | 0...0xfffffff0 - 1 => |unit_length| .{ | ||
| 481 | .format = .@"32", | ||
| 482 | .header_length = 4, | ||
| 483 | .unit_length = unit_length, | ||
| 484 | }, | ||
| 485 | 0xfffffff0...0xffffffff - 1 => badDwarf(), | ||
| 486 | 0xffffffff => .{ | ||
| 487 | .format = .@"64", | ||
| 488 | .header_length = 12, | ||
| 489 | .unit_length = try if (opt_ma) |ma| fbr.readIntChecked(u64, ma) else fbr.readInt(u64), | ||
| 490 | }, | ||
| 491 | }; | ||
| 492 | } | ||
| 493 | |||
| 494 | fn parseFormValue( | ||
| 495 | fbr: *FixedBufferReader, | ||
| 496 | form_id: u64, | ||
| 497 | format: Format, | ||
| 498 | implicit_const: ?i64, | ||
| 499 | ) anyerror!FormValue { | ||
| 500 | return switch (form_id) { | ||
| 501 | FORM.addr => .{ .addr = try fbr.readAddress(switch (@bitSizeOf(usize)) { | ||
| 502 | 32 => .@"32", | ||
| 503 | 64 => .@"64", | ||
| 504 | else => @compileError("unsupported @sizeOf(usize)"), | ||
| 505 | }) }, | ||
| 506 | FORM.addrx1 => .{ .addrx = try fbr.readInt(u8) }, | ||
| 507 | FORM.addrx2 => .{ .addrx = try fbr.readInt(u16) }, | ||
| 508 | FORM.addrx3 => .{ .addrx = try fbr.readInt(u24) }, | ||
| 509 | FORM.addrx4 => .{ .addrx = try fbr.readInt(u32) }, | ||
| 510 | FORM.addrx => .{ .addrx = try fbr.readUleb128(usize) }, | ||
| 511 | |||
| 512 | FORM.block1, | ||
| 513 | FORM.block2, | ||
| 514 | FORM.block4, | ||
| 515 | FORM.block, | ||
| 516 | => .{ .block = try fbr.readBytes(switch (form_id) { | ||
| 517 | FORM.block1 => try fbr.readInt(u8), | ||
| 518 | FORM.block2 => try fbr.readInt(u16), | ||
| 519 | FORM.block4 => try fbr.readInt(u32), | ||
| 520 | FORM.block => try fbr.readUleb128(usize), | ||
| 521 | else => unreachable, | ||
| 522 | }) }, | ||
| 523 | |||
| 524 | FORM.data1 => .{ .udata = try fbr.readInt(u8) }, | ||
| 525 | FORM.data2 => .{ .udata = try fbr.readInt(u16) }, | ||
| 526 | FORM.data4 => .{ .udata = try fbr.readInt(u32) }, | ||
| 527 | FORM.data8 => .{ .udata = try fbr.readInt(u64) }, | ||
| 528 | FORM.data16 => .{ .data16 = (try fbr.readBytes(16))[0..16] }, | ||
| 529 | FORM.udata => .{ .udata = try fbr.readUleb128(u64) }, | ||
| 530 | FORM.sdata => .{ .sdata = try fbr.readIleb128(i64) }, | ||
| 531 | FORM.exprloc => .{ .exprloc = try fbr.readBytes(try fbr.readUleb128(usize)) }, | ||
| 532 | FORM.flag => .{ .flag = (try fbr.readByte()) != 0 }, | ||
| 533 | FORM.flag_present => .{ .flag = true }, | ||
| 534 | FORM.sec_offset => .{ .sec_offset = try fbr.readAddress(format) }, | ||
| 535 | |||
| 536 | FORM.ref1 => .{ .ref = try fbr.readInt(u8) }, | ||
| 537 | FORM.ref2 => .{ .ref = try fbr.readInt(u16) }, | ||
| 538 | FORM.ref4 => .{ .ref = try fbr.readInt(u32) }, | ||
| 539 | FORM.ref8 => .{ .ref = try fbr.readInt(u64) }, | ||
| 540 | FORM.ref_udata => .{ .ref = try fbr.readUleb128(u64) }, | ||
| 541 | |||
| 542 | FORM.ref_addr => .{ .ref_addr = try fbr.readAddress(format) }, | ||
| 543 | FORM.ref_sig8 => .{ .ref = try fbr.readInt(u64) }, | ||
| 544 | |||
| 545 | FORM.string => .{ .string = try fbr.readBytesTo(0) }, | ||
| 546 | FORM.strp => .{ .strp = try fbr.readAddress(format) }, | ||
| 547 | FORM.strx1 => .{ .strx = try fbr.readInt(u8) }, | ||
| 548 | FORM.strx2 => .{ .strx = try fbr.readInt(u16) }, | ||
| 549 | FORM.strx3 => .{ .strx = try fbr.readInt(u24) }, | ||
| 550 | FORM.strx4 => .{ .strx = try fbr.readInt(u32) }, | ||
| 551 | FORM.strx => .{ .strx = try fbr.readUleb128(usize) }, | ||
| 552 | FORM.line_strp => .{ .line_strp = try fbr.readAddress(format) }, | ||
| 553 | FORM.indirect => parseFormValue(fbr, try fbr.readUleb128(u64), format, implicit_const), | ||
| 554 | FORM.implicit_const => .{ .sdata = implicit_const orelse return badDwarf() }, | ||
| 555 | FORM.loclistx => .{ .loclistx = try fbr.readUleb128(u64) }, | ||
| 556 | FORM.rnglistx => .{ .rnglistx = try fbr.readUleb128(u64) }, | ||
| 557 | else => { | ||
| 558 | //debug.print("unrecognized form id: {x}\n", .{form_id}); | ||
| 559 | return badDwarf(); | ||
| 560 | }, | ||
| 561 | }; | ||
| 562 | } | ||
| 563 | |||
| 564 | pub const DwarfSection = enum { | ||
| 565 | debug_info, | ||
| 566 | debug_abbrev, | ||
| 567 | debug_str, | ||
| 568 | debug_str_offsets, | ||
| 569 | debug_line, | ||
| 570 | debug_line_str, | ||
| 571 | debug_ranges, | ||
| 572 | debug_loclists, | ||
| 573 | debug_rnglists, | ||
| 574 | debug_addr, | ||
| 575 | debug_names, | ||
| 576 | debug_frame, | ||
| 577 | eh_frame, | ||
| 578 | eh_frame_hdr, | ||
| 579 | }; | ||
| 580 | |||
| 581 | pub const DwarfInfo = struct { | ||
| 582 | pub const Section = struct { | ||
| 583 | data: []const u8, | ||
| 584 | // Module-relative virtual address. | ||
| 585 | // Only set if the section data was loaded from disk. | ||
| 586 | virtual_address: ?usize = null, | ||
| 587 | // If `data` is owned by this DwarfInfo. | ||
| 588 | owned: bool, | ||
| 589 | |||
| 590 | // For sections that are not memory mapped by the loader, this is an offset | ||
| 591 | // from `data.ptr` to where the section would have been mapped. Otherwise, | ||
| 592 | // `data` is directly backed by the section and the offset is zero. | ||
| 593 | pub fn virtualOffset(self: Section, base_address: usize) i64 { | ||
| 594 | return if (self.virtual_address) |va| | ||
| 595 | @as(i64, @intCast(base_address + va)) - | ||
| 596 | @as(i64, @intCast(@intFromPtr(self.data.ptr))) | ||
| 597 | else | ||
| 598 | 0; | ||
| 599 | } | ||
| 600 | }; | ||
| 601 | |||
| 602 | const num_sections = std.enums.directEnumArrayLen(DwarfSection, 0); | ||
| 603 | pub const SectionArray = [num_sections]?Section; | ||
| 604 | pub const null_section_array = [_]?Section{null} ** num_sections; | ||
| 605 | |||
| 606 | endian: std.builtin.Endian, | ||
| 607 | sections: SectionArray = null_section_array, | ||
| 608 | is_macho: bool, | ||
| 609 | |||
| 610 | // Filled later by the initializer | ||
| 611 | abbrev_table_list: std.ArrayListUnmanaged(Abbrev.Table) = .{}, | ||
| 612 | compile_unit_list: std.ArrayListUnmanaged(CompileUnit) = .{}, | ||
| 613 | func_list: std.ArrayListUnmanaged(Func) = .{}, | ||
| 614 | |||
| 615 | eh_frame_hdr: ?ExceptionFrameHeader = null, | ||
| 616 | // These lookup tables are only used if `eh_frame_hdr` is null | ||
| 617 | cie_map: std.AutoArrayHashMapUnmanaged(u64, CommonInformationEntry) = .{}, | ||
| 618 | // Sorted by start_pc | ||
| 619 | fde_list: std.ArrayListUnmanaged(FrameDescriptionEntry) = .{}, | ||
| 620 | |||
| 621 | pub fn section(di: DwarfInfo, dwarf_section: DwarfSection) ?[]const u8 { | ||
| 622 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.data else null; | ||
| 623 | } | ||
| 624 | |||
| 625 | pub fn sectionVirtualOffset(di: DwarfInfo, dwarf_section: DwarfSection, base_address: usize) ?i64 { | ||
| 626 | return if (di.sections[@intFromEnum(dwarf_section)]) |s| s.virtualOffset(base_address) else null; | ||
| 627 | } | ||
| 628 | |||
| 629 | pub fn deinit(di: *DwarfInfo, allocator: mem.Allocator) void { | ||
| 630 | for (di.sections) |opt_section| { | ||
| 631 | if (opt_section) |s| if (s.owned) allocator.free(s.data); | ||
| 632 | } | ||
| 633 | for (di.abbrev_table_list.items) |*abbrev| { | ||
| 634 | abbrev.deinit(allocator); | ||
| 635 | } | ||
| 636 | di.abbrev_table_list.deinit(allocator); | ||
| 637 | for (di.compile_unit_list.items) |*cu| { | ||
| 638 | cu.die.deinit(allocator); | ||
| 639 | } | ||
| 640 | di.compile_unit_list.deinit(allocator); | ||
| 641 | di.func_list.deinit(allocator); | ||
| 642 | di.cie_map.deinit(allocator); | ||
| 643 | di.fde_list.deinit(allocator); | ||
| 644 | di.* = undefined; | ||
| 645 | } | ||
| 646 | |||
| 647 | pub fn getSymbolName(di: *DwarfInfo, address: u64) ?[]const u8 { | ||
| 648 | for (di.func_list.items) |*func| { | ||
| 649 | if (func.pc_range) |range| { | ||
| 650 | if (address >= range.start and address < range.end) { | ||
| 651 | return func.name; | ||
| 652 | } | ||
| 653 | } | ||
| 654 | } | ||
| 655 | |||
| 656 | return null; | ||
| 657 | } | ||
| 658 | |||
| 659 | fn scanAllFunctions(di: *DwarfInfo, allocator: mem.Allocator) !void { | ||
| 660 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | ||
| 661 | var this_unit_offset: u64 = 0; | ||
| 662 | |||
| 663 | while (this_unit_offset < fbr.buf.len) { | ||
| 664 | try fbr.seekTo(this_unit_offset); | ||
| 665 | |||
| 666 | const unit_header = try readUnitHeader(&fbr, null); | ||
| 667 | if (unit_header.unit_length == 0) return; | ||
| 668 | const next_offset = unit_header.header_length + unit_header.unit_length; | ||
| 669 | |||
| 670 | const version = try fbr.readInt(u16); | ||
| 671 | if (version < 2 or version > 5) return badDwarf(); | ||
| 672 | |||
| 673 | var address_size: u8 = undefined; | ||
| 674 | var debug_abbrev_offset: u64 = undefined; | ||
| 675 | if (version >= 5) { | ||
| 676 | const unit_type = try fbr.readInt(u8); | ||
| 677 | if (unit_type != UT.compile) return badDwarf(); | ||
| 678 | address_size = try fbr.readByte(); | ||
| 679 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | ||
| 680 | } else { | ||
| 681 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | ||
| 682 | address_size = try fbr.readByte(); | ||
| 683 | } | ||
| 684 | if (address_size != @sizeOf(usize)) return badDwarf(); | ||
| 685 | |||
| 686 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); | ||
| 687 | |||
| 688 | var max_attrs: usize = 0; | ||
| 689 | var zig_padding_abbrev_code: u7 = 0; | ||
| 690 | for (abbrev_table.abbrevs) |abbrev| { | ||
| 691 | max_attrs = @max(max_attrs, abbrev.attrs.len); | ||
| 692 | if (math.cast(u7, abbrev.code)) |code| { | ||
| 693 | if (abbrev.tag_id == TAG.ZIG_padding and | ||
| 694 | !abbrev.has_children and | ||
| 695 | abbrev.attrs.len == 0) | ||
| 696 | { | ||
| 697 | zig_padding_abbrev_code = code; | ||
| 698 | } | ||
| 699 | } | ||
| 700 | } | ||
| 701 | const attrs_buf = try allocator.alloc(Die.Attr, max_attrs * 3); | ||
| 702 | defer allocator.free(attrs_buf); | ||
| 703 | var attrs_bufs: [3][]Die.Attr = undefined; | ||
| 704 | for (&attrs_bufs, 0..) |*buf, index| buf.* = attrs_buf[index * max_attrs ..][0..max_attrs]; | ||
| 705 | |||
| 706 | const next_unit_pos = this_unit_offset + next_offset; | ||
| 707 | |||
| 708 | var compile_unit: CompileUnit = .{ | ||
| 709 | .version = version, | ||
| 710 | .format = unit_header.format, | ||
| 711 | .die = undefined, | ||
| 712 | .pc_range = null, | ||
| 713 | |||
| 714 | .str_offsets_base = 0, | ||
| 715 | .addr_base = 0, | ||
| 716 | .rnglists_base = 0, | ||
| 717 | .loclists_base = 0, | ||
| 718 | .frame_base = null, | ||
| 719 | }; | ||
| 720 | |||
| 721 | while (true) { | ||
| 722 | fbr.pos = mem.indexOfNonePos(u8, fbr.buf, fbr.pos, &.{ | ||
| 723 | zig_padding_abbrev_code, 0, | ||
| 724 | }) orelse fbr.buf.len; | ||
| 725 | if (fbr.pos >= next_unit_pos) break; | ||
| 726 | var die_obj = (try parseDie( | ||
| 727 | &fbr, | ||
| 728 | attrs_bufs[0], | ||
| 729 | abbrev_table, | ||
| 730 | unit_header.format, | ||
| 731 | )) orelse continue; | ||
| 732 | |||
| 733 | switch (die_obj.tag_id) { | ||
| 734 | TAG.compile_unit => { | ||
| 735 | compile_unit.die = die_obj; | ||
| 736 | compile_unit.die.attrs = attrs_bufs[1][0..die_obj.attrs.len]; | ||
| 737 | @memcpy(compile_unit.die.attrs, die_obj.attrs); | ||
| 738 | |||
| 739 | compile_unit.str_offsets_base = if (die_obj.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0; | ||
| 740 | compile_unit.addr_base = if (die_obj.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0; | ||
| 741 | compile_unit.rnglists_base = if (die_obj.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0; | ||
| 742 | compile_unit.loclists_base = if (die_obj.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0; | ||
| 743 | compile_unit.frame_base = die_obj.getAttr(AT.frame_base); | ||
| 744 | }, | ||
| 745 | TAG.subprogram, TAG.inlined_subroutine, TAG.subroutine, TAG.entry_point => { | ||
| 746 | const fn_name = x: { | ||
| 747 | var this_die_obj = die_obj; | ||
| 748 | // Prevent endless loops | ||
| 749 | for (0..3) |_| { | ||
| 750 | if (this_die_obj.getAttr(AT.name)) |_| { | ||
| 751 | break :x try this_die_obj.getAttrString(di, AT.name, di.section(.debug_str), compile_unit); | ||
| 752 | } else if (this_die_obj.getAttr(AT.abstract_origin)) |_| { | ||
| 753 | const after_die_offset = fbr.pos; | ||
| 754 | defer fbr.pos = after_die_offset; | ||
| 755 | |||
| 756 | // Follow the DIE it points to and repeat | ||
| 757 | const ref_offset = try this_die_obj.getAttrRef(AT.abstract_origin); | ||
| 758 | if (ref_offset > next_offset) return badDwarf(); | ||
| 759 | try fbr.seekTo(this_unit_offset + ref_offset); | ||
| 760 | this_die_obj = (try parseDie( | ||
| 761 | &fbr, | ||
| 762 | attrs_bufs[2], | ||
| 763 | abbrev_table, | ||
| 764 | unit_header.format, | ||
| 765 | )) orelse return badDwarf(); | ||
| 766 | } else if (this_die_obj.getAttr(AT.specification)) |_| { | ||
| 767 | const after_die_offset = fbr.pos; | ||
| 768 | defer fbr.pos = after_die_offset; | ||
| 769 | |||
| 770 | // Follow the DIE it points to and repeat | ||
| 771 | const ref_offset = try this_die_obj.getAttrRef(AT.specification); | ||
| 772 | if (ref_offset > next_offset) return badDwarf(); | ||
| 773 | try fbr.seekTo(this_unit_offset + ref_offset); | ||
| 774 | this_die_obj = (try parseDie( | ||
| 775 | &fbr, | ||
| 776 | attrs_bufs[2], | ||
| 777 | abbrev_table, | ||
| 778 | unit_header.format, | ||
| 779 | )) orelse return badDwarf(); | ||
| 780 | } else { | ||
| 781 | break :x null; | ||
| 782 | } | ||
| 783 | } | ||
| 784 | |||
| 785 | break :x null; | ||
| 786 | }; | ||
| 787 | |||
| 788 | var range_added = if (die_obj.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| blk: { | ||
| 789 | if (die_obj.getAttr(AT.high_pc)) |high_pc_value| { | ||
| 790 | const pc_end = switch (high_pc_value.*) { | ||
| 791 | .addr => |value| value, | ||
| 792 | .udata => |offset| low_pc + offset, | ||
| 793 | else => return badDwarf(), | ||
| 794 | }; | ||
| 795 | |||
| 796 | try di.func_list.append(allocator, .{ | ||
| 797 | .name = fn_name, | ||
| 798 | .pc_range = .{ | ||
| 799 | .start = low_pc, | ||
| 800 | .end = pc_end, | ||
| 801 | }, | ||
| 802 | }); | ||
| 803 | |||
| 804 | break :blk true; | ||
| 805 | } | ||
| 806 | |||
| 807 | break :blk false; | ||
| 808 | } else |err| blk: { | ||
| 809 | if (err != error.MissingDebugInfo) return err; | ||
| 810 | break :blk false; | ||
| 811 | }; | ||
| 812 | |||
| 813 | if (die_obj.getAttr(AT.ranges)) |ranges_value| blk: { | ||
| 814 | var iter = DebugRangeIterator.init(ranges_value, di, &compile_unit) catch |err| { | ||
| 815 | if (err != error.MissingDebugInfo) return err; | ||
| 816 | break :blk; | ||
| 817 | }; | ||
| 818 | |||
| 819 | while (try iter.next()) |range| { | ||
| 820 | range_added = true; | ||
| 821 | try di.func_list.append(allocator, .{ | ||
| 822 | .name = fn_name, | ||
| 823 | .pc_range = .{ | ||
| 824 | .start = range.start_addr, | ||
| 825 | .end = range.end_addr, | ||
| 826 | }, | ||
| 827 | }); | ||
| 828 | } | ||
| 829 | } | ||
| 830 | |||
| 831 | if (fn_name != null and !range_added) { | ||
| 832 | try di.func_list.append(allocator, .{ | ||
| 833 | .name = fn_name, | ||
| 834 | .pc_range = null, | ||
| 835 | }); | ||
| 836 | } | ||
| 837 | }, | ||
| 838 | else => {}, | ||
| 839 | } | ||
| 840 | } | ||
| 841 | |||
| 842 | this_unit_offset += next_offset; | ||
| 843 | } | ||
| 844 | } | ||
| 845 | |||
| 846 | fn scanAllCompileUnits(di: *DwarfInfo, allocator: mem.Allocator) !void { | ||
| 847 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_info).?, .endian = di.endian }; | ||
| 848 | var this_unit_offset: u64 = 0; | ||
| 849 | |||
| 850 | var attrs_buf = std.ArrayList(Die.Attr).init(allocator); | ||
| 851 | defer attrs_buf.deinit(); | ||
| 852 | |||
| 853 | while (this_unit_offset < fbr.buf.len) { | ||
| 854 | try fbr.seekTo(this_unit_offset); | ||
| 855 | |||
| 856 | const unit_header = try readUnitHeader(&fbr, null); | ||
| 857 | if (unit_header.unit_length == 0) return; | ||
| 858 | const next_offset = unit_header.header_length + unit_header.unit_length; | ||
| 859 | |||
| 860 | const version = try fbr.readInt(u16); | ||
| 861 | if (version < 2 or version > 5) return badDwarf(); | ||
| 862 | |||
| 863 | var address_size: u8 = undefined; | ||
| 864 | var debug_abbrev_offset: u64 = undefined; | ||
| 865 | if (version >= 5) { | ||
| 866 | const unit_type = try fbr.readInt(u8); | ||
| 867 | if (unit_type != UT.compile) return badDwarf(); | ||
| 868 | address_size = try fbr.readByte(); | ||
| 869 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | ||
| 870 | } else { | ||
| 871 | debug_abbrev_offset = try fbr.readAddress(unit_header.format); | ||
| 872 | address_size = try fbr.readByte(); | ||
| 873 | } | ||
| 874 | if (address_size != @sizeOf(usize)) return badDwarf(); | ||
| 875 | |||
| 876 | const abbrev_table = try di.getAbbrevTable(allocator, debug_abbrev_offset); | ||
| 877 | |||
| 878 | var max_attrs: usize = 0; | ||
| 879 | for (abbrev_table.abbrevs) |abbrev| { | ||
| 880 | max_attrs = @max(max_attrs, abbrev.attrs.len); | ||
| 881 | } | ||
| 882 | try attrs_buf.resize(max_attrs); | ||
| 883 | |||
| 884 | var compile_unit_die = (try parseDie( | ||
| 885 | &fbr, | ||
| 886 | attrs_buf.items, | ||
| 887 | abbrev_table, | ||
| 888 | unit_header.format, | ||
| 889 | )) orelse return badDwarf(); | ||
| 890 | |||
| 891 | if (compile_unit_die.tag_id != TAG.compile_unit) return badDwarf(); | ||
| 892 | |||
| 893 | compile_unit_die.attrs = try allocator.dupe(Die.Attr, compile_unit_die.attrs); | ||
| 894 | |||
| 895 | var compile_unit: CompileUnit = .{ | ||
| 896 | .version = version, | ||
| 897 | .format = unit_header.format, | ||
| 898 | .pc_range = null, | ||
| 899 | .die = compile_unit_die, | ||
| 900 | .str_offsets_base = if (compile_unit_die.getAttr(AT.str_offsets_base)) |fv| try fv.getUInt(usize) else 0, | ||
| 901 | .addr_base = if (compile_unit_die.getAttr(AT.addr_base)) |fv| try fv.getUInt(usize) else 0, | ||
| 902 | .rnglists_base = if (compile_unit_die.getAttr(AT.rnglists_base)) |fv| try fv.getUInt(usize) else 0, | ||
| 903 | .loclists_base = if (compile_unit_die.getAttr(AT.loclists_base)) |fv| try fv.getUInt(usize) else 0, | ||
| 904 | .frame_base = compile_unit_die.getAttr(AT.frame_base), | ||
| 905 | }; | ||
| 906 | |||
| 907 | compile_unit.pc_range = x: { | ||
| 908 | if (compile_unit_die.getAttrAddr(di, AT.low_pc, compile_unit)) |low_pc| { | ||
| 909 | if (compile_unit_die.getAttr(AT.high_pc)) |high_pc_value| { | ||
| 910 | const pc_end = switch (high_pc_value.*) { | ||
| 911 | .addr => |value| value, | ||
| 912 | .udata => |offset| low_pc + offset, | ||
| 913 | else => return badDwarf(), | ||
| 914 | }; | ||
| 915 | break :x PcRange{ | ||
| 916 | .start = low_pc, | ||
| 917 | .end = pc_end, | ||
| 918 | }; | ||
| 919 | } else { | ||
| 920 | break :x null; | ||
| 921 | } | ||
| 922 | } else |err| { | ||
| 923 | if (err != error.MissingDebugInfo) return err; | ||
| 924 | break :x null; | ||
| 925 | } | ||
| 926 | }; | ||
| 927 | |||
| 928 | try di.compile_unit_list.append(allocator, compile_unit); | ||
| 929 | |||
| 930 | this_unit_offset += next_offset; | ||
| 931 | } | ||
| 932 | } | ||
| 933 | |||
| 934 | const DebugRangeIterator = struct { | ||
| 935 | base_address: u64, | ||
| 936 | section_type: DwarfSection, | ||
| 937 | di: *const DwarfInfo, | ||
| 938 | compile_unit: *const CompileUnit, | ||
| 939 | fbr: FixedBufferReader, | ||
| 940 | |||
| 941 | pub fn init(ranges_value: *const FormValue, di: *const DwarfInfo, compile_unit: *const CompileUnit) !@This() { | ||
| 942 | const section_type = if (compile_unit.version >= 5) DwarfSection.debug_rnglists else DwarfSection.debug_ranges; | ||
| 943 | const debug_ranges = di.section(section_type) orelse return error.MissingDebugInfo; | ||
| 944 | |||
| 945 | const ranges_offset = switch (ranges_value.*) { | ||
| 946 | .sec_offset, .udata => |off| off, | ||
| 947 | .rnglistx => |idx| off: { | ||
| 948 | switch (compile_unit.format) { | ||
| 949 | .@"32" => { | ||
| 950 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 4 * idx)); | ||
| 951 | if (offset_loc + 4 > debug_ranges.len) return badDwarf(); | ||
| 952 | const offset = mem.readInt(u32, debug_ranges[offset_loc..][0..4], di.endian); | ||
| 953 | break :off compile_unit.rnglists_base + offset; | ||
| 954 | }, | ||
| 955 | .@"64" => { | ||
| 956 | const offset_loc = @as(usize, @intCast(compile_unit.rnglists_base + 8 * idx)); | ||
| 957 | if (offset_loc + 8 > debug_ranges.len) return badDwarf(); | ||
| 958 | const offset = mem.readInt(u64, debug_ranges[offset_loc..][0..8], di.endian); | ||
| 959 | break :off compile_unit.rnglists_base + offset; | ||
| 960 | }, | ||
| 961 | } | ||
| 962 | }, | ||
| 963 | else => return badDwarf(), | ||
| 964 | }; | ||
| 965 | |||
| 966 | // All the addresses in the list are relative to the value | ||
| 967 | // specified by DW_AT.low_pc or to some other value encoded | ||
| 968 | // in the list itself. | ||
| 969 | // If no starting value is specified use zero. | ||
| 970 | const base_address = compile_unit.die.getAttrAddr(di, AT.low_pc, compile_unit.*) catch |err| switch (err) { | ||
| 971 | error.MissingDebugInfo => 0, | ||
| 972 | else => return err, | ||
| 973 | }; | ||
| 974 | |||
| 975 | return .{ | ||
| 976 | .base_address = base_address, | ||
| 977 | .section_type = section_type, | ||
| 978 | .di = di, | ||
| 979 | .compile_unit = compile_unit, | ||
| 980 | .fbr = .{ | ||
| 981 | .buf = debug_ranges, | ||
| 982 | .pos = math.cast(usize, ranges_offset) orelse return badDwarf(), | ||
| 983 | .endian = di.endian, | ||
| 984 | }, | ||
| 985 | }; | ||
| 986 | } | ||
| 987 | |||
| 988 | // Returns the next range in the list, or null if the end was reached. | ||
| 989 | pub fn next(self: *@This()) !?struct { start_addr: u64, end_addr: u64 } { | ||
| 990 | switch (self.section_type) { | ||
| 991 | .debug_rnglists => { | ||
| 992 | const kind = try self.fbr.readByte(); | ||
| 993 | switch (kind) { | ||
| 994 | RLE.end_of_list => return null, | ||
| 995 | RLE.base_addressx => { | ||
| 996 | const index = try self.fbr.readUleb128(usize); | ||
| 997 | self.base_address = try self.di.readDebugAddr(self.compile_unit.*, index); | ||
| 998 | return try self.next(); | ||
| 999 | }, | ||
| 1000 | RLE.startx_endx => { | ||
| 1001 | const start_index = try self.fbr.readUleb128(usize); | ||
| 1002 | const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); | ||
| 1003 | |||
| 1004 | const end_index = try self.fbr.readUleb128(usize); | ||
| 1005 | const end_addr = try self.di.readDebugAddr(self.compile_unit.*, end_index); | ||
| 1006 | |||
| 1007 | return .{ | ||
| 1008 | .start_addr = start_addr, | ||
| 1009 | .end_addr = end_addr, | ||
| 1010 | }; | ||
| 1011 | }, | ||
| 1012 | RLE.startx_length => { | ||
| 1013 | const start_index = try self.fbr.readUleb128(usize); | ||
| 1014 | const start_addr = try self.di.readDebugAddr(self.compile_unit.*, start_index); | ||
| 1015 | |||
| 1016 | const len = try self.fbr.readUleb128(usize); | ||
| 1017 | const end_addr = start_addr + len; | ||
| 1018 | |||
| 1019 | return .{ | ||
| 1020 | .start_addr = start_addr, | ||
| 1021 | .end_addr = end_addr, | ||
| 1022 | }; | ||
| 1023 | }, | ||
| 1024 | RLE.offset_pair => { | ||
| 1025 | const start_addr = try self.fbr.readUleb128(usize); | ||
| 1026 | const end_addr = try self.fbr.readUleb128(usize); | ||
| 1027 | |||
| 1028 | // This is the only kind that uses the base address | ||
| 1029 | return .{ | ||
| 1030 | .start_addr = self.base_address + start_addr, | ||
| 1031 | .end_addr = self.base_address + end_addr, | ||
| 1032 | }; | ||
| 1033 | }, | ||
| 1034 | RLE.base_address => { | ||
| 1035 | self.base_address = try self.fbr.readInt(usize); | ||
| 1036 | return try self.next(); | ||
| 1037 | }, | ||
| 1038 | RLE.start_end => { | ||
| 1039 | const start_addr = try self.fbr.readInt(usize); | ||
| 1040 | const end_addr = try self.fbr.readInt(usize); | ||
| 1041 | |||
| 1042 | return .{ | ||
| 1043 | .start_addr = start_addr, | ||
| 1044 | .end_addr = end_addr, | ||
| 1045 | }; | ||
| 1046 | }, | ||
| 1047 | RLE.start_length => { | ||
| 1048 | const start_addr = try self.fbr.readInt(usize); | ||
| 1049 | const len = try self.fbr.readUleb128(usize); | ||
| 1050 | const end_addr = start_addr + len; | ||
| 1051 | |||
| 1052 | return .{ | ||
| 1053 | .start_addr = start_addr, | ||
| 1054 | .end_addr = end_addr, | ||
| 1055 | }; | ||
| 1056 | }, | ||
| 1057 | else => return badDwarf(), | ||
| 1058 | } | ||
| 1059 | }, | ||
| 1060 | .debug_ranges => { | ||
| 1061 | const start_addr = try self.fbr.readInt(usize); | ||
| 1062 | const end_addr = try self.fbr.readInt(usize); | ||
| 1063 | if (start_addr == 0 and end_addr == 0) return null; | ||
| 1064 | |||
| 1065 | // This entry selects a new value for the base address | ||
| 1066 | if (start_addr == math.maxInt(usize)) { | ||
| 1067 | self.base_address = end_addr; | ||
| 1068 | return try self.next(); | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | return .{ | ||
| 1072 | .start_addr = self.base_address + start_addr, | ||
| 1073 | .end_addr = self.base_address + end_addr, | ||
| 1074 | }; | ||
| 1075 | }, | ||
| 1076 | else => unreachable, | ||
| 1077 | } | ||
| 1078 | } | ||
| 1079 | }; | ||
| 1080 | |||
| 1081 | pub fn findCompileUnit(di: *const DwarfInfo, target_address: u64) !*const CompileUnit { | ||
| 1082 | for (di.compile_unit_list.items) |*compile_unit| { | ||
| 1083 | if (compile_unit.pc_range) |range| { | ||
| 1084 | if (target_address >= range.start and target_address < range.end) return compile_unit; | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | const ranges_value = compile_unit.die.getAttr(AT.ranges) orelse continue; | ||
| 1088 | var iter = DebugRangeIterator.init(ranges_value, di, compile_unit) catch continue; | ||
| 1089 | while (try iter.next()) |range| { | ||
| 1090 | if (target_address >= range.start_addr and target_address < range.end_addr) return compile_unit; | ||
| 1091 | } | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | return missingDwarf(); | ||
| 1095 | } | ||
| 1096 | |||
| 1097 | /// Gets an already existing AbbrevTable given the abbrev_offset, or if not found, | ||
| 1098 | /// seeks in the stream and parses it. | ||
| 1099 | fn getAbbrevTable(di: *DwarfInfo, allocator: mem.Allocator, abbrev_offset: u64) !*const Abbrev.Table { | ||
| 1100 | for (di.abbrev_table_list.items) |*table| { | ||
| 1101 | if (table.offset == abbrev_offset) { | ||
| 1102 | return table; | ||
| 1103 | } | ||
| 1104 | } | ||
| 1105 | try di.abbrev_table_list.append( | ||
| 1106 | allocator, | ||
| 1107 | try di.parseAbbrevTable(allocator, abbrev_offset), | ||
| 1108 | ); | ||
| 1109 | return &di.abbrev_table_list.items[di.abbrev_table_list.items.len - 1]; | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | fn parseAbbrevTable(di: *DwarfInfo, allocator: mem.Allocator, offset: u64) !Abbrev.Table { | ||
| 1113 | var fbr: FixedBufferReader = .{ | ||
| 1114 | .buf = di.section(.debug_abbrev).?, | ||
| 1115 | .pos = math.cast(usize, offset) orelse return badDwarf(), | ||
| 1116 | .endian = di.endian, | ||
| 1117 | }; | ||
| 1118 | |||
| 1119 | var abbrevs = std.ArrayList(Abbrev).init(allocator); | ||
| 1120 | defer { | ||
| 1121 | for (abbrevs.items) |*abbrev| { | ||
| 1122 | abbrev.deinit(allocator); | ||
| 1123 | } | ||
| 1124 | abbrevs.deinit(); | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | var attrs = std.ArrayList(Abbrev.Attr).init(allocator); | ||
| 1128 | defer attrs.deinit(); | ||
| 1129 | |||
| 1130 | while (true) { | ||
| 1131 | const code = try fbr.readUleb128(u64); | ||
| 1132 | if (code == 0) break; | ||
| 1133 | const tag_id = try fbr.readUleb128(u64); | ||
| 1134 | const has_children = (try fbr.readByte()) == CHILDREN.yes; | ||
| 1135 | |||
| 1136 | while (true) { | ||
| 1137 | const attr_id = try fbr.readUleb128(u64); | ||
| 1138 | const form_id = try fbr.readUleb128(u64); | ||
| 1139 | if (attr_id == 0 and form_id == 0) break; | ||
| 1140 | try attrs.append(.{ | ||
| 1141 | .id = attr_id, | ||
| 1142 | .form_id = form_id, | ||
| 1143 | .payload = switch (form_id) { | ||
| 1144 | FORM.implicit_const => try fbr.readIleb128(i64), | ||
| 1145 | else => undefined, | ||
| 1146 | }, | ||
| 1147 | }); | ||
| 1148 | } | ||
| 1149 | |||
| 1150 | try abbrevs.append(.{ | ||
| 1151 | .code = code, | ||
| 1152 | .tag_id = tag_id, | ||
| 1153 | .has_children = has_children, | ||
| 1154 | .attrs = try attrs.toOwnedSlice(), | ||
| 1155 | }); | ||
| 1156 | } | ||
| 1157 | |||
| 1158 | return .{ | ||
| 1159 | .offset = offset, | ||
| 1160 | .abbrevs = try abbrevs.toOwnedSlice(), | ||
| 1161 | }; | ||
| 1162 | } | ||
| 1163 | |||
| 1164 | fn parseDie( | ||
| 1165 | fbr: *FixedBufferReader, | ||
| 1166 | attrs_buf: []Die.Attr, | ||
| 1167 | abbrev_table: *const Abbrev.Table, | ||
| 1168 | format: Format, | ||
| 1169 | ) !?Die { | ||
| 1170 | const abbrev_code = try fbr.readUleb128(u64); | ||
| 1171 | if (abbrev_code == 0) return null; | ||
| 1172 | const table_entry = abbrev_table.get(abbrev_code) orelse return badDwarf(); | ||
| 1173 | |||
| 1174 | const attrs = attrs_buf[0..table_entry.attrs.len]; | ||
| 1175 | for (attrs, table_entry.attrs) |*result_attr, attr| result_attr.* = Die.Attr{ | ||
| 1176 | .id = attr.id, | ||
| 1177 | .value = try parseFormValue( | ||
| 1178 | fbr, | ||
| 1179 | attr.form_id, | ||
| 1180 | format, | ||
| 1181 | attr.payload, | ||
| 1182 | ), | ||
| 1183 | }; | ||
| 1184 | return .{ | ||
| 1185 | .tag_id = table_entry.tag_id, | ||
| 1186 | .has_children = table_entry.has_children, | ||
| 1187 | .attrs = attrs, | ||
| 1188 | }; | ||
| 1189 | } | ||
| 1190 | |||
| 1191 | pub fn getLineNumberInfo( | ||
| 1192 | di: *DwarfInfo, | ||
| 1193 | allocator: mem.Allocator, | ||
| 1194 | compile_unit: CompileUnit, | ||
| 1195 | target_address: u64, | ||
| 1196 | ) !debug.LineInfo { | ||
| 1197 | const compile_unit_cwd = try compile_unit.die.getAttrString(di, AT.comp_dir, di.section(.debug_line_str), compile_unit); | ||
| 1198 | const line_info_offset = try compile_unit.die.getAttrSecOffset(AT.stmt_list); | ||
| 1199 | |||
| 1200 | var fbr: FixedBufferReader = .{ .buf = di.section(.debug_line).?, .endian = di.endian }; | ||
| 1201 | try fbr.seekTo(line_info_offset); | ||
| 1202 | |||
| 1203 | const unit_header = try readUnitHeader(&fbr, null); | ||
| 1204 | if (unit_header.unit_length == 0) return missingDwarf(); | ||
| 1205 | const next_offset = unit_header.header_length + unit_header.unit_length; | ||
| 1206 | |||
| 1207 | const version = try fbr.readInt(u16); | ||
| 1208 | if (version < 2) return badDwarf(); | ||
| 1209 | |||
| 1210 | var addr_size: u8 = switch (unit_header.format) { | ||
| 1211 | .@"32" => 4, | ||
| 1212 | .@"64" => 8, | ||
| 1213 | }; | ||
| 1214 | var seg_size: u8 = 0; | ||
| 1215 | if (version >= 5) { | ||
| 1216 | addr_size = try fbr.readByte(); | ||
| 1217 | seg_size = try fbr.readByte(); | ||
| 1218 | } | ||
| 1219 | |||
| 1220 | const prologue_length = try fbr.readAddress(unit_header.format); | ||
| 1221 | const prog_start_offset = fbr.pos + prologue_length; | ||
| 1222 | |||
| 1223 | const minimum_instruction_length = try fbr.readByte(); | ||
| 1224 | if (minimum_instruction_length == 0) return badDwarf(); | ||
| 1225 | |||
| 1226 | if (version >= 4) { | ||
| 1227 | // maximum_operations_per_instruction | ||
| 1228 | _ = try fbr.readByte(); | ||
| 1229 | } | ||
| 1230 | |||
| 1231 | const default_is_stmt = (try fbr.readByte()) != 0; | ||
| 1232 | const line_base = try fbr.readByteSigned(); | ||
| 1233 | |||
| 1234 | const line_range = try fbr.readByte(); | ||
| 1235 | if (line_range == 0) return badDwarf(); | ||
| 1236 | |||
| 1237 | const opcode_base = try fbr.readByte(); | ||
| 1238 | |||
| 1239 | const standard_opcode_lengths = try fbr.readBytes(opcode_base - 1); | ||
| 1240 | |||
| 1241 | var include_directories = std.ArrayList(FileEntry).init(allocator); | ||
| 1242 | defer include_directories.deinit(); | ||
| 1243 | var file_entries = std.ArrayList(FileEntry).init(allocator); | ||
| 1244 | defer file_entries.deinit(); | ||
| 1245 | |||
| 1246 | if (version < 5) { | ||
| 1247 | try include_directories.append(.{ .path = compile_unit_cwd }); | ||
| 1248 | |||
| 1249 | while (true) { | ||
| 1250 | const dir = try fbr.readBytesTo(0); | ||
| 1251 | if (dir.len == 0) break; | ||
| 1252 | try include_directories.append(.{ .path = dir }); | ||
| 1253 | } | ||
| 1254 | |||
| 1255 | while (true) { | ||
| 1256 | const file_name = try fbr.readBytesTo(0); | ||
| 1257 | if (file_name.len == 0) break; | ||
| 1258 | const dir_index = try fbr.readUleb128(u32); | ||
| 1259 | const mtime = try fbr.readUleb128(u64); | ||
| 1260 | const size = try fbr.readUleb128(u64); | ||
| 1261 | try file_entries.append(.{ | ||
| 1262 | .path = file_name, | ||
| 1263 | .dir_index = dir_index, | ||
| 1264 | .mtime = mtime, | ||
| 1265 | .size = size, | ||
| 1266 | }); | ||
| 1267 | } | ||
| 1268 | } else { | ||
| 1269 | const FileEntFmt = struct { | ||
| 1270 | content_type_code: u8, | ||
| 1271 | form_code: u16, | ||
| 1272 | }; | ||
| 1273 | { | ||
| 1274 | var dir_ent_fmt_buf: [10]FileEntFmt = undefined; | ||
| 1275 | const directory_entry_format_count = try fbr.readByte(); | ||
| 1276 | if (directory_entry_format_count > dir_ent_fmt_buf.len) return badDwarf(); | ||
| 1277 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |*ent_fmt| { | ||
| 1278 | ent_fmt.* = .{ | ||
| 1279 | .content_type_code = try fbr.readUleb128(u8), | ||
| 1280 | .form_code = try fbr.readUleb128(u16), | ||
| 1281 | }; | ||
| 1282 | } | ||
| 1283 | |||
| 1284 | const directories_count = try fbr.readUleb128(usize); | ||
| 1285 | try include_directories.ensureUnusedCapacity(directories_count); | ||
| 1286 | { | ||
| 1287 | var i: usize = 0; | ||
| 1288 | while (i < directories_count) : (i += 1) { | ||
| 1289 | var e: FileEntry = .{ .path = &.{} }; | ||
| 1290 | for (dir_ent_fmt_buf[0..directory_entry_format_count]) |ent_fmt| { | ||
| 1291 | const form_value = try parseFormValue( | ||
| 1292 | &fbr, | ||
| 1293 | ent_fmt.form_code, | ||
| 1294 | unit_header.format, | ||
| 1295 | null, | ||
| 1296 | ); | ||
| 1297 | switch (ent_fmt.content_type_code) { | ||
| 1298 | LNCT.path => e.path = try form_value.getString(di.*), | ||
| 1299 | LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), | ||
| 1300 | LNCT.timestamp => e.mtime = try form_value.getUInt(u64), | ||
| 1301 | LNCT.size => e.size = try form_value.getUInt(u64), | ||
| 1302 | LNCT.MD5 => e.md5 = switch (form_value) { | ||
| 1303 | .data16 => |data16| data16.*, | ||
| 1304 | else => return badDwarf(), | ||
| 1305 | }, | ||
| 1306 | else => continue, | ||
| 1307 | } | ||
| 1308 | } | ||
| 1309 | include_directories.appendAssumeCapacity(e); | ||
| 1310 | } | ||
| 1311 | } | ||
| 1312 | } | ||
| 1313 | |||
| 1314 | var file_ent_fmt_buf: [10]FileEntFmt = undefined; | ||
| 1315 | const file_name_entry_format_count = try fbr.readByte(); | ||
| 1316 | if (file_name_entry_format_count > file_ent_fmt_buf.len) return badDwarf(); | ||
| 1317 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |*ent_fmt| { | ||
| 1318 | ent_fmt.* = .{ | ||
| 1319 | .content_type_code = try fbr.readUleb128(u8), | ||
| 1320 | .form_code = try fbr.readUleb128(u16), | ||
| 1321 | }; | ||
| 1322 | } | ||
| 1323 | |||
| 1324 | const file_names_count = try fbr.readUleb128(usize); | ||
| 1325 | try file_entries.ensureUnusedCapacity(file_names_count); | ||
| 1326 | { | ||
| 1327 | var i: usize = 0; | ||
| 1328 | while (i < file_names_count) : (i += 1) { | ||
| 1329 | var e: FileEntry = .{ .path = &.{} }; | ||
| 1330 | for (file_ent_fmt_buf[0..file_name_entry_format_count]) |ent_fmt| { | ||
| 1331 | const form_value = try parseFormValue( | ||
| 1332 | &fbr, | ||
| 1333 | ent_fmt.form_code, | ||
| 1334 | unit_header.format, | ||
| 1335 | null, | ||
| 1336 | ); | ||
| 1337 | switch (ent_fmt.content_type_code) { | ||
| 1338 | LNCT.path => e.path = try form_value.getString(di.*), | ||
| 1339 | LNCT.directory_index => e.dir_index = try form_value.getUInt(u32), | ||
| 1340 | LNCT.timestamp => e.mtime = try form_value.getUInt(u64), | ||
| 1341 | LNCT.size => e.size = try form_value.getUInt(u64), | ||
| 1342 | LNCT.MD5 => e.md5 = switch (form_value) { | ||
| 1343 | .data16 => |data16| data16.*, | ||
| 1344 | else => return badDwarf(), | ||
| 1345 | }, | ||
| 1346 | else => continue, | ||
| 1347 | } | ||
| 1348 | } | ||
| 1349 | file_entries.appendAssumeCapacity(e); | ||
| 1350 | } | ||
| 1351 | } | ||
| 1352 | } | ||
| 1353 | |||
| 1354 | var prog = LineNumberProgram.init( | ||
| 1355 | default_is_stmt, | ||
| 1356 | include_directories.items, | ||
| 1357 | target_address, | ||
| 1358 | version, | ||
| 1359 | ); | ||
| 1360 | |||
| 1361 | try fbr.seekTo(prog_start_offset); | ||
| 1362 | |||
| 1363 | const next_unit_pos = line_info_offset + next_offset; | ||
| 1364 | |||
| 1365 | while (fbr.pos < next_unit_pos) { | ||
| 1366 | const opcode = try fbr.readByte(); | ||
| 1367 | |||
| 1368 | if (opcode == LNS.extended_op) { | ||
| 1369 | const op_size = try fbr.readUleb128(u64); | ||
| 1370 | if (op_size < 1) return badDwarf(); | ||
| 1371 | const sub_op = try fbr.readByte(); | ||
| 1372 | switch (sub_op) { | ||
| 1373 | LNE.end_sequence => { | ||
| 1374 | prog.end_sequence = true; | ||
| 1375 | if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; | ||
| 1376 | prog.reset(); | ||
| 1377 | }, | ||
| 1378 | LNE.set_address => { | ||
| 1379 | const addr = try fbr.readInt(usize); | ||
| 1380 | prog.address = addr; | ||
| 1381 | }, | ||
| 1382 | LNE.define_file => { | ||
| 1383 | const path = try fbr.readBytesTo(0); | ||
| 1384 | const dir_index = try fbr.readUleb128(u32); | ||
| 1385 | const mtime = try fbr.readUleb128(u64); | ||
| 1386 | const size = try fbr.readUleb128(u64); | ||
| 1387 | try file_entries.append(.{ | ||
| 1388 | .path = path, | ||
| 1389 | .dir_index = dir_index, | ||
| 1390 | .mtime = mtime, | ||
| 1391 | .size = size, | ||
| 1392 | }); | ||
| 1393 | }, | ||
| 1394 | else => try fbr.seekForward(op_size - 1), | ||
| 1395 | } | ||
| 1396 | } else if (opcode >= opcode_base) { | ||
| 1397 | // special opcodes | ||
| 1398 | const adjusted_opcode = opcode - opcode_base; | ||
| 1399 | const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); | ||
| 1400 | const inc_line = @as(i32, line_base) + @as(i32, adjusted_opcode % line_range); | ||
| 1401 | prog.line += inc_line; | ||
| 1402 | prog.address += inc_addr; | ||
| 1403 | if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; | ||
| 1404 | prog.basic_block = false; | ||
| 1405 | } else { | ||
| 1406 | switch (opcode) { | ||
| 1407 | LNS.copy => { | ||
| 1408 | if (try prog.checkLineMatch(allocator, file_entries.items)) |info| return info; | ||
| 1409 | prog.basic_block = false; | ||
| 1410 | }, | ||
| 1411 | LNS.advance_pc => { | ||
| 1412 | const arg = try fbr.readUleb128(usize); | ||
| 1413 | prog.address += arg * minimum_instruction_length; | ||
| 1414 | }, | ||
| 1415 | LNS.advance_line => { | ||
| 1416 | const arg = try fbr.readIleb128(i64); | ||
| 1417 | prog.line += arg; | ||
| 1418 | }, | ||
| 1419 | LNS.set_file => { | ||
| 1420 | const arg = try fbr.readUleb128(usize); | ||
| 1421 | prog.file = arg; | ||
| 1422 | }, | ||
| 1423 | LNS.set_column => { | ||
| 1424 | const arg = try fbr.readUleb128(u64); | ||
| 1425 | prog.column = arg; | ||
| 1426 | }, | ||
| 1427 | LNS.negate_stmt => { | ||
| 1428 | prog.is_stmt = !prog.is_stmt; | ||
| 1429 | }, | ||
| 1430 | LNS.set_basic_block => { | ||
| 1431 | prog.basic_block = true; | ||
| 1432 | }, | ||
| 1433 | LNS.const_add_pc => { | ||
| 1434 | const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); | ||
| 1435 | prog.address += inc_addr; | ||
| 1436 | }, | ||
| 1437 | LNS.fixed_advance_pc => { | ||
| 1438 | const arg = try fbr.readInt(u16); | ||
| 1439 | prog.address += arg; | ||
| 1440 | }, | ||
| 1441 | LNS.set_prologue_end => {}, | ||
| 1442 | else => { | ||
| 1443 | if (opcode - 1 >= standard_opcode_lengths.len) return badDwarf(); | ||
| 1444 | try fbr.seekForward(standard_opcode_lengths[opcode - 1]); | ||
| 1445 | }, | ||
| 1446 | } | ||
| 1447 | } | ||
| 1448 | } | ||
| 1449 | |||
| 1450 | return missingDwarf(); | ||
| 1451 | } | ||
| 1452 | |||
| 1453 | fn getString(di: DwarfInfo, offset: u64) ![:0]const u8 { | ||
| 1454 | return getStringGeneric(di.section(.debug_str), offset); | ||
| 1455 | } | ||
| 1456 | |||
| 1457 | fn getLineString(di: DwarfInfo, offset: u64) ![:0]const u8 { | ||
| 1458 | return getStringGeneric(di.section(.debug_line_str), offset); | ||
| 1459 | } | ||
| 1460 | |||
| 1461 | fn readDebugAddr(di: DwarfInfo, compile_unit: CompileUnit, index: u64) !u64 { | ||
| 1462 | const debug_addr = di.section(.debug_addr) orelse return badDwarf(); | ||
| 1463 | |||
| 1464 | // addr_base points to the first item after the header, however we | ||
| 1465 | // need to read the header to know the size of each item. Empirically, | ||
| 1466 | // it may disagree with is_64 on the compile unit. | ||
| 1467 | // The header is 8 or 12 bytes depending on is_64. | ||
| 1468 | if (compile_unit.addr_base < 8) return badDwarf(); | ||
| 1469 | |||
| 1470 | const version = mem.readInt(u16, debug_addr[compile_unit.addr_base - 4 ..][0..2], di.endian); | ||
| 1471 | if (version != 5) return badDwarf(); | ||
| 1472 | |||
| 1473 | const addr_size = debug_addr[compile_unit.addr_base - 2]; | ||
| 1474 | const seg_size = debug_addr[compile_unit.addr_base - 1]; | ||
| 1475 | |||
| 1476 | const byte_offset = @as(usize, @intCast(compile_unit.addr_base + (addr_size + seg_size) * index)); | ||
| 1477 | if (byte_offset + addr_size > debug_addr.len) return badDwarf(); | ||
| 1478 | return switch (addr_size) { | ||
| 1479 | 1 => debug_addr[byte_offset], | ||
| 1480 | 2 => mem.readInt(u16, debug_addr[byte_offset..][0..2], di.endian), | ||
| 1481 | 4 => mem.readInt(u32, debug_addr[byte_offset..][0..4], di.endian), | ||
| 1482 | 8 => mem.readInt(u64, debug_addr[byte_offset..][0..8], di.endian), | ||
| 1483 | else => badDwarf(), | ||
| 1484 | }; | ||
| 1485 | } | ||
| 1486 | |||
| 1487 | /// If .eh_frame_hdr is present, then only the header needs to be parsed. | ||
| 1488 | /// | ||
| 1489 | /// Otherwise, .eh_frame and .debug_frame are scanned and a sorted list | ||
| 1490 | /// of FDEs is built for binary searching during unwinding. | ||
| 1491 | pub fn scanAllUnwindInfo(di: *DwarfInfo, allocator: mem.Allocator, base_address: usize) !void { | ||
| 1492 | if (di.section(.eh_frame_hdr)) |eh_frame_hdr| blk: { | ||
| 1493 | var fbr: FixedBufferReader = .{ .buf = eh_frame_hdr, .endian = native_endian }; | ||
| 1494 | |||
| 1495 | const version = try fbr.readByte(); | ||
| 1496 | if (version != 1) break :blk; | ||
| 1497 | |||
| 1498 | const eh_frame_ptr_enc = try fbr.readByte(); | ||
| 1499 | if (eh_frame_ptr_enc == EH.PE.omit) break :blk; | ||
| 1500 | const fde_count_enc = try fbr.readByte(); | ||
| 1501 | if (fde_count_enc == EH.PE.omit) break :blk; | ||
| 1502 | const table_enc = try fbr.readByte(); | ||
| 1503 | if (table_enc == EH.PE.omit) break :blk; | ||
| 1504 | |||
| 1505 | const eh_frame_ptr = math.cast(usize, try readEhPointer(&fbr, eh_frame_ptr_enc, @sizeOf(usize), .{ | ||
| 1506 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), | ||
| 1507 | .follow_indirect = true, | ||
| 1508 | }) orelse return badDwarf()) orelse return badDwarf(); | ||
| 1509 | |||
| 1510 | const fde_count = math.cast(usize, try readEhPointer(&fbr, fde_count_enc, @sizeOf(usize), .{ | ||
| 1511 | .pc_rel_base = @intFromPtr(&eh_frame_hdr[fbr.pos]), | ||
| 1512 | .follow_indirect = true, | ||
| 1513 | }) orelse return badDwarf()) orelse return badDwarf(); | ||
| 1514 | |||
| 1515 | const entry_size = try ExceptionFrameHeader.entrySize(table_enc); | ||
| 1516 | const entries_len = fde_count * entry_size; | ||
| 1517 | if (entries_len > eh_frame_hdr.len - fbr.pos) return badDwarf(); | ||
| 1518 | |||
| 1519 | di.eh_frame_hdr = .{ | ||
| 1520 | .eh_frame_ptr = eh_frame_ptr, | ||
| 1521 | .table_enc = table_enc, | ||
| 1522 | .fde_count = fde_count, | ||
| 1523 | .entries = eh_frame_hdr[fbr.pos..][0..entries_len], | ||
| 1524 | }; | ||
| 1525 | |||
| 1526 | // No need to scan .eh_frame, we have a binary search table already | ||
| 1527 | return; | ||
| 1528 | } | ||
| 1529 | |||
| 1530 | const frame_sections = [2]DwarfSection{ .eh_frame, .debug_frame }; | ||
| 1531 | for (frame_sections) |frame_section| { | ||
| 1532 | if (di.section(frame_section)) |section_data| { | ||
| 1533 | var fbr: FixedBufferReader = .{ .buf = section_data, .endian = di.endian }; | ||
| 1534 | while (fbr.pos < fbr.buf.len) { | ||
| 1535 | const entry_header = try EntryHeader.read(&fbr, null, frame_section); | ||
| 1536 | switch (entry_header.type) { | ||
| 1537 | .cie => { | ||
| 1538 | const cie = try CommonInformationEntry.parse( | ||
| 1539 | entry_header.entry_bytes, | ||
| 1540 | di.sectionVirtualOffset(frame_section, base_address).?, | ||
| 1541 | true, | ||
| 1542 | entry_header.format, | ||
| 1543 | frame_section, | ||
| 1544 | entry_header.length_offset, | ||
| 1545 | @sizeOf(usize), | ||
| 1546 | di.endian, | ||
| 1547 | ); | ||
| 1548 | try di.cie_map.put(allocator, entry_header.length_offset, cie); | ||
| 1549 | }, | ||
| 1550 | .fde => |cie_offset| { | ||
| 1551 | const cie = di.cie_map.get(cie_offset) orelse return badDwarf(); | ||
| 1552 | const fde = try FrameDescriptionEntry.parse( | ||
| 1553 | entry_header.entry_bytes, | ||
| 1554 | di.sectionVirtualOffset(frame_section, base_address).?, | ||
| 1555 | true, | ||
| 1556 | cie, | ||
| 1557 | @sizeOf(usize), | ||
| 1558 | di.endian, | ||
| 1559 | ); | ||
| 1560 | try di.fde_list.append(allocator, fde); | ||
| 1561 | }, | ||
| 1562 | .terminator => break, | ||
| 1563 | } | ||
| 1564 | } | ||
| 1565 | |||
| 1566 | mem.sortUnstable(FrameDescriptionEntry, di.fde_list.items, {}, struct { | ||
| 1567 | fn lessThan(ctx: void, a: FrameDescriptionEntry, b: FrameDescriptionEntry) bool { | ||
| 1568 | _ = ctx; | ||
| 1569 | return a.pc_begin < b.pc_begin; | ||
| 1570 | } | ||
| 1571 | }.lessThan); | ||
| 1572 | } | ||
| 1573 | } | ||
| 1574 | } | ||
| 1575 | |||
| 1576 | /// Unwind a stack frame using DWARF unwinding info, updating the register context. | ||
| 1577 | /// | ||
| 1578 | /// If `.eh_frame_hdr` is available, it will be used to binary search for the FDE. | ||
| 1579 | /// Otherwise, a linear scan of `.eh_frame` and `.debug_frame` is done to find the FDE. | ||
| 1580 | /// | ||
| 1581 | /// `explicit_fde_offset` is for cases where the FDE offset is known, such as when __unwind_info | ||
| 1582 | /// defers unwinding to DWARF. This is an offset into the `.eh_frame` section. | ||
| 1583 | pub fn unwindFrame(di: *const DwarfInfo, context: *UnwindContext, ma: *debug.StackIterator.MemoryAccessor, explicit_fde_offset: ?usize) !usize { | ||
| 1584 | if (!comptime abi.supportsUnwinding(builtin.target)) return error.UnsupportedCpuArchitecture; | ||
| 1585 | if (context.pc == 0) return 0; | ||
| 1586 | |||
| 1587 | // Find the FDE and CIE | ||
| 1588 | var cie: CommonInformationEntry = undefined; | ||
| 1589 | var fde: FrameDescriptionEntry = undefined; | ||
| 1590 | |||
| 1591 | if (explicit_fde_offset) |fde_offset| { | ||
| 1592 | const dwarf_section: DwarfSection = .eh_frame; | ||
| 1593 | const frame_section = di.section(dwarf_section) orelse return error.MissingFDE; | ||
| 1594 | if (fde_offset >= frame_section.len) return error.MissingFDE; | ||
| 1595 | |||
| 1596 | var fbr: FixedBufferReader = .{ | ||
| 1597 | .buf = frame_section, | ||
| 1598 | .pos = fde_offset, | ||
| 1599 | .endian = di.endian, | ||
| 1600 | }; | ||
| 1601 | |||
| 1602 | const fde_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); | ||
| 1603 | if (fde_entry_header.type != .fde) return error.MissingFDE; | ||
| 1604 | |||
| 1605 | const cie_offset = fde_entry_header.type.fde; | ||
| 1606 | try fbr.seekTo(cie_offset); | ||
| 1607 | |||
| 1608 | fbr.endian = native_endian; | ||
| 1609 | const cie_entry_header = try EntryHeader.read(&fbr, null, dwarf_section); | ||
| 1610 | if (cie_entry_header.type != .cie) return badDwarf(); | ||
| 1611 | |||
| 1612 | cie = try CommonInformationEntry.parse( | ||
| 1613 | cie_entry_header.entry_bytes, | ||
| 1614 | 0, | ||
| 1615 | true, | ||
| 1616 | cie_entry_header.format, | ||
| 1617 | dwarf_section, | ||
| 1618 | cie_entry_header.length_offset, | ||
| 1619 | @sizeOf(usize), | ||
| 1620 | native_endian, | ||
| 1621 | ); | ||
| 1622 | |||
| 1623 | fde = try FrameDescriptionEntry.parse( | ||
| 1624 | fde_entry_header.entry_bytes, | ||
| 1625 | 0, | ||
| 1626 | true, | ||
| 1627 | cie, | ||
| 1628 | @sizeOf(usize), | ||
| 1629 | native_endian, | ||
| 1630 | ); | ||
| 1631 | } else if (di.eh_frame_hdr) |header| { | ||
| 1632 | const eh_frame_len = if (di.section(.eh_frame)) |eh_frame| eh_frame.len else null; | ||
| 1633 | try header.findEntry( | ||
| 1634 | ma, | ||
| 1635 | eh_frame_len, | ||
| 1636 | @intFromPtr(di.section(.eh_frame_hdr).?.ptr), | ||
| 1637 | context.pc, | ||
| 1638 | &cie, | ||
| 1639 | &fde, | ||
| 1640 | ); | ||
| 1641 | } else { | ||
| 1642 | const index = std.sort.binarySearch(FrameDescriptionEntry, context.pc, di.fde_list.items, {}, struct { | ||
| 1643 | pub fn compareFn(_: void, pc: usize, mid_item: FrameDescriptionEntry) math.Order { | ||
| 1644 | if (pc < mid_item.pc_begin) return .lt; | ||
| 1645 | |||
| 1646 | const range_end = mid_item.pc_begin + mid_item.pc_range; | ||
| 1647 | if (pc < range_end) return .eq; | ||
| 1648 | |||
| 1649 | return .gt; | ||
| 1650 | } | ||
| 1651 | }.compareFn); | ||
| 1652 | |||
| 1653 | fde = if (index) |i| di.fde_list.items[i] else return error.MissingFDE; | ||
| 1654 | cie = di.cie_map.get(fde.cie_length_offset) orelse return error.MissingCIE; | ||
| 1655 | } | ||
| 1656 | |||
| 1657 | var expression_context: expressions.ExpressionContext = .{ | ||
| 1658 | .format = cie.format, | ||
| 1659 | .memory_accessor = ma, | ||
| 1660 | .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, | ||
| 1661 | .thread_context = context.thread_context, | ||
| 1662 | .reg_context = context.reg_context, | ||
| 1663 | .cfa = context.cfa, | ||
| 1664 | }; | ||
| 1665 | |||
| 1666 | context.vm.reset(); | ||
| 1667 | context.reg_context.eh_frame = cie.version != 4; | ||
| 1668 | context.reg_context.is_macho = di.is_macho; | ||
| 1669 | |||
| 1670 | const row = try context.vm.runToNative(context.allocator, context.pc, cie, fde); | ||
| 1671 | context.cfa = switch (row.cfa.rule) { | ||
| 1672 | .val_offset => |offset| blk: { | ||
| 1673 | const register = row.cfa.register orelse return error.InvalidCFARule; | ||
| 1674 | const value = mem.readInt(usize, (try abi.regBytes(context.thread_context, register, context.reg_context))[0..@sizeOf(usize)], native_endian); | ||
| 1675 | break :blk try call_frame.applyOffset(value, offset); | ||
| 1676 | }, | ||
| 1677 | .expression => |expression| blk: { | ||
| 1678 | context.stack_machine.reset(); | ||
| 1679 | const value = try context.stack_machine.run( | ||
| 1680 | expression, | ||
| 1681 | context.allocator, | ||
| 1682 | expression_context, | ||
| 1683 | context.cfa, | ||
| 1684 | ); | ||
| 1685 | |||
| 1686 | if (value) |v| { | ||
| 1687 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 1688 | break :blk v.generic; | ||
| 1689 | } else return error.NoExpressionValue; | ||
| 1690 | }, | ||
| 1691 | else => return error.InvalidCFARule, | ||
| 1692 | }; | ||
| 1693 | |||
| 1694 | if (ma.load(usize, context.cfa.?) == null) return error.InvalidCFA; | ||
| 1695 | expression_context.cfa = context.cfa; | ||
| 1696 | |||
| 1697 | // Buffering the modifications is done because copying the thread context is not portable, | ||
| 1698 | // some implementations (ie. darwin) use internal pointers to the mcontext. | ||
| 1699 | var arena = std.heap.ArenaAllocator.init(context.allocator); | ||
| 1700 | defer arena.deinit(); | ||
| 1701 | const update_allocator = arena.allocator(); | ||
| 1702 | |||
| 1703 | const RegisterUpdate = struct { | ||
| 1704 | // Backed by thread_context | ||
| 1705 | dest: []u8, | ||
| 1706 | // Backed by arena | ||
| 1707 | src: []const u8, | ||
| 1708 | prev: ?*@This(), | ||
| 1709 | }; | ||
| 1710 | |||
| 1711 | var update_tail: ?*RegisterUpdate = null; | ||
| 1712 | var has_return_address = true; | ||
| 1713 | for (context.vm.rowColumns(row)) |column| { | ||
| 1714 | if (column.register) |register| { | ||
| 1715 | if (register == cie.return_address_register) { | ||
| 1716 | has_return_address = column.rule != .undefined; | ||
| 1717 | } | ||
| 1718 | |||
| 1719 | const dest = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 1720 | const src = try update_allocator.alloc(u8, dest.len); | ||
| 1721 | |||
| 1722 | const prev = update_tail; | ||
| 1723 | update_tail = try update_allocator.create(RegisterUpdate); | ||
| 1724 | update_tail.?.* = .{ | ||
| 1725 | .dest = dest, | ||
| 1726 | .src = src, | ||
| 1727 | .prev = prev, | ||
| 1728 | }; | ||
| 1729 | |||
| 1730 | try column.resolveValue( | ||
| 1731 | context, | ||
| 1732 | expression_context, | ||
| 1733 | ma, | ||
| 1734 | src, | ||
| 1735 | ); | ||
| 1736 | } | ||
| 1737 | } | ||
| 1738 | |||
| 1739 | // On all implemented architectures, the CFA is defined as being the previous frame's SP | ||
| 1740 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(context.reg_context), context.reg_context)).* = context.cfa.?; | ||
| 1741 | |||
| 1742 | while (update_tail) |tail| { | ||
| 1743 | @memcpy(tail.dest, tail.src); | ||
| 1744 | update_tail = tail.prev; | ||
| 1745 | } | ||
| 1746 | |||
| 1747 | if (has_return_address) { | ||
| 1748 | context.pc = abi.stripInstructionPtrAuthCode(mem.readInt(usize, (try abi.regBytes( | ||
| 1749 | context.thread_context, | ||
| 1750 | cie.return_address_register, | ||
| 1751 | context.reg_context, | ||
| 1752 | ))[0..@sizeOf(usize)], native_endian)); | ||
| 1753 | } else { | ||
| 1754 | context.pc = 0; | ||
| 1755 | } | ||
| 1756 | |||
| 1757 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), context.reg_context)).* = context.pc; | ||
| 1758 | |||
| 1759 | // The call instruction will have pushed the address of the instruction that follows the call as the return address. | ||
| 1760 | // This next instruction may be past the end of the function if the caller was `noreturn` (ie. the last instruction in | ||
| 1761 | // the function was the call). If we were to look up an FDE entry using the return address directly, it could end up | ||
| 1762 | // either not finding an FDE at all, or using the next FDE in the program, producing incorrect results. To prevent this, | ||
| 1763 | // we subtract one so that the next lookup is guaranteed to land inside the | ||
| 1764 | // | ||
| 1765 | // The exception to this rule is signal frames, where we return execution would be returned to the instruction | ||
| 1766 | // that triggered the handler. | ||
| 1767 | const return_address = context.pc; | ||
| 1768 | if (context.pc > 0 and !cie.isSignalFrame()) context.pc -= 1; | ||
| 1769 | |||
| 1770 | return return_address; | ||
| 1771 | } | ||
| 1772 | }; | ||
| 1773 | |||
| 1774 | /// Returns the DWARF register number for an x86_64 register number found in compact unwind info | ||
| 1775 | fn compactUnwindToDwarfRegNumber(unwind_reg_number: u3) !u8 { | ||
| 1776 | return switch (unwind_reg_number) { | ||
| 1777 | 1 => 3, // RBX | ||
| 1778 | 2 => 12, // R12 | ||
| 1779 | 3 => 13, // R13 | ||
| 1780 | 4 => 14, // R14 | ||
| 1781 | 5 => 15, // R15 | ||
| 1782 | 6 => 6, // RBP | ||
| 1783 | else => error.InvalidUnwindRegisterNumber, | ||
| 1784 | }; | ||
| 1785 | } | ||
| 1786 | |||
| 1787 | const macho = std.macho; | ||
| 1788 | |||
| 1789 | /// Unwind a frame using MachO compact unwind info (from __unwind_info). | ||
| 1790 | /// If the compact encoding can't encode a way to unwind a frame, it will | ||
| 1791 | /// defer unwinding to DWARF, in which case `.eh_frame` will be used if available. | ||
| 1792 | pub fn unwindFrameMachO( | ||
| 1793 | context: *UnwindContext, | ||
| 1794 | ma: *debug.StackIterator.MemoryAccessor, | ||
| 1795 | unwind_info: []const u8, | ||
| 1796 | eh_frame: ?[]const u8, | ||
| 1797 | module_base_address: usize, | ||
| 1798 | ) !usize { | ||
| 1799 | const header = mem.bytesAsValue( | ||
| 1800 | macho.unwind_info_section_header, | ||
| 1801 | unwind_info[0..@sizeOf(macho.unwind_info_section_header)], | ||
| 1802 | ); | ||
| 1803 | const indices = mem.bytesAsSlice( | ||
| 1804 | macho.unwind_info_section_header_index_entry, | ||
| 1805 | unwind_info[header.indexSectionOffset..][0 .. header.indexCount * @sizeOf(macho.unwind_info_section_header_index_entry)], | ||
| 1806 | ); | ||
| 1807 | if (indices.len == 0) return error.MissingUnwindInfo; | ||
| 1808 | |||
| 1809 | const mapped_pc = context.pc - module_base_address; | ||
| 1810 | const second_level_index = blk: { | ||
| 1811 | var left: usize = 0; | ||
| 1812 | var len: usize = indices.len; | ||
| 1813 | |||
| 1814 | while (len > 1) { | ||
| 1815 | const mid = left + len / 2; | ||
| 1816 | const offset = indices[mid].functionOffset; | ||
| 1817 | if (mapped_pc < offset) { | ||
| 1818 | len /= 2; | ||
| 1819 | } else { | ||
| 1820 | left = mid; | ||
| 1821 | if (mapped_pc == offset) break; | ||
| 1822 | len -= len / 2; | ||
| 1823 | } | ||
| 1824 | } | ||
| 1825 | |||
| 1826 | // Last index is a sentinel containing the highest address as its functionOffset | ||
| 1827 | if (indices[left].secondLevelPagesSectionOffset == 0) return error.MissingUnwindInfo; | ||
| 1828 | break :blk &indices[left]; | ||
| 1829 | }; | ||
| 1830 | |||
| 1831 | const common_encodings = mem.bytesAsSlice( | ||
| 1832 | macho.compact_unwind_encoding_t, | ||
| 1833 | unwind_info[header.commonEncodingsArraySectionOffset..][0 .. header.commonEncodingsArrayCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 1834 | ); | ||
| 1835 | |||
| 1836 | const start_offset = second_level_index.secondLevelPagesSectionOffset; | ||
| 1837 | const kind = mem.bytesAsValue( | ||
| 1838 | macho.UNWIND_SECOND_LEVEL, | ||
| 1839 | unwind_info[start_offset..][0..@sizeOf(macho.UNWIND_SECOND_LEVEL)], | ||
| 1840 | ); | ||
| 1841 | |||
| 1842 | const entry: struct { | ||
| 1843 | function_offset: usize, | ||
| 1844 | raw_encoding: u32, | ||
| 1845 | } = switch (kind.*) { | ||
| 1846 | .REGULAR => blk: { | ||
| 1847 | const page_header = mem.bytesAsValue( | ||
| 1848 | macho.unwind_info_regular_second_level_page_header, | ||
| 1849 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_regular_second_level_page_header)], | ||
| 1850 | ); | ||
| 1851 | |||
| 1852 | const entries = mem.bytesAsSlice( | ||
| 1853 | macho.unwind_info_regular_second_level_entry, | ||
| 1854 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.unwind_info_regular_second_level_entry)], | ||
| 1855 | ); | ||
| 1856 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 1857 | |||
| 1858 | var left: usize = 0; | ||
| 1859 | var len: usize = entries.len; | ||
| 1860 | while (len > 1) { | ||
| 1861 | const mid = left + len / 2; | ||
| 1862 | const offset = entries[mid].functionOffset; | ||
| 1863 | if (mapped_pc < offset) { | ||
| 1864 | len /= 2; | ||
| 1865 | } else { | ||
| 1866 | left = mid; | ||
| 1867 | if (mapped_pc == offset) break; | ||
| 1868 | len -= len / 2; | ||
| 1869 | } | ||
| 1870 | } | ||
| 1871 | |||
| 1872 | break :blk .{ | ||
| 1873 | .function_offset = entries[left].functionOffset, | ||
| 1874 | .raw_encoding = entries[left].encoding, | ||
| 1875 | }; | ||
| 1876 | }, | ||
| 1877 | .COMPRESSED => blk: { | ||
| 1878 | const page_header = mem.bytesAsValue( | ||
| 1879 | macho.unwind_info_compressed_second_level_page_header, | ||
| 1880 | unwind_info[start_offset..][0..@sizeOf(macho.unwind_info_compressed_second_level_page_header)], | ||
| 1881 | ); | ||
| 1882 | |||
| 1883 | const entries = mem.bytesAsSlice( | ||
| 1884 | macho.UnwindInfoCompressedEntry, | ||
| 1885 | unwind_info[start_offset + page_header.entryPageOffset ..][0 .. page_header.entryCount * @sizeOf(macho.UnwindInfoCompressedEntry)], | ||
| 1886 | ); | ||
| 1887 | if (entries.len == 0) return error.InvalidUnwindInfo; | ||
| 1888 | |||
| 1889 | var left: usize = 0; | ||
| 1890 | var len: usize = entries.len; | ||
| 1891 | while (len > 1) { | ||
| 1892 | const mid = left + len / 2; | ||
| 1893 | const offset = second_level_index.functionOffset + entries[mid].funcOffset; | ||
| 1894 | if (mapped_pc < offset) { | ||
| 1895 | len /= 2; | ||
| 1896 | } else { | ||
| 1897 | left = mid; | ||
| 1898 | if (mapped_pc == offset) break; | ||
| 1899 | len -= len / 2; | ||
| 1900 | } | ||
| 1901 | } | ||
| 1902 | |||
| 1903 | const entry = entries[left]; | ||
| 1904 | const function_offset = second_level_index.functionOffset + entry.funcOffset; | ||
| 1905 | if (entry.encodingIndex < header.commonEncodingsArrayCount) { | ||
| 1906 | if (entry.encodingIndex >= common_encodings.len) return error.InvalidUnwindInfo; | ||
| 1907 | break :blk .{ | ||
| 1908 | .function_offset = function_offset, | ||
| 1909 | .raw_encoding = common_encodings[entry.encodingIndex], | ||
| 1910 | }; | ||
| 1911 | } else { | ||
| 1912 | const local_index = try math.sub( | ||
| 1913 | u8, | ||
| 1914 | entry.encodingIndex, | ||
| 1915 | math.cast(u8, header.commonEncodingsArrayCount) orelse return error.InvalidUnwindInfo, | ||
| 1916 | ); | ||
| 1917 | const local_encodings = mem.bytesAsSlice( | ||
| 1918 | macho.compact_unwind_encoding_t, | ||
| 1919 | unwind_info[start_offset + page_header.encodingsPageOffset ..][0 .. page_header.encodingsCount * @sizeOf(macho.compact_unwind_encoding_t)], | ||
| 1920 | ); | ||
| 1921 | if (local_index >= local_encodings.len) return error.InvalidUnwindInfo; | ||
| 1922 | break :blk .{ | ||
| 1923 | .function_offset = function_offset, | ||
| 1924 | .raw_encoding = local_encodings[local_index], | ||
| 1925 | }; | ||
| 1926 | } | ||
| 1927 | }, | ||
| 1928 | else => return error.InvalidUnwindInfo, | ||
| 1929 | }; | ||
| 1930 | |||
| 1931 | if (entry.raw_encoding == 0) return error.NoUnwindInfo; | ||
| 1932 | const reg_context = abi.RegisterContext{ | ||
| 1933 | .eh_frame = false, | ||
| 1934 | .is_macho = true, | ||
| 1935 | }; | ||
| 1936 | |||
| 1937 | const encoding: macho.CompactUnwindEncoding = @bitCast(entry.raw_encoding); | ||
| 1938 | const new_ip = switch (builtin.cpu.arch) { | ||
| 1939 | .x86_64 => switch (encoding.mode.x86_64) { | ||
| 1940 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 1941 | .RBP_FRAME => blk: { | ||
| 1942 | const regs: [5]u3 = .{ | ||
| 1943 | encoding.value.x86_64.frame.reg0, | ||
| 1944 | encoding.value.x86_64.frame.reg1, | ||
| 1945 | encoding.value.x86_64.frame.reg2, | ||
| 1946 | encoding.value.x86_64.frame.reg3, | ||
| 1947 | encoding.value.x86_64.frame.reg4, | ||
| 1948 | }; | ||
| 1949 | |||
| 1950 | const frame_offset = encoding.value.x86_64.frame.frame_offset * @sizeOf(usize); | ||
| 1951 | var max_reg: usize = 0; | ||
| 1952 | inline for (regs, 0..) |reg, i| { | ||
| 1953 | if (reg > 0) max_reg = i; | ||
| 1954 | } | ||
| 1955 | |||
| 1956 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | ||
| 1957 | const new_sp = fp + 2 * @sizeOf(usize); | ||
| 1958 | |||
| 1959 | // Verify the stack range we're about to read register values from | ||
| 1960 | if (ma.load(usize, new_sp) == null or ma.load(usize, fp - frame_offset + max_reg * @sizeOf(usize)) == null) return error.InvalidUnwindInfo; | ||
| 1961 | |||
| 1962 | const ip_ptr = fp + @sizeOf(usize); | ||
| 1963 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 1964 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 1965 | |||
| 1966 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 1967 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 1968 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 1969 | |||
| 1970 | for (regs, 0..) |reg, i| { | ||
| 1971 | if (reg == 0) continue; | ||
| 1972 | const addr = fp - frame_offset + i * @sizeOf(usize); | ||
| 1973 | const reg_number = try compactUnwindToDwarfRegNumber(reg); | ||
| 1974 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(addr)).*; | ||
| 1975 | } | ||
| 1976 | |||
| 1977 | break :blk new_ip; | ||
| 1978 | }, | ||
| 1979 | .STACK_IMMD, | ||
| 1980 | .STACK_IND, | ||
| 1981 | => blk: { | ||
| 1982 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | ||
| 1983 | const stack_size = if (encoding.mode.x86_64 == .STACK_IMMD) | ||
| 1984 | @as(usize, encoding.value.x86_64.frameless.stack.direct.stack_size) * @sizeOf(usize) | ||
| 1985 | else stack_size: { | ||
| 1986 | // In .STACK_IND, the stack size is inferred from the subq instruction at the beginning of the function. | ||
| 1987 | const sub_offset_addr = | ||
| 1988 | module_base_address + | ||
| 1989 | entry.function_offset + | ||
| 1990 | encoding.value.x86_64.frameless.stack.indirect.sub_offset; | ||
| 1991 | if (ma.load(usize, sub_offset_addr) == null) return error.InvalidUnwindInfo; | ||
| 1992 | |||
| 1993 | // `sub_offset_addr` points to the offset of the literal within the instruction | ||
| 1994 | const sub_operand = @as(*align(1) const u32, @ptrFromInt(sub_offset_addr)).*; | ||
| 1995 | break :stack_size sub_operand + @sizeOf(usize) * @as(usize, encoding.value.x86_64.frameless.stack.indirect.stack_adjust); | ||
| 1996 | }; | ||
| 1997 | |||
| 1998 | // Decode the Lehmer-coded sequence of registers. | ||
| 1999 | // For a description of the encoding see lib/libc/include/any-macos.13-any/mach-o/compact_unwind_encoding.h | ||
| 2000 | |||
| 2001 | // Decode the variable-based permutation number into its digits. Each digit represents | ||
| 2002 | // an index into the list of register numbers that weren't yet used in the sequence at | ||
| 2003 | // the time the digit was added. | ||
| 2004 | const reg_count = encoding.value.x86_64.frameless.stack_reg_count; | ||
| 2005 | const ip_ptr = if (reg_count > 0) reg_blk: { | ||
| 2006 | var digits: [6]u3 = undefined; | ||
| 2007 | var accumulator: usize = encoding.value.x86_64.frameless.stack_reg_permutation; | ||
| 2008 | var base: usize = 2; | ||
| 2009 | for (0..reg_count) |i| { | ||
| 2010 | const div = accumulator / base; | ||
| 2011 | digits[digits.len - 1 - i] = @intCast(accumulator - base * div); | ||
| 2012 | accumulator = div; | ||
| 2013 | base += 1; | ||
| 2014 | } | ||
| 2015 | |||
| 2016 | const reg_numbers = [_]u3{ 1, 2, 3, 4, 5, 6 }; | ||
| 2017 | var registers: [reg_numbers.len]u3 = undefined; | ||
| 2018 | var used_indices = [_]bool{false} ** reg_numbers.len; | ||
| 2019 | for (digits[digits.len - reg_count ..], 0..) |target_unused_index, i| { | ||
| 2020 | var unused_count: u8 = 0; | ||
| 2021 | const unused_index = for (used_indices, 0..) |used, index| { | ||
| 2022 | if (!used) { | ||
| 2023 | if (target_unused_index == unused_count) break index; | ||
| 2024 | unused_count += 1; | ||
| 2025 | } | ||
| 2026 | } else unreachable; | ||
| 2027 | |||
| 2028 | registers[i] = reg_numbers[unused_index]; | ||
| 2029 | used_indices[unused_index] = true; | ||
| 2030 | } | ||
| 2031 | |||
| 2032 | var reg_addr = sp + stack_size - @sizeOf(usize) * @as(usize, reg_count + 1); | ||
| 2033 | if (ma.load(usize, reg_addr) == null) return error.InvalidUnwindInfo; | ||
| 2034 | for (0..reg_count) |i| { | ||
| 2035 | const reg_number = try compactUnwindToDwarfRegNumber(registers[i]); | ||
| 2036 | (try abi.regValueNative(usize, context.thread_context, reg_number, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2037 | reg_addr += @sizeOf(usize); | ||
| 2038 | } | ||
| 2039 | |||
| 2040 | break :reg_blk reg_addr; | ||
| 2041 | } else sp + stack_size - @sizeOf(usize); | ||
| 2042 | |||
| 2043 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2044 | const new_sp = ip_ptr + @sizeOf(usize); | ||
| 2045 | if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; | ||
| 2046 | |||
| 2047 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2048 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2049 | |||
| 2050 | break :blk new_ip; | ||
| 2051 | }, | ||
| 2052 | .DWARF => { | ||
| 2053 | return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.x86_64.dwarf)); | ||
| 2054 | }, | ||
| 2055 | }, | ||
| 2056 | .aarch64 => switch (encoding.mode.arm64) { | ||
| 2057 | .OLD => return error.UnimplementedUnwindEncoding, | ||
| 2058 | .FRAMELESS => blk: { | ||
| 2059 | const sp = (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).*; | ||
| 2060 | const new_sp = sp + encoding.value.arm64.frameless.stack_size * 16; | ||
| 2061 | const new_ip = (try abi.regValueNative(usize, context.thread_context, 30, reg_context)).*; | ||
| 2062 | if (ma.load(usize, new_sp) == null) return error.InvalidUnwindInfo; | ||
| 2063 | (try abi.regValueNative(usize, context.thread_context, abi.spRegNum(reg_context), reg_context)).* = new_sp; | ||
| 2064 | break :blk new_ip; | ||
| 2065 | }, | ||
| 2066 | .DWARF => { | ||
| 2067 | return unwindFrameMachODwarf(context, ma, eh_frame orelse return error.MissingEhFrame, @intCast(encoding.value.arm64.dwarf)); | ||
| 2068 | }, | ||
| 2069 | .FRAME => blk: { | ||
| 2070 | const fp = (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).*; | ||
| 2071 | const new_sp = fp + 16; | ||
| 2072 | const ip_ptr = fp + @sizeOf(usize); | ||
| 2073 | |||
| 2074 | const num_restored_pairs: usize = | ||
| 2075 | @popCount(@as(u5, @bitCast(encoding.value.arm64.frame.x_reg_pairs))) + | ||
| 2076 | @popCount(@as(u4, @bitCast(encoding.value.arm64.frame.d_reg_pairs))); | ||
| 2077 | const min_reg_addr = fp - num_restored_pairs * 2 * @sizeOf(usize); | ||
| 2078 | |||
| 2079 | if (ma.load(usize, new_sp) == null or ma.load(usize, min_reg_addr) == null) return error.InvalidUnwindInfo; | ||
| 2080 | |||
| 2081 | var reg_addr = fp - @sizeOf(usize); | ||
| 2082 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.x_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 2083 | if (@field(encoding.value.arm64.frame.x_reg_pairs, field.name) != 0) { | ||
| 2084 | (try abi.regValueNative(usize, context.thread_context, 19 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2085 | reg_addr += @sizeOf(usize); | ||
| 2086 | (try abi.regValueNative(usize, context.thread_context, 20 + i, reg_context)).* = @as(*const usize, @ptrFromInt(reg_addr)).*; | ||
| 2087 | reg_addr += @sizeOf(usize); | ||
| 2088 | } | ||
| 2089 | } | ||
| 2090 | |||
| 2091 | inline for (@typeInfo(@TypeOf(encoding.value.arm64.frame.d_reg_pairs)).Struct.fields, 0..) |field, i| { | ||
| 2092 | if (@field(encoding.value.arm64.frame.d_reg_pairs, field.name) != 0) { | ||
| 2093 | // Only the lower half of the 128-bit V registers are restored during unwinding | ||
| 2094 | @memcpy( | ||
| 2095 | try abi.regBytes(context.thread_context, 64 + 8 + i, context.reg_context), | ||
| 2096 | mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 2097 | ); | ||
| 2098 | reg_addr += @sizeOf(usize); | ||
| 2099 | @memcpy( | ||
| 2100 | try abi.regBytes(context.thread_context, 64 + 9 + i, context.reg_context), | ||
| 2101 | mem.asBytes(@as(*const usize, @ptrFromInt(reg_addr))), | ||
| 2102 | ); | ||
| 2103 | reg_addr += @sizeOf(usize); | ||
| 2104 | } | ||
| 2105 | } | ||
| 2106 | |||
| 2107 | const new_ip = @as(*const usize, @ptrFromInt(ip_ptr)).*; | ||
| 2108 | const new_fp = @as(*const usize, @ptrFromInt(fp)).*; | ||
| 2109 | |||
| 2110 | (try abi.regValueNative(usize, context.thread_context, abi.fpRegNum(reg_context), reg_context)).* = new_fp; | ||
| 2111 | (try abi.regValueNative(usize, context.thread_context, abi.ipRegNum(), reg_context)).* = new_ip; | ||
| 2112 | |||
| 2113 | break :blk new_ip; | ||
| 2114 | }, | ||
| 2115 | }, | ||
| 2116 | else => return error.UnimplementedArch, | ||
| 2117 | }; | ||
| 2118 | |||
| 2119 | context.pc = abi.stripInstructionPtrAuthCode(new_ip); | ||
| 2120 | if (context.pc > 0) context.pc -= 1; | ||
| 2121 | return new_ip; | ||
| 2122 | } | ||
| 2123 | |||
| 2124 | fn unwindFrameMachODwarf(context: *UnwindContext, ma: *debug.StackIterator.MemoryAccessor, eh_frame: []const u8, fde_offset: usize) !usize { | ||
| 2125 | var di = DwarfInfo{ | ||
| 2126 | .endian = native_endian, | ||
| 2127 | .is_macho = true, | ||
| 2128 | }; | ||
| 2129 | defer di.deinit(context.allocator); | ||
| 2130 | |||
| 2131 | di.sections[@intFromEnum(DwarfSection.eh_frame)] = .{ | ||
| 2132 | .data = eh_frame, | ||
| 2133 | .owned = false, | ||
| 2134 | }; | ||
| 2135 | |||
| 2136 | return di.unwindFrame(context, ma, fde_offset); | ||
| 2137 | } | ||
| 2138 | |||
| 2139 | pub const UnwindContext = struct { | ||
| 2140 | allocator: mem.Allocator, | ||
| 2141 | cfa: ?usize, | ||
| 2142 | pc: usize, | ||
| 2143 | thread_context: *debug.ThreadContext, | ||
| 2144 | reg_context: abi.RegisterContext, | ||
| 2145 | vm: call_frame.VirtualMachine, | ||
| 2146 | stack_machine: expressions.StackMachine(.{ .call_frame_context = true }), | ||
| 2147 | |||
| 2148 | pub fn init( | ||
| 2149 | allocator: mem.Allocator, | ||
| 2150 | thread_context: *const debug.ThreadContext, | ||
| 2151 | ) !UnwindContext { | ||
| 2152 | const pc = abi.stripInstructionPtrAuthCode( | ||
| 2153 | (try abi.regValueNative( | ||
| 2154 | usize, | ||
| 2155 | thread_context, | ||
| 2156 | abi.ipRegNum(), | ||
| 2157 | null, | ||
| 2158 | )).*, | ||
| 2159 | ); | ||
| 2160 | |||
| 2161 | const context_copy = try allocator.create(debug.ThreadContext); | ||
| 2162 | debug.copyContext(thread_context, context_copy); | ||
| 2163 | |||
| 2164 | return .{ | ||
| 2165 | .allocator = allocator, | ||
| 2166 | .cfa = null, | ||
| 2167 | .pc = pc, | ||
| 2168 | .thread_context = context_copy, | ||
| 2169 | .reg_context = undefined, | ||
| 2170 | .vm = .{}, | ||
| 2171 | .stack_machine = .{}, | ||
| 2172 | }; | ||
| 2173 | } | ||
| 2174 | |||
| 2175 | pub fn deinit(self: *UnwindContext) void { | ||
| 2176 | self.vm.deinit(self.allocator); | ||
| 2177 | self.stack_machine.deinit(self.allocator); | ||
| 2178 | self.allocator.destroy(self.thread_context); | ||
| 2179 | self.* = undefined; | ||
| 2180 | } | ||
| 2181 | |||
| 2182 | pub fn getFp(self: *const UnwindContext) !usize { | ||
| 2183 | return (try abi.regValueNative(usize, self.thread_context, abi.fpRegNum(self.reg_context), self.reg_context)).*; | ||
| 2184 | } | ||
| 2185 | }; | ||
| 2186 | |||
| 2187 | /// Initialize DWARF info. The caller has the responsibility to initialize most | ||
| 2188 | /// the DwarfInfo fields before calling. `binary_mem` is the raw bytes of the | ||
| 2189 | /// main binary file (not the secondary debug info file). | ||
| 2190 | pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: mem.Allocator) !void { | ||
| 2191 | try di.scanAllFunctions(allocator); | ||
| 2192 | try di.scanAllCompileUnits(allocator); | ||
| 2193 | } | ||
| 2194 | |||
| 2195 | /// This function is to make it handy to comment out the return and make it | ||
| 2196 | /// into a crash when working on this file. | ||
| 2197 | fn badDwarf() error{InvalidDebugInfo} { | ||
| 2198 | //if (true) @panic("badDwarf"); // can be handy to uncomment when working on this file | ||
| 2199 | return error.InvalidDebugInfo; | ||
| 2200 | } | ||
| 2201 | |||
| 2202 | fn missingDwarf() error{MissingDebugInfo} { | ||
| 2203 | //if (true) @panic("missingDwarf"); // can be handy to uncomment when working on this file | ||
| 2204 | return error.MissingDebugInfo; | ||
| 2205 | } | ||
| 2206 | |||
| 2207 | fn getStringGeneric(opt_str: ?[]const u8, offset: u64) ![:0]const u8 { | ||
| 2208 | const str = opt_str orelse return badDwarf(); | ||
| 2209 | if (offset > str.len) return badDwarf(); | ||
| 2210 | const casted_offset = math.cast(usize, offset) orelse return badDwarf(); | ||
| 2211 | // Valid strings always have a terminating zero byte | ||
| 2212 | const last = mem.indexOfScalarPos(u8, str, casted_offset, 0) orelse return badDwarf(); | ||
| 2213 | return str[casted_offset..last :0]; | ||
| 2214 | } | ||
| 2215 | |||
| 2216 | const EhPointerContext = struct { | ||
| 2217 | // The address of the pointer field itself | ||
| 2218 | pc_rel_base: u64, | ||
| 2219 | |||
| 2220 | // Whether or not to follow indirect pointers. This should only be | ||
| 2221 | // used when decoding pointers at runtime using the current process's | ||
| 2222 | // debug info | ||
| 2223 | follow_indirect: bool, | ||
| 2224 | |||
| 2225 | // These relative addressing modes are only used in specific cases, and | ||
| 2226 | // might not be available / required in all parsing contexts | ||
| 2227 | data_rel_base: ?u64 = null, | ||
| 2228 | text_rel_base: ?u64 = null, | ||
| 2229 | function_rel_base: ?u64 = null, | ||
| 2230 | }; | ||
| 2231 | fn readEhPointer(fbr: *FixedBufferReader, enc: u8, addr_size_bytes: u8, ctx: EhPointerContext) !?u64 { | ||
| 2232 | if (enc == EH.PE.omit) return null; | ||
| 2233 | |||
| 2234 | const value: union(enum) { | ||
| 2235 | signed: i64, | ||
| 2236 | unsigned: u64, | ||
| 2237 | } = switch (enc & EH.PE.type_mask) { | ||
| 2238 | EH.PE.absptr => .{ | ||
| 2239 | .unsigned = switch (addr_size_bytes) { | ||
| 2240 | 2 => try fbr.readInt(u16), | ||
| 2241 | 4 => try fbr.readInt(u32), | ||
| 2242 | 8 => try fbr.readInt(u64), | ||
| 2243 | else => return error.InvalidAddrSize, | ||
| 2244 | }, | ||
| 2245 | }, | ||
| 2246 | EH.PE.uleb128 => .{ .unsigned = try fbr.readUleb128(u64) }, | ||
| 2247 | EH.PE.udata2 => .{ .unsigned = try fbr.readInt(u16) }, | ||
| 2248 | EH.PE.udata4 => .{ .unsigned = try fbr.readInt(u32) }, | ||
| 2249 | EH.PE.udata8 => .{ .unsigned = try fbr.readInt(u64) }, | ||
| 2250 | EH.PE.sleb128 => .{ .signed = try fbr.readIleb128(i64) }, | ||
| 2251 | EH.PE.sdata2 => .{ .signed = try fbr.readInt(i16) }, | ||
| 2252 | EH.PE.sdata4 => .{ .signed = try fbr.readInt(i32) }, | ||
| 2253 | EH.PE.sdata8 => .{ .signed = try fbr.readInt(i64) }, | ||
| 2254 | else => return badDwarf(), | ||
| 2255 | }; | ||
| 2256 | |||
| 2257 | const base = switch (enc & EH.PE.rel_mask) { | ||
| 2258 | EH.PE.pcrel => ctx.pc_rel_base, | ||
| 2259 | EH.PE.textrel => ctx.text_rel_base orelse return error.PointerBaseNotSpecified, | ||
| 2260 | EH.PE.datarel => ctx.data_rel_base orelse return error.PointerBaseNotSpecified, | ||
| 2261 | EH.PE.funcrel => ctx.function_rel_base orelse return error.PointerBaseNotSpecified, | ||
| 2262 | else => null, | ||
| 2263 | }; | ||
| 2264 | |||
| 2265 | const ptr: u64 = if (base) |b| switch (value) { | ||
| 2266 | .signed => |s| @intCast(try math.add(i64, s, @as(i64, @intCast(b)))), | ||
| 2267 | // absptr can actually contain signed values in some cases (aarch64 MachO) | ||
| 2268 | .unsigned => |u| u +% b, | ||
| 2269 | } else switch (value) { | ||
| 2270 | .signed => |s| @as(u64, @intCast(s)), | ||
| 2271 | .unsigned => |u| u, | ||
| 2272 | }; | ||
| 2273 | |||
| 2274 | if ((enc & EH.PE.indirect) > 0 and ctx.follow_indirect) { | ||
| 2275 | if (@sizeOf(usize) != addr_size_bytes) { | ||
| 2276 | // See the documentation for `follow_indirect` | ||
| 2277 | return error.NonNativeIndirection; | ||
| 2278 | } | ||
| 2279 | |||
| 2280 | const native_ptr = math.cast(usize, ptr) orelse return error.PointerOverflow; | ||
| 2281 | return switch (addr_size_bytes) { | ||
| 2282 | 2, 4, 8 => return @as(*const usize, @ptrFromInt(native_ptr)).*, | ||
| 2283 | else => return error.UnsupportedAddrSize, | ||
| 2284 | }; | ||
| 2285 | } else { | ||
| 2286 | return ptr; | ||
| 2287 | } | ||
| 2288 | } | ||
| 2289 | |||
| 2290 | /// This represents the decoded .eh_frame_hdr header | ||
| 2291 | pub const ExceptionFrameHeader = struct { | ||
| 2292 | eh_frame_ptr: usize, | ||
| 2293 | table_enc: u8, | ||
| 2294 | fde_count: usize, | ||
| 2295 | entries: []const u8, | ||
| 2296 | |||
| 2297 | pub fn entrySize(table_enc: u8) !u8 { | ||
| 2298 | return switch (table_enc & EH.PE.type_mask) { | ||
| 2299 | EH.PE.udata2, | ||
| 2300 | EH.PE.sdata2, | ||
| 2301 | => 4, | ||
| 2302 | EH.PE.udata4, | ||
| 2303 | EH.PE.sdata4, | ||
| 2304 | => 8, | ||
| 2305 | EH.PE.udata8, | ||
| 2306 | EH.PE.sdata8, | ||
| 2307 | => 16, | ||
| 2308 | // This is a binary search table, so all entries must be the same length | ||
| 2309 | else => return badDwarf(), | ||
| 2310 | }; | ||
| 2311 | } | ||
| 2312 | |||
| 2313 | fn isValidPtr( | ||
| 2314 | self: ExceptionFrameHeader, | ||
| 2315 | comptime T: type, | ||
| 2316 | ptr: usize, | ||
| 2317 | ma: *debug.StackIterator.MemoryAccessor, | ||
| 2318 | eh_frame_len: ?usize, | ||
| 2319 | ) bool { | ||
| 2320 | if (eh_frame_len) |len| { | ||
| 2321 | return ptr >= self.eh_frame_ptr and ptr <= self.eh_frame_ptr + len - @sizeOf(T); | ||
| 2322 | } else { | ||
| 2323 | return ma.load(T, ptr) != null; | ||
| 2324 | } | ||
| 2325 | } | ||
| 2326 | |||
| 2327 | /// Find an entry by binary searching the eh_frame_hdr section. | ||
| 2328 | /// | ||
| 2329 | /// Since the length of the eh_frame section (`eh_frame_len`) may not be known by the caller, | ||
| 2330 | /// MemoryAccessor will be used to verify readability of the header entries. | ||
| 2331 | /// If `eh_frame_len` is provided, then these checks can be skipped. | ||
| 2332 | pub fn findEntry( | ||
| 2333 | self: ExceptionFrameHeader, | ||
| 2334 | ma: *debug.StackIterator.MemoryAccessor, | ||
| 2335 | eh_frame_len: ?usize, | ||
| 2336 | eh_frame_hdr_ptr: usize, | ||
| 2337 | pc: usize, | ||
| 2338 | cie: *CommonInformationEntry, | ||
| 2339 | fde: *FrameDescriptionEntry, | ||
| 2340 | ) !void { | ||
| 2341 | const entry_size = try entrySize(self.table_enc); | ||
| 2342 | |||
| 2343 | var left: usize = 0; | ||
| 2344 | var len: usize = self.fde_count; | ||
| 2345 | |||
| 2346 | var fbr: FixedBufferReader = .{ .buf = self.entries, .endian = native_endian }; | ||
| 2347 | |||
| 2348 | while (len > 1) { | ||
| 2349 | const mid = left + len / 2; | ||
| 2350 | |||
| 2351 | fbr.pos = mid * entry_size; | ||
| 2352 | const pc_begin = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | ||
| 2353 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), | ||
| 2354 | .follow_indirect = true, | ||
| 2355 | .data_rel_base = eh_frame_hdr_ptr, | ||
| 2356 | }) orelse return badDwarf(); | ||
| 2357 | |||
| 2358 | if (pc < pc_begin) { | ||
| 2359 | len /= 2; | ||
| 2360 | } else { | ||
| 2361 | left = mid; | ||
| 2362 | if (pc == pc_begin) break; | ||
| 2363 | len -= len / 2; | ||
| 2364 | } | ||
| 2365 | } | ||
| 2366 | |||
| 2367 | if (len == 0) return badDwarf(); | ||
| 2368 | fbr.pos = left * entry_size; | ||
| 2369 | |||
| 2370 | // Read past the pc_begin field of the entry | ||
| 2371 | _ = try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | ||
| 2372 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), | ||
| 2373 | .follow_indirect = true, | ||
| 2374 | .data_rel_base = eh_frame_hdr_ptr, | ||
| 2375 | }) orelse return badDwarf(); | ||
| 2376 | |||
| 2377 | const fde_ptr = math.cast(usize, try readEhPointer(&fbr, self.table_enc, @sizeOf(usize), .{ | ||
| 2378 | .pc_rel_base = @intFromPtr(&self.entries[fbr.pos]), | ||
| 2379 | .follow_indirect = true, | ||
| 2380 | .data_rel_base = eh_frame_hdr_ptr, | ||
| 2381 | }) orelse return badDwarf()) orelse return badDwarf(); | ||
| 2382 | |||
| 2383 | if (fde_ptr < self.eh_frame_ptr) return badDwarf(); | ||
| 2384 | |||
| 2385 | // Even if eh_frame_len is not specified, all ranges accssed are checked via MemoryAccessor | ||
| 2386 | const eh_frame = @as([*]const u8, @ptrFromInt(self.eh_frame_ptr))[0 .. eh_frame_len orelse math.maxInt(u32)]; | ||
| 2387 | |||
| 2388 | const fde_offset = fde_ptr - self.eh_frame_ptr; | ||
| 2389 | var eh_frame_fbr: FixedBufferReader = .{ | ||
| 2390 | .buf = eh_frame, | ||
| 2391 | .pos = fde_offset, | ||
| 2392 | .endian = native_endian, | ||
| 2393 | }; | ||
| 2394 | |||
| 2395 | const fde_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); | ||
| 2396 | if (!self.isValidPtr(u8, @intFromPtr(&fde_entry_header.entry_bytes[fde_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); | ||
| 2397 | if (fde_entry_header.type != .fde) return badDwarf(); | ||
| 2398 | |||
| 2399 | // CIEs always come before FDEs (the offset is a subtraction), so we can assume this memory is readable | ||
| 2400 | const cie_offset = fde_entry_header.type.fde; | ||
| 2401 | try eh_frame_fbr.seekTo(cie_offset); | ||
| 2402 | const cie_entry_header = try EntryHeader.read(&eh_frame_fbr, if (eh_frame_len == null) ma else null, .eh_frame); | ||
| 2403 | if (!self.isValidPtr(u8, @intFromPtr(&cie_entry_header.entry_bytes[cie_entry_header.entry_bytes.len - 1]), ma, eh_frame_len)) return badDwarf(); | ||
| 2404 | if (cie_entry_header.type != .cie) return badDwarf(); | ||
| 2405 | |||
| 2406 | cie.* = try CommonInformationEntry.parse( | ||
| 2407 | cie_entry_header.entry_bytes, | ||
| 2408 | 0, | ||
| 2409 | true, | ||
| 2410 | cie_entry_header.format, | ||
| 2411 | .eh_frame, | ||
| 2412 | cie_entry_header.length_offset, | ||
| 2413 | @sizeOf(usize), | ||
| 2414 | native_endian, | ||
| 2415 | ); | ||
| 2416 | |||
| 2417 | fde.* = try FrameDescriptionEntry.parse( | ||
| 2418 | fde_entry_header.entry_bytes, | ||
| 2419 | 0, | ||
| 2420 | true, | ||
| 2421 | cie.*, | ||
| 2422 | @sizeOf(usize), | ||
| 2423 | native_endian, | ||
| 2424 | ); | ||
| 2425 | } | ||
| 2426 | }; | ||
| 2427 | |||
| 2428 | pub const EntryHeader = struct { | ||
| 2429 | /// Offset of the length field in the backing buffer | ||
| 2430 | length_offset: usize, | ||
| 2431 | format: Format, | ||
| 2432 | type: union(enum) { | ||
| 2433 | cie, | ||
| 2434 | /// Value is the offset of the corresponding CIE | ||
| 2435 | fde: u64, | ||
| 2436 | terminator, | ||
| 2437 | }, | ||
| 2438 | /// The entry's contents, not including the ID field | ||
| 2439 | entry_bytes: []const u8, | ||
| 2440 | |||
| 2441 | /// The length of the entry including the ID field, but not the length field itself | ||
| 2442 | pub fn entryLength(self: EntryHeader) usize { | ||
| 2443 | return self.entry_bytes.len + @as(u8, if (self.format == .@"64") 8 else 4); | ||
| 2444 | } | ||
| 2445 | |||
| 2446 | /// Reads a header for either an FDE or a CIE, then advances the fbr to the position after the trailing structure. | ||
| 2447 | /// `fbr` must be a FixedBufferReader backed by either the .eh_frame or .debug_frame sections. | ||
| 2448 | pub fn read( | ||
| 2449 | fbr: *FixedBufferReader, | ||
| 2450 | opt_ma: ?*debug.StackIterator.MemoryAccessor, | ||
| 2451 | dwarf_section: DwarfSection, | ||
| 2452 | ) !EntryHeader { | ||
| 2453 | assert(dwarf_section == .eh_frame or dwarf_section == .debug_frame); | ||
| 2454 | |||
| 2455 | const length_offset = fbr.pos; | ||
| 2456 | const unit_header = try readUnitHeader(fbr, opt_ma); | ||
| 2457 | const unit_length = math.cast(usize, unit_header.unit_length) orelse return badDwarf(); | ||
| 2458 | if (unit_length == 0) return .{ | ||
| 2459 | .length_offset = length_offset, | ||
| 2460 | .format = unit_header.format, | ||
| 2461 | .type = .terminator, | ||
| 2462 | .entry_bytes = &.{}, | ||
| 2463 | }; | ||
| 2464 | const start_offset = fbr.pos; | ||
| 2465 | const end_offset = start_offset + unit_length; | ||
| 2466 | defer fbr.pos = end_offset; | ||
| 2467 | |||
| 2468 | const id = try if (opt_ma) |ma| | ||
| 2469 | fbr.readAddressChecked(unit_header.format, ma) | ||
| 2470 | else | ||
| 2471 | fbr.readAddress(unit_header.format); | ||
| 2472 | const entry_bytes = fbr.buf[fbr.pos..end_offset]; | ||
| 2473 | const cie_id: u64 = switch (dwarf_section) { | ||
| 2474 | .eh_frame => CommonInformationEntry.eh_id, | ||
| 2475 | .debug_frame => switch (unit_header.format) { | ||
| 2476 | .@"32" => CommonInformationEntry.dwarf32_id, | ||
| 2477 | .@"64" => CommonInformationEntry.dwarf64_id, | ||
| 2478 | }, | ||
| 2479 | else => unreachable, | ||
| 2480 | }; | ||
| 2481 | |||
| 2482 | return .{ | ||
| 2483 | .length_offset = length_offset, | ||
| 2484 | .format = unit_header.format, | ||
| 2485 | .type = if (id == cie_id) .cie else .{ .fde = switch (dwarf_section) { | ||
| 2486 | .eh_frame => try math.sub(u64, start_offset, id), | ||
| 2487 | .debug_frame => id, | ||
| 2488 | else => unreachable, | ||
| 2489 | } }, | ||
| 2490 | .entry_bytes = entry_bytes, | ||
| 2491 | }; | ||
| 2492 | } | ||
| 2493 | }; | ||
| 2494 | |||
| 2495 | pub const CommonInformationEntry = struct { | ||
| 2496 | // Used in .eh_frame | ||
| 2497 | pub const eh_id = 0; | ||
| 2498 | |||
| 2499 | // Used in .debug_frame (DWARF32) | ||
| 2500 | pub const dwarf32_id = math.maxInt(u32); | ||
| 2501 | |||
| 2502 | // Used in .debug_frame (DWARF64) | ||
| 2503 | pub const dwarf64_id = math.maxInt(u64); | ||
| 2504 | |||
| 2505 | // Offset of the length field of this entry in the eh_frame section. | ||
| 2506 | // This is the key that FDEs use to reference CIEs. | ||
| 2507 | length_offset: u64, | ||
| 2508 | version: u8, | ||
| 2509 | address_size: u8, | ||
| 2510 | format: Format, | ||
| 2511 | |||
| 2512 | // Only present in version 4 | ||
| 2513 | segment_selector_size: ?u8, | ||
| 2514 | |||
| 2515 | code_alignment_factor: u32, | ||
| 2516 | data_alignment_factor: i32, | ||
| 2517 | return_address_register: u8, | ||
| 2518 | |||
| 2519 | aug_str: []const u8, | ||
| 2520 | aug_data: []const u8, | ||
| 2521 | lsda_pointer_enc: u8, | ||
| 2522 | personality_enc: ?u8, | ||
| 2523 | personality_routine_pointer: ?u64, | ||
| 2524 | fde_pointer_enc: u8, | ||
| 2525 | initial_instructions: []const u8, | ||
| 2526 | |||
| 2527 | pub fn isSignalFrame(self: CommonInformationEntry) bool { | ||
| 2528 | for (self.aug_str) |c| if (c == 'S') return true; | ||
| 2529 | return false; | ||
| 2530 | } | ||
| 2531 | |||
| 2532 | pub fn addressesSignedWithBKey(self: CommonInformationEntry) bool { | ||
| 2533 | for (self.aug_str) |c| if (c == 'B') return true; | ||
| 2534 | return false; | ||
| 2535 | } | ||
| 2536 | |||
| 2537 | pub fn mteTaggedFrame(self: CommonInformationEntry) bool { | ||
| 2538 | for (self.aug_str) |c| if (c == 'G') return true; | ||
| 2539 | return false; | ||
| 2540 | } | ||
| 2541 | |||
| 2542 | /// This function expects to read the CIE starting with the version field. | ||
| 2543 | /// The returned struct references memory backed by cie_bytes. | ||
| 2544 | /// | ||
| 2545 | /// See the FrameDescriptionEntry.parse documentation for the description | ||
| 2546 | /// of `pc_rel_offset` and `is_runtime`. | ||
| 2547 | /// | ||
| 2548 | /// `length_offset` specifies the offset of this CIE's length field in the | ||
| 2549 | /// .eh_frame / .debug_frame section. | ||
| 2550 | pub fn parse( | ||
| 2551 | cie_bytes: []const u8, | ||
| 2552 | pc_rel_offset: i64, | ||
| 2553 | is_runtime: bool, | ||
| 2554 | format: Format, | ||
| 2555 | dwarf_section: DwarfSection, | ||
| 2556 | length_offset: u64, | ||
| 2557 | addr_size_bytes: u8, | ||
| 2558 | endian: std.builtin.Endian, | ||
| 2559 | ) !CommonInformationEntry { | ||
| 2560 | if (addr_size_bytes > 8) return error.UnsupportedAddrSize; | ||
| 2561 | |||
| 2562 | var fbr: FixedBufferReader = .{ .buf = cie_bytes, .endian = endian }; | ||
| 2563 | |||
| 2564 | const version = try fbr.readByte(); | ||
| 2565 | switch (dwarf_section) { | ||
| 2566 | .eh_frame => if (version != 1 and version != 3) return error.UnsupportedDwarfVersion, | ||
| 2567 | .debug_frame => if (version != 4) return error.UnsupportedDwarfVersion, | ||
| 2568 | else => return error.UnsupportedDwarfSection, | ||
| 2569 | } | ||
| 2570 | |||
| 2571 | var has_eh_data = false; | ||
| 2572 | var has_aug_data = false; | ||
| 2573 | |||
| 2574 | var aug_str_len: usize = 0; | ||
| 2575 | const aug_str_start = fbr.pos; | ||
| 2576 | var aug_byte = try fbr.readByte(); | ||
| 2577 | while (aug_byte != 0) : (aug_byte = try fbr.readByte()) { | ||
| 2578 | switch (aug_byte) { | ||
| 2579 | 'z' => { | ||
| 2580 | if (aug_str_len != 0) return badDwarf(); | ||
| 2581 | has_aug_data = true; | ||
| 2582 | }, | ||
| 2583 | 'e' => { | ||
| 2584 | if (has_aug_data or aug_str_len != 0) return badDwarf(); | ||
| 2585 | if (try fbr.readByte() != 'h') return badDwarf(); | ||
| 2586 | has_eh_data = true; | ||
| 2587 | }, | ||
| 2588 | else => if (has_eh_data) return badDwarf(), | ||
| 2589 | } | ||
| 2590 | |||
| 2591 | aug_str_len += 1; | ||
| 2592 | } | ||
| 2593 | |||
| 2594 | if (has_eh_data) { | ||
| 2595 | // legacy data created by older versions of gcc - unsupported here | ||
| 2596 | for (0..addr_size_bytes) |_| _ = try fbr.readByte(); | ||
| 2597 | } | ||
| 2598 | |||
| 2599 | const address_size = if (version == 4) try fbr.readByte() else addr_size_bytes; | ||
| 2600 | const segment_selector_size = if (version == 4) try fbr.readByte() else null; | ||
| 2601 | |||
| 2602 | const code_alignment_factor = try fbr.readUleb128(u32); | ||
| 2603 | const data_alignment_factor = try fbr.readIleb128(i32); | ||
| 2604 | const return_address_register = if (version == 1) try fbr.readByte() else try fbr.readUleb128(u8); | ||
| 2605 | |||
| 2606 | var lsda_pointer_enc: u8 = EH.PE.omit; | ||
| 2607 | var personality_enc: ?u8 = null; | ||
| 2608 | var personality_routine_pointer: ?u64 = null; | ||
| 2609 | var fde_pointer_enc: u8 = EH.PE.absptr; | ||
| 2610 | |||
| 2611 | var aug_data: []const u8 = &[_]u8{}; | ||
| 2612 | const aug_str = if (has_aug_data) blk: { | ||
| 2613 | const aug_data_len = try fbr.readUleb128(usize); | ||
| 2614 | const aug_data_start = fbr.pos; | ||
| 2615 | aug_data = cie_bytes[aug_data_start..][0..aug_data_len]; | ||
| 2616 | |||
| 2617 | const aug_str = cie_bytes[aug_str_start..][0..aug_str_len]; | ||
| 2618 | for (aug_str[1..]) |byte| { | ||
| 2619 | switch (byte) { | ||
| 2620 | 'L' => { | ||
| 2621 | lsda_pointer_enc = try fbr.readByte(); | ||
| 2622 | }, | ||
| 2623 | 'P' => { | ||
| 2624 | personality_enc = try fbr.readByte(); | ||
| 2625 | personality_routine_pointer = try readEhPointer(&fbr, personality_enc.?, addr_size_bytes, .{ | ||
| 2626 | .pc_rel_base = try pcRelBase(@intFromPtr(&cie_bytes[fbr.pos]), pc_rel_offset), | ||
| 2627 | .follow_indirect = is_runtime, | ||
| 2628 | }); | ||
| 2629 | }, | ||
| 2630 | 'R' => { | ||
| 2631 | fde_pointer_enc = try fbr.readByte(); | ||
| 2632 | }, | ||
| 2633 | 'S', 'B', 'G' => {}, | ||
| 2634 | else => return badDwarf(), | ||
| 2635 | } | ||
| 2636 | } | ||
| 2637 | |||
| 2638 | // aug_data_len can include padding so the CIE ends on an address boundary | ||
| 2639 | fbr.pos = aug_data_start + aug_data_len; | ||
| 2640 | break :blk aug_str; | ||
| 2641 | } else &[_]u8{}; | ||
| 2642 | |||
| 2643 | const initial_instructions = cie_bytes[fbr.pos..]; | ||
| 2644 | return .{ | ||
| 2645 | .length_offset = length_offset, | ||
| 2646 | .version = version, | ||
| 2647 | .address_size = address_size, | ||
| 2648 | .format = format, | ||
| 2649 | .segment_selector_size = segment_selector_size, | ||
| 2650 | .code_alignment_factor = code_alignment_factor, | ||
| 2651 | .data_alignment_factor = data_alignment_factor, | ||
| 2652 | .return_address_register = return_address_register, | ||
| 2653 | .aug_str = aug_str, | ||
| 2654 | .aug_data = aug_data, | ||
| 2655 | .lsda_pointer_enc = lsda_pointer_enc, | ||
| 2656 | .personality_enc = personality_enc, | ||
| 2657 | .personality_routine_pointer = personality_routine_pointer, | ||
| 2658 | .fde_pointer_enc = fde_pointer_enc, | ||
| 2659 | .initial_instructions = initial_instructions, | ||
| 2660 | }; | ||
| 2661 | } | ||
| 2662 | }; | ||
| 2663 | |||
| 2664 | pub const FrameDescriptionEntry = struct { | ||
| 2665 | // Offset into eh_frame where the CIE for this FDE is stored | ||
| 2666 | cie_length_offset: u64, | ||
| 2667 | |||
| 2668 | pc_begin: u64, | ||
| 2669 | pc_range: u64, | ||
| 2670 | lsda_pointer: ?u64, | ||
| 2671 | aug_data: []const u8, | ||
| 2672 | instructions: []const u8, | ||
| 2673 | |||
| 2674 | /// This function expects to read the FDE starting at the PC Begin field. | ||
| 2675 | /// The returned struct references memory backed by `fde_bytes`. | ||
| 2676 | /// | ||
| 2677 | /// `pc_rel_offset` specifies an offset to be applied to pc_rel_base values | ||
| 2678 | /// used when decoding pointers. This should be set to zero if fde_bytes is | ||
| 2679 | /// backed by the memory of a .eh_frame / .debug_frame section in the running executable. | ||
| 2680 | /// Otherwise, it should be the relative offset to translate addresses from | ||
| 2681 | /// where the section is currently stored in memory, to where it *would* be | ||
| 2682 | /// stored at runtime: section base addr - backing data base ptr. | ||
| 2683 | /// | ||
| 2684 | /// Similarly, `is_runtime` specifies this function is being called on a runtime | ||
| 2685 | /// section, and so indirect pointers can be followed. | ||
| 2686 | pub fn parse( | ||
| 2687 | fde_bytes: []const u8, | ||
| 2688 | pc_rel_offset: i64, | ||
| 2689 | is_runtime: bool, | ||
| 2690 | cie: CommonInformationEntry, | ||
| 2691 | addr_size_bytes: u8, | ||
| 2692 | endian: std.builtin.Endian, | ||
| 2693 | ) !FrameDescriptionEntry { | ||
| 2694 | if (addr_size_bytes > 8) return error.InvalidAddrSize; | ||
| 2695 | |||
| 2696 | var fbr: FixedBufferReader = .{ .buf = fde_bytes, .endian = endian }; | ||
| 2697 | |||
| 2698 | const pc_begin = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ | ||
| 2699 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), | ||
| 2700 | .follow_indirect = is_runtime, | ||
| 2701 | }) orelse return badDwarf(); | ||
| 2702 | |||
| 2703 | const pc_range = try readEhPointer(&fbr, cie.fde_pointer_enc, addr_size_bytes, .{ | ||
| 2704 | .pc_rel_base = 0, | ||
| 2705 | .follow_indirect = false, | ||
| 2706 | }) orelse return badDwarf(); | ||
| 2707 | |||
| 2708 | var aug_data: []const u8 = &[_]u8{}; | ||
| 2709 | const lsda_pointer = if (cie.aug_str.len > 0) blk: { | ||
| 2710 | const aug_data_len = try fbr.readUleb128(usize); | ||
| 2711 | const aug_data_start = fbr.pos; | ||
| 2712 | aug_data = fde_bytes[aug_data_start..][0..aug_data_len]; | ||
| 2713 | |||
| 2714 | const lsda_pointer = if (cie.lsda_pointer_enc != EH.PE.omit) | ||
| 2715 | try readEhPointer(&fbr, cie.lsda_pointer_enc, addr_size_bytes, .{ | ||
| 2716 | .pc_rel_base = try pcRelBase(@intFromPtr(&fde_bytes[fbr.pos]), pc_rel_offset), | ||
| 2717 | .follow_indirect = is_runtime, | ||
| 2718 | }) | ||
| 2719 | else | ||
| 2720 | null; | ||
| 2721 | |||
| 2722 | fbr.pos = aug_data_start + aug_data_len; | ||
| 2723 | break :blk lsda_pointer; | ||
| 2724 | } else null; | ||
| 2725 | |||
| 2726 | const instructions = fde_bytes[fbr.pos..]; | ||
| 2727 | return .{ | ||
| 2728 | .cie_length_offset = cie.length_offset, | ||
| 2729 | .pc_begin = pc_begin, | ||
| 2730 | .pc_range = pc_range, | ||
| 2731 | .lsda_pointer = lsda_pointer, | ||
| 2732 | .aug_data = aug_data, | ||
| 2733 | .instructions = instructions, | ||
| 2734 | }; | ||
| 2735 | } | ||
| 2736 | }; | ||
| 2737 | |||
| 2738 | fn pcRelBase(field_ptr: usize, pc_rel_offset: i64) !usize { | ||
| 2739 | if (pc_rel_offset < 0) { | ||
| 2740 | return math.sub(usize, field_ptr, @as(usize, @intCast(-pc_rel_offset))); | ||
| 2741 | } else { | ||
| 2742 | return math.add(usize, field_ptr, @as(usize, @intCast(pc_rel_offset))); | ||
| 2743 | } | ||
| 2744 | } | ||
| 2745 | |||
| 2746 | // Reading debug info needs to be fast, even when compiled in debug mode, | ||
| 2747 | // so avoid using a `std.io.FixedBufferStream` which is too slow. | ||
| 2748 | pub const FixedBufferReader = struct { | ||
| 2749 | buf: []const u8, | ||
| 2750 | pos: usize = 0, | ||
| 2751 | endian: std.builtin.Endian, | ||
| 2752 | |||
| 2753 | pub const Error = error{ EndOfBuffer, Overflow, InvalidBuffer }; | ||
| 2754 | |||
| 2755 | fn seekTo(fbr: *FixedBufferReader, pos: u64) Error!void { | ||
| 2756 | if (pos > fbr.buf.len) return error.EndOfBuffer; | ||
| 2757 | fbr.pos = @intCast(pos); | ||
| 2758 | } | ||
| 2759 | |||
| 2760 | fn seekForward(fbr: *FixedBufferReader, amount: u64) Error!void { | ||
| 2761 | if (fbr.buf.len - fbr.pos < amount) return error.EndOfBuffer; | ||
| 2762 | fbr.pos += @intCast(amount); | ||
| 2763 | } | ||
| 2764 | |||
| 2765 | pub inline fn readByte(fbr: *FixedBufferReader) Error!u8 { | ||
| 2766 | if (fbr.pos >= fbr.buf.len) return error.EndOfBuffer; | ||
| 2767 | defer fbr.pos += 1; | ||
| 2768 | return fbr.buf[fbr.pos]; | ||
| 2769 | } | ||
| 2770 | |||
| 2771 | fn readByteSigned(fbr: *FixedBufferReader) Error!i8 { | ||
| 2772 | return @bitCast(try fbr.readByte()); | ||
| 2773 | } | ||
| 2774 | |||
| 2775 | fn readInt(fbr: *FixedBufferReader, comptime T: type) Error!T { | ||
| 2776 | const size = @divExact(@typeInfo(T).Int.bits, 8); | ||
| 2777 | if (fbr.buf.len - fbr.pos < size) return error.EndOfBuffer; | ||
| 2778 | defer fbr.pos += size; | ||
| 2779 | return mem.readInt(T, fbr.buf[fbr.pos..][0..size], fbr.endian); | ||
| 2780 | } | ||
| 2781 | |||
| 2782 | fn readIntChecked( | ||
| 2783 | fbr: *FixedBufferReader, | ||
| 2784 | comptime T: type, | ||
| 2785 | ma: *debug.StackIterator.MemoryAccessor, | ||
| 2786 | ) Error!T { | ||
| 2787 | if (ma.load(T, @intFromPtr(fbr.buf[fbr.pos..].ptr)) == null) | ||
| 2788 | return error.InvalidBuffer; | ||
| 2789 | |||
| 2790 | return readInt(fbr, T); | ||
| 2791 | } | ||
| 2792 | |||
| 2793 | fn readUleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { | ||
| 2794 | return std.leb.readUleb128(T, fbr); | ||
| 2795 | } | ||
| 2796 | |||
| 2797 | fn readIleb128(fbr: *FixedBufferReader, comptime T: type) Error!T { | ||
| 2798 | return std.leb.readIleb128(T, fbr); | ||
| 2799 | } | ||
| 2800 | |||
| 2801 | fn readAddress(fbr: *FixedBufferReader, format: Format) Error!u64 { | ||
| 2802 | return switch (format) { | ||
| 2803 | .@"32" => try fbr.readInt(u32), | ||
| 2804 | .@"64" => try fbr.readInt(u64), | ||
| 2805 | }; | ||
| 2806 | } | ||
| 2807 | |||
| 2808 | fn readAddressChecked( | ||
| 2809 | fbr: *FixedBufferReader, | ||
| 2810 | format: Format, | ||
| 2811 | ma: *debug.StackIterator.MemoryAccessor, | ||
| 2812 | ) Error!u64 { | ||
| 2813 | return switch (format) { | ||
| 2814 | .@"32" => try fbr.readIntChecked(u32, ma), | ||
| 2815 | .@"64" => try fbr.readIntChecked(u64, ma), | ||
| 2816 | }; | ||
| 2817 | } | ||
| 2818 | |||
| 2819 | fn readBytes(fbr: *FixedBufferReader, len: usize) Error![]const u8 { | ||
| 2820 | if (fbr.buf.len - fbr.pos < len) return error.EndOfBuffer; | ||
| 2821 | defer fbr.pos += len; | ||
| 2822 | return fbr.buf[fbr.pos..][0..len]; | ||
| 2823 | } | ||
| 2824 | |||
| 2825 | fn readBytesTo(fbr: *FixedBufferReader, comptime sentinel: u8) Error![:sentinel]const u8 { | ||
| 2826 | const end = @call(.always_inline, mem.indexOfScalarPos, .{ | ||
| 2827 | u8, | ||
| 2828 | fbr.buf, | ||
| 2829 | fbr.pos, | ||
| 2830 | sentinel, | ||
| 2831 | }) orelse return error.EndOfBuffer; | ||
| 2832 | defer fbr.pos = end + 1; | ||
| 2833 | return fbr.buf[fbr.pos..end :sentinel]; | ||
| 2834 | } | ||
| 2835 | }; | ||
| 2836 | |||
| 2837 | test { | ||
| 2838 | std.testing.refAllDecls(@This()); | ||
| 2839 | } |
lib/std/dwarf/abi.zig deleted-410| ... | @@ -1,410 +0,0 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const std = @import("../std.zig"); | ||
| 3 | const mem = std.mem; | ||
| 4 | const native_os = builtin.os.tag; | ||
| 5 | const posix = std.posix; | ||
| 6 | |||
| 7 | pub fn supportsUnwinding(target: std.Target) bool { | ||
| 8 | return switch (target.cpu.arch) { | ||
| 9 | .x86 => switch (target.os.tag) { | ||
| 10 | .linux, .netbsd, .solaris, .illumos => true, | ||
| 11 | else => false, | ||
| 12 | }, | ||
| 13 | .x86_64 => switch (target.os.tag) { | ||
| 14 | .linux, .netbsd, .freebsd, .openbsd, .macos, .ios, .solaris, .illumos => true, | ||
| 15 | else => false, | ||
| 16 | }, | ||
| 17 | .arm => switch (target.os.tag) { | ||
| 18 | .linux => true, | ||
| 19 | else => false, | ||
| 20 | }, | ||
| 21 | .aarch64 => switch (target.os.tag) { | ||
| 22 | .linux, .netbsd, .freebsd, .macos, .ios => true, | ||
| 23 | else => false, | ||
| 24 | }, | ||
| 25 | else => false, | ||
| 26 | }; | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn ipRegNum() u8 { | ||
| 30 | return switch (builtin.cpu.arch) { | ||
| 31 | .x86 => 8, | ||
| 32 | .x86_64 => 16, | ||
| 33 | .arm => 15, | ||
| 34 | .aarch64 => 32, | ||
| 35 | else => unreachable, | ||
| 36 | }; | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn fpRegNum(reg_context: RegisterContext) u8 { | ||
| 40 | return switch (builtin.cpu.arch) { | ||
| 41 | // GCC on OS X historically did the opposite of ELF for these registers (only in .eh_frame), and that is now the convention for MachO | ||
| 42 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 4 else 5, | ||
| 43 | .x86_64 => 6, | ||
| 44 | .arm => 11, | ||
| 45 | .aarch64 => 29, | ||
| 46 | else => unreachable, | ||
| 47 | }; | ||
| 48 | } | ||
| 49 | |||
| 50 | pub fn spRegNum(reg_context: RegisterContext) u8 { | ||
| 51 | return switch (builtin.cpu.arch) { | ||
| 52 | .x86 => if (reg_context.eh_frame and reg_context.is_macho) 5 else 4, | ||
| 53 | .x86_64 => 7, | ||
| 54 | .arm => 13, | ||
| 55 | .aarch64 => 31, | ||
| 56 | else => unreachable, | ||
| 57 | }; | ||
| 58 | } | ||
| 59 | |||
| 60 | /// Some platforms use pointer authentication - the upper bits of instruction pointers contain a signature. | ||
| 61 | /// This function clears these signature bits to make the pointer usable. | ||
| 62 | pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { | ||
| 63 | if (builtin.cpu.arch == .aarch64) { | ||
| 64 | // `hint 0x07` maps to `xpaclri` (or `nop` if the hardware doesn't support it) | ||
| 65 | // The save / restore is because `xpaclri` operates on x30 (LR) | ||
| 66 | return asm ( | ||
| 67 | \\mov x16, x30 | ||
| 68 | \\mov x30, x15 | ||
| 69 | \\hint 0x07 | ||
| 70 | \\mov x15, x30 | ||
| 71 | \\mov x30, x16 | ||
| 72 | : [ret] "={x15}" (-> usize), | ||
| 73 | : [ptr] "{x15}" (ptr), | ||
| 74 | : "x16" | ||
| 75 | ); | ||
| 76 | } | ||
| 77 | |||
| 78 | return ptr; | ||
| 79 | } | ||
| 80 | |||
| 81 | pub const RegisterContext = struct { | ||
| 82 | eh_frame: bool, | ||
| 83 | is_macho: bool, | ||
| 84 | }; | ||
| 85 | |||
| 86 | pub const AbiError = error{ | ||
| 87 | InvalidRegister, | ||
| 88 | UnimplementedArch, | ||
| 89 | UnimplementedOs, | ||
| 90 | RegisterContextRequired, | ||
| 91 | ThreadContextNotSupported, | ||
| 92 | }; | ||
| 93 | |||
| 94 | fn RegValueReturnType(comptime ContextPtrType: type, comptime T: type) type { | ||
| 95 | const reg_bytes_type = comptime RegBytesReturnType(ContextPtrType); | ||
| 96 | const info = @typeInfo(reg_bytes_type).Pointer; | ||
| 97 | return @Type(.{ | ||
| 98 | .Pointer = .{ | ||
| 99 | .size = .One, | ||
| 100 | .is_const = info.is_const, | ||
| 101 | .is_volatile = info.is_volatile, | ||
| 102 | .is_allowzero = info.is_allowzero, | ||
| 103 | .alignment = info.alignment, | ||
| 104 | .address_space = info.address_space, | ||
| 105 | .child = T, | ||
| 106 | .sentinel = null, | ||
| 107 | }, | ||
| 108 | }); | ||
| 109 | } | ||
| 110 | |||
| 111 | /// Returns a pointer to a register stored in a ThreadContext, preserving the pointer attributes of the context. | ||
| 112 | pub fn regValueNative( | ||
| 113 | comptime T: type, | ||
| 114 | thread_context_ptr: anytype, | ||
| 115 | reg_number: u8, | ||
| 116 | reg_context: ?RegisterContext, | ||
| 117 | ) !RegValueReturnType(@TypeOf(thread_context_ptr), T) { | ||
| 118 | const reg_bytes = try regBytes(thread_context_ptr, reg_number, reg_context); | ||
| 119 | if (@sizeOf(T) != reg_bytes.len) return error.IncompatibleRegisterSize; | ||
| 120 | return mem.bytesAsValue(T, reg_bytes[0..@sizeOf(T)]); | ||
| 121 | } | ||
| 122 | |||
| 123 | fn RegBytesReturnType(comptime ContextPtrType: type) type { | ||
| 124 | const info = @typeInfo(ContextPtrType); | ||
| 125 | if (info != .Pointer or info.Pointer.child != std.debug.ThreadContext) { | ||
| 126 | @compileError("Expected a pointer to std.debug.ThreadContext, got " ++ @typeName(@TypeOf(ContextPtrType))); | ||
| 127 | } | ||
| 128 | |||
| 129 | return if (info.Pointer.is_const) return []const u8 else []u8; | ||
| 130 | } | ||
| 131 | |||
| 132 | /// Returns a slice containing the backing storage for `reg_number`. | ||
| 133 | /// | ||
| 134 | /// `reg_context` describes in what context the register number is used, as it can have different | ||
| 135 | /// meanings depending on the DWARF container. It is only required when getting the stack or | ||
| 136 | /// frame pointer register on some architectures. | ||
| 137 | pub fn regBytes( | ||
| 138 | thread_context_ptr: anytype, | ||
| 139 | reg_number: u8, | ||
| 140 | reg_context: ?RegisterContext, | ||
| 141 | ) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) { | ||
| 142 | if (native_os == .windows) { | ||
| 143 | return switch (builtin.cpu.arch) { | ||
| 144 | .x86 => switch (reg_number) { | ||
| 145 | 0 => mem.asBytes(&thread_context_ptr.Eax), | ||
| 146 | 1 => mem.asBytes(&thread_context_ptr.Ecx), | ||
| 147 | 2 => mem.asBytes(&thread_context_ptr.Edx), | ||
| 148 | 3 => mem.asBytes(&thread_context_ptr.Ebx), | ||
| 149 | 4 => mem.asBytes(&thread_context_ptr.Esp), | ||
| 150 | 5 => mem.asBytes(&thread_context_ptr.Ebp), | ||
| 151 | 6 => mem.asBytes(&thread_context_ptr.Esi), | ||
| 152 | 7 => mem.asBytes(&thread_context_ptr.Edi), | ||
| 153 | 8 => mem.asBytes(&thread_context_ptr.Eip), | ||
| 154 | 9 => mem.asBytes(&thread_context_ptr.EFlags), | ||
| 155 | 10 => mem.asBytes(&thread_context_ptr.SegCs), | ||
| 156 | 11 => mem.asBytes(&thread_context_ptr.SegSs), | ||
| 157 | 12 => mem.asBytes(&thread_context_ptr.SegDs), | ||
| 158 | 13 => mem.asBytes(&thread_context_ptr.SegEs), | ||
| 159 | 14 => mem.asBytes(&thread_context_ptr.SegFs), | ||
| 160 | 15 => mem.asBytes(&thread_context_ptr.SegGs), | ||
| 161 | else => error.InvalidRegister, | ||
| 162 | }, | ||
| 163 | .x86_64 => switch (reg_number) { | ||
| 164 | 0 => mem.asBytes(&thread_context_ptr.Rax), | ||
| 165 | 1 => mem.asBytes(&thread_context_ptr.Rdx), | ||
| 166 | 2 => mem.asBytes(&thread_context_ptr.Rcx), | ||
| 167 | 3 => mem.asBytes(&thread_context_ptr.Rbx), | ||
| 168 | 4 => mem.asBytes(&thread_context_ptr.Rsi), | ||
| 169 | 5 => mem.asBytes(&thread_context_ptr.Rdi), | ||
| 170 | 6 => mem.asBytes(&thread_context_ptr.Rbp), | ||
| 171 | 7 => mem.asBytes(&thread_context_ptr.Rsp), | ||
| 172 | 8 => mem.asBytes(&thread_context_ptr.R8), | ||
| 173 | 9 => mem.asBytes(&thread_context_ptr.R9), | ||
| 174 | 10 => mem.asBytes(&thread_context_ptr.R10), | ||
| 175 | 11 => mem.asBytes(&thread_context_ptr.R11), | ||
| 176 | 12 => mem.asBytes(&thread_context_ptr.R12), | ||
| 177 | 13 => mem.asBytes(&thread_context_ptr.R13), | ||
| 178 | 14 => mem.asBytes(&thread_context_ptr.R14), | ||
| 179 | 15 => mem.asBytes(&thread_context_ptr.R15), | ||
| 180 | 16 => mem.asBytes(&thread_context_ptr.Rip), | ||
| 181 | else => error.InvalidRegister, | ||
| 182 | }, | ||
| 183 | .aarch64 => switch (reg_number) { | ||
| 184 | 0...30 => mem.asBytes(&thread_context_ptr.DUMMYUNIONNAME.X[reg_number]), | ||
| 185 | 31 => mem.asBytes(&thread_context_ptr.Sp), | ||
| 186 | 32 => mem.asBytes(&thread_context_ptr.Pc), | ||
| 187 | else => error.InvalidRegister, | ||
| 188 | }, | ||
| 189 | else => error.UnimplementedArch, | ||
| 190 | }; | ||
| 191 | } | ||
| 192 | |||
| 193 | if (!std.debug.have_ucontext) return error.ThreadContextNotSupported; | ||
| 194 | |||
| 195 | const ucontext_ptr = thread_context_ptr; | ||
| 196 | return switch (builtin.cpu.arch) { | ||
| 197 | .x86 => switch (native_os) { | ||
| 198 | .linux, .netbsd, .solaris, .illumos => switch (reg_number) { | ||
| 199 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EAX]), | ||
| 200 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ECX]), | ||
| 201 | 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDX]), | ||
| 202 | 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBX]), | ||
| 203 | 4...5 => if (reg_context) |r| bytes: { | ||
| 204 | if (reg_number == 4) { | ||
| 205 | break :bytes if (r.eh_frame and r.is_macho) | ||
| 206 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]) | ||
| 207 | else | ||
| 208 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]); | ||
| 209 | } else { | ||
| 210 | break :bytes if (r.eh_frame and r.is_macho) | ||
| 211 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESP]) | ||
| 212 | else | ||
| 213 | mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EBP]); | ||
| 214 | } | ||
| 215 | } else error.RegisterContextRequired, | ||
| 216 | 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ESI]), | ||
| 217 | 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EDI]), | ||
| 218 | 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EIP]), | ||
| 219 | 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.EFL]), | ||
| 220 | 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.CS]), | ||
| 221 | 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.SS]), | ||
| 222 | 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.DS]), | ||
| 223 | 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.ES]), | ||
| 224 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.FS]), | ||
| 225 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.GS]), | ||
| 226 | 16...23 => error.InvalidRegister, // TODO: Support loading ST0-ST7 from mcontext.fpregs | ||
| 227 | 32...39 => error.InvalidRegister, // TODO: Support loading XMM0-XMM7 from mcontext.fpregs | ||
| 228 | else => error.InvalidRegister, | ||
| 229 | }, | ||
| 230 | else => error.UnimplementedOs, | ||
| 231 | }, | ||
| 232 | .x86_64 => switch (native_os) { | ||
| 233 | .linux, .solaris, .illumos => switch (reg_number) { | ||
| 234 | 0 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RAX]), | ||
| 235 | 1 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDX]), | ||
| 236 | 2 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RCX]), | ||
| 237 | 3 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBX]), | ||
| 238 | 4 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSI]), | ||
| 239 | 5 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RDI]), | ||
| 240 | 6 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RBP]), | ||
| 241 | 7 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RSP]), | ||
| 242 | 8 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R8]), | ||
| 243 | 9 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R9]), | ||
| 244 | 10 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R10]), | ||
| 245 | 11 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R11]), | ||
| 246 | 12 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R12]), | ||
| 247 | 13 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R13]), | ||
| 248 | 14 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R14]), | ||
| 249 | 15 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.R15]), | ||
| 250 | 16 => mem.asBytes(&ucontext_ptr.mcontext.gregs[posix.REG.RIP]), | ||
| 251 | 17...32 => |i| if (native_os.isSolarish()) | ||
| 252 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.chip_state.xmm[i - 17]) | ||
| 253 | else | ||
| 254 | mem.asBytes(&ucontext_ptr.mcontext.fpregs.xmm[i - 17]), | ||
| 255 | else => error.InvalidRegister, | ||
| 256 | }, | ||
| 257 | .freebsd => switch (reg_number) { | ||
| 258 | 0 => mem.asBytes(&ucontext_ptr.mcontext.rax), | ||
| 259 | 1 => mem.asBytes(&ucontext_ptr.mcontext.rdx), | ||
| 260 | 2 => mem.asBytes(&ucontext_ptr.mcontext.rcx), | ||
| 261 | 3 => mem.asBytes(&ucontext_ptr.mcontext.rbx), | ||
| 262 | 4 => mem.asBytes(&ucontext_ptr.mcontext.rsi), | ||
| 263 | 5 => mem.asBytes(&ucontext_ptr.mcontext.rdi), | ||
| 264 | 6 => mem.asBytes(&ucontext_ptr.mcontext.rbp), | ||
| 265 | 7 => mem.asBytes(&ucontext_ptr.mcontext.rsp), | ||
| 266 | 8 => mem.asBytes(&ucontext_ptr.mcontext.r8), | ||
| 267 | 9 => mem.asBytes(&ucontext_ptr.mcontext.r9), | ||
| 268 | 10 => mem.asBytes(&ucontext_ptr.mcontext.r10), | ||
| 269 | 11 => mem.asBytes(&ucontext_ptr.mcontext.r11), | ||
| 270 | 12 => mem.asBytes(&ucontext_ptr.mcontext.r12), | ||
| 271 | 13 => mem.asBytes(&ucontext_ptr.mcontext.r13), | ||
| 272 | 14 => mem.asBytes(&ucontext_ptr.mcontext.r14), | ||
| 273 | 15 => mem.asBytes(&ucontext_ptr.mcontext.r15), | ||
| 274 | 16 => mem.asBytes(&ucontext_ptr.mcontext.rip), | ||
| 275 | // TODO: Extract xmm state from mcontext.fpstate? | ||
| 276 | else => error.InvalidRegister, | ||
| 277 | }, | ||
| 278 | .openbsd => switch (reg_number) { | ||
| 279 | 0 => mem.asBytes(&ucontext_ptr.sc_rax), | ||
| 280 | 1 => mem.asBytes(&ucontext_ptr.sc_rdx), | ||
| 281 | 2 => mem.asBytes(&ucontext_ptr.sc_rcx), | ||
| 282 | 3 => mem.asBytes(&ucontext_ptr.sc_rbx), | ||
| 283 | 4 => mem.asBytes(&ucontext_ptr.sc_rsi), | ||
| 284 | 5 => mem.asBytes(&ucontext_ptr.sc_rdi), | ||
| 285 | 6 => mem.asBytes(&ucontext_ptr.sc_rbp), | ||
| 286 | 7 => mem.asBytes(&ucontext_ptr.sc_rsp), | ||
| 287 | 8 => mem.asBytes(&ucontext_ptr.sc_r8), | ||
| 288 | 9 => mem.asBytes(&ucontext_ptr.sc_r9), | ||
| 289 | 10 => mem.asBytes(&ucontext_ptr.sc_r10), | ||
| 290 | 11 => mem.asBytes(&ucontext_ptr.sc_r11), | ||
| 291 | 12 => mem.asBytes(&ucontext_ptr.sc_r12), | ||
| 292 | 13 => mem.asBytes(&ucontext_ptr.sc_r13), | ||
| 293 | 14 => mem.asBytes(&ucontext_ptr.sc_r14), | ||
| 294 | 15 => mem.asBytes(&ucontext_ptr.sc_r15), | ||
| 295 | 16 => mem.asBytes(&ucontext_ptr.sc_rip), | ||
| 296 | // TODO: Extract xmm state from sc_fpstate? | ||
| 297 | else => error.InvalidRegister, | ||
| 298 | }, | ||
| 299 | .macos, .ios => switch (reg_number) { | ||
| 300 | 0 => mem.asBytes(&ucontext_ptr.mcontext.ss.rax), | ||
| 301 | 1 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdx), | ||
| 302 | 2 => mem.asBytes(&ucontext_ptr.mcontext.ss.rcx), | ||
| 303 | 3 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbx), | ||
| 304 | 4 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsi), | ||
| 305 | 5 => mem.asBytes(&ucontext_ptr.mcontext.ss.rdi), | ||
| 306 | 6 => mem.asBytes(&ucontext_ptr.mcontext.ss.rbp), | ||
| 307 | 7 => mem.asBytes(&ucontext_ptr.mcontext.ss.rsp), | ||
| 308 | 8 => mem.asBytes(&ucontext_ptr.mcontext.ss.r8), | ||
| 309 | 9 => mem.asBytes(&ucontext_ptr.mcontext.ss.r9), | ||
| 310 | 10 => mem.asBytes(&ucontext_ptr.mcontext.ss.r10), | ||
| 311 | 11 => mem.asBytes(&ucontext_ptr.mcontext.ss.r11), | ||
| 312 | 12 => mem.asBytes(&ucontext_ptr.mcontext.ss.r12), | ||
| 313 | 13 => mem.asBytes(&ucontext_ptr.mcontext.ss.r13), | ||
| 314 | 14 => mem.asBytes(&ucontext_ptr.mcontext.ss.r14), | ||
| 315 | 15 => mem.asBytes(&ucontext_ptr.mcontext.ss.r15), | ||
| 316 | 16 => mem.asBytes(&ucontext_ptr.mcontext.ss.rip), | ||
| 317 | else => error.InvalidRegister, | ||
| 318 | }, | ||
| 319 | else => error.UnimplementedOs, | ||
| 320 | }, | ||
| 321 | .arm => switch (native_os) { | ||
| 322 | .linux => switch (reg_number) { | ||
| 323 | 0 => mem.asBytes(&ucontext_ptr.mcontext.arm_r0), | ||
| 324 | 1 => mem.asBytes(&ucontext_ptr.mcontext.arm_r1), | ||
| 325 | 2 => mem.asBytes(&ucontext_ptr.mcontext.arm_r2), | ||
| 326 | 3 => mem.asBytes(&ucontext_ptr.mcontext.arm_r3), | ||
| 327 | 4 => mem.asBytes(&ucontext_ptr.mcontext.arm_r4), | ||
| 328 | 5 => mem.asBytes(&ucontext_ptr.mcontext.arm_r5), | ||
| 329 | 6 => mem.asBytes(&ucontext_ptr.mcontext.arm_r6), | ||
| 330 | 7 => mem.asBytes(&ucontext_ptr.mcontext.arm_r7), | ||
| 331 | 8 => mem.asBytes(&ucontext_ptr.mcontext.arm_r8), | ||
| 332 | 9 => mem.asBytes(&ucontext_ptr.mcontext.arm_r9), | ||
| 333 | 10 => mem.asBytes(&ucontext_ptr.mcontext.arm_r10), | ||
| 334 | 11 => mem.asBytes(&ucontext_ptr.mcontext.arm_fp), | ||
| 335 | 12 => mem.asBytes(&ucontext_ptr.mcontext.arm_ip), | ||
| 336 | 13 => mem.asBytes(&ucontext_ptr.mcontext.arm_sp), | ||
| 337 | 14 => mem.asBytes(&ucontext_ptr.mcontext.arm_lr), | ||
| 338 | 15 => mem.asBytes(&ucontext_ptr.mcontext.arm_pc), | ||
| 339 | // CPSR is not allocated a register number (See: https://github.com/ARM-software/abi-aa/blob/main/aadwarf32/aadwarf32.rst, Section 4.1) | ||
| 340 | else => error.InvalidRegister, | ||
| 341 | }, | ||
| 342 | else => error.UnimplementedOs, | ||
| 343 | }, | ||
| 344 | .aarch64 => switch (native_os) { | ||
| 345 | .macos, .ios => switch (reg_number) { | ||
| 346 | 0...28 => mem.asBytes(&ucontext_ptr.mcontext.ss.regs[reg_number]), | ||
| 347 | 29 => mem.asBytes(&ucontext_ptr.mcontext.ss.fp), | ||
| 348 | 30 => mem.asBytes(&ucontext_ptr.mcontext.ss.lr), | ||
| 349 | 31 => mem.asBytes(&ucontext_ptr.mcontext.ss.sp), | ||
| 350 | 32 => mem.asBytes(&ucontext_ptr.mcontext.ss.pc), | ||
| 351 | |||
| 352 | // TODO: Find storage for this state | ||
| 353 | //34 => mem.asBytes(&ucontext_ptr.ra_sign_state), | ||
| 354 | |||
| 355 | // V0-V31 | ||
| 356 | 64...95 => mem.asBytes(&ucontext_ptr.mcontext.ns.q[reg_number - 64]), | ||
| 357 | else => error.InvalidRegister, | ||
| 358 | }, | ||
| 359 | .netbsd => switch (reg_number) { | ||
| 360 | 0...34 => mem.asBytes(&ucontext_ptr.mcontext.gregs[reg_number]), | ||
| 361 | else => error.InvalidRegister, | ||
| 362 | }, | ||
| 363 | .freebsd => switch (reg_number) { | ||
| 364 | 0...29 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.x[reg_number]), | ||
| 365 | 30 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.lr), | ||
| 366 | 31 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.sp), | ||
| 367 | |||
| 368 | // TODO: This seems wrong, but it was in the previous debug.zig code for mapping PC, check this | ||
| 369 | 32 => mem.asBytes(&ucontext_ptr.mcontext.gpregs.elr), | ||
| 370 | |||
| 371 | else => error.InvalidRegister, | ||
| 372 | }, | ||
| 373 | .openbsd => switch (reg_number) { | ||
| 374 | 0...30 => mem.asBytes(&ucontext_ptr.sc_x[reg_number]), | ||
| 375 | 31 => mem.asBytes(&ucontext_ptr.sc_sp), | ||
| 376 | 32 => mem.asBytes(&ucontext_ptr.sc_lr), | ||
| 377 | 33 => mem.asBytes(&ucontext_ptr.sc_elr), | ||
| 378 | 34 => mem.asBytes(&ucontext_ptr.sc_spsr), | ||
| 379 | else => error.InvalidRegister, | ||
| 380 | }, | ||
| 381 | else => switch (reg_number) { | ||
| 382 | 0...30 => mem.asBytes(&ucontext_ptr.mcontext.regs[reg_number]), | ||
| 383 | 31 => mem.asBytes(&ucontext_ptr.mcontext.sp), | ||
| 384 | 32 => mem.asBytes(&ucontext_ptr.mcontext.pc), | ||
| 385 | else => error.InvalidRegister, | ||
| 386 | }, | ||
| 387 | }, | ||
| 388 | else => error.UnimplementedArch, | ||
| 389 | }; | ||
| 390 | } | ||
| 391 | |||
| 392 | /// Returns the ABI-defined default value this register has in the unwinding table | ||
| 393 | /// before running any of the CIE instructions. The DWARF spec defines these as having | ||
| 394 | /// the .undefined rule by default, but allows ABI authors to override that. | ||
| 395 | pub fn getRegDefaultValue(reg_number: u8, context: *std.dwarf.UnwindContext, out: []u8) !void { | ||
| 396 | switch (builtin.cpu.arch) { | ||
| 397 | .aarch64 => { | ||
| 398 | // Callee-saved registers are initialized as if they had the .same_value rule | ||
| 399 | if (reg_number >= 19 and reg_number <= 28) { | ||
| 400 | const src = try regBytes(context.thread_context, reg_number, context.reg_context); | ||
| 401 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 402 | @memcpy(out, src); | ||
| 403 | return; | ||
| 404 | } | ||
| 405 | }, | ||
| 406 | else => {}, | ||
| 407 | } | ||
| 408 | |||
| 409 | @memset(out, undefined); | ||
| 410 | } | ||
lib/std/dwarf/call_frame.zig deleted-687| ... | @@ -1,687 +0,0 @@ | ||
| 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 | const native_endian = builtin.cpu.arch.endian(); | ||
| 11 | |||
| 12 | const Opcode = enum(u8) { | ||
| 13 | advance_loc = 0x1 << 6, | ||
| 14 | offset = 0x2 << 6, | ||
| 15 | restore = 0x3 << 6, | ||
| 16 | |||
| 17 | nop = 0x00, | ||
| 18 | set_loc = 0x01, | ||
| 19 | advance_loc1 = 0x02, | ||
| 20 | advance_loc2 = 0x03, | ||
| 21 | advance_loc4 = 0x04, | ||
| 22 | offset_extended = 0x05, | ||
| 23 | restore_extended = 0x06, | ||
| 24 | undefined = 0x07, | ||
| 25 | same_value = 0x08, | ||
| 26 | register = 0x09, | ||
| 27 | remember_state = 0x0a, | ||
| 28 | restore_state = 0x0b, | ||
| 29 | def_cfa = 0x0c, | ||
| 30 | def_cfa_register = 0x0d, | ||
| 31 | def_cfa_offset = 0x0e, | ||
| 32 | def_cfa_expression = 0x0f, | ||
| 33 | expression = 0x10, | ||
| 34 | offset_extended_sf = 0x11, | ||
| 35 | def_cfa_sf = 0x12, | ||
| 36 | def_cfa_offset_sf = 0x13, | ||
| 37 | val_offset = 0x14, | ||
| 38 | val_offset_sf = 0x15, | ||
| 39 | val_expression = 0x16, | ||
| 40 | |||
| 41 | // These opcodes encode an operand in the lower 6 bits of the opcode itself | ||
| 42 | pub const lo_inline = @intFromEnum(Opcode.advance_loc); | ||
| 43 | pub const hi_inline = @intFromEnum(Opcode.restore) | 0b111111; | ||
| 44 | |||
| 45 | // These opcodes are trailed by zero or more operands | ||
| 46 | pub const lo_reserved = @intFromEnum(Opcode.nop); | ||
| 47 | pub const hi_reserved = @intFromEnum(Opcode.val_expression); | ||
| 48 | |||
| 49 | // Vendor-specific opcodes | ||
| 50 | pub const lo_user = 0x1c; | ||
| 51 | pub const hi_user = 0x3f; | ||
| 52 | }; | ||
| 53 | |||
| 54 | fn readBlock(stream: *std.io.FixedBufferStream([]const u8)) ![]const u8 { | ||
| 55 | const reader = stream.reader(); | ||
| 56 | const block_len = try leb.readUleb128(usize, reader); | ||
| 57 | if (stream.pos + block_len > stream.buffer.len) return error.InvalidOperand; | ||
| 58 | |||
| 59 | const block = stream.buffer[stream.pos..][0..block_len]; | ||
| 60 | reader.context.pos += block_len; | ||
| 61 | |||
| 62 | return block; | ||
| 63 | } | ||
| 64 | |||
| 65 | pub const Instruction = union(Opcode) { | ||
| 66 | advance_loc: struct { | ||
| 67 | delta: u8, | ||
| 68 | }, | ||
| 69 | offset: struct { | ||
| 70 | register: u8, | ||
| 71 | offset: u64, | ||
| 72 | }, | ||
| 73 | restore: struct { | ||
| 74 | register: u8, | ||
| 75 | }, | ||
| 76 | nop: void, | ||
| 77 | set_loc: struct { | ||
| 78 | address: u64, | ||
| 79 | }, | ||
| 80 | advance_loc1: struct { | ||
| 81 | delta: u8, | ||
| 82 | }, | ||
| 83 | advance_loc2: struct { | ||
| 84 | delta: u16, | ||
| 85 | }, | ||
| 86 | advance_loc4: struct { | ||
| 87 | delta: u32, | ||
| 88 | }, | ||
| 89 | offset_extended: struct { | ||
| 90 | register: u8, | ||
| 91 | offset: u64, | ||
| 92 | }, | ||
| 93 | restore_extended: struct { | ||
| 94 | register: u8, | ||
| 95 | }, | ||
| 96 | undefined: struct { | ||
| 97 | register: u8, | ||
| 98 | }, | ||
| 99 | same_value: struct { | ||
| 100 | register: u8, | ||
| 101 | }, | ||
| 102 | register: struct { | ||
| 103 | register: u8, | ||
| 104 | target_register: u8, | ||
| 105 | }, | ||
| 106 | remember_state: void, | ||
| 107 | restore_state: void, | ||
| 108 | def_cfa: struct { | ||
| 109 | register: u8, | ||
| 110 | offset: u64, | ||
| 111 | }, | ||
| 112 | def_cfa_register: struct { | ||
| 113 | register: u8, | ||
| 114 | }, | ||
| 115 | def_cfa_offset: struct { | ||
| 116 | offset: u64, | ||
| 117 | }, | ||
| 118 | def_cfa_expression: struct { | ||
| 119 | block: []const u8, | ||
| 120 | }, | ||
| 121 | expression: struct { | ||
| 122 | register: u8, | ||
| 123 | block: []const u8, | ||
| 124 | }, | ||
| 125 | offset_extended_sf: struct { | ||
| 126 | register: u8, | ||
| 127 | offset: i64, | ||
| 128 | }, | ||
| 129 | def_cfa_sf: struct { | ||
| 130 | register: u8, | ||
| 131 | offset: i64, | ||
| 132 | }, | ||
| 133 | def_cfa_offset_sf: struct { | ||
| 134 | offset: i64, | ||
| 135 | }, | ||
| 136 | val_offset: struct { | ||
| 137 | register: u8, | ||
| 138 | offset: u64, | ||
| 139 | }, | ||
| 140 | val_offset_sf: struct { | ||
| 141 | register: u8, | ||
| 142 | offset: i64, | ||
| 143 | }, | ||
| 144 | val_expression: struct { | ||
| 145 | register: u8, | ||
| 146 | block: []const u8, | ||
| 147 | }, | ||
| 148 | |||
| 149 | pub fn read( | ||
| 150 | stream: *std.io.FixedBufferStream([]const u8), | ||
| 151 | addr_size_bytes: u8, | ||
| 152 | endian: std.builtin.Endian, | ||
| 153 | ) !Instruction { | ||
| 154 | const reader = stream.reader(); | ||
| 155 | switch (try reader.readByte()) { | ||
| 156 | Opcode.lo_inline...Opcode.hi_inline => |opcode| { | ||
| 157 | const e: Opcode = @enumFromInt(opcode & 0b11000000); | ||
| 158 | const value: u6 = @intCast(opcode & 0b111111); | ||
| 159 | return switch (e) { | ||
| 160 | .advance_loc => .{ | ||
| 161 | .advance_loc = .{ .delta = value }, | ||
| 162 | }, | ||
| 163 | .offset => .{ | ||
| 164 | .offset = .{ | ||
| 165 | .register = value, | ||
| 166 | .offset = try leb.readUleb128(u64, reader), | ||
| 167 | }, | ||
| 168 | }, | ||
| 169 | .restore => .{ | ||
| 170 | .restore = .{ .register = value }, | ||
| 171 | }, | ||
| 172 | else => unreachable, | ||
| 173 | }; | ||
| 174 | }, | ||
| 175 | Opcode.lo_reserved...Opcode.hi_reserved => |opcode| { | ||
| 176 | const e: Opcode = @enumFromInt(opcode); | ||
| 177 | return switch (e) { | ||
| 178 | .advance_loc, | ||
| 179 | .offset, | ||
| 180 | .restore, | ||
| 181 | => unreachable, | ||
| 182 | .nop => .{ .nop = {} }, | ||
| 183 | .set_loc => .{ | ||
| 184 | .set_loc = .{ | ||
| 185 | .address = switch (addr_size_bytes) { | ||
| 186 | 2 => try reader.readInt(u16, endian), | ||
| 187 | 4 => try reader.readInt(u32, endian), | ||
| 188 | 8 => try reader.readInt(u64, endian), | ||
| 189 | else => return error.InvalidAddrSize, | ||
| 190 | }, | ||
| 191 | }, | ||
| 192 | }, | ||
| 193 | .advance_loc1 => .{ | ||
| 194 | .advance_loc1 = .{ .delta = try reader.readByte() }, | ||
| 195 | }, | ||
| 196 | .advance_loc2 => .{ | ||
| 197 | .advance_loc2 = .{ .delta = try reader.readInt(u16, endian) }, | ||
| 198 | }, | ||
| 199 | .advance_loc4 => .{ | ||
| 200 | .advance_loc4 = .{ .delta = try reader.readInt(u32, endian) }, | ||
| 201 | }, | ||
| 202 | .offset_extended => .{ | ||
| 203 | .offset_extended = .{ | ||
| 204 | .register = try leb.readUleb128(u8, reader), | ||
| 205 | .offset = try leb.readUleb128(u64, reader), | ||
| 206 | }, | ||
| 207 | }, | ||
| 208 | .restore_extended => .{ | ||
| 209 | .restore_extended = .{ | ||
| 210 | .register = try leb.readUleb128(u8, reader), | ||
| 211 | }, | ||
| 212 | }, | ||
| 213 | .undefined => .{ | ||
| 214 | .undefined = .{ | ||
| 215 | .register = try leb.readUleb128(u8, reader), | ||
| 216 | }, | ||
| 217 | }, | ||
| 218 | .same_value => .{ | ||
| 219 | .same_value = .{ | ||
| 220 | .register = try leb.readUleb128(u8, reader), | ||
| 221 | }, | ||
| 222 | }, | ||
| 223 | .register => .{ | ||
| 224 | .register = .{ | ||
| 225 | .register = try leb.readUleb128(u8, reader), | ||
| 226 | .target_register = try leb.readUleb128(u8, reader), | ||
| 227 | }, | ||
| 228 | }, | ||
| 229 | .remember_state => .{ .remember_state = {} }, | ||
| 230 | .restore_state => .{ .restore_state = {} }, | ||
| 231 | .def_cfa => .{ | ||
| 232 | .def_cfa = .{ | ||
| 233 | .register = try leb.readUleb128(u8, reader), | ||
| 234 | .offset = try leb.readUleb128(u64, reader), | ||
| 235 | }, | ||
| 236 | }, | ||
| 237 | .def_cfa_register => .{ | ||
| 238 | .def_cfa_register = .{ | ||
| 239 | .register = try leb.readUleb128(u8, reader), | ||
| 240 | }, | ||
| 241 | }, | ||
| 242 | .def_cfa_offset => .{ | ||
| 243 | .def_cfa_offset = .{ | ||
| 244 | .offset = try leb.readUleb128(u64, reader), | ||
| 245 | }, | ||
| 246 | }, | ||
| 247 | .def_cfa_expression => .{ | ||
| 248 | .def_cfa_expression = .{ | ||
| 249 | .block = try readBlock(stream), | ||
| 250 | }, | ||
| 251 | }, | ||
| 252 | .expression => .{ | ||
| 253 | .expression = .{ | ||
| 254 | .register = try leb.readUleb128(u8, reader), | ||
| 255 | .block = try readBlock(stream), | ||
| 256 | }, | ||
| 257 | }, | ||
| 258 | .offset_extended_sf => .{ | ||
| 259 | .offset_extended_sf = .{ | ||
| 260 | .register = try leb.readUleb128(u8, reader), | ||
| 261 | .offset = try leb.readIleb128(i64, reader), | ||
| 262 | }, | ||
| 263 | }, | ||
| 264 | .def_cfa_sf => .{ | ||
| 265 | .def_cfa_sf = .{ | ||
| 266 | .register = try leb.readUleb128(u8, reader), | ||
| 267 | .offset = try leb.readIleb128(i64, reader), | ||
| 268 | }, | ||
| 269 | }, | ||
| 270 | .def_cfa_offset_sf => .{ | ||
| 271 | .def_cfa_offset_sf = .{ | ||
| 272 | .offset = try leb.readIleb128(i64, reader), | ||
| 273 | }, | ||
| 274 | }, | ||
| 275 | .val_offset => .{ | ||
| 276 | .val_offset = .{ | ||
| 277 | .register = try leb.readUleb128(u8, reader), | ||
| 278 | .offset = try leb.readUleb128(u64, reader), | ||
| 279 | }, | ||
| 280 | }, | ||
| 281 | .val_offset_sf => .{ | ||
| 282 | .val_offset_sf = .{ | ||
| 283 | .register = try leb.readUleb128(u8, reader), | ||
| 284 | .offset = try leb.readIleb128(i64, reader), | ||
| 285 | }, | ||
| 286 | }, | ||
| 287 | .val_expression => .{ | ||
| 288 | .val_expression = .{ | ||
| 289 | .register = try leb.readUleb128(u8, reader), | ||
| 290 | .block = try readBlock(stream), | ||
| 291 | }, | ||
| 292 | }, | ||
| 293 | }; | ||
| 294 | }, | ||
| 295 | Opcode.lo_user...Opcode.hi_user => return error.UnimplementedUserOpcode, | ||
| 296 | else => return error.InvalidOpcode, | ||
| 297 | } | ||
| 298 | } | ||
| 299 | }; | ||
| 300 | |||
| 301 | /// Since register rules are applied (usually) during a panic, | ||
| 302 | /// checked addition / subtraction is used so that we can return | ||
| 303 | /// an error and fall back to FP-based unwinding. | ||
| 304 | pub fn applyOffset(base: usize, offset: i64) !usize { | ||
| 305 | return if (offset >= 0) | ||
| 306 | try std.math.add(usize, base, @as(usize, @intCast(offset))) | ||
| 307 | else | ||
| 308 | try std.math.sub(usize, base, @as(usize, @intCast(-offset))); | ||
| 309 | } | ||
| 310 | |||
| 311 | /// This is a virtual machine that runs DWARF call frame instructions. | ||
| 312 | pub const VirtualMachine = struct { | ||
| 313 | /// See section 6.4.1 of the DWARF5 specification for details on each | ||
| 314 | const RegisterRule = union(enum) { | ||
| 315 | // The spec says that the default rule for each column is the undefined rule. | ||
| 316 | // However, it also allows ABI / compiler authors to specify alternate defaults, so | ||
| 317 | // there is a distinction made here. | ||
| 318 | default: void, | ||
| 319 | |||
| 320 | undefined: void, | ||
| 321 | same_value: void, | ||
| 322 | |||
| 323 | // offset(N) | ||
| 324 | offset: i64, | ||
| 325 | |||
| 326 | // val_offset(N) | ||
| 327 | val_offset: i64, | ||
| 328 | |||
| 329 | // register(R) | ||
| 330 | register: u8, | ||
| 331 | |||
| 332 | // expression(E) | ||
| 333 | expression: []const u8, | ||
| 334 | |||
| 335 | // val_expression(E) | ||
| 336 | val_expression: []const u8, | ||
| 337 | |||
| 338 | // Augmenter-defined rule | ||
| 339 | architectural: void, | ||
| 340 | }; | ||
| 341 | |||
| 342 | /// Each row contains unwinding rules for a set of registers. | ||
| 343 | pub const Row = struct { | ||
| 344 | /// Offset from `FrameDescriptionEntry.pc_begin` | ||
| 345 | offset: u64 = 0, | ||
| 346 | |||
| 347 | /// Special-case column that defines the CFA (Canonical Frame Address) rule. | ||
| 348 | /// The register field of this column defines the register that CFA is derived from. | ||
| 349 | cfa: Column = .{}, | ||
| 350 | |||
| 351 | /// The register fields in these columns define the register the rule applies to. | ||
| 352 | columns: ColumnRange = .{}, | ||
| 353 | |||
| 354 | /// Indicates that the next write to any column in this row needs to copy | ||
| 355 | /// the backing column storage first, as it may be referenced by previous rows. | ||
| 356 | copy_on_write: bool = false, | ||
| 357 | }; | ||
| 358 | |||
| 359 | pub const Column = struct { | ||
| 360 | register: ?u8 = null, | ||
| 361 | rule: RegisterRule = .{ .default = {} }, | ||
| 362 | |||
| 363 | /// Resolves the register rule and places the result into `out` (see dwarf.abi.regBytes) | ||
| 364 | pub fn resolveValue( | ||
| 365 | self: Column, | ||
| 366 | context: *dwarf.UnwindContext, | ||
| 367 | expression_context: dwarf.expressions.ExpressionContext, | ||
| 368 | ma: *debug.StackIterator.MemoryAccessor, | ||
| 369 | out: []u8, | ||
| 370 | ) !void { | ||
| 371 | switch (self.rule) { | ||
| 372 | .default => { | ||
| 373 | const register = self.register orelse return error.InvalidRegister; | ||
| 374 | try abi.getRegDefaultValue(register, context, out); | ||
| 375 | }, | ||
| 376 | .undefined => { | ||
| 377 | @memset(out, undefined); | ||
| 378 | }, | ||
| 379 | .same_value => { | ||
| 380 | // TODO: This copy could be eliminated if callers always copy the state then call this function to update it | ||
| 381 | const register = self.register orelse return error.InvalidRegister; | ||
| 382 | const src = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 383 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 384 | @memcpy(out, src); | ||
| 385 | }, | ||
| 386 | .offset => |offset| { | ||
| 387 | if (context.cfa) |cfa| { | ||
| 388 | const addr = try applyOffset(cfa, offset); | ||
| 389 | if (ma.load(usize, addr) == null) return error.InvalidAddress; | ||
| 390 | const ptr: *const usize = @ptrFromInt(addr); | ||
| 391 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | ||
| 392 | } else return error.InvalidCFA; | ||
| 393 | }, | ||
| 394 | .val_offset => |offset| { | ||
| 395 | if (context.cfa) |cfa| { | ||
| 396 | mem.writeInt(usize, out[0..@sizeOf(usize)], try applyOffset(cfa, offset), native_endian); | ||
| 397 | } else return error.InvalidCFA; | ||
| 398 | }, | ||
| 399 | .register => |register| { | ||
| 400 | const src = try abi.regBytes(context.thread_context, register, context.reg_context); | ||
| 401 | if (src.len != out.len) return error.RegisterSizeMismatch; | ||
| 402 | @memcpy(out, try abi.regBytes(context.thread_context, register, context.reg_context)); | ||
| 403 | }, | ||
| 404 | .expression => |expression| { | ||
| 405 | context.stack_machine.reset(); | ||
| 406 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 407 | const addr = if (value) |v| blk: { | ||
| 408 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 409 | break :blk v.generic; | ||
| 410 | } else return error.NoExpressionValue; | ||
| 411 | |||
| 412 | if (ma.load(usize, addr) == null) return error.InvalidExpressionAddress; | ||
| 413 | const ptr: *usize = @ptrFromInt(addr); | ||
| 414 | mem.writeInt(usize, out[0..@sizeOf(usize)], ptr.*, native_endian); | ||
| 415 | }, | ||
| 416 | .val_expression => |expression| { | ||
| 417 | context.stack_machine.reset(); | ||
| 418 | const value = try context.stack_machine.run(expression, context.allocator, expression_context, context.cfa.?); | ||
| 419 | if (value) |v| { | ||
| 420 | if (v != .generic) return error.InvalidExpressionValue; | ||
| 421 | mem.writeInt(usize, out[0..@sizeOf(usize)], v.generic, native_endian); | ||
| 422 | } else return error.NoExpressionValue; | ||
| 423 | }, | ||
| 424 | .architectural => return error.UnimplementedRegisterRule, | ||
| 425 | } | ||
| 426 | } | ||
| 427 | }; | ||
| 428 | |||
| 429 | const ColumnRange = struct { | ||
| 430 | /// Index into `columns` of the first column in this row. | ||
| 431 | start: usize = undefined, | ||
| 432 | len: u8 = 0, | ||
| 433 | }; | ||
| 434 | |||
| 435 | columns: std.ArrayListUnmanaged(Column) = .{}, | ||
| 436 | stack: std.ArrayListUnmanaged(ColumnRange) = .{}, | ||
| 437 | current_row: Row = .{}, | ||
| 438 | |||
| 439 | /// The result of executing the CIE's initial_instructions | ||
| 440 | cie_row: ?Row = null, | ||
| 441 | |||
| 442 | pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void { | ||
| 443 | self.stack.deinit(allocator); | ||
| 444 | self.columns.deinit(allocator); | ||
| 445 | self.* = undefined; | ||
| 446 | } | ||
| 447 | |||
| 448 | pub fn reset(self: *VirtualMachine) void { | ||
| 449 | self.stack.clearRetainingCapacity(); | ||
| 450 | self.columns.clearRetainingCapacity(); | ||
| 451 | self.current_row = .{}; | ||
| 452 | self.cie_row = null; | ||
| 453 | } | ||
| 454 | |||
| 455 | /// Return a slice backed by the row's non-CFA columns | ||
| 456 | pub fn rowColumns(self: VirtualMachine, row: Row) []Column { | ||
| 457 | if (row.columns.len == 0) return &.{}; | ||
| 458 | return self.columns.items[row.columns.start..][0..row.columns.len]; | ||
| 459 | } | ||
| 460 | |||
| 461 | /// Either retrieves or adds a column for `register` (non-CFA) in the current row. | ||
| 462 | fn getOrAddColumn(self: *VirtualMachine, allocator: std.mem.Allocator, register: u8) !*Column { | ||
| 463 | for (self.rowColumns(self.current_row)) |*c| { | ||
| 464 | if (c.register == register) return c; | ||
| 465 | } | ||
| 466 | |||
| 467 | if (self.current_row.columns.len == 0) { | ||
| 468 | self.current_row.columns.start = self.columns.items.len; | ||
| 469 | } | ||
| 470 | self.current_row.columns.len += 1; | ||
| 471 | |||
| 472 | const column = try self.columns.addOne(allocator); | ||
| 473 | column.* = .{ | ||
| 474 | .register = register, | ||
| 475 | }; | ||
| 476 | |||
| 477 | return column; | ||
| 478 | } | ||
| 479 | |||
| 480 | /// Runs the CIE instructions, then the FDE instructions. Execution halts | ||
| 481 | /// once the row that corresponds to `pc` is known, and the row is returned. | ||
| 482 | pub fn runTo( | ||
| 483 | self: *VirtualMachine, | ||
| 484 | allocator: std.mem.Allocator, | ||
| 485 | pc: u64, | ||
| 486 | cie: dwarf.CommonInformationEntry, | ||
| 487 | fde: dwarf.FrameDescriptionEntry, | ||
| 488 | addr_size_bytes: u8, | ||
| 489 | endian: std.builtin.Endian, | ||
| 490 | ) !Row { | ||
| 491 | assert(self.cie_row == null); | ||
| 492 | if (pc < fde.pc_begin or pc >= fde.pc_begin + fde.pc_range) return error.AddressOutOfRange; | ||
| 493 | |||
| 494 | var prev_row: Row = self.current_row; | ||
| 495 | |||
| 496 | var cie_stream = std.io.fixedBufferStream(cie.initial_instructions); | ||
| 497 | var fde_stream = std.io.fixedBufferStream(fde.instructions); | ||
| 498 | var streams = [_]*std.io.FixedBufferStream([]const u8){ | ||
| 499 | &cie_stream, | ||
| 500 | &fde_stream, | ||
| 501 | }; | ||
| 502 | |||
| 503 | for (&streams, 0..) |stream, i| { | ||
| 504 | while (stream.pos < stream.buffer.len) { | ||
| 505 | const instruction = try dwarf.call_frame.Instruction.read(stream, addr_size_bytes, endian); | ||
| 506 | prev_row = try self.step(allocator, cie, i == 0, instruction); | ||
| 507 | if (pc < fde.pc_begin + self.current_row.offset) return prev_row; | ||
| 508 | } | ||
| 509 | } | ||
| 510 | |||
| 511 | return self.current_row; | ||
| 512 | } | ||
| 513 | |||
| 514 | pub fn runToNative( | ||
| 515 | self: *VirtualMachine, | ||
| 516 | allocator: std.mem.Allocator, | ||
| 517 | pc: u64, | ||
| 518 | cie: dwarf.CommonInformationEntry, | ||
| 519 | fde: dwarf.FrameDescriptionEntry, | ||
| 520 | ) !Row { | ||
| 521 | return self.runTo(allocator, pc, cie, fde, @sizeOf(usize), builtin.target.cpu.arch.endian()); | ||
| 522 | } | ||
| 523 | |||
| 524 | fn resolveCopyOnWrite(self: *VirtualMachine, allocator: std.mem.Allocator) !void { | ||
| 525 | if (!self.current_row.copy_on_write) return; | ||
| 526 | |||
| 527 | const new_start = self.columns.items.len; | ||
| 528 | if (self.current_row.columns.len > 0) { | ||
| 529 | try self.columns.ensureUnusedCapacity(allocator, self.current_row.columns.len); | ||
| 530 | self.columns.appendSliceAssumeCapacity(self.rowColumns(self.current_row)); | ||
| 531 | self.current_row.columns.start = new_start; | ||
| 532 | } | ||
| 533 | } | ||
| 534 | |||
| 535 | /// Executes a single instruction. | ||
| 536 | /// If this instruction is from the CIE, `is_initial` should be set. | ||
| 537 | /// Returns the value of `current_row` before executing this instruction. | ||
| 538 | pub fn step( | ||
| 539 | self: *VirtualMachine, | ||
| 540 | allocator: std.mem.Allocator, | ||
| 541 | cie: dwarf.CommonInformationEntry, | ||
| 542 | is_initial: bool, | ||
| 543 | instruction: Instruction, | ||
| 544 | ) !Row { | ||
| 545 | // CIE instructions must be run before FDE instructions | ||
| 546 | assert(!is_initial or self.cie_row == null); | ||
| 547 | if (!is_initial and self.cie_row == null) { | ||
| 548 | self.cie_row = self.current_row; | ||
| 549 | self.current_row.copy_on_write = true; | ||
| 550 | } | ||
| 551 | |||
| 552 | const prev_row = self.current_row; | ||
| 553 | switch (instruction) { | ||
| 554 | .set_loc => |i| { | ||
| 555 | if (i.address <= self.current_row.offset) return error.InvalidOperation; | ||
| 556 | // TODO: Check cie.segment_selector_size != 0 for DWARFV4 | ||
| 557 | self.current_row.offset = i.address; | ||
| 558 | }, | ||
| 559 | inline .advance_loc, | ||
| 560 | .advance_loc1, | ||
| 561 | .advance_loc2, | ||
| 562 | .advance_loc4, | ||
| 563 | => |i| { | ||
| 564 | self.current_row.offset += i.delta * cie.code_alignment_factor; | ||
| 565 | self.current_row.copy_on_write = true; | ||
| 566 | }, | ||
| 567 | inline .offset, | ||
| 568 | .offset_extended, | ||
| 569 | .offset_extended_sf, | ||
| 570 | => |i| { | ||
| 571 | try self.resolveCopyOnWrite(allocator); | ||
| 572 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 573 | column.rule = .{ .offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor }; | ||
| 574 | }, | ||
| 575 | inline .restore, | ||
| 576 | .restore_extended, | ||
| 577 | => |i| { | ||
| 578 | try self.resolveCopyOnWrite(allocator); | ||
| 579 | if (self.cie_row) |cie_row| { | ||
| 580 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 581 | column.rule = for (self.rowColumns(cie_row)) |cie_column| { | ||
| 582 | if (cie_column.register == i.register) break cie_column.rule; | ||
| 583 | } else .{ .default = {} }; | ||
| 584 | } else return error.InvalidOperation; | ||
| 585 | }, | ||
| 586 | .nop => {}, | ||
| 587 | .undefined => |i| { | ||
| 588 | try self.resolveCopyOnWrite(allocator); | ||
| 589 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 590 | column.rule = .{ .undefined = {} }; | ||
| 591 | }, | ||
| 592 | .same_value => |i| { | ||
| 593 | try self.resolveCopyOnWrite(allocator); | ||
| 594 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 595 | column.rule = .{ .same_value = {} }; | ||
| 596 | }, | ||
| 597 | .register => |i| { | ||
| 598 | try self.resolveCopyOnWrite(allocator); | ||
| 599 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 600 | column.rule = .{ .register = i.target_register }; | ||
| 601 | }, | ||
| 602 | .remember_state => { | ||
| 603 | try self.stack.append(allocator, self.current_row.columns); | ||
| 604 | self.current_row.copy_on_write = true; | ||
| 605 | }, | ||
| 606 | .restore_state => { | ||
| 607 | const restored_columns = self.stack.popOrNull() orelse return error.InvalidOperation; | ||
| 608 | self.columns.shrinkRetainingCapacity(self.columns.items.len - self.current_row.columns.len); | ||
| 609 | try self.columns.ensureUnusedCapacity(allocator, restored_columns.len); | ||
| 610 | |||
| 611 | self.current_row.columns.start = self.columns.items.len; | ||
| 612 | self.current_row.columns.len = restored_columns.len; | ||
| 613 | self.columns.appendSliceAssumeCapacity(self.columns.items[restored_columns.start..][0..restored_columns.len]); | ||
| 614 | }, | ||
| 615 | .def_cfa => |i| { | ||
| 616 | try self.resolveCopyOnWrite(allocator); | ||
| 617 | self.current_row.cfa = .{ | ||
| 618 | .register = i.register, | ||
| 619 | .rule = .{ .val_offset = @intCast(i.offset) }, | ||
| 620 | }; | ||
| 621 | }, | ||
| 622 | .def_cfa_sf => |i| { | ||
| 623 | try self.resolveCopyOnWrite(allocator); | ||
| 624 | self.current_row.cfa = .{ | ||
| 625 | .register = i.register, | ||
| 626 | .rule = .{ .val_offset = i.offset * cie.data_alignment_factor }, | ||
| 627 | }; | ||
| 628 | }, | ||
| 629 | .def_cfa_register => |i| { | ||
| 630 | try self.resolveCopyOnWrite(allocator); | ||
| 631 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 632 | self.current_row.cfa.register = i.register; | ||
| 633 | }, | ||
| 634 | .def_cfa_offset => |i| { | ||
| 635 | try self.resolveCopyOnWrite(allocator); | ||
| 636 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 637 | self.current_row.cfa.rule = .{ | ||
| 638 | .val_offset = @intCast(i.offset), | ||
| 639 | }; | ||
| 640 | }, | ||
| 641 | .def_cfa_offset_sf => |i| { | ||
| 642 | try self.resolveCopyOnWrite(allocator); | ||
| 643 | if (self.current_row.cfa.register == null or self.current_row.cfa.rule != .val_offset) return error.InvalidOperation; | ||
| 644 | self.current_row.cfa.rule = .{ | ||
| 645 | .val_offset = i.offset * cie.data_alignment_factor, | ||
| 646 | }; | ||
| 647 | }, | ||
| 648 | .def_cfa_expression => |i| { | ||
| 649 | try self.resolveCopyOnWrite(allocator); | ||
| 650 | self.current_row.cfa.register = undefined; | ||
| 651 | self.current_row.cfa.rule = .{ | ||
| 652 | .expression = i.block, | ||
| 653 | }; | ||
| 654 | }, | ||
| 655 | .expression => |i| { | ||
| 656 | try self.resolveCopyOnWrite(allocator); | ||
| 657 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 658 | column.rule = .{ | ||
| 659 | .expression = i.block, | ||
| 660 | }; | ||
| 661 | }, | ||
| 662 | .val_offset => |i| { | ||
| 663 | try self.resolveCopyOnWrite(allocator); | ||
| 664 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 665 | column.rule = .{ | ||
| 666 | .val_offset = @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, | ||
| 667 | }; | ||
| 668 | }, | ||
| 669 | .val_offset_sf => |i| { | ||
| 670 | try self.resolveCopyOnWrite(allocator); | ||
| 671 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 672 | column.rule = .{ | ||
| 673 | .val_offset = i.offset * cie.data_alignment_factor, | ||
| 674 | }; | ||
| 675 | }, | ||
| 676 | .val_expression => |i| { | ||
| 677 | try self.resolveCopyOnWrite(allocator); | ||
| 678 | const column = try self.getOrAddColumn(allocator, i.register); | ||
| 679 | column.rule = .{ | ||
| 680 | .val_expression = i.block, | ||
| 681 | }; | ||
| 682 | }, | ||
| 683 | } | ||
| 684 | |||
| 685 | return prev_row; | ||
| 686 | } | ||
| 687 | }; | ||
lib/std/dwarf/expressions.zig deleted-1648| ... | @@ -1,1648 +0,0 @@ | ||
| 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 | const native_endian = builtin.cpu.arch.endian(); | ||
| 10 | |||
| 11 | /// Expressions can be evaluated in different contexts, each requiring its own set of inputs. | ||
| 12 | /// Callers should specify all the fields relevant to their context. If a field is required | ||
| 13 | /// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned. | ||
| 14 | pub const ExpressionContext = struct { | ||
| 15 | /// The dwarf format of the section this expression is in | ||
| 16 | format: dwarf.Format = .@"32", | ||
| 17 | |||
| 18 | /// If specified, any addresses will pass through before being accessed | ||
| 19 | memory_accessor: ?*std.debug.StackIterator.MemoryAccessor = null, | ||
| 20 | |||
| 21 | /// The compilation unit this expression relates to, if any | ||
| 22 | compile_unit: ?*const dwarf.CompileUnit = null, | ||
| 23 | |||
| 24 | /// When evaluating a user-presented expression, this is the address of the object being evaluated | ||
| 25 | object_address: ?*const anyopaque = null, | ||
| 26 | |||
| 27 | /// .debug_addr section | ||
| 28 | debug_addr: ?[]const u8 = null, | ||
| 29 | |||
| 30 | /// Thread context | ||
| 31 | thread_context: ?*std.debug.ThreadContext = null, | ||
| 32 | reg_context: ?abi.RegisterContext = null, | ||
| 33 | |||
| 34 | /// Call frame address, if in a CFI context | ||
| 35 | cfa: ?usize = null, | ||
| 36 | |||
| 37 | /// This expression is a sub-expression from an OP.entry_value instruction | ||
| 38 | entry_value_context: bool = false, | ||
| 39 | }; | ||
| 40 | |||
| 41 | pub const ExpressionOptions = struct { | ||
| 42 | /// The address size of the target architecture | ||
| 43 | addr_size: u8 = @sizeOf(usize), | ||
| 44 | |||
| 45 | /// Endianness of the target architecture | ||
| 46 | endian: std.builtin.Endian = builtin.target.cpu.arch.endian(), | ||
| 47 | |||
| 48 | /// Restrict the stack machine to a subset of opcodes used in call frame instructions | ||
| 49 | call_frame_context: bool = false, | ||
| 50 | }; | ||
| 51 | |||
| 52 | // Explicitly defined to support executing sub-expressions | ||
| 53 | pub const ExpressionError = error{ | ||
| 54 | UnimplementedExpressionCall, | ||
| 55 | UnimplementedOpcode, | ||
| 56 | UnimplementedUserOpcode, | ||
| 57 | UnimplementedTypedComparison, | ||
| 58 | UnimplementedTypeConversion, | ||
| 59 | |||
| 60 | UnknownExpressionOpcode, | ||
| 61 | |||
| 62 | IncompleteExpressionContext, | ||
| 63 | |||
| 64 | InvalidCFAOpcode, | ||
| 65 | InvalidExpression, | ||
| 66 | InvalidFrameBase, | ||
| 67 | InvalidIntegralTypeSize, | ||
| 68 | InvalidRegister, | ||
| 69 | InvalidSubExpression, | ||
| 70 | InvalidTypeLength, | ||
| 71 | |||
| 72 | TruncatedIntegralType, | ||
| 73 | } || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; | ||
| 74 | |||
| 75 | /// A stack machine that can decode and run DWARF expressions. | ||
| 76 | /// Expressions can be decoded for non-native address size and endianness, | ||
| 77 | /// but can only be executed if the current target matches the configuration. | ||
| 78 | pub fn StackMachine(comptime options: ExpressionOptions) type { | ||
| 79 | const addr_type = switch (options.addr_size) { | ||
| 80 | 2 => u16, | ||
| 81 | 4 => u32, | ||
| 82 | 8 => u64, | ||
| 83 | else => @compileError("Unsupported address size of " ++ options.addr_size), | ||
| 84 | }; | ||
| 85 | |||
| 86 | const addr_type_signed = switch (options.addr_size) { | ||
| 87 | 2 => i16, | ||
| 88 | 4 => i32, | ||
| 89 | 8 => i64, | ||
| 90 | else => @compileError("Unsupported address size of " ++ options.addr_size), | ||
| 91 | }; | ||
| 92 | |||
| 93 | return struct { | ||
| 94 | const Self = @This(); | ||
| 95 | |||
| 96 | const Operand = union(enum) { | ||
| 97 | generic: addr_type, | ||
| 98 | register: u8, | ||
| 99 | type_size: u8, | ||
| 100 | branch_offset: i16, | ||
| 101 | base_register: struct { | ||
| 102 | base_register: u8, | ||
| 103 | offset: i64, | ||
| 104 | }, | ||
| 105 | composite_location: struct { | ||
| 106 | size: u64, | ||
| 107 | offset: i64, | ||
| 108 | }, | ||
| 109 | block: []const u8, | ||
| 110 | register_type: struct { | ||
| 111 | register: u8, | ||
| 112 | type_offset: addr_type, | ||
| 113 | }, | ||
| 114 | const_type: struct { | ||
| 115 | type_offset: addr_type, | ||
| 116 | value_bytes: []const u8, | ||
| 117 | }, | ||
| 118 | deref_type: struct { | ||
| 119 | size: u8, | ||
| 120 | type_offset: addr_type, | ||
| 121 | }, | ||
| 122 | }; | ||
| 123 | |||
| 124 | const Value = union(enum) { | ||
| 125 | generic: addr_type, | ||
| 126 | |||
| 127 | // Typed value with a maximum size of a register | ||
| 128 | regval_type: struct { | ||
| 129 | // Offset of DW_TAG_base_type DIE | ||
| 130 | type_offset: addr_type, | ||
| 131 | type_size: u8, | ||
| 132 | value: addr_type, | ||
| 133 | }, | ||
| 134 | |||
| 135 | // Typed value specified directly in the instruction stream | ||
| 136 | const_type: struct { | ||
| 137 | // Offset of DW_TAG_base_type DIE | ||
| 138 | type_offset: addr_type, | ||
| 139 | // Backed by the instruction stream | ||
| 140 | value_bytes: []const u8, | ||
| 141 | }, | ||
| 142 | |||
| 143 | pub fn asIntegral(self: Value) !addr_type { | ||
| 144 | return switch (self) { | ||
| 145 | .generic => |v| v, | ||
| 146 | |||
| 147 | // TODO: For these two prongs, look up the type and assert it's integral? | ||
| 148 | .regval_type => |regval_type| regval_type.value, | ||
| 149 | .const_type => |const_type| { | ||
| 150 | const value: u64 = switch (const_type.value_bytes.len) { | ||
| 151 | 1 => mem.readInt(u8, const_type.value_bytes[0..1], native_endian), | ||
| 152 | 2 => mem.readInt(u16, const_type.value_bytes[0..2], native_endian), | ||
| 153 | 4 => mem.readInt(u32, const_type.value_bytes[0..4], native_endian), | ||
| 154 | 8 => mem.readInt(u64, const_type.value_bytes[0..8], native_endian), | ||
| 155 | else => return error.InvalidIntegralTypeSize, | ||
| 156 | }; | ||
| 157 | |||
| 158 | return std.math.cast(addr_type, value) orelse error.TruncatedIntegralType; | ||
| 159 | }, | ||
| 160 | }; | ||
| 161 | } | ||
| 162 | }; | ||
| 163 | |||
| 164 | stack: std.ArrayListUnmanaged(Value) = .{}, | ||
| 165 | |||
| 166 | pub fn reset(self: *Self) void { | ||
| 167 | self.stack.clearRetainingCapacity(); | ||
| 168 | } | ||
| 169 | |||
| 170 | pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { | ||
| 171 | self.stack.deinit(allocator); | ||
| 172 | } | ||
| 173 | |||
| 174 | fn generic(value: anytype) Operand { | ||
| 175 | const int_info = @typeInfo(@TypeOf(value)).Int; | ||
| 176 | if (@sizeOf(@TypeOf(value)) > options.addr_size) { | ||
| 177 | return .{ .generic = switch (int_info.signedness) { | ||
| 178 | .signed => @bitCast(@as(addr_type_signed, @truncate(value))), | ||
| 179 | .unsigned => @truncate(value), | ||
| 180 | } }; | ||
| 181 | } else { | ||
| 182 | return .{ .generic = switch (int_info.signedness) { | ||
| 183 | .signed => @bitCast(@as(addr_type_signed, @intCast(value))), | ||
| 184 | .unsigned => @intCast(value), | ||
| 185 | } }; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8, context: ExpressionContext) !?Operand { | ||
| 190 | const reader = stream.reader(); | ||
| 191 | return switch (opcode) { | ||
| 192 | OP.addr => generic(try reader.readInt(addr_type, options.endian)), | ||
| 193 | OP.call_ref => switch (context.format) { | ||
| 194 | .@"32" => generic(try reader.readInt(u32, options.endian)), | ||
| 195 | .@"64" => generic(try reader.readInt(u64, options.endian)), | ||
| 196 | }, | ||
| 197 | OP.const1u, | ||
| 198 | OP.pick, | ||
| 199 | => generic(try reader.readByte()), | ||
| 200 | OP.deref_size, | ||
| 201 | OP.xderef_size, | ||
| 202 | => .{ .type_size = try reader.readByte() }, | ||
| 203 | OP.const1s => generic(try reader.readByteSigned()), | ||
| 204 | OP.const2u, | ||
| 205 | OP.call2, | ||
| 206 | => generic(try reader.readInt(u16, options.endian)), | ||
| 207 | OP.call4 => generic(try reader.readInt(u32, options.endian)), | ||
| 208 | OP.const2s => generic(try reader.readInt(i16, options.endian)), | ||
| 209 | OP.bra, | ||
| 210 | OP.skip, | ||
| 211 | => .{ .branch_offset = try reader.readInt(i16, options.endian) }, | ||
| 212 | OP.const4u => generic(try reader.readInt(u32, options.endian)), | ||
| 213 | OP.const4s => generic(try reader.readInt(i32, options.endian)), | ||
| 214 | OP.const8u => generic(try reader.readInt(u64, options.endian)), | ||
| 215 | OP.const8s => generic(try reader.readInt(i64, options.endian)), | ||
| 216 | OP.constu, | ||
| 217 | OP.plus_uconst, | ||
| 218 | OP.addrx, | ||
| 219 | OP.constx, | ||
| 220 | OP.convert, | ||
| 221 | OP.reinterpret, | ||
| 222 | => generic(try leb.readUleb128(u64, reader)), | ||
| 223 | OP.consts, | ||
| 224 | OP.fbreg, | ||
| 225 | => generic(try leb.readIleb128(i64, reader)), | ||
| 226 | OP.lit0...OP.lit31 => |n| generic(n - OP.lit0), | ||
| 227 | OP.reg0...OP.reg31 => |n| .{ .register = n - OP.reg0 }, | ||
| 228 | OP.breg0...OP.breg31 => |n| .{ .base_register = .{ | ||
| 229 | .base_register = n - OP.breg0, | ||
| 230 | .offset = try leb.readIleb128(i64, reader), | ||
| 231 | } }, | ||
| 232 | OP.regx => .{ .register = try leb.readUleb128(u8, reader) }, | ||
| 233 | OP.bregx => blk: { | ||
| 234 | const base_register = try leb.readUleb128(u8, reader); | ||
| 235 | const offset = try leb.readIleb128(i64, reader); | ||
| 236 | break :blk .{ .base_register = .{ | ||
| 237 | .base_register = base_register, | ||
| 238 | .offset = offset, | ||
| 239 | } }; | ||
| 240 | }, | ||
| 241 | OP.regval_type => blk: { | ||
| 242 | const register = try leb.readUleb128(u8, reader); | ||
| 243 | const type_offset = try leb.readUleb128(addr_type, reader); | ||
| 244 | break :blk .{ .register_type = .{ | ||
| 245 | .register = register, | ||
| 246 | .type_offset = type_offset, | ||
| 247 | } }; | ||
| 248 | }, | ||
| 249 | OP.piece => .{ | ||
| 250 | .composite_location = .{ | ||
| 251 | .size = try leb.readUleb128(u8, reader), | ||
| 252 | .offset = 0, | ||
| 253 | }, | ||
| 254 | }, | ||
| 255 | OP.bit_piece => blk: { | ||
| 256 | const size = try leb.readUleb128(u8, reader); | ||
| 257 | const offset = try leb.readIleb128(i64, reader); | ||
| 258 | break :blk .{ .composite_location = .{ | ||
| 259 | .size = size, | ||
| 260 | .offset = offset, | ||
| 261 | } }; | ||
| 262 | }, | ||
| 263 | OP.implicit_value, OP.entry_value => blk: { | ||
| 264 | const size = try leb.readUleb128(u8, reader); | ||
| 265 | if (stream.pos + size > stream.buffer.len) return error.InvalidExpression; | ||
| 266 | const block = stream.buffer[stream.pos..][0..size]; | ||
| 267 | stream.pos += size; | ||
| 268 | break :blk .{ | ||
| 269 | .block = block, | ||
| 270 | }; | ||
| 271 | }, | ||
| 272 | OP.const_type => blk: { | ||
| 273 | const type_offset = try leb.readUleb128(addr_type, reader); | ||
| 274 | const size = try reader.readByte(); | ||
| 275 | if (stream.pos + size > stream.buffer.len) return error.InvalidExpression; | ||
| 276 | const value_bytes = stream.buffer[stream.pos..][0..size]; | ||
| 277 | stream.pos += size; | ||
| 278 | break :blk .{ .const_type = .{ | ||
| 279 | .type_offset = type_offset, | ||
| 280 | .value_bytes = value_bytes, | ||
| 281 | } }; | ||
| 282 | }, | ||
| 283 | OP.deref_type, | ||
| 284 | OP.xderef_type, | ||
| 285 | => .{ | ||
| 286 | .deref_type = .{ | ||
| 287 | .size = try reader.readByte(), | ||
| 288 | .type_offset = try leb.readUleb128(addr_type, reader), | ||
| 289 | }, | ||
| 290 | }, | ||
| 291 | OP.lo_user...OP.hi_user => return error.UnimplementedUserOpcode, | ||
| 292 | else => null, | ||
| 293 | }; | ||
| 294 | } | ||
| 295 | |||
| 296 | pub fn run( | ||
| 297 | self: *Self, | ||
| 298 | expression: []const u8, | ||
| 299 | allocator: std.mem.Allocator, | ||
| 300 | context: ExpressionContext, | ||
| 301 | initial_value: ?usize, | ||
| 302 | ) ExpressionError!?Value { | ||
| 303 | if (initial_value) |i| try self.stack.append(allocator, .{ .generic = i }); | ||
| 304 | var stream = std.io.fixedBufferStream(expression); | ||
| 305 | while (try self.step(&stream, allocator, context)) {} | ||
| 306 | if (self.stack.items.len == 0) return null; | ||
| 307 | return self.stack.items[self.stack.items.len - 1]; | ||
| 308 | } | ||
| 309 | |||
| 310 | /// Reads an opcode and its operands from `stream`, then executes it | ||
| 311 | pub fn step( | ||
| 312 | self: *Self, | ||
| 313 | stream: *std.io.FixedBufferStream([]const u8), | ||
| 314 | allocator: std.mem.Allocator, | ||
| 315 | context: ExpressionContext, | ||
| 316 | ) ExpressionError!bool { | ||
| 317 | if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian()) | ||
| 318 | @compileError("Execution of non-native address sizes / endianness is not supported"); | ||
| 319 | |||
| 320 | const opcode = try stream.reader().readByte(); | ||
| 321 | if (options.call_frame_context and !isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; | ||
| 322 | const operand = try readOperand(stream, opcode, context); | ||
| 323 | switch (opcode) { | ||
| 324 | |||
| 325 | // 2.5.1.1: Literal Encodings | ||
| 326 | OP.lit0...OP.lit31, | ||
| 327 | OP.addr, | ||
| 328 | OP.const1u, | ||
| 329 | OP.const2u, | ||
| 330 | OP.const4u, | ||
| 331 | OP.const8u, | ||
| 332 | OP.const1s, | ||
| 333 | OP.const2s, | ||
| 334 | OP.const4s, | ||
| 335 | OP.const8s, | ||
| 336 | OP.constu, | ||
| 337 | OP.consts, | ||
| 338 | => try self.stack.append(allocator, .{ .generic = operand.?.generic }), | ||
| 339 | |||
| 340 | OP.const_type => { | ||
| 341 | const const_type = operand.?.const_type; | ||
| 342 | try self.stack.append(allocator, .{ .const_type = .{ | ||
| 343 | .type_offset = const_type.type_offset, | ||
| 344 | .value_bytes = const_type.value_bytes, | ||
| 345 | } }); | ||
| 346 | }, | ||
| 347 | |||
| 348 | OP.addrx, | ||
| 349 | OP.constx, | ||
| 350 | => { | ||
| 351 | if (context.compile_unit == null) return error.IncompleteExpressionContext; | ||
| 352 | if (context.debug_addr == null) return error.IncompleteExpressionContext; | ||
| 353 | const debug_addr_index = operand.?.generic; | ||
| 354 | const offset = context.compile_unit.?.addr_base + debug_addr_index; | ||
| 355 | if (offset >= context.debug_addr.?.len) return error.InvalidExpression; | ||
| 356 | const value = mem.readInt(usize, context.debug_addr.?[offset..][0..@sizeOf(usize)], native_endian); | ||
| 357 | try self.stack.append(allocator, .{ .generic = value }); | ||
| 358 | }, | ||
| 359 | |||
| 360 | // 2.5.1.2: Register Values | ||
| 361 | OP.fbreg => { | ||
| 362 | if (context.compile_unit == null) return error.IncompleteExpressionContext; | ||
| 363 | if (context.compile_unit.?.frame_base == null) return error.IncompleteExpressionContext; | ||
| 364 | |||
| 365 | const offset: i64 = @intCast(operand.?.generic); | ||
| 366 | _ = offset; | ||
| 367 | |||
| 368 | switch (context.compile_unit.?.frame_base.?.*) { | ||
| 369 | .exprloc => { | ||
| 370 | // TODO: Run this expression in a nested stack machine | ||
| 371 | return error.UnimplementedOpcode; | ||
| 372 | }, | ||
| 373 | .loclistx => { | ||
| 374 | // TODO: Read value from .debug_loclists | ||
| 375 | return error.UnimplementedOpcode; | ||
| 376 | }, | ||
| 377 | .sec_offset => { | ||
| 378 | // TODO: Read value from .debug_loclists | ||
| 379 | return error.UnimplementedOpcode; | ||
| 380 | }, | ||
| 381 | else => return error.InvalidFrameBase, | ||
| 382 | } | ||
| 383 | }, | ||
| 384 | OP.breg0...OP.breg31, | ||
| 385 | OP.bregx, | ||
| 386 | => { | ||
| 387 | if (context.thread_context == null) return error.IncompleteExpressionContext; | ||
| 388 | |||
| 389 | const base_register = operand.?.base_register; | ||
| 390 | var value: i64 = @intCast(mem.readInt(usize, (try abi.regBytes( | ||
| 391 | context.thread_context.?, | ||
| 392 | base_register.base_register, | ||
| 393 | context.reg_context, | ||
| 394 | ))[0..@sizeOf(usize)], native_endian)); | ||
| 395 | value += base_register.offset; | ||
| 396 | try self.stack.append(allocator, .{ .generic = @intCast(value) }); | ||
| 397 | }, | ||
| 398 | OP.regval_type => { | ||
| 399 | const register_type = operand.?.register_type; | ||
| 400 | const value = mem.readInt(usize, (try abi.regBytes( | ||
| 401 | context.thread_context.?, | ||
| 402 | register_type.register, | ||
| 403 | context.reg_context, | ||
| 404 | ))[0..@sizeOf(usize)], native_endian); | ||
| 405 | try self.stack.append(allocator, .{ | ||
| 406 | .regval_type = .{ | ||
| 407 | .type_offset = register_type.type_offset, | ||
| 408 | .type_size = @sizeOf(addr_type), | ||
| 409 | .value = value, | ||
| 410 | }, | ||
| 411 | }); | ||
| 412 | }, | ||
| 413 | |||
| 414 | // 2.5.1.3: Stack Operations | ||
| 415 | OP.dup => { | ||
| 416 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 417 | try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1]); | ||
| 418 | }, | ||
| 419 | OP.drop => { | ||
| 420 | _ = self.stack.pop(); | ||
| 421 | }, | ||
| 422 | OP.pick, OP.over => { | ||
| 423 | const stack_index = if (opcode == OP.over) 1 else operand.?.generic; | ||
| 424 | if (stack_index >= self.stack.items.len) return error.InvalidExpression; | ||
| 425 | try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1 - stack_index]); | ||
| 426 | }, | ||
| 427 | OP.swap => { | ||
| 428 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 429 | mem.swap(Value, &self.stack.items[self.stack.items.len - 1], &self.stack.items[self.stack.items.len - 2]); | ||
| 430 | }, | ||
| 431 | OP.rot => { | ||
| 432 | if (self.stack.items.len < 3) return error.InvalidExpression; | ||
| 433 | const first = self.stack.items[self.stack.items.len - 1]; | ||
| 434 | self.stack.items[self.stack.items.len - 1] = self.stack.items[self.stack.items.len - 2]; | ||
| 435 | self.stack.items[self.stack.items.len - 2] = self.stack.items[self.stack.items.len - 3]; | ||
| 436 | self.stack.items[self.stack.items.len - 3] = first; | ||
| 437 | }, | ||
| 438 | OP.deref, | ||
| 439 | OP.xderef, | ||
| 440 | OP.deref_size, | ||
| 441 | OP.xderef_size, | ||
| 442 | OP.deref_type, | ||
| 443 | OP.xderef_type, | ||
| 444 | => { | ||
| 445 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 446 | const addr = try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 447 | const addr_space_identifier: ?usize = switch (opcode) { | ||
| 448 | OP.xderef, | ||
| 449 | OP.xderef_size, | ||
| 450 | OP.xderef_type, | ||
| 451 | => blk: { | ||
| 452 | _ = self.stack.pop(); | ||
| 453 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 454 | break :blk try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 455 | }, | ||
| 456 | else => null, | ||
| 457 | }; | ||
| 458 | |||
| 459 | // Usage of addr_space_identifier in the address calculation is implementation defined. | ||
| 460 | // This code will need to be updated to handle any architectures that utilize this. | ||
| 461 | _ = addr_space_identifier; | ||
| 462 | |||
| 463 | const size = switch (opcode) { | ||
| 464 | OP.deref, | ||
| 465 | OP.xderef, | ||
| 466 | => @sizeOf(addr_type), | ||
| 467 | OP.deref_size, | ||
| 468 | OP.xderef_size, | ||
| 469 | => operand.?.type_size, | ||
| 470 | OP.deref_type, | ||
| 471 | OP.xderef_type, | ||
| 472 | => operand.?.deref_type.size, | ||
| 473 | else => unreachable, | ||
| 474 | }; | ||
| 475 | |||
| 476 | if (context.memory_accessor) |memory_accessor| { | ||
| 477 | if (!switch (size) { | ||
| 478 | 1 => memory_accessor.load(u8, addr) != null, | ||
| 479 | 2 => memory_accessor.load(u16, addr) != null, | ||
| 480 | 4 => memory_accessor.load(u32, addr) != null, | ||
| 481 | 8 => memory_accessor.load(u64, addr) != null, | ||
| 482 | else => return error.InvalidExpression, | ||
| 483 | }) return error.InvalidExpression; | ||
| 484 | } | ||
| 485 | |||
| 486 | const value: addr_type = std.math.cast(addr_type, @as(u64, switch (size) { | ||
| 487 | 1 => @as(*const u8, @ptrFromInt(addr)).*, | ||
| 488 | 2 => @as(*const u16, @ptrFromInt(addr)).*, | ||
| 489 | 4 => @as(*const u32, @ptrFromInt(addr)).*, | ||
| 490 | 8 => @as(*const u64, @ptrFromInt(addr)).*, | ||
| 491 | else => return error.InvalidExpression, | ||
| 492 | })) orelse return error.InvalidExpression; | ||
| 493 | |||
| 494 | switch (opcode) { | ||
| 495 | OP.deref_type, | ||
| 496 | OP.xderef_type, | ||
| 497 | => { | ||
| 498 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 499 | .regval_type = .{ | ||
| 500 | .type_offset = operand.?.deref_type.type_offset, | ||
| 501 | .type_size = operand.?.deref_type.size, | ||
| 502 | .value = value, | ||
| 503 | }, | ||
| 504 | }; | ||
| 505 | }, | ||
| 506 | else => { | ||
| 507 | self.stack.items[self.stack.items.len - 1] = .{ .generic = value }; | ||
| 508 | }, | ||
| 509 | } | ||
| 510 | }, | ||
| 511 | OP.push_object_address => { | ||
| 512 | // In sub-expressions, `push_object_address` is not meaningful (as per the | ||
| 513 | // spec), so treat it like a nop | ||
| 514 | if (!context.entry_value_context) { | ||
| 515 | if (context.object_address == null) return error.IncompleteExpressionContext; | ||
| 516 | try self.stack.append(allocator, .{ .generic = @intFromPtr(context.object_address.?) }); | ||
| 517 | } | ||
| 518 | }, | ||
| 519 | OP.form_tls_address => { | ||
| 520 | return error.UnimplementedOpcode; | ||
| 521 | }, | ||
| 522 | OP.call_frame_cfa => { | ||
| 523 | if (context.cfa) |cfa| { | ||
| 524 | try self.stack.append(allocator, .{ .generic = cfa }); | ||
| 525 | } else return error.IncompleteExpressionContext; | ||
| 526 | }, | ||
| 527 | |||
| 528 | // 2.5.1.4: Arithmetic and Logical Operations | ||
| 529 | OP.abs => { | ||
| 530 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 531 | const value: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 532 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 533 | .generic = @abs(value), | ||
| 534 | }; | ||
| 535 | }, | ||
| 536 | OP.@"and" => { | ||
| 537 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 538 | const a = try self.stack.pop().asIntegral(); | ||
| 539 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 540 | .generic = a & try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 541 | }; | ||
| 542 | }, | ||
| 543 | OP.div => { | ||
| 544 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 545 | const a: isize = @bitCast(try self.stack.pop().asIntegral()); | ||
| 546 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 547 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 548 | .generic = @bitCast(try std.math.divTrunc(isize, b, a)), | ||
| 549 | }; | ||
| 550 | }, | ||
| 551 | OP.minus => { | ||
| 552 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 553 | const b = try self.stack.pop().asIntegral(); | ||
| 554 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 555 | .generic = try std.math.sub(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b), | ||
| 556 | }; | ||
| 557 | }, | ||
| 558 | OP.mod => { | ||
| 559 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 560 | const a: isize = @bitCast(try self.stack.pop().asIntegral()); | ||
| 561 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 562 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 563 | .generic = @bitCast(@mod(b, a)), | ||
| 564 | }; | ||
| 565 | }, | ||
| 566 | OP.mul => { | ||
| 567 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 568 | const a: isize = @bitCast(try self.stack.pop().asIntegral()); | ||
| 569 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 570 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 571 | .generic = @bitCast(@mulWithOverflow(a, b)[0]), | ||
| 572 | }; | ||
| 573 | }, | ||
| 574 | OP.neg => { | ||
| 575 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 576 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 577 | .generic = @bitCast( | ||
| 578 | try std.math.negate( | ||
| 579 | @as(isize, @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral())), | ||
| 580 | ), | ||
| 581 | ), | ||
| 582 | }; | ||
| 583 | }, | ||
| 584 | OP.not => { | ||
| 585 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 586 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 587 | .generic = ~try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 588 | }; | ||
| 589 | }, | ||
| 590 | OP.@"or" => { | ||
| 591 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 592 | const a = try self.stack.pop().asIntegral(); | ||
| 593 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 594 | .generic = a | try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 595 | }; | ||
| 596 | }, | ||
| 597 | OP.plus => { | ||
| 598 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 599 | const b = try self.stack.pop().asIntegral(); | ||
| 600 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 601 | .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b), | ||
| 602 | }; | ||
| 603 | }, | ||
| 604 | OP.plus_uconst => { | ||
| 605 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 606 | const constant = operand.?.generic; | ||
| 607 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 608 | .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), constant), | ||
| 609 | }; | ||
| 610 | }, | ||
| 611 | OP.shl => { | ||
| 612 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 613 | const a = try self.stack.pop().asIntegral(); | ||
| 614 | const b = try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 615 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 616 | .generic = std.math.shl(usize, b, a), | ||
| 617 | }; | ||
| 618 | }, | ||
| 619 | OP.shr => { | ||
| 620 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 621 | const a = try self.stack.pop().asIntegral(); | ||
| 622 | const b = try self.stack.items[self.stack.items.len - 1].asIntegral(); | ||
| 623 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 624 | .generic = std.math.shr(usize, b, a), | ||
| 625 | }; | ||
| 626 | }, | ||
| 627 | OP.shra => { | ||
| 628 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 629 | const a = try self.stack.pop().asIntegral(); | ||
| 630 | const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral()); | ||
| 631 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 632 | .generic = @bitCast(std.math.shr(isize, b, a)), | ||
| 633 | }; | ||
| 634 | }, | ||
| 635 | OP.xor => { | ||
| 636 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 637 | const a = try self.stack.pop().asIntegral(); | ||
| 638 | self.stack.items[self.stack.items.len - 1] = .{ | ||
| 639 | .generic = a ^ try self.stack.items[self.stack.items.len - 1].asIntegral(), | ||
| 640 | }; | ||
| 641 | }, | ||
| 642 | |||
| 643 | // 2.5.1.5: Control Flow Operations | ||
| 644 | OP.le, | ||
| 645 | OP.ge, | ||
| 646 | OP.eq, | ||
| 647 | OP.lt, | ||
| 648 | OP.gt, | ||
| 649 | OP.ne, | ||
| 650 | => { | ||
| 651 | if (self.stack.items.len < 2) return error.InvalidExpression; | ||
| 652 | const a = self.stack.pop(); | ||
| 653 | const b = self.stack.items[self.stack.items.len - 1]; | ||
| 654 | |||
| 655 | if (a == .generic and b == .generic) { | ||
| 656 | const a_int: isize = @bitCast(a.asIntegral() catch unreachable); | ||
| 657 | const b_int: isize = @bitCast(b.asIntegral() catch unreachable); | ||
| 658 | const result = @intFromBool(switch (opcode) { | ||
| 659 | OP.le => b_int <= a_int, | ||
| 660 | OP.ge => b_int >= a_int, | ||
| 661 | OP.eq => b_int == a_int, | ||
| 662 | OP.lt => b_int < a_int, | ||
| 663 | OP.gt => b_int > a_int, | ||
| 664 | OP.ne => b_int != a_int, | ||
| 665 | else => unreachable, | ||
| 666 | }); | ||
| 667 | |||
| 668 | self.stack.items[self.stack.items.len - 1] = .{ .generic = result }; | ||
| 669 | } else { | ||
| 670 | // TODO: Load the types referenced by these values, find their comparison operator, and run it | ||
| 671 | return error.UnimplementedTypedComparison; | ||
| 672 | } | ||
| 673 | }, | ||
| 674 | OP.skip, OP.bra => { | ||
| 675 | const branch_offset = operand.?.branch_offset; | ||
| 676 | const condition = if (opcode == OP.bra) blk: { | ||
| 677 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 678 | break :blk try self.stack.pop().asIntegral() != 0; | ||
| 679 | } else true; | ||
| 680 | |||
| 681 | if (condition) { | ||
| 682 | const new_pos = std.math.cast( | ||
| 683 | usize, | ||
| 684 | try std.math.add(isize, @as(isize, @intCast(stream.pos)), branch_offset), | ||
| 685 | ) orelse return error.InvalidExpression; | ||
| 686 | |||
| 687 | if (new_pos < 0 or new_pos > stream.buffer.len) return error.InvalidExpression; | ||
| 688 | stream.pos = new_pos; | ||
| 689 | } | ||
| 690 | }, | ||
| 691 | OP.call2, | ||
| 692 | OP.call4, | ||
| 693 | OP.call_ref, | ||
| 694 | => { | ||
| 695 | const debug_info_offset = operand.?.generic; | ||
| 696 | _ = debug_info_offset; | ||
| 697 | |||
| 698 | // TODO: Load a DIE entry at debug_info_offset in a .debug_info section (the spec says that it | ||
| 699 | // can be in a separate exe / shared object from the one containing this expression). | ||
| 700 | // Transfer control to the DW_AT_location attribute, with the current stack as input. | ||
| 701 | |||
| 702 | return error.UnimplementedExpressionCall; | ||
| 703 | }, | ||
| 704 | |||
| 705 | // 2.5.1.6: Type Conversions | ||
| 706 | OP.convert => { | ||
| 707 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 708 | const type_offset = operand.?.generic; | ||
| 709 | |||
| 710 | // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size | ||
| 711 | const value = self.stack.items[self.stack.items.len - 1]; | ||
| 712 | if (type_offset == 0) { | ||
| 713 | self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() }; | ||
| 714 | } else { | ||
| 715 | // TODO: Load the DW_TAG_base_type entry in context.compile_unit, find a conversion operator | ||
| 716 | // from the old type to the new type, run it. | ||
| 717 | return error.UnimplementedTypeConversion; | ||
| 718 | } | ||
| 719 | }, | ||
| 720 | OP.reinterpret => { | ||
| 721 | if (self.stack.items.len == 0) return error.InvalidExpression; | ||
| 722 | const type_offset = operand.?.generic; | ||
| 723 | |||
| 724 | // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size | ||
| 725 | const value = self.stack.items[self.stack.items.len - 1]; | ||
| 726 | if (type_offset == 0) { | ||
| 727 | self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() }; | ||
| 728 | } else { | ||
| 729 | self.stack.items[self.stack.items.len - 1] = switch (value) { | ||
| 730 | .generic => |v| .{ | ||
| 731 | .regval_type = .{ | ||
| 732 | .type_offset = type_offset, | ||
| 733 | .type_size = @sizeOf(addr_type), | ||
| 734 | .value = v, | ||
| 735 | }, | ||
| 736 | }, | ||
| 737 | .regval_type => |r| .{ | ||
| 738 | .regval_type = .{ | ||
| 739 | .type_offset = type_offset, | ||
| 740 | .type_size = r.type_size, | ||
| 741 | .value = r.value, | ||
| 742 | }, | ||
| 743 | }, | ||
| 744 | .const_type => |c| .{ | ||
| 745 | .const_type = .{ | ||
| 746 | .type_offset = type_offset, | ||
| 747 | .value_bytes = c.value_bytes, | ||
| 748 | }, | ||
| 749 | }, | ||
| 750 | }; | ||
| 751 | } | ||
| 752 | }, | ||
| 753 | |||
| 754 | // 2.5.1.7: Special Operations | ||
| 755 | OP.nop => {}, | ||
| 756 | OP.entry_value => { | ||
| 757 | const block = operand.?.block; | ||
| 758 | if (block.len == 0) return error.InvalidSubExpression; | ||
| 759 | |||
| 760 | // TODO: The spec states that this sub-expression needs to observe the state (ie. registers) | ||
| 761 | // as it was upon entering the current subprogram. If this isn't being called at the | ||
| 762 | // end of a frame unwind operation, an additional ThreadContext with this state will be needed. | ||
| 763 | |||
| 764 | if (isOpcodeRegisterLocation(block[0])) { | ||
| 765 | if (context.thread_context == null) return error.IncompleteExpressionContext; | ||
| 766 | |||
| 767 | var block_stream = std.io.fixedBufferStream(block); | ||
| 768 | const register = (try readOperand(&block_stream, block[0], context)).?.register; | ||
| 769 | const value = mem.readInt(usize, (try abi.regBytes(context.thread_context.?, register, context.reg_context))[0..@sizeOf(usize)], native_endian); | ||
| 770 | try self.stack.append(allocator, .{ .generic = value }); | ||
| 771 | } else { | ||
| 772 | var stack_machine: Self = .{}; | ||
| 773 | defer stack_machine.deinit(allocator); | ||
| 774 | |||
| 775 | var sub_context = context; | ||
| 776 | sub_context.entry_value_context = true; | ||
| 777 | const result = try stack_machine.run(block, allocator, sub_context, null); | ||
| 778 | try self.stack.append(allocator, result orelse return error.InvalidSubExpression); | ||
| 779 | } | ||
| 780 | }, | ||
| 781 | |||
| 782 | // These have already been handled by readOperand | ||
| 783 | OP.lo_user...OP.hi_user => unreachable, | ||
| 784 | else => { | ||
| 785 | //std.debug.print("Unknown DWARF expression opcode: {x}\n", .{opcode}); | ||
| 786 | return error.UnknownExpressionOpcode; | ||
| 787 | }, | ||
| 788 | } | ||
| 789 | |||
| 790 | return stream.pos < stream.buffer.len; | ||
| 791 | } | ||
| 792 | }; | ||
| 793 | } | ||
| 794 | |||
| 795 | pub fn Builder(comptime options: ExpressionOptions) type { | ||
| 796 | const addr_type = switch (options.addr_size) { | ||
| 797 | 2 => u16, | ||
| 798 | 4 => u32, | ||
| 799 | 8 => u64, | ||
| 800 | else => @compileError("Unsupported address size of " ++ options.addr_size), | ||
| 801 | }; | ||
| 802 | |||
| 803 | return struct { | ||
| 804 | /// Zero-operand instructions | ||
| 805 | pub fn writeOpcode(writer: anytype, comptime opcode: u8) !void { | ||
| 806 | if (options.call_frame_context and !comptime isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; | ||
| 807 | switch (opcode) { | ||
| 808 | OP.dup, | ||
| 809 | OP.drop, | ||
| 810 | OP.over, | ||
| 811 | OP.swap, | ||
| 812 | OP.rot, | ||
| 813 | OP.deref, | ||
| 814 | OP.xderef, | ||
| 815 | OP.push_object_address, | ||
| 816 | OP.form_tls_address, | ||
| 817 | OP.call_frame_cfa, | ||
| 818 | OP.abs, | ||
| 819 | OP.@"and", | ||
| 820 | OP.div, | ||
| 821 | OP.minus, | ||
| 822 | OP.mod, | ||
| 823 | OP.mul, | ||
| 824 | OP.neg, | ||
| 825 | OP.not, | ||
| 826 | OP.@"or", | ||
| 827 | OP.plus, | ||
| 828 | OP.shl, | ||
| 829 | OP.shr, | ||
| 830 | OP.shra, | ||
| 831 | OP.xor, | ||
| 832 | OP.le, | ||
| 833 | OP.ge, | ||
| 834 | OP.eq, | ||
| 835 | OP.lt, | ||
| 836 | OP.gt, | ||
| 837 | OP.ne, | ||
| 838 | OP.nop, | ||
| 839 | OP.stack_value, | ||
| 840 | => try writer.writeByte(opcode), | ||
| 841 | else => @compileError("This opcode requires operands, use `write<Opcode>()` instead"), | ||
| 842 | } | ||
| 843 | } | ||
| 844 | |||
| 845 | // 2.5.1.1: Literal Encodings | ||
| 846 | pub fn writeLiteral(writer: anytype, literal: u8) !void { | ||
| 847 | switch (literal) { | ||
| 848 | 0...31 => |n| try writer.writeByte(n + OP.lit0), | ||
| 849 | else => return error.InvalidLiteral, | ||
| 850 | } | ||
| 851 | } | ||
| 852 | |||
| 853 | pub fn writeConst(writer: anytype, comptime T: type, value: T) !void { | ||
| 854 | if (@typeInfo(T) != .Int) @compileError("Constants must be integers"); | ||
| 855 | |||
| 856 | switch (T) { | ||
| 857 | u8, i8, u16, i16, u32, i32, u64, i64 => { | ||
| 858 | try writer.writeByte(switch (T) { | ||
| 859 | u8 => OP.const1u, | ||
| 860 | i8 => OP.const1s, | ||
| 861 | u16 => OP.const2u, | ||
| 862 | i16 => OP.const2s, | ||
| 863 | u32 => OP.const4u, | ||
| 864 | i32 => OP.const4s, | ||
| 865 | u64 => OP.const8u, | ||
| 866 | i64 => OP.const8s, | ||
| 867 | else => unreachable, | ||
| 868 | }); | ||
| 869 | |||
| 870 | try writer.writeInt(T, value, options.endian); | ||
| 871 | }, | ||
| 872 | else => switch (@typeInfo(T).Int.signedness) { | ||
| 873 | .unsigned => { | ||
| 874 | try writer.writeByte(OP.constu); | ||
| 875 | try leb.writeUleb128(writer, value); | ||
| 876 | }, | ||
| 877 | .signed => { | ||
| 878 | try writer.writeByte(OP.consts); | ||
| 879 | try leb.writeIleb128(writer, value); | ||
| 880 | }, | ||
| 881 | }, | ||
| 882 | } | ||
| 883 | } | ||
| 884 | |||
| 885 | pub fn writeConstx(writer: anytype, debug_addr_offset: anytype) !void { | ||
| 886 | try writer.writeByte(OP.constx); | ||
| 887 | try leb.writeUleb128(writer, debug_addr_offset); | ||
| 888 | } | ||
| 889 | |||
| 890 | pub fn writeConstType(writer: anytype, die_offset: anytype, value_bytes: []const u8) !void { | ||
| 891 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 892 | if (value_bytes.len > 0xff) return error.InvalidTypeLength; | ||
| 893 | try writer.writeByte(OP.const_type); | ||
| 894 | try leb.writeUleb128(writer, die_offset); | ||
| 895 | try writer.writeByte(@intCast(value_bytes.len)); | ||
| 896 | try writer.writeAll(value_bytes); | ||
| 897 | } | ||
| 898 | |||
| 899 | pub fn writeAddr(writer: anytype, value: addr_type) !void { | ||
| 900 | try writer.writeByte(OP.addr); | ||
| 901 | try writer.writeInt(addr_type, value, options.endian); | ||
| 902 | } | ||
| 903 | |||
| 904 | pub fn writeAddrx(writer: anytype, debug_addr_offset: anytype) !void { | ||
| 905 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 906 | try writer.writeByte(OP.addrx); | ||
| 907 | try leb.writeUleb128(writer, debug_addr_offset); | ||
| 908 | } | ||
| 909 | |||
| 910 | // 2.5.1.2: Register Values | ||
| 911 | pub fn writeFbreg(writer: anytype, offset: anytype) !void { | ||
| 912 | try writer.writeByte(OP.fbreg); | ||
| 913 | try leb.writeIleb128(writer, offset); | ||
| 914 | } | ||
| 915 | |||
| 916 | pub fn writeBreg(writer: anytype, register: u8, offset: anytype) !void { | ||
| 917 | if (register > 31) return error.InvalidRegister; | ||
| 918 | try writer.writeByte(OP.breg0 + register); | ||
| 919 | try leb.writeIleb128(writer, offset); | ||
| 920 | } | ||
| 921 | |||
| 922 | pub fn writeBregx(writer: anytype, register: anytype, offset: anytype) !void { | ||
| 923 | try writer.writeByte(OP.bregx); | ||
| 924 | try leb.writeUleb128(writer, register); | ||
| 925 | try leb.writeIleb128(writer, offset); | ||
| 926 | } | ||
| 927 | |||
| 928 | pub fn writeRegvalType(writer: anytype, register: anytype, offset: anytype) !void { | ||
| 929 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 930 | try writer.writeByte(OP.regval_type); | ||
| 931 | try leb.writeUleb128(writer, register); | ||
| 932 | try leb.writeUleb128(writer, offset); | ||
| 933 | } | ||
| 934 | |||
| 935 | // 2.5.1.3: Stack Operations | ||
| 936 | pub fn writePick(writer: anytype, index: u8) !void { | ||
| 937 | try writer.writeByte(OP.pick); | ||
| 938 | try writer.writeByte(index); | ||
| 939 | } | ||
| 940 | |||
| 941 | pub fn writeDerefSize(writer: anytype, size: u8) !void { | ||
| 942 | try writer.writeByte(OP.deref_size); | ||
| 943 | try writer.writeByte(size); | ||
| 944 | } | ||
| 945 | |||
| 946 | pub fn writeXDerefSize(writer: anytype, size: u8) !void { | ||
| 947 | try writer.writeByte(OP.xderef_size); | ||
| 948 | try writer.writeByte(size); | ||
| 949 | } | ||
| 950 | |||
| 951 | pub fn writeDerefType(writer: anytype, size: u8, die_offset: anytype) !void { | ||
| 952 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 953 | try writer.writeByte(OP.deref_type); | ||
| 954 | try writer.writeByte(size); | ||
| 955 | try leb.writeUleb128(writer, die_offset); | ||
| 956 | } | ||
| 957 | |||
| 958 | pub fn writeXDerefType(writer: anytype, size: u8, die_offset: anytype) !void { | ||
| 959 | try writer.writeByte(OP.xderef_type); | ||
| 960 | try writer.writeByte(size); | ||
| 961 | try leb.writeUleb128(writer, die_offset); | ||
| 962 | } | ||
| 963 | |||
| 964 | // 2.5.1.4: Arithmetic and Logical Operations | ||
| 965 | |||
| 966 | pub fn writePlusUconst(writer: anytype, uint_value: anytype) !void { | ||
| 967 | try writer.writeByte(OP.plus_uconst); | ||
| 968 | try leb.writeUleb128(writer, uint_value); | ||
| 969 | } | ||
| 970 | |||
| 971 | // 2.5.1.5: Control Flow Operations | ||
| 972 | |||
| 973 | pub fn writeSkip(writer: anytype, offset: i16) !void { | ||
| 974 | try writer.writeByte(OP.skip); | ||
| 975 | try writer.writeInt(i16, offset, options.endian); | ||
| 976 | } | ||
| 977 | |||
| 978 | pub fn writeBra(writer: anytype, offset: i16) !void { | ||
| 979 | try writer.writeByte(OP.bra); | ||
| 980 | try writer.writeInt(i16, offset, options.endian); | ||
| 981 | } | ||
| 982 | |||
| 983 | pub fn writeCall(writer: anytype, comptime T: type, offset: T) !void { | ||
| 984 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 985 | switch (T) { | ||
| 986 | u16 => try writer.writeByte(OP.call2), | ||
| 987 | u32 => try writer.writeByte(OP.call4), | ||
| 988 | else => @compileError("Call operand must be a 2 or 4 byte offset"), | ||
| 989 | } | ||
| 990 | |||
| 991 | try writer.writeInt(T, offset, options.endian); | ||
| 992 | } | ||
| 993 | |||
| 994 | pub fn writeCallRef(writer: anytype, comptime is_64: bool, value: if (is_64) u64 else u32) !void { | ||
| 995 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 996 | try writer.writeByte(OP.call_ref); | ||
| 997 | try writer.writeInt(if (is_64) u64 else u32, value, options.endian); | ||
| 998 | } | ||
| 999 | |||
| 1000 | pub fn writeConvert(writer: anytype, die_offset: anytype) !void { | ||
| 1001 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 1002 | try writer.writeByte(OP.convert); | ||
| 1003 | try leb.writeUleb128(writer, die_offset); | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | pub fn writeReinterpret(writer: anytype, die_offset: anytype) !void { | ||
| 1007 | if (options.call_frame_context) return error.InvalidCFAOpcode; | ||
| 1008 | try writer.writeByte(OP.reinterpret); | ||
| 1009 | try leb.writeUleb128(writer, die_offset); | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | // 2.5.1.7: Special Operations | ||
| 1013 | |||
| 1014 | pub fn writeEntryValue(writer: anytype, expression: []const u8) !void { | ||
| 1015 | try writer.writeByte(OP.entry_value); | ||
| 1016 | try leb.writeUleb128(writer, expression.len); | ||
| 1017 | try writer.writeAll(expression); | ||
| 1018 | } | ||
| 1019 | |||
| 1020 | // 2.6: Location Descriptions | ||
| 1021 | pub fn writeReg(writer: anytype, register: u8) !void { | ||
| 1022 | try writer.writeByte(OP.reg0 + register); | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | pub fn writeRegx(writer: anytype, register: anytype) !void { | ||
| 1026 | try writer.writeByte(OP.regx); | ||
| 1027 | try leb.writeUleb128(writer, register); | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | pub fn writeImplicitValue(writer: anytype, value_bytes: []const u8) !void { | ||
| 1031 | try writer.writeByte(OP.implicit_value); | ||
| 1032 | try leb.writeUleb128(writer, value_bytes.len); | ||
| 1033 | try writer.writeAll(value_bytes); | ||
| 1034 | } | ||
| 1035 | }; | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | // Certain opcodes are not allowed in a CFA context, see 6.4.2 | ||
| 1039 | fn isOpcodeValidInCFA(opcode: u8) bool { | ||
| 1040 | return switch (opcode) { | ||
| 1041 | OP.addrx, | ||
| 1042 | OP.call2, | ||
| 1043 | OP.call4, | ||
| 1044 | OP.call_ref, | ||
| 1045 | OP.const_type, | ||
| 1046 | OP.constx, | ||
| 1047 | OP.convert, | ||
| 1048 | OP.deref_type, | ||
| 1049 | OP.regval_type, | ||
| 1050 | OP.reinterpret, | ||
| 1051 | OP.push_object_address, | ||
| 1052 | OP.call_frame_cfa, | ||
| 1053 | => false, | ||
| 1054 | else => true, | ||
| 1055 | }; | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | fn isOpcodeRegisterLocation(opcode: u8) bool { | ||
| 1059 | return switch (opcode) { | ||
| 1060 | OP.reg0...OP.reg31, OP.regx => true, | ||
| 1061 | else => false, | ||
| 1062 | }; | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | const testing = std.testing; | ||
| 1066 | test "DWARF expressions" { | ||
| 1067 | const allocator = std.testing.allocator; | ||
| 1068 | |||
| 1069 | const options = ExpressionOptions{}; | ||
| 1070 | var stack_machine = StackMachine(options){}; | ||
| 1071 | defer stack_machine.deinit(allocator); | ||
| 1072 | |||
| 1073 | const b = Builder(options); | ||
| 1074 | |||
| 1075 | var program = std.ArrayList(u8).init(allocator); | ||
| 1076 | defer program.deinit(); | ||
| 1077 | |||
| 1078 | const writer = program.writer(); | ||
| 1079 | |||
| 1080 | // Literals | ||
| 1081 | { | ||
| 1082 | const context = ExpressionContext{}; | ||
| 1083 | for (0..32) |i| { | ||
| 1084 | try b.writeLiteral(writer, @intCast(i)); | ||
| 1085 | } | ||
| 1086 | |||
| 1087 | _ = try stack_machine.run(program.items, allocator, context, 0); | ||
| 1088 | |||
| 1089 | for (0..32) |i| { | ||
| 1090 | const expected = 31 - i; | ||
| 1091 | try testing.expectEqual(expected, stack_machine.stack.popOrNull().?.generic); | ||
| 1092 | } | ||
| 1093 | } | ||
| 1094 | |||
| 1095 | // Constants | ||
| 1096 | { | ||
| 1097 | stack_machine.reset(); | ||
| 1098 | program.clearRetainingCapacity(); | ||
| 1099 | |||
| 1100 | const input = [_]comptime_int{ | ||
| 1101 | 1, | ||
| 1102 | -1, | ||
| 1103 | @as(usize, @truncate(0x0fff)), | ||
| 1104 | @as(isize, @truncate(-0x0fff)), | ||
| 1105 | @as(usize, @truncate(0x0fffffff)), | ||
| 1106 | @as(isize, @truncate(-0x0fffffff)), | ||
| 1107 | @as(usize, @truncate(0x0fffffffffffffff)), | ||
| 1108 | @as(isize, @truncate(-0x0fffffffffffffff)), | ||
| 1109 | @as(usize, @truncate(0x8000000)), | ||
| 1110 | @as(isize, @truncate(-0x8000000)), | ||
| 1111 | @as(usize, @truncate(0x12345678_12345678)), | ||
| 1112 | @as(usize, @truncate(0xffffffff_ffffffff)), | ||
| 1113 | @as(usize, @truncate(0xeeeeeeee_eeeeeeee)), | ||
| 1114 | }; | ||
| 1115 | |||
| 1116 | try b.writeConst(writer, u8, input[0]); | ||
| 1117 | try b.writeConst(writer, i8, input[1]); | ||
| 1118 | try b.writeConst(writer, u16, input[2]); | ||
| 1119 | try b.writeConst(writer, i16, input[3]); | ||
| 1120 | try b.writeConst(writer, u32, input[4]); | ||
| 1121 | try b.writeConst(writer, i32, input[5]); | ||
| 1122 | try b.writeConst(writer, u64, input[6]); | ||
| 1123 | try b.writeConst(writer, i64, input[7]); | ||
| 1124 | try b.writeConst(writer, u28, input[8]); | ||
| 1125 | try b.writeConst(writer, i28, input[9]); | ||
| 1126 | try b.writeAddr(writer, input[10]); | ||
| 1127 | |||
| 1128 | var mock_compile_unit: dwarf.CompileUnit = undefined; | ||
| 1129 | mock_compile_unit.addr_base = 1; | ||
| 1130 | |||
| 1131 | var mock_debug_addr = std.ArrayList(u8).init(allocator); | ||
| 1132 | defer mock_debug_addr.deinit(); | ||
| 1133 | |||
| 1134 | try mock_debug_addr.writer().writeInt(u16, 0, native_endian); | ||
| 1135 | try mock_debug_addr.writer().writeInt(usize, input[11], native_endian); | ||
| 1136 | try mock_debug_addr.writer().writeInt(usize, input[12], native_endian); | ||
| 1137 | |||
| 1138 | const context = ExpressionContext{ | ||
| 1139 | .compile_unit = &mock_compile_unit, | ||
| 1140 | .debug_addr = mock_debug_addr.items, | ||
| 1141 | }; | ||
| 1142 | |||
| 1143 | try b.writeConstx(writer, @as(usize, 1)); | ||
| 1144 | try b.writeAddrx(writer, @as(usize, 1 + @sizeOf(usize))); | ||
| 1145 | |||
| 1146 | const die_offset: usize = @truncate(0xaabbccdd); | ||
| 1147 | const type_bytes: []const u8 = &.{ 1, 2, 3, 4 }; | ||
| 1148 | try b.writeConstType(writer, die_offset, type_bytes); | ||
| 1149 | |||
| 1150 | _ = try stack_machine.run(program.items, allocator, context, 0); | ||
| 1151 | |||
| 1152 | const const_type = stack_machine.stack.popOrNull().?.const_type; | ||
| 1153 | try testing.expectEqual(die_offset, const_type.type_offset); | ||
| 1154 | try testing.expectEqualSlices(u8, type_bytes, const_type.value_bytes); | ||
| 1155 | |||
| 1156 | const expected = .{ | ||
| 1157 | .{ usize, input[12], usize }, | ||
| 1158 | .{ usize, input[11], usize }, | ||
| 1159 | .{ usize, input[10], usize }, | ||
| 1160 | .{ isize, input[9], isize }, | ||
| 1161 | .{ usize, input[8], usize }, | ||
| 1162 | .{ isize, input[7], isize }, | ||
| 1163 | .{ usize, input[6], usize }, | ||
| 1164 | .{ isize, input[5], isize }, | ||
| 1165 | .{ usize, input[4], usize }, | ||
| 1166 | .{ isize, input[3], isize }, | ||
| 1167 | .{ usize, input[2], usize }, | ||
| 1168 | .{ isize, input[1], isize }, | ||
| 1169 | .{ usize, input[0], usize }, | ||
| 1170 | }; | ||
| 1171 | |||
| 1172 | inline for (expected) |e| { | ||
| 1173 | try testing.expectEqual(@as(e[0], e[1]), @as(e[2], @bitCast(stack_machine.stack.popOrNull().?.generic))); | ||
| 1174 | } | ||
| 1175 | } | ||
| 1176 | |||
| 1177 | // Register values | ||
| 1178 | if (@sizeOf(std.debug.ThreadContext) != 0) { | ||
| 1179 | stack_machine.reset(); | ||
| 1180 | program.clearRetainingCapacity(); | ||
| 1181 | |||
| 1182 | const reg_context = abi.RegisterContext{ | ||
| 1183 | .eh_frame = true, | ||
| 1184 | .is_macho = builtin.os.tag == .macos, | ||
| 1185 | }; | ||
| 1186 | var thread_context: std.debug.ThreadContext = undefined; | ||
| 1187 | std.debug.relocateContext(&thread_context); | ||
| 1188 | const context = ExpressionContext{ | ||
| 1189 | .thread_context = &thread_context, | ||
| 1190 | .reg_context = reg_context, | ||
| 1191 | }; | ||
| 1192 | |||
| 1193 | // Only test register operations on arch / os that have them implemented | ||
| 1194 | if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { | ||
| 1195 | |||
| 1196 | // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it | ||
| 1197 | |||
| 1198 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); | ||
| 1199 | (try abi.regValueNative(usize, &thread_context, abi.fpRegNum(reg_context), reg_context)).* = 1; | ||
| 1200 | (try abi.regValueNative(usize, &thread_context, abi.spRegNum(reg_context), reg_context)).* = 2; | ||
| 1201 | (try abi.regValueNative(usize, &thread_context, abi.ipRegNum(), reg_context)).* = 3; | ||
| 1202 | |||
| 1203 | try b.writeBreg(writer, abi.fpRegNum(reg_context), @as(usize, 100)); | ||
| 1204 | try b.writeBreg(writer, abi.spRegNum(reg_context), @as(usize, 200)); | ||
| 1205 | try b.writeBregx(writer, abi.ipRegNum(), @as(usize, 300)); | ||
| 1206 | try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 400)); | ||
| 1207 | |||
| 1208 | _ = try stack_machine.run(program.items, allocator, context, 0); | ||
| 1209 | |||
| 1210 | const regval_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1211 | try testing.expectEqual(@as(usize, 400), regval_type.type_offset); | ||
| 1212 | try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size); | ||
| 1213 | try testing.expectEqual(@as(usize, 0xee), regval_type.value); | ||
| 1214 | |||
| 1215 | try testing.expectEqual(@as(usize, 303), stack_machine.stack.popOrNull().?.generic); | ||
| 1216 | try testing.expectEqual(@as(usize, 202), stack_machine.stack.popOrNull().?.generic); | ||
| 1217 | try testing.expectEqual(@as(usize, 101), stack_machine.stack.popOrNull().?.generic); | ||
| 1218 | } else |err| { | ||
| 1219 | switch (err) { | ||
| 1220 | error.UnimplementedArch, | ||
| 1221 | error.UnimplementedOs, | ||
| 1222 | error.ThreadContextNotSupported, | ||
| 1223 | => {}, | ||
| 1224 | else => return err, | ||
| 1225 | } | ||
| 1226 | } | ||
| 1227 | } | ||
| 1228 | |||
| 1229 | // Stack operations | ||
| 1230 | { | ||
| 1231 | var context = ExpressionContext{}; | ||
| 1232 | |||
| 1233 | stack_machine.reset(); | ||
| 1234 | program.clearRetainingCapacity(); | ||
| 1235 | try b.writeConst(writer, u8, 1); | ||
| 1236 | try b.writeOpcode(writer, OP.dup); | ||
| 1237 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1238 | try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1239 | try testing.expectEqual(@as(usize, 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1240 | |||
| 1241 | stack_machine.reset(); | ||
| 1242 | program.clearRetainingCapacity(); | ||
| 1243 | try b.writeConst(writer, u8, 1); | ||
| 1244 | try b.writeOpcode(writer, OP.drop); | ||
| 1245 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1246 | try testing.expect(stack_machine.stack.popOrNull() == null); | ||
| 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.writePick(writer, 2); | ||
| 1254 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1255 | try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic); | ||
| 1256 | |||
| 1257 | stack_machine.reset(); | ||
| 1258 | program.clearRetainingCapacity(); | ||
| 1259 | try b.writeConst(writer, u8, 4); | ||
| 1260 | try b.writeConst(writer, u8, 5); | ||
| 1261 | try b.writeConst(writer, u8, 6); | ||
| 1262 | try b.writeOpcode(writer, OP.over); | ||
| 1263 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1264 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1265 | |||
| 1266 | stack_machine.reset(); | ||
| 1267 | program.clearRetainingCapacity(); | ||
| 1268 | try b.writeConst(writer, u8, 5); | ||
| 1269 | try b.writeConst(writer, u8, 6); | ||
| 1270 | try b.writeOpcode(writer, OP.swap); | ||
| 1271 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1272 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1273 | try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic); | ||
| 1274 | |||
| 1275 | stack_machine.reset(); | ||
| 1276 | program.clearRetainingCapacity(); | ||
| 1277 | try b.writeConst(writer, u8, 4); | ||
| 1278 | try b.writeConst(writer, u8, 5); | ||
| 1279 | try b.writeConst(writer, u8, 6); | ||
| 1280 | try b.writeOpcode(writer, OP.rot); | ||
| 1281 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1282 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1283 | try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic); | ||
| 1284 | try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic); | ||
| 1285 | |||
| 1286 | const deref_target: usize = @truncate(0xffeeffee_ffeeffee); | ||
| 1287 | |||
| 1288 | stack_machine.reset(); | ||
| 1289 | program.clearRetainingCapacity(); | ||
| 1290 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1291 | try b.writeOpcode(writer, OP.deref); | ||
| 1292 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1293 | try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic); | ||
| 1294 | |||
| 1295 | stack_machine.reset(); | ||
| 1296 | program.clearRetainingCapacity(); | ||
| 1297 | try b.writeLiteral(writer, 0); | ||
| 1298 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1299 | try b.writeOpcode(writer, OP.xderef); | ||
| 1300 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1301 | try testing.expectEqual(deref_target, stack_machine.stack.popOrNull().?.generic); | ||
| 1302 | |||
| 1303 | stack_machine.reset(); | ||
| 1304 | program.clearRetainingCapacity(); | ||
| 1305 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1306 | try b.writeDerefSize(writer, 1); | ||
| 1307 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1308 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic); | ||
| 1309 | |||
| 1310 | stack_machine.reset(); | ||
| 1311 | program.clearRetainingCapacity(); | ||
| 1312 | try b.writeLiteral(writer, 0); | ||
| 1313 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1314 | try b.writeXDerefSize(writer, 1); | ||
| 1315 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1316 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.popOrNull().?.generic); | ||
| 1317 | |||
| 1318 | const type_offset: usize = @truncate(0xaabbaabb_aabbaabb); | ||
| 1319 | |||
| 1320 | stack_machine.reset(); | ||
| 1321 | program.clearRetainingCapacity(); | ||
| 1322 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1323 | try b.writeDerefType(writer, 1, type_offset); | ||
| 1324 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1325 | const deref_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1326 | try testing.expectEqual(type_offset, deref_type.type_offset); | ||
| 1327 | try testing.expectEqual(@as(u8, 1), deref_type.type_size); | ||
| 1328 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), deref_type.value); | ||
| 1329 | |||
| 1330 | stack_machine.reset(); | ||
| 1331 | program.clearRetainingCapacity(); | ||
| 1332 | try b.writeLiteral(writer, 0); | ||
| 1333 | try b.writeAddr(writer, @intFromPtr(&deref_target)); | ||
| 1334 | try b.writeXDerefType(writer, 1, type_offset); | ||
| 1335 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1336 | const xderef_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1337 | try testing.expectEqual(type_offset, xderef_type.type_offset); | ||
| 1338 | try testing.expectEqual(@as(u8, 1), xderef_type.type_size); | ||
| 1339 | try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), xderef_type.value); | ||
| 1340 | |||
| 1341 | context.object_address = &deref_target; | ||
| 1342 | |||
| 1343 | stack_machine.reset(); | ||
| 1344 | program.clearRetainingCapacity(); | ||
| 1345 | try b.writeOpcode(writer, OP.push_object_address); | ||
| 1346 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1347 | try testing.expectEqual(@as(usize, @intFromPtr(context.object_address.?)), stack_machine.stack.popOrNull().?.generic); | ||
| 1348 | |||
| 1349 | // TODO: Test OP.form_tls_address | ||
| 1350 | |||
| 1351 | context.cfa = @truncate(0xccddccdd_ccddccdd); | ||
| 1352 | |||
| 1353 | stack_machine.reset(); | ||
| 1354 | program.clearRetainingCapacity(); | ||
| 1355 | try b.writeOpcode(writer, OP.call_frame_cfa); | ||
| 1356 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1357 | try testing.expectEqual(context.cfa.?, stack_machine.stack.popOrNull().?.generic); | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | // Arithmetic and Logical Operations | ||
| 1361 | { | ||
| 1362 | const context = ExpressionContext{}; | ||
| 1363 | |||
| 1364 | stack_machine.reset(); | ||
| 1365 | program.clearRetainingCapacity(); | ||
| 1366 | try b.writeConst(writer, i16, -4096); | ||
| 1367 | try b.writeOpcode(writer, OP.abs); | ||
| 1368 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1369 | try testing.expectEqual(@as(usize, 4096), stack_machine.stack.popOrNull().?.generic); | ||
| 1370 | |||
| 1371 | stack_machine.reset(); | ||
| 1372 | program.clearRetainingCapacity(); | ||
| 1373 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1374 | try b.writeConst(writer, u16, 0xf0ff); | ||
| 1375 | try b.writeOpcode(writer, OP.@"and"); | ||
| 1376 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1377 | try testing.expectEqual(@as(usize, 0xf00f), stack_machine.stack.popOrNull().?.generic); | ||
| 1378 | |||
| 1379 | stack_machine.reset(); | ||
| 1380 | program.clearRetainingCapacity(); | ||
| 1381 | try b.writeConst(writer, i16, -404); | ||
| 1382 | try b.writeConst(writer, i16, 100); | ||
| 1383 | try b.writeOpcode(writer, OP.div); | ||
| 1384 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1385 | try testing.expectEqual(@as(isize, -404 / 100), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic))); | ||
| 1386 | |||
| 1387 | stack_machine.reset(); | ||
| 1388 | program.clearRetainingCapacity(); | ||
| 1389 | try b.writeConst(writer, u16, 200); | ||
| 1390 | try b.writeConst(writer, u16, 50); | ||
| 1391 | try b.writeOpcode(writer, OP.minus); | ||
| 1392 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1393 | try testing.expectEqual(@as(usize, 150), stack_machine.stack.popOrNull().?.generic); | ||
| 1394 | |||
| 1395 | stack_machine.reset(); | ||
| 1396 | program.clearRetainingCapacity(); | ||
| 1397 | try b.writeConst(writer, u16, 123); | ||
| 1398 | try b.writeConst(writer, u16, 100); | ||
| 1399 | try b.writeOpcode(writer, OP.mod); | ||
| 1400 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1401 | try testing.expectEqual(@as(usize, 23), stack_machine.stack.popOrNull().?.generic); | ||
| 1402 | |||
| 1403 | stack_machine.reset(); | ||
| 1404 | program.clearRetainingCapacity(); | ||
| 1405 | try b.writeConst(writer, u16, 0xff); | ||
| 1406 | try b.writeConst(writer, u16, 0xee); | ||
| 1407 | try b.writeOpcode(writer, OP.mul); | ||
| 1408 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1409 | try testing.expectEqual(@as(usize, 0xed12), stack_machine.stack.popOrNull().?.generic); | ||
| 1410 | |||
| 1411 | stack_machine.reset(); | ||
| 1412 | program.clearRetainingCapacity(); | ||
| 1413 | try b.writeConst(writer, u16, 5); | ||
| 1414 | try b.writeOpcode(writer, OP.neg); | ||
| 1415 | try b.writeConst(writer, i16, -6); | ||
| 1416 | try b.writeOpcode(writer, OP.neg); | ||
| 1417 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1418 | try testing.expectEqual(@as(usize, 6), stack_machine.stack.popOrNull().?.generic); | ||
| 1419 | try testing.expectEqual(@as(isize, -5), @as(isize, @bitCast(stack_machine.stack.popOrNull().?.generic))); | ||
| 1420 | |||
| 1421 | stack_machine.reset(); | ||
| 1422 | program.clearRetainingCapacity(); | ||
| 1423 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1424 | try b.writeOpcode(writer, OP.not); | ||
| 1425 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1426 | try testing.expectEqual(~@as(usize, 0xff0f), stack_machine.stack.popOrNull().?.generic); | ||
| 1427 | |||
| 1428 | stack_machine.reset(); | ||
| 1429 | program.clearRetainingCapacity(); | ||
| 1430 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1431 | try b.writeConst(writer, u16, 0xf0ff); | ||
| 1432 | try b.writeOpcode(writer, OP.@"or"); | ||
| 1433 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1434 | try testing.expectEqual(@as(usize, 0xffff), stack_machine.stack.popOrNull().?.generic); | ||
| 1435 | |||
| 1436 | stack_machine.reset(); | ||
| 1437 | program.clearRetainingCapacity(); | ||
| 1438 | try b.writeConst(writer, i16, 402); | ||
| 1439 | try b.writeConst(writer, i16, 100); | ||
| 1440 | try b.writeOpcode(writer, OP.plus); | ||
| 1441 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1442 | try testing.expectEqual(@as(usize, 502), stack_machine.stack.popOrNull().?.generic); | ||
| 1443 | |||
| 1444 | stack_machine.reset(); | ||
| 1445 | program.clearRetainingCapacity(); | ||
| 1446 | try b.writeConst(writer, u16, 4096); | ||
| 1447 | try b.writePlusUconst(writer, @as(usize, 8192)); | ||
| 1448 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1449 | try testing.expectEqual(@as(usize, 4096 + 8192), stack_machine.stack.popOrNull().?.generic); | ||
| 1450 | |||
| 1451 | stack_machine.reset(); | ||
| 1452 | program.clearRetainingCapacity(); | ||
| 1453 | try b.writeConst(writer, u16, 0xfff); | ||
| 1454 | try b.writeConst(writer, u16, 1); | ||
| 1455 | try b.writeOpcode(writer, OP.shl); | ||
| 1456 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1457 | try testing.expectEqual(@as(usize, 0xfff << 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1458 | |||
| 1459 | stack_machine.reset(); | ||
| 1460 | program.clearRetainingCapacity(); | ||
| 1461 | try b.writeConst(writer, u16, 0xfff); | ||
| 1462 | try b.writeConst(writer, u16, 1); | ||
| 1463 | try b.writeOpcode(writer, OP.shr); | ||
| 1464 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1465 | try testing.expectEqual(@as(usize, 0xfff >> 1), stack_machine.stack.popOrNull().?.generic); | ||
| 1466 | |||
| 1467 | stack_machine.reset(); | ||
| 1468 | program.clearRetainingCapacity(); | ||
| 1469 | try b.writeConst(writer, u16, 0xfff); | ||
| 1470 | try b.writeConst(writer, u16, 1); | ||
| 1471 | try b.writeOpcode(writer, OP.shr); | ||
| 1472 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1473 | try testing.expectEqual(@as(usize, @bitCast(@as(isize, 0xfff) >> 1)), stack_machine.stack.popOrNull().?.generic); | ||
| 1474 | |||
| 1475 | stack_machine.reset(); | ||
| 1476 | program.clearRetainingCapacity(); | ||
| 1477 | try b.writeConst(writer, u16, 0xf0ff); | ||
| 1478 | try b.writeConst(writer, u16, 0xff0f); | ||
| 1479 | try b.writeOpcode(writer, OP.xor); | ||
| 1480 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1481 | try testing.expectEqual(@as(usize, 0x0ff0), stack_machine.stack.popOrNull().?.generic); | ||
| 1482 | } | ||
| 1483 | |||
| 1484 | // Control Flow Operations | ||
| 1485 | { | ||
| 1486 | const context = ExpressionContext{}; | ||
| 1487 | const expected = .{ | ||
| 1488 | .{ OP.le, 1, 1, 0 }, | ||
| 1489 | .{ OP.ge, 1, 0, 1 }, | ||
| 1490 | .{ OP.eq, 1, 0, 0 }, | ||
| 1491 | .{ OP.lt, 0, 1, 0 }, | ||
| 1492 | .{ OP.gt, 0, 0, 1 }, | ||
| 1493 | .{ OP.ne, 0, 1, 1 }, | ||
| 1494 | }; | ||
| 1495 | |||
| 1496 | inline for (expected) |e| { | ||
| 1497 | stack_machine.reset(); | ||
| 1498 | program.clearRetainingCapacity(); | ||
| 1499 | |||
| 1500 | try b.writeConst(writer, u16, 0); | ||
| 1501 | try b.writeConst(writer, u16, 0); | ||
| 1502 | try b.writeOpcode(writer, e[0]); | ||
| 1503 | try b.writeConst(writer, u16, 0); | ||
| 1504 | try b.writeConst(writer, u16, 1); | ||
| 1505 | try b.writeOpcode(writer, e[0]); | ||
| 1506 | try b.writeConst(writer, u16, 1); | ||
| 1507 | try b.writeConst(writer, u16, 0); | ||
| 1508 | try b.writeOpcode(writer, e[0]); | ||
| 1509 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1510 | try testing.expectEqual(@as(usize, e[3]), stack_machine.stack.popOrNull().?.generic); | ||
| 1511 | try testing.expectEqual(@as(usize, e[2]), stack_machine.stack.popOrNull().?.generic); | ||
| 1512 | try testing.expectEqual(@as(usize, e[1]), stack_machine.stack.popOrNull().?.generic); | ||
| 1513 | } | ||
| 1514 | |||
| 1515 | stack_machine.reset(); | ||
| 1516 | program.clearRetainingCapacity(); | ||
| 1517 | try b.writeLiteral(writer, 2); | ||
| 1518 | try b.writeSkip(writer, 1); | ||
| 1519 | try b.writeLiteral(writer, 3); | ||
| 1520 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1521 | try testing.expectEqual(@as(usize, 2), stack_machine.stack.popOrNull().?.generic); | ||
| 1522 | |||
| 1523 | stack_machine.reset(); | ||
| 1524 | program.clearRetainingCapacity(); | ||
| 1525 | try b.writeLiteral(writer, 2); | ||
| 1526 | try b.writeBra(writer, 1); | ||
| 1527 | try b.writeLiteral(writer, 3); | ||
| 1528 | try b.writeLiteral(writer, 0); | ||
| 1529 | try b.writeBra(writer, 1); | ||
| 1530 | try b.writeLiteral(writer, 4); | ||
| 1531 | try b.writeLiteral(writer, 5); | ||
| 1532 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1533 | try testing.expectEqual(@as(usize, 5), stack_machine.stack.popOrNull().?.generic); | ||
| 1534 | try testing.expectEqual(@as(usize, 4), stack_machine.stack.popOrNull().?.generic); | ||
| 1535 | try testing.expect(stack_machine.stack.popOrNull() == null); | ||
| 1536 | |||
| 1537 | // TODO: Test call2, call4, call_ref once implemented | ||
| 1538 | |||
| 1539 | } | ||
| 1540 | |||
| 1541 | // Type conversions | ||
| 1542 | { | ||
| 1543 | const context = ExpressionContext{}; | ||
| 1544 | stack_machine.reset(); | ||
| 1545 | program.clearRetainingCapacity(); | ||
| 1546 | |||
| 1547 | // TODO: Test typed OP.convert once implemented | ||
| 1548 | |||
| 1549 | const value: usize = @truncate(0xffeeffee_ffeeffee); | ||
| 1550 | var value_bytes: [options.addr_size]u8 = undefined; | ||
| 1551 | mem.writeInt(usize, &value_bytes, value, native_endian); | ||
| 1552 | |||
| 1553 | // Convert to generic type | ||
| 1554 | stack_machine.reset(); | ||
| 1555 | program.clearRetainingCapacity(); | ||
| 1556 | try b.writeConstType(writer, @as(usize, 0), &value_bytes); | ||
| 1557 | try b.writeConvert(writer, @as(usize, 0)); | ||
| 1558 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1559 | try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic); | ||
| 1560 | |||
| 1561 | // Reinterpret to generic type | ||
| 1562 | stack_machine.reset(); | ||
| 1563 | program.clearRetainingCapacity(); | ||
| 1564 | try b.writeConstType(writer, @as(usize, 0), &value_bytes); | ||
| 1565 | try b.writeReinterpret(writer, @as(usize, 0)); | ||
| 1566 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1567 | try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic); | ||
| 1568 | |||
| 1569 | // Reinterpret to new type | ||
| 1570 | const die_offset: usize = 0xffee; | ||
| 1571 | |||
| 1572 | stack_machine.reset(); | ||
| 1573 | program.clearRetainingCapacity(); | ||
| 1574 | try b.writeConstType(writer, @as(usize, 0), &value_bytes); | ||
| 1575 | try b.writeReinterpret(writer, die_offset); | ||
| 1576 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1577 | const const_type = stack_machine.stack.popOrNull().?.const_type; | ||
| 1578 | try testing.expectEqual(die_offset, const_type.type_offset); | ||
| 1579 | |||
| 1580 | stack_machine.reset(); | ||
| 1581 | program.clearRetainingCapacity(); | ||
| 1582 | try b.writeLiteral(writer, 0); | ||
| 1583 | try b.writeReinterpret(writer, die_offset); | ||
| 1584 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1585 | const regval_type = stack_machine.stack.popOrNull().?.regval_type; | ||
| 1586 | try testing.expectEqual(die_offset, regval_type.type_offset); | ||
| 1587 | } | ||
| 1588 | |||
| 1589 | // Special operations | ||
| 1590 | { | ||
| 1591 | var context = ExpressionContext{}; | ||
| 1592 | |||
| 1593 | stack_machine.reset(); | ||
| 1594 | program.clearRetainingCapacity(); | ||
| 1595 | try b.writeOpcode(writer, OP.nop); | ||
| 1596 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1597 | try testing.expect(stack_machine.stack.popOrNull() == null); | ||
| 1598 | |||
| 1599 | // Sub-expression | ||
| 1600 | { | ||
| 1601 | var sub_program = std.ArrayList(u8).init(allocator); | ||
| 1602 | defer sub_program.deinit(); | ||
| 1603 | const sub_writer = sub_program.writer(); | ||
| 1604 | try b.writeLiteral(sub_writer, 3); | ||
| 1605 | |||
| 1606 | stack_machine.reset(); | ||
| 1607 | program.clearRetainingCapacity(); | ||
| 1608 | try b.writeEntryValue(writer, sub_program.items); | ||
| 1609 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1610 | try testing.expectEqual(@as(usize, 3), stack_machine.stack.popOrNull().?.generic); | ||
| 1611 | } | ||
| 1612 | |||
| 1613 | // Register location description | ||
| 1614 | const reg_context = abi.RegisterContext{ | ||
| 1615 | .eh_frame = true, | ||
| 1616 | .is_macho = builtin.os.tag == .macos, | ||
| 1617 | }; | ||
| 1618 | var thread_context: std.debug.ThreadContext = undefined; | ||
| 1619 | std.debug.relocateContext(&thread_context); | ||
| 1620 | context = ExpressionContext{ | ||
| 1621 | .thread_context = &thread_context, | ||
| 1622 | .reg_context = reg_context, | ||
| 1623 | }; | ||
| 1624 | |||
| 1625 | if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { | ||
| 1626 | mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian); | ||
| 1627 | |||
| 1628 | var sub_program = std.ArrayList(u8).init(allocator); | ||
| 1629 | defer sub_program.deinit(); | ||
| 1630 | const sub_writer = sub_program.writer(); | ||
| 1631 | try b.writeReg(sub_writer, 0); | ||
| 1632 | |||
| 1633 | stack_machine.reset(); | ||
| 1634 | program.clearRetainingCapacity(); | ||
| 1635 | try b.writeEntryValue(writer, sub_program.items); | ||
| 1636 | _ = try stack_machine.run(program.items, allocator, context, null); | ||
| 1637 | try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.popOrNull().?.generic); | ||
| 1638 | } else |err| { | ||
| 1639 | switch (err) { | ||
| 1640 | error.UnimplementedArch, | ||
| 1641 | error.UnimplementedOs, | ||
| 1642 | error.ThreadContextNotSupported, | ||
| 1643 | => {}, | ||
| 1644 | else => return err, | ||
| 1645 | } | ||
| 1646 | } | ||
| 1647 | } | ||
| 1648 | } | ||
src/target.zig+1-1| ... | @@ -327,7 +327,7 @@ pub fn clangAssemblerSupportsMcpuArg(target: std.Target) bool { | ... | @@ -327,7 +327,7 @@ pub fn clangAssemblerSupportsMcpuArg(target: std.Target) bool { |
| 327 | } | 327 | } |
| 328 | 328 | ||
| 329 | pub fn needUnwindTables(target: std.Target) bool { | 329 | pub fn needUnwindTables(target: std.Target) bool { |
| 330 | return target.os.tag == .windows or target.isDarwin() or std.dwarf.abi.supportsUnwinding(target); | 330 | return target.os.tag == .windows or target.isDarwin() or std.debug.Dwarf.abi.supportsUnwinding(target); |
| 331 | } | 331 | } |
| 332 | 332 | ||
| 333 | pub fn defaultAddressSpace( | 333 | pub fn defaultAddressSpace( |