Fix drcov path parsing (#2884)

* fix drcov path parsing

* refactoring of drcov tool

* add the possibility to sort addresses in drcov tools

* more aggressive clippy. it now catches more warnings as errors than before

* reduce the number of unfixable warnings displayed.
This commit is contained in:
Romain Malmain 2025-01-24 18:21:51 +01:00 committed by GitHub
parent c5b7c7c235
commit 4083f0ba73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 419 additions and 276 deletions

View File

@ -126,12 +126,15 @@ z3 = "0.12.1"
[workspace.lints.rust]
# Deny
warnings = { level = "deny", priority = -1 }
# Forbid
unexpected_cfgs = "forbid"
# Allow
incomplete_features = "allow"
ambiguous_glob_reexports = "allow"
# ambiguous_glob_reexports = "allow"
[workspace.lints.clippy]
@ -142,9 +145,10 @@ cargo_common_metadata = "deny"
# Warn
cargo = { level = "warn", priority = -1 }
negative_feature_names = "warn"
# Allow
negative_feature_names = "allow" # TODO: turn into 'warn' when working
multiple_crate_versions = "allow" # TODO: turn into `warn` when working
unreadable_literal = "allow"
type_repetition_in_bounds = "allow"
missing_errors_doc = "allow"

View File

@ -30,5 +30,7 @@ pyo3-build-config = { workspace = true }
name = "pylibafl"
crate-type = ["cdylib"]
[profile.dev]
panic = "abort"
# TODO: find a way to fix this when a solution is found
# https://github.com/rust-lang/cargo/issues/9330
# [profile.dev]
# panic = "abort"

View File

@ -244,7 +244,7 @@ windows_alias = "unsupported"
command = "${TARGET_DIR}/${PROFILE_DIR}/qemu_coverage-${CARGO_MAKE_PROFILE}"
args = [
"--coverage-path",
"${TARGET_DIR}/drcov.log",
"${TARGET_DIR}/cov.drcov",
"--input-dir",
"./corpus",
"--",
@ -294,11 +294,11 @@ script = '''
cargo make ${FEATURE} || exit 1
cargo run --manifest-path ../../../utils/drcov_utils/Cargo.toml --bin drcov_merge -- \
-i ${TARGET_DIR}/drcov-000.log ${TARGET_DIR}/drcov-001.log ${TARGET_DIR}/drcov-002.log ${TARGET_DIR}/drcov-003.log \
--output ${TARGET_DIR}/drcov-merged.log || exit 1
-i ${TARGET_DIR}/cov-000.drcov ${TARGET_DIR}/cov-001.drcov ${TARGET_DIR}/cov-002.drcov ${TARGET_DIR}/cov-003.drcov \
--output ${TARGET_DIR}/cov-merged.drcov || exit 1
TMP=$(cargo run --manifest-path ../../../utils/drcov_utils/Cargo.toml --bin drcov_dump_addrs -- \
-i ${TARGET_DIR}/drcov-merged.log | wc -l || exit 1)
-i ${TARGET_DIR}/cov-merged.drcov -a | wc -l || exit 1)
NB_BLOCKS=$((TMP - 1))

View File

@ -135,10 +135,7 @@ pub fn fuzz() {
cov_path.set_file_name(format!("{coverage_name}-{core:03}.{coverage_extension}"));
let emulator_modules = tuple_list!(
DrCovModule::builder()
.filename(cov_path.clone())
.full_trace(false)
.build(),
DrCovModule::builder().filename(cov_path.clone()).build(),
SnapshotModule::new()
);

View File

@ -86,6 +86,8 @@ pub use libafl_bolts::{nonzero, Error};
/// The purpose of this module is to alleviate imports of many components by adding a glob import.
#[cfg(feature = "prelude")]
pub mod prelude {
#![expect(ambiguous_glob_reexports)]
pub use super::{
corpus::*, events::*, executors::*, feedbacks::*, fuzzer::*, generators::*, inputs::*,
monitors::*, mutators::*, observers::*, schedulers::*, stages::*, state::*, *,

View File

@ -678,6 +678,8 @@ impl From<pyo3::PyErr> for Error {
/// The purpose of this module is to alleviate imports of many components by adding a glob import.
#[cfg(feature = "prelude")]
pub mod prelude {
#![allow(ambiguous_glob_reexports)]
pub use super::{bolts_prelude::*, *};
}

View File

@ -3,15 +3,12 @@
#[cfg(feature = "alloc")]
use alloc::{borrow::Cow, vec::Vec};
#[cfg(feature = "alloc")]
use core::ops::{Deref, DerefMut};
use core::{
any::{type_name, TypeId},
cell::Cell,
any::type_name,
fmt::{Debug, Formatter},
marker::PhantomData,
mem::transmute,
ops::{Index, IndexMut},
ops::{Deref, DerefMut, Index, IndexMut},
};
use core::{any::TypeId, cell::Cell, marker::PhantomData, mem::transmute};
#[cfg(feature = "alloc")]
use serde::{Deserialize, Serialize};

View File

@ -1,11 +1,17 @@
use std::{
env,
fs::File,
io::Write,
path::{Path, PathBuf},
process::Command,
str,
};
#[cfg(any(
target_vendor = "apple",
feature = "ddg-instr",
feature = "function-logging",
feature = "cmplog-routines",
feature = "autotokens",
feature = "coverage-accounting",
feature = "cmplog-instructions",
feature = "ctx",
feature = "dump-cfg",
feature = "profiling",
))]
use std::path::PathBuf;
use std::{env, fs::File, io::Write, path::Path, process::Command, str};
#[cfg(target_vendor = "apple")]
use glob::glob;
@ -20,6 +26,17 @@ const LLVM_VERSION_MAX: u32 = 33;
const LLVM_VERSION_MIN: u32 = 6;
/// Get the extension for a shared object
#[cfg(any(
feature = "ddg-instr",
feature = "function-logging",
feature = "cmplog-routines",
feature = "autotokens",
feature = "coverage-accounting",
feature = "cmplog-instructions",
feature = "ctx",
feature = "dump-cfg",
feature = "profiling",
))]
fn dll_extension<'a>() -> &'a str {
if let Ok(vendor) = env::var("CARGO_CFG_TARGET_VENDOR") {
if vendor == "apple" {
@ -143,6 +160,17 @@ fn find_llvm_version() -> Option<i32> {
None
}
#[cfg(any(
feature = "ddg-instr",
feature = "function-logging",
feature = "cmplog-routines",
feature = "autotokens",
feature = "coverage-accounting",
feature = "cmplog-instructions",
feature = "ctx",
feature = "dump-cfg",
feature = "profiling",
))]
#[expect(clippy::too_many_arguments)]
fn build_pass(
bindir_path: &Path,

View File

@ -40,6 +40,8 @@ pub mod cpp_runtime {
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused_attributes)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

View File

@ -1,4 +1,5 @@
//! Tracing of expressions in a serialized form.
#![allow(no_mangle_generic_items)]
pub use libafl::observers::concolic::serialization_format::StdShMemMessageFileWriter;
use libafl::observers::concolic::SymExpr;

View File

@ -39,9 +39,9 @@ use arbitrary_int::u4;
use bitbybit::bitfield;
#[cfg(target_os = "linux")]
use caps::{CapSet, Capability};
use libafl_bolts::Error;
#[cfg(target_os = "linux")]
use libafl_bolts::ownedref::OwnedRefMut;
use libafl_bolts::{hash_64_fast, Error};
use libafl_bolts::{hash_64_fast, ownedref::OwnedRefMut};
use libipt::PtError;
#[cfg(target_os = "linux")]
use libipt::{

View File

@ -102,36 +102,39 @@ fn main() -> Result<(), Box<dyn Error>> {
{
todo!("copy all the source files"); // we don't support libafl_libfuzzer for others rn
}
let mut template: toml::Value =
toml::from_str(&fs::read_to_string("runtime/Cargo.toml.template")?)?;
let toml::Value::Table(root) = &mut template else {
unreachable!("Invalid Cargo.toml");
};
root.insert(
"workspace".to_string(),
toml::Value::Table(toml::Table::new()),
);
let Some(toml::Value::Table(deps)) = root.get_mut("dependencies") else {
unreachable!("Invalid Cargo.toml");
};
let version = env!("CARGO_PKG_VERSION");
for (_name, spec) in deps {
if let toml::Value::Table(spec) = spec {
// replace all path deps with version deps
if spec.remove("path").is_some() {
spec.insert(
"version".to_string(),
toml::Value::String(version.to_string()),
);
#[cfg(unix)]
{
let mut template: toml::Value =
toml::from_str(&fs::read_to_string("runtime/Cargo.toml.template")?)?;
let toml::Value::Table(root) = &mut template else {
unreachable!("Invalid Cargo.toml");
};
root.insert(
"workspace".to_string(),
toml::Value::Table(toml::Table::new()),
);
let Some(toml::Value::Table(deps)) = root.get_mut("dependencies") else {
unreachable!("Invalid Cargo.toml");
};
let version = env!("CARGO_PKG_VERSION");
for (_name, spec) in deps {
if let toml::Value::Table(spec) = spec {
// replace all path deps with version deps
if spec.remove("path").is_some() {
spec.insert(
"version".to_string(),
toml::Value::String(version.to_string()),
);
}
}
}
let serialized = toml::to_string(&template)?;
fs::write(custom_lib_dir.join("Cargo.toml"), serialized)?;
// build in this filled out template
command.current_dir(custom_lib_dir);
}
let serialized = toml::to_string(&template)?;
fs::write(custom_lib_dir.join("Cargo.toml"), serialized)?;
// build in this filled out template
command.current_dir(custom_lib_dir);
}
assert!(

View File

@ -115,7 +115,9 @@ pub fn build() {
let cross_cc = if cfg!(feature = "usermode") && (qemu_asan || qemu_asan_guest) {
// TODO try to autodetect a cross compiler with the arch name (e.g. aarch64-linux-gnu-gcc)
let cross_cc = env::var("CROSS_CC").unwrap_or_else(|_| {
println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong if the selected cpu target ({cpu_target}) is not the host arch ({}))", env::consts::ARCH);
if cpu_target != env::consts::ARCH {
println!("cargo:warning=CROSS_CC is not set, default to cc (things can go wrong since the selected cpu target ({cpu_target}) is different from the host arch ({}))", env::consts::ARCH);
}
"cc".to_owned()
});
println!("cargo:rerun-if-env-changed=CROSS_CC");

View File

@ -133,7 +133,7 @@ impl crate::ArchExtras for crate::CPU {
&self,
conv: CallingConvention,
idx: i32,
val: T,
_val: T,
) -> Result<(), QemuRWError>
where
T: Into<GuestReg>,

View File

@ -302,7 +302,6 @@ pub type QemuInProcessForkExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, SP, Z
StatefulInProcessForkExecutor<'a, EM, Emulator<C, CM, ED, ET, I, S, SM>, H, I, OT, S, SP, Z>;
#[cfg(feature = "fork")]
#[expect(clippy::type_complexity)]
pub struct QemuForkExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, SP, Z> {
inner: QemuInProcessForkExecutor<'a, C, CM, ED, EM, ET, H, I, OT, S, SM, SP, Z>,
}

View File

@ -1,3 +1,8 @@
#[cfg(feature = "usermode")]
use std::{
cmp::{max, min},
ops::Range,
};
use std::{path::PathBuf, sync::Mutex};
use hashbrown::{hash_map::Entry, HashMap};
@ -19,8 +24,13 @@ use crate::{
Qemu,
};
/// Trace of `block_id`s met at runtime
static DRCOV_IDS: Mutex<Option<Vec<u64>>> = Mutex::new(None);
///Map of `pc` -> `block_id`
static DRCOV_MAP: Mutex<Option<HashMap<GuestAddr, u64>>> = Mutex::new(None);
/// Map of `pc` -> `block_len`
static DRCOV_LENGTHS: Mutex<Option<HashMap<GuestAddr, GuestUsize>>> = Mutex::new(None);
#[cfg_attr(
@ -46,7 +56,7 @@ pub struct DrCovModuleBuilder<F> {
filter: Option<F>,
module_mapping: Option<RangeMap<u64, (u16, String)>>,
filename: Option<PathBuf>,
full_trace: Option<bool>,
full_trace: bool,
}
impl<F> DrCovModuleBuilder<F>
@ -58,7 +68,7 @@ where
self.filter.unwrap(),
self.filename.unwrap(),
self.module_mapping,
self.full_trace.unwrap(),
self.full_trace,
)
}
@ -97,7 +107,7 @@ where
filter: self.filter,
module_mapping: self.module_mapping,
filename: self.filename,
full_trace: Some(full_trace),
full_trace,
}
}
}
@ -111,20 +121,243 @@ pub struct DrCovModule<F> {
drcov_len: usize,
}
pub fn gen_unique_block_ids<ET, F, I, S>(
_qemu: Qemu,
emulator_modules: &mut EmulatorModules<ET, I, S>,
state: Option<&mut S>,
pc: GuestAddr,
) -> Option<u64>
where
ET: EmulatorModuleTuple<I, S>,
F: AddressFilter,
I: Unpin,
S: Unpin + HasMetadata,
{
let drcov_module = emulator_modules.get::<DrCovModule<F>>().unwrap();
if !drcov_module.must_instrument(pc) {
return None;
}
let state = state.expect("The gen_unique_block_ids hook works only for in-process fuzzing. Is the Executor initialized?");
if state
.metadata_map_mut()
.get_mut::<DrCovMetadata>()
.is_none()
{
state.add_metadata(DrCovMetadata::new());
}
let meta = state.metadata_map_mut().get_mut::<DrCovMetadata>().unwrap();
match DRCOV_MAP.lock().unwrap().as_mut().unwrap().entry(pc) {
Entry::Occupied(entry) => {
let id = *entry.get();
if drcov_module.full_trace {
Some(id)
} else {
None
}
}
Entry::Vacant(entry) => {
let id = meta.current_id;
entry.insert(id);
meta.current_id = id + 1;
if drcov_module.full_trace {
// GuestAddress is u32 for 32 bit guests
#[expect(clippy::unnecessary_cast)]
Some(id as u64)
} else {
None
}
}
}
}
#[allow(clippy::needless_pass_by_value)] // no longer a problem with nightly
pub fn gen_block_lengths<ET, F, I, S>(
_qemu: Qemu,
emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: Option<&mut S>,
pc: GuestAddr,
block_length: GuestUsize,
) where
ET: EmulatorModuleTuple<I, S>,
F: AddressFilter,
I: Unpin,
S: Unpin + HasMetadata,
{
let drcov_module = emulator_modules.get::<DrCovModule<F>>().unwrap();
if !drcov_module.must_instrument(pc) {
return;
}
DRCOV_LENGTHS
.lock()
.unwrap()
.as_mut()
.unwrap()
.insert(pc, block_length);
}
#[allow(clippy::needless_pass_by_value)] // no longer a problem with nightly
pub fn exec_trace_block<ET, F, I, S>(
_qemu: Qemu,
emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: Option<&mut S>,
id: u64,
) where
ET: EmulatorModuleTuple<I, S>,
F: AddressFilter,
I: Unpin,
S: Unpin + HasMetadata,
{
if emulator_modules.get::<DrCovModule<F>>().unwrap().full_trace {
DRCOV_IDS.lock().unwrap().as_mut().unwrap().push(id);
}
}
impl<F, I, S> EmulatorModule<I, S> for DrCovModule<F>
where
F: AddressFilter,
I: Unpin,
S: Unpin + HasMetadata,
{
#[cfg(feature = "usermode")]
fn first_exec<ET>(
&mut self,
qemu: Qemu,
emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: &mut S,
) where
ET: EmulatorModuleTuple<I, S>,
{
if self.full_trace {
emulator_modules.blocks(
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
Hook::Function(gen_block_lengths::<ET, F, I, S>),
Hook::Function(exec_trace_block::<ET, F, I, S>),
);
} else {
emulator_modules.blocks(
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
Hook::Function(gen_block_lengths::<ET, F, I, S>),
Hook::Empty,
);
};
if self.module_mapping.is_none() {
log::info!("Auto-filling module mapping for DrCov module from QEMU mapping.");
let mut module_range_map: RangeMap<u64, (u16, String)> = RangeMap::new();
let mut module_path_map: HashMap<String, (Range<u64>, u16)> = HashMap::default();
// We are building a mapping of
// Path -> pc_start..pc_end, where pc_start is the smallest pc for the path and pc_end the biggest pc.
let mut i = 0;
for ((map_start, map_end), map_path) in qemu.mappings().filter_map(|m| {
m.path().filter(|p| !p.is_empty()).map(|p| {
(
(
u64::try_from(m.start()).unwrap(),
u64::try_from(m.end()).unwrap(),
),
p.to_string(),
)
})
}) {
// Check if path is already present
match module_path_map.entry(map_path) {
Entry::Occupied(mut entry) => {
// If present, try to widen the range if necessary
let (range, _) = entry.get_mut();
range.start = min(range.start, map_start);
range.end = max(range.end, map_end);
}
Entry::Vacant(entry) => {
entry.insert((map_start..map_end, i));
i += 1;
}
}
// module_mapping.insert(r, (i as u16, p));
}
// Now, we can reorder the data by building a RangeMap and consume the old map.
for (path, (range, id)) in module_path_map {
module_range_map.insert(range, (id, path));
}
self.module_mapping = Some(module_range_map);
} else {
log::info!("Using user-provided module mapping for DrCov module.");
}
}
#[cfg(feature = "systemmode")]
fn first_exec<ET>(
&mut self,
_qemu: Qemu,
emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: &mut S,
) where
ET: EmulatorModuleTuple<I, S>,
{
assert!(
self.module_mapping.is_some(),
"DrCov should have a module mapping already set."
);
if self.full_trace {
emulator_modules.blocks(
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
Hook::Function(gen_block_lengths::<ET, F, I, S>),
Hook::Function(exec_trace_block::<ET, F, I, S>),
);
} else {
emulator_modules.blocks(
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
Hook::Function(gen_block_lengths::<ET, F, I, S>),
Hook::Empty,
);
};
}
fn post_exec<OT, ET>(
&mut self,
_qemu: Qemu,
_emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: &mut S,
_input: &I,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<I, S>,
ET: EmulatorModuleTuple<I, S>,
{
self.flush();
}
unsafe fn on_crash(&mut self) {
self.flush();
}
unsafe fn on_timeout(&mut self) {
self.flush();
}
}
impl DrCovModule<NopAddressFilter> {
#[must_use]
pub fn builder() -> DrCovModuleBuilder<NopAddressFilter> {
DrCovModuleBuilder {
filter: Some(NopAddressFilter),
module_mapping: None,
full_trace: None,
full_trace: false,
filename: None,
}
}
}
impl<F> DrCovModule<F> {
#[must_use]
#[expect(clippy::let_underscore_untyped)]
pub fn new(
filter: F,
filename: PathBuf,
@ -132,10 +365,12 @@ impl<F> DrCovModule<F> {
full_trace: bool,
) -> Self {
if full_trace {
let _ = DRCOV_IDS.lock().unwrap().insert(vec![]);
*DRCOV_IDS.lock().unwrap() = Some(vec![]);
}
let _ = DRCOV_MAP.lock().unwrap().insert(HashMap::new());
let _ = DRCOV_LENGTHS.lock().unwrap().insert(HashMap::new());
*DRCOV_MAP.lock().unwrap() = Some(HashMap::new());
*DRCOV_LENGTHS.lock().unwrap() = Some(HashMap::new());
Self {
filter,
module_mapping,
@ -145,7 +380,7 @@ impl<F> DrCovModule<F> {
}
}
pub fn write(&mut self) {
pub fn flush(&mut self) {
let lengths_opt = DRCOV_LENGTHS.lock().unwrap();
let lengths = lengths_opt.as_ref().unwrap();
if self.full_trace {
@ -225,8 +460,9 @@ impl<F> DrCovModule<F> {
match lengths.get(pc) {
Some(block_length) => {
drcov_vec.push(DrCovBasicBlock::new(
*pc as u64,
*pc as u64 + *block_length as u64,
u64::try_from(*pc).unwrap(),
u64::try_from(*pc).unwrap() as u64
+ u64::try_from(*block_length).unwrap(),
));
}
None => {
@ -259,96 +495,6 @@ where
}
}
impl<F, I, S> EmulatorModule<I, S> for DrCovModule<F>
where
F: AddressFilter,
I: Unpin,
S: Unpin + HasMetadata,
{
fn post_qemu_init<ET>(&mut self, _qemu: Qemu, _emulator_modules: &mut EmulatorModules<ET, I, S>)
where
ET: EmulatorModuleTuple<I, S>,
{
}
#[cfg(feature = "usermode")]
fn first_exec<ET>(
&mut self,
qemu: Qemu,
emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: &mut S,
) where
ET: EmulatorModuleTuple<I, S>,
{
emulator_modules.blocks(
Hook::Function(gen_unique_block_ids::<ET, F, I, S>),
Hook::Function(gen_block_lengths::<ET, F, I, S>),
Hook::Function(exec_trace_block::<ET, F, I, S>),
);
if self.module_mapping.is_none() {
log::info!("Auto-filling module mapping for DrCov module from QEMU mapping.");
let mut module_mapping: RangeMap<u64, (u16, String)> = RangeMap::new();
#[expect(clippy::unnecessary_cast)] // for GuestAddr -> u64
for (i, (r, p)) in qemu
.mappings()
.filter_map(|m| {
m.path()
.map(|p| ((m.start() as u64)..(m.end() as u64), p.to_string()))
.filter(|(_, p)| !p.is_empty())
})
.enumerate()
{
module_mapping.insert(r, (i as u16, p));
}
self.module_mapping = Some(module_mapping);
} else {
log::info!("Using user-provided module mapping for DrCov module.");
}
}
#[cfg(feature = "systemmode")]
fn first_exec<ET>(
&mut self,
_qemu: Qemu,
_emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: &mut S,
) where
ET: EmulatorModuleTuple<I, S>,
{
assert!(
self.module_mapping.is_some(),
"DrCov should have a module mapping already set."
);
}
fn post_exec<OT, ET>(
&mut self,
_qemu: Qemu,
_emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: &mut S,
_input: &I,
_observers: &mut OT,
_exit_kind: &mut ExitKind,
) where
OT: ObserversTuple<I, S>,
ET: EmulatorModuleTuple<I, S>,
{
self.write();
}
unsafe fn on_crash(&mut self) {
self.write();
}
unsafe fn on_timeout(&mut self) {
self.write();
}
}
impl<F> HasAddressFilter for DrCovModule<F>
where
F: AddressFilter,
@ -375,96 +521,3 @@ where
unsafe { (&raw mut NOP_PAGE_FILTER).as_mut().unwrap().get_mut() }
}
}
pub fn gen_unique_block_ids<ET, F, I, S>(
_qemu: Qemu,
emulator_modules: &mut EmulatorModules<ET, I, S>,
state: Option<&mut S>,
pc: GuestAddr,
) -> Option<u64>
where
ET: EmulatorModuleTuple<I, S>,
F: AddressFilter,
I: Unpin,
S: Unpin + HasMetadata,
{
let drcov_module = emulator_modules.get::<DrCovModule<F>>().unwrap();
if !drcov_module.must_instrument(pc) {
return None;
}
let state = state.expect("The gen_unique_block_ids hook works only for in-process fuzzing. Is the Executor initialized?");
if state
.metadata_map_mut()
.get_mut::<DrCovMetadata>()
.is_none()
{
state.add_metadata(DrCovMetadata::new());
}
let meta = state.metadata_map_mut().get_mut::<DrCovMetadata>().unwrap();
match DRCOV_MAP.lock().unwrap().as_mut().unwrap().entry(pc) {
Entry::Occupied(e) => {
let id = *e.get();
if drcov_module.full_trace {
Some(id)
} else {
None
}
}
Entry::Vacant(e) => {
let id = meta.current_id;
e.insert(id);
meta.current_id = id + 1;
if drcov_module.full_trace {
// GuestAddress is u32 for 32 bit guests
#[expect(clippy::unnecessary_cast)]
Some(id as u64)
} else {
None
}
}
}
}
#[allow(clippy::needless_pass_by_value)] // no longer a problem with nightly
pub fn gen_block_lengths<ET, F, I, S>(
_qemu: Qemu,
emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: Option<&mut S>,
pc: GuestAddr,
block_length: GuestUsize,
) where
ET: EmulatorModuleTuple<I, S>,
F: AddressFilter,
I: Unpin,
S: Unpin + HasMetadata,
{
let drcov_module = emulator_modules.get::<DrCovModule<F>>().unwrap();
if !drcov_module.must_instrument(pc) {
return;
}
DRCOV_LENGTHS
.lock()
.unwrap()
.as_mut()
.unwrap()
.insert(pc, block_length);
}
#[allow(clippy::needless_pass_by_value)] // no longer a problem with nightly
pub fn exec_trace_block<ET, F, I, S>(
_qemu: Qemu,
emulator_modules: &mut EmulatorModules<ET, I, S>,
_state: Option<&mut S>,
id: u64,
) where
ET: EmulatorModuleTuple<I, S>,
F: AddressFilter,
I: Unpin,
S: Unpin + HasMetadata,
{
if emulator_modules.get::<DrCovModule<F>>().unwrap().full_trace {
DRCOV_IDS.lock().unwrap().as_mut().unwrap().push(id);
}
}

View File

@ -1,10 +1,7 @@
use core::{fmt::Debug, ops::Range};
use core::fmt::Debug;
use libafl::{executors::ExitKind, observers::ObserversTuple};
use libafl_bolts::tuples::{MatchFirstType, SplitBorrowExtractFirstType};
use libafl_qemu_sys::GuestAddr;
#[cfg(feature = "systemmode")]
use libafl_qemu_sys::GuestPhysAddr;
use crate::{
emu::EmulatorModules,
@ -15,6 +12,7 @@ use crate::{
#[cfg(feature = "usermode")]
pub mod usermode;
#[cfg(feature = "usermode")]
#[cfg_attr(feature = "hexagon", allow(unused_imports))]
pub use usermode::*;
#[cfg(feature = "systemmode")]

View File

@ -34,6 +34,7 @@ use crate::{
#[cfg(cpu_target = "hexagon")]
/// Hexagon syscalls are not currently supported by the `syscalls` crate, so we just paste this here for now.
/// <https://github.com/qemu/qemu/blob/11be70677c70fdccd452a3233653949b79e97908/linux-user/hexagon/syscall_nr.h#L230>
#[expect(non_upper_case_globals)]
const SYS_execve: u8 = 221;
/// Parses `injections.yaml`

View File

@ -1,5 +1,5 @@
#![allow(clippy::needless_pass_by_value)] // default compiler complains about Option<&mut T> otherwise, and this is used extensively.
use std::{cell::UnsafeCell, mem::MaybeUninit, sync::Mutex};
use std::{cell::UnsafeCell, mem::MaybeUninit, ops::Range, sync::Mutex};
use hashbrown::{HashMap, HashSet};
use libafl_qemu_sys::{GuestAddr, MmapPerms};
@ -25,7 +25,7 @@ use crate::{
modules::{
asan::AsanModule,
utils::filters::{HasAddressFilter, NopAddressFilter, NOP_ADDRESS_FILTER},
EmulatorModule, EmulatorModuleTuple, Range,
EmulatorModule, EmulatorModuleTuple,
},
qemu::{Hook, SyscallHookResult},
Qemu, SYS_brk, SYS_mprotect, SYS_mremap, SYS_munmap, SYS_pread64, SYS_read, SYS_readlinkat,

View File

@ -311,6 +311,7 @@ impl PageFilter for NopPageFilter {
}
#[cfg(feature = "usermode")]
#[allow(dead_code)]
pub(crate) static mut NOP_ADDRESS_FILTER: UnsafeCell<NopAddressFilter> =
UnsafeCell::new(NopAddressFilter);
#[cfg(feature = "systemmode")]

View File

@ -251,6 +251,19 @@ fn parse_hex_to_u64(str: &str) -> Result<u64, ParseIntError> {
u64::from_str_radix(&str[2..], 16)
}
fn parse_path(s: &str) -> PathBuf {
let s = s.trim();
// If first and last character is a quote, let's remove them
let s = if s.starts_with('\"') && s.ends_with('\"'){
&s[1..s.len() - 1]
} else {
s
};
PathBuf::from(s)
}
impl DrCovReader {
/// Parse a `drcov` file to memory.
pub fn read<P: AsRef<Path> + ?Sized>(file: &P) -> Result<Self, Error> {
@ -336,7 +349,7 @@ impl DrCovReader {
return Err(err("timestamp"));
};
let Some(path) = split.next().map(|s| PathBuf::from(s.trim())) else {
let Some(path) = split.next().map(parse_path) else {
return Err(err("path"));
};

View File

@ -46,7 +46,7 @@
#[macro_use]
extern crate std;
#[allow(unused_imports)] // for no-std
#[allow(unused_imports)]
#[macro_use]
extern crate alloc;

View File

@ -205,7 +205,7 @@ extern "C" {
#[no_mangle]
#[allow(unused_assignments)] // cfg dependent
pub unsafe extern "C" fn __sanitizer_cov_trace_pc_guard(guard: *mut u32) {
#[allow(unused_mut)] // cfg dependent
#[allow(unused_variables, unused_mut)] // cfg dependent
let mut pos = *guard as usize;
#[cfg(any(feature = "sancov_ngram4", feature = "sancov_ngram8"))]

View File

@ -59,7 +59,7 @@ for project in "${PROJECTS[@]}"; do
features="--features=clippy"
fi
if [ -d "$project" ]; then
run_clippy "$project" $features
run_clippy "$project" "$features"
else
echo "Warning: Directory $project does not exist. Skipping."
fi

View File

@ -9,6 +9,7 @@ categories = ["development-tools"]
keywords = ["fuzzing", "libafl", "drcov"]
[dependencies]
env_logger = "0.11.6"
libafl_targets = { workspace = true, default-features = true }
clap = { workspace = true, features = ["derive", "wrap_help"] }

View File

@ -1,6 +1,6 @@
use std::{
fs::{create_dir_all, File},
io::Write,
io::{Error, Write},
path::PathBuf,
};
@ -17,15 +17,25 @@ use libafl_targets::drcov::DrCovReader;
pub struct Opt {
#[arg(short, long, help = "DrCov traces to read", required = true)]
pub inputs: Vec<PathBuf>,
#[arg(
short,
long,
help = "Output folder to write address files to. If none is set, this will output all addresses to stdout."
)]
pub out_dir: Option<PathBuf>,
#[arg(short, long, help = "Print the metadata of the drcov file.")]
pub metadata: bool,
#[arg(short, long, help = "Dump the addresses.")]
pub addrs: bool,
#[arg(short, long, help = "Sort the addresses from smallest to biggest.")]
pub sort: bool,
}
fn main() {
fn main() -> Result<(), Error> {
let opts = Opt::parse();
if let Some(out_dir) = &opts.out_dir {
@ -45,7 +55,13 @@ fn main() {
continue;
};
if let Some(out_dir) = &opts.out_dir {
let mut blocks = drcov.basic_block_addresses_u64();
if opts.sort {
blocks.sort_unstable();
}
let mut writer: Box<dyn Write> = if let Some(out_dir) = &opts.out_dir {
// Write files to a directory
let out_file = out_dir.join(
input
@ -53,27 +69,47 @@ fn main() {
.expect("File without filename shouldn't exist"),
);
let Ok(mut file) = File::create_new(&out_file).map_err(|err| {
let Ok(file) = File::create_new(&out_file).map_err(|err| {
eprintln!("Could not create file {out_file:?} - continuing: {err:?}");
}) else {
continue;
};
println!("Dumping addresses from drcov file {input:?} to {out_file:?}");
println!("Dumping traces from drcov file {input:?} to {out_file:?}",);
for line in drcov.basic_block_addresses_u64() {
file.write_all(format!("{line:#x}\n").as_bytes())
.expect("Could not write to file");
}
Box::new(file)
} else {
// dump to stdout
println!("# Blocks covered in {input:?}:");
Box::new(std::io::stdout())
};
for line in drcov.basic_block_addresses_u64() {
println!("{line:#x}");
// dump to stdout
let modules = &drcov.module_entries;
if opts.metadata {
writeln!(writer, "# {} Modules:", modules.len())?;
for module in &drcov.module_entries {
writeln!(
writer,
"\t{} - [{:#020x}-{:#020x}] {}",
module.id,
module.base,
module.end,
module.path.display()
)?;
}
writeln!(writer, "# {} Blocks covered in {input:?}.", blocks.len())?;
println!();
if opts.addrs {
writeln!(writer)?;
}
}
if opts.addrs {
for line in blocks {
writeln!(writer, "{line:#x}")?;
}
}
}
Ok(())
}

View File

@ -24,6 +24,7 @@ pub struct Opt {
}
fn main() {
env_logger::init();
let opts = Opt::parse();
assert!(