It took me longer than it should have to get 0.16.0 to build from source given linker bugs on my libc (Docker to the rescue), and then took my best guess at applying the patch to 0.16 (it did not apply cleanly).
It seems to compile now, so I’m pretty sure that was it! But now crashes inside the fuzzer code I patched immediately, which makes me suspect I did the patch wrong…
diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig
index c50ce759ba..b6a954a059 100644
--- a/lib/fuzzer.zig
+++ b/lib/fuzzer.zig
@@ -55,6 +55,12 @@ fn bitsetUsizes(elems: usize) usize {
return math.divCeil(usize, elems, @bitSizeOf(usize)) catch unreachable;
}
+/// https://github.com/llvm/llvm-project/blob/6121df77a781d2e6f9a8e569aa45ccfffd6c7e0e/compiler-rt/lib/fuzzer/FuzzerTracePC.h#L122
+const PCTableEntry = extern struct {
+ PC: usize,
+ PCFlags: usize,
+};
+
const Executable = struct {
/// Tracks the hit count for each pc as updated by the test's instrumentation.
pc_counters: []u8,
@@ -68,7 +74,7 @@ const Executable = struct {
fn getCoverageMap(
cache_dir: Io.Dir,
- pcs: []const usize,
+ pc_table: []const PCTableEntry,
pc_digest: u64,
) []align(std.heap.page_size_min) volatile u8 {
const file_name = std.fmt.hex(pc_digest);
@@ -101,10 +107,10 @@ const Executable = struct {
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 pc_bitset_usizes = bitsetUsizes(pc_table.len);
const coverage_file_len = @sizeOf(abi.SeenPcsHeader) +
pc_bitset_usizes * @sizeOf(usize) +
- pcs.len * @sizeOf(usize);
+ pc_table.len * @sizeOf(usize);
var populate: bool = false;
const size = coverage_file.length(io) catch |e|
@@ -137,11 +143,11 @@ const Executable = struct {
header.* = .{
.n_runs = 0,
.unique_runs = 0,
- .pcs_len = pcs.len,
+ .pcs_len = pc_table.len,
};
@memset(trailing_bitset, 0);
- for (trailing_addresses, pcs) |*cov_pc, slided_pc| {
- cov_pc.* = fuzzer_unslide_address(slided_pc);
+ for (trailing_addresses, pc_table) |*cov_pc, entry| {
+ cov_pc.* = fuzzer_unslide_address(entry.PC);
}
io_map.write(io) catch |e|
panic("failed to write memory map of '{s}': {t}", .{ &file_name, e });
@@ -151,12 +157,12 @@ const Executable = struct {
.{ &file_name, e },
);
} else { // Check expected contents
- if (header.pcs_len != pcs.len) panic(
+ if (header.pcs_len != pc_table.len) panic(
"incompatible existing coverage file '{s}' (differing pcs length: {} != {})",
- .{ &file_name, header.pcs_len, pcs.len },
+ .{ &file_name, header.pcs_len, pc_table.len },
);
- for (0.., header.pcAddrs(), pcs) |i, cov_pc, slided_pc| {
- const pc = fuzzer_unslide_address(slided_pc);
+ for (0.., header.pcAddrs(), pc_table) |i, cov_pc, entry| {
+ const pc = fuzzer_unslide_address(entry.PC);
if (cov_pc != pc) panic(
"incompatible existing coverage file '{s}' (differing pc at index {d}: {x} != {x})",
.{ &file_name, i, cov_pc, pc },
@@ -201,15 +207,15 @@ const Executable = struct {
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, .{
+ const pc_table = blk: {
+ const pcs_start_name = section_start_prefix ++ "__sancov_pcs";
+ const pcs_start = @extern([*]PCTableEntry, .{
.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, .{
+ const pcs_end_name = section_end_prefix ++ "__sancov_pcs";
+ const pcs_end = @extern([*]PCTableEntry, .{
.name = pcs_end_name,
.linkage = .weak,
}) orelse panic("missing {s} symbol", .{pcs_end_name});
@@ -217,22 +223,22 @@ const Executable = struct {
break :blk pcs_start[0 .. pcs_end - pcs_start];
};
- if (self.pc_counters.len != pcs.len) panic(
+ if (self.pc_counters.len != pc_table.len) panic(
"pc counters length and pcs length do not match ({} != {})",
- .{ self.pc_counters.len, pcs.len },
+ .{ self.pc_counters.len, pc_table.len },
);
self.pc_digest = digest: {
// Relocations have been applied to `pcs` so it contains runtime addresses (with slide
// 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);
+ for (pc_table) |entry| {
+ const pc_vaddr = fuzzer_unslide_address(entry.PC);
h.update(@ptrCast(&pc_vaddr));
}
break :digest h.final();
};
- self.shared_seen_pcs = getCoverageMap(cache_dir, pcs, self.pc_digest);
+ self.shared_seen_pcs = getCoverageMap(cache_dir, pc_table, self.pc_digest);
return self;
}
diff --git a/src/Compilation.zig b/src/Compilation.zig
index 695dd8918f..00c528f09c 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -6600,10 +6600,6 @@ fn addCommonCCArgs(
if (san_arg.items.len == 0) try san_arg.appendSlice(arena, prefix);
try san_arg.appendSlice(arena, "thread,");
}
- if (mod.fuzz) {
- if (san_arg.items.len == 0) try san_arg.appendSlice(arena, prefix);
- try san_arg.appendSlice(arena, "fuzzer-no-link,");
- }
// Chop off the trailing comma and append to argv.
if (san_arg.pop()) |_| {
try argv.append(san_arg.items);
@@ -6638,8 +6634,12 @@ fn addCommonCCArgs(
}
}
+ // This logic must be kept in sync with the coverage emitted by the LLVM backend.
+
if (comp.config.san_cov_trace_pc_guard) {
try argv.append("-fsanitize-coverage=trace-pc-guard");
+ } else if (mod.fuzz) {
+ try argv.append("-fsanitize-coverage=edge,inline-8bit-counters,pc-table");
}
}
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 1ba3b272da..5dbe565377 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1069,7 +1069,10 @@ pub const Object = struct {
.bitcode_filename = null,
// `.coverage` value is only used when `.sancov` is enabled.
- .sancov = options.fuzz or comp.config.san_cov_trace_pc_guard,
+ .sancov = comp.config.san_cov_trace_pc_guard,
+ // At least one of these values has to be non-default if `sancov` is
+ // set, otherwise LLVM will implicitly set `TracePCGuard`!
+ // https://github.com/llvm/llvm-project/pull/106464
.coverage = .{
.CoverageType = .Edge,
// Works in tandem with Inline8bitCounters or InlineBoolFlag.
@@ -1089,8 +1092,7 @@ pub const Object = struct {
// Zig emits its own PC table instrumentation.
.PCTable = false,
.NoPrune = false,
- // Workaround for https://github.com/llvm/llvm-project/pull/106464
- .StackDepth = true,
+ .StackDepth = false,
.TraceLoads = false,
.TraceStores = false,
.CollectControlFlow = false,
@@ -1466,7 +1468,7 @@ pub const Object = struct {
break :f .{
.counters_variable = counters_variable,
- .pcs = .empty,
+ .pc_table = .empty,
};
};
@@ -1516,18 +1518,14 @@ pub const Object = struct {
llvm_function.setAttributes(try attributes.finish(&o.builder), &o.builder);
if (fg.fuzz) |*f| {
- {
- const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .i8);
- f.counters_variable.ptrConst(&o.builder).global.ptr(&o.builder).type = array_llvm_ty;
- const zero_init = try o.builder.zeroInitConst(array_llvm_ty);
- try f.counters_variable.setInitializer(zero_init, &o.builder);
- }
-
- const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .ptr);
- const init_val = try o.builder.arrayConst(array_llvm_ty, f.pcs.items);
- // Due to error "members of llvm.compiler.used must be named", this global needs a name.
+ const cntrs_array_llvm_ty = try o.builder.arrayType(f.pc_table.items.len / 2, .i8);
+ f.counters_variable.ptrConst(&o.builder).global.ptr(&o.builder).type = cntrs_array_llvm_ty;
+ const zero_init = try o.builder.zeroInitConst(cntrs_array_llvm_ty);
+ try f.counters_variable.setInitializer(zero_init, &o.builder);
+ const pc_table_llvm_ty = try o.builder.arrayType(f.pc_table.items.len, .ptr);
+ const init_val = try o.builder.arrayConst(pc_table_llvm_ty, f.pc_table.items);
const anon_name = try o.builder.strtabStringFmt("__sancov_gen_.{d}", .{o.used.items.len});
- const pcs_variable = try o.builder.addVariable(anon_name, array_llvm_ty, .default);
+ const pcs_variable = try o.builder.addVariable(anon_name, pc_table_llvm_ty, .default);
try pcs_variable.setInitializer(init_val, &o.builder);
pcs_variable.setMutability(.constant, &o.builder);
pcs_variable.setSection(switch (target.ofmt) {
diff --git a/src/codegen/llvm/FuncGen.zig b/src/codegen/llvm/FuncGen.zig
index f1ebab4443..44568e7834 100644
--- a/src/codegen/llvm/FuncGen.zig
+++ b/src/codegen/llvm/FuncGen.zig
@@ -92,10 +92,10 @@ fn maybeMarkAllowZeroAccess(self: *FuncGen, info: InternPool.Key.PtrType) void {
pub const Fuzz = struct {
counters_variable: Builder.Variable.Index,
- pcs: std.ArrayList(Builder.Constant),
+ pc_table: std.ArrayList(Builder.Constant),
fn deinit(f: *Fuzz, gpa: Allocator) void {
- f.pcs.deinit(gpa);
+ f.pc_table.deinit(gpa);
f.* = undefined;
}
};
@@ -183,7 +183,7 @@ pub fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air
switch (coverage_point) {
.none => {},
.poi => if (self.fuzz) |*fuzz| {
- const poi_index = fuzz.pcs.items.len;
+ const poi_index = fuzz.pc_table.items.len;
const base_ptr = fuzz.counters_variable.toValue(&o.builder);
const ptr = try self.ptraddConst(base_ptr, poi_index);
const one = try o.builder.intValue(.i8, 1);
@@ -195,7 +195,7 @@ pub fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air
else
try o.builder.blockAddrConst(self.wip.function, self.wip.cursor.block);
const gpa = self.gpa;
- try fuzz.pcs.append(gpa, pc);
+ try fuzz.pc_table.append(gpa, pc);
},
}
for (body, 0..) |inst, i| {
Any final ideas? I’ll probably just consider this solved as close enough if not.