Improve introspection (#200)

* remove NUM_FEEDBACKS

* working introspection

* adust introspection stats

* bugfixes, clippy

* removed outdated define

* more clippy;

* no_std

Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
Andrea Fioraldi 2021-07-02 10:58:36 +02:00 committed by GitHub
parent 204b15a432
commit 44f6e4c389
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 180 additions and 267 deletions

View File

@ -20,7 +20,7 @@ which = { version = "4.0.2" }
num_cpus = "1.0" num_cpus = "1.0"
[dependencies] [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"] } libafl_targets = { path = "../../libafl_targets/", features = ["sancov_pcguard_hitcounts", "libfuzzer"] }
# TODO Include it only when building cc # TODO Include it only when building cc
libafl_cc = { path = "../../libafl_cc/" } libafl_cc = { path = "../../libafl_cc/" }

View File

@ -1012,7 +1012,7 @@ where
let shm = self.out_maps.last().unwrap(); let shm = self.out_maps.last().unwrap();
println!( println!(
"LLMP_DEBUG: End of page reached for map {} with len {}, sending EOP, bt: {:?}", "LLMP_DEBUG: End of page reached for map {} with len {}, sending EOP, bt: {:?}",
shm.shmem.id().to_string(), shm.shmem.id(),
shm.shmem.len(), shm.shmem.len(),
bt bt
); );
@ -1369,7 +1369,7 @@ where
#[cfg(all(feature = "llmp_debug", feature = "std"))] #[cfg(all(feature = "llmp_debug", feature = "std"))]
println!( println!(
"LLMP_DEBUG: Got a new recv map {} with len {:?}", "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() self.current_recv_map.shmem.len()
); );
// After we mapped the new page, return the next message, if available // After we mapped the new page, return the next message, if available
@ -1500,7 +1500,7 @@ where
#[cfg(all(feature = "llmp_debug", feature = "std"))] #[cfg(all(feature = "llmp_debug", feature = "std"))]
println!( println!(
"LLMP_DEBUG: Initializing map on {} with size {}", "LLMP_DEBUG: Initializing map on {} with size {}",
new_map.id().to_string(), new_map.id(),
new_map.len() new_map.len()
); );
@ -1520,7 +1520,7 @@ where
//let bt = "<n/a (release)>"; //let bt = "<n/a (release)>";
dbg!( dbg!(
"LLMP_DEBUG: Using existing map {} with size {}", "LLMP_DEBUG: Using existing map {} with size {}",
existing_map.id().to_string(), existing_map.id(),
existing_map.len(), existing_map.len(),
//bt //bt
); );

View File

@ -94,8 +94,7 @@ impl ServedShMemProvider {
.expect("Did not receive a response"); .expect("Did not receive a response");
let server_id = ShMemId::from_slice(&shm_slice); let server_id = ShMemId::from_slice(&shm_slice);
let server_id_str = server_id.to_string(); let server_fd: i32 = server_id.into();
let server_fd: i32 = server_id_str.parse()?;
Ok((server_fd, fd_buf[0])) 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<Self::Mem, Error> { fn from_id_and_size(&mut self, id: ShMemId, size: usize) -> Result<Self::Mem, Error> {
let parts = id.to_string().split(':').collect::<Vec<&str>>(); let parts = id.as_str().split(':').collect::<Vec<&str>>();
let server_id_str = parts.get(0).unwrap(); let server_id_str = parts.get(0).unwrap();
let (server_fd, client_fd) = self.send_receive(AshmemRequest::ExistingMap( let (server_fd, client_fd) = self.send_receive(AshmemRequest::ExistingMap(
ShMemDescription::from_string_and_size(server_id_str, size), ShMemDescription::from_string_and_size(server_id_str, size),
@ -257,16 +256,17 @@ impl AshmemService {
let description = new_map.description(); let description = new_map.description();
let new_rc = Rc::new(RefCell::new(new_map)); let new_rc = Rc::new(RefCell::new(new_map));
self.all_maps 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)) Ok(AshmemResponse::Mapping(new_rc))
} }
AshmemRequest::ExistingMap(description) => { AshmemRequest::ExistingMap(description) => {
let client = self.clients.get_mut(&client_id).unwrap(); 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( Ok(AshmemResponse::Mapping(
client client
.maps .maps
.get_mut(&description.id.to_int()) .get_mut(&description_id)
.as_mut() .as_mut()
.unwrap() .unwrap()
.first() .first()
@ -277,7 +277,7 @@ impl AshmemService {
} else { } else {
Ok(AshmemResponse::Mapping( Ok(AshmemResponse::Mapping(
self.all_maps self.all_maps
.get_mut(&description.id.to_int()) .get_mut(&description_id)
.unwrap() .unwrap()
.clone() .clone()
.upgrade() .upgrade()

View File

@ -38,7 +38,11 @@ use serde::{Deserialize, Serialize};
use std::env; use std::env;
use alloc::{rc::Rc, string::ToString}; 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"))] #[cfg(all(unix, feature = "std"))]
use crate::bolts::os::pipes::Pipe; use crate::bolts::os::pipes::Pipe;
@ -102,18 +106,28 @@ impl ShMemId {
&self.id &self.id
} }
/// Get a string representation of this id /// Returns the first null-byte in or the end of the buffer
#[must_use] #[must_use]
pub fn to_string(&self) -> &str { pub fn null_pos(&self) -> usize {
let eof_pos = self.id.iter().position(|&c| c == 0).unwrap(); self.id.iter().position(|&c| c == 0).unwrap()
alloc::str::from_utf8(&self.id[..eof_pos]).unwrap()
} }
/// Get an integer representation of this id /// Returns a `str` representation of this [`ShMemId`]
#[must_use] #[must_use]
pub fn to_int(&self) -> i32 { pub fn as_str(&self) -> &str {
let id: i32 = self.to_string().parse().unwrap(); alloc::str::from_utf8(&self.id[..self.null_pos()]).unwrap()
id }
}
impl From<ShMemId> 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 /// Get a [`UnixShMem`] of the existing shared memory mapping identified by id
pub fn from_id_and_size(id: ShMemId, map_size: usize) -> Result<Self, Error> { pub fn from_id_and_size(id: ShMemId, map_size: usize) -> Result<Self, Error> {
unsafe { 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() { if map == usize::MAX as *mut c_void as *mut c_uchar || map.is_null() {
return Err(Error::Unknown( return Err(Error::Unknown(
@ -560,7 +575,8 @@ pub mod unix_shmem {
impl Drop for CommonUnixShMem { impl Drop for CommonUnixShMem {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
shmctl(self.id.to_int(), 0, ptr::null_mut()); let id_int: i32 = self.id.into();
shmctl(id_int, 0, ptr::null_mut());
} }
} }
} }

View File

@ -216,7 +216,7 @@ where
client.update_executions(*executions as u64, *time); client.update_executions(*executions as u64, *time);
// Update the performance stats for this client // 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 // Display the stats via `.display` only on core #1
stats.display(event.name().to_string(), sender_id); stats.display(event.name().to_string(), sender_id);

View File

@ -176,7 +176,8 @@ where
} => { } => {
// TODO: The stats buffer should be added on client add. // 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_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); stats.display(event.name().to_string(), 0);
Ok(BrokerEventResult::Handled) Ok(BrokerEventResult::Handled)
} }

View File

@ -28,7 +28,7 @@ use crate::{
fuzzer::HasObjective, fuzzer::HasObjective,
inputs::Input, inputs::Input,
observers::ObserversTuple, observers::ObserversTuple,
state::HasSolutions, state::{HasClientPerfStats, HasSolutions},
Error, Error,
}; };
@ -170,7 +170,7 @@ where
EM: EventFirer<I, S> + EventRestarter<S>, EM: EventFirer<I, S> + EventRestarter<S>,
OC: Corpus<I>, OC: Corpus<I>,
OF: Feedback<I, S>, OF: Feedback<I, S>,
S: HasSolutions<OC, I>, S: HasSolutions<OC, I> + HasClientPerfStats,
Z: HasObjective<I, OF, S>, Z: HasObjective<I, OF, S>,
{ {
#[cfg(unix)] #[cfg(unix)]
@ -255,7 +255,7 @@ mod unix_signal_handler {
fuzzer::HasObjective, fuzzer::HasObjective,
inputs::Input, inputs::Input,
observers::ObserversTuple, observers::ObserversTuple,
state::HasSolutions, state::{HasClientPerfStats, HasSolutions},
}; };
pub type HandlerFuncPtr = pub type HandlerFuncPtr =
@ -343,7 +343,7 @@ mod unix_signal_handler {
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
OC: Corpus<I>, OC: Corpus<I>,
OF: Feedback<I, S>, OF: Feedback<I, S>,
S: HasSolutions<OC, I>, S: HasSolutions<OC, I> + HasClientPerfStats,
I: Input, I: Input,
Z: HasObjective<I, OF, S>, Z: HasObjective<I, OF, S>,
{ {
@ -417,7 +417,7 @@ mod unix_signal_handler {
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
OC: Corpus<I>, OC: Corpus<I>,
OF: Feedback<I, S>, OF: Feedback<I, S>,
S: HasSolutions<OC, I>, S: HasSolutions<OC, I> + HasClientPerfStats,
I: Input, I: Input,
Z: HasObjective<I, OF, S>, Z: HasObjective<I, OF, S>,
{ {
@ -562,7 +562,7 @@ mod windows_exception_handler {
fuzzer::HasObjective, fuzzer::HasObjective,
inputs::Input, inputs::Input,
observers::ObserversTuple, observers::ObserversTuple,
state::HasSolutions, state::{HasClientPerfStats, HasSolutions},
}; };
pub type HandlerFuncPtr = pub type HandlerFuncPtr =
@ -628,7 +628,7 @@ mod windows_exception_handler {
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
OC: Corpus<I>, OC: Corpus<I>,
OF: Feedback<I, S>, OF: Feedback<I, S>,
S: HasSolutions<OC, I>, S: HasSolutions<OC, I> + HasClientPerfStats,
I: Input, I: Input,
Z: HasObjective<I, OF, S>, Z: HasObjective<I, OF, S>,
{ {

View File

@ -16,7 +16,7 @@ use crate::{
feedbacks::{Feedback, FeedbackState, FeedbackStatesTuple}, feedbacks::{Feedback, FeedbackState, FeedbackStatesTuple},
inputs::Input, inputs::Input,
observers::{MapObserver, ObserversTuple}, observers::{MapObserver, ObserversTuple},
state::{HasFeedbackStates, HasMetadata}, state::{HasClientPerfStats, HasFeedbackStates, HasMetadata},
stats::UserStats, stats::UserStats,
Error, Error,
}; };
@ -211,7 +211,7 @@ where
R: Reducer<T>, R: Reducer<T>,
O: MapObserver<T>, O: MapObserver<T>,
I: Input, I: Input,
S: HasFeedbackStates<FT>, S: HasFeedbackStates<FT> + HasClientPerfStats,
FT: FeedbackStatesTuple, FT: FeedbackStatesTuple,
{ {
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
@ -430,6 +430,7 @@ impl<I, O, S> Feedback<I, S> for ReachabilityFeedback<O>
where where
I: Input, I: Input,
O: MapObserver<usize>, O: MapObserver<usize>,
S: HasClientPerfStats,
{ {
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
&mut self, &mut self,

View File

@ -14,12 +14,10 @@ use crate::{
executors::ExitKind, executors::ExitKind,
inputs::Input, inputs::Input,
observers::{ObserversTuple, TimeObserver}, observers::{ObserversTuple, TimeObserver},
state::HasClientPerfStats,
Error, Error,
}; };
#[cfg(feature = "introspection")]
use crate::stats::NUM_FEEDBACKS;
use core::{marker::PhantomData, time::Duration}; use core::{marker::PhantomData, time::Duration};
/// Feedbacks evaluate the observers. /// Feedbacks evaluate the observers.
@ -28,6 +26,7 @@ use core::{marker::PhantomData, time::Duration};
pub trait Feedback<I, S>: Named pub trait Feedback<I, S>: Named
where where
I: Input, I: Input,
S: HasClientPerfStats,
{ {
/// `is_interesting ` return if an input is worth the addition to the corpus /// `is_interesting ` return if an input is worth the addition to the corpus
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
@ -44,15 +43,13 @@ where
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn is_interesting_with_perf<EM, OT>( fn is_interesting_introspection<EM, OT>(
&mut self, &mut self,
state: &mut S, state: &mut S,
manager: &mut EM, manager: &mut EM,
input: &I, input: &I,
observers: &OT, observers: &OT,
exit_kind: &ExitKind, exit_kind: &ExitKind,
feedback_stats: &mut [u64; NUM_FEEDBACKS],
feedback_index: usize,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
EM: EventFirer<I, S>, EM: EventFirer<I, S>,
@ -67,10 +64,10 @@ where
// Get the elapsed time for checking this feedback // Get the elapsed time for checking this feedback
let elapsed = crate::bolts::cpu::read_time_counter() - start_time; 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 // Add this stat to the feedback metrics
feedback_stats[feedback_index] = elapsed; state
.introspection_stats_mut()
.update_feedback(self.name(), elapsed);
ret ret
} }
@ -114,6 +111,7 @@ where
B: Feedback<I, S>, B: Feedback<I, S>,
FL: FeedbackLogic<A, B, I, S>, FL: FeedbackLogic<A, B, I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
pub first: A, pub first: A,
pub second: B, pub second: B,
@ -127,6 +125,7 @@ where
B: Feedback<I, S>, B: Feedback<I, S>,
FL: FeedbackLogic<A, B, I, S>, FL: FeedbackLogic<A, B, I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn name(&self) -> &str { fn name(&self) -> &str {
self.name.as_ref() self.name.as_ref()
@ -139,6 +138,7 @@ where
B: Feedback<I, S>, B: Feedback<I, S>,
FL: FeedbackLogic<A, B, I, S>, FL: FeedbackLogic<A, B, I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
pub fn new(first: A, second: B) -> Self { pub fn new(first: A, second: B) -> Self {
let name = format!("{} ({},{})", FL::name(), first.name(), second.name()); let name = format!("{} ({},{})", FL::name(), first.name(), second.name());
@ -157,6 +157,7 @@ where
B: Feedback<I, S>, B: Feedback<I, S>,
FL: FeedbackLogic<A, B, I, S>, FL: FeedbackLogic<A, B, I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
&mut self, &mut self,
@ -182,21 +183,19 @@ where
} }
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
fn is_interesting_with_perf<EM, OT>( fn is_interesting_introspection<EM, OT>(
&mut self, &mut self,
state: &mut S, state: &mut S,
manager: &mut EM, manager: &mut EM,
input: &I, input: &I,
observers: &OT, observers: &OT,
exit_kind: &ExitKind, exit_kind: &ExitKind,
feedback_stats: &mut [u64; NUM_FEEDBACKS],
feedback_index: usize,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
EM: EventFirer<I, S>, EM: EventFirer<I, S>,
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
{ {
FL::is_pair_interesting_with_perf( FL::is_pair_interesting_introspection(
&mut self.first, &mut self.first,
&mut self.second, &mut self.second,
state, state,
@ -204,8 +203,6 @@ where
input, input,
observers, observers,
exit_kind, exit_kind,
feedback_stats,
feedback_index,
) )
} }
@ -227,6 +224,7 @@ where
A: Feedback<I, S>, A: Feedback<I, S>,
B: Feedback<I, S>, B: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn name() -> &'static str; fn name() -> &'static str;
@ -245,7 +243,7 @@ where
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn is_pair_interesting_with_perf<EM, OT>( fn is_pair_interesting_introspection<EM, OT>(
first: &mut A, first: &mut A,
second: &mut B, second: &mut B,
state: &mut S, state: &mut S,
@ -253,8 +251,6 @@ where
input: &I, input: &I,
observers: &OT, observers: &OT,
exit_kind: &ExitKind, exit_kind: &ExitKind,
feedback_stats: &mut [u64; NUM_FEEDBACKS],
feedback_index: usize,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
EM: EventFirer<I, S>, EM: EventFirer<I, S>,
@ -271,6 +267,7 @@ where
A: Feedback<I, S>, A: Feedback<I, S>,
B: Feedback<I, S>, B: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn name() -> &'static str { fn name() -> &'static str {
"Eager OR" "Eager OR"
@ -295,7 +292,7 @@ where
} }
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
fn is_pair_interesting_with_perf<EM, OT>( fn is_pair_interesting_introspection<EM, OT>(
first: &mut A, first: &mut A,
second: &mut B, second: &mut B,
state: &mut S, state: &mut S,
@ -303,33 +300,15 @@ where
input: &I, input: &I,
observers: &OT, observers: &OT,
exit_kind: &ExitKind, exit_kind: &ExitKind,
feedback_stats: &mut [u64; NUM_FEEDBACKS],
feedback_index: usize,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
EM: EventFirer<I, S>, EM: EventFirer<I, S>,
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
{ {
// Execute this feedback // Execute this feedback
let a = first.is_interesting_with_perf( let a = first.is_interesting_introspection(state, manager, input, observers, exit_kind)?;
state,
manager,
input,
observers,
exit_kind,
feedback_stats,
feedback_index,
)?;
let b = second.is_interesting_with_perf( let b = second.is_interesting_introspection(state, manager, input, observers, exit_kind)?;
state,
manager,
input,
observers,
exit_kind,
feedback_stats,
feedback_index + 1,
)?;
Ok(a || b) Ok(a || b)
} }
} }
@ -339,6 +318,7 @@ where
A: Feedback<I, S>, A: Feedback<I, S>,
B: Feedback<I, S>, B: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn name() -> &'static str { fn name() -> &'static str {
"Fast OR" "Fast OR"
@ -366,7 +346,7 @@ where
} }
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
fn is_pair_interesting_with_perf<EM, OT>( fn is_pair_interesting_introspection<EM, OT>(
first: &mut A, first: &mut A,
second: &mut B, second: &mut B,
state: &mut S, state: &mut S,
@ -374,37 +354,19 @@ where
input: &I, input: &I,
observers: &OT, observers: &OT,
exit_kind: &ExitKind, exit_kind: &ExitKind,
feedback_stats: &mut [u64; NUM_FEEDBACKS],
feedback_index: usize,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
EM: EventFirer<I, S>, EM: EventFirer<I, S>,
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
{ {
// Execute this feedback // Execute this feedback
let a = first.is_interesting_with_perf( let a = first.is_interesting_introspection(state, manager, input, observers, exit_kind)?;
state,
manager,
input,
observers,
exit_kind,
feedback_stats,
feedback_index,
)?;
if a { if a {
return Ok(true); return Ok(true);
} }
second.is_interesting_with_perf( second.is_interesting_introspection(state, manager, input, observers, exit_kind)
state,
manager,
input,
observers,
exit_kind,
feedback_stats,
feedback_index + 1,
)
} }
} }
@ -413,6 +375,7 @@ where
A: Feedback<I, S>, A: Feedback<I, S>,
B: Feedback<I, S>, B: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn name() -> &'static str { fn name() -> &'static str {
"Eager AND" "Eager AND"
@ -437,7 +400,7 @@ where
} }
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
fn is_pair_interesting_with_perf<EM, OT>( fn is_pair_interesting_introspection<EM, OT>(
first: &mut A, first: &mut A,
second: &mut B, second: &mut B,
state: &mut S, state: &mut S,
@ -445,33 +408,15 @@ where
input: &I, input: &I,
observers: &OT, observers: &OT,
exit_kind: &ExitKind, exit_kind: &ExitKind,
feedback_stats: &mut [u64; NUM_FEEDBACKS],
feedback_index: usize,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
EM: EventFirer<I, S>, EM: EventFirer<I, S>,
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
{ {
// Execute this feedback // Execute this feedback
let a = first.is_interesting_with_perf( let a = first.is_interesting_introspection(state, manager, input, observers, exit_kind)?;
state,
manager,
input,
observers,
exit_kind,
feedback_stats,
feedback_index,
)?;
let b = second.is_interesting_with_perf( let b = second.is_interesting_introspection(state, manager, input, observers, exit_kind)?;
state,
manager,
input,
observers,
exit_kind,
feedback_stats,
feedback_index + 1,
)?;
Ok(a && b) Ok(a && b)
} }
} }
@ -481,6 +426,7 @@ where
A: Feedback<I, S>, A: Feedback<I, S>,
B: Feedback<I, S>, B: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn name() -> &'static str { fn name() -> &'static str {
"Fast AND" "Fast AND"
@ -508,7 +454,7 @@ where
} }
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
fn is_pair_interesting_with_perf<EM, OT>( fn is_pair_interesting_introspection<EM, OT>(
first: &mut A, first: &mut A,
second: &mut B, second: &mut B,
state: &mut S, state: &mut S,
@ -516,37 +462,19 @@ where
input: &I, input: &I,
observers: &OT, observers: &OT,
exit_kind: &ExitKind, exit_kind: &ExitKind,
feedback_stats: &mut [u64; NUM_FEEDBACKS],
feedback_index: usize,
) -> Result<bool, Error> ) -> Result<bool, Error>
where where
EM: EventFirer<I, S>, EM: EventFirer<I, S>,
OT: ObserversTuple<I, S>, OT: ObserversTuple<I, S>,
{ {
// Execute this feedback // Execute this feedback
let a = first.is_interesting_with_perf( let a = first.is_interesting_introspection(state, manager, input, observers, exit_kind)?;
state,
manager,
input,
observers,
exit_kind,
feedback_stats,
feedback_index,
)?;
if !a { if !a {
return Ok(false); return Ok(false);
} }
second.is_interesting_with_perf( second.is_interesting_introspection(state, manager, input, observers, exit_kind)
state,
manager,
input,
observers,
exit_kind,
feedback_stats,
feedback_index + 1,
)
} }
} }
@ -573,6 +501,7 @@ pub struct NotFeedback<A, I, S>
where where
A: Feedback<I, S>, A: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
/// The feedback to invert /// The feedback to invert
pub first: A, pub first: A,
@ -585,6 +514,7 @@ impl<A, I, S> Feedback<I, S> for NotFeedback<A, I, S>
where where
A: Feedback<I, S>, A: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
&mut self, &mut self,
@ -618,6 +548,7 @@ impl<A, I, S> Named for NotFeedback<A, I, S>
where where
A: Feedback<I, S>, A: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
#[inline] #[inline]
fn name(&self) -> &str { fn name(&self) -> &str {
@ -629,6 +560,7 @@ impl<A, I, S> NotFeedback<A, I, S>
where where
A: Feedback<I, S>, A: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
/// Creates a new [`NotFeedback`]. /// Creates a new [`NotFeedback`].
pub fn new(first: A) -> Self { pub fn new(first: A) -> Self {
@ -696,6 +628,7 @@ macro_rules! feedback_not {
impl<I, S> Feedback<I, S> for () impl<I, S> Feedback<I, S> for ()
where where
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
&mut self, &mut self,
@ -727,6 +660,7 @@ pub struct CrashFeedback {}
impl<I, S> Feedback<I, S> for CrashFeedback impl<I, S> Feedback<I, S> for CrashFeedback
where where
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
&mut self, &mut self,
@ -776,6 +710,7 @@ pub struct TimeoutFeedback {}
impl<I, S> Feedback<I, S> for TimeoutFeedback impl<I, S> Feedback<I, S> for TimeoutFeedback
where where
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
&mut self, &mut self,
@ -830,6 +765,7 @@ pub struct TimeFeedback {
impl<I, S> Feedback<I, S> for TimeFeedback impl<I, S> Feedback<I, S> for TimeFeedback
where where
I: Input, I: Input,
S: HasClientPerfStats,
{ {
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
&mut self, &mut self,

View File

@ -44,6 +44,7 @@ pub trait HasFeedback<F, I, S>
where where
F: Feedback<I, S>, F: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
/// The feedback /// The feedback
fn feedback(&self) -> &F; fn feedback(&self) -> &F;
@ -57,6 +58,7 @@ pub trait HasObjective<I, OF, S>
where where
OF: Feedback<I, S>, OF: Feedback<I, S>,
I: Input, I: Input,
S: HasClientPerfStats,
{ {
/// The objective feedback /// The objective feedback
fn objective(&self) -> &OF; fn objective(&self) -> &OF;
@ -240,6 +242,7 @@ where
F: Feedback<I, S>, F: Feedback<I, S>,
I: Input, I: Input,
OF: Feedback<I, S>, OF: Feedback<I, S>,
S: HasClientPerfStats,
{ {
scheduler: CS, scheduler: CS,
feedback: F, feedback: F,
@ -254,6 +257,7 @@ where
F: Feedback<I, S>, F: Feedback<I, S>,
I: Input, I: Input,
OF: Feedback<I, S>, OF: Feedback<I, S>,
S: HasClientPerfStats,
{ {
fn scheduler(&self) -> &CS { fn scheduler(&self) -> &CS {
&self.scheduler &self.scheduler
@ -270,6 +274,7 @@ where
F: Feedback<I, S>, F: Feedback<I, S>,
I: Input, I: Input,
OF: Feedback<I, S>, OF: Feedback<I, S>,
S: HasClientPerfStats,
{ {
fn feedback(&self) -> &F { fn feedback(&self) -> &F {
&self.feedback &self.feedback
@ -286,6 +291,7 @@ where
F: Feedback<I, S>, F: Feedback<I, S>,
I: Input, I: Input,
OF: Feedback<I, S>, OF: Feedback<I, S>,
S: HasClientPerfStats,
{ {
fn objective(&self) -> &OF { fn objective(&self) -> &OF {
&self.objective &self.objective
@ -323,11 +329,15 @@ where
{ {
let mut res = ExecuteInputResult::None; let mut res = ExecuteInputResult::None;
start_timer!(state); #[cfg(not(feature = "introspection"))]
let is_solution = self let is_solution = self
.objective_mut() .objective_mut()
.is_interesting(state, manager, &input, observers, &exit_kind)?; .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 { if is_solution {
res = ExecuteInputResult::Solution; res = ExecuteInputResult::Solution;
@ -338,31 +348,9 @@ where
.is_interesting(state, manager, &input, observers, &exit_kind)?; .is_interesting(state, manager, &input, observers, &exit_kind)?;
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
let is_corpus = { let is_corpus = self
// Init temporary feedback stats here. We can't use the typical pattern above .feedback_mut()
// since we need a `mut self` for `feedbacks_mut`, so we can't also hand a .is_interesting_introspection(state, manager, &input, observers, &exit_kind)?;
// 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
};
if is_corpus { if is_corpus {
res = ExecuteInputResult::Corpus; res = ExecuteInputResult::Corpus;
@ -569,7 +557,7 @@ where
Event::UpdatePerfStats { Event::UpdatePerfStats {
executions: *state.executions(), executions: *state.executions(),
time: cur, time: cur,
introspection_stats: Box::new(*state.introspection_stats()), introspection_stats: Box::new(state.introspection_stats().clone()),
phantom: PhantomData, phantom: PhantomData,
}, },
)?; )?;

View File

@ -113,7 +113,7 @@ where
where where
S: HasMetadata, 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::<CmpValuesMetadata>() { let meta = if let Some(meta) = state.metadata_mut().get_mut::<CmpValuesMetadata>() {
meta meta
} else { } else {

View File

@ -19,12 +19,6 @@ use crate::bolts::current_time;
const CLIENT_STATS_TIME_WINDOW_SECS: u64 = 5; // 5 seconds 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 /// User-defined stats types
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum UserStats { pub enum UserStats {
@ -294,7 +288,7 @@ where
{ {
// Print the client performance stats. // Print the client performance stats.
let fmt = format!( let fmt = format!(
"Client {:03}: {}", "Client {:03}:\n{}",
sender_id, self.client_stats[sender_id as usize].introspection_stats sender_id, self.client_stats[sender_id as usize].introspection_stats
); );
(self.print_fn)(fmt); (self.print_fn)(fmt);
@ -358,7 +352,7 @@ macro_rules! mark_feedback_time {
} }
/// Client performance statistics /// Client performance statistics
#[derive(Serialize, Deserialize, Debug, Copy, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ClientPerfStats { pub struct ClientPerfStats {
/// Starting counter (in clock cycles from [`cpu::read_time_counter`]) /// Starting counter (in clock cycles from [`cpu::read_time_counter`])
start_time: u64, start_time: u64,
@ -375,26 +369,22 @@ pub struct ClientPerfStats {
/// Current stage index to write the next stage benchmark time /// Current stage index to write the next stage benchmark time
curr_stage: u8, 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 /// Flag to dictate this stage is in use. Used during printing to not print the empty
/// stages if they are not in use. /// stages if they are not in use.
stages_used: [bool; NUM_STAGES], stages_used: Vec<bool>,
// TODO(andrea) use an hashmap and indetify feaures using a &'static str
/// Clock cycles spent in the the various features of each stage /// 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. /// Clock cycles spent in each feedback mechanism of the fuzzer.
feedbacks: [u64; NUM_FEEDBACKS], feedbacks: HashMap<String, u64>,
/// Current time set by `start_timer` /// Current time set by `start_timer`
timer_start: Option<u64>, timer_start: Option<u64>,
} }
/// Various features that are measured for performance /// Various features that are measured for performance
#[derive(Serialize, Deserialize, Debug, Copy, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[repr(u8)] #[repr(u8)]
pub enum PerfFeature { pub enum PerfFeature {
/// Getting an input from the corpus /// Getting an input from the corpus
@ -477,7 +467,7 @@ impl From<usize> for PerfFeature {
/// Number of features we can measure for performance /// Number of features we can measure for performance
#[cfg(feature = "introspection")] #[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")] #[cfg(feature = "introspection")]
impl ClientPerfStats { impl ClientPerfStats {
@ -493,10 +483,9 @@ impl ClientPerfStats {
scheduler: 0, scheduler: 0,
manager: 0, manager: 0,
curr_stage: 0, curr_stage: 0,
curr_feedback: 0, stages: vec![],
stages: [[0; NUM_PERF_FEATURES]; NUM_STAGES], stages_used: vec![],
stages_used: [false; NUM_STAGES], feedbacks: HashMap::new(),
feedbacks: [0; NUM_FEEDBACKS],
timer_start: None, timer_start: None,
} }
} }
@ -514,12 +503,12 @@ impl ClientPerfStats {
} }
/// Update the current [`ClientPerfStats`] with the given [`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.set_current_time(stats.current_time);
self.update_scheduler(stats.scheduler); self.update_scheduler(stats.scheduler);
self.update_manager(stats.manager); self.update_manager(stats.manager);
self.update_stages(stats.stages); self.update_stages(&stats.stages);
self.update_feedbacks(stats.feedbacks); self.update_feedbacks(&stats.feedbacks);
} }
/// Gets the elapsed time since the internal timer started. Resets the timer when /// Gets the elapsed time since the internal timer started. Resets the timer when
@ -578,33 +567,6 @@ impl ClientPerfStats {
self.update_feature(feature, elapsed); 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 /// Add the given `time` to the `scheduler` stats
#[inline] #[inline]
pub fn update_scheduler(&mut self, time: u64) { pub fn update_scheduler(&mut self, time: u64) {
@ -637,23 +599,32 @@ impl ClientPerfStats {
self.curr_stage = 0; self.curr_stage = 0;
} }
/// Reset the feedback index counter to zero /// Update the time spent in the feedback
#[inline] pub fn update_feedback(&mut self, name: &str, time: u64) {
pub fn reset_feedback_index(&mut self) { self.feedbacks.insert(
self.curr_feedback = 0; name.into(),
self.feedbacks
.get(name)
.unwrap_or(&0)
.checked_add(time)
.expect("update_feedback overflow"),
);
} }
/// Update the time spent in the feedbacks /// Update the time spent in all the feedbacks
pub fn update_feedbacks(&mut self, feedbacks: [u64; NUM_FEEDBACKS]) { pub fn update_feedbacks(&mut self, feedbacks: &HashMap<String, u64>) {
for (feedback_index, feedback) in feedbacks.iter().enumerate() { for (key, value) in feedbacks {
self.feedbacks[feedback_index] = self.feedbacks[feedback_index] self.update_feedback(key, *value);
.checked_add(*feedback)
.expect("update_feedback overflow");
} }
} }
/// Update the time spent in the stages /// 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 (stage_index, features) in stages.iter().enumerate() {
for (feature_index, feature) in features.iter().enumerate() { for (feature_index, feature) in features.iter().enumerate() {
self.stages[stage_index][feature_index] = self.stages[stage_index][feature_index] 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` /// Update the given [`PerfFeature`] with the given `time`
pub fn update_feature(&mut self, feature: PerfFeature, time: u64) { 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` // Get the current stage index as `usize`
let stage_index: usize = self.curr_stage.try_into().unwrap(); let stage_index: usize = self.curr_stage.try_into().unwrap();
// Get the index of the given feature // Get the index of the given feature
let feature_index: usize = feature.try_into().unwrap(); 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 // Update the given feature
self.stages[stage_index][feature_index] = self.stages[stage_index][feature_index] self.stages[stage_index][feature_index] = self.stages[stage_index][feature_index]
.checked_add(time) .checked_add(time)
@ -689,18 +657,25 @@ impl ClientPerfStats {
self.stages_used[stage_index] = true; self.stages_used[stage_index] = true;
} }
/// The elapsed cycles (or time)
#[must_use]
pub fn elapsed_cycles(&self) -> u64 { pub fn elapsed_cycles(&self) -> u64 {
self.current_time - self.start_time self.current_time - self.start_time
} }
/// The amount of cycles the `manager` did
#[must_use]
pub fn manager_cycles(&self) -> u64 { pub fn manager_cycles(&self) -> u64 {
self.manager self.manager
} }
/// The amount of cycles the `scheduler` did
#[must_use]
pub fn scheduler_cycles(&self) -> u64 { pub fn scheduler_cycles(&self) -> u64 {
self.scheduler self.scheduler
} }
/// Iterator over all used stages
pub fn used_stages( pub fn used_stages(
&self, &self,
) -> impl Iterator<Item = (usize, &[u64; PerfFeature::Count as usize])> { ) -> impl Iterator<Item = (usize, &[u64; PerfFeature::Count as usize])> {
@ -711,8 +686,10 @@ impl ClientPerfStats {
.filter(move |(stage_index, _)| used[*stage_index as usize]) .filter(move |(stage_index, _)| used[*stage_index as usize])
} }
pub fn feedbacks(&self) -> impl Iterator<Item = (usize, &u64)> { /// A map of all `feedbacks`
self.feedbacks.iter().enumerate() #[must_use]
pub fn feedbacks(&self) -> &HashMap<String, u64> {
&self.feedbacks
} }
} }
@ -735,7 +712,7 @@ impl core::fmt::Display for ClientPerfStats {
// Create the formatted string // Create the formatted string
writeln!( writeln!(
f, f,
"Scheduler: {:4.2} | Manager: {:4.2} | Stages:", " {:6.4}: Scheduler\n {:6.4}: Manager",
scheduler_percent, manager_percent scheduler_percent, manager_percent
)?; )?;
@ -763,29 +740,27 @@ impl core::fmt::Display for ClientPerfStats {
// Write the percentage for this feature // Write the percentage for this feature
writeln!(f, " {:6.4}: {:?}", feature_percent, 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(()) Ok(())
} }

View File

@ -80,7 +80,7 @@ where
{ {
// Print the client performance stats. Skip the Client 0 which is the broker // Print the client performance stats. Skip the Client 0 which is the broker
for (i, client) in self.client_stats.iter().skip(1).enumerate() { 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); (self.print_fn)(fmt);
} }

View File

@ -13,7 +13,7 @@ use libafl::{
feedbacks::Feedback, feedbacks::Feedback,
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
observers::{Observer, ObserversTuple}, observers::{Observer, ObserversTuple},
state::HasMetadata, state::{HasClientPerfStats, HasMetadata},
Error, SerdeAny, Error, SerdeAny,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -510,6 +510,7 @@ pub struct AsanErrorsFeedback {
impl<I, S> Feedback<I, S> for AsanErrorsFeedback impl<I, S> Feedback<I, S> for AsanErrorsFeedback
where where
I: Input + HasTargetBytes, I: Input + HasTargetBytes,
S: HasClientPerfStats,
{ {
fn is_interesting<EM, OT>( fn is_interesting<EM, OT>(
&mut self, &mut self,

View File

@ -73,8 +73,6 @@
#define CHECK_WEAK_FN(Name) (Name != NULL) #define CHECK_WEAK_FN(Name) (Name != NULL)
#endif // _MSC_VER #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) \ #define EXT_FUNC_IMPL(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN)
@ -84,10 +82,7 @@
#else #else
#if defined(__APPLE__) #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. // 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) \ #define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \
__attribute__((weak, visibility("default"))) RETURN_TYPE NAME FUNC_SIG { \ __attribute__((weak, visibility("default"))) RETURN_TYPE NAME FUNC_SIG { \