721 lines
28 KiB
Rust
721 lines
28 KiB
Rust
|
|
use hashbrown::HashSet;
|
|
use libafl::inputs::Input;
|
|
/// Feedbacks organizing SystemStates as a graph
|
|
use libafl::SerdeAny;
|
|
use libafl_bolts::ownedref::OwnedMutSlice;
|
|
use petgraph::graph::EdgeIndex;
|
|
use libafl::prelude::UsesInput;
|
|
use libafl::common::HasNamedMetadata;
|
|
use libafl::state::UsesState;
|
|
use libafl::prelude::State;
|
|
use libafl::schedulers::MinimizerScheduler;
|
|
use libafl_bolts::HasRefCnt;
|
|
use std::path::PathBuf;
|
|
use libafl::corpus::Testcase;
|
|
use std::collections::hash_map::DefaultHasher;
|
|
use std::hash::Hasher;
|
|
use std::hash::Hash;
|
|
use libafl::events::EventFirer;
|
|
use libafl::state::MaybeHasClientPerfMonitor;
|
|
use libafl::feedbacks::Feedback;
|
|
use libafl_bolts::Named;
|
|
use libafl::Error;
|
|
use libafl_qemu::edges::EDGES_MAP_SIZE_IN_USE;
|
|
use hashbrown::HashMap;
|
|
use libafl::{executors::ExitKind, observers::ObserversTuple, common::HasMetadata};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use super::AtomicBasicBlock;
|
|
use super::CaptureEvent;
|
|
use super::ExecInterval;
|
|
use super::JobInstance;
|
|
use super::ReducedFreeRTOSSystemState;
|
|
use super::observers::QemuSystemStateObserver;
|
|
use super::TaskJob;
|
|
use petgraph::prelude::DiGraph;
|
|
use petgraph::graph::NodeIndex;
|
|
use petgraph::Direction;
|
|
|
|
use crate::time::clock::QemuClockObserver;
|
|
use crate::time::clock::FUZZ_START_TIMESTAMP;
|
|
use crate::time::worst::MaxTimeFavFactor;
|
|
use std::time::SystemTime;
|
|
use std::{fs::OpenOptions, io::Write};
|
|
use std::borrow::Cow;
|
|
use std::ops::Deref;
|
|
use std::ops::DerefMut;
|
|
use std::rc::Rc;
|
|
use petgraph::visit::EdgeRef;
|
|
|
|
//============================= Data Structures
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Default, Hash)]
|
|
pub struct STGNode
|
|
{
|
|
base: ReducedFreeRTOSSystemState,
|
|
abb: AtomicBasicBlock,
|
|
}
|
|
impl STGNode {
|
|
pub fn _pretty_print(&self) -> String {
|
|
format!("{}\nl{} {:x}-{:x}\n{}", self.base.current_task.task_name, self.abb.level, self.abb.start, self.abb.ends.iter().next().unwrap_or_else(||&0xFFFF), self.base.print_lists())
|
|
}
|
|
pub fn color_print(&self) -> String {
|
|
let color = match self.abb.level {
|
|
1 => "\", shape=box, style=filled, fillcolor=\"lightblue",
|
|
2 => "\", shape=box, style=filled, fillcolor=\"yellow",
|
|
0 => "\", shape=box, style=filled, fillcolor=\"white",
|
|
_ => "\", style=filled, fillcolor=\"lightgray",
|
|
};
|
|
let message = match self.abb.level {
|
|
1 => format!("API Call"),
|
|
2 => format!("ISR"),
|
|
0 => format!("Task: {}",self.base.current_task.task_name),
|
|
_ => format!(""),
|
|
};
|
|
let mut label = format!("{}\nABB: {:x}-{:x}\nHash:{:X}\n{}", message, self.abb.start, self.abb.ends.iter().next().unwrap_or_else(||&0xFFFF), self.base.get_hash()>>48, self.base.print_lists());
|
|
label.push_str(color);
|
|
label
|
|
}
|
|
fn get_hash(&self) -> u64 {
|
|
let mut s = DefaultHasher::new();
|
|
self.base.hash(&mut s);
|
|
self.abb.hash(&mut s);
|
|
s.finish()
|
|
}
|
|
}
|
|
impl PartialEq for STGNode {
|
|
fn eq(&self, other: &STGNode) -> bool {
|
|
self.base==other.base
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)]
|
|
pub struct STGEdge
|
|
{
|
|
pub event: CaptureEvent,
|
|
pub name: String,
|
|
pub worst: Option<(u64, Vec<(u32, u8)>)>,
|
|
}
|
|
|
|
impl STGEdge {
|
|
pub fn _pretty_print(&self) -> String {
|
|
let mut short = match self.event {
|
|
CaptureEvent::APIStart => "Call: ",
|
|
CaptureEvent::APIEnd => "Ret: ",
|
|
CaptureEvent::ISRStart => "Int: ",
|
|
CaptureEvent::ISREnd => "IRet: ",
|
|
CaptureEvent::End => "End: ",
|
|
CaptureEvent::Undefined => "",
|
|
}.to_string();
|
|
short.push_str(&self.name);
|
|
short
|
|
}
|
|
pub fn color_print(&self) -> String {
|
|
let mut short = self.name.clone();
|
|
short.push_str(match self.event {
|
|
CaptureEvent::APIStart => "\", color=\"blue",
|
|
CaptureEvent::APIEnd => "\", color=\"black",
|
|
CaptureEvent::ISRStart => "\", color=red, style=\"dashed",
|
|
CaptureEvent::ISREnd => "\", color=red, style=\"solid",
|
|
CaptureEvent::End => "",
|
|
CaptureEvent::Undefined => "",
|
|
});
|
|
short
|
|
}
|
|
pub fn is_abb_end(&self) -> bool {
|
|
match self.event {
|
|
CaptureEvent::APIStart | CaptureEvent::APIEnd | CaptureEvent::ISREnd | CaptureEvent::End => true,
|
|
_ => false
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Hash for STGEdge {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
self.event.hash(state);
|
|
self.name.hash(state);
|
|
}
|
|
}
|
|
|
|
/// Shared Metadata for a systemstateFeedback
|
|
#[derive(Debug, Serialize, Deserialize, SerdeAny, Clone)]
|
|
pub struct STGFeedbackState
|
|
{
|
|
name: Cow<'static, str>,
|
|
// aggregated traces as a graph
|
|
pub graph: DiGraph<STGNode, STGEdge>,
|
|
systemstate_index: HashMap<u64, ReducedFreeRTOSSystemState>,
|
|
pub state_abb_hash_index: HashMap<(u64, u64), NodeIndex>,
|
|
stgnode_index: HashMap<u64, NodeIndex>,
|
|
entrypoint: NodeIndex,
|
|
exitpoint: NodeIndex,
|
|
// Metadata about aggregated traces. aggegated meaning, order has been removed
|
|
worst_observed_per_aggegated_path: HashMap<Vec<AtomicBasicBlock>,u64>,
|
|
worst_observed_per_abb_path: HashMap<u64,u64>,
|
|
worst_observed_per_stg_path: HashMap<u64,u64>,
|
|
worst_abb_exec_count: HashMap<AtomicBasicBlock, usize>,
|
|
// Metadata about job instances
|
|
pub worst_task_jobs: HashMap<u64, TaskJob>,
|
|
}
|
|
|
|
impl Default for STGFeedbackState {
|
|
fn default() -> STGFeedbackState {
|
|
let mut graph = DiGraph::new();
|
|
let mut entry = STGNode::default();
|
|
entry.base.current_task.task_name="Start".to_string();
|
|
let mut exit = STGNode::default();
|
|
exit.base.current_task.task_name="End".to_string();
|
|
|
|
let systemstate_index = HashMap::from([(entry.base.get_hash(), entry.base.clone()), (exit.base.get_hash(), exit.base.clone())]);
|
|
|
|
let h_entry = entry.get_hash();
|
|
let h_exit = exit.get_hash();
|
|
|
|
let entrypoint = graph.add_node(entry.clone());
|
|
let exitpoint = graph.add_node(exit.clone());
|
|
|
|
let state_abb_hash_index = HashMap::from([((entry.base.get_hash(), entry.abb.get_hash()), entrypoint), ((exit.base.get_hash(), exit.abb.get_hash()), exitpoint)]);
|
|
|
|
let index = HashMap::from([(h_entry, entrypoint), (h_exit, exitpoint)]);
|
|
|
|
STGFeedbackState {
|
|
name: Cow::from("stgfeedbackstate".to_string()),
|
|
graph,
|
|
stgnode_index: index,
|
|
entrypoint,
|
|
exitpoint,
|
|
worst_observed_per_aggegated_path: HashMap::new(),
|
|
worst_observed_per_abb_path: HashMap::new(),
|
|
worst_observed_per_stg_path: HashMap::new(),
|
|
worst_abb_exec_count: HashMap::new(),
|
|
systemstate_index,
|
|
state_abb_hash_index,
|
|
worst_task_jobs: HashMap::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Named for STGFeedbackState
|
|
{
|
|
#[inline]
|
|
fn name(&self) -> &Cow<'static, str> {
|
|
&self.name
|
|
}
|
|
}
|
|
|
|
// Wrapper around Vec<RefinedFreeRTOSSystemState> to attach as Metadata
|
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
|
pub struct STGNodeMetadata {
|
|
nodes: Vec<NodeIndex>,
|
|
edges: Vec<EdgeIndex>,
|
|
abbs: u64,
|
|
aggregate: u64,
|
|
top_abb_counts: Vec<u64>,
|
|
intervals: Vec<ExecInterval>,
|
|
jobs: Vec<JobInstance>,
|
|
indices: Vec<usize>,
|
|
tcref: isize,
|
|
}
|
|
impl STGNodeMetadata {
|
|
pub fn new(nodes: Vec<NodeIndex>, edges: Vec<EdgeIndex>, abb_trace: Vec<AtomicBasicBlock>, abbs_pathhash: u64, aggregate: u64, top_abb_counts: Vec<u64>, intervals: Vec<ExecInterval>, jobs: Vec<JobInstance>) -> Self {
|
|
#[allow(unused)]
|
|
let mut indices : Vec<_> = vec![];
|
|
#[cfg(feature = "sched_stg_edge")]
|
|
{
|
|
indices = edges.iter().map(|x| x.index()).collect();
|
|
indices.sort_unstable();
|
|
indices.dedup();
|
|
}
|
|
#[cfg(feature = "sched_stg_pathhash")]
|
|
{
|
|
indices.push(get_generic_hash(&edges) as usize);
|
|
}
|
|
#[cfg(feature = "sched_stg_abbhash")]
|
|
{
|
|
indices.push(abbs_pathhash as usize);
|
|
}
|
|
#[cfg(feature = "sched_stg_aggregatehash")]
|
|
{
|
|
// indices.push(aggregate as usize);
|
|
indices = top_abb_counts.iter().map(|x| (*x) as usize).collect();
|
|
}
|
|
Self {indices, intervals, jobs, nodes, abbs: abbs_pathhash, aggregate, top_abb_counts, edges, tcref: 0}
|
|
}
|
|
|
|
pub fn nodes(&self) -> &Vec<NodeIndex> {
|
|
&self.nodes
|
|
}
|
|
|
|
pub fn edges(&self) -> &Vec<EdgeIndex> {
|
|
&self.edges
|
|
}
|
|
|
|
pub fn abbs(&self) -> u64 {
|
|
self.abbs
|
|
}
|
|
|
|
pub fn aggregate(&self) -> u64 {
|
|
self.aggregate
|
|
}
|
|
|
|
pub fn top_abb_counts(&self) -> &Vec<u64> {
|
|
&self.top_abb_counts
|
|
}
|
|
|
|
pub fn intervals(&self) -> &Vec<ExecInterval> {
|
|
&self.intervals
|
|
}
|
|
|
|
pub fn jobs(&self) -> &Vec<JobInstance> {
|
|
&self.jobs
|
|
}
|
|
}
|
|
|
|
impl Deref for STGNodeMetadata {
|
|
type Target = [usize];
|
|
/// Convert to a slice
|
|
fn deref(&self) -> &[usize] {
|
|
&self.indices
|
|
}
|
|
}
|
|
|
|
impl DerefMut for STGNodeMetadata {
|
|
/// Convert to a slice
|
|
fn deref_mut(&mut self) -> &mut [usize] {
|
|
&mut self.indices
|
|
}
|
|
}
|
|
|
|
impl HasRefCnt for STGNodeMetadata {
|
|
fn refcnt(&self) -> isize {
|
|
self.tcref
|
|
}
|
|
|
|
fn refcnt_mut(&mut self) -> &mut isize {
|
|
&mut self.tcref
|
|
}
|
|
}
|
|
|
|
libafl_bolts::impl_serdeany!(STGNodeMetadata);
|
|
|
|
pub type GraphMaximizerCorpusScheduler<CS, O> =
|
|
MinimizerScheduler<CS, MaxTimeFavFactor<<CS as UsesState>::State>,STGNodeMetadata,O>;
|
|
|
|
// AI generated, human verified
|
|
/// Count the occurrences of each element in a vector, assumes the vector is sorted
|
|
fn count_occurrences_sorted<T>(vec: &Vec<T>) -> HashMap<&T, usize>
|
|
where
|
|
T: PartialEq + Eq + Hash + Clone,
|
|
{
|
|
let mut counts = HashMap::new();
|
|
|
|
if vec.is_empty() {
|
|
return counts;
|
|
}
|
|
|
|
let mut current_obj = &vec[0];
|
|
let mut current_count = 1;
|
|
|
|
for obj in vec.iter().skip(1) {
|
|
if obj == current_obj {
|
|
current_count += 1;
|
|
} else {
|
|
counts.insert(current_obj, current_count);
|
|
current_obj = obj;
|
|
current_count = 1;
|
|
}
|
|
}
|
|
|
|
// Insert the count of the last object
|
|
counts.insert(current_obj, current_count);
|
|
|
|
counts
|
|
}
|
|
|
|
//============================= Graph Feedback
|
|
|
|
pub static mut STG_MAP: [u16; EDGES_MAP_SIZE_IN_USE] = [0; EDGES_MAP_SIZE_IN_USE];
|
|
pub static mut MAX_STG_NUM: usize = 0;
|
|
pub unsafe fn stg_map_mut_slice<'a>() -> OwnedMutSlice<'a, u16> {
|
|
OwnedMutSlice::from_raw_parts_mut(STG_MAP.as_mut_ptr(), STG_MAP.len())
|
|
}
|
|
|
|
/// A Feedback reporting novel System-State Transitions. Depends on [`QemuSystemStateObserver`]
|
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
|
pub struct StgFeedback
|
|
{
|
|
name: Cow<'static, str>,
|
|
last_node_trace: Option<Vec<NodeIndex>>,
|
|
last_edge_trace: Option<Vec<EdgeIndex>>,
|
|
last_intervals: Option<Vec<ExecInterval>>,
|
|
last_abb_trace: Option<Vec<AtomicBasicBlock>>,
|
|
last_abbs_hash: Option<u64>, // only set, if it was interesting
|
|
last_aggregate_hash: Option<u64>, // only set, if it was interesting
|
|
last_top_abb_hashes: Option<Vec<u64>>, // only set, if it was interesting
|
|
last_job_trace: Option<Vec<JobInstance>>, // only set, if it was interesting
|
|
dump_path: Option<PathBuf>
|
|
}
|
|
#[cfg(feature = "feed_stg")]
|
|
const INTEREST_EDGE : bool = true;
|
|
#[cfg(feature = "feed_stg")]
|
|
const INTEREST_NODE : bool = true;
|
|
#[cfg(feature = "feed_stg_pathhash")]
|
|
const INTEREST_PATH : bool = true;
|
|
#[cfg(feature = "feed_stg_abbhash")]
|
|
const INTEREST_ABBPATH : bool = true;
|
|
#[cfg(feature = "feed_stg_aggregatehash")]
|
|
const INTEREST_AGGREGATE : bool = true;
|
|
|
|
#[cfg(not(feature = "feed_stg"))]
|
|
const INTEREST_EDGE : bool = false;
|
|
#[cfg(not(feature = "feed_stg"))]
|
|
const INTEREST_NODE : bool = false;
|
|
#[cfg(not(feature = "feed_stg_pathhash"))]
|
|
const INTEREST_PATH : bool = false;
|
|
#[cfg(not(feature = "feed_stg_abbhash"))]
|
|
const INTEREST_ABBPATH : bool = false;
|
|
#[cfg(not(feature = "feed_stg_aggregatehash"))]
|
|
const INTEREST_AGGREGATE : bool = false;
|
|
|
|
const INTEREST_JOB_INSTANCE : bool = true;
|
|
|
|
fn set_observer_map(trace : &Vec<EdgeIndex>) {
|
|
// dbg!(trace);
|
|
unsafe {
|
|
for i in 0..MAX_STG_NUM {
|
|
STG_MAP[i] = 0;
|
|
}
|
|
for i in trace {
|
|
if MAX_STG_NUM < i.index() {
|
|
MAX_STG_NUM = i.index();
|
|
}
|
|
STG_MAP[i.index()] = STG_MAP[i.index()].saturating_add(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_generic_hash<H>(input: &H) -> u64
|
|
where
|
|
H: Hash,
|
|
{
|
|
let mut s = DefaultHasher::new();
|
|
input.hash(&mut s);
|
|
s.finish()
|
|
}
|
|
|
|
fn execinterval_to_abb_instances(trace: &Vec<ExecInterval>, read_trace: &Vec<Vec<(u32, u8)>>) -> HashMap<usize, (u64, Vec<(u32, u8)>)>{
|
|
let mut instance_time: HashMap<usize, (u64, Vec<(u32, u8)>)> = HashMap::new();
|
|
for (_i,interval) in trace.iter().enumerate() { // Iterate intervals
|
|
// sum up execution time and accesses per ABB
|
|
let temp = interval.abb.as_ref().map(|abb| abb.instance_id).unwrap_or(usize::MAX);
|
|
match instance_time.get_mut(&temp) {
|
|
Some(x) => {
|
|
x.0 += interval.get_exec_time();
|
|
x.1.extend(read_trace[_i].clone());
|
|
},
|
|
None => {
|
|
if temp != usize::MAX {
|
|
instance_time.insert(temp, (interval.get_exec_time(), read_trace[_i].clone()));
|
|
}
|
|
}
|
|
};
|
|
}
|
|
return instance_time;
|
|
}
|
|
|
|
impl StgFeedback {
|
|
pub fn new(dump_name: Option<PathBuf>) -> Self {
|
|
// Self {name: String::from("STGFeedback"), last_node_trace: None, last_edge_trace: None, last_intervals: None }
|
|
let mut s = Self::default();
|
|
s.dump_path = dump_name.map(|x| x.with_extension("stgsize"));
|
|
s
|
|
}
|
|
|
|
/// params:
|
|
/// tarce of intervals
|
|
/// hashtable of states
|
|
/// feedbackstate
|
|
/// produces:
|
|
/// tarce of node indexes representing the path trough the graph
|
|
/// newly discovered node?
|
|
/// side effect:
|
|
/// the graph gets new nodes and edge
|
|
fn update_stg_interval(trace: &Vec<ExecInterval>, read_trace: &Vec<Vec<(u32, u8)>>, table: &HashMap<u64, ReducedFreeRTOSSystemState>, fbs: &mut STGFeedbackState) -> (Vec<(NodeIndex, u64)>, Vec<(EdgeIndex, u64)>, bool, bool) {
|
|
let mut return_node_trace = vec![(fbs.entrypoint, 0)]; // Assuming entrypoint timestamp is 0
|
|
let mut return_edge_trace = vec![];
|
|
let mut interesting = false;
|
|
let mut updated = false;
|
|
if trace.is_empty() {
|
|
return (return_node_trace, return_edge_trace, interesting, updated);
|
|
}
|
|
let mut instance_time = execinterval_to_abb_instances(trace, read_trace);
|
|
// add all missing state+abb combinations to the graph
|
|
for (_i,interval) in trace.iter().enumerate() { // Iterate intervals
|
|
let node = STGNode {base: table[&interval.start_state].clone(), abb: interval.abb.as_ref().unwrap().clone()};
|
|
let h_node = node.get_hash();
|
|
let next_idx = if let Some(idx) = fbs.stgnode_index.get(&h_node) {
|
|
// already present
|
|
*idx
|
|
} else {
|
|
// not present
|
|
let h = (node.base.get_hash(), node.abb.get_hash());
|
|
let idx = fbs.graph.add_node(node);
|
|
fbs.stgnode_index.insert(h_node, idx);
|
|
fbs.state_abb_hash_index.insert(h, idx);
|
|
interesting |= INTEREST_NODE;
|
|
updated = true;
|
|
idx
|
|
};
|
|
// connect in graph if edge not present
|
|
let e = fbs.graph.edges_directed(return_node_trace[return_node_trace.len()-1].0, Direction::Outgoing).find(|x| petgraph::visit::EdgeRef::target(x) == next_idx);
|
|
if let Some(e_) = e {
|
|
return_edge_trace.push((petgraph::visit::EdgeRef::id(&e_), interval.start_tick));
|
|
if let Some((time, accesses)) = instance_time.get_mut(&interval.abb.as_ref().unwrap().instance_id) {
|
|
let ref_ = &mut fbs.graph.edge_weight_mut(e_.id()).unwrap().worst;
|
|
if ref_.is_some() {
|
|
let w = ref_.as_mut().unwrap();
|
|
if w.0 < *time {*w = (*time, accesses.clone())};
|
|
} else {
|
|
*ref_ = Some((*time, accesses.clone()));
|
|
}
|
|
}
|
|
} else {
|
|
let mut e__ = STGEdge{event: interval.start_capture.0, name: interval.start_capture.1.clone(), worst: None};
|
|
if e__.is_abb_end() {
|
|
if let Some((time,accesses)) = instance_time.get_mut(&interval.abb.as_ref().unwrap().instance_id) {
|
|
e__.worst = Some((*time, accesses.clone()));
|
|
}
|
|
}
|
|
let e_ = fbs.graph.add_edge(return_node_trace[return_node_trace.len()-1].0, next_idx, e__);
|
|
return_edge_trace.push((e_, interval.start_tick));
|
|
interesting |= INTEREST_EDGE;
|
|
updated = true;
|
|
}
|
|
return_node_trace.push((next_idx, interval.start_tick));
|
|
}
|
|
// every path terminates at the end
|
|
if !fbs.graph.neighbors_directed(return_node_trace[return_node_trace.len()-1].0, Direction::Outgoing).any(|x| x == fbs.exitpoint) {
|
|
let mut e__ = STGEdge { event: CaptureEvent::End, name: String::from("End"), worst: None };
|
|
if e__.is_abb_end() {
|
|
if let Some((time, accesses)) = instance_time.get_mut(&trace[trace.len()-1].abb.as_ref().unwrap().instance_id) {
|
|
e__.worst = Some((*time, accesses.clone()));
|
|
}
|
|
}
|
|
let e_ = fbs.graph.add_edge(return_node_trace[return_node_trace.len()-1].0, fbs.exitpoint, e__);
|
|
return_edge_trace.push((e_, trace[trace.len()-1].start_tick));
|
|
interesting |= INTEREST_EDGE;
|
|
updated = true;
|
|
}
|
|
return_node_trace.push((fbs.exitpoint, trace[trace.len()-1].start_tick));
|
|
(return_node_trace, return_edge_trace, interesting, updated)
|
|
}
|
|
|
|
fn abbs_in_exec_order(trace: &Vec<ExecInterval>) -> Vec<AtomicBasicBlock> {
|
|
let mut ret = Vec::new();
|
|
for i in 0..trace.len() {
|
|
if trace[i].abb != None &&
|
|
(trace[i].end_capture.0 == CaptureEvent::APIStart || trace[i].end_capture.0 == CaptureEvent::APIEnd || trace[i].end_capture.0 == CaptureEvent::End || trace[i].end_capture.0 == CaptureEvent::ISREnd) {
|
|
ret.push(trace[i].abb.as_ref().unwrap().clone());
|
|
}
|
|
}
|
|
ret
|
|
}
|
|
}
|
|
|
|
impl<S> Feedback<S> for StgFeedback
|
|
where
|
|
S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata,
|
|
S::Input: Default,
|
|
{
|
|
#[allow(clippy::wrong_self_convention)]
|
|
fn is_interesting<EM, OT>(
|
|
&mut self,
|
|
state: &mut S,
|
|
_manager: &mut EM,
|
|
_input: &S::Input,
|
|
observers: &OT,
|
|
_exit_kind: &ExitKind,
|
|
) -> Result<bool, Error>
|
|
where
|
|
EM: EventFirer<State = S>,
|
|
OT: ObserversTuple<S>,
|
|
S::Input: Default,
|
|
{
|
|
let observer = observers.match_name::<QemuSystemStateObserver<S::Input>>("systemstate")
|
|
.expect("QemuSystemStateObserver not found");
|
|
let clock_observer = observers.match_name::<QemuClockObserver>("clocktime")
|
|
.expect("QemuClockObserver not found");
|
|
#[cfg(feature = "trace_job_response_times")]
|
|
let last_runtime = observer.last_runtime();
|
|
#[cfg(not(feature = "trace_job_response_times"))]
|
|
let last_runtime = clock_observer.last_runtime();
|
|
let feedbackstate = match state
|
|
.named_metadata_map_mut()
|
|
.get_mut::<STGFeedbackState>("stgfeedbackstate") {
|
|
Some(s) => s,
|
|
Option::None => {
|
|
let n=STGFeedbackState::default();
|
|
state.named_metadata_map_mut().insert("stgfeedbackstate",n);
|
|
state.named_metadata_map_mut().get_mut::<STGFeedbackState>("stgfeedbackstate").unwrap()
|
|
}
|
|
};
|
|
|
|
// --------------------------------- Update STG
|
|
let (mut nodetrace, mut edgetrace, mut interesting, mut updated) = StgFeedback::update_stg_interval(&observer.last_trace, &observer.last_reads, &observer.last_states, feedbackstate);
|
|
|
|
|
|
#[cfg(feature = "trace_job_response_times")]
|
|
let worst_target_instance = observer.job_instances.iter().filter(|x| Some(x.name.clone()) == observer.select_task).max_by(|a,b| (a.response-a.release).cmp(&(b.response-b.release)));
|
|
|
|
#[cfg(feature = "trace_job_response_times")]
|
|
if let Some(worst_instance) = worst_target_instance {
|
|
edgetrace = edgetrace.into_iter().filter(|x| x.1 <= worst_instance.response && x.1 >= worst_instance.release ).collect();
|
|
nodetrace = nodetrace.into_iter().filter(|x| x.1 <= worst_instance.response && x.1 >= worst_instance.release ).collect();
|
|
} else {
|
|
if observer.select_task.is_none() { // if nothing was selected, just take the whole trace, otherwise there is nothing interesting here
|
|
edgetrace = Vec::new();
|
|
nodetrace = Vec::new();
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "feed_stg")]
|
|
set_observer_map(&edgetrace.iter().map(|x| x.0).collect::<Vec<_>>());
|
|
|
|
// --------------------------------- Update job instances
|
|
for i in observer.worst_job_instances.iter() {
|
|
interesting |= INTEREST_JOB_INSTANCE && if let Some(x) = feedbackstate.worst_task_jobs.get_mut(&i.1.get_hash_cached()) {
|
|
// eprintln!("Job instance already present");
|
|
x.try_update(i.1)
|
|
} else {
|
|
// eprintln!("New Job instance");
|
|
feedbackstate.worst_task_jobs.insert(i.1.get_hash_cached(), TaskJob::from_instance(&i.1));
|
|
true
|
|
}
|
|
};
|
|
self.last_job_trace = Some(observer.job_instances.clone());
|
|
// dbg!(&observer.job_instances);
|
|
|
|
{
|
|
let h = get_generic_hash(&edgetrace);
|
|
if let Some(x) = feedbackstate.worst_observed_per_stg_path.get_mut(&h) {
|
|
let t = last_runtime;
|
|
if t > *x {
|
|
*x = t;
|
|
interesting |= INTEREST_PATH;
|
|
}
|
|
} else {
|
|
feedbackstate.worst_observed_per_stg_path.insert(h, last_runtime);
|
|
updated = true;
|
|
interesting |= INTEREST_PATH;
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "trace_job_response_times"))]
|
|
let tmp = StgFeedback::abbs_in_exec_order(&observer.last_trace);
|
|
#[cfg(feature = "trace_job_response_times")]
|
|
let tmp = {
|
|
if let Some(worst_instance) = worst_target_instance {
|
|
let t = observer.last_trace.iter().filter(|x| x.start_tick < worst_instance.response && x.end_tick > worst_instance.release ).cloned().collect();
|
|
StgFeedback::abbs_in_exec_order(&t)
|
|
} else {
|
|
if observer.select_task.is_none() { // if nothing was selected, just take the whole trace, otherwise there is nothing interesting here
|
|
StgFeedback::abbs_in_exec_order(&observer.last_trace)
|
|
} else {
|
|
Vec::new()
|
|
}
|
|
}
|
|
};
|
|
if INTEREST_AGGREGATE || INTEREST_ABBPATH {
|
|
if INTEREST_ABBPATH {
|
|
let h = get_generic_hash(&tmp);
|
|
self.last_abbs_hash = Some(h);
|
|
// order of execution is relevant
|
|
if let Some(x) = feedbackstate.worst_observed_per_abb_path.get_mut(&h) {
|
|
let t = last_runtime;
|
|
if t > *x {
|
|
*x = t;
|
|
interesting |= INTEREST_ABBPATH;
|
|
}
|
|
} else {
|
|
feedbackstate.worst_observed_per_abb_path.insert(h, last_runtime);
|
|
interesting |= INTEREST_ABBPATH;
|
|
}
|
|
}
|
|
if INTEREST_AGGREGATE {
|
|
// aggegation by sorting, order of states is not relevant
|
|
let mut _tmp = tmp.clone();
|
|
_tmp.sort(); // use sort+count, because we need the sorted trace anyways
|
|
let counts = count_occurrences_sorted(&_tmp);
|
|
let mut top_indices = Vec::new();
|
|
for (k,c) in counts {
|
|
if let Some(reference) = feedbackstate.worst_abb_exec_count.get_mut(k) {
|
|
if *reference < c {
|
|
*reference = c;
|
|
top_indices.push(get_generic_hash(k));
|
|
}
|
|
} else {
|
|
top_indices.push(get_generic_hash(k));
|
|
feedbackstate.worst_abb_exec_count.insert(k.clone(), c);
|
|
}
|
|
}
|
|
self.last_top_abb_hashes = Some(top_indices);
|
|
|
|
self.last_aggregate_hash = Some(get_generic_hash(&_tmp));
|
|
if let Some(x) = feedbackstate.worst_observed_per_aggegated_path.get_mut(&_tmp) {
|
|
let t = last_runtime;
|
|
if t > *x {
|
|
*x = t;
|
|
interesting |= INTEREST_AGGREGATE;
|
|
}
|
|
} else {
|
|
feedbackstate.worst_observed_per_aggegated_path.insert(_tmp, last_runtime);
|
|
interesting |= INTEREST_AGGREGATE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// let out = feedbackstate.graph.map(|i,x| x.pretty_print(), |_,_| "");
|
|
// let outs = Dot::with_config(&out, &[Config::EdgeNoLabel]).to_string();
|
|
// let outs = outs.replace(';',"\\n");
|
|
// fs::write("./mystg.dot",outs).expect("Failed to write graph");
|
|
self.last_node_trace = Some(nodetrace.into_iter().map(|x| x.0).collect::<Vec<_>>());
|
|
self.last_edge_trace = Some(edgetrace.into_iter().map(|x| x.0).collect::<Vec<_>>());
|
|
self.last_intervals = Some(observer.last_trace.clone());
|
|
self.last_abb_trace = Some(tmp);
|
|
|
|
if let Some(dp) = &self.dump_path {
|
|
if updated {
|
|
let timestamp = SystemTime::now().duration_since(unsafe {FUZZ_START_TIMESTAMP}).unwrap().as_millis();
|
|
let mut file = OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.create(true)
|
|
.append(true)
|
|
.open(dp).expect("Could not open stgsize");
|
|
writeln!(file, "{},{},{},{},{}", feedbackstate.graph.edge_count(), feedbackstate.graph.node_count(), feedbackstate.worst_observed_per_aggegated_path.len(),feedbackstate.worst_observed_per_stg_path.len(), timestamp).expect("Write to dump failed");
|
|
}
|
|
}
|
|
Ok(interesting)
|
|
}
|
|
|
|
/// Append to the testcase the generated metadata in case of a new corpus item
|
|
#[inline]
|
|
fn append_metadata<EM, OT>(&mut self, _state: &mut S, _manager: &mut EM, _observers: &OT, testcase: &mut Testcase<S::Input>) -> Result<(), Error> {
|
|
let meta = STGNodeMetadata::new(self.last_node_trace.take().unwrap_or_default(), self.last_edge_trace.take().unwrap_or_default(), self.last_abb_trace.take().unwrap_or_default(), self.last_abbs_hash.take().unwrap_or_default(), self.last_aggregate_hash.take().unwrap_or_default(), self.last_top_abb_hashes.take().unwrap_or_default(), self.last_intervals.take().unwrap_or_default(), self.last_job_trace.take().unwrap_or_default());
|
|
testcase.metadata_map_mut().insert(meta);
|
|
Ok(())
|
|
}
|
|
|
|
/// Discard the stored metadata in case that the testcase is not added to the corpus
|
|
#[inline]
|
|
fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
|
|
Ok(())
|
|
}
|
|
}
|
|
impl Named for StgFeedback
|
|
{
|
|
#[inline]
|
|
fn name(&self) -> &Cow<'static, str> {
|
|
&self.name
|
|
}
|
|
} |