From bbcc8649a56bbd0bea224a4e453fd25552786630 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 23 Jul 2025 20:45:53 +0200 Subject: [PATCH] poc --- crates/libafl_sugar/src/qemu.rs | 785 +++++++++++++++++--------------- 1 file changed, 419 insertions(+), 366 deletions(-) diff --git a/crates/libafl_sugar/src/qemu.rs b/crates/libafl_sugar/src/qemu.rs index 63b8442302..dd0caa1f1e 100644 --- a/crates/libafl_sugar/src/qemu.rs +++ b/crates/libafl_sugar/src/qemu.rs @@ -5,7 +5,7 @@ use core::{ time::Duration, }; use std::{fs, path::PathBuf}; - +use libafl_qemu::modules::SnapshotModule; use libafl::{ HasMetadata, corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus}, @@ -25,7 +25,7 @@ use libafl::{ }, observers::{CanTrack, HitcountsMapObserver, TimeObserver, VariableMapObserver}, schedulers::{IndexesLenTimeMinimizerScheduler, QueueScheduler}, - stages::{CalibrationStage, ShadowTracingStage, StdMutationalStage}, + stages::{ShadowTracingStage, StdMutationalStage}, state::{HasCorpus, StdState}, }; use libafl_bolts::{ @@ -37,7 +37,6 @@ use libafl_bolts::{ shmem::{ShMemProvider, StdShMemProvider}, tuples::{Merge, tuple_list}, }; -#[cfg(not(any(feature = "mips", feature = "hexagon")))] use libafl_qemu::modules::CmpLogModule; pub use libafl_qemu::qemu::Qemu; use libafl_qemu::{Emulator, QemuExecutor, modules::edges::StdEdgeCoverageModule}; @@ -46,94 +45,230 @@ use typed_builder::TypedBuilder; use crate::{CORPUS_CACHE_SIZE, DEFAULT_TIMEOUT_SECS}; -/// Sugar to create a `libfuzzer`-style fuzzer that uses -/// `QEMU`-based binary-only instrumentation -#[derive(TypedBuilder)] -pub struct QemuBytesCoverageSugar<'a, H> -where - H: FnMut(&[u8]), -{ - /// Laucher configuration (default is random) - #[builder(default = None, setter(strip_option))] - configuration: Option, - /// Timeout of the executor - #[builder(default = None)] - timeout: Option, - /// Input directories - input_dirs: &'a [PathBuf], - /// Output directory - output_dir: PathBuf, - /// Dictionary - #[builder(default = None)] - tokens_file: Option, - /// Flag if use `CmpLog` - #[builder(default = None)] - use_cmplog: Option, - /// The port the fuzzing nodes communicate over - /// This will spawn a server on this port, and connect to other brokers using this port. - #[builder(default = 1337_u16)] - broker_port: u16, - /// The list of cores to run on - cores: &'a Cores, - /// The `ip:port` address of another broker to connect our new broker to for multi-machine - /// clusters. - #[builder(default = None, setter(strip_option))] - remote_broker_addr: Option, - /// Bytes harness - #[builder(setter(strip_option))] - harness: Option, - /// Fuzz `iterations` number of times, instead of indefinitely; implies use of `fuzz_loop_for` - #[builder(default = None)] - iterations: Option, - /// Disable redirection of stdout to /dev/null on unix build targets - #[builder(default = None)] - enable_stdout: Option, +/// Macro to handle the repetitive fuzzing logic +/// This macro generates the complete fuzzing implementation for both cmplog and non-cmplog cases +macro_rules! implement_fuzzing { + ( + // Macro parameters + modules: $modules:expr, + use_shadow: $use_shadow:literal, + self: $self:expr, + qemu: $qemu:expr, + harness_bytes: $harness_bytes:expr, + edges_observer: $edges_observer:expr, + time_observer: $time_observer:expr, + cmplog_observer: $cmplog_observer:expr, + state: $state:expr, + mgr: $mgr:expr, + timeout: $timeout:expr, + map_feedback: $map_feedback:expr, + scheduler: $scheduler:expr, + feedback: $feedback:expr, + objective: $objective:expr + ) => {{ + + // Create executor - this is where the types differ + if $use_shadow { + // Create harness closure + let mut harness = + |_emulator: &mut Emulator<_, _, _, _, _, _, _>, _state: &mut _, input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + $harness_bytes(buf); + ExitKind::Ok + }; + // A fuzzer with feedbacks and a corpus scheduler + // Build emulator + let emulator = match $qemu { + QemuSugarParameter::QemuCli(qemu_cli) => Emulator::empty() + .qemu_parameters(qemu_cli.to_owned()) + .modules($modules) + .build() + .expect("Could not initialize Emulator"), + QemuSugarParameter::Qemu(qemu) => Emulator::empty() + .modules($modules) + .build_with_qemu(*qemu) + .expect("Could not initialize Emulator"), + }; + let mut fuzzer = StdFuzzer::new($scheduler, $feedback, $objective); + let base_executor = QemuExecutor::new( + emulator, + &mut harness, + tuple_list!($edges_observer, $time_observer), + &mut fuzzer, + &mut $state, + &mut $mgr, + $timeout, + )?; + let mut executor = ShadowExecutor::new(base_executor, tuple_list!($cmplog_observer)); + // Load initial inputs + load_initial_inputs!(executor, $self, fuzzer, $state, $mgr); + + // Setup and run stages for shadow executor + setup_fuzzing_stages!( + shadow, + executor, + $self, + fuzzer, + $state, + $mgr, + $map_feedback + ); + } else { + // Create harness closure + let mut harness = + |_emulator: &mut Emulator<_, _, _, _, _, _, _>, _state: &mut _, input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + $harness_bytes(buf); + ExitKind::Ok + }; + // A fuzzer with feedbacks and a corpus scheduler + // Build emulator + let emulator = match $qemu { + QemuSugarParameter::QemuCli(qemu_cli) => Emulator::empty() + .qemu_parameters(qemu_cli.to_owned()) + .modules($modules) + .build() + .expect("Could not initialize Emulator"), + QemuSugarParameter::Qemu(qemu) => Emulator::empty() + .modules($modules) + .build_with_qemu(*qemu) + .expect("Could not initialize Emulator"), + }; + let mut fuzzer = StdFuzzer::new($scheduler, $feedback, $objective); + let mut executor = QemuExecutor::new( + emulator, + &mut harness, + tuple_list!($edges_observer, $time_observer), + &mut fuzzer, + &mut $state, + &mut $mgr, + $timeout, + )?; + + // Load initial inputs + load_initial_inputs!(executor, $self, fuzzer, $state, $mgr); + + // Setup and run stages for regular executor + setup_fuzzing_stages!( + regular, + executor, + $self, + fuzzer, + $state, + $mgr, + $map_feedback + ); + } + }}; } -impl Debug for QemuBytesCoverageSugar<'_, H> -where - H: FnMut(&[u8]), -{ - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("QemuBytesCoverageSugar") - .field("configuration", &self.configuration) - .field("timeout", &self.timeout) - .field("input_dirs", &self.input_dirs) - .field("output_dir", &self.output_dir) - .field("tokens_file", &self.tokens_file) - .field("use_cmplog", &self.use_cmplog) - .field("broker_port", &self.broker_port) - .field("cores", &self.cores) - .field("remote_broker_addr", &self.remote_broker_addr) - .field( - "harness", - if self.harness.is_some() { - &"" - } else { - &"None" - }, - ) - .field("iterations", &self.iterations) - .field("enable_stdout", &self.enable_stdout) - .finish() - } +/// Macro to handle initial input loading (common for both paths) +macro_rules! load_initial_inputs { + ($executor:expr, $self:expr, $fuzzer:expr, $state:expr, $mgr:expr) => { + if $state.must_load_initial_inputs() { + if $self.input_dirs.is_empty() { + // Generator of printable bytearrays of max size 32 + let mut generator = RandBytesGenerator::new(nonzero!(32)); + + // Generate 8 initial inputs + $state + .generate_initial_inputs( + &mut $fuzzer, + &mut $executor, + &mut generator, + &mut $mgr, + 8, + ) + .expect("Failed to generate the initial corpus"); + log::info!( + "We imported {} inputs from the generator.", + $state.corpus().count() + ); + } else { + log::info!("Loading from {:?}", &$self.input_dirs); + // Load from disk + $state + .load_initial_inputs(&mut $fuzzer, &mut $executor, &mut $mgr, $self.input_dirs) + .unwrap_or_else(|_| { + panic!("Failed to load initial corpus at {:?}", &$self.input_dirs); + }); + log::info!("We imported {} inputs from disk.", $state.corpus().count()); + } + } + }; } -/// Enum to allow passing either qemu cli parameters or a running qemu instance -#[derive(Debug, Copy, Clone)] -pub enum QemuSugarParameter<'a> { - /// Argument list to pass to initialize Qemu - QemuCli(&'a [String]), - /// Already existing Qemu instance - Qemu(&'a Qemu), +/// Macro to setup fuzzing stages based on configuration +macro_rules! setup_fuzzing_stages { + (shadow, $executor:expr, $self:expr, $fuzzer:expr, $state:expr, $mgr:expr, $map_feedback:expr) => { + if $self.tokens_file.is_some() { + // Setup a basic mutator with tokens + let mutator = HavocScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + let mutational = StdMutationalStage::new(mutator); + + let tracing = ShadowTracingStage::new(); + let i2s = StdMutationalStage::new(HavocScheduledMutator::new(tuple_list!( + I2SRandReplace::new() + ))); + + let mut stages = tuple_list!(tracing, i2s, mutational); + run_fuzzing_loop!(stages, $executor, $self, $fuzzer, $state, $mgr); + } else { + // Setup a basic mutator without tokens + let mutator = HavocScheduledMutator::new(havoc_mutations()); + let mutational = StdMutationalStage::new(mutator); + + let tracing = ShadowTracingStage::new(); + let i2s = StdMutationalStage::new(HavocScheduledMutator::new(tuple_list!( + I2SRandReplace::new() + ))); + + let mut stages = tuple_list!(tracing, i2s, mutational); + run_fuzzing_loop!(stages, $executor, $self, $fuzzer, $state, $mgr); + } + }; + + (regular, $executor:expr, $self:expr, $fuzzer:expr, $state:expr, $mgr:expr, $map_feedback:expr) => { + if $self.tokens_file.is_some() { + // Setup a basic mutator with tokens + let mutator = HavocScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + let mutational = StdMutationalStage::new(mutator); + + // Regular executor stages + let mut stages = tuple_list!(mutational); + run_fuzzing_loop!(stages, $executor, $self, $fuzzer, $state, $mgr); + } else { + // Setup a basic mutator without tokens + let mutator = HavocScheduledMutator::new(havoc_mutations()); + let mutational = StdMutationalStage::new(mutator); + + // Regular executor stages + let mut stages = tuple_list!(mutational); + run_fuzzing_loop!(stages, $executor, $self, $fuzzer, $state, $mgr); + } + }; +} + +/// Macro to run the final fuzzing loop (common for both paths) +macro_rules! run_fuzzing_loop { + ($stages:expr, $executor:expr, $self:expr, $fuzzer:expr, $state:expr, $mgr:expr) => { + if let Some(iters) = $self.iterations { + $fuzzer.fuzz_loop_for(&mut $stages, &mut $executor, &mut $state, &mut $mgr, iters)?; + $mgr.on_restart(&mut $state)?; + std::process::exit(0); + } else { + $fuzzer.fuzz_loop(&mut $stages, &mut $executor, &mut $state, &mut $mgr)?; + } + }; } impl QemuBytesCoverageSugar<'_, H> where H: FnMut(&[u8]), { - /// Run the fuzzer - #[expect(clippy::too_many_lines)] + /// Run the fuzzer - now dramatically simplified thanks to macro deduplication pub fn run(&mut self, qemu: QemuSugarParameter) { let conf = match self.configuration.as_ref() { Some(name) => EventConfig::from_name(name), @@ -158,10 +293,7 @@ where let mut harness_bytes = self.harness.take().unwrap(); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); - let monitor = MultiMonitor::new(|s| println!("{s}")); - - // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); let mut run_client = |state: Option<_>, @@ -187,17 +319,8 @@ where // Extra MapFeedback to deduplicate finds according to the cov map let map_objective = MaxMapFeedback::with_name("map_objective", &edges_observer); - let calibration = CalibrationStage::new(&map_feedback); - let calibration_cmplog = CalibrationStage::new(&map_feedback); - // Feedback to rate the interestingness of an input - // This one is composed by two Feedbacks in OR - let mut feedback = feedback_or!( - map_feedback, - // Time feedback, this one does not need a feedback state - TimeFeedback::new(&time_observer) - ); - + let mut feedback = feedback_or!(map_feedback, TimeFeedback::new(&time_observer)); // A feedback to choose if an input is a solution or not let mut objective = feedback_and_fast!( feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()), @@ -207,12 +330,8 @@ where // If not restarting, create a State from scratch let mut state = state.unwrap_or_else(|| { StdState::new( - // RNG StdRand::new(), - // Corpus that will be evolved, we keep a part in memory for performance CachedOnDiskCorpus::new(out_dir.clone(), CORPUS_CACHE_SIZE).unwrap(), - // Corpus in which we store solutions (crashes in this example), - // on disk so the user can get them after stopping the fuzzer OnDiskCorpus::new(crashes.clone()).unwrap(), &mut feedback, &mut objective, @@ -227,279 +346,128 @@ where } } - // A minimization+queue policy to get testcasess from the corpus + // A minimization+queue policy to get testcases from the corpus let scheduler = IndexesLenTimeMinimizerScheduler::new(&edges_observer, QueueScheduler::new()); - // A fuzzer with feedbacks and a corpus scheduler - let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); - - // The wrapped harness function, calling out to the LLVM-style harness - if self.use_cmplog.unwrap_or(false) { - let modules = { - #[cfg(not(any(feature = "mips", feature = "hexagon")))] - { - tuple_list!( - StdEdgeCoverageModule::builder() - .map_observer(edges_observer.as_mut()) - .build() - .unwrap(), - CmpLogModule::default(), - ) - } - #[cfg(any(feature = "mips", feature = "hexagon"))] - { - tuple_list!( - StdEdgeCoverageModule::builder() - .map_observer(edges_observer.as_mut()) - .build() - .unwrap() - ) - } - }; - - let mut harness = |_emulator: &mut Emulator<_, _, _, _, _, _, _>, - _state: &mut _, - input: &BytesInput| { - let target = input.target_bytes(); - let buf = target.as_slice(); - harness_bytes(buf); - ExitKind::Ok - }; - - let emulator = match qemu { - QemuSugarParameter::QemuCli(qemu_cli) => Emulator::empty() - .qemu_parameters(qemu_cli.to_owned()) - .modules(modules) - .build() - .expect("Could not initialize Emulator"), - QemuSugarParameter::Qemu(qemu) => Emulator::empty() - .modules(modules) - .build_with_qemu(*qemu) - .expect("Could not initialize Emulator"), - }; - - let executor = QemuExecutor::new( - emulator, - &mut harness, - tuple_list!(edges_observer, time_observer), - &mut fuzzer, - &mut state, - &mut mgr, - timeout, - )?; - let mut executor = ShadowExecutor::new(executor, tuple_list!(cmplog_observer)); - - // In case the corpus is empty (on first run), reset - if state.must_load_initial_inputs() { - if self.input_dirs.is_empty() { - // Generator of printable bytearrays of max size 32 - let mut generator = RandBytesGenerator::new(nonzero!(32)); - - // Generate 8 initial inputs - state - .generate_initial_inputs( - &mut fuzzer, - &mut executor, - &mut generator, - &mut mgr, - 8, - ) - .expect("Failed to generate the initial corpus"); - log::info!( - "We imported {} inputs from the generator.", - state.corpus().count() - ); - } else { - log::info!("Loading from {:?}", &self.input_dirs); - // Load from disk - state - .load_initial_inputs( - &mut fuzzer, - &mut executor, - &mut mgr, - self.input_dirs, - ) - .unwrap_or_else(|_| { - panic!("Failed to load initial corpus at {:?}", &self.input_dirs); - }); - log::info!("We imported {} inputs from disk.", state.corpus().count()); - } - } - // Setup a tracing stage in which we log comparisons - let tracing = ShadowTracingStage::new(); - - // Setup a randomic Input2State stage - let i2s = StdMutationalStage::new(HavocScheduledMutator::new(tuple_list!( - I2SRandReplace::new() - ))); - - if self.tokens_file.is_some() { - // Setup a basic mutator - let mutator = - HavocScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); - let mutational = StdMutationalStage::new(mutator); - - // The order of the stages matter! - let mut stages = tuple_list!(calibration_cmplog, tracing, i2s, mutational); - - if let Some(iters) = self.iterations { - fuzzer.fuzz_loop_for( - &mut stages, - &mut executor, - &mut state, - &mut mgr, - iters, - )?; - mgr.on_restart(&mut state)?; - std::process::exit(0); - } else { - fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; - } - } else { - // Setup a basic mutator - let mutator = HavocScheduledMutator::new(havoc_mutations()); - let mutational = StdMutationalStage::new(mutator); - - // The order of the stages matter! - let mut stages = tuple_list!(calibration_cmplog, tracing, i2s, mutational); - - if let Some(iters) = self.iterations { - fuzzer.fuzz_loop_for( - &mut stages, - &mut executor, - &mut state, - &mut mgr, - iters, - )?; - mgr.on_restart(&mut state)?; - std::process::exit(0); - } else { - fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; - } - } - } else { - let modules = tuple_list!( - StdEdgeCoverageModule::builder() - .map_observer(edges_observer.as_mut()) - .build() - .unwrap() - ); - - let mut harness = |_emulator: &mut Emulator<_, _, _, _, _, _, _>, - _state: &mut _, - input: &BytesInput| { - let target = input.target_bytes(); - let buf = target.as_slice(); - harness_bytes(buf); - ExitKind::Ok - }; - - let emulator = match qemu { - QemuSugarParameter::QemuCli(qemu_cli) => Emulator::empty() - .qemu_parameters(qemu_cli.to_owned()) - .modules(modules) - .build() - .expect("Could not initialize Emulator"), - QemuSugarParameter::Qemu(qemu) => Emulator::empty() - .modules(modules) - .build_with_qemu(*qemu) - .expect("Could not initialize Emulator"), - }; - - let mut executor = QemuExecutor::new( - emulator, - &mut harness, - tuple_list!(edges_observer, time_observer), - &mut fuzzer, - &mut state, - &mut mgr, - timeout, - )?; - - // In case the corpus is empty (on first run), reset - if state.must_load_initial_inputs() { - if self.input_dirs.is_empty() { - // Generator of printable bytearrays of max size 32 - let mut generator = RandBytesGenerator::new(nonzero!(32)); - - // Generate 8 initial inputs - state - .generate_initial_inputs( - &mut fuzzer, - &mut executor, - &mut generator, - &mut mgr, - 8, - ) - .expect("Failed to generate the initial corpus"); - log::info!( - "We imported {} inputs from the generator.", - state.corpus().count() - ); - } else { - log::info!("Loading from {:?}", &self.input_dirs); - // Load from disk - state - .load_initial_inputs( - &mut fuzzer, - &mut executor, - &mut mgr, - self.input_dirs, - ) - .unwrap_or_else(|_| { - panic!("Failed to load initial corpus at {:?}", &self.input_dirs); - }); - log::info!("We imported {} inputs from disk.", state.corpus().count()); - } - } - if self.tokens_file.is_some() { - // Setup a basic mutator - let mutator = - HavocScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); - let mutational = StdMutationalStage::new(mutator); - - // The order of the stages matter! - let mut stages = tuple_list!(calibration, mutational); - - if let Some(iters) = self.iterations { - fuzzer.fuzz_loop_for( - &mut stages, - &mut executor, - &mut state, - &mut mgr, - iters, - )?; - mgr.on_restart(&mut state)?; - std::process::exit(0); - } else { - fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; - } - } else { - // Setup a basic mutator - let mutator = HavocScheduledMutator::new(havoc_mutations()); - let mutational = StdMutationalStage::new(mutator); - - // The order of the stages matter! - let mut stages = tuple_list!(calibration, mutational); - - if let Some(iters) = self.iterations { - fuzzer.fuzz_loop_for( - &mut stages, - &mut executor, - &mut state, - &mut mgr, - iters, - )?; - mgr.on_restart(&mut state)?; - std::process::exit(0); - } else { - fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; - } + // Here's where the magic happens - all the duplicated code is now in a single macro call + match (self.use_cmplog.unwrap_or(false), self.use_snapshot.unwrap_or(false)) { + (true, true) => { + let modules = + tuple_list!( + StdEdgeCoverageModule::builder() + .map_observer(edges_observer.as_mut()) + .build() + .unwrap(), + CmpLogModule::default(), + SnapshotModule::new(), + ); + + implement_fuzzing!( + modules: modules, + use_shadow: true, + self: self, + qemu: qemu, + harness_bytes: harness_bytes, + edges_observer: edges_observer, + time_observer: time_observer, + cmplog_observer: cmplog_observer, + state: state, + mgr: mgr, + timeout: timeout, + map_feedback: map_feedback, + scheduler: scheduler, + feedback: feedback, + objective: objective + ); + }, + (true, false) => { + let modules = + tuple_list!( + StdEdgeCoverageModule::builder() + .map_observer(edges_observer.as_mut()) + .build() + .unwrap(), + CmpLogModule::default(), + ); + + implement_fuzzing!( + modules: modules, + use_shadow: true, + self: self, + qemu: qemu, + harness_bytes: harness_bytes, + edges_observer: edges_observer, + time_observer: time_observer, + cmplog_observer: cmplog_observer, + state: state, + mgr: mgr, + timeout: timeout, + map_feedback: map_feedback, + scheduler: scheduler, + feedback: feedback, + objective: objective + ); + }, + (false, true) => { + let modules = + tuple_list!( + StdEdgeCoverageModule::builder() + .map_observer(edges_observer.as_mut()) + .build() + .unwrap(), + SnapshotModule::new(), + ); + + implement_fuzzing!( + modules: modules, + use_shadow: true, + self: self, + qemu: qemu, + harness_bytes: harness_bytes, + edges_observer: edges_observer, + time_observer: time_observer, + cmplog_observer: cmplog_observer, + state: state, + mgr: mgr, + timeout: timeout, + map_feedback: map_feedback, + scheduler: scheduler, + feedback: feedback, + objective: objective + ); + }, + (false, false) => { + let modules = + tuple_list!( + StdEdgeCoverageModule::builder() + .map_observer(edges_observer.as_mut()) + .build() + .unwrap(), + ); + + implement_fuzzing!( + modules: modules, + use_shadow: true, + self: self, + qemu: qemu, + harness_bytes: harness_bytes, + edges_observer: edges_observer, + time_observer: time_observer, + cmplog_observer: cmplog_observer, + state: state, + mgr: mgr, + timeout: timeout, + map_feedback: map_feedback, + scheduler: scheduler, + feedback: feedback, + objective: objective + ); } } + Ok(()) }; @@ -525,6 +493,91 @@ where } } + +/// Sugar to create a `libfuzzer`-style fuzzer that uses +/// `QEMU`-based binary-only instrumentation +#[derive(TypedBuilder)] +pub struct QemuBytesCoverageSugar<'a, H> +where + H: FnMut(&[u8]), +{ + /// Laucher configuration (default is random) + #[builder(default = None, setter(strip_option))] + configuration: Option, + /// Timeout of the executor + #[builder(default = None)] + timeout: Option, + /// Input directories + input_dirs: &'a [PathBuf], + /// Output directory + output_dir: PathBuf, + /// Dictionary + #[builder(default = None)] + tokens_file: Option, + /// Flag if use `CmpLog` + #[builder(default = None)] + use_cmplog: Option, + #[builder(default = None)] + use_snapshot: Option, + /// The port the fuzzing nodes communicate over + /// This will spawn a server on this port, and connect to other brokers using this port. + #[builder(default = 1337_u16)] + broker_port: u16, + /// The list of cores to run on + cores: &'a Cores, + /// The `ip:port` address of another broker to connect our new broker to for multi-machine + /// clusters. + #[builder(default = None, setter(strip_option))] + remote_broker_addr: Option, + /// Bytes harness + #[builder(setter(strip_option))] + harness: Option, + /// Fuzz `iterations` number of times, instead of indefinitely; implies use of `fuzz_loop_for` + #[builder(default = None)] + iterations: Option, + /// Disable redirection of stdout to /dev/null on unix build targets + #[builder(default = None)] + enable_stdout: Option, +} + +impl Debug for QemuBytesCoverageSugar<'_, H> +where + H: FnMut(&[u8]), +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("QemuBytesCoverageSugar") + .field("configuration", &self.configuration) + .field("timeout", &self.timeout) + .field("input_dirs", &self.input_dirs) + .field("output_dir", &self.output_dir) + .field("tokens_file", &self.tokens_file) + .field("use_cmplog", &self.use_cmplog) + .field("broker_port", &self.broker_port) + .field("cores", &self.cores) + .field("remote_broker_addr", &self.remote_broker_addr) + .field( + "harness", + if self.harness.is_some() { + &"" + } else { + &"None" + }, + ) + .field("iterations", &self.iterations) + .field("enable_stdout", &self.enable_stdout) + .finish() + } +} + +/// Enum to allow passing either qemu cli parameters or a running qemu instance +#[derive(Debug, Copy, Clone)] +pub enum QemuSugarParameter<'a> { + /// Argument list to pass to initialize Qemu + QemuCli(&'a [String]), + /// Already existing Qemu instance + Qemu(&'a Qemu), +} + /// python bindings for this sugar #[cfg(feature = "python")] pub mod pybind { @@ -601,7 +654,7 @@ pub mod pybind { .cores(&self.cores) .harness(|buf| { Python::with_gil(|py| -> PyResult<()> { - let args = (PyBytes::new(py, buf),); // TODO avoid copy + let args = (PyBytes::new(py, buf),); harness.call1(py, args)?; Ok(()) }) @@ -622,4 +675,4 @@ pub mod pybind { m.add_class::()?; Ok(()) } -} +} \ No newline at end of file