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.
This commit is contained in:
Loris Cro 2025-02-28 10:34:06 +01:00
parent f8fe503146
commit 7a50e76eb1
3 changed files with 87 additions and 21 deletions

View File

@ -582,6 +582,7 @@ fn prepareTables(
ws.coverage_mutex.lock(); ws.coverage_mutex.lock();
defer ws.coverage_mutex.unlock(); defer ws.coverage_mutex.unlock();
std.debug.print("SET {}\n", .{coverage_id});
const gop = try ws.coverage_files.getOrPut(gpa, coverage_id); const gop = try ws.coverage_files.getOrPut(gpa, coverage_id);
if (gop.found_existing) { if (gop.found_existing) {
// We are fuzzing the same executable with multiple threads. // 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(); ws.coverage_mutex.lock();
defer ws.coverage_mutex.unlock(); defer ws.coverage_mutex.unlock();
std.debug.print("GET {}\n", .{coverage_id});
const coverage_map = ws.coverage_files.getPtr(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 header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
const pcs = header.pcAddrs(); const pcs = header.pcAddrs();

View File

@ -7,6 +7,7 @@
//! properties. //! properties.
const std = @import("../std.zig"); const std = @import("../std.zig");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Path = std.Build.Cache.Path; const Path = std.Build.Cache.Path;
const Dwarf = std.debug.Dwarf; const Dwarf = std.debug.Dwarf;
@ -17,7 +18,7 @@ const SourceLocation = std.debug.Coverage.SourceLocation;
const Info = @This(); const Info = @This();
/// Sorted by key, ascending. /// 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. /// Externally managed, outlives this `Info` instance.
coverage: *Coverage, coverage: *Coverage,
@ -25,20 +26,41 @@ pub const LoadError = Dwarf.ElfModule.LoadError;
pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info { pub fn load(gpa: Allocator, path: Path, coverage: *Coverage) LoadError!Info {
var sections: Dwarf.SectionArray = Dwarf.null_section_array; var sections: Dwarf.SectionArray = Dwarf.null_section_array;
var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null);
try elf_module.dwarf.populateRanges(gpa);
var info: Info = .{ var info: Info = .{
.address_map = .{}, .address_map = .{},
.coverage = coverage, .coverage = coverage,
}; };
switch (builtin.os.tag) {
.linux => {
var elf_module = try Dwarf.ElfModule.loadPath(gpa, path, null, null, &sections, null);
try elf_module.dwarf.populateRanges(gpa);
try info.address_map.put(gpa, elf_module.base_address, elf_module); 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; return info;
} }
pub fn deinit(info: *Info, gpa: Allocator) void { pub fn deinit(info: *Info, gpa: Allocator) void {
for (info.address_map.values()) |*elf_module| { // for (info.address_map.values()) |*module| {
elf_module.dwarf.deinit(gpa); // module.dwarf.deinit(gpa);
} // }
info.address_map.deinit(gpa); info.address_map.deinit(gpa);
info.* = undefined; info.* = undefined;
} }
@ -57,6 +79,38 @@ pub fn resolveAddresses(
) ResolveAddressesError!void { ) ResolveAddressesError!void {
assert(sorted_pc_addrs.len == output.len); assert(sorted_pc_addrs.len == output.len);
if (info.address_map.entries.len != 1) @panic("TODO"); if (info.address_map.entries.len != 1) @panic("TODO");
switch (builtin.os.tag) {
else => @compileError("unsupported"),
.linux => {
const elf_module = &info.address_map.values()[0]; const elf_module = &info.address_map.values()[0];
return info.coverage.resolveAddressesDwarf(gpa, sorted_pc_addrs, output, &elf_module.dwarf); 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;
}
},
}
} }

View File

@ -687,8 +687,17 @@ pub const Module = switch (native_os) {
// Check if its debug infos are already in the cache // Check if its debug infos are already in the cache
const o_file_path = mem.sliceTo(self.strings[symbol.ofile..], 0); 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) { 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.FileNotFound,
error.MissingDebugInfo, error.MissingDebugInfo,
error.InvalidDebugInfo, error.InvalidDebugInfo,
@ -699,6 +708,7 @@ pub const Module = switch (native_os) {
else => return err, else => return err,
}); });
std.debug.print("success\n", .{});
return .{ return .{
.relocated_address = relocated_address, .relocated_address = relocated_address,
.symbol = symbol, .symbol = symbol,
@ -846,7 +856,7 @@ pub const WindowsModule = struct {
/// This takes ownership of macho_file: users of this function should not close /// This takes ownership of macho_file: users of this function should not close
/// it themselves, even on error. /// it themselves, even on error.
/// TODO it's weird to take ownership even on error, rework this code. /// 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 mapped_mem = try mapWholeFile(macho_file);
const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr)); const hdr: *const macho.mach_header_64 = @ptrCast(@alignCast(mapped_mem.ptr));