Introduce TuiMonitor builder, clean up monitor docs and names (#2385)

* Introduce TuiMonitor builder

* Some random docs

* More documentation for monitors

* fixed critical whitespace

* Rename all-caps TOML and JSON to Toml and Json in monitors

* actually rename

* more
This commit is contained in:
Dominik Maier 2024-07-12 15:27:45 +02:00 committed by GitHub
parent 24aa640df7
commit 3c93b96b70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 191 additions and 102 deletions

View File

@ -6,6 +6,6 @@ Mutators can be composed, and they are generally linked to a specific Input type
There can be, for instance, a Mutator that applies more than a single type of mutation to the input. Consider a generic Mutator for a byte stream, bit flip is just one of the possible mutations but not the only one, there is also, for instance, the random replacement of a byte of the copy of a chunk. There can be, for instance, a Mutator that applies more than a single type of mutation to the input. Consider a generic Mutator for a byte stream, bit flip is just one of the possible mutations but not the only one, there is also, for instance, the random replacement of a byte of the copy of a chunk.
There are also mutators that always produce valid inputs, say a mutator that generates valid JSON or code, but these grammar based mutators need a grammar to work. There are also mutators that always produce valid inputs, say a mutator that generates valid Json or code, but these grammar based mutators need a grammar to work.
In LibAFL, [`Mutator`](https://docs.rs/libafl/latest/libafl/mutators/trait.Mutator.html) is a trait. In LibAFL, [`Mutator`](https://docs.rs/libafl/latest/libafl/mutators/trait.Mutator.html) is a trait.

View File

@ -3,7 +3,7 @@ use std::ptr::write_volatile;
use std::{path::PathBuf, ptr::write}; use std::{path::PathBuf, ptr::write};
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
use libafl::monitors::tui::{ui::TuiUI, TuiMonitor}; use libafl::monitors::tui::TuiMonitor;
#[cfg(not(feature = "tui"))] #[cfg(not(feature = "tui"))]
use libafl::monitors::SimpleMonitor; use libafl::monitors::SimpleMonitor;
use libafl::{ use libafl::{
@ -118,9 +118,10 @@ pub fn main() {
#[cfg(not(feature = "tui"))] #[cfg(not(feature = "tui"))]
let mon = SimpleMonitor::new(|s| println!("{s}")); let mon = SimpleMonitor::new(|s| println!("{s}"));
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
let ui = TuiUI::with_version(String::from("Baby Fuzzer"), String::from("0.0.1"), false); let mon = TuiMonitor::builder()
#[cfg(feature = "tui")] .title("Baby Fuzzer")
let mon = TuiMonitor::new(ui); .enhanced_graphics(false)
.build();
// The event manager handle the various events generated during the fuzzing loop // The event manager handle the various events generated during the fuzzing loop
// such as the notification of the addition of a new item to the corpus // such as the notification of the addition of a new item to the corpus

View File

@ -6,7 +6,7 @@ use std::{
}; };
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
use libafl::monitors::tui::{ui::TuiUI, TuiMonitor}; use libafl::monitors::tui::TuiMonitor;
#[cfg(not(feature = "tui"))] #[cfg(not(feature = "tui"))]
use libafl::monitors::SimpleMonitor; use libafl::monitors::SimpleMonitor;
use libafl::{ use libafl::{
@ -204,9 +204,10 @@ pub fn main() {
#[cfg(not(feature = "tui"))] #[cfg(not(feature = "tui"))]
let mon = SimpleMonitor::with_user_monitor(|s| println!("{s}")); let mon = SimpleMonitor::with_user_monitor(|s| println!("{s}"));
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
let ui = TuiUI::new(String::from("Baby Fuzzer"), false); let mon = TuiMonitor::builder()
#[cfg(feature = "tui")] .title("Baby Fuzzer")
let mon = TuiMonitor::new(ui); .enhanced_graphics(false)
.build();
// The event manager handle the various events generated during the fuzzing loop // The event manager handle the various events generated during the fuzzing loop
// such as the notification of the addition of a new item to the corpus // such as the notification of the addition of a new item to the corpus

View File

@ -3,7 +3,7 @@ use std::ptr::write_volatile;
use std::{path::PathBuf, ptr::write}; use std::{path::PathBuf, ptr::write};
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
use libafl::monitors::tui::{ui::TuiUI, TuiMonitor}; use libafl::monitors::tui::TuiMonitor;
#[cfg(not(feature = "tui"))] #[cfg(not(feature = "tui"))]
use libafl::monitors::SimpleMonitor; use libafl::monitors::SimpleMonitor;
use libafl::{ use libafl::{
@ -85,9 +85,11 @@ pub fn main() {
#[cfg(not(feature = "tui"))] #[cfg(not(feature = "tui"))]
let mon = SimpleMonitor::new(|s| println!("{s}")); let mon = SimpleMonitor::new(|s| println!("{s}"));
#[cfg(feature = "tui")] #[cfg(feature = "tui")]
let ui = TuiUI::with_version(String::from("Baby Fuzzer"), String::from("0.0.1"), false); let mon = TuiMonitor::builder()
#[cfg(feature = "tui")] .title("Baby Fuzzer")
let mon = TuiMonitor::new(ui); .version("0.0.1")
.enhanced_graphics(false)
.build();
// The event manager handle the various events generated during the fuzzing loop // The event manager handle the various events generated during the fuzzing loop
// such as the notification of the addition of a new item to the corpus // such as the notification of the addition of a new item to the corpus

View File

@ -25,7 +25,7 @@ def concatenate_json_files(input_dir):
with open(output_file, 'w') as file: with open(output_file, 'w') as file:
json.dump([data], file) json.dump([data], file)
print(f"JSON files concatenated successfully! Output file: {output_file}") print(f"Json files concatenated successfully! Output file: {output_file}")
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) != 2: if len(sys.argv) != 2:

View File

@ -4,7 +4,7 @@
#include <string.h> #include <string.h>
extern "C" __declspec(dllexport) size_t extern "C" __declspec(dllexport) size_t
LLVMFuzzerTestOneInput(const char *data, unsigned int len) { LLVMFuzzerTestOneInput(const char *data, unsigned int len) {
if (data[0] == 'b') { if (data[0] == 'b') {
if (data[1] == 'a') { if (data[1] == 'a') {
if (data[2] == 'd') { if (data[2] == 'd') {

View File

@ -13,7 +13,7 @@ use libafl::{
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes}, inputs::{BytesInput, HasTargetBytes},
monitors::tui::{ui::TuiUI, TuiMonitor}, monitors::tui::TuiMonitor,
mutators::{ mutators::{
scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
token_mutations::Tokens, token_mutations::Tokens,
@ -70,12 +70,12 @@ fn fuzz(
// let monitor = MultiMonitor::new(|s| println!("{s}")); // let monitor = MultiMonitor::new(|s| println!("{s}"));
//Setup an Monitor with AFL-Style UI to display the stats //Setup an Monitor with AFL-Style UI to display the stats
let ui = TuiUI::with_version( #[cfg(feature = "tui")]
String::from("Libfuzzer For Libpng"), let monitor = TuiMonitor::builder()
String::from("0.0.1"), .title("Libfuzzer in LibAFL")
false, .version("0.0.1")
); .enhanced_graphics(true)
let monitor = TuiMonitor::new(ui); .build();
// The restarting state will spawn the same process again as child, then restarted it each time it crashes. // The restarting state will spawn the same process again as child, then restarted it each time it crashes.
let (state, mut restarting_mgr) = let (state, mut restarting_mgr) =

View File

@ -14,7 +14,7 @@ use libafl::{
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes}, inputs::{BytesInput, HasTargetBytes},
monitors::{MultiMonitor, OnDiskTOMLMonitor}, monitors::{MultiMonitor, OnDiskTomlMonitor},
mutators::{ mutators::{
scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
token_mutations::Tokens, token_mutations::Tokens,
@ -131,7 +131,7 @@ pub extern "C" fn libafl_main() {
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
let monitor = OnDiskTOMLMonitor::new( let monitor = OnDiskTomlMonitor::new(
"./fuzzer_stats.toml", "./fuzzer_stats.toml",
MultiMonitor::new(|s| println!("{s}")), MultiMonitor::new(|s| println!("{s}")),
); );

View File

@ -18,7 +18,7 @@ use libafl::{
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes}, inputs::{BytesInput, HasTargetBytes},
monitors::{MultiMonitor, OnDiskTOMLMonitor}, monitors::{MultiMonitor, OnDiskTomlMonitor},
mutators::{ mutators::{
scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
token_mutations::Tokens, token_mutations::Tokens,
@ -154,7 +154,7 @@ pub extern "C" fn libafl_main() {
let shmem_provider = MmapShMemProvider::new().expect("Failed to init shared memory"); let shmem_provider = MmapShMemProvider::new().expect("Failed to init shared memory");
let monitor = OnDiskTOMLMonitor::new( let monitor = OnDiskTomlMonitor::new(
"./fuzzer_stats.toml", "./fuzzer_stats.toml",
MultiMonitor::new(|s| println!("{s}")), MultiMonitor::new(|s| println!("{s}")),
); );

View File

@ -5,7 +5,7 @@ use libafl::{
events::SimpleEventManager, events::SimpleEventManager,
feedbacks::{CrashFeedback, MaxMapFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback},
inputs::BytesInput, inputs::BytesInput,
monitors::tui::{ui::TuiUI, TuiMonitor}, monitors::tui::TuiMonitor,
mutators::{havoc_mutations, StdScheduledMutator}, mutators::{havoc_mutations, StdScheduledMutator},
observers::StdMapObserver, observers::StdMapObserver,
schedulers::RandScheduler, schedulers::RandScheduler,
@ -40,8 +40,7 @@ fn main() {
// switch monitor if you want // switch monitor if you want
// let monitor = SimpleMonitor::new(|x|-> () {println!("{}",x)}); // let monitor = SimpleMonitor::new(|x|-> () {println!("{}",x)});
let ui = TuiUI::new(String::from("test_fuzz"), true); let monitor = TuiMonitor::builder().title("test_fuzz").build();
let monitor = TuiMonitor::new(ui);
let mut mgr = SimpleEventManager::new(monitor); let mut mgr = SimpleEventManager::new(monitor);
let mut executor = NyxExecutor::builder().build(helper, tuple_list!(observer)); let mut executor = NyxExecutor::builder().build(helper, tuple_list!(observer));

View File

@ -10,10 +10,7 @@ use libafl::events::SimpleEventManager;
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
use libafl::events::{EventConfig, Launcher, MonitorTypedEventManager}; use libafl::events::{EventConfig, Launcher, MonitorTypedEventManager};
use libafl::{ use libafl::{
monitors::{ monitors::{tui::TuiMonitor, Monitor, MultiMonitor},
tui::{ui::TuiUI, TuiMonitor},
Monitor, MultiMonitor,
},
Error, Error,
}; };
#[cfg(feature = "simplemgr")] #[cfg(feature = "simplemgr")]
@ -42,9 +39,11 @@ impl Fuzzer {
pub fn fuzz(&self) -> Result<(), Error> { pub fn fuzz(&self) -> Result<(), Error> {
if self.options.tui { if self.options.tui {
let ui = let monitor = TuiMonitor::builder()
TuiUI::with_version(String::from("QEMU Launcher"), String::from("0.10.1"), true); .title("QEMU Launcher")
let monitor = TuiMonitor::new(ui); .version("0.13.1")
.enhanced_graphics(true)
.build();
self.launch(monitor) self.launch(monitor)
} else { } else {
let log = self.options.log.as_ref().and_then(|l| { let log = self.options.log.as_ref().and_then(|l| {

View File

@ -24,9 +24,9 @@ use crate::{
pub enum OnDiskMetadataFormat { pub enum OnDiskMetadataFormat {
/// A binary-encoded postcard /// A binary-encoded postcard
Postcard, Postcard,
/// JSON /// Json
Json, Json,
/// JSON formatted for readability /// Json formatted for readability
#[default] #[default]
JsonPretty, JsonPretty,
/// The same as [`OnDiskMetadataFormat::JsonPretty`], but compressed /// The same as [`OnDiskMetadataFormat::JsonPretty`], but compressed

View File

@ -1,4 +1,4 @@
//! Monitors that wrap a base one and log on disk //! Monitors that wrap a base monitor and also log to disk using different formats.
use alloc::{string::String, vec::Vec}; use alloc::{string::String, vec::Vec};
use core::time::Duration; use core::time::Duration;
@ -13,9 +13,9 @@ use serde_json::json;
use crate::monitors::{ClientStats, Monitor, NopMonitor}; use crate::monitors::{ClientStats, Monitor, NopMonitor};
/// Wrap a monitor and log the current state of the monitor into a TOML file. /// Wrap a monitor and log the current state of the monitor into a Toml file.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct OnDiskTOMLMonitor<M> pub struct OnDiskTomlMonitor<M>
where where
M: Monitor, M: Monitor,
{ {
@ -25,7 +25,7 @@ where
update_interval: Duration, update_interval: Duration,
} }
impl<M> Monitor for OnDiskTOMLMonitor<M> impl<M> Monitor for OnDiskTomlMonitor<M>
where where
M: Monitor, M: Monitor,
{ {
@ -59,10 +59,10 @@ where
if cur_time - self.last_update >= self.update_interval { if cur_time - self.last_update >= self.update_interval {
self.last_update = cur_time; self.last_update = cur_time;
let mut file = File::create(&self.filename).expect("Failed to open the TOML file"); let mut file = File::create(&self.filename).expect("Failed to open the Toml file");
write!( write!(
&mut file, &mut file,
"# This TOML is generated using the OnDiskMonitor component of LibAFL "# This Toml is generated using the OnDiskMonitor component of LibAFL
[global] [global]
run_time = \"{}\" run_time = \"{}\"
@ -79,7 +79,7 @@ exec_sec = {}
self.total_execs(), self.total_execs(),
self.execs_per_sec() self.execs_per_sec()
) )
.expect("Failed to write to the TOML file"); .expect("Failed to write to the Toml file");
for (i, client) in self.client_stats_mut().iter_mut().enumerate() { for (i, client) in self.client_stats_mut().iter_mut().enumerate() {
let exec_sec = client.execs_per_sec(cur_time); let exec_sec = client.execs_per_sec(cur_time);
@ -95,7 +95,7 @@ exec_sec = {}
", ",
i, client.corpus_size, client.objective_size, client.executions, exec_sec i, client.corpus_size, client.objective_size, client.executions, exec_sec
) )
.expect("Failed to write to the TOML file"); .expect("Failed to write to the Toml file");
for (key, val) in &client.user_monitor { for (key, val) in &client.user_monitor {
let k: String = key let k: String = key
@ -104,7 +104,7 @@ exec_sec = {}
.filter(|c| c.is_alphanumeric() || *c == '_') .filter(|c| c.is_alphanumeric() || *c == '_')
.collect(); .collect();
writeln!(&mut file, "{k} = \"{val}\"") writeln!(&mut file, "{k} = \"{val}\"")
.expect("Failed to write to the TOML file"); .expect("Failed to write to the Toml file");
} }
} }
@ -115,11 +115,11 @@ exec_sec = {}
} }
} }
impl<M> OnDiskTOMLMonitor<M> impl<M> OnDiskTomlMonitor<M>
where where
M: Monitor, M: Monitor,
{ {
/// Create new [`OnDiskTOMLMonitor`] /// Create new [`OnDiskTomlMonitor`]
#[must_use] #[must_use]
pub fn new<P>(filename: P, base: M) -> Self pub fn new<P>(filename: P, base: M) -> Self
where where
@ -128,7 +128,7 @@ where
Self::with_update_interval(filename, base, Duration::from_secs(60)) Self::with_update_interval(filename, base, Duration::from_secs(60))
} }
/// Create new [`OnDiskTOMLMonitor`] with custom update interval /// Create new [`OnDiskTomlMonitor`] with custom update interval
#[must_use] #[must_use]
pub fn with_update_interval<P>(filename: P, base: M, update_interval: Duration) -> Self pub fn with_update_interval<P>(filename: P, base: M, update_interval: Duration) -> Self
where where
@ -143,8 +143,8 @@ where
} }
} }
impl OnDiskTOMLMonitor<NopMonitor> { impl OnDiskTomlMonitor<NopMonitor> {
/// Create new [`OnDiskTOMLMonitor`] without a base /// Create new [`OnDiskTomlMonitor`] without a base
#[must_use] #[must_use]
pub fn nop<P>(filename: P) -> Self pub fn nop<P>(filename: P) -> Self
where where
@ -155,8 +155,8 @@ impl OnDiskTOMLMonitor<NopMonitor> {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Wraps a base monitor and continuously appends the current statistics to a JSON lines file. /// Wraps a base monitor and continuously appends the current statistics to a Json lines file.
pub struct OnDiskJSONMonitor<F, M> pub struct OnDiskJsonMonitor<F, M>
where where
F: FnMut(&mut M) -> bool, F: FnMut(&mut M) -> bool,
M: Monitor, M: Monitor,
@ -167,12 +167,12 @@ where
log_record: F, log_record: F,
} }
impl<F, M> OnDiskJSONMonitor<F, M> impl<F, M> OnDiskJsonMonitor<F, M>
where where
F: FnMut(&mut M) -> bool, F: FnMut(&mut M) -> bool,
M: Monitor, M: Monitor,
{ {
/// Create a new [`OnDiskJSONMonitor`] /// Create a new [`OnDiskJsonMonitor`]
pub fn new<P>(filename: P, base: M, log_record: F) -> Self pub fn new<P>(filename: P, base: M, log_record: F) -> Self
where where
P: Into<PathBuf>, P: Into<PathBuf>,
@ -187,7 +187,7 @@ where
} }
} }
impl<F, M> Monitor for OnDiskJSONMonitor<F, M> impl<F, M> Monitor for OnDiskJsonMonitor<F, M>
where where
F: FnMut(&mut M) -> bool, F: FnMut(&mut M) -> bool,
M: Monitor, M: Monitor,
@ -225,7 +225,7 @@ where
"exec_sec": self.base.execs_per_sec(), "exec_sec": self.base.execs_per_sec(),
"client_stats": self.client_stats(), "client_stats": self.client_stats(),
}); });
writeln!(&file, "{line}").expect("Unable to write JSON to file"); writeln!(&file, "{line}").expect("Unable to write Json to file");
} }
self.base.display(event_msg, sender_id); self.base.display(event_msg, sender_id);
} }

View File

@ -4,7 +4,6 @@ pub mod multi;
pub use multi::MultiMonitor; pub use multi::MultiMonitor;
#[cfg(all(feature = "tui_monitor", feature = "std"))] #[cfg(all(feature = "tui_monitor", feature = "std"))]
#[allow(missing_docs)]
pub mod tui; pub mod tui;
#[cfg(all(feature = "prometheus_monitor", feature = "std"))] #[cfg(all(feature = "prometheus_monitor", feature = "std"))]
@ -20,7 +19,7 @@ use alloc::{borrow::Cow, fmt::Debug, string::String, vec::Vec};
use core::{fmt, fmt::Write, time::Duration}; use core::{fmt, fmt::Write, time::Duration};
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use disk::{OnDiskJSONMonitor, OnDiskTOMLMonitor}; pub use disk::{OnDiskJsonMonitor, OnDiskTomlMonitor};
use hashbrown::HashMap; use hashbrown::HashMap;
use libafl_bolts::{current_time, format_duration_hms, ClientId}; use libafl_bolts::{current_time, format_duration_hms, ClientId};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -1,26 +1,31 @@
// ===== overview for prommon ===== //! Prometheus Monitor to log to a prometheus endpoint.
// The client (i.e., the fuzzer) sets up an HTTP endpoint (/metrics). //!
// The endpoint contains metrics such as execution rate. //! ## Overview
//!
// A prometheus server (can use a precompiled binary or docker) then scrapes \ //! The client (i.e., the fuzzer) sets up an HTTP endpoint (/metrics).
// the endpoint at regular intervals (configurable via prometheus.yml file). //! The endpoint contains metrics such as execution rate.
// ==================== //!
// //! A prometheus server (can use a precompiled binary or docker) then scrapes \
// == how to use it === //! the endpoint at regular intervals (configurable via prometheus.yml file).
// This monitor should plug into any fuzzer similar to other monitors. //!
// In your fuzzer, include: //! ## How to use it
// ```rust,ignore //!
// use libafl::monitors::PrometheusMonitor; //! This monitor should plug into any fuzzer similar to other monitors.
// ``` //! In your fuzzer:
// as well as: //!
// ```rust,ignore //! ```rust
// let listener = "127.0.0.1:8080".to_string(); // point prometheus to scrape here in your prometheus.yml //! // First, include:
// let mon = PrometheusMonitor::new(listener, |s| log::info!("{s}")); //! use libafl::monitors::PrometheusMonitor;
// and then like with any other monitor, pass it into the event manager like so: //!
// let mut mgr = SimpleEventManager::new(mon); //! // Then, create the monitor:
// ``` //! let listener = "127.0.0.1:8080".to_string(); // point prometheus to scrape here in your prometheus.yml
// When using docker, you may need to point prometheus.yml to the docker0 interface or host.docker.internal //! let mon = PrometheusMonitor::new(listener, |s| log::info!("{s}"));
// ==================== //!
//! // and finally, like with any other monitor, pass it into the event manager like so:
//! // let mgr = SimpleEventManager::new(mon);
//! ```
//!
//! When using docker, you may need to point `prometheus.yml` to the `docker0` interface or `host.docker.internal`
use alloc::{borrow::Cow, fmt::Debug, string::String, vec::Vec}; use alloc::{borrow::Cow, fmt::Debug, string::String, vec::Vec};
use core::{fmt, time::Duration}; use core::{fmt, time::Duration};

View File

@ -1,4 +1,4 @@
//! Monitor based on ratatui //! Fancy-looking terminal UI monitor, similar to AFL, based on [ratatui](https://ratatui.rs/)
use alloc::{borrow::Cow, boxed::Box, string::ToString}; use alloc::{borrow::Cow, boxed::Box, string::ToString};
use core::cmp; use core::cmp;
@ -24,30 +24,60 @@ use hashbrown::HashMap;
use libafl_bolts::{current_time, format_duration_hms, ClientId}; use libafl_bolts::{current_time, format_duration_hms, ClientId};
use ratatui::{backend::CrosstermBackend, Terminal}; use ratatui::{backend::CrosstermBackend, Terminal};
use serde_json::Value; use serde_json::Value;
use typed_builder::TypedBuilder;
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
use super::{ClientPerfMonitor, PerfFeature}; use super::{ClientPerfMonitor, PerfFeature};
use crate::monitors::{Aggregator, AggregatorOps, ClientStats, Monitor, UserStats, UserStatsValue}; use crate::monitors::{Aggregator, AggregatorOps, ClientStats, Monitor, UserStats, UserStatsValue};
#[allow(missing_docs)]
pub mod ui; pub mod ui;
use ui::TuiUI; use ui::TuiUi;
const DEFAULT_TIME_WINDOW: u64 = 60 * 10; // 10 min const DEFAULT_TIME_WINDOW: u64 = 60 * 10; // 10 min
const DEFAULT_LOGS_NUMBER: usize = 128; const DEFAULT_LOGS_NUMBER: usize = 128;
#[derive(Debug, Clone, TypedBuilder)]
#[builder(build_method(into = TuiMonitor), builder_method(vis = "pub(crate)",
doc = "Build the [`TuiMonitor`] from the set values"))]
/// Settings to create a new [`TuiMonitor`].
/// Use `TuiMonitor::builder()` or create this config and call `.into()` to create a new [`TuiMonitor`].
pub struct TuiMonitorConfig {
/// The title to show
#[builder(default_code = r#""LibAFL Fuzzer".to_string()"#, setter(into))]
pub title: String,
/// A version string to show for this (optional)
#[builder(default_code = r#""default".to_string()"#, setter(into))]
pub version: String,
/// Creates the monitor with an explicit `start_time`.
/// If nothings was set, this will use [`current_time`] instead.
#[builder(default_code = "current_time()")]
pub start_time: Duration,
/// Enables unicode TUI graphics, Looks better but may interfere with old terminals.
#[builder(default = true)]
pub enhanced_graphics: bool,
}
/// A single status entry for timings
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct TimedStat { pub struct TimedStat {
/// The time
pub time: Duration, pub time: Duration,
/// The item
pub item: u64, pub item: u64,
} }
/// Stats for timings
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TimedStats { pub struct TimedStats {
/// Series of [`TimedStat`] entries
pub series: VecDeque<TimedStat>, pub series: VecDeque<TimedStat>,
/// The time window to keep track of
pub window: Duration, pub window: Duration,
} }
impl TimedStats { impl TimedStats {
/// Create a new [`TimedStats`] struct
#[must_use] #[must_use]
pub fn new(window: Duration) -> Self { pub fn new(window: Duration) -> Self {
Self { Self {
@ -56,6 +86,7 @@ impl TimedStats {
} }
} }
/// Add a stat datapoint
pub fn add(&mut self, time: Duration, item: u64) { pub fn add(&mut self, time: Duration, item: u64) {
if self.series.is_empty() || self.series.back().unwrap().item != item { if self.series.is_empty() || self.series.back().unwrap().item != item {
if self.series.front().is_some() if self.series.front().is_some()
@ -67,6 +98,7 @@ impl TimedStats {
} }
} }
/// Add a stat datapoint for the `current_time`
pub fn add_now(&mut self, item: u64) { pub fn add_now(&mut self, item: u64) {
if self.series.is_empty() || self.series[self.series.len() - 1].item != item { if self.series.is_empty() || self.series[self.series.len() - 1].item != item {
let time = current_time(); let time = current_time();
@ -79,6 +111,7 @@ impl TimedStats {
} }
} }
/// Change the window duration
pub fn update_window(&mut self, window: Duration) { pub fn update_window(&mut self, window: Duration) {
self.window = window; self.window = window;
while !self.series.is_empty() while !self.series.is_empty()
@ -89,6 +122,7 @@ impl TimedStats {
} }
} }
/// The context to show performance metrics
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct PerfTuiContext { pub struct PerfTuiContext {
@ -101,6 +135,7 @@ pub struct PerfTuiContext {
#[cfg(feature = "introspection")] #[cfg(feature = "introspection")]
impl PerfTuiContext { impl PerfTuiContext {
/// Get the data for performance metrics
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
pub fn grab_data(&mut self, m: &ClientPerfMonitor) { pub fn grab_data(&mut self, m: &ClientPerfMonitor) {
// Calculate the elapsed time from the monitor // Calculate the elapsed time from the monitor
@ -164,15 +199,21 @@ impl PerfTuiContext {
} }
} }
/// Data struct to process timings
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ProcessTiming { pub struct ProcessTiming {
/// The start time
pub client_start_time: Duration, pub client_start_time: Duration,
/// The executions speed
pub exec_speed: String, pub exec_speed: String,
/// Timing of the last new corpus entry
pub last_new_entry: Duration, pub last_new_entry: Duration,
/// Timing of the last new solution
pub last_saved_solution: Duration, pub last_saved_solution: Duration,
} }
impl ProcessTiming { impl ProcessTiming {
/// Create a new [`ProcessTiming`] struct
fn new() -> Self { fn new() -> Self {
Self { Self {
exec_speed: "0".to_string(), exec_speed: "0".to_string(),
@ -181,6 +222,8 @@ impl ProcessTiming {
} }
} }
/// The geometry of a single data point
#[allow(missing_docs)]
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ItemGeometry { pub struct ItemGeometry {
pub pending: u64, pub pending: u64,
@ -199,6 +242,8 @@ impl ItemGeometry {
} }
} }
/// The context for a single client tracked in this [`TuiMonitor`]
#[allow(missing_docs)]
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ClientTuiContext { pub struct ClientTuiContext {
pub corpus: u64, pub corpus: u64,
@ -215,6 +260,7 @@ pub struct ClientTuiContext {
} }
impl ClientTuiContext { impl ClientTuiContext {
/// Grab data for a single client
pub fn grab_data(&mut self, client: &ClientStats, exec_sec: String) { pub fn grab_data(&mut self, client: &ClientStats, exec_sec: String) {
self.corpus = client.corpus_size; self.corpus = client.corpus_size;
self.objectives = client.objective_size; self.objectives = client.objective_size;
@ -267,6 +313,8 @@ impl ClientTuiContext {
} }
} }
/// The [`TuiContext`] for this [`TuiMonitor`]
#[allow(missing_docs)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TuiContext { pub struct TuiContext {
pub graphs: Vec<String>, pub graphs: Vec<String>,
@ -326,7 +374,7 @@ impl TuiContext {
} }
} }
/// Tracking monitor during fuzzing and display with ratatui /// Tracking monitor during fuzzing and display with [`ratatui`](https://ratatui.rs/)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TuiMonitor { pub struct TuiMonitor {
pub(crate) context: Arc<RwLock<TuiContext>>, pub(crate) context: Arc<RwLock<TuiContext>>,
@ -336,6 +384,16 @@ pub struct TuiMonitor {
aggregator: Aggregator, aggregator: Aggregator,
} }
impl From<TuiMonitorConfig> for TuiMonitor {
#[allow(deprecated)]
fn from(builder: TuiMonitorConfig) -> Self {
Self::with_time(
TuiUi::with_version(builder.title, builder.version, builder.enhanced_graphics),
builder.start_time,
)
}
}
impl Monitor for TuiMonitor { impl Monitor for TuiMonitor {
/// The client monitor, mutable /// The client monitor, mutable
/// This also includes disabled "padding" clients. /// This also includes disabled "padding" clients.
@ -443,15 +501,35 @@ impl Monitor for TuiMonitor {
} }
impl TuiMonitor { impl TuiMonitor {
/// Creates the monitor /// Create a builder for [`TuiMonitor`]
pub fn builder() -> TuiMonitorConfigBuilder {
TuiMonitorConfig::builder()
}
/// Creates the monitor.
///
/// # Deprecation Note
/// Use `TuiMonitor::builder()` instead.
#[deprecated(
since = "0.13.2",
note = "Please use TuiMonitor::builder() instead of creating TuiUi directly."
)]
#[must_use] #[must_use]
pub fn new(tui_ui: TuiUI) -> Self { #[allow(deprecated)]
pub fn new(tui_ui: TuiUi) -> Self {
Self::with_time(tui_ui, current_time()) Self::with_time(tui_ui, current_time())
} }
/// Creates the monitor with a given `start_time`. /// Creates the monitor with a given `start_time`.
///
/// # Deprecation Note
/// Use `TuiMonitor::builder()` instead.
#[deprecated(
since = "0.13.2",
note = "Please use TuiMonitor::builder() instead of creating TuiUi directly."
)]
#[must_use] #[must_use]
pub fn with_time(tui_ui: TuiUI, start_time: Duration) -> Self { pub fn with_time(tui_ui: TuiUi, start_time: Duration) -> Self {
let context = Arc::new(RwLock::new(TuiContext::new(start_time))); let context = Arc::new(RwLock::new(TuiContext::new(start_time)));
enable_raw_mode().unwrap(); enable_raw_mode().unwrap();
@ -565,7 +643,7 @@ impl TuiMonitor {
fn run_tui_thread<W: Write + Send + Sync + 'static>( fn run_tui_thread<W: Write + Send + Sync + 'static>(
context: Arc<RwLock<TuiContext>>, context: Arc<RwLock<TuiContext>>,
tick_rate: Duration, tick_rate: Duration,
tui_ui: TuiUI, tui_ui: TuiUi,
stdout_provider: impl Send + Sync + 'static + Fn() -> W, stdout_provider: impl Send + Sync + 'static + Fn() -> W,
) { ) {
thread::spawn(move || -> io::Result<()> { thread::spawn(move || -> io::Result<()> {

View File

@ -21,7 +21,7 @@ use super::{
}; };
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct TuiUI { pub struct TuiUi {
title: String, title: String,
version: String, version: String,
enhanced_graphics: bool, enhanced_graphics: bool,
@ -34,13 +34,13 @@ pub struct TuiUI {
pub should_quit: bool, pub should_quit: bool,
} }
impl TuiUI { impl TuiUi {
#[must_use] #[must_use]
pub fn new(title: String, enhanced_graphics: bool) -> Self { pub fn new(title: String, enhanced_graphics: bool) -> Self {
Self::with_version(title, String::from("default"), enhanced_graphics) Self::with_version(title, String::from("default"), enhanced_graphics)
} }
// create the TuiUI with a given `version`. // create the TuiUi with a given `version`.
#[must_use] #[must_use]
pub fn with_version(title: String, version: String, enhanced_graphics: bool) -> Self { pub fn with_version(title: String, version: String, enhanced_graphics: bool) -> Self {
Self { Self {
@ -49,7 +49,7 @@ impl TuiUI {
enhanced_graphics, enhanced_graphics,
show_logs: true, show_logs: true,
clients_idx: 1, clients_idx: 1,
..TuiUI::default() ..TuiUi::default()
} }
} }
pub fn on_key(&mut self, c: char) { pub fn on_key(&mut self, c: char) {

View File

@ -18,10 +18,7 @@ use libafl::{
}, },
executors::ExitKind, executors::ExitKind,
inputs::UsesInput, inputs::UsesInput,
monitors::{ monitors::{tui::TuiMonitor, Monitor, MultiMonitor},
tui::{ui::TuiUI, TuiMonitor},
Monitor, MultiMonitor,
},
stages::{HasCurrentStage, StagesTuple}, stages::{HasCurrentStage, StagesTuple},
state::{HasExecutions, HasLastReportTime, HasSolutions, Stoppable, UsesState}, state::{HasExecutions, HasLastReportTime, HasSolutions, Stoppable, UsesState},
Error, Fuzzer, HasMetadata, Error, Fuzzer, HasMetadata,
@ -208,7 +205,10 @@ pub fn fuzz(
if let Some(forks) = options.forks() { if let Some(forks) = options.forks() {
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
if options.tui() { if options.tui() {
let monitor = TuiMonitor::new(TuiUI::new(options.fuzzer_name().to_string(), true)); let monitor = TuiMonitor::builder()
.title(options.fuzzer_name())
.enhanced_graphics(true)
.build();
fuzz_many_forking(options, harness, shmem_provider, forks, monitor) fuzz_many_forking(options, harness, shmem_provider, forks, monitor)
} else if forks == 1 { } else if forks == 1 {
let monitor = MultiMonitor::with_time( let monitor = MultiMonitor::with_time(
@ -227,7 +227,10 @@ pub fn fuzz(
// if the user specifies TUI, we assume they want to fork; it would not be possible to use // if the user specifies TUI, we assume they want to fork; it would not be possible to use
// TUI safely otherwise // TUI safely otherwise
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
let monitor = TuiMonitor::new(TuiUI::new(options.fuzzer_name().to_string(), true)); let monitor = TuiMonitor::builder()
.title(options.fuzzer_name())
.enhanced_graphics(true)
.build();
fuzz_many_forking(options, harness, shmem_provider, 1, monitor) fuzz_many_forking(options, harness, shmem_provider, 1, monitor)
} else { } else {
destroy_output_fds(options); destroy_output_fds(options);

View File

@ -35,6 +35,8 @@ for task in output[
print(os.environ) print(os.environ)
if "libafl_frida" in task: if "libafl_frida" in task:
# DOCS_RS is needed for libafl_frida to build without auto-download feature # DOCS_RS is needed for libafl_frida to build without auto-download feature
cargo_check = subprocess.check_output(task, shell=True, text=True, env=dict(os.environ, DOCS_RS="1")) cargo_check = subprocess.check_output(
task, shell=True, text=True, env=dict(os.environ, DOCS_RS="1")
)
else: else:
cargo_check = subprocess.check_output(task, shell=True, text=True) cargo_check = subprocess.check_output(task, shell=True, text=True)