authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-09 12:50:48-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-08-09 12:50:48-07:00
logeb17fe85a595c81da0304f4168c5dcf8181bd972
treee4cc1b923c05ea12826edb85c82919865d0950e6
parent9f46abf598a1bc27aa42a96aa6d94f3112fa210b
parentf6f1ecf0f9227bee33b963a6ff5dfc3001ad2c46
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #21006 from ziglang/fuzz

fuzzing: more optimized and correct management of 8-bit PC counters

5 files changed, 114 insertions(+), 64 deletions(-)

lib/fuzzer.zig+31-19
......@@ -213,8 +213,12 @@ const Fuzzer = struct {
213213 .truncate = false,
214214 });
215215 defer coverage_file.close();
216 const n_bitset_elems = (flagged_pcs.len + 7) / 8;
217 const bytes_len = @sizeOf(SeenPcsHeader) + flagged_pcs.len * @sizeOf(usize) + n_bitset_elems;
216 const n_bitset_elems = (flagged_pcs.len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
217 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
218 comptime assert(SeenPcsHeader.trailing[1] == .pc_addr);
219 const bytes_len = @sizeOf(SeenPcsHeader) +
220 n_bitset_elems * @sizeOf(usize) +
221 flagged_pcs.len * @sizeOf(usize);
218222 const existing_len = coverage_file.getEndPos() catch |err| {
219223 fatal("unable to check len of coverage file: {s}", .{@errorName(err)});
220224 };
......@@ -229,7 +233,7 @@ const Fuzzer = struct {
229233 fatal("unable to init coverage memory map: {s}", .{@errorName(err)});
230234 };
231235 if (existing_len != 0) {
232 const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader)..][0 .. flagged_pcs.len * @sizeOf(usize)];
236 const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + @sizeOf(usize) * n_bitset_elems ..][0 .. flagged_pcs.len * @sizeOf(usize)];
233237 const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes);
234238 for (existing_pcs, flagged_pcs, 0..) |old, new, i| {
235239 if (old != new.addr) {
......@@ -246,10 +250,10 @@ const Fuzzer = struct {
246250 .lowest_stack = std.math.maxInt(usize),
247251 };
248252 f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header));
253 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
249254 for (flagged_pcs) |flagged_pc| {
250255 f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&flagged_pc.addr));
251256 }
252 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems);
253257 }
254258 }
255259
......@@ -299,22 +303,30 @@ const Fuzzer = struct {
299303 .score = analysis.score,
300304 };
301305
302 // Track code coverage from all runs.
303306 {
304 const seen_pcs = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + f.flagged_pcs.len * @sizeOf(usize) ..];
305 for (seen_pcs, 0..) |*elem, i| {
306 const byte_i = i * 8;
307 const mask: u8 =
308 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 0] != 0)) << 0) |
309 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 1] != 0)) << 1) |
310 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 2] != 0)) << 2) |
311 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 3] != 0)) << 3) |
312 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 4] != 0)) << 4) |
313 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 5] != 0)) << 5) |
314 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 6] != 0)) << 6) |
315 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 7] != 0)) << 7);
316
317 _ = @atomicRmw(u8, elem, .Or, mask, .monotonic);
307 // Track code coverage from all runs.
308 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
309 const header_end_ptr: [*]volatile usize = @ptrCast(f.seen_pcs.items[@sizeOf(SeenPcsHeader)..]);
310 const remainder = f.flagged_pcs.len % @bitSizeOf(usize);
311 const aligned_len = f.flagged_pcs.len - remainder;
312 const seen_pcs = header_end_ptr[0..aligned_len];
313 const pc_counters = std.mem.bytesAsSlice([@bitSizeOf(usize)]u8, f.pc_counters[0..aligned_len]);
314 const V = @Vector(@bitSizeOf(usize), u8);
315 const zero_v: V = @splat(0);
316
317 for (header_end_ptr[0..pc_counters.len], pc_counters) |*elem, *array| {
318 const v: V = array.*;
319 const mask: usize = @bitCast(v != zero_v);
320 _ = @atomicRmw(usize, elem, .Or, mask, .monotonic);
321 }
322 if (remainder > 0) {
323 const i = pc_counters.len;
324 const elem = &seen_pcs[i];
325 var mask: usize = 0;
326 for (f.pc_counters[i * @bitSizeOf(usize) ..][0..remainder], 0..) |byte, bit_index| {
327 mask |= @as(usize, @intFromBool(byte != 0)) << @intCast(bit_index);
328 }
329 _ = @atomicRmw(usize, elem, .Or, mask, .monotonic);
318330 }
319331 }
320332
lib/fuzzer/wasm/main.zig+20-16
......@@ -125,12 +125,12 @@ export fn coveredSourceLocations() usize {
125125}
126126
127127export fn totalRuns() u64 {
128 const header: *abi.CoverageUpdateHeader = @ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]);
128 const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
129129 return header.n_runs;
130130}
131131
132132export fn uniqueRuns() u64 {
133 const header: *abi.CoverageUpdateHeader = @ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]);
133 const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
134134 return header.unique_runs;
135135}
136136
......@@ -335,7 +335,7 @@ fn computeSourceAnnotations(
335335 if (next_loc_index >= locs.items.len) return;
336336 const next_sli = locs.items[next_loc_index];
337337 const next_sl = next_sli.ptr();
338 if (next_sl.line > line or (next_sl.line == line and next_sl.column > column)) break;
338 if (next_sl.line > line or (next_sl.line == line and next_sl.column >= column)) break;
339339 try annotations.append(gpa, .{
340340 .file_byte_offset = offset,
341341 .dom_id = @intFromEnum(next_sli),
......@@ -349,7 +349,7 @@ var coverage = Coverage.init;
349349/// Index of type `SourceLocationIndex`.
350350var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};
351351/// Contains the most recent coverage update message, unmodified.
352var recent_coverage_update: std.ArrayListUnmanaged(u8) = .{};
352var recent_coverage_update: std.ArrayListAlignedUnmanaged(u8, @alignOf(u64)) = .{};
353353
354354fn updateCoverage(
355355 directories: []const Coverage.String,
......@@ -406,19 +406,23 @@ export fn sourceLocationFileCoveredList(sli_file: SourceLocationIndex) Slice(Sou
406406 };
407407 const want_file = sli_file.ptr().file;
408408 global.result.clearRetainingCapacity();
409 const covered_bits = recent_coverage_update.items[@sizeOf(abi.CoverageUpdateHeader)..];
409
410 // This code assumes 64-bit elements, which is incorrect if the executable
411 // being fuzzed is not a 64-bit CPU. It also assumes little-endian which
412 // can also be incorrect.
413 comptime assert(abi.CoverageUpdateHeader.trailing[0] == .pc_bits_usize);
414 const n_bitset_elems = (coverage_source_locations.items.len + @bitSizeOf(u64) - 1) / @bitSizeOf(u64);
415 const covered_bits = std.mem.bytesAsSlice(
416 u64,
417 recent_coverage_update.items[@sizeOf(abi.CoverageUpdateHeader)..][0 .. n_bitset_elems * @sizeOf(u64)],
418 );
410419 var sli: u32 = 0;
411 for (covered_bits) |byte| {
412 global.result.ensureUnusedCapacity(gpa, 8) catch @panic("OOM");
413 if ((byte & 0b0000_0001) != 0) global.add(sli + 0, want_file);
414 if ((byte & 0b0000_0010) != 0) global.add(sli + 1, want_file);
415 if ((byte & 0b0000_0100) != 0) global.add(sli + 2, want_file);
416 if ((byte & 0b0000_1000) != 0) global.add(sli + 3, want_file);
417 if ((byte & 0b0001_0000) != 0) global.add(sli + 4, want_file);
418 if ((byte & 0b0010_0000) != 0) global.add(sli + 5, want_file);
419 if ((byte & 0b0100_0000) != 0) global.add(sli + 6, want_file);
420 if ((byte & 0b1000_0000) != 0) global.add(sli + 7, want_file);
421 sli += 8;
420 for (covered_bits) |elem| {
421 global.result.ensureUnusedCapacity(gpa, 64) catch @panic("OOM");
422 for (0..@bitSizeOf(u64)) |i| {
423 if ((elem & (@as(u64, 1) << @intCast(i))) != 0) global.add(sli, want_file);
424 sli += 1;
425 }
422426 }
423427 return Slice(SourceLocationIndex).init(global.result.items);
424428}
lib/std/Build/Fuzz/WebServer.zig+6-7
......@@ -7,6 +7,7 @@ const Step = std.Build.Step;
77const Coverage = std.debug.Coverage;
88const abi = std.Build.Fuzz.abi;
99const log = std.log;
10const assert = std.debug.assert;
1011
1112const WebServer = @This();
1213
......@@ -383,7 +384,7 @@ fn sendCoverageContext(
383384 // TODO: make each events URL correspond to one coverage map
384385 const coverage_map = &coverage_maps[0];
385386 const cov_header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
386 const seen_pcs = coverage_map.mapped_memory[@sizeOf(abi.SeenPcsHeader) + coverage_map.source_locations.len * @sizeOf(usize) ..];
387 const seen_pcs = cov_header.seenBits();
387388 const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic);
388389 const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic);
389390 const lowest_stack = @atomicLoad(usize, &cov_header.lowest_stack, .monotonic);
......@@ -415,7 +416,7 @@ fn sendCoverageContext(
415416 };
416417 const iovecs: [2]std.posix.iovec_const = .{
417418 makeIov(std.mem.asBytes(&header)),
418 makeIov(seen_pcs),
419 makeIov(std.mem.sliceAsBytes(seen_pcs)),
419420 };
420421 try web_socket.writeMessagev(&iovecs, .binary);
421422
......@@ -630,8 +631,7 @@ fn prepareTables(
630631 gop.value_ptr.mapped_memory = mapped_memory;
631632
632633 const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
633 const pcs_bytes = mapped_memory[@sizeOf(abi.SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)];
634 const pcs = std.mem.bytesAsSlice(usize, pcs_bytes);
634 const pcs = header.pcAddrs();
635635 const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len);
636636 errdefer gpa.free(source_locations);
637637 debug_info.resolveAddresses(gpa, pcs, source_locations) catch |err| {
......@@ -648,9 +648,8 @@ fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyRepo
648648 defer ws.coverage_mutex.unlock();
649649
650650 const coverage_map = ws.coverage_files.getPtr(coverage_id).?;
651 const ptr = coverage_map.mapped_memory;
652 const pcs_bytes = ptr[@sizeOf(abi.SeenPcsHeader)..][0 .. coverage_map.source_locations.len * @sizeOf(usize)];
653 const pcs: []const usize = @alignCast(std.mem.bytesAsSlice(usize, pcs_bytes));
651 const header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
652 const pcs = header.pcAddrs();
654653 const index = std.sort.upperBound(usize, pcs, addr, struct {
655654 fn order(context: usize, item: usize) std.math.Order {
656655 return std.math.order(item, context);
lib/std/Build/Fuzz/abi.zig+42-6
......@@ -7,13 +7,40 @@
77/// make the ints be the size of the target used with libfuzzer.
88///
99/// Trailing:
10/// * pc_addr: usize for each pcs_len
1110/// * 1 bit per pc_addr, usize elements
11/// * pc_addr: usize for each pcs_len
1212pub const SeenPcsHeader = extern struct {
1313 n_runs: usize,
1414 unique_runs: usize,
1515 pcs_len: usize,
1616 lowest_stack: usize,
17
18 /// Used for comptime assertions. Provides a mechanism for strategically
19 /// causing compile errors.
20 pub const trailing = .{
21 .pc_bits_usize,
22 .pc_addr,
23 };
24
25 pub fn headerEnd(header: *const SeenPcsHeader) []const usize {
26 const ptr: [*]align(@alignOf(usize)) const u8 = @ptrCast(header);
27 const header_end_ptr: [*]const usize = @ptrCast(ptr + @sizeOf(SeenPcsHeader));
28 const pcs_len = header.pcs_len;
29 return header_end_ptr[0 .. pcs_len + seenElemsLen(pcs_len)];
30 }
31
32 pub fn seenBits(header: *const SeenPcsHeader) []const usize {
33 return header.headerEnd()[0..seenElemsLen(header.pcs_len)];
34 }
35
36 pub fn seenElemsLen(pcs_len: usize) usize {
37 return (pcs_len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
38 }
39
40 pub fn pcAddrs(header: *const SeenPcsHeader) []const usize {
41 const pcs_len = header.pcs_len;
42 return header.headerEnd()[seenElemsLen(pcs_len)..][0..pcs_len];
43 }
1744};
1845
1946pub const ToClientTag = enum(u8) {
......@@ -47,12 +74,21 @@ pub const SourceIndexHeader = extern struct {
4774/// changes.
4875///
4976/// Trailing:
50/// * one bit per source_locations_len, contained in u8 elements
77/// * one bit per source_locations_len, contained in u64 elements
5178pub const CoverageUpdateHeader = extern struct {
52 tag: ToClientTag = .coverage_update,
53 n_runs: u64 align(1),
54 unique_runs: u64 align(1),
55 lowest_stack: u64 align(1),
79 flags: Flags = .{},
80 n_runs: u64,
81 unique_runs: u64,
82 lowest_stack: u64,
83
84 pub const Flags = packed struct(u64) {
85 tag: ToClientTag = .coverage_update,
86 _: u56 = 0,
87 };
88
89 pub const trailing = .{
90 .pc_bits_usize,
91 };
5692};
5793
5894/// Sent to the fuzzer web client when the set of entry points is updated.
tools/dump-cov.zig+15-16
......@@ -5,6 +5,7 @@ const std = @import("std");
55const fatal = std.process.fatal;
66const Path = std.Build.Cache.Path;
77const assert = std.debug.assert;
8const SeenPcsHeader = std.Build.Fuzz.abi.SeenPcsHeader;
89
910pub fn main() !void {
1011 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
......@@ -36,24 +37,29 @@ pub fn main() !void {
3637 };
3738 defer debug_info.deinit(gpa);
3839
39 const cov_bytes = cov_path.root_dir.handle.readFileAlloc(arena, cov_path.sub_path, 1 << 30) catch |err| {
40 const cov_bytes = cov_path.root_dir.handle.readFileAllocOptions(
41 arena,
42 cov_path.sub_path,
43 1 << 30,
44 null,
45 @alignOf(SeenPcsHeader),
46 null,
47 ) catch |err| {
4048 fatal("failed to load coverage file {}: {s}", .{ cov_path, @errorName(err) });
4149 };
4250
4351 var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
4452 const stdout = bw.writer();
4553
46 const header: *align(1) SeenPcsHeader = @ptrCast(cov_bytes);
54 const header: *SeenPcsHeader = @ptrCast(cov_bytes);
4755 try stdout.print("{any}\n", .{header.*});
48 //const n_bitset_elems = (header.pcs_len + 7) / 8;
49 const pcs_bytes = cov_bytes[@sizeOf(SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)];
50 const pcs = try arena.alloc(usize, header.pcs_len);
51 for (0..pcs_bytes.len / @sizeOf(usize), pcs) |i, *pc| {
52 pc.* = std.mem.readInt(usize, pcs_bytes[i * @sizeOf(usize) ..][0..@sizeOf(usize)], .little);
56 const pcs = header.pcAddrs();
57 for (0.., pcs[0 .. pcs.len - 1], pcs[1..]) |i, a, b| {
58 if (a > b) std.log.err("{d}: 0x{x} > 0x{x}", .{ i, a, b });
5359 }
5460 assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize)));
5561
56 const seen_pcs = cov_bytes[@sizeOf(SeenPcsHeader) + pcs.len * @sizeOf(usize) ..];
62 const seen_pcs = header.seenBits();
5763
5864 const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, pcs.len);
5965 try debug_info.resolveAddresses(gpa, pcs, source_locations);
......@@ -62,7 +68,7 @@ pub fn main() !void {
6268 const file = debug_info.coverage.fileAt(sl.file);
6369 const dir_name = debug_info.coverage.directories.keys()[file.directory_index];
6470 const dir_name_slice = debug_info.coverage.stringAt(dir_name);
65 const hit: u1 = @truncate(seen_pcs[i / 8] >> @intCast(i % 8));
71 const hit: u1 = @truncate(seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize)));
6672 try stdout.print("{c}{x}: {s}/{s}:{d}:{d}\n", .{
6773 "-+"[hit], pc, dir_name_slice, debug_info.coverage.stringAt(file.basename), sl.line, sl.column,
6874 });
......@@ -70,10 +76,3 @@ pub fn main() !void {
7076
7177 try bw.flush();
7278}
73
74const SeenPcsHeader = extern struct {
75 n_runs: usize,
76 deduplicated_runs: usize,
77 pcs_len: usize,
78 lowest_stack: usize,
79};