| author | |
| committer | |
| log | 8a73fc8d8ee0065e4d07e473ce6ab095cebfaece |
| tree | 36bed2955666bdfce7b96e2f33c9409bd26f03c1 |
| parent | a9568ed2963298864e5c9a92a3eafe81771128ff |
| parent | a87b5332319b20347916f77e57253e2df4b2a3af |
| signature |
make the fuzzer vaguely work on macOS21 files changed, 876 insertions(+), 534 deletions(-)
lib/build-web/fuzz.zig+15-14| ... | ... | @@ -228,20 +228,21 @@ fn unpackSourcesInner(tar_bytes: []u8) !void { |
| 228 | 228 | if (std.mem.endsWith(u8, tar_file.name, ".zig")) { |
| 229 | 229 | log.debug("found file: '{s}'", .{tar_file.name}); |
| 230 | 230 | const file_name = try gpa.dupe(u8, tar_file.name); |
| 231 | if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| { | |
| 232 | const pkg_name = file_name[0..pkg_name_end]; | |
| 233 | const gop = try Walk.modules.getOrPut(gpa, pkg_name); | |
| 234 | const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len); | |
| 235 | if (!gop.found_existing or | |
| 236 | std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or | |
| 237 | std.mem.eql(u8, file_name[pkg_name_end + 1 .. file_name.len - ".zig".len], pkg_name)) | |
| 238 | { | |
| 239 | gop.value_ptr.* = file; | |
| 240 | } | |
| 241 | const file_bytes = tar_reader.take(@intCast(tar_file.size)) catch unreachable; | |
| 242 | it.unread_file_bytes = 0; // we have read the whole thing | |
| 243 | assert(file == try Walk.add_file(file_name, file_bytes)); | |
| 244 | } | |
| 231 | // This is a hack to guess modules from the tar file contents. To handle modules | |
| 232 | // properly, the build system will need to change the structure here to have one | |
| 233 | // directory per module. This in turn requires compiler enhancements to allow | |
| 234 | // the build system to actually discover the required information. | |
| 235 | const mod_name, const is_module_root = p: { | |
| 236 | if (std.mem.find(u8, file_name, "std/")) |i| break :p .{ "std", std.mem.eql(u8, file_name[i + 4 ..], "std.zig") }; | |
| 237 | if (std.mem.endsWith(u8, file_name, "/builtin.zig")) break :p .{ "builtin", true }; | |
| 238 | break :p .{ "root", std.mem.endsWith(u8, file_name, "/root.zig") }; | |
| 239 | }; | |
| 240 | const gop = try Walk.modules.getOrPut(gpa, mod_name); | |
| 241 | const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len); | |
| 242 | if (!gop.found_existing or is_module_root) gop.value_ptr.* = file; | |
| 243 | const file_bytes = tar_reader.take(@intCast(tar_file.size)) catch unreachable; | |
| 244 | it.unread_file_bytes = 0; // we have read the whole thing | |
| 245 | assert(file == try Walk.add_file(file_name, file_bytes)); | |
| 245 | 246 | } else { |
| 246 | 247 | log.warn("skipping: '{s}' - the tar creation should have done that", .{tar_file.name}); |
| 247 | 248 | } |
lib/compiler/test_runner.zig+1-1| ... | ... | @@ -184,7 +184,7 @@ fn mainServer() !void { |
| 184 | 184 | const test_fn = builtin.test_functions[index]; |
| 185 | 185 | const entry_addr = @intFromPtr(test_fn.func); |
| 186 | 186 | |
| 187 | try server.serveU64Message(.fuzz_start_addr, entry_addr); | |
| 187 | try server.serveU64Message(.fuzz_start_addr, fuzz_abi.fuzzer_unslide_address(entry_addr)); | |
| 188 | 188 | defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1); |
| 189 | 189 | is_fuzz_test = false; |
| 190 | 190 | fuzz_test_index = index; |
lib/fuzzer.zig+28-6| ... | ... | @@ -116,13 +116,18 @@ const Executable = struct { |
| 116 | 116 | "failed to init memory map for coverage file '{s}': {t}", |
| 117 | 117 | .{ &coverage_file_name, e }, |
| 118 | 118 | ); |
| 119 | map.appendSliceAssumeCapacity(mem.asBytes(&abi.SeenPcsHeader{ | |
| 119 | map.appendSliceAssumeCapacity(@ptrCast(&abi.SeenPcsHeader{ | |
| 120 | 120 | .n_runs = 0, |
| 121 | 121 | .unique_runs = 0, |
| 122 | 122 | .pcs_len = pcs.len, |
| 123 | 123 | })); |
| 124 | 124 | map.appendNTimesAssumeCapacity(0, pc_bitset_usizes * @sizeOf(usize)); |
| 125 | map.appendSliceAssumeCapacity(mem.sliceAsBytes(pcs)); | |
| 125 | // Relocations have been applied to `pcs` so it contains runtime addresses (with slide | |
| 126 | // applied). We need to translate these to the virtual addresses as on disk. | |
| 127 | for (pcs) |pc| { | |
| 128 | const pc_vaddr = fuzzer_unslide_address(pc); | |
| 129 | map.appendSliceAssumeCapacity(@ptrCast(&pc_vaddr)); | |
| 130 | } | |
| 126 | 131 | return map; |
| 127 | 132 | } else { |
| 128 | 133 | const size = coverage_file.getEndPos() catch |e| panic( |
| ... | ... | @@ -215,7 +220,16 @@ const Executable = struct { |
| 215 | 220 | .{ self.pc_counters.len, pcs.len }, |
| 216 | 221 | ); |
| 217 | 222 | |
| 218 | self.pc_digest = std.hash.Wyhash.hash(0, mem.sliceAsBytes(pcs)); | |
| 223 | self.pc_digest = digest: { | |
| 224 | // Relocations have been applied to `pcs` so it contains runtime addresses (with slide | |
| 225 | // applied). We need to translate these to the virtual addresses as on disk. | |
| 226 | var h: std.hash.Wyhash = .init(0); | |
| 227 | for (pcs) |pc| { | |
| 228 | const pc_vaddr = fuzzer_unslide_address(pc); | |
| 229 | h.update(@ptrCast(&pc_vaddr)); | |
| 230 | } | |
| 231 | break :digest h.final(); | |
| 232 | }; | |
| 219 | 233 | self.shared_seen_pcs = getCoverageFile(cache_dir, pcs, self.pc_digest); |
| 220 | 234 | |
| 221 | 235 | return self; |
| ... | ... | @@ -622,6 +636,14 @@ export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void { |
| 622 | 636 | } |
| 623 | 637 | } |
| 624 | 638 | |
| 639 | export fn fuzzer_unslide_address(addr: usize) usize { | |
| 640 | const si = std.debug.getSelfDebugInfo() catch @compileError("unsupported"); | |
| 641 | const slide = si.getModuleSlide(std.debug.getDebugInfoAllocator(), addr) catch |err| { | |
| 642 | std.debug.panic("failed to find virtual address slide: {t}", .{err}); | |
| 643 | }; | |
| 644 | return addr - slide; | |
| 645 | } | |
| 646 | ||
| 625 | 647 | /// Helps determine run uniqueness in the face of recursion. |
| 626 | 648 | /// Currently not used by the fuzzer. |
| 627 | 649 | export threadlocal var __sancov_lowest_stack: usize = 0; |
| ... | ... | @@ -1185,13 +1207,13 @@ const Mutation = enum { |
| 1185 | 1207 | const j = rng.uintAtMostBiased(usize, corpus[splice_i].len - len); |
| 1186 | 1208 | out.appendSliceAssumeCapacity(corpus[splice_i][j..][0..len]); |
| 1187 | 1209 | }, |
| 1188 | .@"const" => out.appendSliceAssumeCapacity(mem.asBytes( | |
| 1210 | .@"const" => out.appendSliceAssumeCapacity(@ptrCast( | |
| 1189 | 1211 | &data_ctx[rng.uintLessThanBiased(usize, data_ctx.len)], |
| 1190 | 1212 | )), |
| 1191 | .small => out.appendSliceAssumeCapacity(mem.asBytes( | |
| 1213 | .small => out.appendSliceAssumeCapacity(@ptrCast( | |
| 1192 | 1214 | &mem.nativeTo(data_ctx[0], rng.int(SmallValue), data_ctx[1]), |
| 1193 | 1215 | )), |
| 1194 | .few => out.appendSliceAssumeCapacity(mem.asBytes( | |
| 1216 | .few => out.appendSliceAssumeCapacity(@ptrCast( | |
| 1195 | 1217 | &fewValue(rng, data_ctx[0], data_ctx[1]), |
| 1196 | 1218 | )), |
| 1197 | 1219 | } |
lib/std/Build/Fuzz.zig+25-4| ... | ... | @@ -383,7 +383,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO |
| 383 | 383 | errdefer gop.value_ptr.coverage.deinit(fuzz.gpa); |
| 384 | 384 | |
| 385 | 385 | const rebuilt_exe_path = run_step.rebuilt_executable.?; |
| 386 | var debug_info = std.debug.Info.load(fuzz.gpa, rebuilt_exe_path, &gop.value_ptr.coverage) catch |err| { | |
| 386 | const target = run_step.producer.?.rootModuleTarget(); | |
| 387 | var debug_info = std.debug.Info.load( | |
| 388 | fuzz.gpa, | |
| 389 | rebuilt_exe_path, | |
| 390 | &gop.value_ptr.coverage, | |
| 391 | target.ofmt, | |
| 392 | target.cpu.arch, | |
| 393 | ) catch |err| { | |
| 387 | 394 | log.err("step '{s}': failed to load debug information for '{f}': {s}", .{ |
| 388 | 395 | run_step.step.name, rebuilt_exe_path, @errorName(err), |
| 389 | 396 | }); |
| ... | ... | @@ -479,9 +486,23 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte |
| 479 | 486 | if (false) { |
| 480 | 487 | const sl = coverage_map.source_locations[index]; |
| 481 | 488 | const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename); |
| 482 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{ | |
| 483 | addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1], | |
| 484 | }); | |
| 489 | if (pcs.len == 1) { | |
| 490 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 (final)", .{ | |
| 491 | addr, file_name, sl.line, sl.column, | |
| 492 | }); | |
| 493 | } else if (index == 0) { | |
| 494 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 before {x}", .{ | |
| 495 | addr, file_name, sl.line, sl.column, pcs[index + 1], | |
| 496 | }); | |
| 497 | } else if (index == pcs.len - 1) { | |
| 498 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} (final) after {x}", .{ | |
| 499 | addr, file_name, sl.line, sl.column, index, pcs[index - 1], | |
| 500 | }); | |
| 501 | } else { | |
| 502 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{ | |
| 503 | addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1], | |
| 504 | }); | |
| 505 | } | |
| 485 | 506 | } |
| 486 | 507 | try coverage_map.entry_points.append(fuzz.gpa, @intCast(index)); |
| 487 | 508 | } |
lib/std/Build/Step/CheckObject.zig+14-15| ... | ... | @@ -729,10 +729,10 @@ const MachODumper = struct { |
| 729 | 729 | imports: std.ArrayListUnmanaged([]const u8) = .empty, |
| 730 | 730 | |
| 731 | 731 | fn parse(ctx: *ObjectContext) !void { |
| 732 | var it = ctx.getLoadCommandIterator(); | |
| 732 | var it = try ctx.getLoadCommandIterator(); | |
| 733 | 733 | var i: usize = 0; |
| 734 | while (it.next()) |cmd| { | |
| 735 | switch (cmd.cmd()) { | |
| 734 | while (try it.next()) |cmd| { | |
| 735 | switch (cmd.hdr.cmd) { | |
| 736 | 736 | .SEGMENT_64 => { |
| 737 | 737 | const seg = cmd.cast(macho.segment_command_64).?; |
| 738 | 738 | try ctx.segments.append(ctx.gpa, seg); |
| ... | ... | @@ -771,14 +771,13 @@ const MachODumper = struct { |
| 771 | 771 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + off)), 0); |
| 772 | 772 | } |
| 773 | 773 | |
| 774 | fn getLoadCommandIterator(ctx: ObjectContext) macho.LoadCommandIterator { | |
| 775 | const data = ctx.data[@sizeOf(macho.mach_header_64)..][0..ctx.header.sizeofcmds]; | |
| 776 | return .{ .ncmds = ctx.header.ncmds, .buffer = data }; | |
| 774 | fn getLoadCommandIterator(ctx: ObjectContext) !macho.LoadCommandIterator { | |
| 775 | return .init(&ctx.header, ctx.data[@sizeOf(macho.mach_header_64)..]); | |
| 777 | 776 | } |
| 778 | 777 | |
| 779 | fn getLoadCommand(ctx: ObjectContext, cmd: macho.LC) ?macho.LoadCommandIterator.LoadCommand { | |
| 780 | var it = ctx.getLoadCommandIterator(); | |
| 781 | while (it.next()) |lc| if (lc.cmd() == cmd) { | |
| 778 | fn getLoadCommand(ctx: ObjectContext, cmd: macho.LC) !?macho.LoadCommandIterator.LoadCommand { | |
| 779 | var it = try ctx.getLoadCommandIterator(); | |
| 780 | while (try it.next()) |lc| if (lc.hdr.cmd == cmd) { | |
| 782 | 781 | return lc; |
| 783 | 782 | }; |
| 784 | 783 | return null; |
| ... | ... | @@ -872,9 +871,9 @@ const MachODumper = struct { |
| 872 | 871 | \\LC {d} |
| 873 | 872 | \\cmd {s} |
| 874 | 873 | \\cmdsize {d} |
| 875 | , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); | |
| 874 | , .{ index, @tagName(lc.hdr.cmd), lc.hdr.cmdsize }); | |
| 876 | 875 | |
| 877 | switch (lc.cmd()) { | |
| 876 | switch (lc.hdr.cmd) { | |
| 878 | 877 | .SEGMENT_64 => { |
| 879 | 878 | const seg = lc.cast(macho.segment_command_64).?; |
| 880 | 879 | try writer.writeByte('\n'); |
| ... | ... | @@ -1592,9 +1591,9 @@ const MachODumper = struct { |
| 1592 | 1591 | .headers => { |
| 1593 | 1592 | try ObjectContext.dumpHeader(ctx.header, writer); |
| 1594 | 1593 | |
| 1595 | var it = ctx.getLoadCommandIterator(); | |
| 1594 | var it = try ctx.getLoadCommandIterator(); | |
| 1596 | 1595 | var i: usize = 0; |
| 1597 | while (it.next()) |cmd| { | |
| 1596 | while (try it.next()) |cmd| { | |
| 1598 | 1597 | try ObjectContext.dumpLoadCommand(cmd, i, writer); |
| 1599 | 1598 | try writer.writeByte('\n'); |
| 1600 | 1599 | |
| ... | ... | @@ -1615,7 +1614,7 @@ const MachODumper = struct { |
| 1615 | 1614 | .dyld_weak_bind, |
| 1616 | 1615 | .dyld_lazy_bind, |
| 1617 | 1616 | => { |
| 1618 | const cmd = ctx.getLoadCommand(.DYLD_INFO_ONLY) orelse | |
| 1617 | const cmd = try ctx.getLoadCommand(.DYLD_INFO_ONLY) orelse | |
| 1619 | 1618 | return step.fail("no dyld info found", .{}); |
| 1620 | 1619 | const lc = cmd.cast(macho.dyld_info_command).?; |
| 1621 | 1620 | |
| ... | ... | @@ -1649,7 +1648,7 @@ const MachODumper = struct { |
| 1649 | 1648 | }, |
| 1650 | 1649 | |
| 1651 | 1650 | .exports => blk: { |
| 1652 | if (ctx.getLoadCommand(.DYLD_INFO_ONLY)) |cmd| { | |
| 1651 | if (try ctx.getLoadCommand(.DYLD_INFO_ONLY)) |cmd| { | |
| 1653 | 1652 | const lc = cmd.cast(macho.dyld_info_command).?; |
| 1654 | 1653 | if (lc.export_size > 0) { |
| 1655 | 1654 | const data = ctx.data[lc.export_off..][0..lc.export_size]; |
lib/std/Build/Step/Compile.zig+5| ... | ... | @@ -1932,6 +1932,11 @@ pub fn rebuildInFuzzMode(c: *Compile, gpa: Allocator, progress_node: std.Progres |
| 1932 | 1932 | c.step.result_error_bundle.deinit(gpa); |
| 1933 | 1933 | c.step.result_error_bundle = std.zig.ErrorBundle.empty; |
| 1934 | 1934 | |
| 1935 | if (c.step.result_failed_command) |cmd| { | |
| 1936 | gpa.free(cmd); | |
| 1937 | c.step.result_failed_command = null; | |
| 1938 | } | |
| 1939 | ||
| 1935 | 1940 | const zig_args = try getZigArgs(c, true); |
| 1936 | 1941 | const maybe_output_bin_path = try c.step.evalZigProcess(zig_args, progress_node, false, null, gpa); |
| 1937 | 1942 | return maybe_output_bin_path.?; |
lib/std/Build/Step/Run.zig+11-2| ... | ... | @@ -1140,6 +1140,12 @@ pub fn rerunInFuzzMode( |
| 1140 | 1140 | .output_file, .output_directory => unreachable, |
| 1141 | 1141 | } |
| 1142 | 1142 | } |
| 1143 | ||
| 1144 | if (run.step.result_failed_command) |cmd| { | |
| 1145 | fuzz.gpa.free(cmd); | |
| 1146 | run.step.result_failed_command = null; | |
| 1147 | } | |
| 1148 | ||
| 1143 | 1149 | const has_side_effects = false; |
| 1144 | 1150 | const rand_int = std.crypto.random.int(u64); |
| 1145 | 1151 | const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); |
| ... | ... | @@ -1150,7 +1156,7 @@ pub fn rerunInFuzzMode( |
| 1150 | 1156 | .web_server = null, // only needed for time reports |
| 1151 | 1157 | .ttyconf = fuzz.ttyconf, |
| 1152 | 1158 | .unit_test_timeout_ns = null, // don't time out fuzz tests for now |
| 1153 | .gpa = undefined, // not used by `runCommand` | |
| 1159 | .gpa = fuzz.gpa, | |
| 1154 | 1160 | }, .{ |
| 1155 | 1161 | .unit_test_index = unit_test_index, |
| 1156 | 1162 | .fuzz = fuzz, |
| ... | ... | @@ -1870,7 +1876,10 @@ fn pollZigTest( |
| 1870 | 1876 | // test. For instance, if the test runner leaves this much time between us requesting a test to |
| 1871 | 1877 | // start and it acknowledging the test starting, we terminate the child and raise an error. This |
| 1872 | 1878 | // *should* never happen, but could in theory be caused by some very unlucky IB in a test. |
| 1873 | const response_timeout_ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); | |
| 1879 | const response_timeout_ns: ?u64 = ns: { | |
| 1880 | if (fuzz_context != null) break :ns null; // don't timeout fuzz tests | |
| 1881 | break :ns @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); | |
| 1882 | }; | |
| 1874 | 1883 | |
| 1875 | 1884 | const stdout = poller.reader(.stdout); |
| 1876 | 1885 | const stderr = poller.reader(.stderr); |
lib/std/Build/abi.zig+1| ... | ... | @@ -145,6 +145,7 @@ pub const fuzz = struct { |
| 145 | 145 | pub extern fn fuzzer_init_test(test_one: TestOne, unit_test_name: Slice) void; |
| 146 | 146 | pub extern fn fuzzer_new_input(bytes: Slice) void; |
| 147 | 147 | pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void; |
| 148 | pub extern fn fuzzer_unslide_address(addr: usize) usize; | |
| 148 | 149 | |
| 149 | 150 | pub const Slice = extern struct { |
| 150 | 151 | ptr: [*]const u8, |
lib/std/Io/Writer.zig+16-6| ... | ... | @@ -270,16 +270,17 @@ fn writeSplatHeaderLimitFinish( |
| 270 | 270 | remaining -= copy_len; |
| 271 | 271 | if (remaining == 0) break :v; |
| 272 | 272 | } |
| 273 | for (data[0 .. data.len - 1]) |buf| if (buf.len != 0) { | |
| 274 | const copy_len = @min(header.len, remaining); | |
| 275 | vecs[i] = buf; | |
| 273 | for (data[0 .. data.len - 1]) |buf| { | |
| 274 | if (buf.len == 0) continue; | |
| 275 | const copy_len = @min(buf.len, remaining); | |
| 276 | vecs[i] = buf[0..copy_len]; | |
| 276 | 277 | i += 1; |
| 277 | 278 | remaining -= copy_len; |
| 278 | 279 | if (remaining == 0) break :v; |
| 279 | 280 | if (vecs.len - i == 0) break :v; |
| 280 | }; | |
| 281 | } | |
| 281 | 282 | const pattern = data[data.len - 1]; |
| 282 | if (splat == 1) { | |
| 283 | if (splat == 1 or remaining < pattern.len) { | |
| 283 | 284 | vecs[i] = pattern[0..@min(remaining, pattern.len)]; |
| 284 | 285 | i += 1; |
| 285 | 286 | break :v; |
| ... | ... | @@ -915,7 +916,16 @@ pub fn sendFileHeader( |
| 915 | 916 | if (new_end <= w.buffer.len) { |
| 916 | 917 | @memcpy(w.buffer[w.end..][0..header.len], header); |
| 917 | 918 | w.end = new_end; |
| 918 | return header.len + try w.vtable.sendFile(w, file_reader, limit); | |
| 919 | const file_bytes = w.vtable.sendFile(w, file_reader, limit) catch |err| switch (err) { | |
| 920 | error.ReadFailed, error.WriteFailed => |e| return e, | |
| 921 | error.EndOfStream, error.Unimplemented => |e| { | |
| 922 | // These errors are non-fatal, so if we wrote any header bytes, we will report that | |
| 923 | // and suppress this error. Only if there was no header may we return the error. | |
| 924 | if (header.len != 0) return header.len; | |
| 925 | return e; | |
| 926 | }, | |
| 927 | }; | |
| 928 | return header.len + file_bytes; | |
| 919 | 929 | } |
| 920 | 930 | const buffered_contents = limit.slice(file_reader.interface.buffered()); |
| 921 | 931 | const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1); |
lib/std/debug.zig+2-1| ... | ... | @@ -21,6 +21,7 @@ const root = @import("root"); |
| 21 | 21 | pub const Dwarf = @import("debug/Dwarf.zig"); |
| 22 | 22 | pub const Pdb = @import("debug/Pdb.zig"); |
| 23 | 23 | pub const ElfFile = @import("debug/ElfFile.zig"); |
| 24 | pub const MachOFile = @import("debug/MachOFile.zig"); | |
| 24 | 25 | pub const Info = @import("debug/Info.zig"); |
| 25 | 26 | pub const Coverage = @import("debug/Coverage.zig"); |
| 26 | 27 | pub const cpu_context = @import("debug/cpu_context.zig"); |
| ... | ... | @@ -1366,7 +1367,7 @@ test printLineFromFile { |
| 1366 | 1367 | |
| 1367 | 1368 | /// The returned allocator should be thread-safe if the compilation is multi-threaded, because |
| 1368 | 1369 | /// multiple threads could capture and/or print stack traces simultaneously. |
| 1369 | fn getDebugInfoAllocator() Allocator { | |
| 1370 | pub fn getDebugInfoAllocator() Allocator { | |
| 1370 | 1371 | // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`. |
| 1371 | 1372 | if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) { |
| 1372 | 1373 | return root.debug.getDebugInfoAllocator(); |
lib/std/debug/Info.zig+65-26| ... | ... | @@ -9,49 +9,67 @@ |
| 9 | 9 | const std = @import("../std.zig"); |
| 10 | 10 | const Allocator = std.mem.Allocator; |
| 11 | 11 | const Path = std.Build.Cache.Path; |
| 12 | const ElfFile = std.debug.ElfFile; | |
| 13 | 12 | const assert = std.debug.assert; |
| 14 | 13 | const Coverage = std.debug.Coverage; |
| 15 | 14 | const SourceLocation = std.debug.Coverage.SourceLocation; |
| 16 | 15 | |
| 16 | const ElfFile = std.debug.ElfFile; | |
| 17 | const MachOFile = std.debug.MachOFile; | |
| 18 | ||
| 17 | 19 | const Info = @This(); |
| 18 | 20 | |
| 19 | /// Sorted by key, ascending. | |
| 20 | address_map: std.AutoArrayHashMapUnmanaged(u64, ElfFile), | |
| 21 | impl: union(enum) { | |
| 22 | elf: ElfFile, | |
| 23 | macho: MachOFile, | |
| 24 | }, | |
| 21 | 25 | /// Externally managed, outlives this `Info` instance. |
| 22 | 26 | coverage: *Coverage, |
| 23 | 27 | |
| 24 | pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || std.debug.Dwarf.ScanError || error{MissingDebugInfo}; | |
| 28 | pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError || error{ MissingDebugInfo, UnsupportedDebugInfo }; | |
| 29 | ||
| 30 | pub fn load(gpa: Allocator, path: Path, coverage: *Coverage, format: std.Target.ObjectFormat, arch: std.Target.Cpu.Arch) LoadError!Info { | |
| 31 | switch (format) { | |
| 32 | .elf => { | |
| 33 | var file = try path.root_dir.handle.openFile(path.sub_path, .{}); | |
| 34 | defer file.close(); | |
| 25 | 35 | |
| 26 | pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info { | |
| 27 | var file = try path.root_dir.handle.openFile(path.sub_path, .{}); | |
| 28 | defer file.close(); | |
| 36 | var elf_file: ElfFile = try .load(gpa, file, null, &.none); | |
| 37 | errdefer elf_file.deinit(gpa); | |
| 29 | 38 | |
| 30 | var elf_file: ElfFile = try .load(gpa, file, null, &.none); | |
| 31 | errdefer elf_file.deinit(gpa); | |
| 39 | if (elf_file.dwarf == null) return error.MissingDebugInfo; | |
| 40 | try elf_file.dwarf.?.open(gpa, elf_file.endian); | |
| 41 | try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian); | |
| 32 | 42 | |
| 33 | if (elf_file.dwarf == null) return error.MissingDebugInfo; | |
| 34 | try elf_file.dwarf.?.open(gpa, elf_file.endian); | |
| 35 | try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian); | |
| 43 | return .{ | |
| 44 | .impl = .{ .elf = elf_file }, | |
| 45 | .coverage = coverage, | |
| 46 | }; | |
| 47 | }, | |
| 48 | .macho => { | |
| 49 | const path_str = try path.toString(gpa); | |
| 50 | defer gpa.free(path_str); | |
| 36 | 51 | |
| 37 | var info: Info = .{ | |
| 38 | .address_map = .{}, | |
| 39 | .coverage = coverage, | |
| 40 | }; | |
| 41 | try info.address_map.put(gpa, 0, elf_file); | |
| 42 | errdefer comptime unreachable; // elf_file is owned by the map now | |
| 43 | return info; | |
| 52 | var macho_file: MachOFile = try .load(gpa, path_str, arch); | |
| 53 | errdefer macho_file.deinit(gpa); | |
| 54 | ||
| 55 | return .{ | |
| 56 | .impl = .{ .macho = macho_file }, | |
| 57 | .coverage = coverage, | |
| 58 | }; | |
| 59 | }, | |
| 60 | else => return error.UnsupportedDebugInfo, | |
| 61 | } | |
| 44 | 62 | } |
| 45 | 63 | |
| 46 | 64 | pub fn deinit(info: *Info, gpa: Allocator) void { |
| 47 | for (info.address_map.values()) |*elf_file| { | |
| 48 | elf_file.dwarf.?.deinit(gpa); | |
| 65 | switch (info.impl) { | |
| 66 | .elf => |*ef| ef.deinit(gpa), | |
| 67 | .macho => |*mf| mf.deinit(gpa), | |
| 49 | 68 | } |
| 50 | info.address_map.deinit(gpa); | |
| 51 | 69 | info.* = undefined; |
| 52 | 70 | } |
| 53 | 71 | |
| 54 | pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError; | |
| 72 | pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{UnsupportedDebugInfo}; | |
| 55 | 73 | |
| 56 | 74 | /// Given an array of virtual memory addresses, sorted ascending, outputs a |
| 57 | 75 | /// corresponding array of source locations. |
| ... | ... | @@ -64,7 +82,28 @@ pub fn resolveAddresses( |
| 64 | 82 | output: []SourceLocation, |
| 65 | 83 | ) ResolveAddressesError!void { |
| 66 | 84 | assert(sorted_pc_addrs.len == output.len); |
| 67 | if (info.address_map.entries.len != 1) @panic("TODO"); | |
| 68 | const elf_file = &info.address_map.values()[0]; | |
| 69 | return info.coverage.resolveAddressesDwarf(gpa, elf_file.endian, sorted_pc_addrs, output, &elf_file.dwarf.?); | |
| 85 | switch (info.impl) { | |
| 86 | .elf => |*ef| return info.coverage.resolveAddressesDwarf(gpa, ef.endian, sorted_pc_addrs, output, &ef.dwarf.?), | |
| 87 | .macho => |*mf| { | |
| 88 | // Resolving all of the addresses at once unfortunately isn't so easy in Mach-O binaries | |
| 89 | // due to split debug information. For now, we'll just resolve the addreses one by one. | |
| 90 | for (sorted_pc_addrs, output) |pc_addr, *src_loc| { | |
| 91 | const dwarf, const dwarf_pc_addr = mf.getDwarfForAddress(gpa, pc_addr) catch |err| switch (err) { | |
| 92 | error.InvalidMachO, error.InvalidDwarf => return error.InvalidDebugInfo, | |
| 93 | else => |e| return e, | |
| 94 | }; | |
| 95 | if (dwarf.ranges.items.len == 0) { | |
| 96 | dwarf.populateRanges(gpa, .little) catch |err| switch (err) { | |
| 97 | error.EndOfStream, | |
| 98 | error.Overflow, | |
| 99 | error.StreamTooLong, | |
| 100 | error.ReadFailed, | |
| 101 | => return error.InvalidDebugInfo, | |
| 102 | else => |e| return e, | |
| 103 | }; | |
| 104 | } | |
| 105 | try info.coverage.resolveAddressesDwarf(gpa, .little, &.{dwarf_pc_addr}, src_loc[0..1], dwarf); | |
| 106 | } | |
| 107 | }, | |
| 108 | } | |
| 70 | 109 | } |
lib/std/debug/MachOFile.zig created+548| ... | ... | @@ -0,0 +1,548 @@ |
| 1 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 2 | symbols: []const Symbol, | |
| 3 | strings: []const u8, | |
| 4 | text_vmaddr: u64, | |
| 5 | ||
| 6 | /// Key is index into `strings` of the file path. | |
| 7 | ofiles: std.AutoArrayHashMapUnmanaged(u32, Error!OFile), | |
| 8 | ||
| 9 | pub const Error = error{ | |
| 10 | InvalidMachO, | |
| 11 | InvalidDwarf, | |
| 12 | MissingDebugInfo, | |
| 13 | UnsupportedDebugInfo, | |
| 14 | ReadFailed, | |
| 15 | OutOfMemory, | |
| 16 | }; | |
| 17 | ||
| 18 | pub fn deinit(mf: *MachOFile, gpa: Allocator) void { | |
| 19 | for (mf.ofiles.values()) |*maybe_of| { | |
| 20 | const of = &(maybe_of.* catch continue); | |
| 21 | posix.munmap(of.mapped_memory); | |
| 22 | of.dwarf.deinit(gpa); | |
| 23 | of.symbols_by_name.deinit(gpa); | |
| 24 | } | |
| 25 | mf.ofiles.deinit(gpa); | |
| 26 | gpa.free(mf.symbols); | |
| 27 | posix.munmap(mf.mapped_memory); | |
| 28 | } | |
| 29 | ||
| 30 | pub fn load(gpa: Allocator, path: []const u8, arch: std.Target.Cpu.Arch) Error!MachOFile { | |
| 31 | switch (arch) { | |
| 32 | .x86_64, .aarch64 => {}, | |
| 33 | else => unreachable, | |
| 34 | } | |
| 35 | ||
| 36 | const all_mapped_memory = try mapDebugInfoFile(path); | |
| 37 | errdefer posix.munmap(all_mapped_memory); | |
| 38 | ||
| 39 | // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal | |
| 40 | // binary": a simple file format which contains Mach-O binaries for multiple targets. For | |
| 41 | // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images | |
| 42 | // for both ARM64 macOS and x86_64 macOS. | |
| 43 | if (all_mapped_memory.len < 4) return error.InvalidMachO; | |
| 44 | const magic = std.mem.readInt(u32, all_mapped_memory.ptr[0..4], .little); | |
| 45 | ||
| 46 | // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`. | |
| 47 | const mapped_macho = switch (magic) { | |
| 48 | macho.MH_MAGIC_64 => all_mapped_memory, | |
| 49 | ||
| 50 | macho.FAT_CIGAM => mapped_macho: { | |
| 51 | // This is the universal binary format (aka a "fat binary"). | |
| 52 | var fat_r: Io.Reader = .fixed(all_mapped_memory); | |
| 53 | const hdr = fat_r.takeStruct(macho.fat_header, .big) catch |err| switch (err) { | |
| 54 | error.ReadFailed => unreachable, | |
| 55 | error.EndOfStream => return error.InvalidMachO, | |
| 56 | }; | |
| 57 | const want_cpu_type = switch (arch) { | |
| 58 | .x86_64 => macho.CPU_TYPE_X86_64, | |
| 59 | .aarch64 => macho.CPU_TYPE_ARM64, | |
| 60 | else => unreachable, | |
| 61 | }; | |
| 62 | for (0..hdr.nfat_arch) |_| { | |
| 63 | const fat_arch = fat_r.takeStruct(macho.fat_arch, .big) catch |err| switch (err) { | |
| 64 | error.ReadFailed => unreachable, | |
| 65 | error.EndOfStream => return error.InvalidMachO, | |
| 66 | }; | |
| 67 | if (fat_arch.cputype != want_cpu_type) continue; | |
| 68 | if (fat_arch.offset + fat_arch.size > all_mapped_memory.len) return error.InvalidMachO; | |
| 69 | break :mapped_macho all_mapped_memory[fat_arch.offset..][0..fat_arch.size]; | |
| 70 | } | |
| 71 | // `arch` was not present in the fat binary. | |
| 72 | return error.MissingDebugInfo; | |
| 73 | }, | |
| 74 | ||
| 75 | // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It | |
| 76 | // will be fairly easy to add support here if necessary; it's very similar to above. | |
| 77 | macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo, | |
| 78 | ||
| 79 | else => return error.InvalidMachO, | |
| 80 | }; | |
| 81 | ||
| 82 | var r: Io.Reader = .fixed(mapped_macho); | |
| 83 | const hdr = r.takeStruct(macho.mach_header_64, .little) catch |err| switch (err) { | |
| 84 | error.ReadFailed => unreachable, | |
| 85 | error.EndOfStream => return error.InvalidMachO, | |
| 86 | }; | |
| 87 | ||
| 88 | if (hdr.magic != macho.MH_MAGIC_64) | |
| 89 | return error.InvalidMachO; | |
| 90 | ||
| 91 | const symtab: macho.symtab_command, const text_vmaddr: u64 = lcs: { | |
| 92 | var it: macho.LoadCommandIterator = try .init(&hdr, mapped_macho[@sizeOf(macho.mach_header_64)..]); | |
| 93 | var symtab: ?macho.symtab_command = null; | |
| 94 | var text_vmaddr: ?u64 = null; | |
| 95 | while (try it.next()) |cmd| switch (cmd.hdr.cmd) { | |
| 96 | .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidMachO, | |
| 97 | .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| { | |
| 98 | if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue; | |
| 99 | text_vmaddr = seg_cmd.vmaddr; | |
| 100 | }, | |
| 101 | else => {}, | |
| 102 | }; | |
| 103 | break :lcs .{ | |
| 104 | symtab orelse return error.MissingDebugInfo, | |
| 105 | text_vmaddr orelse return error.MissingDebugInfo, | |
| 106 | }; | |
| 107 | }; | |
| 108 | ||
| 109 | const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1]; | |
| 110 | ||
| 111 | var symbols: std.ArrayList(Symbol) = try .initCapacity(gpa, symtab.nsyms); | |
| 112 | defer symbols.deinit(gpa); | |
| 113 | ||
| 114 | // This map is temporary; it is used only to detect duplicates here. This is | |
| 115 | // necessary because we prefer to use STAB ("symbolic debugging table") symbols, | |
| 116 | // but they might not be present, so we track normal symbols too. | |
| 117 | // Indices match 1-1 with those of `symbols`. | |
| 118 | var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty; | |
| 119 | defer symbol_names.deinit(gpa); | |
| 120 | try symbol_names.ensureUnusedCapacity(gpa, symtab.nsyms); | |
| 121 | ||
| 122 | var ofile: u32 = undefined; | |
| 123 | var last_sym: Symbol = undefined; | |
| 124 | var state: enum { | |
| 125 | init, | |
| 126 | oso_open, | |
| 127 | oso_close, | |
| 128 | bnsym, | |
| 129 | fun_strx, | |
| 130 | fun_size, | |
| 131 | ensym, | |
| 132 | } = .init; | |
| 133 | ||
| 134 | var sym_r: Io.Reader = .fixed(mapped_macho[symtab.symoff..]); | |
| 135 | for (0..symtab.nsyms) |_| { | |
| 136 | const sym = sym_r.takeStruct(macho.nlist_64, .little) catch |err| switch (err) { | |
| 137 | error.ReadFailed => unreachable, | |
| 138 | error.EndOfStream => return error.InvalidMachO, | |
| 139 | }; | |
| 140 | if (sym.n_type.bits.is_stab == 0) { | |
| 141 | if (sym.n_strx == 0) continue; | |
| 142 | switch (sym.n_type.bits.type) { | |
| 143 | .undf, .pbud, .indr, .abs, _ => continue, | |
| 144 | .sect => { | |
| 145 | const name = std.mem.sliceTo(strings[sym.n_strx..], 0); | |
| 146 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 147 | if (!gop.found_existing) { | |
| 148 | assert(gop.index == symbols.items.len); | |
| 149 | symbols.appendAssumeCapacity(.{ | |
| 150 | .strx = sym.n_strx, | |
| 151 | .addr = sym.n_value, | |
| 152 | .ofile = Symbol.unknown_ofile, | |
| 153 | }); | |
| 154 | } | |
| 155 | }, | |
| 156 | } | |
| 157 | continue; | |
| 158 | } | |
| 159 | ||
| 160 | // TODO handle globals N_GSYM, and statics N_STSYM | |
| 161 | switch (sym.n_type.stab) { | |
| 162 | .oso => switch (state) { | |
| 163 | .init, .oso_close => { | |
| 164 | state = .oso_open; | |
| 165 | ofile = sym.n_strx; | |
| 166 | }, | |
| 167 | else => return error.InvalidMachO, | |
| 168 | }, | |
| 169 | .bnsym => switch (state) { | |
| 170 | .oso_open, .ensym => { | |
| 171 | state = .bnsym; | |
| 172 | last_sym = .{ | |
| 173 | .strx = 0, | |
| 174 | .addr = sym.n_value, | |
| 175 | .ofile = ofile, | |
| 176 | }; | |
| 177 | }, | |
| 178 | else => return error.InvalidMachO, | |
| 179 | }, | |
| 180 | .fun => switch (state) { | |
| 181 | .bnsym => { | |
| 182 | state = .fun_strx; | |
| 183 | last_sym.strx = sym.n_strx; | |
| 184 | }, | |
| 185 | .fun_strx => { | |
| 186 | state = .fun_size; | |
| 187 | }, | |
| 188 | else => return error.InvalidMachO, | |
| 189 | }, | |
| 190 | .ensym => switch (state) { | |
| 191 | .fun_size => { | |
| 192 | state = .ensym; | |
| 193 | if (last_sym.strx != 0) { | |
| 194 | const name = std.mem.sliceTo(strings[last_sym.strx..], 0); | |
| 195 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 196 | if (!gop.found_existing) { | |
| 197 | assert(gop.index == symbols.items.len); | |
| 198 | symbols.appendAssumeCapacity(last_sym); | |
| 199 | } else { | |
| 200 | symbols.items[gop.index] = last_sym; | |
| 201 | } | |
| 202 | } | |
| 203 | }, | |
| 204 | else => return error.InvalidMachO, | |
| 205 | }, | |
| 206 | .so => switch (state) { | |
| 207 | .init, .oso_close => {}, | |
| 208 | .oso_open, .ensym => { | |
| 209 | state = .oso_close; | |
| 210 | }, | |
| 211 | else => return error.InvalidMachO, | |
| 212 | }, | |
| 213 | else => {}, | |
| 214 | } | |
| 215 | } | |
| 216 | ||
| 217 | switch (state) { | |
| 218 | .init => { | |
| 219 | // Missing STAB symtab entries is still okay, unless there were also no normal symbols. | |
| 220 | if (symbols.items.len == 0) return error.MissingDebugInfo; | |
| 221 | }, | |
| 222 | .oso_close => {}, | |
| 223 | else => return error.InvalidMachO, // corrupted STAB entries in symtab | |
| 224 | } | |
| 225 | ||
| 226 | const symbols_slice = try symbols.toOwnedSlice(gpa); | |
| 227 | errdefer gpa.free(symbols_slice); | |
| 228 | ||
| 229 | // Even though lld emits symbols in ascending order, this debug code | |
| 230 | // should work for programs linked in any valid way. | |
| 231 | // This sort is so that we can binary search later. | |
| 232 | mem.sort(Symbol, symbols_slice, {}, Symbol.addressLessThan); | |
| 233 | ||
| 234 | return .{ | |
| 235 | .mapped_memory = all_mapped_memory, | |
| 236 | .symbols = symbols_slice, | |
| 237 | .strings = strings, | |
| 238 | .ofiles = .empty, | |
| 239 | .text_vmaddr = text_vmaddr, | |
| 240 | }; | |
| 241 | } | |
| 242 | pub fn getDwarfForAddress(mf: *MachOFile, gpa: Allocator, vaddr: u64) !struct { *Dwarf, u64 } { | |
| 243 | const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo; | |
| 244 | ||
| 245 | if (symbol.ofile == Symbol.unknown_ofile) return error.MissingDebugInfo; | |
| 246 | ||
| 247 | // offset of `address` from start of `symbol` | |
| 248 | const address_symbol_offset = vaddr - symbol.addr; | |
| 249 | ||
| 250 | // Take the symbol name from the N_FUN STAB entry, we're going to | |
| 251 | // use it if we fail to find the DWARF infos | |
| 252 | const stab_symbol = mem.sliceTo(mf.strings[symbol.strx..], 0); | |
| 253 | ||
| 254 | const gop = try mf.ofiles.getOrPut(gpa, symbol.ofile); | |
| 255 | if (!gop.found_existing) { | |
| 256 | const name = mem.sliceTo(mf.strings[symbol.ofile..], 0); | |
| 257 | gop.value_ptr.* = loadOFile(gpa, name); | |
| 258 | } | |
| 259 | const of = &(gop.value_ptr.* catch |err| return err); | |
| 260 | ||
| 261 | const symbol_index = of.symbols_by_name.getKeyAdapted( | |
| 262 | @as([]const u8, stab_symbol), | |
| 263 | @as(OFile.SymbolAdapter, .{ .strtab = of.strtab, .symtab_raw = of.symtab_raw }), | |
| 264 | ) orelse return error.MissingDebugInfo; | |
| 265 | ||
| 266 | const symbol_ofile_vaddr = vaddr: { | |
| 267 | var sym = of.symtab_raw[symbol_index]; | |
| 268 | if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.nlist_64, &sym); | |
| 269 | break :vaddr sym.n_value; | |
| 270 | }; | |
| 271 | ||
| 272 | return .{ &of.dwarf, symbol_ofile_vaddr + address_symbol_offset }; | |
| 273 | } | |
| 274 | pub fn lookupSymbolName(mf: *MachOFile, vaddr: u64) error{MissingDebugInfo}![]const u8 { | |
| 275 | const symbol = Symbol.find(mf.symbols, vaddr) orelse return error.MissingDebugInfo; | |
| 276 | return mem.sliceTo(mf.strings[symbol.strx..], 0); | |
| 277 | } | |
| 278 | ||
| 279 | const OFile = struct { | |
| 280 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 281 | dwarf: Dwarf, | |
| 282 | strtab: []const u8, | |
| 283 | symtab_raw: []align(1) const macho.nlist_64, | |
| 284 | /// All named symbols in `symtab_raw`. Stored `u32` key is the index into `symtab_raw`. Accessed | |
| 285 | /// through `SymbolAdapter`, so that the symbol name is used as the logical key. | |
| 286 | symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true), | |
| 287 | ||
| 288 | const SymbolAdapter = struct { | |
| 289 | strtab: []const u8, | |
| 290 | symtab_raw: []align(1) const macho.nlist_64, | |
| 291 | pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 { | |
| 292 | _ = ctx; | |
| 293 | return @truncate(std.hash.Wyhash.hash(0, sym_name)); | |
| 294 | } | |
| 295 | pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool { | |
| 296 | _ = b_index; | |
| 297 | var b_sym = ctx.symtab_raw[b_sym_index]; | |
| 298 | if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.nlist_64, &b_sym); | |
| 299 | const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0); | |
| 300 | return mem.eql(u8, a_sym_name, b_sym_name); | |
| 301 | } | |
| 302 | }; | |
| 303 | }; | |
| 304 | ||
| 305 | const Symbol = struct { | |
| 306 | strx: u32, | |
| 307 | addr: u64, | |
| 308 | /// Value may be `unknown_ofile`. | |
| 309 | ofile: u32, | |
| 310 | const unknown_ofile = std.math.maxInt(u32); | |
| 311 | fn addressLessThan(context: void, lhs: Symbol, rhs: Symbol) bool { | |
| 312 | _ = context; | |
| 313 | return lhs.addr < rhs.addr; | |
| 314 | } | |
| 315 | /// Assumes that `symbols` is sorted in order of ascending `addr`. | |
| 316 | fn find(symbols: []const Symbol, address: usize) ?*const Symbol { | |
| 317 | if (symbols.len == 0) return null; // no potential match | |
| 318 | if (address < symbols[0].addr) return null; // address is before the lowest-address symbol | |
| 319 | var left: usize = 0; | |
| 320 | var len: usize = symbols.len; | |
| 321 | while (len > 1) { | |
| 322 | const mid = left + len / 2; | |
| 323 | if (address < symbols[mid].addr) { | |
| 324 | len /= 2; | |
| 325 | } else { | |
| 326 | left = mid; | |
| 327 | len -= len / 2; | |
| 328 | } | |
| 329 | } | |
| 330 | return &symbols[left]; | |
| 331 | } | |
| 332 | ||
| 333 | test find { | |
| 334 | const symbols: []const Symbol = &.{ | |
| 335 | .{ .addr = 100, .strx = undefined, .ofile = undefined }, | |
| 336 | .{ .addr = 200, .strx = undefined, .ofile = undefined }, | |
| 337 | .{ .addr = 300, .strx = undefined, .ofile = undefined }, | |
| 338 | }; | |
| 339 | ||
| 340 | try testing.expectEqual(null, find(symbols, 0)); | |
| 341 | try testing.expectEqual(null, find(symbols, 99)); | |
| 342 | try testing.expectEqual(&symbols[0], find(symbols, 100).?); | |
| 343 | try testing.expectEqual(&symbols[0], find(symbols, 150).?); | |
| 344 | try testing.expectEqual(&symbols[0], find(symbols, 199).?); | |
| 345 | ||
| 346 | try testing.expectEqual(&symbols[1], find(symbols, 200).?); | |
| 347 | try testing.expectEqual(&symbols[1], find(symbols, 250).?); | |
| 348 | try testing.expectEqual(&symbols[1], find(symbols, 299).?); | |
| 349 | ||
| 350 | try testing.expectEqual(&symbols[2], find(symbols, 300).?); | |
| 351 | try testing.expectEqual(&symbols[2], find(symbols, 301).?); | |
| 352 | try testing.expectEqual(&symbols[2], find(symbols, 5000).?); | |
| 353 | } | |
| 354 | }; | |
| 355 | test { | |
| 356 | _ = Symbol; | |
| 357 | } | |
| 358 | ||
| 359 | fn loadOFile(gpa: Allocator, o_file_name: []const u8) !OFile { | |
| 360 | const all_mapped_memory, const mapped_ofile = map: { | |
| 361 | const open_paren = paren: { | |
| 362 | if (std.mem.endsWith(u8, o_file_name, ")")) { | |
| 363 | if (std.mem.findScalarLast(u8, o_file_name, '(')) |i| { | |
| 364 | break :paren i; | |
| 365 | } | |
| 366 | } | |
| 367 | // Not an archive, just a normal path to a .o file | |
| 368 | const m = try mapDebugInfoFile(o_file_name); | |
| 369 | break :map .{ m, m }; | |
| 370 | }; | |
| 371 | ||
| 372 | // We have the form 'path/to/archive.a(entry.o)'. Map the archive and find the object file in question. | |
| 373 | ||
| 374 | const archive_path = o_file_name[0..open_paren]; | |
| 375 | const target_name_in_archive = o_file_name[open_paren + 1 .. o_file_name.len - 1]; | |
| 376 | const mapped_archive = try mapDebugInfoFile(archive_path); | |
| 377 | errdefer posix.munmap(mapped_archive); | |
| 378 | ||
| 379 | var ar_reader: Io.Reader = .fixed(mapped_archive); | |
| 380 | const ar_magic = ar_reader.take(8) catch return error.InvalidMachO; | |
| 381 | if (!std.mem.eql(u8, ar_magic, "!<arch>\n")) return error.InvalidMachO; | |
| 382 | while (true) { | |
| 383 | if (ar_reader.seek == ar_reader.buffer.len) return error.MissingDebugInfo; | |
| 384 | ||
| 385 | const raw_name = ar_reader.takeArray(16) catch return error.InvalidMachO; | |
| 386 | ar_reader.discardAll(12 + 6 + 6 + 8) catch return error.InvalidMachO; | |
| 387 | const raw_size = ar_reader.takeArray(10) catch return error.InvalidMachO; | |
| 388 | const file_magic = ar_reader.takeArray(2) catch return error.InvalidMachO; | |
| 389 | if (!std.mem.eql(u8, file_magic, "`\n")) return error.InvalidMachO; | |
| 390 | ||
| 391 | const size = std.fmt.parseInt(u32, mem.sliceTo(raw_size, ' '), 10) catch return error.InvalidMachO; | |
| 392 | const raw_data = ar_reader.take(size) catch return error.InvalidMachO; | |
| 393 | ||
| 394 | const entry_name: []const u8, const entry_contents: []const u8 = entry: { | |
| 395 | if (!std.mem.startsWith(u8, raw_name, "#1/")) { | |
| 396 | break :entry .{ mem.sliceTo(raw_name, '/'), raw_data }; | |
| 397 | } | |
| 398 | const len = std.fmt.parseInt(u32, mem.sliceTo(raw_name[3..], ' '), 10) catch return error.InvalidMachO; | |
| 399 | if (len > size) return error.InvalidMachO; | |
| 400 | break :entry .{ mem.sliceTo(raw_data[0..len], 0), raw_data[len..] }; | |
| 401 | }; | |
| 402 | ||
| 403 | if (std.mem.eql(u8, entry_name, target_name_in_archive)) { | |
| 404 | break :map .{ mapped_archive, entry_contents }; | |
| 405 | } | |
| 406 | } | |
| 407 | }; | |
| 408 | errdefer posix.munmap(all_mapped_memory); | |
| 409 | ||
| 410 | var r: Io.Reader = .fixed(mapped_ofile); | |
| 411 | const hdr = r.takeStruct(macho.mach_header_64, .little) catch |err| switch (err) { | |
| 412 | error.ReadFailed => unreachable, | |
| 413 | error.EndOfStream => return error.InvalidMachO, | |
| 414 | }; | |
| 415 | if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidMachO; | |
| 416 | ||
| 417 | const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: { | |
| 418 | var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null; | |
| 419 | var symtab_cmd: ?macho.symtab_command = null; | |
| 420 | var it: macho.LoadCommandIterator = try .init(&hdr, mapped_ofile[@sizeOf(macho.mach_header_64)..]); | |
| 421 | while (try it.next()) |lc| switch (lc.hdr.cmd) { | |
| 422 | .SEGMENT_64 => seg_cmd = lc, | |
| 423 | .SYMTAB => symtab_cmd = lc.cast(macho.symtab_command) orelse return error.InvalidMachO, | |
| 424 | else => {}, | |
| 425 | }; | |
| 426 | break :cmds .{ | |
| 427 | seg_cmd orelse return error.MissingDebugInfo, | |
| 428 | symtab_cmd orelse return error.MissingDebugInfo, | |
| 429 | }; | |
| 430 | }; | |
| 431 | ||
| 432 | if (mapped_ofile.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidMachO; | |
| 433 | if (mapped_ofile[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidMachO; | |
| 434 | const strtab = mapped_ofile[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1]; | |
| 435 | ||
| 436 | const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64); | |
| 437 | if (mapped_ofile.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidMachO; | |
| 438 | const symtab_raw: []align(1) const macho.nlist_64 = @ptrCast(mapped_ofile[symtab_cmd.symoff..][0..n_sym_bytes]); | |
| 439 | ||
| 440 | // TODO handle tentative (common) symbols | |
| 441 | var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty; | |
| 442 | defer symbols_by_name.deinit(gpa); | |
| 443 | try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab_raw.len)); | |
| 444 | for (symtab_raw, 0..) |sym_raw, sym_index| { | |
| 445 | var sym = sym_raw; | |
| 446 | if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.nlist_64, &sym); | |
| 447 | if (sym.n_strx == 0) continue; | |
| 448 | switch (sym.n_type.bits.type) { | |
| 449 | .undf => continue, // includes tentative symbols | |
| 450 | .abs => continue, | |
| 451 | else => {}, | |
| 452 | } | |
| 453 | const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); | |
| 454 | const gop = symbols_by_name.getOrPutAssumeCapacityAdapted( | |
| 455 | @as([]const u8, sym_name), | |
| 456 | @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab_raw = symtab_raw }), | |
| 457 | ); | |
| 458 | if (gop.found_existing) return error.InvalidMachO; | |
| 459 | gop.key_ptr.* = @intCast(sym_index); | |
| 460 | } | |
| 461 | ||
| 462 | var sections: Dwarf.SectionArray = @splat(null); | |
| 463 | for (seg_cmd.getSections()) |sect_raw| { | |
| 464 | var sect = sect_raw; | |
| 465 | if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(macho.section_64, &sect); | |
| 466 | ||
| 467 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; | |
| 468 | ||
| 469 | const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 470 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i; | |
| 471 | } else continue; | |
| 472 | ||
| 473 | if (mapped_ofile.len < sect.offset + sect.size) return error.InvalidMachO; | |
| 474 | const section_bytes = mapped_ofile[sect.offset..][0..sect.size]; | |
| 475 | sections[section_index] = .{ | |
| 476 | .data = section_bytes, | |
| 477 | .owned = false, | |
| 478 | }; | |
| 479 | } | |
| 480 | ||
| 481 | if (sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | |
| 482 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | |
| 483 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | |
| 484 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null) | |
| 485 | { | |
| 486 | return error.MissingDebugInfo; | |
| 487 | } | |
| 488 | ||
| 489 | var dwarf: Dwarf = .{ .sections = sections }; | |
| 490 | errdefer dwarf.deinit(gpa); | |
| 491 | dwarf.open(gpa, .little) catch |err| switch (err) { | |
| 492 | error.InvalidDebugInfo, | |
| 493 | error.EndOfStream, | |
| 494 | error.Overflow, | |
| 495 | error.StreamTooLong, | |
| 496 | => return error.InvalidDwarf, | |
| 497 | ||
| 498 | error.MissingDebugInfo, | |
| 499 | error.ReadFailed, | |
| 500 | error.OutOfMemory, | |
| 501 | => |e| return e, | |
| 502 | }; | |
| 503 | ||
| 504 | return .{ | |
| 505 | .mapped_memory = all_mapped_memory, | |
| 506 | .dwarf = dwarf, | |
| 507 | .strtab = strtab, | |
| 508 | .symtab_raw = symtab_raw, | |
| 509 | .symbols_by_name = symbols_by_name.move(), | |
| 510 | }; | |
| 511 | } | |
| 512 | ||
| 513 | /// Uses `mmap` to map the file at `path` into memory. | |
| 514 | fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 { | |
| 515 | const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 516 | error.FileNotFound => return error.MissingDebugInfo, | |
| 517 | else => return error.ReadFailed, | |
| 518 | }; | |
| 519 | defer file.close(); | |
| 520 | ||
| 521 | const file_len = std.math.cast( | |
| 522 | usize, | |
| 523 | file.getEndPos() catch return error.ReadFailed, | |
| 524 | ) orelse return error.ReadFailed; | |
| 525 | ||
| 526 | return posix.mmap( | |
| 527 | null, | |
| 528 | file_len, | |
| 529 | posix.PROT.READ, | |
| 530 | .{ .TYPE = .SHARED }, | |
| 531 | file.handle, | |
| 532 | 0, | |
| 533 | ) catch return error.ReadFailed; | |
| 534 | } | |
| 535 | ||
| 536 | const std = @import("std"); | |
| 537 | const Allocator = std.mem.Allocator; | |
| 538 | const Dwarf = std.debug.Dwarf; | |
| 539 | const Io = std.Io; | |
| 540 | const assert = std.debug.assert; | |
| 541 | const posix = std.posix; | |
| 542 | const macho = std.macho; | |
| 543 | const mem = std.mem; | |
| 544 | const testing = std.testing; | |
| 545 | ||
| 546 | const builtin = @import("builtin"); | |
| 547 | ||
| 548 | const MachOFile = @This(); |
lib/std/debug/SelfInfo/Elf.zig+5| ... | ... | @@ -80,6 +80,11 @@ pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]cons |
| 80 | 80 | if (module.name.len == 0) return error.MissingDebugInfo; |
| 81 | 81 | return module.name; |
| 82 | 82 | } |
| 83 | pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, address: usize) Error!usize { | |
| 84 | const module = try si.findModule(gpa, address, .shared); | |
| 85 | defer si.rwlock.unlockShared(); | |
| 86 | return module.load_offset; | |
| 87 | } | |
| 83 | 88 | |
| 84 | 89 | pub const can_unwind: bool = s: { |
| 85 | 90 | // The DWARF code can't deal with ILP32 ABIs yet: https://github.com/ziglang/zig/issues/25447 |
lib/std/debug/SelfInfo/MachO.zig+61-397| ... | ... | @@ -1,12 +1,10 @@ |
| 1 | 1 | mutex: std.Thread.Mutex, |
| 2 | 2 | /// Accessed through `Module.Adapter`. |
| 3 | 3 | modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false), |
| 4 | ofiles: std.StringArrayHashMapUnmanaged(?OFile), | |
| 5 | 4 | |
| 6 | 5 | pub const init: SelfInfo = .{ |
| 7 | 6 | .mutex = .{}, |
| 8 | 7 | .modules = .empty, |
| 9 | .ofiles = .empty, | |
| 10 | 8 | }; |
| 11 | 9 | pub fn deinit(si: *SelfInfo, gpa: Allocator) void { |
| 12 | 10 | for (si.modules.keys()) |*module| { |
| ... | ... | @@ -14,20 +12,12 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void { |
| 14 | 12 | const u = &(module.unwind orelse break :unwind catch break :unwind); |
| 15 | 13 | if (u.dwarf) |*dwarf| dwarf.deinit(gpa); |
| 16 | 14 | } |
| 17 | loaded: { | |
| 18 | const l = &(module.loaded_macho orelse break :loaded catch break :loaded); | |
| 19 | gpa.free(l.symbols); | |
| 20 | posix.munmap(l.mapped_memory); | |
| 15 | file: { | |
| 16 | const f = &(module.file orelse break :file catch break :file); | |
| 17 | f.deinit(gpa); | |
| 21 | 18 | } |
| 22 | 19 | } |
| 23 | for (si.ofiles.values()) |*opt_ofile| { | |
| 24 | const ofile = &(opt_ofile.* orelse continue); | |
| 25 | ofile.dwarf.deinit(gpa); | |
| 26 | ofile.symbols_by_name.deinit(gpa); | |
| 27 | posix.munmap(ofile.mapped_memory); | |
| 28 | } | |
| 29 | 20 | si.modules.deinit(gpa); |
| 30 | si.ofiles.deinit(gpa); | |
| 31 | 21 | } |
| 32 | 22 | |
| 33 | 23 | pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { |
| ... | ... | @@ -35,67 +25,55 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st |
| 35 | 25 | const module = try si.findModule(gpa, address); |
| 36 | 26 | defer si.mutex.unlock(); |
| 37 | 27 | |
| 38 | const loaded_macho = try module.getLoadedMachO(gpa); | |
| 39 | ||
| 40 | const vaddr = address - loaded_macho.vaddr_offset; | |
| 41 | const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown; | |
| 28 | const file = try module.getFile(gpa); | |
| 42 | 29 | |
| 43 | // offset of `address` from start of `symbol` | |
| 44 | const address_symbol_offset = vaddr - symbol.addr; | |
| 30 | // This is not necessarily the same as the vmaddr_slide that dyld would report. This is | |
| 31 | // because the segments in the file on disk might differ from the ones in memory. Normally | |
| 32 | // we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying: | |
| 33 | // it exists on disk (necessarily, because the kernel needs to load it!), but is also in | |
| 34 | // the dyld cache (dyld actually restart itself from cache after loading it), and the two | |
| 35 | // versions have (very) different segment base addresses. It's sort of like a large slide | |
| 36 | // has been applied to all addresses in memory. For an optimal experience, we consider the | |
| 37 | // on-disk vmaddr instead of the in-memory one. | |
| 38 | const vaddr_offset = module.text_base - file.text_vmaddr; | |
| 45 | 39 | |
| 46 | // Take the symbol name from the N_FUN STAB entry, we're going to | |
| 47 | // use it if we fail to find the DWARF infos | |
| 48 | const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0); | |
| 40 | const vaddr = address - vaddr_offset; | |
| 49 | 41 | |
| 50 | // If any information is missing, we can at least return this from now on. | |
| 51 | const sym_only_result: std.debug.Symbol = .{ | |
| 52 | .name = stab_symbol, | |
| 53 | .compile_unit_name = null, | |
| 54 | .source_location = null, | |
| 42 | const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, vaddr) catch { | |
| 43 | // Return at least the symbol name if available. | |
| 44 | return .{ | |
| 45 | .name = try file.lookupSymbolName(vaddr), | |
| 46 | .compile_unit_name = null, | |
| 47 | .source_location = null, | |
| 48 | }; | |
| 55 | 49 | }; |
| 56 | 50 | |
| 57 | if (symbol.ofile == MachoSymbol.unknown_ofile) { | |
| 58 | // We don't have STAB info, so can't track down the object file; all we can do is the symbol name. | |
| 59 | return sym_only_result; | |
| 60 | } | |
| 61 | ||
| 62 | const o_file: *OFile = of: { | |
| 63 | const path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0); | |
| 64 | const gop = try si.ofiles.getOrPut(gpa, path); | |
| 65 | if (!gop.found_existing) { | |
| 66 | gop.value_ptr.* = loadOFile(gpa, path) catch null; | |
| 67 | } | |
| 68 | if (gop.value_ptr.*) |*o_file| { | |
| 69 | break :of o_file; | |
| 70 | } else { | |
| 71 | return sym_only_result; | |
| 72 | } | |
| 51 | const compile_unit = ofile_dwarf.findCompileUnit(native_endian, ofile_vaddr) catch { | |
| 52 | // Return at least the symbol name if available. | |
| 53 | return .{ | |
| 54 | .name = try file.lookupSymbolName(vaddr), | |
| 55 | .compile_unit_name = null, | |
| 56 | .source_location = null, | |
| 57 | }; | |
| 73 | 58 | }; |
| 74 | 59 | |
| 75 | const symbol_index = o_file.symbols_by_name.getKeyAdapted( | |
| 76 | @as([]const u8, stab_symbol), | |
| 77 | @as(OFile.SymbolAdapter, .{ .strtab = o_file.strtab, .symtab = o_file.symtab }), | |
| 78 | ) orelse return sym_only_result; | |
| 79 | const symbol_ofile_vaddr = o_file.symtab[symbol_index].n_value; | |
| 80 | ||
| 81 | const compile_unit = o_file.dwarf.findCompileUnit(native_endian, symbol_ofile_vaddr) catch return sym_only_result; | |
| 82 | ||
| 83 | 60 | return .{ |
| 84 | .name = o_file.dwarf.getSymbolName(symbol_ofile_vaddr + address_symbol_offset) orelse stab_symbol, | |
| 61 | .name = ofile_dwarf.getSymbolName(ofile_vaddr) orelse | |
| 62 | try file.lookupSymbolName(vaddr), | |
| 85 | 63 | .compile_unit_name = compile_unit.die.getAttrString( |
| 86 | &o_file.dwarf, | |
| 64 | ofile_dwarf, | |
| 87 | 65 | native_endian, |
| 88 | 66 | std.dwarf.AT.name, |
| 89 | o_file.dwarf.section(.debug_str), | |
| 67 | ofile_dwarf.section(.debug_str), | |
| 90 | 68 | compile_unit, |
| 91 | 69 | ) catch |err| switch (err) { |
| 92 | 70 | error.MissingDebugInfo, error.InvalidDebugInfo => null, |
| 93 | 71 | }, |
| 94 | .source_location = o_file.dwarf.getLineNumberInfo( | |
| 72 | .source_location = ofile_dwarf.getLineNumberInfo( | |
| 95 | 73 | gpa, |
| 96 | 74 | native_endian, |
| 97 | 75 | compile_unit, |
| 98 | symbol_ofile_vaddr + address_symbol_offset, | |
| 76 | ofile_vaddr, | |
| 99 | 77 | ) catch null, |
| 100 | 78 | }; |
| 101 | 79 | } |
| ... | ... | @@ -104,6 +82,20 @@ pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]cons |
| 104 | 82 | defer si.mutex.unlock(); |
| 105 | 83 | return module.name; |
| 106 | 84 | } |
| 85 | pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, address: usize) Error!usize { | |
| 86 | const module = try si.findModule(gpa, address); | |
| 87 | defer si.mutex.unlock(); | |
| 88 | const header: *std.macho.mach_header_64 = @ptrFromInt(module.text_base); | |
| 89 | const raw_macho: [*]u8 = @ptrCast(header); | |
| 90 | var it = macho.LoadCommandIterator.init(header, raw_macho[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds]) catch unreachable; | |
| 91 | const text_vmaddr = while (it.next() catch unreachable) |load_cmd| { | |
| 92 | if (load_cmd.hdr.cmd != .SEGMENT_64) continue; | |
| 93 | const segment_cmd = load_cmd.cast(macho.segment_command_64).?; | |
| 94 | if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; | |
| 95 | break segment_cmd.vmaddr; | |
| 96 | } else unreachable; | |
| 97 | return module.text_base - text_vmaddr; | |
| 98 | } | |
| 107 | 99 | |
| 108 | 100 | pub const can_unwind: bool = true; |
| 109 | 101 | pub const UnwindContext = std.debug.Dwarf.SelfUnwinder; |
| ... | ... | @@ -447,7 +439,7 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module { |
| 447 | 439 | .text_base = @intFromPtr(info.fbase), |
| 448 | 440 | .name = std.mem.span(info.fname), |
| 449 | 441 | .unwind = null, |
| 450 | .loaded_macho = null, | |
| 442 | .file = null, | |
| 451 | 443 | }; |
| 452 | 444 | } |
| 453 | 445 | return gop.key_ptr; |
| ... | ... | @@ -457,7 +449,7 @@ const Module = struct { |
| 457 | 449 | text_base: usize, |
| 458 | 450 | name: []const u8, |
| 459 | 451 | unwind: ?(Error!Unwind), |
| 460 | loaded_macho: ?(Error!LoadedMachO), | |
| 452 | file: ?(Error!MachOFile), | |
| 461 | 453 | |
| 462 | 454 | const Adapter = struct { |
| 463 | 455 | pub fn hash(_: Adapter, text_base: usize) u32 { |
| ... | ... | @@ -488,34 +480,17 @@ const Module = struct { |
| 488 | 480 | dwarf: ?Dwarf.Unwind, |
| 489 | 481 | }; |
| 490 | 482 | |
| 491 | const LoadedMachO = struct { | |
| 492 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 493 | symbols: []const MachoSymbol, | |
| 494 | strings: []const u8, | |
| 495 | /// This is not necessarily the same as the vmaddr_slide that dyld would report. This is | |
| 496 | /// because the segments in the file on disk might differ from the ones in memory. Normally | |
| 497 | /// we wouldn't necessarily expect that to work, but /usr/lib/dyld is incredibly annoying: | |
| 498 | /// it exists on disk (necessarily, because the kernel needs to load it!), but is also in | |
| 499 | /// the dyld cache (dyld actually restart itself from cache after loading it), and the two | |
| 500 | /// versions have (very) different segment base addresses. It's sort of like a large slide | |
| 501 | /// has been applied to all addresses in memory. For an optimal experience, we consider the | |
| 502 | /// on-disk vmaddr instead of the in-memory one. | |
| 503 | vaddr_offset: usize, | |
| 504 | }; | |
| 505 | ||
| 506 | 483 | fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind { |
| 507 | 484 | if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa); |
| 508 | 485 | return if (module.unwind.?) |*unwind| unwind else |err| err; |
| 509 | 486 | } |
| 510 | 487 | fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind { |
| 511 | const header: *std.macho.mach_header = @ptrFromInt(module.text_base); | |
| 488 | const header: *std.macho.mach_header_64 = @ptrFromInt(module.text_base); | |
| 512 | 489 | |
| 513 | var it: macho.LoadCommandIterator = .{ | |
| 514 | .ncmds = header.ncmds, | |
| 515 | .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], | |
| 516 | }; | |
| 517 | const sections, const text_vmaddr = while (it.next()) |load_cmd| { | |
| 518 | if (load_cmd.cmd() != .SEGMENT_64) continue; | |
| 490 | const raw_macho: [*]u8 = @ptrCast(header); | |
| 491 | var it = macho.LoadCommandIterator.init(header, raw_macho[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds]) catch unreachable; | |
| 492 | const sections, const text_vmaddr = while (it.next() catch unreachable) |load_cmd| { | |
| 493 | if (load_cmd.hdr.cmd != .SEGMENT_64) continue; | |
| 519 | 494 | const segment_cmd = load_cmd.cast(macho.segment_command_64).?; |
| 520 | 495 | if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue; |
| 521 | 496 | break .{ load_cmd.getSections(), segment_cmd.vmaddr }; |
| ... | ... | @@ -568,237 +543,15 @@ const Module = struct { |
| 568 | 543 | }; |
| 569 | 544 | } |
| 570 | 545 | |
| 571 | fn getLoadedMachO(module: *Module, gpa: Allocator) Error!*LoadedMachO { | |
| 572 | if (module.loaded_macho == null) module.loaded_macho = loadMachO(module, gpa) catch |err| switch (err) { | |
| 573 | error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| e, | |
| 574 | else => error.ReadFailed, | |
| 575 | }; | |
| 576 | return if (module.loaded_macho.?) |*lm| lm else |err| err; | |
| 577 | } | |
| 578 | fn loadMachO(module: *const Module, gpa: Allocator) Error!LoadedMachO { | |
| 579 | const all_mapped_memory = try mapDebugInfoFile(module.name); | |
| 580 | errdefer posix.munmap(all_mapped_memory); | |
| 581 | ||
| 582 | // In most cases, the file we just mapped is a Mach-O binary. However, it could be a "universal | |
| 583 | // binary": a simple file format which contains Mach-O binaries for multiple targets. For | |
| 584 | // instance, `/usr/lib/dyld` is currently distributed as a universal binary containing images | |
| 585 | // for both ARM64 macOS and x86_64 macOS. | |
| 586 | if (all_mapped_memory.len < 4) return error.InvalidDebugInfo; | |
| 587 | const magic = @as(*const u32, @ptrCast(all_mapped_memory.ptr)).*; | |
| 588 | // The contents of a Mach-O file, which may or may not be the whole of `all_mapped_memory`. | |
| 589 | const mapped_macho = switch (magic) { | |
| 590 | macho.MH_MAGIC_64 => all_mapped_memory, | |
| 591 | ||
| 592 | macho.FAT_CIGAM => mapped_macho: { | |
| 593 | // This is the universal binary format (aka a "fat binary"). Annoyingly, the whole thing | |
| 594 | // is big-endian, so we'll be swapping some bytes. | |
| 595 | if (all_mapped_memory.len < @sizeOf(macho.fat_header)) return error.InvalidDebugInfo; | |
| 596 | const hdr: *const macho.fat_header = @ptrCast(all_mapped_memory.ptr); | |
| 597 | const archs_ptr: [*]const macho.fat_arch = @ptrCast(all_mapped_memory.ptr + @sizeOf(macho.fat_header)); | |
| 598 | const archs: []const macho.fat_arch = archs_ptr[0..@byteSwap(hdr.nfat_arch)]; | |
| 599 | const native_cpu_type = switch (builtin.cpu.arch) { | |
| 600 | .x86_64 => macho.CPU_TYPE_X86_64, | |
| 601 | .aarch64 => macho.CPU_TYPE_ARM64, | |
| 602 | else => comptime unreachable, | |
| 603 | }; | |
| 604 | for (archs) |*arch| { | |
| 605 | if (@byteSwap(arch.cputype) != native_cpu_type) continue; | |
| 606 | const offset = @byteSwap(arch.offset); | |
| 607 | const size = @byteSwap(arch.size); | |
| 608 | break :mapped_macho all_mapped_memory[offset..][0..size]; | |
| 609 | } | |
| 610 | // Our native architecture was not present in the fat binary. | |
| 611 | return error.MissingDebugInfo; | |
| 612 | }, | |
| 613 | ||
| 614 | // Even on modern 64-bit targets, this format doesn't seem to be too extensively used. It | |
| 615 | // will be fairly easy to add support here if necessary; it's very similar to above. | |
| 616 | macho.FAT_CIGAM_64 => return error.UnsupportedDebugInfo, | |
| 617 | ||
| 618 | else => return error.InvalidDebugInfo, | |
| 619 | }; | |
| 620 | ||
| 621 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_macho.ptr)); | |
| 622 | if (hdr.magic != macho.MH_MAGIC_64) | |
| 623 | return error.InvalidDebugInfo; | |
| 624 | ||
| 625 | const symtab: macho.symtab_command, const text_vmaddr: u64 = lc_iter: { | |
| 626 | var it: macho.LoadCommandIterator = .{ | |
| 627 | .ncmds = hdr.ncmds, | |
| 628 | .buffer = mapped_macho[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 629 | }; | |
| 630 | var symtab: ?macho.symtab_command = null; | |
| 631 | var text_vmaddr: ?u64 = null; | |
| 632 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 633 | .SYMTAB => symtab = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, | |
| 634 | .SEGMENT_64 => if (cmd.cast(macho.segment_command_64)) |seg_cmd| { | |
| 635 | if (!mem.eql(u8, seg_cmd.segName(), "__TEXT")) continue; | |
| 636 | text_vmaddr = seg_cmd.vmaddr; | |
| 637 | }, | |
| 638 | else => {}, | |
| 639 | }; | |
| 640 | break :lc_iter .{ | |
| 641 | symtab orelse return error.MissingDebugInfo, | |
| 642 | text_vmaddr orelse return error.MissingDebugInfo, | |
| 643 | }; | |
| 644 | }; | |
| 645 | ||
| 646 | const syms_ptr: [*]align(1) const macho.nlist_64 = @ptrCast(mapped_macho[symtab.symoff..]); | |
| 647 | const syms = syms_ptr[0..symtab.nsyms]; | |
| 648 | const strings = mapped_macho[symtab.stroff..][0 .. symtab.strsize - 1]; | |
| 649 | ||
| 650 | var symbols: std.ArrayList(MachoSymbol) = try .initCapacity(gpa, syms.len); | |
| 651 | defer symbols.deinit(gpa); | |
| 652 | ||
| 653 | // This map is temporary; it is used only to detect duplicates here. This is | |
| 654 | // necessary because we prefer to use STAB ("symbolic debugging table") symbols, | |
| 655 | // but they might not be present, so we track normal symbols too. | |
| 656 | // Indices match 1-1 with those of `symbols`. | |
| 657 | var symbol_names: std.StringArrayHashMapUnmanaged(void) = .empty; | |
| 658 | defer symbol_names.deinit(gpa); | |
| 659 | try symbol_names.ensureUnusedCapacity(gpa, syms.len); | |
| 660 | ||
| 661 | var ofile: u32 = undefined; | |
| 662 | var last_sym: MachoSymbol = undefined; | |
| 663 | var state: enum { | |
| 664 | init, | |
| 665 | oso_open, | |
| 666 | oso_close, | |
| 667 | bnsym, | |
| 668 | fun_strx, | |
| 669 | fun_size, | |
| 670 | ensym, | |
| 671 | } = .init; | |
| 672 | ||
| 673 | for (syms) |*sym| { | |
| 674 | if (sym.n_type.bits.is_stab == 0) { | |
| 675 | if (sym.n_strx == 0) continue; | |
| 676 | switch (sym.n_type.bits.type) { | |
| 677 | .undf, .pbud, .indr, .abs, _ => continue, | |
| 678 | .sect => { | |
| 679 | const name = std.mem.sliceTo(strings[sym.n_strx..], 0); | |
| 680 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 681 | if (!gop.found_existing) { | |
| 682 | assert(gop.index == symbols.items.len); | |
| 683 | symbols.appendAssumeCapacity(.{ | |
| 684 | .strx = sym.n_strx, | |
| 685 | .addr = sym.n_value, | |
| 686 | .ofile = MachoSymbol.unknown_ofile, | |
| 687 | }); | |
| 688 | } | |
| 689 | }, | |
| 690 | } | |
| 691 | continue; | |
| 692 | } | |
| 693 | ||
| 694 | // TODO handle globals N_GSYM, and statics N_STSYM | |
| 695 | switch (sym.n_type.stab) { | |
| 696 | .oso => switch (state) { | |
| 697 | .init, .oso_close => { | |
| 698 | state = .oso_open; | |
| 699 | ofile = sym.n_strx; | |
| 700 | }, | |
| 701 | else => return error.InvalidDebugInfo, | |
| 702 | }, | |
| 703 | .bnsym => switch (state) { | |
| 704 | .oso_open, .ensym => { | |
| 705 | state = .bnsym; | |
| 706 | last_sym = .{ | |
| 707 | .strx = 0, | |
| 708 | .addr = sym.n_value, | |
| 709 | .ofile = ofile, | |
| 710 | }; | |
| 711 | }, | |
| 712 | else => return error.InvalidDebugInfo, | |
| 713 | }, | |
| 714 | .fun => switch (state) { | |
| 715 | .bnsym => { | |
| 716 | state = .fun_strx; | |
| 717 | last_sym.strx = sym.n_strx; | |
| 718 | }, | |
| 719 | .fun_strx => { | |
| 720 | state = .fun_size; | |
| 721 | }, | |
| 722 | else => return error.InvalidDebugInfo, | |
| 723 | }, | |
| 724 | .ensym => switch (state) { | |
| 725 | .fun_size => { | |
| 726 | state = .ensym; | |
| 727 | if (last_sym.strx != 0) { | |
| 728 | const name = std.mem.sliceTo(strings[last_sym.strx..], 0); | |
| 729 | const gop = symbol_names.getOrPutAssumeCapacity(name); | |
| 730 | if (!gop.found_existing) { | |
| 731 | assert(gop.index == symbols.items.len); | |
| 732 | symbols.appendAssumeCapacity(last_sym); | |
| 733 | } else { | |
| 734 | symbols.items[gop.index] = last_sym; | |
| 735 | } | |
| 736 | } | |
| 737 | }, | |
| 738 | else => return error.InvalidDebugInfo, | |
| 739 | }, | |
| 740 | .so => switch (state) { | |
| 741 | .init, .oso_close => {}, | |
| 742 | .oso_open, .ensym => { | |
| 743 | state = .oso_close; | |
| 744 | }, | |
| 745 | else => return error.InvalidDebugInfo, | |
| 746 | }, | |
| 747 | else => {}, | |
| 748 | } | |
| 749 | } | |
| 750 | ||
| 751 | switch (state) { | |
| 752 | .init => { | |
| 753 | // Missing STAB symtab entries is still okay, unless there were also no normal symbols. | |
| 754 | if (symbols.items.len == 0) return error.MissingDebugInfo; | |
| 755 | }, | |
| 756 | .oso_close => {}, | |
| 757 | else => return error.InvalidDebugInfo, // corrupted STAB entries in symtab | |
| 758 | } | |
| 759 | ||
| 760 | const symbols_slice = try symbols.toOwnedSlice(gpa); | |
| 761 | errdefer gpa.free(symbols_slice); | |
| 762 | ||
| 763 | // Even though lld emits symbols in ascending order, this debug code | |
| 764 | // should work for programs linked in any valid way. | |
| 765 | // This sort is so that we can binary search later. | |
| 766 | mem.sort(MachoSymbol, symbols_slice, {}, MachoSymbol.addressLessThan); | |
| 767 | ||
| 768 | return .{ | |
| 769 | .mapped_memory = all_mapped_memory, | |
| 770 | .symbols = symbols_slice, | |
| 771 | .strings = strings, | |
| 772 | .vaddr_offset = module.text_base - text_vmaddr, | |
| 546 | fn getFile(module: *Module, gpa: Allocator) Error!*MachOFile { | |
| 547 | if (module.file == null) module.file = MachOFile.load(gpa, module.name, builtin.cpu.arch) catch |err| switch (err) { | |
| 548 | error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo, | |
| 549 | error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e, | |
| 773 | 550 | }; |
| 551 | return if (module.file.?) |*f| f else |err| err; | |
| 774 | 552 | } |
| 775 | 553 | }; |
| 776 | 554 | |
| 777 | const OFile = struct { | |
| 778 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 779 | dwarf: Dwarf, | |
| 780 | strtab: []const u8, | |
| 781 | symtab: []align(1) const macho.nlist_64, | |
| 782 | /// All named symbols in `symtab`. Stored `u32` key is the index into `symtab`. Accessed | |
| 783 | /// through `SymbolAdapter`, so that the symbol name is used as the logical key. | |
| 784 | symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true), | |
| 785 | ||
| 786 | const SymbolAdapter = struct { | |
| 787 | strtab: []const u8, | |
| 788 | symtab: []align(1) const macho.nlist_64, | |
| 789 | pub fn hash(ctx: SymbolAdapter, sym_name: []const u8) u32 { | |
| 790 | _ = ctx; | |
| 791 | return @truncate(std.hash.Wyhash.hash(0, sym_name)); | |
| 792 | } | |
| 793 | pub fn eql(ctx: SymbolAdapter, a_sym_name: []const u8, b_sym_index: u32, b_index: usize) bool { | |
| 794 | _ = b_index; | |
| 795 | const b_sym = ctx.symtab[b_sym_index]; | |
| 796 | const b_sym_name = std.mem.sliceTo(ctx.strtab[b_sym.n_strx..], 0); | |
| 797 | return mem.eql(u8, a_sym_name, b_sym_name); | |
| 798 | } | |
| 799 | }; | |
| 800 | }; | |
| 801 | ||
| 802 | 555 | const MachoSymbol = struct { |
| 803 | 556 | strx: u32, |
| 804 | 557 | addr: u64, |
| ... | ... | @@ -880,101 +633,12 @@ fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8 |
| 880 | 633 | }; |
| 881 | 634 | } |
| 882 | 635 | |
| 883 | fn loadOFile(gpa: Allocator, o_file_path: []const u8) !OFile { | |
| 884 | const mapped_mem = try mapDebugInfoFile(o_file_path); | |
| 885 | errdefer posix.munmap(mapped_mem); | |
| 886 | ||
| 887 | if (mapped_mem.len < @sizeOf(macho.mach_header_64)) return error.InvalidDebugInfo; | |
| 888 | const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); | |
| 889 | if (hdr.magic != std.macho.MH_MAGIC_64) return error.InvalidDebugInfo; | |
| 890 | ||
| 891 | const seg_cmd: macho.LoadCommandIterator.LoadCommand, const symtab_cmd: macho.symtab_command = cmds: { | |
| 892 | var seg_cmd: ?macho.LoadCommandIterator.LoadCommand = null; | |
| 893 | var symtab_cmd: ?macho.symtab_command = null; | |
| 894 | var it: macho.LoadCommandIterator = .{ | |
| 895 | .ncmds = hdr.ncmds, | |
| 896 | .buffer = mapped_mem[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | |
| 897 | }; | |
| 898 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 899 | .SEGMENT_64 => seg_cmd = cmd, | |
| 900 | .SYMTAB => symtab_cmd = cmd.cast(macho.symtab_command) orelse return error.InvalidDebugInfo, | |
| 901 | else => {}, | |
| 902 | }; | |
| 903 | break :cmds .{ | |
| 904 | seg_cmd orelse return error.MissingDebugInfo, | |
| 905 | symtab_cmd orelse return error.MissingDebugInfo, | |
| 906 | }; | |
| 907 | }; | |
| 908 | ||
| 909 | if (mapped_mem.len < symtab_cmd.stroff + symtab_cmd.strsize) return error.InvalidDebugInfo; | |
| 910 | if (mapped_mem[symtab_cmd.stroff + symtab_cmd.strsize - 1] != 0) return error.InvalidDebugInfo; | |
| 911 | const strtab = mapped_mem[symtab_cmd.stroff..][0 .. symtab_cmd.strsize - 1]; | |
| 912 | ||
| 913 | const n_sym_bytes = symtab_cmd.nsyms * @sizeOf(macho.nlist_64); | |
| 914 | if (mapped_mem.len < symtab_cmd.symoff + n_sym_bytes) return error.InvalidDebugInfo; | |
| 915 | const symtab: []align(1) const macho.nlist_64 = @ptrCast(mapped_mem[symtab_cmd.symoff..][0..n_sym_bytes]); | |
| 916 | ||
| 917 | // TODO handle tentative (common) symbols | |
| 918 | var symbols_by_name: std.ArrayHashMapUnmanaged(u32, void, void, true) = .empty; | |
| 919 | defer symbols_by_name.deinit(gpa); | |
| 920 | try symbols_by_name.ensureUnusedCapacity(gpa, @intCast(symtab.len)); | |
| 921 | for (symtab, 0..) |sym, sym_index| { | |
| 922 | if (sym.n_strx == 0) continue; | |
| 923 | switch (sym.n_type.bits.type) { | |
| 924 | .undf => continue, // includes tentative symbols | |
| 925 | .abs => continue, | |
| 926 | else => {}, | |
| 927 | } | |
| 928 | const sym_name = mem.sliceTo(strtab[sym.n_strx..], 0); | |
| 929 | const gop = symbols_by_name.getOrPutAssumeCapacityAdapted( | |
| 930 | @as([]const u8, sym_name), | |
| 931 | @as(OFile.SymbolAdapter, .{ .strtab = strtab, .symtab = symtab }), | |
| 932 | ); | |
| 933 | if (gop.found_existing) return error.InvalidDebugInfo; | |
| 934 | gop.key_ptr.* = @intCast(sym_index); | |
| 935 | } | |
| 936 | ||
| 937 | var sections: Dwarf.SectionArray = @splat(null); | |
| 938 | for (seg_cmd.getSections()) |sect| { | |
| 939 | if (!std.mem.eql(u8, "__DWARF", sect.segName())) continue; | |
| 940 | ||
| 941 | const section_index: usize = inline for (@typeInfo(Dwarf.Section.Id).@"enum".fields, 0..) |section, i| { | |
| 942 | if (mem.eql(u8, "__" ++ section.name, sect.sectName())) break i; | |
| 943 | } else continue; | |
| 944 | ||
| 945 | if (mapped_mem.len < sect.offset + sect.size) return error.InvalidDebugInfo; | |
| 946 | const section_bytes = mapped_mem[sect.offset..][0..sect.size]; | |
| 947 | sections[section_index] = .{ | |
| 948 | .data = section_bytes, | |
| 949 | .owned = false, | |
| 950 | }; | |
| 951 | } | |
| 952 | ||
| 953 | const missing_debug_info = | |
| 954 | sections[@intFromEnum(Dwarf.Section.Id.debug_info)] == null or | |
| 955 | sections[@intFromEnum(Dwarf.Section.Id.debug_abbrev)] == null or | |
| 956 | sections[@intFromEnum(Dwarf.Section.Id.debug_str)] == null or | |
| 957 | sections[@intFromEnum(Dwarf.Section.Id.debug_line)] == null; | |
| 958 | if (missing_debug_info) return error.MissingDebugInfo; | |
| 959 | ||
| 960 | var dwarf: Dwarf = .{ .sections = sections }; | |
| 961 | errdefer dwarf.deinit(gpa); | |
| 962 | try dwarf.open(gpa, native_endian); | |
| 963 | ||
| 964 | return .{ | |
| 965 | .mapped_memory = mapped_mem, | |
| 966 | .dwarf = dwarf, | |
| 967 | .strtab = strtab, | |
| 968 | .symtab = symtab, | |
| 969 | .symbols_by_name = symbols_by_name.move(), | |
| 970 | }; | |
| 971 | } | |
| 972 | ||
| 973 | 636 | const std = @import("std"); |
| 974 | 637 | const Io = std.Io; |
| 975 | 638 | const Allocator = std.mem.Allocator; |
| 976 | 639 | const Dwarf = std.debug.Dwarf; |
| 977 | 640 | const Error = std.debug.SelfInfoError; |
| 641 | const MachOFile = std.debug.MachOFile; | |
| 978 | 642 | const assert = std.debug.assert; |
| 979 | 643 | const posix = std.posix; |
| 980 | 644 | const macho = std.macho; |
lib/std/debug/SelfInfo/Windows.zig+6| ... | ... | @@ -33,6 +33,12 @@ pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]cons |
| 33 | 33 | const module = try si.findModule(gpa, address); |
| 34 | 34 | return module.name; |
| 35 | 35 | } |
| 36 | pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, address: usize) Error!usize { | |
| 37 | si.mutex.lock(); | |
| 38 | defer si.mutex.unlock(); | |
| 39 | const module = try si.findModule(gpa, address); | |
| 40 | return module.base_address; | |
| 41 | } | |
| 36 | 42 | |
| 37 | 43 | pub const can_unwind: bool = switch (builtin.cpu.arch) { |
| 38 | 44 | else => true, |
lib/std/http.zig+3-4| ... | ... | @@ -962,6 +962,7 @@ pub const BodyWriter = struct { |
| 962 | 962 | // have to flush the chunk header before knowing the chunk length. |
| 963 | 963 | return error.Unimplemented; |
| 964 | 964 | }; |
| 965 | if (data_len == 0) return error.EndOfStream; | |
| 965 | 966 | const out = bw.http_protocol_output; |
| 966 | 967 | l: switch (bw.state.chunk_len) { |
| 967 | 968 | 0 => { |
| ... | ... | @@ -975,8 +976,7 @@ pub const BodyWriter = struct { |
| 975 | 976 | 2 => { |
| 976 | 977 | try out.writeAll("\r\n"); |
| 977 | 978 | bw.state.chunk_len = 0; |
| 978 | assert(file_reader.atEnd()); | |
| 979 | return error.EndOfStream; | |
| 979 | continue :l 0; | |
| 980 | 980 | }, |
| 981 | 981 | else => { |
| 982 | 982 | const chunk_limit: std.Io.Limit = .limited(bw.state.chunk_len - 2); |
| ... | ... | @@ -985,8 +985,7 @@ pub const BodyWriter = struct { |
| 985 | 985 | else |
| 986 | 986 | try out.write(chunk_limit.slice(w.buffered())); |
| 987 | 987 | bw.state.chunk_len -= n; |
| 988 | const ret = w.consume(n); | |
| 989 | return ret; | |
| 988 | return w.consume(n); | |
| 990 | 989 | }, |
| 991 | 990 | } |
| 992 | 991 | } |
lib/std/macho.zig+35-33| ... | ... | @@ -1902,74 +1902,76 @@ pub const data_in_code_entry = extern struct { |
| 1902 | 1902 | }; |
| 1903 | 1903 | |
| 1904 | 1904 | pub const LoadCommandIterator = struct { |
| 1905 | next_index: usize, | |
| 1905 | 1906 | ncmds: usize, |
| 1906 | buffer: []const u8, | |
| 1907 | index: usize = 0, | |
| 1907 | r: std.Io.Reader, | |
| 1908 | 1908 | |
| 1909 | 1909 | pub const LoadCommand = struct { |
| 1910 | 1910 | hdr: load_command, |
| 1911 | 1911 | data: []const u8, |
| 1912 | 1912 | |
| 1913 | pub fn cmd(lc: LoadCommand) LC { | |
| 1914 | return lc.hdr.cmd; | |
| 1915 | } | |
| 1916 | ||
| 1917 | pub fn cmdsize(lc: LoadCommand) u32 { | |
| 1918 | return lc.hdr.cmdsize; | |
| 1919 | } | |
| 1920 | ||
| 1921 | 1913 | pub fn cast(lc: LoadCommand, comptime Cmd: type) ?Cmd { |
| 1922 | 1914 | if (lc.data.len < @sizeOf(Cmd)) return null; |
| 1923 | return @as(*align(1) const Cmd, @ptrCast(lc.data.ptr)).*; | |
| 1915 | const ptr: *align(1) const Cmd = @ptrCast(lc.data.ptr); | |
| 1916 | var cmd = ptr.*; | |
| 1917 | if (builtin.cpu.arch.endian() != .little) std.mem.byteSwapAllFields(Cmd, &cmd); | |
| 1918 | return cmd; | |
| 1924 | 1919 | } |
| 1925 | 1920 | |
| 1926 | 1921 | /// Asserts LoadCommand is of type segment_command_64. |
| 1922 | /// If the native endian is not `.little`, the `section_64` values must be byte-swapped by the caller. | |
| 1927 | 1923 | pub fn getSections(lc: LoadCommand) []align(1) const section_64 { |
| 1928 | 1924 | const segment_lc = lc.cast(segment_command_64).?; |
| 1929 | if (segment_lc.nsects == 0) return &[0]section_64{}; | |
| 1930 | const data = lc.data[@sizeOf(segment_command_64)..]; | |
| 1931 | const sections = @as([*]align(1) const section_64, @ptrCast(data.ptr))[0..segment_lc.nsects]; | |
| 1932 | return sections; | |
| 1925 | const sects_ptr: [*]align(1) const section_64 = @ptrCast(lc.data[@sizeOf(segment_command_64)..]); | |
| 1926 | return sects_ptr[0..segment_lc.nsects]; | |
| 1933 | 1927 | } |
| 1934 | 1928 | |
| 1935 | 1929 | /// Asserts LoadCommand is of type dylib_command. |
| 1936 | 1930 | pub fn getDylibPathName(lc: LoadCommand) []const u8 { |
| 1937 | 1931 | const dylib_lc = lc.cast(dylib_command).?; |
| 1938 | const data = lc.data[dylib_lc.dylib.name..]; | |
| 1939 | return mem.sliceTo(data, 0); | |
| 1932 | return mem.sliceTo(lc.data[dylib_lc.dylib.name..], 0); | |
| 1940 | 1933 | } |
| 1941 | 1934 | |
| 1942 | 1935 | /// Asserts LoadCommand is of type rpath_command. |
| 1943 | 1936 | pub fn getRpathPathName(lc: LoadCommand) []const u8 { |
| 1944 | 1937 | const rpath_lc = lc.cast(rpath_command).?; |
| 1945 | const data = lc.data[rpath_lc.path..]; | |
| 1946 | return mem.sliceTo(data, 0); | |
| 1938 | return mem.sliceTo(lc.data[rpath_lc.path..], 0); | |
| 1947 | 1939 | } |
| 1948 | 1940 | |
| 1949 | 1941 | /// Asserts LoadCommand is of type build_version_command. |
| 1942 | /// If the native endian is not `.little`, the `build_tool_version` values must be byte-swapped by the caller. | |
| 1950 | 1943 | pub fn getBuildVersionTools(lc: LoadCommand) []align(1) const build_tool_version { |
| 1951 | 1944 | const build_lc = lc.cast(build_version_command).?; |
| 1952 | const ntools = build_lc.ntools; | |
| 1953 | if (ntools == 0) return &[0]build_tool_version{}; | |
| 1954 | const data = lc.data[@sizeOf(build_version_command)..]; | |
| 1955 | const tools = @as([*]align(1) const build_tool_version, @ptrCast(data.ptr))[0..ntools]; | |
| 1956 | return tools; | |
| 1945 | const tools_ptr: [*]align(1) const build_tool_version = @ptrCast(lc.data[@sizeOf(build_version_command)..]); | |
| 1946 | return tools_ptr[0..build_lc.ntools]; | |
| 1957 | 1947 | } |
| 1958 | 1948 | }; |
| 1959 | 1949 | |
| 1960 | pub fn next(it: *LoadCommandIterator) ?LoadCommand { | |
| 1961 | if (it.index >= it.ncmds) return null; | |
| 1950 | pub fn next(it: *LoadCommandIterator) error{InvalidMachO}!?LoadCommand { | |
| 1951 | if (it.next_index >= it.ncmds) return null; | |
| 1962 | 1952 | |
| 1963 | const hdr = @as(*align(1) const load_command, @ptrCast(it.buffer.ptr)).*; | |
| 1964 | const cmd = LoadCommand{ | |
| 1965 | .hdr = hdr, | |
| 1966 | .data = it.buffer[0..hdr.cmdsize], | |
| 1953 | const hdr = it.r.peekStruct(load_command, .little) catch |err| switch (err) { | |
| 1954 | error.ReadFailed => unreachable, | |
| 1955 | error.EndOfStream => return error.InvalidMachO, | |
| 1956 | }; | |
| 1957 | const data = it.r.take(hdr.cmdsize) catch |err| switch (err) { | |
| 1958 | error.ReadFailed => unreachable, | |
| 1959 | error.EndOfStream => return error.InvalidMachO, | |
| 1967 | 1960 | }; |
| 1968 | 1961 | |
| 1969 | it.buffer = it.buffer[hdr.cmdsize..]; | |
| 1970 | it.index += 1; | |
| 1962 | it.next_index += 1; | |
| 1963 | return .{ .hdr = hdr, .data = data }; | |
| 1964 | } | |
| 1971 | 1965 | |
| 1972 | return cmd; | |
| 1966 | pub fn init(hdr: *const mach_header_64, cmds_buf_overlong: []const u8) error{InvalidMachO}!LoadCommandIterator { | |
| 1967 | if (cmds_buf_overlong.len < hdr.sizeofcmds) return error.InvalidMachO; | |
| 1968 | if (hdr.ncmds > 0 and hdr.sizeofcmds < @sizeOf(load_command)) return error.InvalidMachO; | |
| 1969 | const cmds_buf = cmds_buf_overlong[0..hdr.sizeofcmds]; | |
| 1970 | return .{ | |
| 1971 | .next_index = 0, | |
| 1972 | .ncmds = hdr.ncmds, | |
| 1973 | .r = .fixed(cmds_buf), | |
| 1974 | }; | |
| 1973 | 1975 | } |
| 1974 | 1976 | }; |
| 1975 | 1977 |
src/link/MachO.zig+2-2| ... | ... | @@ -4167,7 +4167,7 @@ pub const Platform = struct { |
| 4167 | 4167 | /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to |
| 4168 | 4168 | /// the extracted minimum platform version. |
| 4169 | 4169 | pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform { |
| 4170 | switch (lc.cmd()) { | |
| 4170 | switch (lc.hdr.cmd) { | |
| 4171 | 4171 | .BUILD_VERSION => { |
| 4172 | 4172 | const cmd = lc.cast(macho.build_version_command).?; |
| 4173 | 4173 | return .{ |
| ... | ... | @@ -4200,7 +4200,7 @@ pub const Platform = struct { |
| 4200 | 4200 | // We can't distinguish Mac Catalyst here, but this is legacy stuff anyway. |
| 4201 | 4201 | const cmd = lc.cast(macho.version_min_command).?; |
| 4202 | 4202 | return .{ |
| 4203 | .os_tag = switch (lc.cmd()) { | |
| 4203 | .os_tag = switch (lc.hdr.cmd) { | |
| 4204 | 4204 | .VERSION_MIN_IPHONEOS => .ios, |
| 4205 | 4205 | .VERSION_MIN_MACOSX => .macos, |
| 4206 | 4206 | .VERSION_MIN_TVOS => .tvos, |
src/link/MachO/Dylib.zig+2-5| ... | ... | @@ -90,11 +90,8 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void { |
| 90 | 90 | if (amt != lc_buffer.len) return error.InputOutput; |
| 91 | 91 | } |
| 92 | 92 | |
| 93 | var it = LoadCommandIterator{ | |
| 94 | .ncmds = header.ncmds, | |
| 95 | .buffer = lc_buffer, | |
| 96 | }; | |
| 97 | while (it.next()) |cmd| switch (cmd.cmd()) { | |
| 93 | var it = LoadCommandIterator.init(&header, lc_buffer) catch |err| std.debug.panic("bad dylib: {t}", .{err}); | |
| 94 | while (it.next() catch |err| std.debug.panic("bad dylib: {t}", .{err})) |cmd| switch (cmd.hdr.cmd) { | |
| 98 | 95 | .ID_DYLIB => { |
| 99 | 96 | self.id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName()); |
| 100 | 97 | }, |
src/link/MachO/Object.zig+4-10| ... | ... | @@ -109,11 +109,8 @@ pub fn parse(self: *Object, macho_file: *MachO) !void { |
| 109 | 109 | if (amt != self.header.?.sizeofcmds) return error.InputOutput; |
| 110 | 110 | } |
| 111 | 111 | |
| 112 | var it = LoadCommandIterator{ | |
| 113 | .ncmds = self.header.?.ncmds, | |
| 114 | .buffer = lc_buffer, | |
| 115 | }; | |
| 116 | while (it.next()) |lc| switch (lc.cmd()) { | |
| 112 | var it = LoadCommandIterator.init(&self.header.?, lc_buffer) catch |err| std.debug.panic("bad object: {t}", .{err}); | |
| 113 | while (it.next() catch |err| std.debug.panic("bad object: {t}", .{err})) |lc| switch (lc.hdr.cmd) { | |
| 117 | 114 | .SEGMENT_64 => { |
| 118 | 115 | const sections = lc.getSections(); |
| 119 | 116 | try self.sections.ensureUnusedCapacity(gpa, sections.len); |
| ... | ... | @@ -1644,11 +1641,8 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void { |
| 1644 | 1641 | if (amt != self.header.?.sizeofcmds) return error.InputOutput; |
| 1645 | 1642 | } |
| 1646 | 1643 | |
| 1647 | var it = LoadCommandIterator{ | |
| 1648 | .ncmds = self.header.?.ncmds, | |
| 1649 | .buffer = lc_buffer, | |
| 1650 | }; | |
| 1651 | while (it.next()) |lc| switch (lc.cmd()) { | |
| 1644 | var it = LoadCommandIterator.init(&self.header.?, lc_buffer) catch |err| std.debug.panic("bad object: {t}", .{err}); | |
| 1645 | while (it.next() catch |err| std.debug.panic("bad object: {t}", .{err})) |lc| switch (lc.hdr.cmd) { | |
| 1652 | 1646 | .SYMTAB => { |
| 1653 | 1647 | const cmd = lc.cast(macho.symtab_command).?; |
| 1654 | 1648 | try self.strtab.resize(gpa, cmd.strsize); |
tools/dump-cov.zig+27-8| ... | ... | @@ -8,31 +8,50 @@ const assert = std.debug.assert; |
| 8 | 8 | const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader; |
| 9 | 9 | |
| 10 | 10 | pub fn main() !void { |
| 11 | var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init; | |
| 12 | defer _ = general_purpose_allocator.deinit(); | |
| 13 | const gpa = general_purpose_allocator.allocator(); | |
| 11 | var debug_allocator: std.heap.DebugAllocator(.{}) = .init; | |
| 12 | defer _ = debug_allocator.deinit(); | |
| 13 | const gpa = debug_allocator.allocator(); | |
| 14 | 14 | |
| 15 | var arena_instance = std.heap.ArenaAllocator.init(gpa); | |
| 15 | var arena_instance: std.heap.ArenaAllocator = .init(gpa); | |
| 16 | 16 | defer arena_instance.deinit(); |
| 17 | 17 | const arena = arena_instance.allocator(); |
| 18 | 18 | |
| 19 | var threaded: std.Io.Threaded = .init(gpa); | |
| 20 | defer threaded.deinit(); | |
| 21 | const io = threaded.io(); | |
| 22 | ||
| 19 | 23 | const args = try std.process.argsAlloc(arena); |
| 24 | ||
| 25 | const target_query_str = switch (args.len) { | |
| 26 | 3 => "native", | |
| 27 | 4 => args[3], | |
| 28 | else => return fatal( | |
| 29 | \\usage: {0s} path/to/exe path/to/coverage [target] | |
| 30 | \\ if omitted, 'target' defaults to 'native' | |
| 31 | \\ example: {0s} zig-out/test .zig-cache/v/xxxxxxxx x86_64-linux | |
| 32 | , .{if (args.len == 0) "dump-cov" else args[0]}), | |
| 33 | }; | |
| 34 | ||
| 35 | const target = std.zig.resolveTargetQueryOrFatal(io, try .parse(.{ | |
| 36 | .arch_os_abi = target_query_str, | |
| 37 | })); | |
| 38 | ||
| 20 | 39 | const exe_file_name = args[1]; |
| 21 | 40 | const cov_file_name = args[2]; |
| 22 | 41 | |
| 23 | 42 | const exe_path: Path = .{ |
| 24 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 43 | .root_dir = .cwd(), | |
| 25 | 44 | .sub_path = exe_file_name, |
| 26 | 45 | }; |
| 27 | 46 | const cov_path: Path = .{ |
| 28 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 47 | .root_dir = .cwd(), | |
| 29 | 48 | .sub_path = cov_file_name, |
| 30 | 49 | }; |
| 31 | 50 | |
| 32 | var coverage = std.debug.Coverage.init; | |
| 51 | var coverage: std.debug.Coverage = .init; | |
| 33 | 52 | defer coverage.deinit(gpa); |
| 34 | 53 | |
| 35 | var debug_info = std.debug.Info.load(gpa, exe_path, &coverage) catch |err| { | |
| 54 | var debug_info = std.debug.Info.load(gpa, exe_path, &coverage, target.ofmt, target.cpu.arch) catch |err| { | |
| 36 | 55 | fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) }); |
| 37 | 56 | }; |
| 38 | 57 | defer debug_info.deinit(gpa); |