From 7a50e76eb14df322033f3b96547cd62a5691df6f Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Fri, 28 Feb 2025 10:34:06 +0100 Subject: [PATCH] wip this is a bad commit that contains work in progress changes to make fuzzing on macos work. more specifically it contains macho-specific code for loading debug information in the webserver. it's messy because I'm still trying to understand how this stuff works. with these changes the web server loads but the wasm code panics. it's unclear if the wasm panic is due to my dwarf loading code being wrong or if we're encountering some other latent issue. --- lib/std/Build/Fuzz/WebServer.zig | 2 + lib/std/debug/Info.zig | 72 ++++++++++++++++++++++++++++---- lib/std/debug/SelfInfo.zig | 34 +++++++++------ 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/lib/std/Build/Fuzz/WebServer.zig b/lib/std/Build/Fuzz/WebServer.zig index 87cd7a1a1d..a30b67eeae 100644 --- a/lib/std/Build/Fuzz/WebServer.zig +++ b/lib/std/Build/Fuzz/WebServer.zig @@ -582,6 +582,7 @@ fn prepareTables( ws.coverage_mutex.lock(); defer ws.coverage_mutex.unlock(); + std.debug.print("SET {}\n", .{coverage_id}); const gop = try ws.coverage_files.getOrPut(gpa, coverage_id); if (gop.found_existing) { // We are fuzzing the same executable with multiple threads. @@ -676,6 +677,7 @@ fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyRepo ws.coverage_mutex.lock(); defer ws.coverage_mutex.unlock(); + std.debug.print("GET {}\n", .{coverage_id}); const coverage_map = ws.coverage_files.getPtr(coverage_id).?; const header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); const pcs = header.pcAddrs(); diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index c809547f73..550ae27e96 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -7,6 +7,7 @@ //! properties. const std = @import("../std.zig"); +const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const Path = std.Build.Cache.Path; const Dwarf = std.debug.Dwarf; @@ -17,7 +18,7 @@ const SourceLocation = std.debug.Coverage.SourceLocation; const Info = @This(); /// Sorted by key, ascending. -address_map: std.AutoArrayHashMapUnmanaged(u64, Dwarf.ElfModule), +address_map: std.AutoArrayHashMapUnmanaged(u64, std.debug.SelfInfo.Module), /// Externally managed, outlives this `Info` instance. coverage: *Coverage, @@ -25,20 +26,41 @@ pub const LoadError = Dwarf.ElfModule.LoadError; pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info { var sections: Dwarf.SectionArray = Dwarf.null_section_array; - var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, §ions, null); - try elf_module.dwarf.populateRanges(gpa); var info: Info = .{ .address_map = .{}, .coverage = coverage, }; - try info.address_map.put(gpa, elf_module.base_address, elf_module); + switch (builtin.os.tag) { + .linux => { + var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, §ions, null); + try elf_module.dwarf.populateRanges(gpa); + try info.address_map.put(gpa, elf_module.base_address, elf_module); + }, + .macos => { + const macho_file = path.root_dir.handle.openFile(path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => return error.MissingDebugInfo, + else => return error.InvalidDebugInfo, + }; + // readMachoDebugInfo takes ownership of the file + // defer elf_file.close(); + var module = std.debug.SelfInfo.readMachODebugInfo(gpa, macho_file) catch { + return error.InvalidDebugInfo; + }; + + module.base_address = 0; + module.vmaddr_slide = 0; + + try info.address_map.put(gpa, 0, module); + }, + else => @compileError("TODO: implement debug info loading for the target platform"), + } return info; } pub fn deinit(info: *Info, gpa: Allocator) void { - for (info.address_map.values()) |*elf_module| { - elf_module.dwarf.deinit(gpa); - } + // for (info.address_map.values()) |*module| { + // module.dwarf.deinit(gpa); + // } info.address_map.deinit(gpa); info.* = undefined; } @@ -57,6 +79,38 @@ pub fn resolveAddresses( ) ResolveAddressesError!void { assert(sorted_pc_addrs.len == output.len); if (info.address_map.entries.len != 1) @panic("TODO"); - const elf_module = &info.address_map.values()[0]; - return info.coverage.resolveAddressesDwarf(gpa, sorted_pc_addrs, output, &elf_module.dwarf); + switch (builtin.os.tag) { + else => @compileError("unsupported"), + .linux => { + const elf_module = &info.address_map.values()[0]; + return info.coverage.resolveAddressesDwarf(gpa, sorted_pc_addrs, output, &elf_module.dwarf); + }, + .macos => { + const module = &info.address_map.values()[0]; + + var idx: usize = 0; + while (idx < sorted_pc_addrs.len) { + const dw = (module.getDwarfInfoForAddress(gpa, sorted_pc_addrs[idx]) catch return error.InvalidDebugInfo).?; + try dw.populateRanges(gpa); + const last = dw.ranges.getLastOrNull() orelse return; + var end_idx = idx; + while (end_idx < sorted_pc_addrs.len and + sorted_pc_addrs[end_idx] < last.end) end_idx += 1; + + if (end_idx == idx) { + std.debug.print("made no progress", .{}); + return; + } + + try info.coverage.resolveAddressesDwarf( + gpa, + sorted_pc_addrs[idx..end_idx], + output[idx..end_idx], + dw, + ); + + idx = end_idx; + } + }, + } } diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index ea7ecac4ed..df0fd01642 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -687,18 +687,28 @@ pub const Module = switch (native_os) { // Check if its debug infos are already in the cache const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); - const o_file_info = self.ofiles.getPtr(o_file_path) orelse - (self.loadOFile(allocator, o_file_path) catch |err| switch (err) { - error.FileNotFound, - error.MissingDebugInfo, - error.InvalidDebugInfo, - => return .{ - .relocated_address = relocated_address, - .symbol = symbol, - }, - else => return err, - }); + std.debug.print("loading '{s}'\n", .{o_file_path}); + + const clean_name = if (std.mem.endsWith(u8, o_file_path, ".a.o)")) blk: { + var it = std.mem.tokenizeScalar(u8, o_file_path, '('); + break :blk std.fmt.allocPrint(allocator, "{s}.o", .{it.next().?}) catch unreachable; + } else o_file_path; + std.debug.print("clean '{s}'\n", .{clean_name}); + + const o_file_info = self.ofiles.getPtr(clean_name) orelse + (self.loadOFile(allocator, clean_name) catch |err| switch (err) { + error.FileNotFound, + error.MissingDebugInfo, + error.InvalidDebugInfo, + => return .{ + .relocated_address = relocated_address, + .symbol = symbol, + }, + else => return err, + }); + + std.debug.print("success\n", .{}); return .{ .relocated_address = relocated_address, .symbol = symbol, @@ -846,7 +856,7 @@ pub const WindowsModule = struct { /// This takes ownership of macho_file: users of this function should not close /// it themselves, even on error. /// TODO it's weird to take ownership even on error, rework this code. -fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module { +pub fn readMachODebugInfo(allocator: Allocator, macho_file: File) !Module { const mapped_mem = try mapWholeFile(macho_file); const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));