Skip to content

Commit 2a5f5bc

Browse files
committed
Dump coverage using PCGuard
1 parent e9301d3 commit 2a5f5bc

File tree

10 files changed

+288
-37
lines changed

10 files changed

+288
-37
lines changed

Cargo.lock

Lines changed: 14 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/libafl_targets/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ sancov_cmplog = [
5151
"common",
5252
] # Defines cmp and __sanitizer_weak_hook functions. Use libfuzzer_interceptors to define interceptors (only compatible with Linux)
5353
sancov_pcguard = ["sancov_pcguard_hitcounts"]
54+
sancov_pcguard_dump_cov = ["std", "backtrace"]
5455
sanitizer_interfaces = []
5556
clippy = [] # Ignore compiler warnings during clippy
5657
observers = ["meminterval"]
@@ -94,6 +95,7 @@ serde = { workspace = true, default-features = false, features = [
9495
"alloc",
9596
] } # serialization lib
9697
meminterval = { workspace = true, features = ["serde"], optional = true }
98+
backtrace = { workspace = true, optional = true }
9799

98100
[lints]
99101
workspace = true

crates/libafl_targets/build.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,19 @@ fn main() {
196196
.compile("coverage");
197197
}
198198

199+
#[cfg(feature = "sancov_pcguard_dump_cov")]
200+
{
201+
println!("cargo:rerun-if-changed=src/sancov_pcguard.c");
202+
let mut sancov_pcguard = cc::Build::new();
203+
#[cfg(feature = "whole_archive")]
204+
{
205+
sancov_pcguard.link_lib_modifier("+whole-archive");
206+
}
207+
sancov_pcguard
208+
.file(src_dir.join("sancov_pcguard.c"))
209+
.compile("sancov_pcguard");
210+
}
211+
199212
#[cfg(feature = "cmplog")]
200213
{
201214
println!("cargo:rerun-if-changed=src/cmplog.h");

crates/libafl_targets/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,17 @@ include!(concat!(env!("OUT_DIR"), "/constants.rs"));
5858
feature = "sancov_pcguard_hitcounts",
5959
feature = "sancov_ngram4",
6060
feature = "sancov_ngram8",
61-
feature = "sancov_ctx"
61+
feature = "sancov_ctx",
62+
feature = "sancov_pcguard_dump_cov"
6263
))]
6364
pub mod sancov_pcguard;
6465
#[cfg(any(
6566
feature = "sancov_pcguard_edges",
6667
feature = "sancov_pcguard_hitcounts",
6768
feature = "sancov_ngram4",
6869
feature = "sancov_ngram8",
69-
feature = "sancov_ctx"
70+
feature = "sancov_ctx",
71+
feature = "sancov_pcguard_dump_cov"
7072
))]
7173
pub use sancov_pcguard::*;
7274

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include <stdint.h>
2+
#include "common.h"
3+
4+
void __libafl_targets_trace_pc_guard(uint32_t* guard, uintptr_t pc);
5+
6+
void __sanitizer_cov_trace_pc_guard(uint32_t* guard) {
7+
uintptr_t pc = RETADDR;
8+
__libafl_targets_trace_pc_guard(guard, pc);
9+
}

crates/libafl_targets/src/sancov_pcguard.rs

Lines changed: 169 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
//! [`LLVM` `PcGuard`](https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards) runtime for `LibAFL`.
22
3+
#[cfg(feature = "sancov_pcguard_dump_cov")]
4+
use core::ffi::c_void;
35
#[rustversion::nightly]
46
#[cfg(any(feature = "sancov_ngram4", feature = "sancov_ngram8"))]
57
use core::simd::num::SimdUint;
8+
#[cfg(feature = "sancov_pcguard_dump_cov")]
9+
use core::sync::atomic::{AtomicPtr, Ordering};
610
use core::{mem::align_of, slice};
11+
#[cfg(feature = "sancov_pcguard_dump_cov")]
12+
use std::collections::{HashMap, HashSet};
13+
#[cfg(feature = "sancov_pcguard_dump_cov")]
14+
use std::string::String;
15+
#[cfg(feature = "sancov_pcguard_dump_cov")]
16+
use std::sync::Mutex;
717

