Skip to content

Commit 592a8df

Browse files
authored
Add option to output execution traces during fuzzing (#81)
1 parent 5da3606 commit 592a8df

File tree

10 files changed

+187
-4
lines changed

10 files changed

+187
-4
lines changed

src/arch/aarch64.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,16 @@ impl TracerDisassembler for Disassembler {
10561056
Ok(())
10571057
}
10581058

1059+
fn disassemble_to_string(&mut self, bytes: &[u8]) -> Result<String> {
1060+
let mut r = U8Reader::new(bytes);
1061+
1062+
if let Ok(insn) = self.decoder.decode(&mut r) {
1063+
Ok(insn.to_string())
1064+
} else {
1065+
bail!("Could not disassemble {:?}", bytes);
1066+
}
1067+
}
1068+
10591069
fn last_was_control_flow(&self) -> bool {
10601070
if let Some(last) = self.last.as_ref() {
10611071
// NOTE: This is imprecise on ARM because PC is not restricted

src/arch/arm.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,16 @@ impl TracerDisassembler for Disassembler {
10531053
Ok(())
10541054
}
10551055

1056+
fn disassemble_to_string(&mut self, bytes: &[u8]) -> Result<String> {
1057+
let mut r = U8Reader::new(bytes);
1058+
1059+
if let Ok(insn) = self.decoder.decode(&mut r) {
1060+
Ok(insn.to_string())
1061+
} else {
1062+
bail!("Could not disassemble {:?}", bytes);
1063+
}
1064+
}
1065+
10561066
fn last_was_control_flow(&self) -> bool {
10571067
if let Some(last) = self.last.as_ref() {
10581068
// NOTE: This is imprecise on ARM because PC is not restricted

src/arch/risc_v.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,16 @@ impl TracerDisassembler for Disassembler {
459459
Ok(())
460460
}
461461

462+
fn disassemble_to_string(&mut self, bytes: &[u8]) -> Result<String> {
463+
let mut r = U8Reader::new(bytes);
464+
465+
if let Ok(insn) = self.decoder.decode(&mut r) {
466+
Ok(insn.to_string())
467+
} else {
468+
bail!("Could not disassemble {:?}", bytes);
469+
}
470+
}
471+
462472
fn last_was_control_flow(&self) -> bool {
463473
if let Some(last) = self.last.as_ref() {
464474
if matches!(last.opcode(), |Opcode::BEQ| Opcode::BNE

src/arch/x86.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,14 @@ impl TracerDisassembler for Disassembler {
985985
Ok(())
986986
}
987987

988+
fn disassemble_to_string(&mut self, bytes: &[u8]) -> Result<String> {
989+
if let Ok(insn) = self.decoder.decode_slice(bytes) {
990+
Ok(insn.to_string())
991+
} else {
992+
bail!("Could not disassemble {:?}", bytes);
993+
}
994+
}
995+
988996
fn cmp(&self) -> Vec<CmpExpr> {
989997
let mut cmp_exprs = Vec::new();
990998
if self.last_was_cmp() {

src/arch/x86_64.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,14 @@ impl TracerDisassembler for Disassembler {
964964
Ok(())
965965
}
966966

967+
fn disassemble_to_string(&mut self, bytes: &[u8]) -> Result<String> {
968+
if let Ok(insn) = self.decoder.decode_slice(bytes) {
969+
Ok(insn.to_string())
970+
} else {
971+
bail!("Could not disassemble {:?}", bytes);
972+
}
973+
}
974+
967975
fn cmp(&self) -> Vec<CmpExpr> {
968976
let mut cmp_exprs = Vec::new();
969977
if self.last_was_cmp() {

src/haps/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ impl Tsffs {
5858
self.post_timeout_event()?;
5959
}
6060

61+
self.execution_trace.0.clear();
6162
self.save_repro_bookmark_if_needed()?;
6263

6364
debug!(self.as_conf_object(), "Resuming simulation");
@@ -152,6 +153,10 @@ impl Tsffs {
152153
self.post_timeout_event()?;
153154
}
154155

156+
if self.save_all_execution_traces {
157+
self.save_execution_trace()?;
158+
}
159+
155160
debug!(self.as_conf_object(), "Resuming simulation");
156161

157162
run_alone(|| {
@@ -204,6 +209,7 @@ impl Tsffs {
204209
self.post_timeout_event()?;
205210
}
206211

212+
self.execution_trace.0.clear();
207213
self.save_repro_bookmark_if_needed()?;
208214

209215
debug!(self.as_conf_object(), "Resuming simulation");
@@ -233,6 +239,7 @@ impl Tsffs {
233239
self.post_timeout_event()?;
234240
}
235241

242+
self.execution_trace.0.clear();
236243
self.save_repro_bookmark_if_needed()?;
237244

238245
debug!(self.as_conf_object(), "Resuming simulation");
@@ -323,6 +330,10 @@ impl Tsffs {
323330
self.post_timeout_event()?;
324331
}
325332

333+
if self.save_all_execution_traces {
334+
self.save_execution_trace()?;
335+
}
336+
326337
debug!(self.as_conf_object(), "Resuming simulation");
327338

328339
run_alone(|| {
@@ -416,6 +427,10 @@ impl Tsffs {
416427
self.post_timeout_event()?;
417428
}
418429

430+
if self.save_all_execution_traces || self.save_solution_execution_traces {
431+
self.save_execution_trace()?;
432+
}
433+
419434
debug!(self.as_conf_object(), "Resuming simulation");
420435

421436
run_alone(|| {

src/lib.rs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use libafl_targets::AFLppCmpLogMap;
4242
use magic::MagicNumber;
4343
use num_traits::FromPrimitive as _;
4444
use serde::{Deserialize, Serialize};
45+
use serde_json::to_writer;
4546
use simics::{
4647
break_simulation, class, debug, error, free_attribute, get_class, get_interface,
4748
get_processor_number, info, lookup_file, object_clock, run_command, run_python, simics_init,
@@ -66,15 +67,19 @@ use std::{
6667
alloc::{alloc_zeroed, Layout},
6768
cell::OnceCell,
6869
collections::{hash_map::Entry, BTreeSet, HashMap, HashSet},
69-
fs::File,
70+
fs::{create_dir_all, File},
71+
hash::{DefaultHasher, Hash, Hasher},
7072
path::PathBuf,
7173
ptr::null_mut,
7274
str::FromStr,
7375
sync::mpsc::{Receiver, Sender},
7476
thread::JoinHandle,
7577
time::SystemTime,
7678
};
77-
use tracer::tsffs::{on_instruction_after, on_instruction_before};
79+
use tracer::{
80+
tsffs::{on_instruction_after, on_instruction_before},
81+
ExecutionTrace,
82+
};
7883
use typed_builder::TypedBuilder;
7984
use versions::{Requirement, Versioning};
8085

@@ -375,6 +380,25 @@ pub(crate) struct Tsffs {
375380
#[class(attribute(optional, default = true))]
376381
/// Whether to quit on iteration limit
377382
pub quit_on_iteration_limit: bool,
383+
#[class(attribute(optional, default = false))]
384+
/// Whether to save execution traces of test cases which result in a solution
385+
pub save_solution_execution_traces: bool,
386+
#[class(attribute(optional, default = false))]
387+
/// Whether to save execution traces of test cases which result in an interesting input
388+
pub save_interesting_execution_traces: bool,
389+
#[class(attribute(optional, default = false))]
390+
/// Whether to save all execution traces. This will consume a very large amount of resources
391+
/// and should only be used for debugging and testing purposes.
392+
pub save_all_execution_traces: bool,
393+
#[class(attribute(optional, default = lookup_file("%simics%")?.join("execution-traces")))]
394+
#[attr_value(fallible)]
395+
/// The directory to save execution traces to, if any are set to be saved. This
396+
/// directory may be a SIMICS relative path prefixed with "%simics%". If not
397+
/// provided, "%simics%/execution-traces" will be used by default.
398+
pub execution_trace_directory: PathBuf,
399+
#[class(attribute(optional, default = false))]
400+
/// Whether execution traces should include just PC (vs instruction text and bytes)
401+
pub execution_trace_pc_only: bool,
378402

379403
#[attr_value(skip)]
380404
/// Handle for the core simulation stopped hap
@@ -440,8 +464,11 @@ pub(crate) struct Tsffs {
440464
edges_seen: HashSet<u64>,
441465
#[attr_value(skip)]
442466
/// A map of the new edges to their AFL indices seen since the last time the fuzzer
443-
/// provided an update
467+
/// provided an update. This is not cleared every execution.
444468
edges_seen_since_last: HashMap<u64, u64>,
469+
#[attr_value(skip)]
470+
/// The set of PCs comprising the current execution trace. This is cleared every execution.
471+
execution_trace: ExecutionTrace,
445472

446473
#[attr_value(skip)]
447474
/// The name of the fuzz snapshot, if saved
@@ -865,6 +892,29 @@ impl Tsffs {
865892
}
866893
Ok(())
867894
}
895+
896+
/// Save the current execution trace to a file
897+
pub fn save_execution_trace(&mut self) -> Result<()> {
898+
let mut hasher = DefaultHasher::new();
899+
self.execution_trace.hash(&mut hasher);
900+
let hash = hasher.finish();
901+
902+
if !self.execution_trace_directory.is_dir() {
903+
create_dir_all(&self.execution_trace_directory)?;
904+
}
905+
906+
let trace_path = self
907+
.execution_trace_directory
908+
.join(format!("{:x}.json", hash));
909+
910+
if !trace_path.exists() {
911+
let trace_file = File::create(trace_path)?;
912+
913+
to_writer(trace_file, &self.execution_trace)?;
914+
}
915+
916+
Ok(())
917+
}
868918
}
869919

870920
#[simics_init(name = "tsffs", class = "tsffs")]

src/log/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ impl Tsffs {
7474

7575
self.edges_seen_since_last.clear();
7676
}
77+
78+
if self.save_interesting_execution_traces {
79+
self.save_execution_trace()?;
80+
}
7781
}
7882
}
7983

src/tracer/mod.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,45 @@ use ffi::ffi;
66
use libafl::prelude::CmpValues;
77
use libafl_bolts::{AsMutSlice, AsSlice};
88
use libafl_targets::{AFLppCmpLogOperands, AFL_CMP_TYPE_INS, CMPLOG_MAP_H};
9+
use serde::{Deserialize, Serialize};
910
use simics::{
1011
api::{
1112
get_processor_number, sys::instruction_handle_t, AsConfObject, AttrValue, AttrValueType,
1213
ConfObject,
1314
},
1415
trace,
1516
};
16-
use std::{collections::HashMap, ffi::c_void, fmt::Display, num::Wrapping, str::FromStr};
17+
use std::{
18+
collections::HashMap, ffi::c_void, fmt::Display, hash::Hash, num::Wrapping,
19+
slice::from_raw_parts, str::FromStr,
20+
};
1721
use typed_builder::TypedBuilder;
1822

1923
use crate::{arch::ArchitectureOperations, Tsffs};
2024

25+
#[derive(Deserialize, Serialize, Debug, Default)]
26+
pub(crate) struct ExecutionTrace(pub HashMap<i32, Vec<ExecutionTraceEntry>>);
27+
28+
impl Hash for ExecutionTrace {
29+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
30+
for (k, v) in self.0.iter() {
31+
for entry in v.iter() {
32+
k.hash(state);
33+
entry.hash(state);
34+
}
35+
}
36+
}
37+
}
38+
39+
#[derive(TypedBuilder, Deserialize, Serialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
40+
pub(crate) struct ExecutionTraceEntry {
41+
pc: u64,
42+
#[builder(default, setter(into, strip_option))]
43+
insn: Option<String>,
44+
#[builder(default, setter(into, strip_option))]
45+
insn_bytes: Option<Vec<u8>>,
46+
}
47+
2148
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
2249
pub(crate) enum CmpExprShift {
2350
Lsl,
@@ -346,6 +373,46 @@ impl Tsffs {
346373
}
347374
}
348375

376+
if self.coverage_enabled
377+
&& (self.save_all_execution_traces
378+
|| self.save_interesting_execution_traces
379+
|| self.save_solution_execution_traces)
380+
{
381+
if let Some(arch) = self.processors.get_mut(&processor_number) {
382+
self.execution_trace
383+
.0
384+
.entry(processor_number)
385+
.or_default()
386+
.push(if self.execution_trace_pc_only {
387+
ExecutionTraceEntry::builder()
388+
.pc(arch.processor_info_v2().get_program_counter()?)
389+
.build()
390+
} else {
391+
let instruction_bytes =
392+
arch.cpu_instruction_query().get_instruction_bytes(handle)?;
393+
let instruction_bytes = unsafe {
394+
from_raw_parts(instruction_bytes.data, instruction_bytes.size)
395+
};
396+
397+
if let Ok(disassembly_string) =
398+
arch.disassembler().disassemble_to_string(instruction_bytes)
399+
{
400+
ExecutionTraceEntry::builder()
401+
.pc(arch.processor_info_v2().get_program_counter()?)
402+
.insn(disassembly_string)
403+
.insn_bytes(instruction_bytes.to_vec())
404+
.build()
405+
} else {
406+
ExecutionTraceEntry::builder()
407+
.pc(arch.processor_info_v2().get_program_counter()?)
408+
.insn("(unknown)".to_string())
409+
.insn_bytes(instruction_bytes.to_vec())
410+
.build()
411+
}
412+
});
413+
}
414+
}
415+
349416
Ok(())
350417
}
351418
}

src/traits/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use anyhow::Result;
88
/// and compare tracing
99
pub trait TracerDisassembler {
1010
fn disassemble(&mut self, bytes: &[u8]) -> Result<()>;
11+
fn disassemble_to_string(&mut self, bytes: &[u8]) -> Result<String>;
1112
fn last_was_control_flow(&self) -> bool;
1213
fn last_was_call(&self) -> bool;
1314
fn last_was_ret(&self) -> bool;

0 commit comments

Comments
 (0)