fuzz testing: implement initial macos support

This commit implements the linker-related code
required to have the `zig init` canyoufindme test
succeed on macos.

It fixes usage of the linker in order to account
for macos specific symbol mangling and introduces
some checks in the fuzzer code to prevent crashes
in case that instrumented code is invoked before
`fuzz_init` runs.

`@disableInstrumentation` has been added to the
start code to help reduce the amount of (needlessly)
instrumented code that runs, but the builtin is
active only in the scope where it's used, meaning
that any non-inlined function call that happens in
that same scope will still have instrumentation
enabled unless it too gets its own
`@disableInstrumentation` call.

Removing temporarily the code that bails out from
instrumentation callbacks when the fuzzer has not
been inited can be used to turn early (and wasteful)
execution of instrumented code into a crash, helping
finding places where to put more calls to
`@disableInstrumentation`.
This commit is contained in:
Loris Cro 2025-02-21 18:53:21 +01:00
parent c45dcd013b
commit f8fe503146
5 changed files with 80 additions and 29 deletions

View file

@ -17,12 +17,7 @@ fn logOverride(
comptime format: []const u8,
args: anytype,
) void {
const f = if (log_file) |f| f else f: {
const f = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch
@panic("failed to open fuzzer log file");
log_file = f;
break :f f;
};
const f = if (log_file) |f| f else return;
const prefix1 = comptime level.asText();
const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
f.writer().print(prefix1 ++ prefix2 ++ format ++ "\n", args) catch @panic("failed to write to fuzzer log");
@ -102,6 +97,7 @@ fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {
}
const Fuzzer = struct {
inited: bool = false,
rng: std.Random.DefaultPrng,
pcs: []const usize,
pc_counters: []u8,
@ -157,6 +153,9 @@ const Fuzzer = struct {
f.pc_counters = pc_counters;
f.pcs = pcs;
log_file = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch
@panic("failed to open fuzzer log file");
// Choose a file name for the coverage based on a hash of the PCs that will be stored within.
const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs));
f.coverage_id = pc_digest;
@ -210,6 +209,8 @@ const Fuzzer = struct {
f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs));
}
f.inited = true;
}
fn initNextInput(f: *Fuzzer) void {
@ -296,8 +297,10 @@ const Fuzzer = struct {
/// where it branches.
fn traceValue(f: *Fuzzer, x: usize) void {
errdefer |err| oom(err);
if (f.inited) {
try f.traced_comparisons.put(gpa, x, {});
}
}
const Mutation = enum {
remove_byte,
@ -310,19 +313,21 @@ const Fuzzer = struct {
f.input.clearRetainingCapacity();
const old_input = f.corpus.items[corpus_index].bytes;
f.input.ensureTotalCapacity(old_input.len + 1) catch @panic("mmap file resize failed");
switch (mutation) {
sw: switch (mutation) {
.remove_byte => {
if (old_input.len == 0) continue :sw .add_byte;
const omitted_index = rng.uintLessThanBiased(usize, old_input.len);
f.input.appendSliceAssumeCapacity(old_input[0..omitted_index]);
f.input.appendSliceAssumeCapacity(old_input[omitted_index + 1 ..]);
},
.modify_byte => {
if (old_input.len == 0) continue :sw .add_byte;
const modified_index = rng.uintLessThanBiased(usize, old_input.len);
f.input.appendSliceAssumeCapacity(old_input);
f.input.items[modified_index] = rng.int(u8);
},
.add_byte => {
const modified_index = rng.uintLessThanBiased(usize, old_input.len);
const modified_index = if (old_input.len == 0) 0 else rng.uintLessThanBiased(usize, old_input.len);
f.input.appendSliceAssumeCapacity(old_input[0..modified_index]);
f.input.appendAssumeCapacity(rng.int(u8));
f.input.appendSliceAssumeCapacity(old_input[modified_index..]);
@ -468,27 +473,55 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
// Linkers are expected to automatically add `__start_<section>` and
// `__stop_<section>` symbols when section names are valid C identifiers.
const pc_counters_start = @extern([*]u8, .{
const pc_counters_start = switch (builtin.os.tag) {
.linux => @extern([*]u8, .{
.name = "__start___sancov_cntrs",
.linkage = .weak,
}) orelse fatal("missing __start___sancov_cntrs symbol", .{});
}) orelse fatal("missing __start___sancov_cntrs symbol", .{}),
.macos => @extern([*]u8, .{
.name = "\x01section$start$__DATA$__sancov_cntrs",
.linkage = .weak,
}) orelse fatal("missing section$start$__DATA$__sancov_cntrs symbol", .{}),
else => @compileError("TODO: implement fuzzing support for the target platform"),
};
const pc_counters_end = @extern([*]u8, .{
const pc_counters_end = switch (builtin.os.tag) {
.linux => @extern([*]u8, .{
.name = "__stop___sancov_cntrs",
.linkage = .weak,
}) orelse fatal("missing __stop___sancov_cntrs symbol", .{});
}) orelse fatal("missing __stop___sancov_cntrs symbol", .{}),
.macos => @extern([*]u8, .{
.name = "\x01section$end$__DATA$__sancov_cntrs",
.linkage = .weak,
}) orelse fatal("missing section$end$__DATA$__sancov_cntrs symbol", .{}),
else => @compileError("TODO: implement fuzzing support for the target platform"),
};
const pc_counters = pc_counters_start[0 .. pc_counters_end - pc_counters_start];
const pcs_start = @extern([*]usize, .{
const pcs_start = switch (builtin.os.tag) {
.linux => @extern([*]usize, .{
.name = "__start___sancov_pcs1",
.linkage = .weak,
}) orelse fatal("missing __start___sancov_pcs1 symbol", .{});
}) orelse fatal("missing __start___sancov_pcs1 symbol", .{}),
.macos => @extern([*]usize, .{
.name = "\x01section$start$__DATA_CONST$__sancov_pcs1",
.linkage = .weak,
}) orelse fatal("missing section$start$__DATA_CONST$__sancov_pcs1 symbol", .{}),
else => @compileError("TODO: implement fuzzing support for the target platform"),
};
const pcs_end = @extern([*]usize, .{
const pcs_end = switch (builtin.os.tag) {
.linux => @extern([*]usize, .{
.name = "__stop___sancov_pcs1",
.linkage = .weak,
}) orelse fatal("missing __stop___sancov_pcs1 symbol", .{});
}) orelse fatal("missing __stop___sancov_pcs1 symbol", .{}),
.macos => @extern([*]usize, .{
.name = "\x01section$end$__DATA_CONST$__sancov_pcs1",
.linkage = .weak,
}) orelse fatal("missing section$end$__DATA_CONST$__sancov_pcs1 symbol", .{}),
else => @compileError("TODO: implement fuzzing support for the target platform"),
};
const pcs = pcs_start[0 .. pcs_end - pcs_start];

View file

@ -617,6 +617,9 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
}
fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {
// Code coverage instrumentation might try to use thread local variables.
@disableInstrumentation();
var env_count: usize = 0;
while (c_envp[env_count] != null) : (env_count += 1) {}
const envp = @as([*][*:0]u8, @ptrCast(c_envp))[0..env_count];

View file

@ -1732,7 +1732,11 @@ pub const Object = struct {
try o.used.append(gpa, counters_variable.toConst(&o.builder));
counters_variable.setLinkage(.private, &o.builder);
counters_variable.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);
counters_variable.setSection(try o.builder.string("__sancov_cntrs"), &o.builder);
const name = if (target.os.tag == .macos)
"__DATA,__sancov_cntrs"
else
"__sancov_cntrs";
counters_variable.setSection(try o.builder.string(name), &o.builder);
break :f .{
.counters_variable = counters_variable,
@ -1794,7 +1798,11 @@ pub const Object = struct {
pcs_variable.setLinkage(.private, &o.builder);
pcs_variable.setMutability(.constant, &o.builder);
pcs_variable.setAlignment(Type.usize.abiAlignment(zcu).toLlvm(), &o.builder);
pcs_variable.setSection(try o.builder.string("__sancov_pcs1"), &o.builder);
const name = if (target.os.tag == .macos)
"__DATA_CONST,__sancov_pcs1"
else
"__sancov_pcs1";
pcs_variable.setSection(try o.builder.string(name), &o.builder);
try pcs_variable.setInitializer(init_val, &o.builder);
}

View file

@ -416,7 +416,12 @@ pub fn flushModule(
}
if (comp.config.any_fuzz) {
try positionals.append(try link.openObjectInput(diags, comp.fuzzer_lib.?.full_object_path));
try positionals.append(try link.openArchiveInput(
diags,
comp.fuzzer_lib.?.full_object_path,
true,
false,
));
}
if (comp.ubsan_rt_lib) |crt_file| {
@ -1524,6 +1529,7 @@ fn scanRelocs(self: *MachO) !void {
if (self.getInternalObject()) |obj| {
try obj.checkUndefs(self);
}
try self.reportUndefs();
if (self.getZigObject()) |zo| {

View file

@ -1313,6 +1313,7 @@ pub fn updateExports(
}
const exp_name = exp.opts.name.toSlice(&zcu.intern_pool);
const global_nlist_index = if (metadata.@"export"(self, exp_name)) |exp_index|
exp_index.*
else blk: {