diff --git a/fuzzers/libfuzzer_libpng_launcher/Cargo.toml b/fuzzers/libfuzzer_libpng_launcher/Cargo.toml index 9fdebf8089..752477b4d3 100644 --- a/fuzzers/libfuzzer_libpng_launcher/Cargo.toml +++ b/fuzzers/libfuzzer_libpng_launcher/Cargo.toml @@ -20,7 +20,7 @@ which = { version = "4.0.2" } num_cpus = "1.0" [dependencies] -libafl = { path = "../../libafl/" } +libafl = { path = "../../libafl/", features = ["std", "anymap_debug", "derive", "llmp_compression", "introspection"] } libafl_targets = { path = "../../libafl_targets/", features = ["sancov_pcguard_hitcounts", "libfuzzer"] } # TODO Include it only when building cc libafl_cc = { path = "../../libafl_cc/" } diff --git a/libafl/src/bolts/llmp.rs b/libafl/src/bolts/llmp.rs index c3a311990a..022adc98cb 100644 --- a/libafl/src/bolts/llmp.rs +++ b/libafl/src/bolts/llmp.rs @@ -1012,7 +1012,7 @@ where let shm = self.out_maps.last().unwrap(); println!( "LLMP_DEBUG: End of page reached for map {} with len {}, sending EOP, bt: {:?}", - shm.shmem.id().to_string(), + shm.shmem.id(), shm.shmem.len(), bt ); @@ -1369,7 +1369,7 @@ where #[cfg(all(feature = "llmp_debug", feature = "std"))] println!( "LLMP_DEBUG: Got a new recv map {} with len {:?}", - self.current_recv_map.shmem.id().to_string(), + self.current_recv_map.shmem.id(), self.current_recv_map.shmem.len() ); // After we mapped the new page, return the next message, if available @@ -1500,7 +1500,7 @@ where #[cfg(all(feature = "llmp_debug", feature = "std"))] println!( "LLMP_DEBUG: Initializing map on {} with size {}", - new_map.id().to_string(), + new_map.id(), new_map.len() ); @@ -1520,7 +1520,7 @@ where //let bt = ""; dbg!( "LLMP_DEBUG: Using existing map {} with size {}", - existing_map.id().to_string(), + existing_map.id(), existing_map.len(), //bt ); diff --git a/libafl/src/bolts/os/ashmem_server.rs b/libafl/src/bolts/os/ashmem_server.rs index ff23a9bd64..acb2f6c28d 100644 --- a/libafl/src/bolts/os/ashmem_server.rs +++ b/libafl/src/bolts/os/ashmem_server.rs @@ -94,8 +94,7 @@ impl ServedShMemProvider { .expect("Did not receive a response"); let server_id = ShMemId::from_slice(&shm_slice); - let server_id_str = server_id.to_string(); - let server_fd: i32 = server_id_str.parse()?; + let server_fd: i32 = server_id.into(); Ok((server_fd, fd_buf[0])) } } @@ -139,7 +138,7 @@ impl ShMemProvider for ServedShMemProvider { } fn from_id_and_size(&mut self, id: ShMemId, size: usize) -> Result { - let parts = id.to_string().split(':').collect::>(); + let parts = id.as_str().split(':').collect::>(); let server_id_str = parts.get(0).unwrap(); let (server_fd, client_fd) = self.send_receive(AshmemRequest::ExistingMap( ShMemDescription::from_string_and_size(server_id_str, size), @@ -257,16 +256,17 @@ impl AshmemService { let description = new_map.description(); let new_rc = Rc::new(RefCell::new(new_map)); self.all_maps - .insert(description.id.to_int(), Rc::downgrade(&new_rc)); + .insert(description.id.into(), Rc::downgrade(&new_rc)); Ok(AshmemResponse::Mapping(new_rc)) } AshmemRequest::ExistingMap(description) => { let client = self.clients.get_mut(&client_id).unwrap(); - if client.maps.contains_key(&description.id.to_int()) { + let description_id: i32 = description.id.into(); + if client.maps.contains_key(&description_id) { Ok(AshmemResponse::Mapping( client .maps - .get_mut(&description.id.to_int()) + .get_mut(&description_id) .as_mut() .unwrap() .first() @@ -277,7 +277,7 @@ impl AshmemService { } else { Ok(AshmemResponse::Mapping( self.all_maps - .get_mut(&description.id.to_int()) + .get_mut(&description_id) .unwrap() .clone() .upgrade() diff --git a/libafl/src/bolts/shmem.rs b/libafl/src/bolts/shmem.rs index cc352722cd..0ee13fe3a4 100644 --- a/libafl/src/bolts/shmem.rs +++ b/libafl/src/bolts/shmem.rs @@ -38,7 +38,11 @@ use serde::{Deserialize, Serialize}; use std::env; use alloc::{rc::Rc, string::ToString}; -use core::{cell::RefCell, fmt::Debug, mem::ManuallyDrop}; +use core::{ + cell::RefCell, + fmt::{self, Debug, Display}, + mem::ManuallyDrop, +}; #[cfg(all(unix, feature = "std"))] use crate::bolts::os::pipes::Pipe; @@ -102,18 +106,28 @@ impl ShMemId { &self.id } - /// Get a string representation of this id + /// Returns the first null-byte in or the end of the buffer #[must_use] - pub fn to_string(&self) -> &str { - let eof_pos = self.id.iter().position(|&c| c == 0).unwrap(); - alloc::str::from_utf8(&self.id[..eof_pos]).unwrap() + pub fn null_pos(&self) -> usize { + self.id.iter().position(|&c| c == 0).unwrap() } - /// Get an integer representation of this id + /// Returns a `str` representation of this [`ShMemId`] #[must_use] - pub fn to_int(&self) -> i32 { - let id: i32 = self.to_string().parse().unwrap(); - id + pub fn as_str(&self) -> &str { + alloc::str::from_utf8(&self.id[..self.null_pos()]).unwrap() + } +} + +impl From for i32 { + fn from(id: ShMemId) -> i32 { + id.as_str().parse().unwrap() + } +} + +impl Display for ShMemId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) } } @@ -523,7 +537,8 @@ pub mod unix_shmem { /// Get a [`UnixShMem`] of the existing shared memory mapping identified by id pub fn from_id_and_size(id: ShMemId, map_size: usize) -> Result { unsafe { - let map = shmat(id.to_int(), ptr::null(), 0) as *mut c_uchar; + let id_int: i32 = id.into(); + let map = shmat(id_int, ptr::null(), 0) as *mut c_uchar; if map == usize::MAX as *mut c_void as *mut c_uchar || map.is_null() { return Err(Error::Unknown( @@ -560,7 +575,8 @@ pub mod unix_shmem { impl Drop for CommonUnixShMem { fn drop(&mut self) { unsafe { - shmctl(self.id.to_int(), 0, ptr::null_mut()); + let id_int: i32 = self.id.into(); + shmctl(id_int, 0, ptr::null_mut()); } } } diff --git a/libafl/src/events/llmp.rs b/libafl/src/events/llmp.rs index 2d8a4e0d29..d6565790bd 100644 --- a/libafl/src/events/llmp.rs +++ b/libafl/src/events/llmp.rs @@ -216,7 +216,7 @@ where client.update_executions(*executions as u64, *time); // Update the performance stats for this client - client.update_introspection_stats(**introspection_stats); + client.update_introspection_stats((**introspection_stats).clone()); // Display the stats via `.display` only on core #1 stats.display(event.name().to_string(), sender_id); diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index db2d447561..72da996d1e 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -176,7 +176,8 @@ where } => { // TODO: The stats buffer should be added on client add. stats.client_stats_mut()[0].update_executions(*executions as u64, *time); - stats.client_stats_mut()[0].update_introspection_stats(**introspection_stats); + stats.client_stats_mut()[0] + .update_introspection_stats((**introspection_stats).clone()); stats.display(event.name().to_string(), 0); Ok(BrokerEventResult::Handled) } diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index f68ee24c72..443ebd49c7 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -28,7 +28,7 @@ use crate::{ fuzzer::HasObjective, inputs::Input, observers::ObserversTuple, - state::HasSolutions, + state::{HasClientPerfStats, HasSolutions}, Error, }; @@ -170,7 +170,7 @@ where EM: EventFirer + EventRestarter, OC: Corpus, OF: Feedback, - S: HasSolutions, + S: HasSolutions + HasClientPerfStats, Z: HasObjective, { #[cfg(unix)] @@ -255,7 +255,7 @@ mod unix_signal_handler { fuzzer::HasObjective, inputs::Input, observers::ObserversTuple, - state::HasSolutions, + state::{HasClientPerfStats, HasSolutions}, }; pub type HandlerFuncPtr = @@ -343,7 +343,7 @@ mod unix_signal_handler { OT: ObserversTuple, OC: Corpus, OF: Feedback, - S: HasSolutions, + S: HasSolutions + HasClientPerfStats, I: Input, Z: HasObjective, { @@ -417,7 +417,7 @@ mod unix_signal_handler { OT: ObserversTuple, OC: Corpus, OF: Feedback, - S: HasSolutions, + S: HasSolutions + HasClientPerfStats, I: Input, Z: HasObjective, { @@ -562,7 +562,7 @@ mod windows_exception_handler { fuzzer::HasObjective, inputs::Input, observers::ObserversTuple, - state::HasSolutions, + state::{HasClientPerfStats, HasSolutions}, }; pub type HandlerFuncPtr = @@ -628,7 +628,7 @@ mod windows_exception_handler { OT: ObserversTuple, OC: Corpus, OF: Feedback, - S: HasSolutions, + S: HasSolutions + HasClientPerfStats, I: Input, Z: HasObjective, { diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index 27204bd6c6..7d0545edf9 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -16,7 +16,7 @@ use crate::{ feedbacks::{Feedback, FeedbackState, FeedbackStatesTuple}, inputs::Input, observers::{MapObserver, ObserversTuple}, - state::{HasFeedbackStates, HasMetadata}, + state::{HasClientPerfStats, HasFeedbackStates, HasMetadata}, stats::UserStats, Error, }; @@ -211,7 +211,7 @@ where R: Reducer, O: MapObserver, I: Input, - S: HasFeedbackStates, + S: HasFeedbackStates + HasClientPerfStats, FT: FeedbackStatesTuple, { fn is_interesting( @@ -430,6 +430,7 @@ impl Feedback for ReachabilityFeedback where I: Input, O: MapObserver, + S: HasClientPerfStats, { fn is_interesting( &mut self, diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 2d21045c8a..8f726adeeb 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -14,12 +14,10 @@ use crate::{ executors::ExitKind, inputs::Input, observers::{ObserversTuple, TimeObserver}, + state::HasClientPerfStats, Error, }; -#[cfg(feature = "introspection")] -use crate::stats::NUM_FEEDBACKS; - use core::{marker::PhantomData, time::Duration}; /// Feedbacks evaluate the observers. @@ -28,6 +26,7 @@ use core::{marker::PhantomData, time::Duration}; pub trait Feedback: Named where I: Input, + S: HasClientPerfStats, { /// `is_interesting ` return if an input is worth the addition to the corpus fn is_interesting( @@ -44,15 +43,13 @@ where #[cfg(feature = "introspection")] #[allow(clippy::too_many_arguments)] - fn is_interesting_with_perf( + fn is_interesting_introspection( &mut self, state: &mut S, manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, - feedback_stats: &mut [u64; NUM_FEEDBACKS], - feedback_index: usize, ) -> Result where EM: EventFirer, @@ -67,10 +64,10 @@ where // Get the elapsed time for checking this feedback let elapsed = crate::bolts::cpu::read_time_counter() - start_time; - // TODO: A more meaningful way to get perf for each feedback - // Add this stat to the feedback metrics - feedback_stats[feedback_index] = elapsed; + state + .introspection_stats_mut() + .update_feedback(self.name(), elapsed); ret } @@ -114,6 +111,7 @@ where B: Feedback, FL: FeedbackLogic, I: Input, + S: HasClientPerfStats, { pub first: A, pub second: B, @@ -127,6 +125,7 @@ where B: Feedback, FL: FeedbackLogic, I: Input, + S: HasClientPerfStats, { fn name(&self) -> &str { self.name.as_ref() @@ -139,6 +138,7 @@ where B: Feedback, FL: FeedbackLogic, I: Input, + S: HasClientPerfStats, { pub fn new(first: A, second: B) -> Self { let name = format!("{} ({},{})", FL::name(), first.name(), second.name()); @@ -157,6 +157,7 @@ where B: Feedback, FL: FeedbackLogic, I: Input, + S: HasClientPerfStats, { fn is_interesting( &mut self, @@ -182,21 +183,19 @@ where } #[cfg(feature = "introspection")] - fn is_interesting_with_perf( + fn is_interesting_introspection( &mut self, state: &mut S, manager: &mut EM, input: &I, observers: &OT, exit_kind: &ExitKind, - feedback_stats: &mut [u64; NUM_FEEDBACKS], - feedback_index: usize, ) -> Result where EM: EventFirer, OT: ObserversTuple, { - FL::is_pair_interesting_with_perf( + FL::is_pair_interesting_introspection( &mut self.first, &mut self.second, state, @@ -204,8 +203,6 @@ where input, observers, exit_kind, - feedback_stats, - feedback_index, ) } @@ -227,6 +224,7 @@ where A: Feedback, B: Feedback, I: Input, + S: HasClientPerfStats, { fn name() -> &'static str; @@ -245,7 +243,7 @@ where #[cfg(feature = "introspection")] #[allow(clippy::too_many_arguments)] - fn is_pair_interesting_with_perf( + fn is_pair_interesting_introspection( first: &mut A, second: &mut B, state: &mut S, @@ -253,8 +251,6 @@ where input: &I, observers: &OT, exit_kind: &ExitKind, - feedback_stats: &mut [u64; NUM_FEEDBACKS], - feedback_index: usize, ) -> Result where EM: EventFirer, @@ -271,6 +267,7 @@ where A: Feedback, B: Feedback, I: Input, + S: HasClientPerfStats, { fn name() -> &'static str { "Eager OR" @@ -295,7 +292,7 @@ where } #[cfg(feature = "introspection")] - fn is_pair_interesting_with_perf( + fn is_pair_interesting_introspection( first: &mut A, second: &mut B, state: &mut S, @@ -303,33 +300,15 @@ where input: &I, observers: &OT, exit_kind: &ExitKind, - feedback_stats: &mut [u64; NUM_FEEDBACKS], - feedback_index: usize, ) -> Result where EM: EventFirer, OT: ObserversTuple, { // Execute this feedback - let a = first.is_interesting_with_perf( - state, - manager, - input, - observers, - exit_kind, - feedback_stats, - feedback_index, - )?; + let a = first.is_interesting_introspection(state, manager, input, observers, exit_kind)?; - let b = second.is_interesting_with_perf( - state, - manager, - input, - observers, - exit_kind, - feedback_stats, - feedback_index + 1, - )?; + let b = second.is_interesting_introspection(state, manager, input, observers, exit_kind)?; Ok(a || b) } } @@ -339,6 +318,7 @@ where A: Feedback, B: Feedback, I: Input, + S: HasClientPerfStats, { fn name() -> &'static str { "Fast OR" @@ -366,7 +346,7 @@ where } #[cfg(feature = "introspection")] - fn is_pair_interesting_with_perf( + fn is_pair_interesting_introspection( first: &mut A, second: &mut B, state: &mut S, @@ -374,37 +354,19 @@ where input: &I, observers: &OT, exit_kind: &ExitKind, - feedback_stats: &mut [u64; NUM_FEEDBACKS], - feedback_index: usize, ) -> Result where EM: EventFirer, OT: ObserversTuple, { // Execute this feedback - let a = first.is_interesting_with_perf( - state, - manager, - input, - observers, - exit_kind, - feedback_stats, - feedback_index, - )?; + let a = first.is_interesting_introspection(state, manager, input, observers, exit_kind)?; if a { return Ok(true); } - second.is_interesting_with_perf( - state, - manager, - input, - observers, - exit_kind, - feedback_stats, - feedback_index + 1, - ) + second.is_interesting_introspection(state, manager, input, observers, exit_kind) } } @@ -413,6 +375,7 @@ where A: Feedback, B: Feedback, I: Input, + S: HasClientPerfStats, { fn name() -> &'static str { "Eager AND" @@ -437,7 +400,7 @@ where } #[cfg(feature = "introspection")] - fn is_pair_interesting_with_perf( + fn is_pair_interesting_introspection( first: &mut A, second: &mut B, state: &mut S, @@ -445,33 +408,15 @@ where input: &I, observers: &OT, exit_kind: &ExitKind, - feedback_stats: &mut [u64; NUM_FEEDBACKS], - feedback_index: usize, ) -> Result where EM: EventFirer, OT: ObserversTuple, { // Execute this feedback - let a = first.is_interesting_with_perf( - state, - manager, - input, - observers, - exit_kind, - feedback_stats, - feedback_index, - )?; + let a = first.is_interesting_introspection(state, manager, input, observers, exit_kind)?; - let b = second.is_interesting_with_perf( - state, - manager, - input, - observers, - exit_kind, - feedback_stats, - feedback_index + 1, - )?; + let b = second.is_interesting_introspection(state, manager, input, observers, exit_kind)?; Ok(a && b) } } @@ -481,6 +426,7 @@ where A: Feedback, B: Feedback, I: Input, + S: HasClientPerfStats, { fn name() -> &'static str { "Fast AND" @@ -508,7 +454,7 @@ where } #[cfg(feature = "introspection")] - fn is_pair_interesting_with_perf( + fn is_pair_interesting_introspection( first: &mut A, second: &mut B, state: &mut S, @@ -516,37 +462,19 @@ where input: &I, observers: &OT, exit_kind: &ExitKind, - feedback_stats: &mut [u64; NUM_FEEDBACKS], - feedback_index: usize, ) -> Result where EM: EventFirer, OT: ObserversTuple, { // Execute this feedback - let a = first.is_interesting_with_perf( - state, - manager, - input, - observers, - exit_kind, - feedback_stats, - feedback_index, - )?; + let a = first.is_interesting_introspection(state, manager, input, observers, exit_kind)?; if !a { return Ok(false); } - second.is_interesting_with_perf( - state, - manager, - input, - observers, - exit_kind, - feedback_stats, - feedback_index + 1, - ) + second.is_interesting_introspection(state, manager, input, observers, exit_kind) } } @@ -573,6 +501,7 @@ pub struct NotFeedback where A: Feedback, I: Input, + S: HasClientPerfStats, { /// The feedback to invert pub first: A, @@ -585,6 +514,7 @@ impl Feedback for NotFeedback where A: Feedback, I: Input, + S: HasClientPerfStats, { fn is_interesting( &mut self, @@ -618,6 +548,7 @@ impl Named for NotFeedback where A: Feedback, I: Input, + S: HasClientPerfStats, { #[inline] fn name(&self) -> &str { @@ -629,6 +560,7 @@ impl NotFeedback where A: Feedback, I: Input, + S: HasClientPerfStats, { /// Creates a new [`NotFeedback`]. pub fn new(first: A) -> Self { @@ -696,6 +628,7 @@ macro_rules! feedback_not { impl Feedback for () where I: Input, + S: HasClientPerfStats, { fn is_interesting( &mut self, @@ -727,6 +660,7 @@ pub struct CrashFeedback {} impl Feedback for CrashFeedback where I: Input, + S: HasClientPerfStats, { fn is_interesting( &mut self, @@ -776,6 +710,7 @@ pub struct TimeoutFeedback {} impl Feedback for TimeoutFeedback where I: Input, + S: HasClientPerfStats, { fn is_interesting( &mut self, @@ -830,6 +765,7 @@ pub struct TimeFeedback { impl Feedback for TimeFeedback where I: Input, + S: HasClientPerfStats, { fn is_interesting( &mut self, diff --git a/libafl/src/fuzzer.rs b/libafl/src/fuzzer.rs index 63a3225196..126c66b8cd 100644 --- a/libafl/src/fuzzer.rs +++ b/libafl/src/fuzzer.rs @@ -44,6 +44,7 @@ pub trait HasFeedback where F: Feedback, I: Input, + S: HasClientPerfStats, { /// The feedback fn feedback(&self) -> &F; @@ -57,6 +58,7 @@ pub trait HasObjective where OF: Feedback, I: Input, + S: HasClientPerfStats, { /// The objective feedback fn objective(&self) -> &OF; @@ -240,6 +242,7 @@ where F: Feedback, I: Input, OF: Feedback, + S: HasClientPerfStats, { scheduler: CS, feedback: F, @@ -254,6 +257,7 @@ where F: Feedback, I: Input, OF: Feedback, + S: HasClientPerfStats, { fn scheduler(&self) -> &CS { &self.scheduler @@ -270,6 +274,7 @@ where F: Feedback, I: Input, OF: Feedback, + S: HasClientPerfStats, { fn feedback(&self) -> &F { &self.feedback @@ -286,6 +291,7 @@ where F: Feedback, I: Input, OF: Feedback, + S: HasClientPerfStats, { fn objective(&self) -> &OF { &self.objective @@ -323,11 +329,15 @@ where { let mut res = ExecuteInputResult::None; - start_timer!(state); + #[cfg(not(feature = "introspection"))] let is_solution = self .objective_mut() .is_interesting(state, manager, &input, observers, &exit_kind)?; - mark_feature_time!(state, PerfFeature::GetObjectivesInterestingAll); + + #[cfg(feature = "introspection")] + let is_solution = self + .objective_mut() + .is_interesting_introspection(state, manager, &input, observers, &exit_kind)?; if is_solution { res = ExecuteInputResult::Solution; @@ -338,31 +348,9 @@ where .is_interesting(state, manager, &input, observers, &exit_kind)?; #[cfg(feature = "introspection")] - let is_corpus = { - // Init temporary feedback stats here. We can't use the typical pattern above - // since we need a `mut self` for `feedbacks_mut`, so we can't also hand a - // new `mut self` to `is_interesting_with_perf`. We use this stack - // variable to get the stats and then update the feedbacks directly - let mut feedback_stats = [0_u64; crate::stats::NUM_FEEDBACKS]; - let feedback_index = 0; - let is_corpus = self.feedback_mut().is_interesting_with_perf( - state, - manager, - &input, - observers, - &exit_kind, - &mut feedback_stats, - feedback_index, - )?; - - // Update the feedback stats - state - .introspection_stats_mut() - .update_feedbacks(feedback_stats); - - // Return the total fitness - is_corpus - }; + let is_corpus = self + .feedback_mut() + .is_interesting_introspection(state, manager, &input, observers, &exit_kind)?; if is_corpus { res = ExecuteInputResult::Corpus; @@ -569,7 +557,7 @@ where Event::UpdatePerfStats { executions: *state.executions(), time: cur, - introspection_stats: Box::new(*state.introspection_stats()), + introspection_stats: Box::new(state.introspection_stats().clone()), phantom: PhantomData, }, )?; diff --git a/libafl/src/observers/cmp.rs b/libafl/src/observers/cmp.rs index 0affc220e5..29f6384179 100644 --- a/libafl/src/observers/cmp.rs +++ b/libafl/src/observers/cmp.rs @@ -113,7 +113,7 @@ where where S: HasMetadata, { - #[allow(clippy::clippy::option_if_let_else)] // we can't mutate state in a closure + #[allow(clippy::option_if_let_else)] // we can't mutate state in a closure let meta = if let Some(meta) = state.metadata_mut().get_mut::() { meta } else { diff --git a/libafl/src/stats/mod.rs b/libafl/src/stats/mod.rs index 876a222e8c..4d7785d88b 100644 --- a/libafl/src/stats/mod.rs +++ b/libafl/src/stats/mod.rs @@ -19,12 +19,6 @@ use crate::bolts::current_time; const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds -/// Number of stages in the fuzzer -pub(crate) const NUM_STAGES: usize = 8; - -/// Number of feedback mechanisms to measure for performance -pub(crate) const NUM_FEEDBACKS: usize = 16; - /// User-defined stats types #[derive(Serialize, Deserialize, Debug, Clone)] pub enum UserStats { @@ -294,7 +288,7 @@ where { // Print the client performance stats. let fmt = format!( - "Client {:03}: {}", + "Client {:03}:\n{}", sender_id, self.client_stats[sender_id as usize].introspection_stats ); (self.print_fn)(fmt); @@ -358,7 +352,7 @@ macro_rules! mark_feedback_time { } /// Client performance statistics -#[derive(Serialize, Deserialize, Debug, Copy, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct ClientPerfStats { /// Starting counter (in clock cycles from [`cpu::read_time_counter`]) start_time: u64, @@ -375,26 +369,22 @@ pub struct ClientPerfStats { /// Current stage index to write the next stage benchmark time curr_stage: u8, - /// Current feedback index to write the next feedback benchmark time - curr_feedback: u8, - /// Flag to dictate this stage is in use. Used during printing to not print the empty /// stages if they are not in use. - stages_used: [bool; NUM_STAGES], + stages_used: Vec, - // TODO(andrea) use an hashmap and indetify feaures using a &'static str /// Clock cycles spent in the the various features of each stage - stages: [[u64; PerfFeature::Count as usize]; NUM_STAGES], + stages: Vec<[u64; PerfFeature::Count as usize]>, /// Clock cycles spent in each feedback mechanism of the fuzzer. - feedbacks: [u64; NUM_FEEDBACKS], + feedbacks: HashMap, /// Current time set by `start_timer` timer_start: Option, } /// Various features that are measured for performance -#[derive(Serialize, Deserialize, Debug, Copy, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[repr(u8)] pub enum PerfFeature { /// Getting an input from the corpus @@ -477,7 +467,7 @@ impl From for PerfFeature { /// Number of features we can measure for performance #[cfg(feature = "introspection")] -const NUM_PERF_FEATURES: usize = PerfFeature::Count as usize; +pub const NUM_PERF_FEATURES: usize = PerfFeature::Count as usize; #[cfg(feature = "introspection")] impl ClientPerfStats { @@ -493,10 +483,9 @@ impl ClientPerfStats { scheduler: 0, manager: 0, curr_stage: 0, - curr_feedback: 0, - stages: [[0; NUM_PERF_FEATURES]; NUM_STAGES], - stages_used: [false; NUM_STAGES], - feedbacks: [0; NUM_FEEDBACKS], + stages: vec![], + stages_used: vec![], + feedbacks: HashMap::new(), timer_start: None, } } @@ -514,12 +503,12 @@ impl ClientPerfStats { } /// Update the current [`ClientPerfStats`] with the given [`ClientPerfStats`] - pub fn update(&mut self, stats: ClientPerfStats) { + pub fn update(&mut self, stats: &ClientPerfStats) { self.set_current_time(stats.current_time); self.update_scheduler(stats.scheduler); self.update_manager(stats.manager); - self.update_stages(stats.stages); - self.update_feedbacks(stats.feedbacks); + self.update_stages(&stats.stages); + self.update_feedbacks(&stats.feedbacks); } /// Gets the elapsed time since the internal timer started. Resets the timer when @@ -578,33 +567,6 @@ impl ClientPerfStats { self.update_feature(feature, elapsed); } - /// Update the time spent in the current [`Feedback`] with the elapsed time that we - /// have seen - pub fn mark_feedback_time(&mut self) { - // Sanity check that these stats have enough room for these benchmarks - assert!( - (self.curr_feedback as usize) < NUM_FEEDBACKS, - "Current fuzzer has more - stages than the `ClientPerfStats` supports ({}). Please update the - NUM_FEEDBACKS const value in src/stats/mod.rs and rebuild", - NUM_FEEDBACKS - ); - - // Get the current elapsed time - let elapsed = self.mark_time(); - - // Get a `usize` for the index - let index: usize = self.curr_feedback.try_into().unwrap(); - - // Update the current feedback's time with the given time - self.feedbacks[index] = self.feedbacks[index] - .checked_add(elapsed) - .expect("mark_feedback_time overflow"); - - // Increment the feedback index to the next feedback - self.curr_feedback += 1; - } - /// Add the given `time` to the `scheduler` stats #[inline] pub fn update_scheduler(&mut self, time: u64) { @@ -637,23 +599,32 @@ impl ClientPerfStats { self.curr_stage = 0; } - /// Reset the feedback index counter to zero - #[inline] - pub fn reset_feedback_index(&mut self) { - self.curr_feedback = 0; + /// Update the time spent in the feedback + pub fn update_feedback(&mut self, name: &str, time: u64) { + self.feedbacks.insert( + name.into(), + self.feedbacks + .get(name) + .unwrap_or(&0) + .checked_add(time) + .expect("update_feedback overflow"), + ); } - /// Update the time spent in the feedbacks - pub fn update_feedbacks(&mut self, feedbacks: [u64; NUM_FEEDBACKS]) { - for (feedback_index, feedback) in feedbacks.iter().enumerate() { - self.feedbacks[feedback_index] = self.feedbacks[feedback_index] - .checked_add(*feedback) - .expect("update_feedback overflow"); + /// Update the time spent in all the feedbacks + pub fn update_feedbacks(&mut self, feedbacks: &HashMap) { + for (key, value) in feedbacks { + self.update_feedback(key, *value); } } /// Update the time spent in the stages - pub fn update_stages(&mut self, stages: [[u64; NUM_PERF_FEATURES]; NUM_STAGES]) { + pub fn update_stages(&mut self, stages: &[[u64; PerfFeature::Count as usize]]) { + if self.stages.len() < stages.len() { + self.stages + .resize(stages.len(), [0; PerfFeature::Count as usize]); + self.stages_used.resize(stages.len(), false); + } for (stage_index, features) in stages.iter().enumerate() { for (feature_index, feature) in features.iter().enumerate() { self.stages[stage_index][feature_index] = self.stages[stage_index][feature_index] @@ -665,21 +636,18 @@ impl ClientPerfStats { /// Update the given [`PerfFeature`] with the given `time` pub fn update_feature(&mut self, feature: PerfFeature, time: u64) { - // Sanity check that these stats have enough room for these benchmarks - assert!( - (self.curr_stage as usize) < NUM_STAGES, - "Current fuzzer has more stages - than the `ClientPerfStats` supports ({}). Please update the NUM_STAGES - const value in src/stats/mod.rs and rebuild", - NUM_STAGES - ); - // Get the current stage index as `usize` let stage_index: usize = self.curr_stage.try_into().unwrap(); // Get the index of the given feature let feature_index: usize = feature.try_into().unwrap(); + if stage_index >= self.stages.len() { + self.stages + .resize(stage_index + 1, [0; PerfFeature::Count as usize]); + self.stages_used.resize(stage_index + 1, false); + } + // Update the given feature self.stages[stage_index][feature_index] = self.stages[stage_index][feature_index] .checked_add(time) @@ -689,18 +657,25 @@ impl ClientPerfStats { self.stages_used[stage_index] = true; } + /// The elapsed cycles (or time) + #[must_use] pub fn elapsed_cycles(&self) -> u64 { self.current_time - self.start_time } + /// The amount of cycles the `manager` did + #[must_use] pub fn manager_cycles(&self) -> u64 { self.manager } + /// The amount of cycles the `scheduler` did + #[must_use] pub fn scheduler_cycles(&self) -> u64 { self.scheduler } + /// Iterator over all used stages pub fn used_stages( &self, ) -> impl Iterator { @@ -711,8 +686,10 @@ impl ClientPerfStats { .filter(move |(stage_index, _)| used[*stage_index as usize]) } - pub fn feedbacks(&self) -> impl Iterator { - self.feedbacks.iter().enumerate() + /// A map of all `feedbacks` + #[must_use] + pub fn feedbacks(&self) -> &HashMap { + &self.feedbacks } } @@ -735,7 +712,7 @@ impl core::fmt::Display for ClientPerfStats { // Create the formatted string writeln!( f, - "Scheduler: {:4.2} | Manager: {:4.2} | Stages:", + " {:6.4}: Scheduler\n {:6.4}: Manager", scheduler_percent, manager_percent )?; @@ -763,29 +740,27 @@ impl core::fmt::Display for ClientPerfStats { // Write the percentage for this feature writeln!(f, " {:6.4}: {:?}", feature_percent, feature)?; } - - for (feedback_index, feedback) in self.feedbacks() { - // Calculate this current stage's percentage - let feedback_percent = *feedback as f64 / elapsed; - - // Ignore this feedback if it isn't used - if feedback_percent == 0.0 { - continue; - } - - // Update the other percent by removing this current percent - other_percent -= feedback_percent; - - // Write the percentage for this feedback - writeln!( - f, - " {:6.4}: Feedback index {}", - feedback_percent, feedback_index - )?; - } } - write!(f, " Not Measured: {:4.2}", other_percent)?; + writeln!(f, " Feedbacks:")?; + + for (feedback_name, feedback_time) in self.feedbacks() { + // Calculate this current stage's percentage + let feedback_percent = *feedback_time as f64 / elapsed; + + // Ignore this feedback if it isn't used + if feedback_percent == 0.0 { + continue; + } + + // Update the other percent by removing this current percent + other_percent -= feedback_percent; + + // Write the percentage for this feedback + writeln!(f, " {:6.4}: {}", feedback_percent, feedback_name)?; + } + + write!(f, " {:6.4}: Not Measured", other_percent)?; Ok(()) } diff --git a/libafl/src/stats/multi.rs b/libafl/src/stats/multi.rs index 7f2b099a4f..41ba0639c0 100644 --- a/libafl/src/stats/multi.rs +++ b/libafl/src/stats/multi.rs @@ -80,7 +80,7 @@ where { // Print the client performance stats. Skip the Client 0 which is the broker for (i, client) in self.client_stats.iter().skip(1).enumerate() { - let fmt = format!("Client {:03}: {}", i + 1, client.introspection_stats); + let fmt = format!("Client {:03}:\n{}", i + 1, client.introspection_stats); (self.print_fn)(fmt); } diff --git a/libafl_frida/src/asan_errors.rs b/libafl_frida/src/asan_errors.rs index 54746d93b5..c4db043d65 100644 --- a/libafl_frida/src/asan_errors.rs +++ b/libafl_frida/src/asan_errors.rs @@ -13,7 +13,7 @@ use libafl::{ feedbacks::Feedback, inputs::{HasTargetBytes, Input}, observers::{Observer, ObserversTuple}, - state::HasMetadata, + state::{HasClientPerfStats, HasMetadata}, Error, SerdeAny, }; use serde::{Deserialize, Serialize}; @@ -510,6 +510,7 @@ pub struct AsanErrorsFeedback { impl Feedback for AsanErrorsFeedback where I: Input + HasTargetBytes, + S: HasClientPerfStats, { fn is_interesting( &mut self, diff --git a/libafl_targets/src/common.h b/libafl_targets/src/common.h index 2fefa05ee9..e2695fe16d 100644 --- a/libafl_targets/src/common.h +++ b/libafl_targets/src/common.h @@ -73,8 +73,6 @@ #define CHECK_WEAK_FN(Name) (Name != NULL) #endif // _MSC_VER -#define EXT_FUNC_DEF(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ - EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) #define EXT_FUNC_IMPL(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) @@ -84,10 +82,7 @@ #else #if defined(__APPLE__) - // TODO: Find a proper way to deal with weak fns on Apple! // On Apple, weak_import and weak attrs behave differently to linux. - #define EXT_FUNC_DEF(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ - EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) { return (RETURN_TYPE) 0; } #define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ __attribute__((weak, visibility("default"))) RETURN_TYPE NAME FUNC_SIG { \