818
#[cfg(any(
919
feature = "sancov_ngram4",
@@ -16,15 +26,9 @@ use libafl::executors::hooks::ExecutorHook;
1626
#[cfg(any(feature = "sancov_ngram4", feature = "sancov_ngram8"))]
1727
#[allow(unused_imports)] // only used in an unused function
1828
use crate::EDGES_MAP_DEFAULT_SIZE;
19-
#[cfg(any(
20-
feature = "pointer_maps",
21-
feature = "sancov_pcguard_edges",
22-
feature = "sancov_pcguard_hitcounts",
23-
feature = "sancov_ctx",
24-
feature = "sancov_ngram4",
25-
feature = "sancov_ngram8",
26-
))]
29+
#[cfg(feature = "coverage")]
2730
use crate::coverage::EDGES_MAP;
31+
#[cfg(feature = "coverage")]
2832
use crate::coverage::MAX_EDGES_FOUND;
2933
#[cfg(feature = "pointer_maps")]
3034
use crate::{EDGES_MAP_ALLOCATED_SIZE, coverage::EDGES_MAP_PTR};
@@ -69,6 +73,21 @@ pub static SHR_8: Ngram8 = Ngram8::from_array([1, 1, 1, 1, 1, 1, 1, 1]);
6973

7074
static mut PC_TABLES: Vec<&'static [PcTableEntry]> = Vec::new();
7175

76+
/// Type for the PC guard hook
77+
pub type PcGuardHook = unsafe extern "C" fn(*mut u32);
78+
79+
/// Type for the target PC guard hook (with PC)
80+
#[cfg(feature = "sancov_pcguard_dump_cov")]
81+
pub type TargetPcGuardHook = unsafe extern "C" fn(*mut u32, usize);
82+
83+
#[cfg(feature = "sancov_pcguard_dump_cov")]
84+
unsafe extern "C" fn nop_target_pc_guard(_guard: *mut u32, _pc: usize) {}
85+
86+
/// The global hook for `__libafl_targets_trace_pc_guard`
87+
#[cfg(feature = "sancov_pcguard_dump_cov")]
88+
pub static LIBAFL_TARGETS_TRACE_PC_GUARD_HOOK: AtomicPtr<c_void> =
89+
AtomicPtr::new(nop_target_pc_guard as *mut c_void);
90+
7291
use alloc::vec::Vec;
7392
#[cfg(any(
7493
feature = "sancov_ngram4",
@@ -205,14 +224,74 @@ unsafe extern "C" {
205224
pub static mut __afl_prev_ctx: u32;
206225
}
207226

