documentation

This commit is contained in:
Alwin Berger 2025-08-16 07:58:58 +00:00
parent a7e00004b2
commit 25d598f30b
4 changed files with 196 additions and 16 deletions

View File

@ -12,7 +12,14 @@ use super::ExecInterval;
//============================= API symbols //============================= API symbols
/// Read ELF program headers to resolve physical load addresses. /// Resolves a virtual address to a physical address using ELF program headers.
///
/// # Arguments
/// * `vaddr` - The virtual address to resolve.
/// * `tab` - The ELF file containing program headers.
///
/// # Returns
/// The corresponding physical address, or the original address if not found.
fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr { fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr {
let ret; let ret;
for i in &tab.goblin().program_headers { for i in &tab.goblin().program_headers {
@ -25,12 +32,31 @@ fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr {
return vaddr; return vaddr;
} }
/// Lookup a symbol in the ELF file, optionally resolve segment offsets /// Looks up a symbol in the ELF file and returns its address, optionally translating to a physical address.
///
/// # Arguments
/// * `elf` - The ELF file to search.
/// * `symbol` - The symbol name to look up.
/// * `do_translation` - Whether to translate the address to a physical address.
///
/// # Panics
/// Panics if the symbol is not found.
///
/// # Returns
/// The address of the symbol.
pub fn load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> GuestAddr { 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)) try_load_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol))
} }
/// Lookup a symbol in the ELF file, optionally resolve segment offsets /// Looks up a symbol in the ELF file and returns its address, optionally translating to a physical address.
///
/// # Arguments
/// * `elf` - The ELF file to search.
/// * `symbol` - The symbol name to look up.
/// * `do_translation` - Whether to translate the address to a physical address.
///
/// # 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_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Option<GuestAddr> {
let ret = elf.resolve_symbol(symbol, 0); let ret = elf.resolve_symbol(symbol, 0);
if do_translation { if do_translation {
@ -42,7 +68,14 @@ pub fn try_load_symbol(elf: &EasyElf, symbol: &str, do_translation: bool) -> Opt
} }
} }
/// Try looking up the address range of a function in the ELF file /// Returns the address range of a function symbol in the ELF file.
///
/// # Arguments
/// * `elf` - The ELF file to search.
/// * `symbol` - The function symbol name.
///
/// # Returns
/// Some(range) if found, None otherwise.
pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option<std::ops::Range<GuestAddr>> { pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option<std::ops::Range<GuestAddr>> {
let gob = elf.goblin(); let gob = elf.goblin();
@ -74,7 +107,14 @@ pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option<std::ops::Range
return None; return None;
} }
/// Check if an address is in any of the ranges /// Checks if an address is within any of the provided ranges.
///
/// # Arguments
/// * `ranges` - A vector of (name, range) tuples.
/// * `addr` - The address to check.
///
/// # Returns
/// Some(range) if the address is in any range, None otherwise.
pub fn in_any_range<'a>( pub fn in_any_range<'a>(
ranges: &'a Vec<(Cow<'static, str>, Range<u32>)>, ranges: &'a Vec<(Cow<'static, str>, Range<u32>)>,
addr: GuestAddr, addr: GuestAddr,
@ -89,6 +129,13 @@ pub fn in_any_range<'a>(
//============================= QEMU related utility functions //============================= QEMU related utility functions
/// Retrieves the current QEMU instruction count.
///
/// # Arguments
/// * `emulator` - The QEMU emulator instance.
///
/// # Returns
/// The current instruction count as a u64.
pub fn get_icount(emulator: &libafl_qemu::Qemu) -> u64 { pub fn get_icount(emulator: &libafl_qemu::Qemu) -> u64 {
unsafe { unsafe {
// TODO: investigate why can_do_io is not set sometimes, as this is just a workaround // TODO: investigate why can_do_io is not set sometimes, as this is just a workaround
@ -101,6 +148,14 @@ pub fn get_icount(emulator: &libafl_qemu::Qemu) -> u64 {
} }
} }
/// Converts input bytes to a vector of interrupt times, enforcing minimum inter-arrival time.
///
/// # Arguments
/// * `buf` - The input byte buffer.
/// * `config` - Tuple of (number of interrupts, minimum inter-arrival time).
///
/// # Returns
/// A sorted vector of interrupt times.
pub fn input_bytes_to_interrupt_times(buf: &[u8], config: (usize, u32)) -> Vec<u32> { pub fn input_bytes_to_interrupt_times(buf: &[u8], config: (usize, u32)) -> Vec<u32> {
let len = buf.len(); let len = buf.len();
let mut start_tick; let mut start_tick;
@ -140,6 +195,13 @@ pub fn input_bytes_to_interrupt_times(buf: &[u8], config: (usize, u32)) -> Vec<u
ret ret
} }
/// Converts interrupt times back to input bytes.
///
/// # Arguments
/// * `interrupt_times` - A slice of interrupt times.
///
/// # Returns
/// A vector of bytes representing the interrupt times.
pub fn interrupt_times_to_input_bytes(interrupt_times: &[u32]) -> Vec<u8> { pub fn interrupt_times_to_input_bytes(interrupt_times: &[u32]) -> Vec<u8> {
let mut ret = Vec::with_capacity(interrupt_times.len() * 4); let mut ret = Vec::with_capacity(interrupt_times.len() * 4);
for i in interrupt_times { for i in interrupt_times {
@ -148,6 +210,14 @@ pub fn interrupt_times_to_input_bytes(interrupt_times: &[u32]) -> Vec<u8> {
ret ret
} }
/// Reads the return address from the stack frame, handling ARM exception return conventions.
///
/// # Arguments
/// * `emu` - The QEMU emulator instance.
/// * `lr` - The link register value.
///
/// # Returns
/// The return address from the stack frame.
pub fn read_rec_return_stackframe(emu: &libafl_qemu::Qemu, lr: GuestAddr) -> GuestAddr { pub fn read_rec_return_stackframe(emu: &libafl_qemu::Qemu, lr: GuestAddr) -> GuestAddr {
let lr_ = lr & u32::MAX - 1; let lr_ = lr & u32::MAX - 1;
if lr_ == 0xfffffffc || lr_ == 0xFFFFFFF8 || lr_ == 0xFFFFFFF0 { if lr_ == 0xfffffffc || lr_ == 0xFFFFFFF8 || lr_ == 0xFFFFFFF0 {
@ -171,6 +241,15 @@ pub fn read_rec_return_stackframe(emu: &libafl_qemu::Qemu, lr: GuestAddr) -> Gue
//============================= Tracing related utility functions //============================= Tracing related utility functions
/// Inserts or updates metadata in a map, returning a mutable reference.
///
/// # Arguments
/// * `metadata` - The metadata map.
/// * `default` - Function to create a default value if not present.
/// * `update` - Function to update the value if present.
///
/// # Returns
/// A mutable reference to the metadata value.
pub fn metadata_insert_or_update_get<T>( pub fn metadata_insert_or_update_get<T>(
metadata: &mut SerdeAnyMap, metadata: &mut SerdeAnyMap,
default: impl FnOnce() -> T, default: impl FnOnce() -> T,
@ -188,8 +267,13 @@ where
} }
} }
/// Build an ABB-profile from a stretch of intervals /// Builds an ABB (atomic basic block) profile from execution intervals.
/// returns mapping: task_name -> (abb_addr -> (interval_count, exec_count, exec_time, woet)) ///
/// # Arguments
/// * `intervals` - A vector of execution intervals.
///
/// # Returns
/// A mapping from task name to ABB address to (interval count, exec count, exec time, woet).
#[allow(unused)] #[allow(unused)]
pub fn abb_profile( pub fn abb_profile(
mut intervals: Vec<ExecInterval>, mut intervals: Vec<ExecInterval>,
@ -270,6 +354,13 @@ pub fn abb_profile(
ret ret
} }
/// Returns an immutable reference from a mutable one.
///
/// # Arguments
/// * `x` - A mutable reference.
///
/// # Returns
/// An immutable reference to the same value.
pub fn unmut<T>(x: &mut T) -> &T { pub fn unmut<T>(x: &mut T) -> &T {
&(*x) &(*x)
} }

View File

@ -55,10 +55,15 @@ pub enum CaptureEvent {
pub struct ExecInterval { pub struct ExecInterval {
pub start_tick: u64, pub start_tick: u64,
pub end_tick: u64, pub end_tick: u64,
/// Hash of the start state
pub start_state: u64, pub start_state: u64,
/// Hash of the end state
pub end_state: u64, pub end_state: u64,
/// The event that started this interval
pub start_capture: (CaptureEvent, Cow<'static, str>), pub start_capture: (CaptureEvent, Cow<'static, str>),
/// The event that ended this interval
pub end_capture: (CaptureEvent, Cow<'static, str>), pub end_capture: (CaptureEvent, Cow<'static, str>),
/// Execution level: 0 = APP, 1 = API, 2 = ISR
pub level: u8, pub level: u8,
// tick_spend_preempted: u64, // tick_spend_preempted: u64,
pub abb: Option<AtomicBasicBlock> pub abb: Option<AtomicBasicBlock>
@ -217,6 +222,7 @@ libafl_bolts::impl_serdeany!(AtomicBasicBlock);
// ============================= Job instances // ============================= Job instances
/// Represents a single execution of a task, recording the place and input read.
#[derive(Debug, Default, Serialize, Deserialize, Clone)] #[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct RTOSJob { pub struct RTOSJob {
pub name: String, pub name: String,
@ -288,6 +294,7 @@ impl Hash for RTOSTask {
} }
} }
impl RTOSTask { impl RTOSTask {
/// Returns the hash value for the task, computing it if not cached.
pub fn get_hash(&mut self) -> u64 { pub fn get_hash(&mut self) -> u64 {
if self.hash_cache == 0 { if self.hash_cache == 0 {
let mut s = DefaultHasher::new(); let mut s = DefaultHasher::new();
@ -296,6 +303,7 @@ impl RTOSTask {
} }
self.hash_cache self.hash_cache
} }
/// Returns the cached hash value for the task.
pub fn get_hash_cached(&self) -> u64 { pub fn get_hash_cached(&self) -> u64 {
if self.hash_cache == 0 { if self.hash_cache == 0 {
let mut s = DefaultHasher::new(); let mut s = DefaultHasher::new();
@ -305,7 +313,7 @@ impl RTOSTask {
self.hash_cache self.hash_cache
} }
} }
/// Update woet (time, inputs) and wort (time only) if the new instance is better /// Update WOET (time, inputs) and WORT (time only) if the new instance is better
pub fn try_update(&mut self, other: &RTOSJob) -> bool { pub fn try_update(&mut self, other: &RTOSJob) -> bool {
assert_eq!(self.get_hash(), other.get_hash_cached()); assert_eq!(self.get_hash(), other.get_hash_cached());
let mut ret = false; let mut ret = false;
@ -321,6 +329,7 @@ impl RTOSTask {
} }
ret ret
} }
/// Creates a RTOSTask instance from a given RTOSJob instance.
pub fn from_instance(input: &RTOSJob) -> Self { pub fn from_instance(input: &RTOSJob) -> Self {
let c = input.get_hash_cached(); let c = input.get_hash_cached();
Self { Self {
@ -333,9 +342,24 @@ impl RTOSTask {
hash_cache: c hash_cache: c
} }
} }
pub fn map_bytes_onto(&self, input: &RTOSJob, offset: Option<u32>) -> Vec<(u32,u8)> { /// Maps bytes onto a given RTOSJob instance, returning the differences.
if input.mem_reads.len() == 0 {return vec![];} pub fn map_bytes_onto(&self, input: &RTOSJob, offset: Option<u32>) -> Vec<(u32, u8)> {
let ret = input.mem_reads.iter().take(self.woet_bytes.len()).enumerate().filter_map(|(idx,(addr,oldbyte))| if self.woet_bytes[idx]!=*oldbyte {Some((*addr-offset.unwrap_or_default(), self.woet_bytes[idx]))} else {None}).collect(); if input.mem_reads.len() == 0 {
return vec![];
}
let ret = input
.mem_reads
.iter()
.take(self.woet_bytes.len())
.enumerate()
.filter_map(|(idx, (addr, oldbyte))| {
if self.woet_bytes[idx] != *oldbyte {
Some((*addr - offset.unwrap_or_default(), self.woet_bytes[idx]))
} else {
None
}
})
.collect();
// eprintln!("Mapped: {:?}", ret); // eprintln!("Mapped: {:?}", ret);
ret ret
} }

View File

@ -128,6 +128,15 @@ pub const USR_ISR_SYMBOLS: &'static [&'static str] = &[
//============================================================================= Helper functions //============================================================================= 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( fn read_freertos_list(
systemstate: &mut RawFreeRTOSSystemState, systemstate: &mut RawFreeRTOSSystemState,
emulator: &libafl_qemu::Qemu, emulator: &libafl_qemu::Qemu,
@ -179,6 +188,13 @@ fn read_freertos_list(
return (read, true); 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] #[inline]
fn trigger_collection( fn trigger_collection(
emulator: &libafl_qemu::Qemu, emulator: &libafl_qemu::Qemu,
@ -327,6 +343,13 @@ impl Hash for RefinedTCB {
} }
impl RefinedTCB { impl RefinedTCB {
/// Constructs a `RefinedTCB` from a raw FreeRTOS TCB struct reference.
///
/// # Arguments
/// * `input` - Reference to a raw TCB_t struct.
///
/// # Returns
/// A new `RefinedTCB` instance.
pub fn from_tcb(input: &TCB_t) -> Self { pub fn from_tcb(input: &TCB_t) -> Self {
unsafe { unsafe {
let tmp = std::mem::transmute::<[i8; 10], [u8; 10]>(input.pcTaskName); let tmp = std::mem::transmute::<[i8; 10], [u8; 10]>(input.pcTaskName);
@ -345,6 +368,13 @@ impl RefinedTCB {
} }
} }
} }
/// Constructs a `RefinedTCB` from a raw FreeRTOS TCB struct (by value).
///
/// # Arguments
/// * `input` - The TCB_t struct.
///
/// # Returns
/// A new `RefinedTCB` instance.
pub fn from_tcb_owned(input: TCB_t) -> Self { pub fn from_tcb_owned(input: TCB_t) -> Self {
unsafe { unsafe {
let tmp = std::mem::transmute::<[i8; 10], [u8; 10]>(input.pcTaskName); let tmp = std::mem::transmute::<[i8; 10], [u8; 10]>(input.pcTaskName);
@ -391,10 +421,10 @@ impl Hash for FreeRTOSSystemState {
} }
} }
impl FreeRTOSSystemState { impl FreeRTOSSystemState {
// fn get_tick(&self) -> u64 { /// Prints the ready and delay lists as a formatted string.
// self.tick ///
// } /// # Returns
/// A string representation of the ready and delay lists.
pub fn print_lists(&self) -> String { pub fn print_lists(&self) -> String {
let mut ret = String::from("+"); let mut ret = String::from("+");
for j in self.ready_list_after.iter() { for j in self.ready_list_after.iter() {
@ -406,6 +436,10 @@ impl FreeRTOSSystemState {
} }
ret ret
} }
/// Computes a hash for the system state.
///
/// # Returns
/// The hash value as a u64.
pub fn get_hash(&self) -> u64 { pub fn get_hash(&self) -> u64 {
let mut h = DefaultHasher::new(); let mut h = DefaultHasher::new();
self.hash(&mut h); self.hash(&mut h);
@ -461,6 +495,17 @@ pub struct FreeRTOSTraceMetadata
} }
impl FreeRTOSTraceMetadata impl FreeRTOSTraceMetadata
{ {
/// Constructs a new `FreeRTOSTraceMetadata` from trace data.
///
/// # Arguments
/// * `trace` - Vector of system states.
/// * `intervals` - Vector of execution intervals.
/// * `mem_reads` - Vector of memory reads.
/// * `jobs` - Vector of RTOS jobs.
/// * `need_to_debug` - Whether the current trace should be dumped for debugging purposes.
///
/// # Returns
/// A new `FreeRTOSTraceMetadata` instance.
pub fn new(trace: Vec<<FreeRTOSTraceMetadata as SystemTraceData>::State>, intervals: Vec<ExecInterval>, mem_reads: Vec<Vec<(u32, u8)>>, jobs: Vec<RTOSJob>, need_to_debug: bool) -> Self { pub fn new(trace: Vec<<FreeRTOSTraceMetadata as SystemTraceData>::State>, intervals: Vec<ExecInterval>, mem_reads: Vec<Vec<(u32, u8)>>, jobs: Vec<RTOSJob>, need_to_debug: bool) -> Self {
let hashes : Vec<_> = trace let hashes : Vec<_> = trace
.iter() .iter()
@ -529,7 +574,14 @@ libafl_bolts::impl_serdeany!(RefinedTCB);
libafl_bolts::impl_serdeany!(FreeRTOSSystemState); libafl_bolts::impl_serdeany!(FreeRTOSSystemState);
libafl_bolts::impl_serdeany!(FreeRTOSSystem); libafl_bolts::impl_serdeany!(FreeRTOSSystem);
fn get_task_names(trace: &Vec<FreeRTOSSystemState>) -> HashSet<String> { /// Returns a set of all task names present in the given trace.
///
/// # Arguments
/// * `trace` - A vector of FreeRTOS system states.
///
/// # Returns
/// A set of unique task names as strings.
pub(crate) fn get_task_names(trace: &Vec<FreeRTOSSystemState>) -> HashSet<String> {
let mut ret: HashSet<_, _> = HashSet::new(); let mut ret: HashSet<_, _> = HashSet::new();
for state in trace { for state in trace {
ret.insert(state.current_task.task_name.to_string()); ret.insert(state.current_task.task_name.to_string());

View File

@ -20,12 +20,16 @@ pub mod freertos;
//============================= Trait definitions //============================= Trait definitions
/// A trait representing a target system, which includes a system state, task control block, and trace data.
pub trait TargetSystem: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Clone + SerdeAny { pub trait TargetSystem: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Clone + SerdeAny {
type State: SystemState<TCB = Self::TCB>; type State: SystemState<TCB = Self::TCB>;
/// The type of a task control block used in the system state.
type TCB: TaskControlBlock; type TCB: TaskControlBlock;
/// The type used to store trace data for the system.
type TraceData: SystemTraceData<State = Self::State>; type TraceData: SystemTraceData<State = Self::State>;
} }
/// A trait representing the system state of a target system, which includes methods to access the current task.
pub trait SystemState: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Hash + PartialEq + Clone + SerdeAny { pub trait SystemState: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Hash + PartialEq + Clone + SerdeAny {
type TCB: TaskControlBlock; type TCB: TaskControlBlock;
@ -39,14 +43,20 @@ pub trait SystemState: Serialize + Sized + for<'a> Deserialize<'a> + Default + D
pub trait SystemTraceData: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Clone + SerdeAny + HasRefCnt { pub trait SystemTraceData: Serialize + Sized + for<'a> Deserialize<'a> + Default + Debug + Clone + SerdeAny + HasRefCnt {
type State: SystemState; type State: SystemState;
/// Returns a vector of all system states in the trace.
fn states(&self) -> Vec<&Self::State>; fn states(&self) -> Vec<&Self::State>;
/// Returns hash map of system states, where the key is the hash value of the state.
fn states_map(&self) -> &HashMap<u64, Self::State>; fn states_map(&self) -> &HashMap<u64, Self::State>;
/// Returns a vector of execution intervals in the trace.
fn intervals(&self) -> &Vec<ExecInterval>; fn intervals(&self) -> &Vec<ExecInterval>;
/// Returns a vector of memory reads, where each read is represented as a tuple of (address, value).
fn mem_reads(&self) -> &Vec<Vec<(u32, u8)>>; fn mem_reads(&self) -> &Vec<Vec<(u32, u8)>>;
/// Returns a vector of RTOS jobs which were executed during the trace.
fn jobs(&self) -> &Vec<RTOSJob>; fn jobs(&self) -> &Vec<RTOSJob>;
fn trace_length(&self) -> usize; fn trace_length(&self) -> usize;
#[inline] #[inline]
/// Returns the worst job of each task by a given predicate.
fn worst_jobs_per_task_by(&self, pred: &dyn Fn(&RTOSJob,&RTOSJob) -> bool) -> HashMap<String, RTOSJob> { fn worst_jobs_per_task_by(&self, pred: &dyn Fn(&RTOSJob,&RTOSJob) -> bool) -> HashMap<String, RTOSJob> {
self.jobs().iter().fold(HashMap::new(), |mut acc, next| { self.jobs().iter().fold(HashMap::new(), |mut acc, next| {
match acc.get_mut(&next.name) { match acc.get_mut(&next.name) {
@ -63,10 +73,12 @@ pub trait SystemTraceData: Serialize + Sized + for<'a> Deserialize<'a> + Default
}) })
} }
#[inline] #[inline]
/// Gives the worst job of each task by execution time.
fn worst_jobs_per_task_by_exec_time(&self) -> HashMap<String, RTOSJob> { fn worst_jobs_per_task_by_exec_time(&self) -> HashMap<String, RTOSJob> {
self.worst_jobs_per_task_by(&|old, x| x.exec_ticks > old.exec_ticks) self.worst_jobs_per_task_by(&|old, x| x.exec_ticks > old.exec_ticks)
} }
#[inline] #[inline]
/// Gives the worst job of each task by response time.
fn worst_jobs_per_task_by_response_time(&self) -> HashMap<String, RTOSJob> { fn worst_jobs_per_task_by_response_time(&self) -> HashMap<String, RTOSJob> {
self.worst_jobs_per_task_by(&|old, x| x.response_time() > old.response_time()) self.worst_jobs_per_task_by(&|old, x| x.response_time() > old.response_time())
} }
@ -118,6 +130,7 @@ pub trait TaskControlBlock: Serialize + for<'a> Deserialize<'a> + Default + Debu
//============================= //=============================
/// A trait for looking up data in a QEMU emulation environment.
pub trait QemuLookup { pub trait QemuLookup {
fn lookup(emu: &Qemu, addr: ::std::os::raw::c_uint) -> Self; fn lookup(emu: &Qemu, addr: ::std::os::raw::c_uint) -> Self;
} }