authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-11-20 17:48:35+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-11-20 17:48:35+00:00
log8a73fc8d8ee0065e4d07e473ce6ab095cebfaece
tree36bed2955666bdfce7b96e2f33c9409bd26f03c1
parenta9568ed2963298864e5c9a92a3eafe81771128ff
parenta87b5332319b20347916f77e57253e2df4b2a3af
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #25981 from mlugg/macos-fuzz-2

make the fuzzer vaguely work on macOS

21 files changed, 876 insertions(+), 534 deletions(-)

lib/build-web/fuzz.zig+15-14
...@@ -228,20 +228,21 @@ fn unpackSourcesInner(tar_bytes: []u8) !void {...@@ -228,20 +228,21 @@ fn unpackSourcesInner(tar_bytes: []u8) !void {
228 if (std.mem.endsWith(u8, tar_file.name, ".zig")) {228 if (std.mem.endsWith(u8, tar_file.name, ".zig")) {
229 log.debug("found file: '{s}'", .{tar_file.name});229 log.debug("found file: '{s}'", .{tar_file.name});
230 const file_name = try gpa.dupe(u8, tar_file.name);230 const file_name = try gpa.dupe(u8, tar_file.name);
231 if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| {231 // This is a hack to guess modules from the tar file contents. To handle modules
232 const pkg_name = file_name[0..pkg_name_end];232 // properly, the build system will need to change the structure here to have one
233 const gop = try Walk.modules.getOrPut(gpa, pkg_name);233 // directory per module. This in turn requires compiler enhancements to allow
234 const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len);234 // the build system to actually discover the required information.
235 if (!gop.found_existing or235 const mod_name, const is_module_root = p: {
236 std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or236 if (std.mem.find(u8, file_name, "std/")) |i| break :p .{ "std", std.mem.eql(u8, file_name[i + 4 ..], "std.zig") };
237 std.mem.eql(u8, file_name[pkg_name_end + 1 .. file_name.len - ".zig".len], pkg_name))237 if (std.mem.endsWith(u8, file_name, "/builtin.zig")) break :p .{ "builtin", true };
238 {238 break :p .{ "root", std.mem.endsWith(u8, file_name, "/root.zig") };
239 gop.value_ptr.* = file;239 };
240 }240 const gop = try Walk.modules.getOrPut(gpa, mod_name);
241 const file_bytes = tar_reader.take(@intCast(tar_file.size)) catch unreachable;241 const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len);
242 it.unread_file_bytes = 0; // we have read the whole thing242 if (!gop.found_existing or is_module_root) gop.value_ptr.* = file;
243 assert(file == try Walk.add_file(file_name, file_bytes));243 const file_bytes = tar_reader.take(@intCast(tar_file.size)) catch unreachable;
244 }244 it.unread_file_bytes = 0; // we have read the whole thing
245 assert(file == try Walk.add_file(file_name, file_bytes));
245 } else {246 } else {
246 log.warn("skipping: '{s}' - the tar creation should have done that", .{tar_file.name});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,7 +184,7 @@ fn mainServer() !void {
184 const test_fn = builtin.test_functions[index];184 const test_fn = builtin.test_functions[index];
185 const entry_addr = @intFromPtr(test_fn.func);185 const entry_addr = @intFromPtr(test_fn.func);
186186
187 try server.serveU64Message(.fuzz_start_addr, entry_addr);187 try server.serveU64Message(.fuzz_start_addr, fuzz_abi.fuzzer_unslide_address(entry_addr));
188 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);188 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
189 is_fuzz_test = false;189 is_fuzz_test = false;
190 fuzz_test_index = index;190 fuzz_test_index = index;
lib/fuzzer.zig+28-6
...@@ -116,13 +116,18 @@ const Executable = struct {...@@ -116,13 +116,18 @@ const Executable = struct {
116 "failed to init memory map for coverage file '{s}': {t}",116 "failed to init memory map for coverage file '{s}': {t}",
117 .{ &coverage_file_name, e },117 .{ &coverage_file_name, e },
118 );118 );
119 map.appendSliceAssumeCapacity(mem.asBytes(&abi.SeenPcsHeader{119 map.appendSliceAssumeCapacity(@ptrCast(&abi.SeenPcsHeader{
120 .n_runs = 0,120 .n_runs = 0,
121 .unique_runs = 0,121 .unique_runs = 0,
122 .pcs_len = pcs.len,122 .pcs_len = pcs.len,
123 }));123 }));
124 map.appendNTimesAssumeCapacity(0, pc_bitset_usizes * @sizeOf(usize));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 return map;131 return map;
127 } else {132 } else {
128 const size = coverage_file.getEndPos() catch |e| panic(133 const size = coverage_file.getEndPos() catch |e| panic(
...@@ -215,7 +220,16 @@ const Executable = struct {...@@ -215,7 +220,16 @@ const Executable = struct {
215 .{ self.pc_counters.len, pcs.len },220 .{ self.pc_counters.len, pcs.len },
216 );221 );
217222
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 self.shared_seen_pcs = getCoverageFile(cache_dir, pcs, self.pc_digest);233 self.shared_seen_pcs = getCoverageFile(cache_dir, pcs, self.pc_digest);
220234
221 return self;235 return self;
...@@ -622,6 +636,14 @@ export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {...@@ -622,6 +636,14 @@ export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {
622 }636 }
623}637}
624638
639export 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/// Helps determine run uniqueness in the face of recursion.647/// Helps determine run uniqueness in the face of recursion.
626/// Currently not used by the fuzzer.648/// Currently not used by the fuzzer.
627export threadlocal var __sancov_lowest_stack: usize = 0;649export threadlocal var __sancov_lowest_stack: usize = 0;
...@@ -1185,13 +1207,13 @@ const Mutation = enum {...@@ -1185,13 +1207,13 @@ const Mutation = enum {
1185 const j = rng.uintAtMostBiased(usize, corpus[splice_i].len - len);1207 const j = rng.uintAtMostBiased(usize, corpus[splice_i].len - len);
1186 out.appendSliceAssumeCapacity(corpus[splice_i][j..][0..len]);1208 out.appendSliceAssumeCapacity(corpus[splice_i][j..][0..len]);
1187 },1209 },
1188 .@"const" => out.appendSliceAssumeCapacity(mem.asBytes(1210 .@"const" => out.appendSliceAssumeCapacity(@ptrCast(
1189 &data_ctx[rng.uintLessThanBiased(usize, data_ctx.len)],1211 &data_ctx[rng.uintLessThanBiased(usize, data_ctx.len)],
1190 )),1212 )),
1191 .small => out.appendSliceAssumeCapacity(mem.asBytes(1213 .small => out.appendSliceAssumeCapacity(@ptrCast(
1192 &mem.nativeTo(data_ctx[0], rng.int(SmallValue), data_ctx[1]),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 &fewValue(rng, data_ctx[0], data_ctx[1]),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,7 +383,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
383 errdefer gop.value_ptr.coverage.deinit(fuzz.gpa);383 errdefer gop.value_ptr.coverage.deinit(fuzz.gpa);
384384
385 const rebuilt_exe_path = run_step.rebuilt_executable.?;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 log.err("step '{s}': failed to load debug information for '{f}': {s}", .{394 log.err("step '{s}': failed to load debug information for '{f}': {s}", .{
388 run_step.step.name, rebuilt_exe_path, @errorName(err),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,9 +486,23 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte
479 if (false) {486 if (false) {
480 const sl = coverage_map.source_locations[index];487 const sl = coverage_map.source_locations[index];
481 const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename);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}", .{489 if (pcs.len == 1) {
483 addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1],490 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 (final)", .{
484 });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 try coverage_map.entry_points.append(fuzz.gpa, @intCast(index));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,10 +729,10 @@ const MachODumper = struct {
729 imports: std.ArrayListUnmanaged([]const u8) = .empty,729 imports: std.ArrayListUnmanaged([]const u8) = .empty,
730730
731 fn parse(ctx: *ObjectContext) !void {731 fn parse(ctx: *ObjectContext) !void {
732 var it = ctx.getLoadCommandIterator();732 var it = try ctx.getLoadCommandIterator();
733 var i: usize = 0;733 var i: usize = 0;
734 while (it.next()) |cmd| {734 while (try it.next()) |cmd| {
735 switch (cmd.cmd()) {735 switch (cmd.hdr.cmd) {
736 .SEGMENT_64 => {736 .SEGMENT_64 => {
737 const seg = cmd.cast(macho.segment_command_64).?;737 const seg = cmd.cast(macho.segment_command_64).?;
738 try ctx.segments.append(ctx.gpa, seg);738 try ctx.segments.append(ctx.gpa, seg);
...@@ -771,14 +771,13 @@ const MachODumper = struct {...@@ -771,14 +771,13 @@ const MachODumper = struct {
771 return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + off)), 0);771 return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + off)), 0);
772 }772 }
773773
774 fn getLoadCommandIterator(ctx: ObjectContext) macho.LoadCommandIterator {774 fn getLoadCommandIterator(ctx: ObjectContext) !macho.LoadCommandIterator {
775 const data = ctx.data[@sizeOf(macho.mach_header_64)..][0..ctx.header.sizeofcmds];775 return .init(&ctx.header, ctx.data[@sizeOf(macho.mach_header_64)..]);
776 return .{ .ncmds = ctx.header.ncmds, .buffer = data };
777 }776 }
778777
779 fn getLoadCommand(ctx: ObjectContext, cmd: macho.LC) ?macho.LoadCommandIterator.LoadCommand {778 fn getLoadCommand(ctx: ObjectContext, cmd: macho.LC) !?macho.LoadCommandIterator.LoadCommand {
780 var it = ctx.getLoadCommandIterator();779 var it = try ctx.getLoadCommandIterator();
781 while (it.next()) |lc| if (lc.cmd() == cmd) {780 while (try it.next()) |lc| if (lc.hdr.cmd == cmd) {
782 return lc;781 return lc;
783 };782 };
784 return null;783 return null;
...@@ -872,9 +871,9 @@ const MachODumper = struct {...@@ -872,9 +871,9 @@ const MachODumper = struct {
872 \\LC {d}871 \\LC {d}
873 \\cmd {s}872 \\cmd {s}
874 \\cmdsize {d}873 \\cmdsize {d}
875 , .{ index, @tagName(lc.cmd()), lc.cmdsize() });874 , .{ index, @tagName(lc.hdr.cmd), lc.hdr.cmdsize });
876875
877 switch (lc.cmd()) {876 switch (lc.hdr.cmd) {
878 .SEGMENT_64 => {877 .SEGMENT_64 => {
879 const seg = lc.cast(macho.segment_command_64).?;878 const seg = lc.cast(macho.segment_command_64).?;
880 try writer.writeByte('\n');879 try writer.writeByte('\n');
...@@ -1592,9 +1591,9 @@ const MachODumper = struct {...@@ -1592,9 +1591,9 @@ const MachODumper = struct {
1592 .headers => {1591 .headers => {
1593 try ObjectContext.dumpHeader(ctx.header, writer);1592 try ObjectContext.dumpHeader(ctx.header, writer);
15941593
1595 var it = ctx.getLoadCommandIterator();1594 var it = try ctx.getLoadCommandIterator();
1596 var i: usize = 0;1595 var i: usize = 0;
1597 while (it.next()) |cmd| {1596 while (try it.next()) |cmd| {
1598 try ObjectContext.dumpLoadCommand(cmd, i, writer);1597 try ObjectContext.dumpLoadCommand(cmd, i, writer);
1599 try writer.writeByte('\n');1598 try writer.writeByte('\n');
16001599
...@@ -1615,7 +1614,7 @@ const MachODumper = struct {...@@ -1615,7 +1614,7 @@ const MachODumper = struct {
1615 .dyld_weak_bind,1614 .dyld_weak_bind,
1616 .dyld_lazy_bind,1615 .dyld_lazy_bind,
1617 => {1616 => {
1618 const cmd = ctx.getLoadCommand(.DYLD_INFO_ONLY) orelse1617 const cmd = try ctx.getLoadCommand(.DYLD_INFO_ONLY) orelse
1619 return step.fail("no dyld info found", .{});1618 return step.fail("no dyld info found", .{});
1620 const lc = cmd.cast(macho.dyld_info_command).?;1619 const lc = cmd.cast(macho.dyld_info_command).?;
16211620
...@@ -1649,7 +1648,7 @@ const MachODumper = struct {...@@ -1649,7 +1648,7 @@ const MachODumper = struct {
1649 },1648 },
16501649
1651 .exports => blk: {1650 .exports => blk: {
1652 if (ctx.getLoadCommand(.DYLD_INFO_ONLY)) |cmd| {1651 if (try ctx.getLoadCommand(.DYLD_INFO_ONLY)) |cmd| {
1653 const lc = cmd.cast(macho.dyld_info_command).?;1652 const lc = cmd.cast(macho.dyld_info_command).?;
1654 if (lc.export_size > 0) {1653 if (lc.export_size > 0) {
1655 const data = ctx.data[lc.export_off..][0..lc.export_size];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,6 +1932,11 @@ pub fn rebuildInFuzzMode(c: *Compile, gpa: Allocator, progress_node: std.Progres
1932 c.step.result_error_bundle.deinit(gpa);1932 c.step.result_error_bundle.deinit(gpa);
1933 c.step.result_error_bundle = std.zig.ErrorBundle.empty;1933 c.step.result_error_bundle = std.zig.ErrorBundle.empty;
19341934
1935 if (c.step.result_failed_command) |cmd| {
1936 gpa.free(cmd);
1937 c.step.result_failed_command = null;
1938 }
1939
1935 const zig_args = try getZigArgs(c, true);1940 const zig_args = try getZigArgs(c, true);
1936 const maybe_output_bin_path = try c.step.evalZigProcess(zig_args, progress_node, false, null, gpa);1941 const maybe_output_bin_path = try c.step.evalZigProcess(zig_args, progress_node, false, null, gpa);
1937 return maybe_output_bin_path.?;1942 return maybe_output_bin_path.?;
lib/std/Build/Step/Run.zig+11-2
...@@ -1140,6 +1140,12 @@ pub fn rerunInFuzzMode(...@@ -1140,6 +1140,12 @@ pub fn rerunInFuzzMode(
1140 .output_file, .output_directory => unreachable,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 const has_side_effects = false;1149 const has_side_effects = false;
1144 const rand_int = std.crypto.random.int(u64);1150 const rand_int = std.crypto.random.int(u64);
1145 const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);1151 const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
...@@ -1150,7 +1156,7 @@ pub fn rerunInFuzzMode(...@@ -1150,7 +1156,7 @@ pub fn rerunInFuzzMode(
1150 .web_server = null, // only needed for time reports1156 .web_server = null, // only needed for time reports
1151 .ttyconf = fuzz.ttyconf,1157 .ttyconf = fuzz.ttyconf,
1152 .unit_test_timeout_ns = null, // don't time out fuzz tests for now1158 .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 .unit_test_index = unit_test_index,1161 .unit_test_index = unit_test_index,
1156 .fuzz = fuzz,1162 .fuzz = fuzz,
...@@ -1870,7 +1876,10 @@ fn pollZigTest(...@@ -1870,7 +1876,10 @@ fn pollZigTest(
1870 // test. For instance, if the test runner leaves this much time between us requesting a test to1876 // test. For instance, if the test runner leaves this much time between us requesting a test to
1871 // start and it acknowledging the test starting, we terminate the child and raise an error. This1877 // start and it acknowledging the test starting, we terminate the child and raise an error. This
1872 // *should* never happen, but could in theory be caused by some very unlucky IB in a test.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 };
18741883
1875 const stdout = poller.reader(.stdout);1884 const stdout = poller.reader(.stdout);
1876 const stderr = poller.reader(.stderr);1885 const stderr = poller.reader(.stderr);
lib/std/Build/abi.zig+1
...@@ -145,6 +145,7 @@ pub const fuzz = struct {...@@ -145,6 +145,7 @@ pub const fuzz = struct {
145 pub extern fn fuzzer_init_test(test_one: TestOne, unit_test_name: Slice) void;145 pub extern fn fuzzer_init_test(test_one: TestOne, unit_test_name: Slice) void;
146 pub extern fn fuzzer_new_input(bytes: Slice) void;146 pub extern fn fuzzer_new_input(bytes: Slice) void;
147 pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void;147 pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void;
148 pub extern fn fuzzer_unslide_address(addr: usize) usize;
148149
149 pub const Slice = extern struct {150 pub const Slice = extern struct {
150 ptr: [*]const u8,151 ptr: [*]const u8,
lib/std/Io/Writer.zig+16-6
...@@ -270,16 +270,17 @@ fn writeSplatHeaderLimitFinish(...@@ -270,16 +270,17 @@ fn writeSplatHeaderLimitFinish(
270 remaining -= copy_len;270 remaining -= copy_len;
271 if (remaining == 0) break :v;271 if (remaining == 0) break :v;
272 }272 }
273 for (data[0 .. data.len - 1]) |buf| if (buf.len != 0) {273 for (data[0 .. data.len - 1]) |buf| {
274 const copy_len = @min(header.len, remaining);274 if (buf.len == 0) continue;
275 vecs[i] = buf;275 const copy_len = @min(buf.len, remaining);
276 vecs[i] = buf[0..copy_len];
276 i += 1;277 i += 1;
277 remaining -= copy_len;278 remaining -= copy_len;
278 if (remaining == 0) break :v;279 if (remaining == 0) break :v;
279 if (vecs.len - i == 0) break :v;280 if (vecs.len - i == 0) break :v;
280 };281 }
281 const pattern = data[data.len - 1];282 const pattern = data[data.len - 1];
282 if (splat == 1) {283 if (splat == 1 or remaining < pattern.len) {
283 vecs[i] = pattern[0..@min(remaining, pattern.len)];284 vecs[i] = pattern[0..@min(remaining, pattern.len)];
284 i += 1;285 i += 1;
285 break :v;286 break :v;
...@@ -915,7 +916,16 @@ pub fn sendFileHeader(...@@ -915,7 +916,16 @@ pub fn sendFileHeader(
915 if (new_end <= w.buffer.len) {916 if (new_end <= w.buffer.len) {
916 @memcpy(w.buffer[w.end..][0..header.len], header);917 @memcpy(w.buffer[w.end..][0..header.len], header);
917 w.end = new_end;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 const buffered_contents = limit.slice(file_reader.interface.buffered());930 const buffered_contents = limit.slice(file_reader.interface.buffered());
921 const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);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,6 +21,7 @@ const root = @import("root");
21pub const Dwarf = @import("debug/Dwarf.zig");21pub const Dwarf = @import("debug/Dwarf.zig");
22pub const Pdb = @import("debug/Pdb.zig");22pub const Pdb = @import("debug/Pdb.zig");
23pub const ElfFile = @import("debug/ElfFile.zig");23pub const ElfFile = @import("debug/ElfFile.zig");
24pub const MachOFile = @import("debug/MachOFile.zig");
24pub const Info = @import("debug/Info.zig");25pub const Info = @import("debug/Info.zig");
25pub const Coverage = @import("debug/Coverage.zig");26pub const Coverage = @import("debug/Coverage.zig");
26pub const cpu_context = @import("debug/cpu_context.zig");27pub const cpu_context = @import("debug/cpu_context.zig");
...@@ -1366,7 +1367,7 @@ test printLineFromFile {...@@ -1366,7 +1367,7 @@ test printLineFromFile {
13661367
1367/// The returned allocator should be thread-safe if the compilation is multi-threaded, because1368/// The returned allocator should be thread-safe if the compilation is multi-threaded, because
1368/// multiple threads could capture and/or print stack traces simultaneously.1369/// multiple threads could capture and/or print stack traces simultaneously.
1369fn getDebugInfoAllocator() Allocator {1370pub fn getDebugInfoAllocator() Allocator {
1370 // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`.1371 // Allow overriding the debug info allocator by exposing `root.debug.getDebugInfoAllocator`.
1371 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) {1372 if (@hasDecl(root, "debug") and @hasDecl(root.debug, "getDebugInfoAllocator")) {
1372 return root.debug.getDebugInfoAllocator();1373 return root.debug.getDebugInfoAllocator();
lib/std/debug/Info.zig+65-26
...@@ -9,49 +9,67 @@...@@ -9,49 +9,67 @@
9const std = @import("../std.zig");9const std = @import("../std.zig");
10const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
11const Path = std.Build.Cache.Path;11const Path = std.Build.Cache.Path;
12const ElfFile = std.debug.ElfFile;
13const assert = std.debug.assert;12const assert = std.debug.assert;
14const Coverage = std.debug.Coverage;13const Coverage = std.debug.Coverage;
15const SourceLocation = std.debug.Coverage.SourceLocation;14const SourceLocation = std.debug.Coverage.SourceLocation;
1615
16const ElfFile = std.debug.ElfFile;
17const MachOFile = std.debug.MachOFile;
18
17const Info = @This();19const Info = @This();
1820
19/// Sorted by key, ascending.21impl: union(enum) {
20address_map: std.AutoArrayHashMapUnmanaged(u64, ElfFile),22 elf: ElfFile,
23 macho: MachOFile,
24},
21/// Externally managed, outlives this `Info` instance.25/// Externally managed, outlives this `Info` instance.
22coverage: *Coverage,26coverage: *Coverage,
2327
24pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || std.debug.Dwarf.ScanError || error{MissingDebugInfo};28pub const LoadError = std.fs.File.OpenError || ElfFile.LoadError || MachOFile.Error || std.debug.Dwarf.ScanError || error{ MissingDebugInfo, UnsupportedDebugInfo };
29
30pub 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();
2535
26pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info {36 var elf_file: ElfFile = try .load(gpa, file, null, &.none);
27 var file = try path.root_dir.handle.openFile(path.sub_path, .{});37 errdefer elf_file.deinit(gpa);
28 defer file.close();
2938
30 var elf_file: ElfFile = try .load(gpa, file, null, &.none);39 if (elf_file.dwarf == null) return error.MissingDebugInfo;
31 errdefer elf_file.deinit(gpa);40 try elf_file.dwarf.?.open(gpa, elf_file.endian);
41 try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian);
3242
33 if (elf_file.dwarf == null) return error.MissingDebugInfo;43 return .{
34 try elf_file.dwarf.?.open(gpa, elf_file.endian);44 .impl = .{ .elf = elf_file },
35 try elf_file.dwarf.?.populateRanges(gpa, elf_file.endian);45 .coverage = coverage,
46 };
47 },
48 .macho => {
49 const path_str = try path.toString(gpa);
50 defer gpa.free(path_str);
3651
37 var info: Info = .{52 var macho_file: MachOFile = try .load(gpa, path_str, arch);
38 .address_map = .{},53 errdefer macho_file.deinit(gpa);
39 .coverage = coverage,54
40 };55 return .{
41 try info.address_map.put(gpa, 0, elf_file);56 .impl = .{ .macho = macho_file },
42 errdefer comptime unreachable; // elf_file is owned by the map now57 .coverage = coverage,
43 return info;58 };
59 },
60 else => return error.UnsupportedDebugInfo,
61 }
44}62}
4563
46pub fn deinit(info: *Info, gpa: Allocator) void {64pub fn deinit(info: *Info, gpa: Allocator) void {
47 for (info.address_map.values()) |*elf_file| {65 switch (info.impl) {
48 elf_file.dwarf.?.deinit(gpa);66 .elf => |*ef| ef.deinit(gpa),
67 .macho => |*mf| mf.deinit(gpa),
49 }68 }
50 info.address_map.deinit(gpa);
51 info.* = undefined;69 info.* = undefined;
52}70}
5371
54pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError;72pub const ResolveAddressesError = Coverage.ResolveAddressesDwarfError || error{UnsupportedDebugInfo};
5573
56/// Given an array of virtual memory addresses, sorted ascending, outputs a74/// Given an array of virtual memory addresses, sorted ascending, outputs a
57/// corresponding array of source locations.75/// corresponding array of source locations.
...@@ -64,7 +82,28 @@ pub fn resolveAddresses(...@@ -64,7 +82,28 @@ pub fn resolveAddresses(
64 output: []SourceLocation,82 output: []SourceLocation,
65) ResolveAddressesError!void {83) ResolveAddressesError!void {
66 assert(sorted_pc_addrs.len == output.len);84 assert(sorted_pc_addrs.len == output.len);
67 if (info.address_map.entries.len != 1) @panic("TODO");85 switch (info.impl) {
68 const elf_file = &info.address_map.values()[0];86 .elf => |*ef| return info.coverage.resolveAddressesDwarf(gpa, ef.endian, sorted_pc_addrs, output, &ef.dwarf.?),
69 return info.coverage.resolveAddressesDwarf(gpa, elf_file.endian, sorted_pc_addrs, output, &elf_file.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 @@
1mapped_memory: []align(std.heap.page_size_min) const u8,
2symbols: []const Symbol,
3strings: []const u8,
4text_vmaddr: u64,
5
6/// Key is index into `strings` of the file path.
7ofiles: std.AutoArrayHashMapUnmanaged(u32, Error!OFile),
8
9pub const Error = error{
10 InvalidMachO,
11 InvalidDwarf,
12 MissingDebugInfo,
13 UnsupportedDebugInfo,
14 ReadFailed,
15 OutOfMemory,
16};
17
18pub 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
30pub 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}
242pub 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}
274pub 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
279const 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
305const 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};
355test {
356 _ = Symbol;
357}
358
359fn 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.
514fn 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
536const std = @import("std");
537const Allocator = std.mem.Allocator;
538const Dwarf = std.debug.Dwarf;
539const Io = std.Io;
540const assert = std.debug.assert;
541const posix = std.posix;
542const macho = std.macho;
543const mem = std.mem;
544const testing = std.testing;
545
546const builtin = @import("builtin");
547
548const 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,6 +80,11 @@ pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]cons
80 if (module.name.len == 0) return error.MissingDebugInfo;80 if (module.name.len == 0) return error.MissingDebugInfo;
81 return module.name;81 return module.name;
82}82}
83pub 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}
8388
84pub const can_unwind: bool = s: {89pub const can_unwind: bool = s: {
85 // The DWARF code can't deal with ILP32 ABIs yet: https://github.com/ziglang/zig/issues/2544790 // 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,12 +1,10 @@
1mutex: std.Thread.Mutex,1mutex: std.Thread.Mutex,
2/// Accessed through `Module.Adapter`.2/// Accessed through `Module.Adapter`.
3modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false),3modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false),
4ofiles: std.StringArrayHashMapUnmanaged(?OFile),
54
6pub const init: SelfInfo = .{5pub const init: SelfInfo = .{
7 .mutex = .{},6 .mutex = .{},
8 .modules = .empty,7 .modules = .empty,
9 .ofiles = .empty,
10};8};
11pub fn deinit(si: *SelfInfo, gpa: Allocator) void {9pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
12 for (si.modules.keys()) |*module| {10 for (si.modules.keys()) |*module| {
...@@ -14,20 +12,12 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {...@@ -14,20 +12,12 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void {
14 const u = &(module.unwind orelse break :unwind catch break :unwind);12 const u = &(module.unwind orelse break :unwind catch break :unwind);
15 if (u.dwarf) |*dwarf| dwarf.deinit(gpa);13 if (u.dwarf) |*dwarf| dwarf.deinit(gpa);
16 }14 }
17 loaded: {15 file: {
18 const l = &(module.loaded_macho orelse break :loaded catch break :loaded);16 const f = &(module.file orelse break :file catch break :file);
19 gpa.free(l.symbols);17 f.deinit(gpa);
20 posix.munmap(l.mapped_memory);
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 si.modules.deinit(gpa);20 si.modules.deinit(gpa);
30 si.ofiles.deinit(gpa);
31}21}
3222
33pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol {23pub 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,67 +25,55 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st
35 const module = try si.findModule(gpa, address);25 const module = try si.findModule(gpa, address);
36 defer si.mutex.unlock();26 defer si.mutex.unlock();
3727
38 const loaded_macho = try module.getLoadedMachO(gpa);28 const file = try module.getFile(gpa);
39
40 const vaddr = address - loaded_macho.vaddr_offset;
41 const symbol = MachoSymbol.find(loaded_macho.symbols, vaddr) orelse return .unknown;
4229
43 // offset of `address` from start of `symbol`30 // This is not necessarily the same as the vmaddr_slide that dyld would report. This is
44 const address_symbol_offset = vaddr - symbol.addr;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;
4539
46 // Take the symbol name from the N_FUN STAB entry, we're going to40 const vaddr = address - vaddr_offset;
47 // use it if we fail to find the DWARF infos
48 const stab_symbol = mem.sliceTo(loaded_macho.strings[symbol.strx..], 0);
4941
50 // If any information is missing, we can at least return this from now on.42 const ofile_dwarf, const ofile_vaddr = file.getDwarfForAddress(gpa, vaddr) catch {
51 const sym_only_result: std.debug.Symbol = .{43 // Return at least the symbol name if available.
52 .name = stab_symbol,44 return .{
53 .compile_unit_name = null,45 .name = try file.lookupSymbolName(vaddr),
54 .source_location = null,46 .compile_unit_name = null,
47 .source_location = null,
48 };
55 };49 };
5650
57 if (symbol.ofile == MachoSymbol.unknown_ofile) {51 const compile_unit = ofile_dwarf.findCompileUnit(native_endian, ofile_vaddr) catch {
58 // We don't have STAB info, so can't track down the object file; all we can do is the symbol name.52 // Return at least the symbol name if available.
59 return sym_only_result;53 return .{
60 }54 .name = try file.lookupSymbolName(vaddr),
6155 .compile_unit_name = null,
62 const o_file: *OFile = of: {56 .source_location = null,
63 const path = mem.sliceTo(loaded_macho.strings[symbol.ofile..], 0);57 };
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 }
73 };58 };
7459
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 return .{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 .compile_unit_name = compile_unit.die.getAttrString(63 .compile_unit_name = compile_unit.die.getAttrString(
86 &o_file.dwarf,64 ofile_dwarf,
87 native_endian,65 native_endian,
88 std.dwarf.AT.name,66 std.dwarf.AT.name,
89 o_file.dwarf.section(.debug_str),67 ofile_dwarf.section(.debug_str),
90 compile_unit,68 compile_unit,
91 ) catch |err| switch (err) {69 ) catch |err| switch (err) {
92 error.MissingDebugInfo, error.InvalidDebugInfo => null,70 error.MissingDebugInfo, error.InvalidDebugInfo => null,
93 },71 },
94 .source_location = o_file.dwarf.getLineNumberInfo(72 .source_location = ofile_dwarf.getLineNumberInfo(
95 gpa,73 gpa,
96 native_endian,74 native_endian,
97 compile_unit,75 compile_unit,
98 symbol_ofile_vaddr + address_symbol_offset,76 ofile_vaddr,
99 ) catch null,77 ) catch null,
100 };78 };
101}79}
...@@ -104,6 +82,20 @@ pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]cons...@@ -104,6 +82,20 @@ pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]cons
104 defer si.mutex.unlock();82 defer si.mutex.unlock();
105 return module.name;83 return module.name;
106}84}
85pub 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}
10799
108pub const can_unwind: bool = true;100pub const can_unwind: bool = true;
109pub const UnwindContext = std.debug.Dwarf.SelfUnwinder;101pub const UnwindContext = std.debug.Dwarf.SelfUnwinder;
...@@ -447,7 +439,7 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module {...@@ -447,7 +439,7 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module {
447 .text_base = @intFromPtr(info.fbase),439 .text_base = @intFromPtr(info.fbase),
448 .name = std.mem.span(info.fname),440 .name = std.mem.span(info.fname),
449 .unwind = null,441 .unwind = null,
450 .loaded_macho = null,442 .file = null,
451 };443 };
452 }444 }
453 return gop.key_ptr;445 return gop.key_ptr;
...@@ -457,7 +449,7 @@ const Module = struct {...@@ -457,7 +449,7 @@ const Module = struct {
457 text_base: usize,449 text_base: usize,
458 name: []const u8,450 name: []const u8,
459 unwind: ?(Error!Unwind),451 unwind: ?(Error!Unwind),
460 loaded_macho: ?(Error!LoadedMachO),452 file: ?(Error!MachOFile),
461453
462 const Adapter = struct {454 const Adapter = struct {
463 pub fn hash(_: Adapter, text_base: usize) u32 {455 pub fn hash(_: Adapter, text_base: usize) u32 {
...@@ -488,34 +480,17 @@ const Module = struct {...@@ -488,34 +480,17 @@ const Module = struct {
488 dwarf: ?Dwarf.Unwind,480 dwarf: ?Dwarf.Unwind,
489 };481 };
490482
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 fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind {483 fn getUnwindInfo(module: *Module, gpa: Allocator) Error!*Unwind {
507 if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa);484 if (module.unwind == null) module.unwind = loadUnwindInfo(module, gpa);
508 return if (module.unwind.?) |*unwind| unwind else |err| err;485 return if (module.unwind.?) |*unwind| unwind else |err| err;
509 }486 }
510 fn loadUnwindInfo(module: *const Module, gpa: Allocator) Error!Unwind {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);
512489
513 var it: macho.LoadCommandIterator = .{490 const raw_macho: [*]u8 = @ptrCast(header);
514 .ncmds = header.ncmds,491 var it = macho.LoadCommandIterator.init(header, raw_macho[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds]) catch unreachable;
515 .buffer = @as([*]u8, @ptrCast(header))[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds],492 const sections, const text_vmaddr = while (it.next() catch unreachable) |load_cmd| {
516 };493 if (load_cmd.hdr.cmd != .SEGMENT_64) continue;
517 const sections, const text_vmaddr = while (it.next()) |load_cmd| {
518 if (load_cmd.cmd() != .SEGMENT_64) continue;
519 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;494 const segment_cmd = load_cmd.cast(macho.segment_command_64).?;
520 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;495 if (!mem.eql(u8, segment_cmd.segName(), "__TEXT")) continue;
521 break .{ load_cmd.getSections(), segment_cmd.vmaddr };496 break .{ load_cmd.getSections(), segment_cmd.vmaddr };
...@@ -568,237 +543,15 @@ const Module = struct {...@@ -568,237 +543,15 @@ const Module = struct {
568 };543 };
569 }544 }
570545
571 fn getLoadedMachO(module: *Module, gpa: Allocator) Error!*LoadedMachO {546 fn getFile(module: *Module, gpa: Allocator) Error!*MachOFile {
572 if (module.loaded_macho == null) module.loaded_macho = loadMachO(module, gpa) catch |err| switch (err) {547 if (module.file == null) module.file = MachOFile.load(gpa, module.name, builtin.cpu.arch) catch |err| switch (err) {
573 error.InvalidDebugInfo, error.MissingDebugInfo, error.OutOfMemory, error.Unexpected => |e| e,548 error.InvalidMachO, error.InvalidDwarf => error.InvalidDebugInfo,
574 else => error.ReadFailed,549 error.MissingDebugInfo, error.OutOfMemory, error.UnsupportedDebugInfo, error.ReadFailed => |e| e,
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,
773 };550 };
551 return if (module.file.?) |*f| f else |err| err;
774 }552 }
775};553};
776554
777const 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
802const MachoSymbol = struct {555const MachoSymbol = struct {
803 strx: u32,556 strx: u32,
804 addr: u64,557 addr: u64,
...@@ -880,101 +633,12 @@ fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8...@@ -880,101 +633,12 @@ fn mapDebugInfoFile(path: []const u8) ![]align(std.heap.page_size_min) const u8
880 };633 };
881}634}
882635
883fn 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
973const std = @import("std");636const std = @import("std");
974const Io = std.Io;637const Io = std.Io;
975const Allocator = std.mem.Allocator;638const Allocator = std.mem.Allocator;
976const Dwarf = std.debug.Dwarf;639const Dwarf = std.debug.Dwarf;
977const Error = std.debug.SelfInfoError;640const Error = std.debug.SelfInfoError;
641const MachOFile = std.debug.MachOFile;
978const assert = std.debug.assert;642const assert = std.debug.assert;
979const posix = std.posix;643const posix = std.posix;
980const macho = std.macho;644const 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,6 +33,12 @@ pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]cons
33 const module = try si.findModule(gpa, address);33 const module = try si.findModule(gpa, address);
34 return module.name;34 return module.name;
35}35}
36pub 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}
3642
37pub const can_unwind: bool = switch (builtin.cpu.arch) {43pub const can_unwind: bool = switch (builtin.cpu.arch) {
38 else => true,44 else => true,
lib/std/http.zig+3-4
...@@ -962,6 +962,7 @@ pub const BodyWriter = struct {...@@ -962,6 +962,7 @@ pub const BodyWriter = struct {
962 // have to flush the chunk header before knowing the chunk length.962 // have to flush the chunk header before knowing the chunk length.
963 return error.Unimplemented;963 return error.Unimplemented;
964 };964 };
965 if (data_len == 0) return error.EndOfStream;
965 const out = bw.http_protocol_output;966 const out = bw.http_protocol_output;
966 l: switch (bw.state.chunk_len) {967 l: switch (bw.state.chunk_len) {
967 0 => {968 0 => {
...@@ -975,8 +976,7 @@ pub const BodyWriter = struct {...@@ -975,8 +976,7 @@ pub const BodyWriter = struct {
975 2 => {976 2 => {
976 try out.writeAll("\r\n");977 try out.writeAll("\r\n");
977 bw.state.chunk_len = 0;978 bw.state.chunk_len = 0;
978 assert(file_reader.atEnd());979 continue :l 0;
979 return error.EndOfStream;
980 },980 },
981 else => {981 else => {
982 const chunk_limit: std.Io.Limit = .limited(bw.state.chunk_len - 2);982 const chunk_limit: std.Io.Limit = .limited(bw.state.chunk_len - 2);
...@@ -985,8 +985,7 @@ pub const BodyWriter = struct {...@@ -985,8 +985,7 @@ pub const BodyWriter = struct {
985 else985 else
986 try out.write(chunk_limit.slice(w.buffered()));986 try out.write(chunk_limit.slice(w.buffered()));
987 bw.state.chunk_len -= n;987 bw.state.chunk_len -= n;
988 const ret = w.consume(n);988 return w.consume(n);
989 return ret;
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,74 +1902,76 @@ pub const data_in_code_entry = extern struct {
1902};1902};
19031903
1904pub const LoadCommandIterator = struct {1904pub const LoadCommandIterator = struct {
1905 next_index: usize,
1905 ncmds: usize,1906 ncmds: usize,
1906 buffer: []const u8,1907 r: std.Io.Reader,
1907 index: usize = 0,
19081908
1909 pub const LoadCommand = struct {1909 pub const LoadCommand = struct {
1910 hdr: load_command,1910 hdr: load_command,
1911 data: []const u8,1911 data: []const u8,
19121912
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 pub fn cast(lc: LoadCommand, comptime Cmd: type) ?Cmd {1913 pub fn cast(lc: LoadCommand, comptime Cmd: type) ?Cmd {
1922 if (lc.data.len < @sizeOf(Cmd)) return null;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 }
19251920
1926 /// Asserts LoadCommand is of type segment_command_64.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 pub fn getSections(lc: LoadCommand) []align(1) const section_64 {1923 pub fn getSections(lc: LoadCommand) []align(1) const section_64 {
1928 const segment_lc = lc.cast(segment_command_64).?;1924 const segment_lc = lc.cast(segment_command_64).?;
1929 if (segment_lc.nsects == 0) return &[0]section_64{};1925 const sects_ptr: [*]align(1) const section_64 = @ptrCast(lc.data[@sizeOf(segment_command_64)..]);
1930 const data = lc.data[@sizeOf(segment_command_64)..];1926 return sects_ptr[0..segment_lc.nsects];
1931 const sections = @as([*]align(1) const section_64, @ptrCast(data.ptr))[0..segment_lc.nsects];
1932 return sections;
1933 }1927 }
19341928
1935 /// Asserts LoadCommand is of type dylib_command.1929 /// Asserts LoadCommand is of type dylib_command.
1936 pub fn getDylibPathName(lc: LoadCommand) []const u8 {1930 pub fn getDylibPathName(lc: LoadCommand) []const u8 {
1937 const dylib_lc = lc.cast(dylib_command).?;1931 const dylib_lc = lc.cast(dylib_command).?;
1938 const data = lc.data[dylib_lc.dylib.name..];1932 return mem.sliceTo(lc.data[dylib_lc.dylib.name..], 0);
1939 return mem.sliceTo(data, 0);
1940 }1933 }
19411934
1942 /// Asserts LoadCommand is of type rpath_command.1935 /// Asserts LoadCommand is of type rpath_command.
1943 pub fn getRpathPathName(lc: LoadCommand) []const u8 {1936 pub fn getRpathPathName(lc: LoadCommand) []const u8 {
1944 const rpath_lc = lc.cast(rpath_command).?;1937 const rpath_lc = lc.cast(rpath_command).?;
1945 const data = lc.data[rpath_lc.path..];1938 return mem.sliceTo(lc.data[rpath_lc.path..], 0);
1946 return mem.sliceTo(data, 0);
1947 }1939 }
19481940
1949 /// Asserts LoadCommand is of type build_version_command.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 pub fn getBuildVersionTools(lc: LoadCommand) []align(1) const build_tool_version {1943 pub fn getBuildVersionTools(lc: LoadCommand) []align(1) const build_tool_version {
1951 const build_lc = lc.cast(build_version_command).?;1944 const build_lc = lc.cast(build_version_command).?;
1952 const ntools = build_lc.ntools;1945 const tools_ptr: [*]align(1) const build_tool_version = @ptrCast(lc.data[@sizeOf(build_version_command)..]);
1953 if (ntools == 0) return &[0]build_tool_version{};1946 return tools_ptr[0..build_lc.ntools];
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;
1957 }1947 }
1958 };1948 };
19591949
1960 pub fn next(it: *LoadCommandIterator) ?LoadCommand {1950 pub fn next(it: *LoadCommandIterator) error{InvalidMachO}!?LoadCommand {
1961 if (it.index >= it.ncmds) return null;1951 if (it.next_index >= it.ncmds) return null;
19621952
1963 const hdr = @as(*align(1) const load_command, @ptrCast(it.buffer.ptr)).*;1953 const hdr = it.r.peekStruct(load_command, .little) catch |err| switch (err) {
1964 const cmd = LoadCommand{1954 error.ReadFailed => unreachable,
1965 .hdr = hdr,1955 error.EndOfStream => return error.InvalidMachO,
1966 .data = it.buffer[0..hdr.cmdsize],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 };
19681961
1969 it.buffer = it.buffer[hdr.cmdsize..];1962 it.next_index += 1;
1970 it.index += 1;1963 return .{ .hdr = hdr, .data = data };
1964 }
19711965
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};
19751977
src/link/MachO.zig+2-2
...@@ -4167,7 +4167,7 @@ pub const Platform = struct {...@@ -4167,7 +4167,7 @@ pub const Platform = struct {
4167 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to4167 /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to
4168 /// the extracted minimum platform version.4168 /// the extracted minimum platform version.
4169 pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {4169 pub fn fromLoadCommand(lc: macho.LoadCommandIterator.LoadCommand) Platform {
4170 switch (lc.cmd()) {4170 switch (lc.hdr.cmd) {
4171 .BUILD_VERSION => {4171 .BUILD_VERSION => {
4172 const cmd = lc.cast(macho.build_version_command).?;4172 const cmd = lc.cast(macho.build_version_command).?;
4173 return .{4173 return .{
...@@ -4200,7 +4200,7 @@ pub const Platform = struct {...@@ -4200,7 +4200,7 @@ pub const Platform = struct {
4200 // We can't distinguish Mac Catalyst here, but this is legacy stuff anyway.4200 // We can't distinguish Mac Catalyst here, but this is legacy stuff anyway.
4201 const cmd = lc.cast(macho.version_min_command).?;4201 const cmd = lc.cast(macho.version_min_command).?;
4202 return .{4202 return .{
4203 .os_tag = switch (lc.cmd()) {4203 .os_tag = switch (lc.hdr.cmd) {
4204 .VERSION_MIN_IPHONEOS => .ios,4204 .VERSION_MIN_IPHONEOS => .ios,
4205 .VERSION_MIN_MACOSX => .macos,4205 .VERSION_MIN_MACOSX => .macos,
4206 .VERSION_MIN_TVOS => .tvos,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,11 +90,8 @@ fn parseBinary(self: *Dylib, macho_file: *MachO) !void {
90 if (amt != lc_buffer.len) return error.InputOutput;90 if (amt != lc_buffer.len) return error.InputOutput;
91 }91 }
9292
93 var it = LoadCommandIterator{93 var it = LoadCommandIterator.init(&header, lc_buffer) catch |err| std.debug.panic("bad dylib: {t}", .{err});
94 .ncmds = header.ncmds,94 while (it.next() catch |err| std.debug.panic("bad dylib: {t}", .{err})) |cmd| switch (cmd.hdr.cmd) {
95 .buffer = lc_buffer,
96 };
97 while (it.next()) |cmd| switch (cmd.cmd()) {
98 .ID_DYLIB => {95 .ID_DYLIB => {
99 self.id = try Id.fromLoadCommand(gpa, cmd.cast(macho.dylib_command).?, cmd.getDylibPathName());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,11 +109,8 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
109 if (amt != self.header.?.sizeofcmds) return error.InputOutput;109 if (amt != self.header.?.sizeofcmds) return error.InputOutput;
110 }110 }
111111
112 var it = LoadCommandIterator{112 var it = LoadCommandIterator.init(&self.header.?, lc_buffer) catch |err| std.debug.panic("bad object: {t}", .{err});
113 .ncmds = self.header.?.ncmds,113 while (it.next() catch |err| std.debug.panic("bad object: {t}", .{err})) |lc| switch (lc.hdr.cmd) {
114 .buffer = lc_buffer,
115 };
116 while (it.next()) |lc| switch (lc.cmd()) {
117 .SEGMENT_64 => {114 .SEGMENT_64 => {
118 const sections = lc.getSections();115 const sections = lc.getSections();
119 try self.sections.ensureUnusedCapacity(gpa, sections.len);116 try self.sections.ensureUnusedCapacity(gpa, sections.len);
...@@ -1644,11 +1641,8 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {...@@ -1644,11 +1641,8 @@ pub fn parseAr(self: *Object, macho_file: *MachO) !void {
1644 if (amt != self.header.?.sizeofcmds) return error.InputOutput;1641 if (amt != self.header.?.sizeofcmds) return error.InputOutput;
1645 }1642 }
16461643
1647 var it = LoadCommandIterator{1644 var it = LoadCommandIterator.init(&self.header.?, lc_buffer) catch |err| std.debug.panic("bad object: {t}", .{err});
1648 .ncmds = self.header.?.ncmds,1645 while (it.next() catch |err| std.debug.panic("bad object: {t}", .{err})) |lc| switch (lc.hdr.cmd) {
1649 .buffer = lc_buffer,
1650 };
1651 while (it.next()) |lc| switch (lc.cmd()) {
1652 .SYMTAB => {1646 .SYMTAB => {
1653 const cmd = lc.cast(macho.symtab_command).?;1647 const cmd = lc.cast(macho.symtab_command).?;
1654 try self.strtab.resize(gpa, cmd.strsize);1648 try self.strtab.resize(gpa, cmd.strsize);
tools/dump-cov.zig+27-8
...@@ -8,31 +8,50 @@ const assert = std.debug.assert;...@@ -8,31 +8,50 @@ const assert = std.debug.assert;
8const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader;8const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader;
99
10pub fn main() !void {10pub fn main() !void {
11 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;11 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
12 defer _ = general_purpose_allocator.deinit();12 defer _ = debug_allocator.deinit();
13 const gpa = general_purpose_allocator.allocator();13 const gpa = debug_allocator.allocator();
1414
15 var arena_instance = std.heap.ArenaAllocator.init(gpa);15 var arena_instance: std.heap.ArenaAllocator = .init(gpa);
16 defer arena_instance.deinit();16 defer arena_instance.deinit();
17 const arena = arena_instance.allocator();17 const arena = arena_instance.allocator();
1818
19 var threaded: std.Io.Threaded = .init(gpa);
20 defer threaded.deinit();
21 const io = threaded.io();
22
19 const args = try std.process.argsAlloc(arena);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 const exe_file_name = args[1];39 const exe_file_name = args[1];
21 const cov_file_name = args[2];40 const cov_file_name = args[2];
2241
23 const exe_path: Path = .{42 const exe_path: Path = .{
24 .root_dir = std.Build.Cache.Directory.cwd(),43 .root_dir = .cwd(),
25 .sub_path = exe_file_name,44 .sub_path = exe_file_name,
26 };45 };
27 const cov_path: Path = .{46 const cov_path: Path = .{
28 .root_dir = std.Build.Cache.Directory.cwd(),47 .root_dir = .cwd(),
29 .sub_path = cov_file_name,48 .sub_path = cov_file_name,
30 };49 };
3150
32 var coverage = std.debug.Coverage.init;51 var coverage: std.debug.Coverage = .init;
33 defer coverage.deinit(gpa);52 defer coverage.deinit(gpa);
3453
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 fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) });55 fatal("failed to load debug info for {f}: {s}", .{ exe_path, @errorName(err) });
37 };56 };
38 defer debug_info.deinit(gpa);57 defer debug_info.deinit(gpa);