208-
/// Callback for sancov `pc_guard` - usually called by `llvm` on each block or edge.
227+
#[cfg(feature = "sancov_pcguard_dump_cov")]
228+
static COVERED_PCS: Mutex<Option<HashSet<usize>>> = Mutex::new(None);
229+
230+
#[cfg(feature = "sancov_pcguard_dump_cov")]
231+
/// Dump the covered lines
209232
///
210-
/// # Safety
211-
/// Dereferences `guard`, reads the position from there, then dereferences the [`EDGES_MAP`] at that position.
212-
/// Should usually not be called directly.
213-
#[unsafe(no_mangle)]
214-
#[allow(unused_assignments)] // cfg dependent
215-
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) {
233+
/// # Arguments
234+
///
235+
/// * `clear` - Whether to clear the covered lines
236+
///
237+
/// # Returns
238+
///
239+
/// * `HashMap<usize, String>` - The covered lines, location and symbol
240+
///
241+
/// # Example
242+
///
243+
/// ```
244+
/// # use libafl_targets::sancov_pcguard::dump_covered_lines;
245+
///
246+
/// let map = dump_covered_lines(true);
247+
/// for (pc, sym) in map {
248+
/// println!("PC: {:x} -> {}", pc, sym);
249+
/// }
250+
/// ```
251+
pub fn dump_covered_lines(clear: bool) -> HashMap<usize, String> {
252+
let mut res = HashMap::new();
253+
if let Ok(mut guard) = COVERED_PCS.lock() {
254+
if let Some(set) = guard.as_mut() {
255+
for &pc in set.iter() {
256+
let mut symbol_str = String::new();
257+
backtrace::resolve(pc as *mut _, |symbol| {
258+
if let Some(name) = symbol.name() {
259+
symbol_str.push_str(&format!("{}", name));
260+
}
261+
if let Some(filename) = symbol.filename() {
262+
symbol_str.push_str(&format!(" at {:?}", filename));
263+
}
264+
if let Some(lineno) = symbol.lineno() {
265+
symbol_str.push_str(&format!(":{}", lineno));
266+
}
267+
});
268+
res.insert(pc, symbol_str);
269+
}
270+
if clear {
271+
set.clear();
272+
}
273+
}
274+
}
275+
res
276+
}
277+
278+
/// Enable coverage collection
279+
pub fn libafl_targets_enable_coverage_collection() {
280+
#[cfg(feature = "sancov_pcguard_dump_cov")]
281+
LIBAFL_TARGETS_TRACE_PC_GUARD_HOOK.store(
282+
__libafl_targets_trace_pc_guard_impl as *mut c_void,
283+
Ordering::Release,
284+
);
285+
}
286+
287+
/// Disable coverage collection
288+
pub fn libafl_targets_disable_coverage_collection() {
289+
#[cfg(feature = "sancov_pcguard_dump_cov")]
290+
LIBAFL_TARGETS_TRACE_PC_GUARD_HOOK.store(nop_target_pc_guard as *mut c_void, Ordering::Release);
291+
}
292+
293+
#[inline(always)]
294+
unsafe fn handle_pc_guard_inner(guard: *mut u32) {
216295
unsafe {
217296
#[allow(unused_variables, unused_mut)] // cfg dependent
218297
let mut pos = *guard as usize;
@@ -260,6 +339,54 @@ pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) {
260339
}
261340
}
262341

342+
/// Callback for sancov `pc_guard` - usually called by `llvm` on each block or edge.
343+
///
344+
/// # Safety
345+
/// Dereferences `guard`, reads the position from there, then dereferences the [`EDGES_MAP`] at that position.
346+
/// Should usually not be called directly.
347+
#[unsafe(no_mangle)]
348+
#[allow(unused_assignments)] // cfg dependent
349+
#[cfg(not(feature = "sancov_pcguard_dump_cov"))]
350+
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) {
351+
unsafe {
352+
handle_pc_guard_inner(guard);
353+
}
354+
}
355+
356+
#[cfg(not(feature = "sancov_pcguard_dump_cov"))]
357+
unsafe extern "C" fn __sanitizer_cov_trace_pc_guard_impl(guard: *mut u32) {
358+
unsafe {
359+
handle_pc_guard_inner(guard);
360+
}
361+
}
362+
363+
/// The C shim for `__sanitizer_cov_trace_pc_guard`
364+
#[unsafe(no_mangle)]
365+
#[allow(unused_assignments)] // cfg dependent
366+
#[cfg(feature = "sancov_pcguard_dump_cov")]
367+
pub unsafe extern "C" fn __libafl_targets_trace_pc_guard(guard: *mut u32, pc: usize) {
368+
unsafe {
369+
let hook_ptr = LIBAFL_TARGETS_TRACE_PC_GUARD_HOOK.load(Ordering::Acquire);
370+
let hook: TargetPcGuardHook = core::mem::transmute(hook_ptr);
371+
hook(guard, pc);
372+
}
373+
}
374+
375+
#[cfg(feature = "sancov_pcguard_dump_cov")]
376+
unsafe extern "C" fn __libafl_targets_trace_pc_guard_impl(guard: *mut u32, pc: usize) {
377+
unsafe {
378+
if let Ok(mut guard) = COVERED_PCS.lock() {
379+
if guard.is_none() {
380+
*guard = Some(HashSet::new());
381+
}
382+
if let Some(set) = guard.as_mut() {
383+
set.insert(pc);
384+
}
385+
}
386+
handle_pc_guard_inner(guard);
387+
}
388+
}
389+
263390
/// Initialize the sancov `pc_guard` - usually called by `llvm`.
264391
///
265392
/// # Safety
@@ -272,10 +399,12 @@ pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard_init(mut start: *mut u32
272399
EDGES_MAP_PTR = &raw mut EDGES_MAP as *mut u8;
273400
}
274401

402+
#[cfg(feature = "coverage")]
275403
if core::ptr::eq(start, stop) || *start != 0 {
276404
return;
277405
}
278406

407+
#[cfg(feature = "coverage")]
279408
while start < stop {
280409
*start = MAX_EDGES_FOUND as u32;
281410
start = start.offset(1);
@@ -355,3 +484,28 @@ pub fn sanitizer_cov_pc_table<'a>() -> impl Iterator<Item = &'a [PcTableEntry]>
355484
pc_tables.iter().copied()
356485
}
357486
}
487+
488+
#[cfg(test)]
489+
#[cfg(feature = "sancov_pcguard_dump_cov")]
490+
mod tests {
491+
use super::*;
492+
493+
#[test]
494+
fn test_dump_cov() {
495+
unsafe extern "C" {
496+
fn __sanitizer_cov_trace_pc_guard(guard: *mut u32);
497+
}
498+
// Simulate a call to __sanitizer_cov_trace_pc_guard
499+
let mut guard = 0;
500+
unsafe {
501+
__sanitizer_cov_trace_pc_guard(&mut guard);
502+
}
503+
504+
let map = dump_covered_lines(false);
505+
assert!(!map.is_empty());
506+
for (pc, sym) in map {
507+
println!("PC: {:x} -> {}", pc, sym);
508+
assert!(!sym.is_empty());
509+
}
510+
}
511+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
libpng-*
22
fuzzer
3+
fuzzbench

fuzzers/inprocess/fuzzbench_text/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ edition = "2021"
1111
default = ["std"]
1212
std = []
1313
no_link_main = ["libafl_targets/libfuzzer_no_link_main"]
14+
dump_cov = ["libafl_targets/sancov_pcguard_dump_cov"]
1415

1516
[profile.release]
1617
lto = true

0 commit comments

Comments
 (0)