systemstate::target_os comments and refactoring

This commit is contained in:
Alwin Berger 2025-08-25 12:06:08 +00:00
parent 6d5e886f39
commit 1caa49f470
11 changed files with 543 additions and 408 deletions

View File

@ -2,33 +2,33 @@ use hashbrown::HashMap;
use libafl_qemu::{elf::EasyElf, GuestAddr};
use std::env;
use crate::systemstate::helpers::{load_symbol, try_load_symbol};
use crate::systemstate::helpers::symbol_helpers::{load_elf_symbol, try_load_elf_symbol};
pub fn get_target_symbols(elf: &EasyElf) -> HashMap<&'static str, GuestAddr> {
pub fn get_target_symbols(elf: &EasyElf, add_target_specific_symbols: fn (elf: &EasyElf, addrs: &mut HashMap<&'static str, GuestAddr>)) -> HashMap<&'static str, GuestAddr> {
let mut addrs = HashMap::new();
addrs.insert(
"__APP_CODE_START__",
load_symbol(&elf, "__APP_CODE_START__", false),
load_elf_symbol(&elf, "__APP_CODE_START__", false),
);
addrs.insert(
"__APP_CODE_END__",
load_symbol(&elf, "__APP_CODE_END__", false),
load_elf_symbol(&elf, "__APP_CODE_END__", false),
);
addrs.insert(
"__API_CODE_START__",
load_symbol(&elf, "__API_CODE_START__", false),
load_elf_symbol(&elf, "__API_CODE_START__", false),
);
addrs.insert(
"__API_CODE_END__",
load_symbol(&elf, "__API_CODE_END__", false),
load_elf_symbol(&elf, "__API_CODE_END__", false),
);
addrs.insert(
"trigger_job_done",
load_symbol(&elf, "trigger_job_done", false),
load_elf_symbol(&elf, "trigger_job_done", false),
);
crate::systemstate::target_os::freertos::config::add_target_symbols(elf, &mut addrs);
add_target_specific_symbols(elf, &mut addrs);
// the main address where the fuzzer starts
// if this is set for freeRTOS it has an influence on where the data will have to be written,
@ -41,14 +41,14 @@ pub fn get_target_symbols(elf: &EasyElf) -> HashMap<&'static str, GuestAddr> {
addrs.insert("FUZZ_MAIN", main_addr);
}
let input_addr = load_symbol(
let input_addr = load_elf_symbol(
&elf,
&env::var("FUZZ_INPUT").unwrap_or_else(|_| "FUZZ_INPUT".to_owned()),
true,
);
addrs.insert("FUZZ_INPUT", input_addr);
let input_length_ptr = try_load_symbol(
let input_length_ptr = try_load_elf_symbol(
&elf,
&env::var("FUZZ_LENGTH").unwrap_or_else(|_| "FUZZ_LENGTH".to_owned()),
true,
@ -56,7 +56,7 @@ pub fn get_target_symbols(elf: &EasyElf) -> HashMap<&'static str, GuestAddr> {
if let Some(input_length_ptr) = input_length_ptr {
addrs.insert("FUZZ_LENGTH", input_length_ptr);
}
let input_counter_ptr = try_load_symbol(
let input_counter_ptr = try_load_elf_symbol(
&elf,
&env::var("FUZZ_POINTER").unwrap_or_else(|_| "FUZZ_POINTER".to_owned()),
true,

View File

@ -16,7 +16,7 @@ elf::EasyElf, emu::Emulator, modules::{edges::{self}, EdgeCoverageModule, Filter
use libafl_targets::{edges_map_mut_ptr, EDGES_MAP_DEFAULT_SIZE, MAX_EDGES_FOUND};
use rand::{SeedableRng, StdRng, Rng};
use crate::{
config::{get_target_ranges, get_target_symbols}, systemstate::{self, feedbacks::{DumpSystraceFeedback, SystraceErrorFeedback}, helpers::{get_function_range, input_bytes_to_interrupt_times, load_symbol, try_load_symbol}, mutational::{InterruptShiftStage, STGSnippetStage}, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}, target_os::freertos::{config::get_range_groups, qemu_module::FreeRTOSSystemStateHelper, FreeRTOSSystem}}, time::{
config::{get_target_ranges, get_target_symbols}, systemstate::{self, feedbacks::{DumpSystraceFeedback, SystraceErrorFeedback}, helpers::{ input_bytes_to_interrupt_times}, mutational::{InterruptShiftStage, STGSnippetStage}, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}, target_os::freertos::{config::get_range_groups, extraction::qemu_module::FreeRTOSSystemStateModule, FreeRTOSSystem}}, time::{
clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP, QEMU_ICOUNT_SHIFT, QEMU_ISNS_PER_MSEC, QEMU_ISNS_PER_USEC}, qemustate::QemuStateRestoreHelper, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, RateLimitedMonitor, TimeMaximizerCorpusScheduler, TimeProbMassScheduler, TimeStateMaximizerCorpusScheduler}
}
};
@ -45,27 +45,6 @@ pub const NUM_INTERRUPT_SOURCES: usize = 6; // Keep in sync with qemu-libafl-bri
pub const DO_NUM_INTERRUPT: usize = 128;
pub static mut MAX_INPUT_SIZE: usize = 1024;
pub fn get_all_fn_symbol_ranges(elf: &EasyElf, range: std::ops::Range<GuestAddr>) -> HashMap<String,std::ops::Range<GuestAddr>> {
let mut ret : HashMap<String,std::ops::Range<GuestAddr>> = HashMap::new();
let gob = elf.goblin();
let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function() && range.contains(&x.st_value.try_into().unwrap())).collect();
funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value));
for sym in &funcs {
let sym_name = gob.strtab.get_at(sym.st_name);
if let Some(sym_name) = sym_name {
// if ISR_SYMBOLS.contains(&sym_name) {continue;}; // skip select symbols, which correspond to ISR-safe system calls
if let Some(r) = get_function_range(elf, sym_name) {
ret.insert(sym_name.to_string(), r);
}
}
}
return ret;
}
#[allow(unused)]
extern "C" {
static mut libafl_interrupt_offsets : [[u32; MAX_NUM_INTERRUPT]; NUM_INTERRUPT_SOURCES];
@ -182,7 +161,7 @@ let elf = EasyElf::from_file(
)
.unwrap();
let TARGET_SYMBOLS: HashMap<&'static str, GuestAddr> = get_target_symbols(&elf);
let TARGET_SYMBOLS: HashMap<&'static str, GuestAddr> = get_target_symbols(&elf, crate::systemstate::target_os::freertos::config::add_target_symbols);
let TARGET_RANGES: HashMap<&'static str, Range<GuestAddr>> = get_target_ranges(&elf, &TARGET_SYMBOLS);
let TARGET_GROUPS: HashMap<&'static str, HashMap<String, Range<GuestAddr>>> = get_range_groups(&elf, &TARGET_SYMBOLS, &TARGET_RANGES);
@ -426,7 +405,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| {
let qhelpers = tuple_list!();
#[cfg(feature = "observe_systemstate")]
let qhelpers = (FreeRTOSSystemStateHelper::new(&TARGET_SYMBOLS,&TARGET_RANGES,&TARGET_GROUPS), qhelpers);
let qhelpers = (FreeRTOSSystemStateModule::new(&TARGET_SYMBOLS,&TARGET_RANGES,&TARGET_GROUPS), qhelpers);
#[cfg(feature = "observe_edges")]
let qhelpers = (
StdEdgeCoverageModule::builder()
@ -498,7 +477,7 @@ let run_client = |state: Option<_>, mut mgr, _core_id| {
let mut rng = StdRng::seed_from_u64(se);
let bound = 10000;
#[cfg(feature = "shortcut")]
let bound = 100;
let bound = 5;
for _ in 0..bound {
let inp2 = BytesInput::new((0..MAX_INPUT_SIZE).map(|_| rng.gen::<u8>()).collect());
let inp = setup_interrupt_inputs(MultipartInput::from([("bytes",inp2)]), &interrupt_config, Some(&mut rng));

View File

@ -1,7 +1,7 @@
use hashbrown::HashMap;
use libafl_bolts::prelude::{SerdeAny, SerdeAnyMap};
use libafl_qemu::{elf::EasyElf, read_user_reg_unchecked, GuestAddr, GuestPhysAddr};
use std::{borrow::Cow, cmp::min, hash::{DefaultHasher, Hash, Hasher}, ops::Range};
use libafl_qemu::{read_user_reg_unchecked, GuestAddr};
use std::{borrow::Cow, cmp::min, hash::{DefaultHasher, Hash, Hasher}};
use crate::{
fuzzer::{DO_NUM_INTERRUPT, FIRST_INT},
@ -10,8 +10,31 @@ use crate::{
use super::ExecInterval;
pub mod symbol_helpers {
//! Helper functions for working with ELF symbols and address translation.
//!
//! This module provides utilities for:
//! - Translating virtual addresses to physical addresses using ELF program headers.
//! - Looking up symbol addresses in an ELF file, with optional address translation.
//! - Determining the address range of a function symbol.
//! - Retrieving all function symbol ranges within a specified address range from an ELF file.
//! - Checking if an address falls within any of a set of address ranges.
//!
//! # Functions
//! - `virt2phys`: Resolves a virtual address to a physical address using ELF program headers.
//! - `load_elf_symbol`: Looks up a symbol and returns its address, panicking if not found.
//! - `try_load_elf_symbol`: Looks up a symbol and returns its address if found.
//! - `get_function_range`: Returns the address range of a function symbol.
//! - `get_all_fn_symbols_in_range`: Returns a map of function names to their address ranges within a given range.
//! - `in_any_range`: Checks if an address is within any of the provided ranges.
//============================= API symbols
use std::{borrow::Cow, ops::Range};
use hashbrown::HashMap;
use libafl_qemu::{elf::EasyElf, GuestAddr, GuestPhysAddr};
/// Resolves a virtual address to a physical address using ELF program headers.
///
/// # Arguments
@ -44,8 +67,8 @@ fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr {
///
/// # Returns
/// The address of the symbol.
pub fn load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> GuestAddr {
try_load_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol))
pub fn load_elf_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> GuestAddr {
try_load_elf_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol))
}
/// Looks up a symbol in the ELF file and returns its address, optionally translating to a physical address.
@ -57,7 +80,7 @@ pub fn load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> GuestAd
///
/// # Returns
/// Some(address) if found, None otherwise.
pub fn try_load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Option<GuestAddr> {
pub fn try_load_elf_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Option<GuestAddr> {
let ret = elf.resolve_symbol(symbol, 0);
if do_translation {
Option::map_or(ret, None, |x| {
@ -76,7 +99,7 @@ pub fn try_load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Opt
///
/// # Returns
/// Some(range) if found, None otherwise.
pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option<std::ops::Range<GuestAddr>> {
pub(crate) fn get_function_range(elf: &EasyElf, symbol: &str) -> Option<std::ops::Range<GuestAddr>> {
let gob = elf.goblin();
let mut funcs: Vec<_> = gob.syms.iter().filter(|x| x.is_function()).collect();
@ -107,6 +130,50 @@ pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option<std::ops::Range
return None;
}
/// Retrieves all function symbol ranges within a specified address range from an ELF file.
///
/// This function extracts function symbols from the ELF file's symbol table, filters them
/// to include only those within the given address range, and returns a mapping of function
/// names to their address ranges. The function handles ARM interworking addresses by
/// using `get_function_range` to determine precise function boundaries.
///
/// # Arguments
/// * `elf` - The ELF file to search for function symbols
/// * `range` - The address range to filter functions by (only functions within this range are included)
///
/// # Returns
/// A `HashMap` mapping function names (`String`) to their address ranges (`Range<GuestAddr>`).
/// Only functions that have valid ranges (as determined by `get_function_range`) are included.
///
/// # Example
/// ```rust
/// let api_functions = get_all_fn_symbols_in_range(&elf, 0x1000..0x2000);
/// for (name, range) in &api_functions {
/// println!("Function {} spans {:#x}..{:#x}", name, range.start, range.end);
/// }
/// ```
pub(crate) fn get_all_fn_symbols_in_range(elf: &EasyElf, range: std::ops::Range<GuestAddr>) -> HashMap<String,std::ops::Range<GuestAddr>> {
let mut ret : HashMap<String,std::ops::Range<GuestAddr>> = HashMap::new();
let gob = elf.goblin();
let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function() && range.contains(&x.st_value.try_into().unwrap())).collect();
funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value));
for sym in &funcs {
let sym_name = gob.strtab.get_at(sym.st_name);
if let Some(sym_name) = sym_name {
// if ISR_SYMBOLS.contains(&sym_name) {continue;}; // skip select symbols, which correspond to ISR-safe system calls
if let Some(r) = get_function_range(elf, sym_name) {
ret.insert(sym_name.to_string(), r);
}
}
}
return ret;
}
/// Checks if an address is within any of the provided ranges.
///
/// # Arguments
@ -127,6 +194,10 @@ pub fn in_any_range<'a>(
return None;
}
}
//============================= QEMU related utility functions
/// Retrieves the current QEMU instruction count.

View File

@ -9,7 +9,7 @@ use libafl_bolts::{rands::{
random_seed, Rand, StdRand
}, Named};
use libafl::{
common::{HasMetadata, HasNamedMetadata}, corpus::{self, Corpus, HasCurrentCorpusId, Testcase}, events::{Event, EventFirer, EventProcessor, LogSeverity}, fuzzer::Evaluator, inputs::{HasMutatorBytes, HasTargetBytes, Input, MultipartInput}, mark_feature_time, prelude::{new_hash_feedback, AggregatorOps, CorpusId, MutationResult, Mutator, UserStats, UserStatsValue, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error
common::{HasMetadata, HasNamedMetadata}, corpus::{self, HasCurrentCorpusId, Testcase}, events::{Event, EventFirer, EventProcessor, LogSeverity}, fuzzer::Evaluator, inputs::{HasMutatorBytes, HasTargetBytes, Input, MultipartInput}, mark_feature_time, prelude::{new_hash_feedback, AggregatorOps, MutationResult, Mutator, UserStats, UserStatsValue, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error
};
use libafl::prelude::State;
use petgraph::{graph::NodeIndex, graph::{self, DiGraph}};
@ -145,7 +145,7 @@ where
EM: UsesState<State = S>,
Z: Evaluator<E, EM, State = S>,
S: State<Input = MultipartInput<I>> + HasRand + HasCorpus + HasCurrentTestcase + HasMetadata + HasNamedMetadata,
<<Self as UsesState>::State as HasCorpus>::Corpus: Corpus<Input = Self::Input>, //delete me
<<Self as UsesState>::State as HasCorpus>::Corpus: libafl::prelude::Corpus<Input = Self::Input>,
EM: EventFirer,
I: Default + Input + HasMutatorBytes,
SYS: TargetSystem,
@ -189,7 +189,7 @@ where
} else {
new_input.add_part(String::from(&name), I::default()); new_input.parts_by_name_mut(&name).next().unwrap()
}.1;
let old_interrupt_times = input_bytes_to_interrupt_times(new_interrupt_part.bytes(), *interrup_config);
let old_interrupt_times : Vec<u32> = input_bytes_to_interrupt_times(new_interrupt_part.bytes(), *interrup_config);
let mut new_interrupt_times = Vec::with_capacity(MAX_NUM_INTERRUPT);
let mut do_rerun = false;
// if state.rand_mut().between(1, 100) <= 50 // only attempt the mutation half of the time
@ -224,54 +224,6 @@ where
new_interrupt_times=t;
}
}
// let tmp = current_case.metadata_map().get::<STGNodeMetadata>();
// if tmp.is_some() {
// let trace = tmp.expect("STGNodeMetadata not found");
// let mut node_indices = vec![];
// for i in (0..trace.intervals.len()).into_iter() {
// if let Some(abb) = &trace.intervals[i].abb {
// if let Some(idx) = feedbackstate.state_abb_hash_index.get(&(trace.intervals[i].start_state,abb.get_hash())) {
// node_indices.push(Some(idx));
// continue;
// }
// }
// node_indices.push(None);
// }
// // let mut marks : HashMap<u32, usize>= HashMap::new(); // interrupt -> block hit
// // for i in 0..trace.intervals.len() {
// // let curr = &trace.intervals[i];
// // let m = interrupt_offsets[0..num_interrupts].iter().filter(|x| (curr.start_tick..curr.end_tick).contains(&((**x) as u64)));
// // for k in m {
// // marks.insert(*k,i);
// // }
// // }
// // walk backwards trough the trace and try moving the interrupt to a block that does not have an outgoing interrupt edge or ist already hit by a predecessor
// for i in (0..num_interrupts).rev() {
// let mut lb = FIRST_INT;
// let mut ub : u32 = trace.intervals[trace.intervals.len()-1].end_tick.try_into().expect("ticks > u32");
// if i > 0 {
// lb = u32::saturating_add(interrupt_offsets[i-1],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
// }
// if i < num_interrupts-1 {
// ub = u32::saturating_sub(interrupt_offsets[i+1],unsafe{MINIMUM_INTER_ARRIVAL_TIME});
// }
// let alternatives : Vec<_> = (0..trace.intervals.len()).filter(|x|
// node_indices[*x].is_some() &&
// (trace.intervals[*x].start_tick < (lb as u64) && (lb as u64) < trace.intervals[*x].end_tick
// || trace.intervals[*x].start_tick > (lb as u64) && trace.intervals[*x].start_tick < (ub as u64))
// ).collect();
// let not_yet_hit : Vec<_> = alternatives.iter().filter(
// |x| feedbackstate.graph.edges_directed(*node_indices[**x].unwrap(), petgraph::Direction::Outgoing).any(|y| y.weight().event != CaptureEvent::ISRStart)).collect();
// if not_yet_hit.len() > 0 {
// let replacement = &trace.intervals[*myrand.choose(not_yet_hit).unwrap()];
// interrupt_offsets[i] = (myrand.between(replacement.start_tick as usize,
// replacement.end_tick as usize)).try_into().expect("ticks > u32");
// // println!("chose new alternative, i: {} {} -> {}",i,tmp, interrupt_offsets[i]);
// do_rerun = true;
// break;
// }
// }
// }
}
else { // old version of the alternative search
new_interrupt_times = old_interrupt_times.clone();
@ -406,11 +358,11 @@ where
Ok(())
}
fn should_restart(&mut self, state: &mut Self::State) -> Result<bool, Error> {
fn should_restart(&mut self, _state: &mut Self::State) -> Result<bool, Error> {
Ok(true)
}
fn clear_progress(&mut self, state: &mut Self::State) -> Result<(), Error> {
fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> {
Ok(())
}
}
@ -504,7 +456,7 @@ where
Z::State: UsesInput<Input = MultipartInput<I>>,
I: HasMutatorBytes + Default,
Z::State: HasCurrentTestcase+HasCorpus+HasCurrentCorpusId,
<Z::State as HasCorpus>::Corpus: Corpus<Input = MultipartInput<I>>,
<Z::State as HasCorpus>::Corpus: libafl::prelude::Corpus<Input = MultipartInput<I>>,
SYS: TargetSystem,
{
fn perform(
@ -562,11 +514,11 @@ where
Ok(())
}
fn should_restart(&mut self, state: &mut Self::State) -> Result<bool, Error> {
fn should_restart(&mut self, _state: &mut Self::State) -> Result<bool, Error> {
Ok(true)
}
fn clear_progress(&mut self, state: &mut Self::State) -> Result<(), Error> {
fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> {
Ok(())
}
}

View File

@ -6,9 +6,9 @@ use std::{cmp::{max, min}, mem::swap};
use serde::{Deserialize, Serialize};
use libafl_bolts::{rands::Rand, AsIter, HasLen};
use libafl_bolts::{rands::Rand};
use libafl::{
common::HasMetadata, corpus::{Corpus, Testcase}, inputs::UsesInput, prelude::{CanTrack, CorpusId, RemovableScheduler}, schedulers::{minimizer::DEFAULT_SKIP_NON_FAVORED_PROB, Scheduler, TestcaseScore }, state::{HasCorpus, HasRand, State, UsesState}, Error, SerdeAny
common::HasMetadata, corpus::{Corpus, Testcase}, inputs::UsesInput, prelude::{CorpusId, RemovableScheduler}, schedulers::{minimizer::DEFAULT_SKIP_NON_FAVORED_PROB, Scheduler, TestcaseScore }, state::{HasCorpus, HasRand, State, UsesState}, Error, SerdeAny
};
@ -249,8 +249,8 @@ where
fn set_current_scheduled(
&mut self,
state: &mut S,
next_id: Option<libafl::corpus::CorpusId>,
_state: &mut S,
_next_id: Option<libafl::corpus::CorpusId>,
) -> Result<(), Error> {
Ok(())
}
@ -288,9 +288,9 @@ where
/// Replaces the testcase at the given idx
fn on_replace(
&mut self,
state: &mut <Self as UsesState>::State,
idx: CorpusId,
testcase: &Testcase<I>,
_state: &mut <Self as UsesState>::State,
_idx: CorpusId,
_testcase: &Testcase<I>,
) -> Result<(), Error> {
Ok(())
}
@ -298,9 +298,9 @@ where
/// Removes an entry from the corpus
fn on_remove(
&mut self,
state: &mut <Self as UsesState>::State,
idx: CorpusId,
testcase: &Option<Testcase<I>>,
_state: &mut <Self as UsesState>::State,
_idx: CorpusId,
_testcase: &Option<Testcase<I>>,
) -> Result<(), Error> {
Ok(())
}

View File

@ -1,3 +1,4 @@
//! Bindings for FreeRTOS types and constants.
#![allow(non_camel_case_types,non_snake_case,non_upper_case_globals,deref_nullptr,unused)]
use serde::{Deserialize, Serialize};
use std::fmt::Debug;

View File

@ -1,47 +1,159 @@
//! FreeRTOS-specific configuration for system state observation.
//!
//! This module provides functionality to extract and organize FreeRTOS kernel symbols
//! and function ranges for fuzzing and system state monitoring.
//!
//! # Overview
//!
//! The module handles:
//! - Loading FreeRTOS kernel symbols required for system state observation
//! - Categorizing functions into API, application, and ISR (Interrupt Service Routine) groups
//! - Managing symbol addresses and function ranges for fuzzing instrumentation
//!
//! # Functions
//!
//! - [`add_target_symbols`]: Adds FreeRTOS-specific symbols to the target symbol map
//! - [`get_range_groups`]: Groups functions by type (API, APP, ISR) for targeted fuzzing
use hashbrown::HashMap;
use libafl_qemu::{elf::EasyElf, GuestAddr};
use crate::{
fuzzer::get_all_fn_symbol_ranges,
systemstate::{helpers::{get_function_range, load_symbol}, target_os::freertos::ISR_SYMBOLS},
};
use crate::systemstate::{helpers::symbol_helpers::{get_all_fn_symbols_in_range, get_function_range, load_elf_symbol}};
// Add os-specific symbols to the target symbol hashmap
/// List of ISR handler symbols in FreeRTOS, including default handlers and user-defined ISRs.
pub(crate) const ISR_SYMBOLS: &'static [&'static str] = &[
"Reset_Handler",
"Default_Handler",
"Default_Handler2",
"Default_Handler3",
"Default_Handler4",
"Default_Handler5",
"Default_Handler6",
"vPortSVCHandler",
"xPortPendSVHandler",
"xPortSysTickHandler",
"ISR_0_Handler",
"ISR_1_Handler",
"ISR_2_Handler",
"ISR_3_Handler",
"ISR_4_Handler",
"ISR_5_Handler",
"ISR_6_Handler",
"ISR_7_Handler",
"ISR_8_Handler",
"ISR_9_Handler",
"ISR_10_Handler",
"ISR_11_Handler",
"ISR_12_Handler",
"ISR_13_Handler",
];
/// User-defined ISR handler symbols in FreeRTOS.
pub(crate) const USR_ISR_SYMBOLS: &'static [&'static str] = &[
"ISR_0_Handler",
"ISR_1_Handler",
"ISR_2_Handler",
"ISR_3_Handler",
"ISR_4_Handler",
"ISR_5_Handler",
"ISR_6_Handler",
"ISR_7_Handler",
"ISR_8_Handler",
"ISR_9_Handler",
"ISR_10_Handler",
"ISR_11_Handler",
"ISR_12_Handler",
"ISR_13_Handler",
];
/// Adds FreeRTOS-specific symbols to the target symbol hashmap.
///
/// This function extracts essential FreeRTOS kernel symbols from the ELF file
/// and adds them to the provided address hashmap. These symbols are required
/// for system state observation during fuzzing.
///
/// # Arguments
///
/// * `elf` - Reference to the parsed ELF file containing the FreeRTOS kernel
/// * `addrs` - Mutable reference to the hashmap where symbol addresses will be stored
///
/// # Symbols Added
///
/// The function adds the following FreeRTOS kernel symbols:
/// - `pxCurrentTCB` - Pointer to the currently executing task control block
/// - `pxReadyTasksLists` - Array of ready task lists for each priority level
/// - `pxDelayedTaskList` - List of tasks waiting for a timeout
/// - `pxOverflowDelayedTaskList` - Overflow list for delayed tasks
/// - `uxSchedulerSuspended` - Counter for scheduler suspension nesting
/// - `xSchedulerRunning` - Flag indicating if the scheduler is running
/// - `uxCriticalNesting` - Critical section nesting counter
/// - `xQueueRegistry` - Registry of queues in the system
pub fn add_target_symbols(elf: &EasyElf, addrs: &mut HashMap<&'static str, GuestAddr>) {
// required for system state observation
addrs.insert("pxCurrentTCB", load_symbol(&elf, "pxCurrentTCB", false)); // loads to the address specified in elf, without respecting program headers
addrs.insert("pxCurrentTCB", load_elf_symbol(&elf, "pxCurrentTCB", false)); // loads to the address specified in elf, without respecting program headers
addrs.insert(
"pxReadyTasksLists",
load_symbol(&elf, "pxReadyTasksLists", false),
load_elf_symbol(&elf, "pxReadyTasksLists", false),
);
addrs.insert(
"pxDelayedTaskList",
load_symbol(&elf, "pxDelayedTaskList", false),
load_elf_symbol(&elf, "pxDelayedTaskList", false),
);
addrs.insert(
"pxOverflowDelayedTaskList",
load_symbol(&elf, "pxOverflowDelayedTaskList", false),
load_elf_symbol(&elf, "pxOverflowDelayedTaskList", false),
);
addrs.insert(
"uxSchedulerSuspended",
load_symbol(&elf, "uxSchedulerSuspended", false),
load_elf_symbol(&elf, "uxSchedulerSuspended", false),
);
addrs.insert(
"xSchedulerRunning",
load_symbol(&elf, "xSchedulerRunning", false),
load_elf_symbol(&elf, "xSchedulerRunning", false),
);
addrs.insert(
"uxCriticalNesting",
load_symbol(&elf, "uxCriticalNesting", false),
load_elf_symbol(&elf, "uxCriticalNesting", false),
);
addrs.insert(
"xQueueRegistry",
load_symbol(&elf, "xQueueRegistry", false),
load_elf_symbol(&elf, "xQueueRegistry", false),
);
}
// Group functions into api, app and isr functions
/// Groups functions into API, application, and ISR function categories.
///
/// This function categorizes all functions in the FreeRTOS binary into three groups:
/// API functions, application functions, and interrupt service routine (ISR) functions.
/// This categorization is used for targeted fuzzing and system state monitoring.
///
/// # Arguments
///
/// * `elf` - Reference to the parsed ELF file containing the FreeRTOS kernel and application
/// * `_addrs` - Reference to the symbol addresses hashmap (currently unused)
/// * `ranges` - Reference to a hashmap containing memory ranges for different code sections
///
/// # Returns
///
/// A hashmap containing three groups of functions:
/// - `"API_FN"` - FreeRTOS API functions (kernel functions excluding ISRs)
/// - `"APP_FN"` - Application-specific functions
/// - `"ISR_FN"` - Interrupt Service Routine functions
///
/// Each group maps function names to their memory address ranges.
///
/// # Behavior
///
/// 1. Extracts all functions from the API_CODE and APP_CODE memory ranges
/// 2. Identifies and removes ISR functions from both API and application function lists
/// 3. Creates a separate ISR function group containing all interrupt handlers
/// 4. Ensures all ISR symbols are properly categorized, even if not found in the initial ranges
///
/// # Note
///
/// The function uses predefined ISR symbol lists (`ISR_SYMBOLS`) to identify interrupt handlers.
/// If an ISR symbol is not found in the existing ranges, it attempts to locate it elsewhere
/// in the ELF file.
pub fn get_range_groups(
elf: &EasyElf,
_addrs: &HashMap<&'static str, GuestAddr>,
@ -50,10 +162,11 @@ pub fn get_range_groups(
let api_range = ranges.get("API_CODE").unwrap();
let app_range = ranges.get("APP_CODE").unwrap();
let mut api_fn_ranges = get_all_fn_symbol_ranges(&elf, api_range.clone());
let mut app_fn_ranges = get_all_fn_symbol_ranges(&elf, app_range.clone());
// Extract function symbols from API and application memory ranges
let mut api_fn_ranges = get_all_fn_symbols_in_range(&elf, api_range.clone());
let mut app_fn_ranges = get_all_fn_symbols_in_range(&elf, app_range.clone());
// Regular ISR functions, remove from API functions
// Create ISR function map by removing ISR symbols from API functions
let mut isr_fn_ranges: HashMap<String, std::ops::Range<GuestAddr>> = ISR_SYMBOLS
.iter()
.filter_map(|x| {
@ -62,7 +175,8 @@ pub fn get_range_groups(
.map(|y| (x.to_string(), y.clone()))
})
.collect();
// User-defined ISR functions, remove from APP functions
// Remove user-defined ISR functions from application functions and add to ISR group
ISR_SYMBOLS.iter().for_each(|x| {
let _ = (app_fn_ranges
.remove(&x.to_string())
@ -70,7 +184,7 @@ pub fn get_range_groups(
.map(|z| isr_fn_ranges.insert(z.0, z.1));
});
// Add the rest of the ISR function, if not already found
// Find any remaining ISR symbols that weren't in the initial ranges
for i in ISR_SYMBOLS {
if isr_fn_ranges.get(&i.to_string()).is_none() {
if let Some(fr) = get_function_range(&elf, i) {

View File

@ -1,4 +1,18 @@
use std::borrow::Cow;
use libafl_qemu::GuestAddr;
use crate::systemstate::{
helpers::get_icount,
target_os::{freertos::{extraction::qemu_module::{FreeRTOSSystemStateModule, MEM_READ}, FreeRTOSStruct, RawFreeRTOSSystemState}, *},
CaptureEvent,
};
use super::{
bindings::{self, *}, CURRENT_SYSTEMSTATE_VEC
};
pub mod qemu_module {
use std::borrow::Cow;
use std::ops::Range;
use freertos::FreeRTOSTraceMetadata;
@ -15,43 +29,53 @@ use libafl_qemu::{
};
use crate::{fuzzer::MAX_INPUT_SIZE, systemstate::{
helpers::{get_icount, in_any_range, read_rec_return_stackframe},
target_os::*,
CaptureEvent, RTOSJob,
}};
helpers::{get_icount, read_rec_return_stackframe, symbol_helpers::in_any_range},
target_os::{freertos::{extraction::trigger_collection, post_processing::{refine_system_states, states2intervals,get_releases,get_release_response_pairs}, *}, RTOSJob,
}}};
use super::{
bindings::{self, *}, post_processing::{get_release_response_pairs, get_releases, refine_system_states, states2intervals}, trigger_collection, CURRENT_SYSTEMSTATE_VEC
bindings::{self, *}, CURRENT_SYSTEMSTATE_VEC
};
//============================= Qemu Helper
//============================= Qemu Module
/// A Qemu Helper with reads FreeRTOS specific structs from Qemu whenever certain syscalls occur, also inject inputs
#[derive(Debug)]
pub struct FreeRTOSSystemStateHelper {
// Address of the application code
pub struct FreeRTOSSystemStateModule {
/// Address of the application code
pub app_range: Range<GuestAddr>,
// Address of API functions
/// Address of API functions
pub api_fn_addrs: HashMap<GuestAddr, Cow<'static, str>>,
/// Ranges of API functions
pub api_fn_ranges: Vec<(Cow<'static, str>, std::ops::Range<GuestAddr>)>,
// Address of interrupt routines
pub isr_fn_addrs: HashMap<GuestAddr, Cow<'static, str>>,
/// Ranges of interrupt routines
pub isr_fn_ranges: Vec<(Cow<'static, str>, std::ops::Range<GuestAddr>)>,
// Address of input memory
/// Range of input memory
pub input_mem: Range<GuestAddr>,
// FreeRTOS specific addresses
/// Address of the current TCB pointer
pub tcb_addr: GuestAddr,
/// Address of the ready queues
pub ready_queues: GuestAddr,
/// Address of the delay queue
pub delay_queue: GuestAddr,
/// Address of the delay overflow queue
pub delay_queue_overflow: GuestAddr,
/// Address of the scheduler lock variable
pub scheduler_lock_addr: GuestAddr,
/// Address of the scheduler running variable
pub scheduler_running_addr: GuestAddr,
/// Address of the critical nesting variable
pub critical_addr: GuestAddr,
/// Address of the function which signals that a job is done
pub job_done_addrs: GuestAddr,
pub queue_registry_addrs: GuestAddr,
/// Address of the queue registry (not used, but kept for possible future use)
pub _queue_registry_addrs: GuestAddr,
}
impl FreeRTOSSystemStateHelper {
impl FreeRTOSSystemStateModule {
#[must_use]
pub fn new(
target_symbols: &HashMap<&'static str, GuestAddr>,
@ -77,7 +101,7 @@ impl FreeRTOSSystemStateHelper {
let job_done_addrs = *target_symbols.get("trigger_job_done").unwrap();
let queue_registry_addrs = *target_symbols.get("xQueueRegistry").unwrap();
FreeRTOSSystemStateHelper {
FreeRTOSSystemStateModule {
app_range,
api_fn_addrs,
api_fn_ranges,
@ -92,12 +116,12 @@ impl FreeRTOSSystemStateHelper {
scheduler_running_addr,
critical_addr,
job_done_addrs,
queue_registry_addrs,
_queue_registry_addrs: queue_registry_addrs,
}
}
}
impl<S, I> EmulatorModule<S> for FreeRTOSSystemStateHelper
impl<S, I> EmulatorModule<S> for FreeRTOSSystemStateModule
where
S: UsesInput<Input = I> + Unpin + HasMetadata,
{
@ -280,6 +304,7 @@ where
//============================= Trace job response times
/// List of all finished jobs (icount, task name)
pub static mut JOBS_DONE: Vec<(u64, String)> = vec![];
pub fn job_done_hook<QT, S>(
@ -293,7 +318,7 @@ pub fn job_done_hook<QT, S>(
let emulator = hooks.qemu();
let h = hooks
.modules()
.match_first_type::<FreeRTOSSystemStateHelper>()
.match_first_type::<FreeRTOSSystemStateModule>()
.expect("QemuSystemHelper not found in helper tupel");
let curr_tcb_addr: bindings::void_ptr = super::QemuLookup::lookup(&emulator, h.tcb_addr);
if curr_tcb_addr == 0 {
@ -324,7 +349,7 @@ pub fn exec_isr_hook<QT, S>(
let emulator = hooks.qemu();
let h = hooks
.modules()
.match_first_type::<FreeRTOSSystemStateHelper>()
.match_first_type::<FreeRTOSSystemStateModule>()
.expect("QemuSystemHelper not found in helper tupel");
let src = read_rec_return_stackframe(&emulator, 0xfffffffc);
trigger_collection(&emulator, (src, pc), CaptureEvent::ISRStart, h);
@ -345,7 +370,7 @@ where
{
if let Some(h) = hooks
.modules()
.match_first_type::<FreeRTOSSystemStateHelper>()
.match_first_type::<FreeRTOSSystemStateModule>()
{
if h.app_range.contains(&src)
&& !h.app_range.contains(&dest)
@ -385,7 +410,7 @@ pub fn trace_jmp<QT, S>(
{
let h = hooks
.modules()
.match_first_type::<FreeRTOSSystemStateHelper>()
.match_first_type::<FreeRTOSSystemStateModule>()
.expect("QemuSystemHelper not found in helper tupel");
let emulator = hooks.qemu();
if id == 1 {
@ -433,7 +458,7 @@ where
{
if let Some(h) = hooks
.modules()
.match_first_type::<FreeRTOSSystemStateHelper>()
.match_first_type::<FreeRTOSSystemStateModule>()
{
if h.app_range.contains(&pc) {
// println!("gen_read {:x}", pc);
@ -472,4 +497,182 @@ pub fn trace_reads<QT, S>(
}
}
}
}
}
// ============================= FreeRTOS State Reading
/// Reads a FreeRTOS list from the target and populates the system state.
///
/// # Arguments
/// * `systemstate` - The mutable system state to populate.
/// * `emulator` - The QEMU emulator instance.
/// * `target` - The address of the list to read.
///
/// # Returns
/// A tuple containing the read list and a boolean indicating if the read was valid.
fn read_freertos_list(
systemstate: &mut RawFreeRTOSSystemState,
emulator: &libafl_qemu::Qemu,
target: GuestAddr,
) -> (List_t, bool) {
let read: List_t = QemuLookup::lookup(emulator, target);
let listbytes: GuestAddr = GuestAddr::try_from(std::mem::size_of::<List_t>()).unwrap();
let mut next_index = read.pxIndex;
for _j in 0..read.uxNumberOfItems {
// always jump over the xListEnd marker
if (target..target + listbytes).contains(&next_index) {
let next_item: MiniListItem_t = QemuLookup::lookup(emulator, next_index);
let new_next_index = next_item.pxNext;
systemstate
.dumping_ground
.insert(next_index, FreeRTOSStruct::List_MiniItem_struct(next_item));
next_index = new_next_index;
}
let next_item: ListItem_t = QemuLookup::lookup(emulator, next_index);
// println!("Item at {}: {:?}",next_index,next_item);
if next_item.pvContainer != target {
// the list is being modified, abort by setting the list empty
eprintln!("Warning: attempted to read a list that is being modified");
let mut read = read;
read.uxNumberOfItems = 0;
return (read, false);
}
// assert_eq!(next_item.pvContainer,target);
let new_next_index = next_item.pxNext;
let next_tcb: TCB_t = QemuLookup::lookup(emulator, next_item.pvOwner);
// println!("TCB at {}: {:?}",next_item.pvOwner,next_tcb);
systemstate.dumping_ground.insert(
next_item.pvOwner,
FreeRTOSStruct::TCB_struct(next_tcb.clone()),
);
systemstate
.dumping_ground
.insert(next_index, FreeRTOSStruct::List_Item_struct(next_item));
next_index = new_next_index;
}
// Handle edge case where the end marker was not included yet
if (target..target + listbytes).contains(&next_index) {
let next_item: freertos::MiniListItem_t = QemuLookup::lookup(emulator, next_index);
systemstate
.dumping_ground
.insert(next_index, FreeRTOSStruct::List_MiniItem_struct(next_item));
}
return (read, true);
}
/// Triggers the collection of a FreeRTOS system state snapshot at a given event.
///
/// # Arguments
/// * `emulator` - The QEMU emulator instance.
/// * `edge` - A tuple of (from, to) addresses representing the edge.
/// * `event` - The capture event type.
/// * `h` - The FreeRTOS system state helper.
#[inline]
fn trigger_collection(
emulator: &libafl_qemu::Qemu,
edge: (GuestAddr, GuestAddr),
event: CaptureEvent,
h: &FreeRTOSSystemStateModule,
) {
let listbytes: GuestAddr =
GuestAddr::try_from(std::mem::size_of::<freertos::List_t>()).unwrap();
let mut systemstate = RawFreeRTOSSystemState::default();
match event {
CaptureEvent::APIStart => {
let s : &Cow<'static, str> = h.api_fn_addrs.get(&edge.1).unwrap();
systemstate.capture_point = (CaptureEvent::APIStart, s.clone());
}
CaptureEvent::APIEnd => {
let s : &Cow<'static, str> = h.api_fn_addrs.get(&edge.0).unwrap();
systemstate.capture_point = (CaptureEvent::APIEnd, s.clone());
}
CaptureEvent::ISRStart => {
let s : &Cow<'static, str> = h.isr_fn_addrs.get(&edge.1).unwrap();
systemstate.capture_point = (CaptureEvent::ISRStart, s.clone());
}
CaptureEvent::ISREnd => {
let s : &Cow<'static, str> = h.isr_fn_addrs.get(&edge.0).unwrap();
systemstate.capture_point = (CaptureEvent::ISREnd, s.clone());
}
CaptureEvent::End => {
systemstate.capture_point = (CaptureEvent::End, Cow::Borrowed(""));
}
CaptureEvent::Undefined => (),
}
if systemstate.capture_point.0 == CaptureEvent::Undefined {
// println!("Not found: {:#x} {:#x}", edge.0.unwrap_or(0), edge.1.unwrap_or(0));
}
systemstate.edge = ((edge.0), (edge.1));
systemstate.qemu_tick = get_icount(emulator);
let curr_tcb_addr: freertos::void_ptr = QemuLookup::lookup(emulator, h.tcb_addr);
if curr_tcb_addr == 0 {
return;
};
/*
let mut queue_registry : Vec<QueueRegistryItem_t> = QemuLookup::lookup_slice(emulator, h.queue_registry_addrs, configQUEUE_REGISTRY_SIZE as usize);
let queue_registry = queue_registry.into_iter().filter(|x| x.xHandle != 0).map(|x| {
let queue_def: freertos::QueueDefinition = QemuLookup::lookup(emulator, x.xHandle);
let queue_name: String = emu_lookup_string(emulator, x.pcQueueName, None);
if queue_def.cRxLock == 0xFF && queue_def.cTxLock == 0xFF {
let sending = read_freertos_list(&mut systemstate, emulator, queue_def.xTasksWaitingToSend);
let recieving = read_freertos_list(&mut systemstate, emulator, queue_def.xTasksWaitingToSend);
}
(queue_def, queue_name)
}
).collect::<Vec<_>>();
dbg!(&queue_registry);
*/
// println!("{:?}",std::str::from_utf8(&current_tcb.pcTaskName));
let critical: void_ptr = QemuLookup::lookup(emulator, h.critical_addr);
let suspended: void_ptr = QemuLookup::lookup(emulator, h.scheduler_lock_addr);
let _running: void_ptr = QemuLookup::lookup(emulator, h.scheduler_running_addr);
systemstate.current_tcb = QemuLookup::lookup(emulator, curr_tcb_addr);
// During ISRs it is only safe to extract structs if they are not currently being modified
if systemstate.capture_point.0 == CaptureEvent::APIStart
|| systemstate.capture_point.0 == CaptureEvent::APIEnd
|| (critical == 0 && suspended == 0)
{
// Extract delay list
let mut target: GuestAddr = h.delay_queue;
target = QemuLookup::lookup(emulator, target);
let _temp = read_freertos_list(&mut systemstate, emulator, target);
systemstate.delay_list = _temp.0;
systemstate.read_invalid |= !_temp.1;
// Extract delay list overflow
let mut target: GuestAddr = h.delay_queue_overflow;
target = QemuLookup::lookup(emulator, target);
let _temp = read_freertos_list(&mut systemstate, emulator, target);
systemstate.delay_list_overflow = _temp.0;
systemstate.read_invalid |= !_temp.1;
// Extract suspended tasks (infinite wait), seems broken, always appreas to be modified
// let mut target : GuestAddr = h.suspended_queue;
// target = QemuLookup::lookup(emulator, target);
// systemstate.suspended_list = read_freertos_list(&mut systemstate, emulator, target);
// Extract priority lists
for i in 0..configMAX_PRIORITIES as usize {
let target: GuestAddr = listbytes * GuestAddr::try_from(i).unwrap() + h.ready_queues;
let _temp = read_freertos_list(&mut systemstate, emulator, target);
systemstate.prio_ready_lists[i] = _temp.0;
systemstate.read_invalid |= !_temp.1;
}
} else {
systemstate.read_invalid = true;
}
systemstate.mem_reads = unsafe { std::mem::replace((&raw mut MEM_READ).as_mut().unwrap(), vec![])};
unsafe {
(&raw mut CURRENT_SYSTEMSTATE_VEC).as_mut().unwrap().push(systemstate);
}
}

View File

@ -1,32 +1,25 @@
#![allow(non_camel_case_types)]
use libafl_qemu::GuestAddr;
use qemu_module::{FreeRTOSSystemStateHelper, MEM_READ};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use crate::{
impl_emu_lookup,
systemstate::{helpers::get_icount, CaptureEvent},
systemstate::CaptureEvent,
};
pub mod bindings;
pub mod qemu_module;
pub mod extraction;
pub mod config;
pub mod post_processing;
use bindings::*;
use super::QemuLookup;
use crate::systemstate::target_os::*;
// Constants
const NUM_PRIOS: usize = 15;
//============================================================================= Outside interface
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FreeRTOSSystem {
pub raw_trace: Vec<RawFreeRTOSSystemState>,
}
pub struct FreeRTOSSystem {}
impl TargetSystem for FreeRTOSSystem {
type State = FreeRTOSSystemState;
@ -54,7 +47,7 @@ impl SystemState for FreeRTOSSystemState {
&self.ready_list_after
}
fn get_delay_list(&self) -> &Vec<Self::TCB> {
fn _get_delay_list(&self) -> &Vec<Self::TCB> {
&self.delay_list_after
}
@ -70,7 +63,7 @@ impl SystemState for FreeRTOSSystemState {
//============================================================================= Data structures
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum FreeRTOSStruct {
enum FreeRTOSStruct {
TCB_struct(TCB_t),
List_struct(List_t),
List_Item_struct(ListItem_t),
@ -86,233 +79,16 @@ impl_emu_lookup!(TaskStatus_t);
impl_emu_lookup!(QueueRegistryItem_t);
impl_emu_lookup!(Queue_t);
pub const ISR_SYMBOLS: &'static [&'static str] = &[
// ISRs
"Reset_Handler",
"Default_Handler",
"Default_Handler2",
"Default_Handler3",
"Default_Handler4",
"Default_Handler5",
"Default_Handler6",
"vPortSVCHandler",
"xPortPendSVHandler",
"xPortSysTickHandler",
"ISR_0_Handler",
"ISR_1_Handler",
"ISR_2_Handler",
"ISR_3_Handler",
"ISR_4_Handler",
"ISR_5_Handler",
"ISR_6_Handler",
"ISR_7_Handler",
"ISR_8_Handler",
"ISR_9_Handler",
"ISR_10_Handler",
"ISR_11_Handler",
"ISR_12_Handler",
"ISR_13_Handler",
];
pub const USR_ISR_SYMBOLS: &'static [&'static str] = &[
"ISR_0_Handler",
"ISR_1_Handler",
"ISR_2_Handler",
"ISR_3_Handler",
"ISR_4_Handler",
"ISR_5_Handler",
"ISR_6_Handler",
"ISR_7_Handler",
"ISR_8_Handler",
"ISR_9_Handler",
"ISR_10_Handler",
"ISR_11_Handler",
"ISR_12_Handler",
"ISR_13_Handler",
];
//============================================================================= Helper functions
/// Reads a FreeRTOS list from the target and populates the system state.
///
/// # Arguments
/// * `systemstate` - The mutable system state to populate.
/// * `emulator` - The QEMU emulator instance.
/// * `target` - The address of the list to read.
///
/// # Returns
/// A tuple containing the read list and a boolean indicating if the read was valid.
fn read_freertos_list(
systemstate: &mut RawFreeRTOSSystemState,
emulator: &libafl_qemu::Qemu,
target: GuestAddr,
) -> (List_t, bool) {
let read: List_t = QemuLookup::lookup(emulator, target);
let listbytes: GuestAddr = GuestAddr::try_from(std::mem::size_of::<List_t>()).unwrap();
let mut next_index = read.pxIndex;
for _j in 0..read.uxNumberOfItems {
// always jump over the xListEnd marker
if (target..target + listbytes).contains(&next_index) {
let next_item: MiniListItem_t = QemuLookup::lookup(emulator, next_index);
let new_next_index = next_item.pxNext;
systemstate
.dumping_ground
.insert(next_index, FreeRTOSStruct::List_MiniItem_struct(next_item));
next_index = new_next_index;
}
let next_item: ListItem_t = QemuLookup::lookup(emulator, next_index);
// println!("Item at {}: {:?}",next_index,next_item);
if next_item.pvContainer != target {
// the list is being modified, abort by setting the list empty
eprintln!("Warning: attempted to read a list that is being modified");
let mut read = read;
read.uxNumberOfItems = 0;
return (read, false);
}
// assert_eq!(next_item.pvContainer,target);
let new_next_index = next_item.pxNext;
let next_tcb: TCB_t = QemuLookup::lookup(emulator, next_item.pvOwner);
// println!("TCB at {}: {:?}",next_item.pvOwner,next_tcb);
systemstate.dumping_ground.insert(
next_item.pvOwner,
FreeRTOSStruct::TCB_struct(next_tcb.clone()),
);
systemstate
.dumping_ground
.insert(next_index, FreeRTOSStruct::List_Item_struct(next_item));
next_index = new_next_index;
}
// Handle edge case where the end marker was not included yet
if (target..target + listbytes).contains(&next_index) {
let next_item: freertos::MiniListItem_t = QemuLookup::lookup(emulator, next_index);
systemstate
.dumping_ground
.insert(next_index, FreeRTOSStruct::List_MiniItem_struct(next_item));
}
return (read, true);
}
/// Triggers the collection of a FreeRTOS system state snapshot at a given event.
///
/// # Arguments
/// * `emulator` - The QEMU emulator instance.
/// * `edge` - A tuple of (from, to) addresses representing the edge.
/// * `event` - The capture event type.
/// * `h` - The FreeRTOS system state helper.
#[inline]
fn trigger_collection(
emulator: &libafl_qemu::Qemu,
edge: (GuestAddr, GuestAddr),
event: CaptureEvent,
h: &FreeRTOSSystemStateHelper,
) {
let listbytes: GuestAddr =
GuestAddr::try_from(std::mem::size_of::<freertos::List_t>()).unwrap();
let mut systemstate = RawFreeRTOSSystemState::default();
match event {
CaptureEvent::APIStart => {
let s : &Cow<'static, str> = h.api_fn_addrs.get(&edge.1).unwrap();
systemstate.capture_point = (CaptureEvent::APIStart, s.clone());
}
CaptureEvent::APIEnd => {
let s : &Cow<'static, str> = h.api_fn_addrs.get(&edge.0).unwrap();
systemstate.capture_point = (CaptureEvent::APIEnd, s.clone());
}
CaptureEvent::ISRStart => {
let s : &Cow<'static, str> = h.isr_fn_addrs.get(&edge.1).unwrap();
systemstate.capture_point = (CaptureEvent::ISRStart, s.clone());
}
CaptureEvent::ISREnd => {
let s : &Cow<'static, str> = h.isr_fn_addrs.get(&edge.0).unwrap();
systemstate.capture_point = (CaptureEvent::ISREnd, s.clone());
}
CaptureEvent::End => {
systemstate.capture_point = (CaptureEvent::End, Cow::Borrowed(""));
}
CaptureEvent::Undefined => (),
}
if systemstate.capture_point.0 == CaptureEvent::Undefined {
// println!("Not found: {:#x} {:#x}", edge.0.unwrap_or(0), edge.1.unwrap_or(0));
}
systemstate.edge = ((edge.0), (edge.1));
systemstate.qemu_tick = get_icount(emulator);
let curr_tcb_addr: freertos::void_ptr = QemuLookup::lookup(emulator, h.tcb_addr);
if curr_tcb_addr == 0 {
return;
};
/*
let mut queue_registry : Vec<QueueRegistryItem_t> = QemuLookup::lookup_slice(emulator, h.queue_registry_addrs, configQUEUE_REGISTRY_SIZE as usize);
let queue_registry = queue_registry.into_iter().filter(|x| x.xHandle != 0).map(|x| {
let queue_def: freertos::QueueDefinition = QemuLookup::lookup(emulator, x.xHandle);
let queue_name: String = emu_lookup_string(emulator, x.pcQueueName, None);
if queue_def.cRxLock == 0xFF && queue_def.cTxLock == 0xFF {
let sending = read_freertos_list(&mut systemstate, emulator, queue_def.xTasksWaitingToSend);
let recieving = read_freertos_list(&mut systemstate, emulator, queue_def.xTasksWaitingToSend);
}
(queue_def, queue_name)
}
).collect::<Vec<_>>();
dbg!(&queue_registry);
*/
// println!("{:?}",std::str::from_utf8(&current_tcb.pcTaskName));
let critical: void_ptr = QemuLookup::lookup(emulator, h.critical_addr);
let suspended: void_ptr = QemuLookup::lookup(emulator, h.scheduler_lock_addr);
let _running: void_ptr = QemuLookup::lookup(emulator, h.scheduler_running_addr);
systemstate.current_tcb = QemuLookup::lookup(emulator, curr_tcb_addr);
// During ISRs it is only safe to extract structs if they are not currently being modified
if systemstate.capture_point.0 == CaptureEvent::APIStart
|| systemstate.capture_point.0 == CaptureEvent::APIEnd
|| (critical == 0 && suspended == 0)
{
// Extract delay list
let mut target: GuestAddr = h.delay_queue;
target = QemuLookup::lookup(emulator, target);
let _temp = read_freertos_list(&mut systemstate, emulator, target);
systemstate.delay_list = _temp.0;
systemstate.read_invalid |= !_temp.1;
// Extract delay list overflow
let mut target: GuestAddr = h.delay_queue_overflow;
target = QemuLookup::lookup(emulator, target);
let _temp = read_freertos_list(&mut systemstate, emulator, target);
systemstate.delay_list_overflow = _temp.0;
systemstate.read_invalid |= !_temp.1;
// Extract suspended tasks (infinite wait), seems broken, always appreas to be modified
// let mut target : GuestAddr = h.suspended_queue;
// target = QemuLookup::lookup(emulator, target);
// systemstate.suspended_list = read_freertos_list(&mut systemstate, emulator, target);
// Extract priority lists
for i in 0..NUM_PRIOS {
let target: GuestAddr = listbytes * GuestAddr::try_from(i).unwrap() + h.ready_queues;
let _temp = read_freertos_list(&mut systemstate, emulator, target);
systemstate.prio_ready_lists[i] = _temp.0;
systemstate.read_invalid |= !_temp.1;
}
} else {
systemstate.read_invalid = true;
}
systemstate.mem_reads = unsafe { std::mem::replace((&raw mut MEM_READ).as_mut().unwrap(), vec![])};
unsafe {
(&raw mut CURRENT_SYSTEMSTATE_VEC).as_mut().unwrap().push(systemstate);
}
}
/// Raw info Dump from Qemu
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct RawFreeRTOSSystemState {
pub(super) struct RawFreeRTOSSystemState {
qemu_tick: u64,
current_tcb: TCB_t,
prio_ready_lists: [freertos::List_t; NUM_PRIOS],
prio_ready_lists: [freertos::List_t; configMAX_PRIORITIES as usize],
delay_list: freertos::List_t,
delay_list_overflow: freertos::List_t,
dumping_ground: HashMap<u32, freertos::FreeRTOSStruct>,
@ -492,7 +268,7 @@ impl fmt::Display for FreeRTOSSystemState {
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub(crate)struct FreeRTOSSystemStateContext {
pub(super)struct FreeRTOSSystemStateContext {
pub qemu_tick: u64,
pub capture_point: (CaptureEvent, Cow<'static, str>),
pub edge: (GuestAddr, GuestAddr),

View File

@ -1,6 +1,45 @@
//! Utilities to refine and analyze raw FreeRTOS captures into higher-level
//! runtime artifacts.
//!
//! This module performs several post-processing steps on traces produced by
//! the FreeRTOS target collector:
//!
//! - Parsing helpers to extract TCB lists from the dumped memory cache
//! (tcb_list_to_vec_cached).
//! - State refinement that converts RawFreeRTOSSystemState into
//! FreeRTOSSystemState and FreeRTOSSystemStateContext pairs
//! (refine_system_states).
//! - Conversion of a sequence of states + metadata into execution
//! intervals (ExecInterval), tracking state hashes and memory reads
//! (states2intervals).
//! - Annotation of intervals with executed Atomic Basic Blocks (ABBs)
//! by matching capture events and trace edges (add_abb_to_interval).
//! - Extraction of task release events from interval transitions
//! (get_releases) and pairing releases with responses
//! (get_release_response_pairs).
//!
//! Key assumptions and behavior
//! - The input traces are sequences of captured system states along with
//! capture events (API/ISR start/end). Many algorithms rely on the
//! availability of valid reads in neighboring states when the direct
//! state is marked read_invalid.
//! - ABB annotation assumes consistent capture events and edge tracing —
//! unexpected or missing starts/ends will be tolerated where possible
//! but may set return flags indicating imperfect reconstruction.
//! - get_releases attempts to detect releases from timer ISRs and API
//! calls and employs heuristics to tolerate nested interrupts and
//! transient invalid reads.
//! - get_release_response_pairs uses simple bookkeeping to match releases
//! to responses; it flags potential ambiguities via the returned bool.
//!
//! Note: The module focuses on deterministic and conservative
//! reconstruction; the heuristics present are tuned to real-world trace
//! imperfections and prioritize producing usable release/response pairs
//! over strict completeness.
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
use freertos::USR_ISR_SYMBOLS;
use freertos::config::USR_ISR_SYMBOLS;
use hashbrown::HashMap;
use crate::systemstate::{
@ -14,6 +53,22 @@ use super::{
FreeRTOSSystemStateContext, RawFreeRTOSSystemState, RefinedTCB,
};
/* Call-graph
States:
refine_system_states
-> tcb_list_to_vec_cached
Intervals:
states2intervals
-> add_abb_to_interval
Jobs:
get_releases
get_release_response_pairs
*/
//============================= Parsing helpers
/// Parse a List_t containing TCB_t into Vec<TCB_t> from cache. Consumes the elements from cache
@ -26,7 +81,7 @@ use super::{
/// # Returns
///
/// A Vec of TCB_t extracted from the list.
pub fn tcb_list_to_vec_cached(list: List_t, dump: &mut HashMap<u32, FreeRTOSStruct>) -> Vec<TCB_t> {
fn tcb_list_to_vec_cached(list: List_t, dump: &mut HashMap<u32, FreeRTOSStruct>) -> Vec<TCB_t> {
let mut ret: Vec<TCB_t> = Vec::new();
if list.uxNumberOfItems == 0 {
return ret;
@ -94,7 +149,7 @@ pub fn tcb_list_to_vec_cached(list: List_t, dump: &mut HashMap<u32, FreeRTOSStru
/// A tuple containing:
/// - Vec of FreeRTOSSystemState
/// - Vec of FreeRTOSSystemStateContext (qemu_tick, (capture_event, capture_name), edge, mem_reads)
pub(crate) fn refine_system_states(
pub(super) fn refine_system_states(
mut input: Vec<RawFreeRTOSSystemState>,
) -> (Vec<FreeRTOSSystemState>, Vec<FreeRTOSSystemStateContext>) {
let mut ret = (Vec::<_>::new(), Vec::<_>::new());
@ -113,8 +168,8 @@ pub(crate) fn refine_system_states(
#[cfg(feature = "observe_systemstate_unordered")]
{
// respect the order of the first ``lookahead`` tasks and sort the rest by task name
const lookahead : usize = 2;
collector.get_mut(lookahead..).map(|x| x.sort_by(|a, b| a.task_name.cmp(&b.task_name)));
const LOOKAHEAD : usize = 2;
collector.get_mut(LOOKAHEAD..).map(|x| x.sort_by(|a, b| a.task_name.cmp(&b.task_name)));
}
// collect delay list
@ -162,7 +217,7 @@ pub(crate) fn refine_system_states(
/// - Vec of Vec<(u32, u8)> marking memory reads during these intervals
/// - HashMap<u64, FreeRTOSSystemState> by hash
/// - bool indicating success
pub(crate) fn states2intervals(
pub(super) fn states2intervals(
trace: Vec<FreeRTOSSystemState>,
meta: Vec<FreeRTOSSystemStateContext>,
) -> (
@ -263,7 +318,7 @@ pub(crate) fn states2intervals(
last_hash = next_hash;
edges.push((meta[i].edge.1, meta[i + 1].edge.0));
}
let t = add_abb_info(&mut ret, &table, &edges);
let t = add_abb_to_interval(&mut ret, &table, &edges);
(ret, reads, table, t)
}
@ -278,7 +333,7 @@ pub(crate) fn states2intervals(
/// # Returns
///
/// A bool indicating whether ABB annotation was successful.
pub(crate) fn add_abb_info(
fn add_abb_to_interval(
trace: &mut Vec<ExecInterval>,
state_table: &HashMap<u64, FreeRTOSSystemState>,
edges: &Vec<(u32, u32)>,
@ -485,7 +540,7 @@ pub(crate) fn add_abb_info(
//============================================= Task release times
// Find all task release times.
/// Find all task release times by looking for chanegs in the ready list
///
/// # Arguments
///
@ -495,7 +550,7 @@ pub(crate) fn add_abb_info(
/// # Returns
///
/// A Vec of (u64, String) tuples, where each tuple is (tick, task_name) for each release event.
pub(crate) fn get_releases(
pub(super) fn get_releases(
trace: &Vec<ExecInterval>,
states: &HashMap<u64, FreeRTOSSystemState>,
) -> Vec<(u64, String)> {
@ -584,11 +639,6 @@ pub(crate) fn get_releases(
ret.push((i.end_tick, x.task_name.clone()));
}
});
// start_state.delay_list_after.iter().for_each(|x| {
// if !end_state.delay_list_after.iter().any(|y| x.task_name == y.task_name) {
// ret.push((i.end_tick, x.task_name.clone()));
// }
// });
} else if i.end_capture.0 == CaptureEvent::ISRStart {
// Nested interrupts. Fast-forward to the end of the original interrupt, or the first valid state thereafter
// TODO: this may cause the same release to be registered multiple times
@ -618,17 +668,6 @@ pub(crate) fn get_releases(
break;
}
}
// if let Some(interval_end) = trace.get(_n+2) {
// if interval_end.start_capture.0 == CaptureEvent::ISREnd && interval_end.end_capture.0 == CaptureEvent::ISREnd && interval_end.end_capture.1 == i.start_capture.1 {
// let start_state = states.get(&i.start_state).expect("State not found");
// let end_state = states.get(&interval_end.end_state).expect("State not found");
// end_state.ready_list_after.iter().for_each(|x| {
// if x.task_name != end_state.current_task.task_name && x.task_name != start_state.current_task.task_name && !start_state.ready_list_after.iter().any(|y| x.task_name == y.task_name) {
// ret.push((i.end_tick, x.task_name.clone()));
// }
// });
// }
// }
}
}
// Release driven by an API call. This produces a lot of false positives, as a job may block multiple times per instance. Despite this, aperiodic jobs not be modeled otherwise. If we assume the first release is the real one, we can filter out the rest.
@ -680,7 +719,7 @@ pub(crate) fn get_releases(
/// A tuple containing:
/// - Vec of (u64, u64, String) tuples for (release_tick, response_tick, task_name)
/// - bool indicating if there was a possible error in pairing
pub(crate) fn get_release_response_pairs(
pub(super) fn get_release_response_pairs(
rel: &Vec<(u64, String)>,
resp: &Vec<(u64, String)>,
) -> (Vec<(u64, u64, String)>, bool) {

View File

@ -36,7 +36,7 @@ pub trait SystemState: Serialize + Sized + for<'a> Deserialize<'a> + Default + D
fn current_task(&self) -> &Self::TCB;
fn current_task_mut(&mut self) -> &mut Self::TCB;
fn get_ready_lists(&self) -> &Vec<Self::TCB>;
fn get_delay_list(&self) -> &Vec<Self::TCB>;
fn _get_delay_list(&self) -> &Vec<Self::TCB>;
fn print_lists(&self) -> String;
}