feature. See also
. The project being documented here (as the example) is the Zig library itself.
fuzzer.Executable
const Executable = struct
File
Code
const Executable = struct {
pc_counters: []u8,
cache_f: Io.Dir,
shared_seen_pcs: []align(std.heap.page_size_min) volatile u8,
pc_digest: u64,
fn getCoverageMap(
cache_dir: Io.Dir,
pcs: []const usize,
pc_digest: u64,
) []align(std.heap.page_size_min) volatile u8 {
const file_name = std.fmt.hex(pc_digest);
var v = cache_dir.createDirPathOpen(io, "v", .{}) catch |e|
panic("failed to create directory 'v': {t}", .{e});
defer v.close(io);
// to ensure if we create the file we obtain an exclusive lock to populate it since another
// process may acquire a shared lock between the file being created and the lock request.
//
// Instead, the length will be used to determine if the file needs populated, and no
// process will acquire a shared lock before the coverage file is known to have been
// exclusively locked (i.e. is already locked). This means another process than the
// one which created the file could populate it, which is fine.
const coverage_file = v.createFile(io, &file_name, .{
.read = true,
.truncate = false,
}) catch |e| panic("failed to open coverage file '{s}': {t}", .{ &file_name, e });
const maybe_populate = coverage_file.tryLock(io, .exclusive) catch |e| panic(
"failed to acquire exclusive lock coverage file '{s}': {t}",
.{ &file_name, e },
);
if (!maybe_populate) {
coverage_file.lock(io, .shared) catch |e|
panic("failed to acquire share lock coverage file '{s}': {t}", .{ &file_name, e });
}
comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
const pc_bitset_usizes = bitsetUsizes(pcs.len);
const coverage_file_len = @sizeOf(abi.SeenPcsHeader) +
pc_bitset_usizes * @sizeOf(usize) +
pcs.len * @sizeOf(usize);
var populate: bool = false;
const size = coverage_file.length(io) catch |e|
panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });
if (size == 0 and maybe_populate) {
coverage_file.setLength(io, coverage_file_len) catch |e|
panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });
populate = true;
} else if (size != coverage_file_len) {
panic(
"incompatible existing coverage file '{s}' (differing lengths: {} != {})",
.{ &file_name, size, coverage_file_len },
);
} else if (maybe_populate) {
coverage_file.lock(io, .shared) catch |e|
panic("failed to demote lock for coverage file '{s}': {t}", .{ &file_name, e });
}
var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|
panic("failed to memmap coverage file '{s}': {t}", .{ &file_name, e });
const map = io_map.memory;
const header: *abi.SeenPcsHeader = @ptrCast(map[0..@sizeOf(abi.SeenPcsHeader)]);
const trailing = map[@sizeOf(abi.SeenPcsHeader)..];
const trailing_bitset_end = pc_bitset_usizes * @sizeOf(usize);
const trailing_bitset: []usize = @ptrCast(@alignCast(trailing[0..trailing_bitset_end]));
const trailing_addresses: []usize = @ptrCast(@alignCast(trailing[trailing_bitset_end..]));
if (populate) {
header.* = .{
.n_runs = 0,
.unique_runs = 0,
.pcs_len = pcs.len,
};
@memset(trailing_bitset, 0);
for (trailing_addresses, pcs) |*cov_pc, slided_pc| {
cov_pc.* = fuzzer_unslide_address(slided_pc);
}
io_map.write(io) catch |e|
panic("failed to write memory map of '{s}': {t}", .{ &file_name, e });
coverage_file.lock(io, .shared) catch |e| panic(
"failed to demote lock for coverage file '{s}': {t}",
.{ &file_name, e },
);
} else {
if (header.pcs_len != pcs.len) panic(
"incompatible existing coverage file '{s}' (differing pcs length: {} != {})",
.{ &file_name, header.pcs_len, pcs.len },
);
for (0.., header.pcAddrs(), pcs) |i, cov_pc, slided_pc| {
const pc = fuzzer_unslide_address(slided_pc);
if (cov_pc != pc) panic(
"incompatible existing coverage file '{s}' (differing pc at index {d}: {x} != {x})",
.{ &file_name, i, cov_pc, pc },
);
}
}
return map;
}
pub fn init(cache_dir_path: []const u8) Executable {
var self: Executable = undefined;
const cache_dir = Io.Dir.cwd().createDirPathOpen(io, cache_dir_path, .{}) catch |e|
panic("failed to open directory '{s}': {t}", .{ cache_dir_path, e });
cache_dir.createDirPath(io, "tmp") catch |e|
panic("failed to create directory 'tmp': {t}", .{e});
log_f = cache_dir.createFile(io, "tmp/libfuzzer.log", .{ .truncate = false }) catch |e|
panic("failed to create file 'tmp/libfuzzer.log': {t}", .{e});
self.cache_f = cache_dir.createDirPathOpen(io, "f", .{}) catch |e|
panic("failed to open directory 'f': {t}", .{e});
// end of sections whose names are valid C identifiers.
const ofmt = builtin.object_format;
const section_start_prefix, const section_end_prefix = switch (ofmt) {
.elf => .{ "__start_", "__stop_" },
.macho => .{ "\x01section$start$__DATA$", "\x01section$end$__DATA$" },
else => @compileError("unsupported fuzzing object format '" ++ @tagName(ofmt) ++ "'"),
};
self.pc_counters = blk: {
const pc_counters_start_name = section_start_prefix ++ "__sancov_cntrs";
const pc_counters_start = @extern([*]u8, .{
.name = pc_counters_start_name,
.linkage = .weak,
}) orelse panic("missing {s} symbol", .{pc_counters_start_name});
const pc_counters_end_name = section_end_prefix ++ "__sancov_cntrs";
const pc_counters_end = @extern([*]u8, .{
.name = pc_counters_end_name,
.linkage = .weak,
}) orelse panic("missing {s} symbol", .{pc_counters_end_name});
break :blk pc_counters_start[0 .. pc_counters_end - pc_counters_start];
};
const pcs = blk: {
const pcs_start_name = section_start_prefix ++ "__sancov_pcs1";
const pcs_start = @extern([*]usize, .{
.name = pcs_start_name,
.linkage = .weak,
}) orelse panic("missing {s} symbol", .{pcs_start_name});
const pcs_end_name = section_end_prefix ++ "__sancov_pcs1";
const pcs_end = @extern([*]usize, .{
.name = pcs_end_name,
.linkage = .weak,
}) orelse panic("missing {s} symbol", .{pcs_end_name});
break :blk pcs_start[0 .. pcs_end - pcs_start];
};
if (self.pc_counters.len != pcs.len) panic(
"pc counters length and pcs length do not match ({} != {})",
.{ self.pc_counters.len, pcs.len },
);
self.pc_digest = digest: {
// applied). We need to translate these to the virtual addresses as on disk.
var h: std.hash.Wyhash = .init(0);
for (pcs) |pc| {
const pc_vaddr = fuzzer_unslide_address(pc);
h.update(@ptrCast(&pc_vaddr));
}
break :digest h.final();
};
self.shared_seen_pcs = getCoverageMap(cache_dir, pcs, self.pc_digest);
return self;
}
fn inputFileName(buf: *[10]u8, i: u32) []u8 {
assert(buf[0..2].* == "in".*);
const hex = std.fmt.bufPrint(buf[2..], "{x}", .{i}) catch unreachable;
return buf[0 .. 2 + hex.len];
}
pub fn pcBitsetIterator(self: Executable) PcBitsetIterator {
return .{ .pc_counters = self.pc_counters };
}
pub const PcBitsetIterator = struct {
index: usize = 0,
pc_counters: []u8,
pub fn next(i: *PcBitsetIterator) usize {
const rest = i.pc_counters[i.index..];
if (rest.len >= @bitSizeOf(usize)) {
defer i.index += @bitSizeOf(usize);
const V = @Vector(@bitSizeOf(usize), u8);
return @as(usize, @bitCast(@as(V, @splat(0)) != rest[0..@bitSizeOf(usize)].*));
} else if (rest.len != 0) {
defer i.index += rest.len;
var res: usize = 0;
for (0.., rest) |bit_index, byte| {
res |= @shlExact(@as(usize, @intFromBool(byte != 0)), @intCast(bit_index));
}
return res;
} else unreachable;
}
};
pub fn seenPcsHeader(e: Executable) *align(std.heap.page_size_min) volatile abi.SeenPcsHeader {
return mem.bytesAsValue(
abi.SeenPcsHeader,
e.shared_seen_pcs[0..@sizeOf(abi.SeenPcsHeader)],
);
}
}