diff --git a/.gitignore b/.gitignore index 56683f3..36eaa25 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,10 @@ Cargo.lock build/ fusesoc* output + +chipyard +build.err +build.log + +# Mac files +.DS_Store diff --git a/fuzzers/chipyard-vcs-fuzzer/Cargo.toml b/fuzzers/chipyard-vcs-fuzzer/Cargo.toml new file mode 100644 index 0000000..2febae8 --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "chipyard_vcs_fuzzer" +version = "0.0.1" +edition = "2021" +authors = ["Mohamadreza Rostami "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = false + +[profile.dev] +opt-level = 0 +strip = "debuginfo" +lto = false +debug = true +panic = "unwind" + +[dependencies] +libpresifuzz_riscv = { path = "../../libpresifuzz_riscv"} +libpresifuzz_ec = { path = "../../libpresifuzz_ec"} +libpresifuzz_mutators = {path="../../libpresifuzz_mutators"} +libpresifuzz_observers = { path = "../../libpresifuzz_observers"} +libpresifuzz_feedbacks = { path = "../../libpresifuzz_feedbacks"} +libpresifuzz_stages = { path = "../../libpresifuzz_stages"} +libafl = { version = "0.11.2"} +libafl_bolts = { version = "0.11.2"} +yaml-rust = "0.4.5" +rand = "0.8.5" +serde_yaml = "0.9.27" +tempdir = "0.3.7" +serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib +clap = { version = "3.2", features = ["default"] } +fs_extra = "1.2.0" +wait-timeout = "0.1.5" +color-print = "0.3.6" + +[features] +debug = [] \ No newline at end of file diff --git a/fuzzers/chipyard-vcs-fuzzer/README.md b/fuzzers/chipyard-vcs-fuzzer/README.md new file mode 100644 index 0000000..85150f1 --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/README.md @@ -0,0 +1,80 @@ +# Fuzzer Overview + +This documentation outlines the process of setting up and using an RTL fuzzer for the Chipyard framework using libAFL and PreSiFuzz. +This setup demonstrates feedback-guided fuzzing using hardware code coverage reported by a commercial simulator. + +# Prerequisites + +The following tools need to be installed on your own. + +* Latest version of [Spike](https://github.com/riscv-software-src/riscv-isa-sim), the RISC-V ISA simulator, installed. +Follow the installation instructions from the Spike GitHub repository. + +* Synopsys VCS (Verilog Compiler Simulator) installed and properly initialized. VCS is a widely used Verilog simulator. + Ensure it is configured and ready for simulation tasks. + +* [RISC-V GNU Compiler Toolchain] (https://github.com/riscv-collab/riscv-gnu-toolchain) + +## Building + +The build.rs script performs the following tasks: + +* Initially, it downloads the `Chipyard` mainstream, initializes submodules, and applies the `chipyard_cov.patch` patch to support coverage collection. +* Next, build the default `Rocketchip` for VCS based simulation. +* Finally, it compiles the `./src/testcase.S` file and builds the simv self-contained simulator. Generated files are then copied into the `build` folder. + +To complete the steps above, simply run: +```sh +$ cargo build +``` +## Troubleshooting + + +## Running the fuzzer + +When starting, the fuzzer creates a work directory where it saves intermediates files such as mutants, and symbolic links to the `simv` and its dependencies in `./build`. +Work directory are saved into `TMPDIR` with a unique directory per fuzzer instance. Naming follows `presifuzz_`. +Synchronization information are saved into the `sync` directory, it includes `testcase` and associated `coverage map`. + +``` +$ cp ../../target/debug/chipyard_vcs_fuzzer . +$ mkdir sync +``` + +To run a single fuzzer instance: +``` +$ AFL_LAUNCHER_CLIENT=1 ./chipyard_vcs_fuzzer +``` + +To run multiple fuzzer instances: +``` +for i in {1..10}; do AFL_LAUNCHER_CLIENT=$i ./chipyard_vcs_fuzzer & done +``` + +# Customizing + +The fuzzer is bootstraped using the seed files into the `seeds` folder. Feel free to customize the content of this file with any interesting seed. +When starting the fuzzer loads the initial inputs (i.e., the seeds), and only keep interesting ones in the corpus (i.e., coverage novelty). +Coverage novelty consider any changes for all supported code coverage metrics on vcs, i.e., branch, conditional, line, toggle, and FSM. +Then, starts the fuzzer loop that iteratively calls the different stages. +StdMutationalStage is responsible for generating new mutant by applying mutation to the existing testcase in the corpus. +The mutations work at the ISA level by first deserializing the binary testcase into stream of instruction, then different mutations might be applied (e.g., adding instruction, removing instruction, changing opcode, ..). +The mutation can easily be customized by changing `../../libpresifuzz_mutators/src/riscv_isa.rs`. +The generated testcase is then inserted into a template ELF file by simplify injecting the code after the `payload` label. +This template contains epilogue and prologue code. +The current version is very simple. We first init registers to some known values, and we change the `mtvec` to points to our own trap handler. +The trap handler is there to stop earlier the testcase execution if we trop too often. Otherwise, it always try to return to the instruction after the failing one. +This version is a naive implementation, better performance could be achieved with some changes on the testharness (e.g., early simulation stop, irq support). + + +# Ploting data + +The fuzzer saves statistics into the `sync`directory. +It is possible to plot coverage over time using the `plot.py`: + +```python +python3 ./plot.py -m branch -d ./sync +``` + +The `-m` option is there to provide the coverage metric that is either tgl, cond, branch, line, fsm. +The `-d` points to the directory where stats are saved. \ No newline at end of file diff --git a/fuzzers/chipyard-vcs-fuzzer/build.rs b/fuzzers/chipyard-vcs-fuzzer/build.rs new file mode 100644 index 0000000..9aeba1b --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/build.rs @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 + +use std::path::{Path}; +use std::env; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use std::fs; +use std::fs::File; + +fn main() { + println!("cargo:warning=MESSAGE"); + + if Path::new("./build").is_dir() { + assert!(fs::remove_dir_all("./build").is_ok()); + } + assert!(fs::create_dir("./build").is_ok()); + + let build_exec_log = File::create("build.log").expect("Failed to build.log"); + let build_exec_err = File::create("build.err").expect("Failed to build.err"); + + let rtl_dir = PathBuf::from("chipyard"); + if !rtl_dir.exists() { + println!("INFO: Builing chipyard."); + + assert!(Command::new("bash") + .arg("build.sh") + .stdin(Stdio::null()) + .stdout(build_exec_log) + .stderr(build_exec_err) + .status() + .unwrap() + .success()) + } + + println!("INFO: Creating build dir."); + + assert!(Command::new("bash") + .arg("-c") + .arg("cp -r ./chipyard/sims/vcs/* ./build") + .status() + .unwrap() + .success()); + + let key = "VERDI_HOME"; + let mut verdi_lib = match env::var(key) { + Ok(val) => val, + Err(_e) => "".to_string(), + }; + + if verdi_lib.is_empty() { + println!("The env variable 'VERDI_HOME' is not set"); + return; + } + + verdi_lib.push_str("/share/NPI/lib/linux64"); + + println!("cargo:rustc-link-search=native=./build"); + println!("cargo:rustc-link-search=native={}", verdi_lib); + +} + diff --git a/fuzzers/chipyard-vcs-fuzzer/build.sh b/fuzzers/chipyard-vcs-fuzzer/build.sh new file mode 100644 index 0000000..201493e --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/build.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +conda install -n base conda-libmamba-solver +conda config --set solver libmamba +conda install -n base conda-lock==1.4.0 +conda activate base +if [ ! -d "chipyard" ] ; then + git clone https://github.com/ucb-bar/chipyard.git +fi +cd chipyard +git checkout 1.11.0 +./build-setup.sh riscv-tools +source ./env.sh +cd sims/vcs/ +git apply ../chipyard_cov.patch +make -j 12 + +echo "Build testcase" +RISCV_BENCHMARKS="./toolchains/riscv-tools/riscv-tests/benchmarks" +riscv64-unknown-linux-gnu-gcc -I $RISCV_BENCHMARKS/../env -I $RISCV_BENCHMARKS/common -I./testcase -DPREALLOCATE=1 -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf -fno-tree-loop-distribute-patterns -o ../build/iram.elf $RISCV_BENCHMARKS/common/crt.S ../src/testcase.S -static -nostdlib -nostartfiles -lm -lgcc -T $RISCV_BENCHMARKS/common/test.ld \ No newline at end of file diff --git a/fuzzers/chipyard-vcs-fuzzer/chipyard_cov.patch b/fuzzers/chipyard-vcs-fuzzer/chipyard_cov.patch new file mode 100644 index 0000000..304885a --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/chipyard_cov.patch @@ -0,0 +1,13 @@ +diff --git a/sims/vcs/Makefile b/sims/vcs/Makefile +index 8517fc1d..f942a17b 100644 +--- a/sims/vcs/Makefile ++++ b/sims/vcs/Makefile +@@ -69,7 +69,7 @@ model_dir_debug = $(build_dir)/$(long_name).debug + ######################################################################################### + $(sim): $(sim_common_files) $(dramsim_lib) $(EXTRA_SIM_REQS) + rm -rf $(model_dir) +- $(VCS) $(VCS_OPTS) $(EXTRA_SIM_SOURCES) -o $@ -Mdir=$(model_dir) ++ $(VCS) $(VCS_OPTS) $(EXTRA_SIM_SOURCES) -o $@ -Mdir=$(model_dir) -lca -cm line+cond+fsm+tgl+path+branch+assert -cm_dir Coverage.vdb + + $(sim_debug): $(sim_common_files) $(dramsim_lib) $(EXTRA_SIM_REQS) + rm -rf $(model_dir_debug) diff --git a/fuzzers/chipyard-vcs-fuzzer/config.yml b/fuzzers/chipyard-vcs-fuzzer/config.yml new file mode 100644 index 0000000..28572ef --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/config.yml @@ -0,0 +1,12 @@ +fuzzer: + max_testcase_size: 128 +simv: + vcs_args: + plus_args: + cov_enable: true + coverage_metrics: "line+fsm+cond+tgl+branch" + coverage_directory: "Coverage.vdb" + reset_coverage_before_use: false + system_timeout_s: 180 + vcs_timeout: "10us" + multi_seed: 0 diff --git a/fuzzers/chipyard-vcs-fuzzer/plot.py b/fuzzers/chipyard-vcs-fuzzer/plot.py new file mode 100644 index 0000000..a59d81f --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/plot.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# SPDX-FileCopyrightText: 2022 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import re +import time +import copy +from datetime import datetime +import matplotlib.pyplot as plt +import numpy as np +from collections import OrderedDict +import pandas +import seaborn as sns +import json +import argparse + +print(sns.__version__) + + +def is_valid_directory(arg): + if not os.path.isdir(arg): + raise argparse.ArgumentTypeError(f"'{arg}' is not a valid directory") + return arg + +def parse_args(): + parser = argparse.ArgumentParser(description="Parse command line arguments") + parser.add_argument("-d", "--directory", type=is_valid_directory, required=True, help="Input directory") + parser.add_argument("-m", "--metric", choices=["line", "branch", "cond", "tgl", "fsm"], required=True, help="Metric to be provided") + return parser.parse_args() + +args = parse_args() +print("Input directory:", args.directory) +print("Metric:", args.metric) + +stats_directories = [args.directory] +metric = args.metric + +# first, let's load all the data +data = [] +for stats_directory in stats_directories: + + i = 0 + delta = 0 + for stats_filename in os.scandir(stats_directory): + + f = stats_filename + + if "stats" not in f.name: + continue + + + print(f"Analyzing file {f.name}: ") + + if os.path.isfile(f): + f = open(f) + + last_runtime = 0 + last_coverage = 0 + + lines = f.readlines() + for l in lines: + + l = json.loads(l) + + if "coverage_verdi_"+metric in l["UpdateUserStats"]["name"]: + a = l["UpdateUserStats"]["value"]["value"]["Ratio"][0] + b = l["UpdateUserStats"]["value"]["value"]["Ratio"][1] + last_coverage = a/b + + if "time_verdi_"+metric in l["UpdateUserStats"]["name"]: + last_runtime = l["UpdateUserStats"]["value"]["value"]["Number"] + data += [{"runtime": last_runtime, "score": last_coverage}] + i += 1 + +# let's order the timepoint +dataset = [] +runtime = [] +coverage = [] +max_cov = 0.0 + +time_sorted = sorted(data, key=lambda x: x['runtime']) + +delta = time_sorted[0]["runtime"] + +for item in time_sorted: + + runtime += [item["runtime"]] + + if max_cov < item["score"]: + max_cov = item["score"] + + coverage += [max_cov] + +dataset = {"Execution Time": runtime, "Score": coverage} +print(dataset) +ax = sns.lineplot(x=dataset["Execution Time"], y=dataset["Score"], legend="full") + +plt.title(f"{metric} coverage score over time.") +plt.legend(loc='upper center') +plt.savefig(f'{metric}.png') diff --git a/fuzzers/chipyard-vcs-fuzzer/seeds/seed.bin b/fuzzers/chipyard-vcs-fuzzer/seeds/seed.bin new file mode 100644 index 0000000..cb94550 --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/seeds/seed.bin @@ -0,0 +1 @@ +ø•lÌÊ(}©®ã¥# ‹69ÅíÉ÷I û…&W[ÀÊNÔr0 $绿ÿ˜9eÏ›xÊßA·dqJ¿©yðÅw \ No newline at end of file diff --git a/fuzzers/chipyard-vcs-fuzzer/src/differential.rs b/fuzzers/chipyard-vcs-fuzzer/src/differential.rs new file mode 100644 index 0000000..011c3ac --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/src/differential.rs @@ -0,0 +1,290 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +#![cfg_attr( + test, + deny( + dead_code, + unreachable_code + ) +)] +#![allow(dead_code, unreachable_code)] +//! Executor for differential fuzzing. +//! It wraps two executors that will be run after each other with the same input. +//! In comparison to the [`crate::executors::CombinedExecutor`] it also runs the secondary executor in `run_target`. +//! +use std::{cell::UnsafeCell, fmt::Debug}; + +use libafl_bolts::{ownedref::OwnedMutPtr, tuples::MatchName}; +use serde::{Deserialize, Serialize}; + +use libafl::{ + executors::{Executor, ExitKind, HasObservers}, + inputs::UsesInput, + observers::{DifferentialObserversTuple, ObserversTuple, UsesObservers}, + state::UsesState, + Error, +}; +#[cfg(feature = "debug")] +use color_print::cprintln; + +/// A [`DiffExecutor`] wraps a primary executor, forwarding its methods, and a secondary one +#[derive(Debug)] +pub struct DiffExecutor { + primary: A, + secondary: B, + observers: UnsafeCell>, +} + +impl DiffExecutor { + /// Create a new `DiffExecutor`, wrapping the given `executor`s. + pub fn new(primary: A, secondary: B, observers: DOT) -> Self + where + A: UsesState + HasObservers, + B: UsesState + HasObservers, + DOT: DifferentialObserversTuple, + OTA: ObserversTuple, + OTB: ObserversTuple, + { + Self { + primary, + secondary, + observers: UnsafeCell::new(ProxyObserversTuple { + primary: OwnedMutPtr::Ptr(core::ptr::null_mut()), + secondary: OwnedMutPtr::Ptr(core::ptr::null_mut()), + differential: observers, + }), + } + } + + /// Retrieve the primary `Executor` that is wrapped by this `DiffExecutor`. + pub fn primary(&mut self) -> &mut A { + &mut self.primary + } + + /// Retrieve the secondary `Executor` that is wrapped by this `DiffExecutor`. + pub fn secondary(&mut self) -> &mut B { + &mut self.secondary + } +} + +impl Executor for DiffExecutor +where + A: Executor + HasObservers, + B: Executor + HasObservers, + EM: UsesState, + DOT: DifferentialObserversTuple, + Z: UsesState, +{ + fn run_target( + &mut self, + fuzzer: &mut Z, + state: &mut Self::State, + mgr: &mut EM, + input: &Self::Input, + ) -> Result { + self.observers(); // update in advance + let observers = self.observers.get_mut(); + observers + .differential + .pre_observe_first_all(observers.primary.as_mut())?; + observers.primary.as_mut().pre_exec_all(state, input)?; + let ret1 = self.primary.run_target(fuzzer, state, mgr, input)?; + + if ret1 != ExitKind::Ok && ret1 != ExitKind::Timeout { + #[cfg(feature = "debug")] + cprintln!("[WARNING] First executor did not return success {:?}, skipping testcase!", ret1); + + return Ok(ExitKind::Ok); + } + + self.primary.post_run_reset(); + observers + .primary + .as_mut() + .post_exec_all(state, input, &ret1)?; + observers + .differential + .post_observe_first_all(observers.primary.as_mut())?; + observers + .differential + .pre_observe_second_all(observers.secondary.as_mut())?; + observers.secondary.as_mut().pre_exec_all(state, input)?; + + let ret2 = self.secondary.run_target(fuzzer, state, mgr, input)?; + + self.secondary.post_run_reset(); + observers + .secondary + .as_mut() + .post_exec_all(state, input, &ret2)?; + observers + .differential + .post_observe_second_all(observers.secondary.as_mut())?; + + if ret1 == ret2 { + Ok(ret1) + } else { + // We found a diff in the exit codes! + Ok(ExitKind::Diff { + primary: ret1.into(), + secondary: ret2.into(), + }) + } + } +} + +/// Proxy the observers of the inner executors +#[derive(Serialize, Deserialize, Debug)] +#[serde( + bound = "A: serde::Serialize + serde::de::DeserializeOwned, B: serde::Serialize + serde::de::DeserializeOwned, DOT: serde::Serialize + serde::de::DeserializeOwned" +)] +pub struct ProxyObserversTuple { + primary: OwnedMutPtr, + secondary: OwnedMutPtr, + differential: DOT, +} + +impl ObserversTuple for ProxyObserversTuple +where + A: ObserversTuple, + B: ObserversTuple, + DOT: DifferentialObserversTuple, + S: UsesInput, +{ + fn pre_exec_all(&mut self, state: &mut S, input: &S::Input) -> Result<(), Error> { + self.differential.pre_exec_all(state, input) + } + + fn post_exec_all( + &mut self, + state: &mut S, + input: &S::Input, + exit_kind: &ExitKind, + ) -> Result<(), Error> { + self.differential.post_exec_all(state, input, exit_kind) + } + + fn pre_exec_child_all(&mut self, state: &mut S, input: &S::Input) -> Result<(), Error> { + self.differential.pre_exec_child_all(state, input) + } + + fn post_exec_child_all( + &mut self, + state: &mut S, + input: &S::Input, + exit_kind: &ExitKind, + ) -> Result<(), Error> { + self.differential + .post_exec_child_all(state, input, exit_kind) + } + + /// Returns true if a `stdout` observer was added to the list + #[inline] + fn observes_stdout(&self) -> bool { + self.primary.as_ref().observes_stdout() || self.secondary.as_ref().observes_stdout() + } + /// Returns true if a `stderr` observer was added to the list + #[inline] + fn observes_stderr(&self) -> bool { + self.primary.as_ref().observes_stderr() || self.secondary.as_ref().observes_stderr() + } + + /// Runs `observe_stdout` for all stdout observers in the list + fn observe_stdout(&mut self, stdout: &[u8]) { + self.primary.as_mut().observe_stderr(stdout); + self.secondary.as_mut().observe_stderr(stdout); + } + + /// Runs `observe_stderr` for all stderr observers in the list + fn observe_stderr(&mut self, stderr: &[u8]) { + self.primary.as_mut().observe_stderr(stderr); + self.secondary.as_mut().observe_stderr(stderr); + } +} + +impl MatchName for ProxyObserversTuple +where + A: MatchName, + B: MatchName, + DOT: MatchName, +{ + fn match_name(&self, name: &str) -> Option<&T> { + if let Some(t) = self.primary.as_ref().match_name::(name) { + Some(t) + } else if let Some(t) = self.secondary.as_ref().match_name::(name) { + Some(t) + } else { + self.differential.match_name::(name) + } + } + fn match_name_mut(&mut self, name: &str) -> Option<&mut T> { + if let Some(t) = self.primary.as_mut().match_name_mut::(name) { + Some(t) + } else if let Some(t) = self.secondary.as_mut().match_name_mut::(name) { + Some(t) + } else { + self.differential.match_name_mut::(name) + } + } +} + +impl ProxyObserversTuple { + fn set(&mut self, primary: &A, secondary: &B) { + self.primary = OwnedMutPtr::Ptr(primary as *const A as *mut A); + self.secondary = OwnedMutPtr::Ptr(secondary as *const B as *mut B); + } +} + +impl UsesObservers for DiffExecutor +where + A: HasObservers, + B: HasObservers, + OTA: ObserversTuple, + OTB: ObserversTuple, + DOT: DifferentialObserversTuple, +{ + type Observers = ProxyObserversTuple; +} + +impl UsesState for DiffExecutor +where + A: UsesState, + B: UsesState, +{ + type State = A::State; +} + +impl HasObservers for DiffExecutor +where + A: HasObservers, + B: HasObservers, + OTA: ObserversTuple, + OTB: ObserversTuple, + DOT: DifferentialObserversTuple, +{ + #[inline] + fn observers(&self) -> &ProxyObserversTuple { + unsafe { + self.observers + .get() + .as_mut() + .unwrap() + .set(self.primary.observers(), self.secondary.observers()); + // .set(self.primary.observers(), self.secondary.observers()); + self.observers.get().as_ref().unwrap() + } + } + + #[inline] + fn observers_mut(&mut self) -> &mut ProxyObserversTuple { + unsafe { + self.observers + .get() + .as_mut() + .unwrap() + .set(self.primary.observers(), self.secondary.observers()); + self.observers.get().as_mut().unwrap() + } + } +} diff --git a/fuzzers/chipyard-vcs-fuzzer/src/differential_feedback.rs b/fuzzers/chipyard-vcs-fuzzer/src/differential_feedback.rs new file mode 100644 index 0000000..93e8c03 --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/src/differential_feedback.rs @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +#![cfg_attr( + test, + deny( + dead_code, + unreachable_code + ) +)] +#![allow(dead_code, unreachable_code, unused_variables, unused_mut)] + +use core::fmt::Debug; +use libafl_bolts::Named; +use libafl::{ + corpus::Testcase, + events::EventFirer, + executors::ExitKind, + feedbacks::Feedback, + inputs::{UsesInput}, + observers::ObserversTuple, + state::State, + Error, +}; +use serde::{Deserialize, Serialize}; +use std::str; +extern crate fs_extra; + +#[cfg(feature = "debug")] +use color_print::cprintln; + +use std::fmt::Write; +use std::{ + fs, +}; + +/// Nop feedback that annotates execution time in the new testcase, if any +/// for this Feedback, the testcase is never interesting (use with an OR). +/// It decides, if the given [`TimeObserver`] value of a run is interesting. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct DifferentialFeedback { + first_name: String, + name: String, + counter: u32, +} + +impl Feedback for DifferentialFeedback +where + S: UsesInput + State +{ + #[allow(clippy::wrong_self_convention)] + #[allow(dead_code)] + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &S::Input, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + + #[cfg(feature = "debug")] + cprintln!("[WARNING] Skipping trace comparison feedback because it is not implemented for Rocketchip ..."); + return Ok(false); + + //TODO: implement differential testing for Spike + } + + #[inline] + fn append_metadata( + &mut self, + _state: &mut S, + _observers: &OT, + _testcase: &mut Testcase, + ) -> Result<(), Error> + where + OT: ObserversTuple + { + 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 DifferentialFeedback { + #[inline] + fn name(&self) -> &str { + self.name.as_str() + } +} + +impl DifferentialFeedback { + /// Creates a new [`DifferentialFeedback`], deciding if the given [`VerdiObserver`] value of a run is interesting. + #[must_use] + pub fn new_with_observer( + name: &'static str, + first_name: &'static str, + ) -> Self { + Self { + first_name: first_name.to_string(), + name: name.to_string(), + counter: 0, + } + } +} diff --git a/fuzzers/chipyard-vcs-fuzzer/src/main.rs b/fuzzers/chipyard-vcs-fuzzer/src/main.rs new file mode 100644 index 0000000..3249c88 --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/src/main.rs @@ -0,0 +1,336 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +#![cfg_attr( + test, + deny( + dead_code, + unreachable_code + ) +)] +#![allow(dead_code, unreachable_code)] + +use std::path::PathBuf; + +#[cfg(feature = "tui")] +use libafl::monitors::tui::TuiMonitor; + +// #[cfg(not(feature = "tui"))] +use libafl::{ + corpus::{InMemoryCorpus}, + events::{EventConfig}, + fuzzer::{Fuzzer, StdFuzzer}, + schedulers::QueueScheduler, + state::StdState, + inputs::BytesInput, + events::SimpleEventManager, + monitors::multi::MultiMonitor, + feedback_not, feedback_or, + Error, + stages::{ + StdMutationalStage + }, +}; +use libafl::executors::command::CommandConfigurator; +use libafl::state::HasMaxSize; + +use tempdir::TempDir; + +#[cfg(not(target_vendor = "apple"))] +use libafl_bolts::shmem::StdShMemProvider; +#[cfg(target_vendor = "apple")] +use libafl_bolts::shmem::UnixShMemProvider; +use libafl_bolts::{ + current_nanos, + rands::StdRand, + shmem::{ShMemProvider}, + tuples::tuple_list, + AsMutSlice +}; +#[cfg(feature = "std")] +use std::{ + net::{SocketAddr, ToSocketAddrs} +}; + +#[cfg(feature = "debug")] +use color_print::cprintln; + +use std::os::unix::fs; + +use std::io::Result; +use std::path::Path; +use std::env; +use std::rc::Rc; + +use libpresifuzz_feedbacks::verdi_feedback::VerdiFeedback; + +use libpresifuzz_observers::verdi_observer::VerdiShMapObserver; +use libpresifuzz_observers::verdi_observer::VerdiCoverageMetric; + +pub mod simv; +use crate::simv::SimvCommandConfigurator; + +use libpresifuzz_mutators::riscv_isa::riscv_mutations; +use libpresifuzz_mutators::scheduled::StdISAScheduledMutator; + +use libpresifuzz_ec::llmp::Launcher; +use libpresifuzz_stages::sync::SyncFromDiskStage; +use libpresifuzz_feedbacks::transferred::TransferredFeedback; + +mod differential_feedback; +mod differential; + +#[derive(Debug)] +pub struct WorkDir(Option); + +// Forward inherent methods to the tempdir crate. +impl WorkDir { + pub fn new(prefix: &str) -> Result { + TempDir::new(prefix).map(Some).map(WorkDir) + } + + pub fn path(&self) -> &Path { + self.0.as_ref().unwrap().path() + } +} + +/// Leaks the inner TempDir if we are unwinding. +impl Drop for WorkDir { + fn drop(&mut self) { + ::std::mem::forget(self.0.take()) + } +} + +pub fn symlink_files(from: Vec<&str>, to: Vec<&str>, workdir: &str) { + + let current_dir = env::current_dir().unwrap().as_os_str().to_str().unwrap().to_string(); + + for i in 0..from.len(){ + + #[cfg(feature = "debug")] + cprintln!("[INFO] symbolic link for {}/{} to {}/{}", current_dir, from[i], workdir, to[i]); + + let src = format!("{}/{}", current_dir, from[i]); + let dst = format!("{}/{}", workdir, to[i]); + + fs::symlink(&src, &dst).expect("Fail to create symlink for yaml file to workdir!"); + } +} + +/// The actual fuzzer +#[allow(clippy::too_many_lines, clippy::too_many_arguments)] +#[allow(clippy::similar_names)] +pub fn main() { + fuzz(); +} + +pub fn fuzz() { + + let yaml_fd = std::fs::File::open("config.yml").unwrap(); + let config: serde_yaml::Value = serde_yaml::from_reader(yaml_fd).unwrap(); + + let max_testcase_size: usize = config["fuzzer"]["max_testcase_size"] + .as_u64() + .unwrap_or(1024).try_into().unwrap(); + + // allocate the shared memory provider for later use + #[cfg(target_vendor = "apple")] + let mut shmem_provider = UnixShMemProvider::new().unwrap(); + #[cfg(not(target_vendor = "apple"))] + let shmem_provider = StdShMemProvider::new().unwrap(); + let mut shmem_provider_client = shmem_provider.clone(); + + let mon = MultiMonitor::new(|s| println!("{s}")); + + let sync_dir = format!("{}/sync/", std::env::current_dir().unwrap().display()); + #[cfg(feature = "debug")] + println!("sync_dir: {}", sync_dir); + + let corpus_dir = format!("{}/seeds", env::current_dir().unwrap().display()); + + // let mut mgr = SimpleEventManager::new(mon); + + let mut run_client = |_state: Option<_>, mut mgr, _core_id| { + + // get a unique temp-dir name + let tmp_dir = WorkDir::new("presifuzz_").expect("Unable to create temporary directory"); + let workdir = tmp_dir.path().as_os_str().to_str().unwrap().to_owned(); + + let workdir: &str = workdir.as_str(); + + symlink_files(vec!["config.yml"], vec!["config.yml"], workdir); + + let simv = SimvCommandConfigurator::new_from_config_file("config.yml", workdir, &mut [], "", 1); + + std::env::set_current_dir(&workdir).expect("Unable to change into {dir}"); + + // allocate a shared memory for coverage map + + // create verdi observer and feedback + // monitor Toogle coverage + // apply filter if needed + // 7297130 coverable tgl signals -> RC + // map encoding is 1bit per signal + // 7297130/8 = 912141 + const TGL_MAP_SIZE: usize = 912141+8; + let mut shmem = shmem_provider_client.new_shmem(TGL_MAP_SIZE).unwrap(); + let shmem_buf = shmem.as_mut_slice(); + let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + let (verdi_feedback_tgl, verdi_observer_tgl) = { + let verdi_observer = unsafe { + VerdiShMapObserver::<{ TGL_MAP_SIZE / 4 }>::from_mut_ptr( + "verdi_tgl", + workdir, + shmem_ptr, + &VerdiCoverageMetric::Toggle, + &"TestDriver.testHarness.chiptop0".to_string() + ) + }; + + let feedback = VerdiFeedback::<{TGL_MAP_SIZE/4}>::new_with_observer("verdi_tgl", TGL_MAP_SIZE, workdir); + + (feedback, verdi_observer) + }; + + // FSM seems buggy, disable + //const FSM_MAP_SIZE: usize = (??.0/8.0).ceil() as usize; + // let mut shmem = shmem_provider_client.new_shmem(MAP_SIZE).unwrap(); + // let shmem_buf = shmem.as_mut_slice(); + // let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + // let (verdi_feedback_fsm, verdi_observer_fsm) = { + // let verdi_observer = unsafe { + // VerdiShMapObserver::<{ MAP_SIZE / 4 }>::from_mut_ptr( + // "verdi_fsm", + // workdir, + // shmem_ptr, + // &VerdiCoverageMetric::FSM, + // &"TestDriver.testHarness.chiptop0".to_string() + // ) + // }; + + // let feedback = VerdiFeedback::<{MAP_SIZE/4}>::new_with_observer("verdi_fsm", MAP_SIZE, workdir); + + // (feedback, verdi_observer) + // }; + + // 79757 cond coverable + // 79757/8=9969 + const COND_MAP_SIZE: usize = 9969+8; + let mut shmem = shmem_provider_client.new_shmem(COND_MAP_SIZE).unwrap(); + let shmem_buf = shmem.as_mut_slice(); + let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + let (verdi_feedback_condition, verdi_observer_condition) = { + let verdi_observer = unsafe { + VerdiShMapObserver::<{ COND_MAP_SIZE / 4 }>::from_mut_ptr( + "verdi_condition", + workdir, + shmem_ptr, + &VerdiCoverageMetric::Condition, + &"TestDriver.testHarness.chiptop0".to_string() + ) + }; + + let feedback = VerdiFeedback::<{COND_MAP_SIZE/4}>::new_with_observer("verdi_condition", COND_MAP_SIZE, workdir); + (feedback, verdi_observer) + }; + + // 47294 coverable lines + // 47294/8=5911 + const LINE_MAP_SIZE: usize = 5911+8; + let mut shmem = shmem_provider_client.new_shmem(LINE_MAP_SIZE).unwrap(); + let shmem_buf = shmem.as_mut_slice(); + let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + let (verdi_feedback_line, verdi_observer_line) = { + let verdi_observer = unsafe { + VerdiShMapObserver::<{ LINE_MAP_SIZE / 4 }>::from_mut_ptr( + "verdi_line", + workdir, + shmem_ptr, + &VerdiCoverageMetric::Line, + &"TestDriver.testHarness.chiptop0".to_string() + ) + }; + + let feedback = VerdiFeedback::<{LINE_MAP_SIZE/4}>::new_with_observer("verdi_line", LINE_MAP_SIZE, workdir); + (feedback, verdi_observer) + }; + + // 39357 branches coverable + // 39357 / 8 = 4919 + // const BRANCH_MAP_SIZE: usize = 4919+8; + let mut shmem = shmem_provider_client.new_shmem(BRANCH_MAP_SIZE).unwrap(); + let shmem_buf = shmem.as_mut_slice(); + let shmem_ptr = shmem_buf.as_mut_ptr() as *mut u32; + let (verdi_feedback_branch, verdi_observer_branch) = { + let verdi_observer = unsafe { + VerdiShMapObserver::<{ BRANCH_MAP_SIZE / 4 }>::from_mut_ptr( + "verdi_branch", + workdir, + shmem_ptr, + &VerdiCoverageMetric::Branch, + &"TestDriver.testHarness.chiptop0".to_string() + ) + }; + let feedback = VerdiFeedback::<{BRANCH_MAP_SIZE/4}>::new_with_observer("verdi_branch", BRANCH_MAP_SIZE, workdir); + (feedback, verdi_observer) + }; + + let mut feedback = feedback_or!(verdi_feedback_line, verdi_feedback_tgl, verdi_feedback_branch, verdi_feedback_condition); + //verdi_feedback_fsm, + let mut objective = (); + + // Instantiate State with feedback, objective, in/out corpus + let mut state = StdState::new( + StdRand::with_seed(current_nanos()), + InMemoryCorpus::::new(), + InMemoryCorpus::new(), + &mut feedback, + &mut objective, + ) + .unwrap(); + state.set_max_size(max_testcase_size); + + // Simle FIFO scheduler + let scheduler = QueueScheduler::new(); + + // RISCV ISA mutator + let mutator = StdISAScheduledMutator::with_max_stack_pow(riscv_mutations(), 8); + + // Finally, instantiate the fuzzer + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + let mut executor = simv.into_executor(tuple_list!(verdi_observer_line, verdi_observer_tgl, verdi_observer_branch, verdi_observer_condition)); + //verdi_observer_fsm, + #[cfg(feature = "debug")] + println!("corpus_dir: {}", corpus_dir.to_string()); + + let corpus_dir = PathBuf::from(corpus_dir.to_string()); + + // load initial inputs if any seeds provided + state.load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[corpus_dir.clone()]).unwrap(); + // Instantiate a mutational stage that will apply mutations to the selected testcase + let sync_dir = PathBuf::from(sync_dir.to_string()); + let mut stages = tuple_list!(SyncFromDiskStage::new(sync_dir), StdMutationalStage::with_max_iterations(mutator, 1)); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + .expect("Error in fuzzing loop"); + + Ok(()) + }; + + + match Launcher::<_,_,_,BytesInput>::builder() + .configuration(EventConfig::from_name("default")) + .monitor(mon) + .run_client(&mut run_client) + .stdout_file(Some("stdout.log")) + .sync_dir(sync_dir.clone()) + .build() + .launch() + { + Ok(()) => (), + Err(Error::ShuttingDown) => (), + Err(err) => panic!("Fuzzingg failed {err:?}"), + }; +} + diff --git a/fuzzers/chipyard-vcs-fuzzer/src/simv.rs b/fuzzers/chipyard-vcs-fuzzer/src/simv.rs new file mode 100644 index 0000000..acb1062 --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/src/simv.rs @@ -0,0 +1,366 @@ +// SPDX-FileCopyrightText: 2022 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +#![cfg_attr( + test, + deny( + dead_code, + unreachable_code + ) +)] +#![allow(dead_code, unreachable_code)] + +use libafl::{ + executors::{command::CommandConfigurator}, + inputs::{HasTargetBytes, Input}, + Error, +}; +use libafl_bolts::{ + AsMutSlice, AsSlice, + ownedref::OwnedMutSlice +}; + +use std::assert; +use std::path::Path; +use std::process::{Child, Command}; +use std::os::unix::fs; +use std::time::Duration; +use std::env; +use std::process::Stdio; +use rand::Rng; +use std::io::ErrorKind; +use libpresifuzz_riscv::dasm::RiscvInstructions; +use libpresifuzz_riscv::elf::ELF; +use std::fs::File; + +#[cfg(feature = "root_snapshot")] +use subprocess::Exec; +#[cfg(feature = "root_snapshot")] +use subprocess::Redirection; + +#[cfg(feature = "debug")] +use color_print::cprintln; + +extern crate yaml_rust; + +fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { + std::fs::create_dir_all(&dst)?; + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + } else { + std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + } + Ok(()) +} + + +#[derive(Clone, Debug)] +pub struct SimvCommandConfigurator<'a> { + workdir : String, + vcs_args : String, + plus_args : String, + coverage_metrics : String, + coverage_directory : String, + reset_coverage_before_use : bool, + system_timeout_s : u64, + vcs_timeout : String, + testcase_buf : OwnedMutSlice<'a, u8>, + shm_id : String, + seed : u32, +} + +impl<'a> CommandConfigurator for SimvCommandConfigurator<'a> { + + fn spawn_child(&mut self, input: &I) -> Result { + + // clean old files if any + let old_log = "testcase_state.log"; + if let Ok(_metadata) = std::fs::metadata(&old_log) { + let _ = std::fs::remove_file(&old_log); + } + + if self.seed != 0 { + let mut rng = rand::thread_rng(); + + self.seed = rng.gen_range(0..100000); + } + + #[cfg(feature = "debug")] + cprintln!("[INFO] Running simv with seed {} ...", self.seed); + + if self.reset_coverage_before_use { + + // Clean existing vdb + assert!(Command::new("rm") + .arg("-rf") + .arg("./Coverage.vdb") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .unwrap() + .success()); + + // Copy virgin vdb + assert!(Command::new("cp") + .arg("-r") + .arg("./Virgin_coverage.vdb") + .arg("./Coverage.vdb") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .unwrap() + .success()); + } + + // Simv Command Executor prepares simv inputs and start simv with proper arguments + // 1. Generate testcase in expected format + self.generate_testcase(input); + + // 2. args string into vec + let mut forged_cmd_args = format!("\ + +permissive + +vcs+finish+{vcs_timeout} \ + -cm {coverage_metrics} \ + -cm_dir {coverage_directory} \ + +verbose \ + +loadmem=./testcase.elf\ + ", + vcs_timeout = self.vcs_timeout, + coverage_metrics = self.coverage_metrics, + coverage_directory = self.coverage_directory); + + if cfg!(feature = "root_snapshot") + { + forged_cmd_args.push_str(" +restore "); + forged_cmd_args.push_str(&format!(" +ntb_random_reseed={} ", self.seed)); + } else { + forged_cmd_args.push_str(&format!(" +ntb_random_seed={} ", self.seed)); + } + + forged_cmd_args.push_str(" +permissive-off ./testcase.elf"); + + let args_vec: Vec<&str> = forged_cmd_args.split(' ').collect(); + + #[cfg(feature = "debug")] + { + let args_v = &args_vec[0 .. args_vec.len()]; + println!("Executing command: ./simv-chipyard.harness-RocketConfig {:?}", args_v); + } + + let hw_execution_log = File::create("terminal.log").expect("Failed to terminal.log"); + let hw_execution_err = File::create("terminal.err").expect("Failed to terminal.err"); + + // 3. spawn simv + Ok(Command::new("./simv-chipyard.harness-RocketConfig") + .args(&args_vec) + .stdin(Stdio::null()) + .stdout(hw_execution_log) + .stderr(hw_execution_err) + .spawn() + .expect("failed to start process")) + } + + fn exec_timeout(&self) -> Duration { + Duration::from_secs(self.system_timeout_s.into()) + } +} + +impl<'a> SimvCommandConfigurator<'a> { + pub fn new_from_simv(simv: &SimvCommandConfigurator, testcase_buf: &'a mut [u8], shm_id: &'static str, seed: u32) -> SimvCommandConfigurator<'a> { + + #[cfg(feature = "debug")] + cprintln!("[INFO] New simv with shm_id {} ...", shm_id); + + return SimvCommandConfigurator{ + // testcase_file_name: testcase_file_name, + workdir : simv.workdir.to_string(), + vcs_args : simv.vcs_args.to_string(), + plus_args : simv.plus_args.to_string(), + coverage_metrics : simv.coverage_metrics.to_string(), + coverage_directory : simv.coverage_directory.to_string(), + reset_coverage_before_use : simv.reset_coverage_before_use, + system_timeout_s : simv.system_timeout_s, + vcs_timeout : simv.vcs_timeout.to_string(), + testcase_buf : OwnedMutSlice::from(testcase_buf), + shm_id : shm_id.to_string(), + seed : seed, + }; + } + + pub fn new_from_config_file(config_filename: &'static str, workdir: &str, testcase_buf: &'a mut [u8], shm_id: &'static str, seed: u32) -> SimvCommandConfigurator<'a> { + + #[cfg(feature = "debug")] + { + println!("Loading simv configuration from {}", config_filename); + println!("Simv workdir directory is {}", workdir); + } + + // parse yaml configuration file to extact: + // * simv arguments + // * simv executable name + // * simv version + // * simv ISA + + let yaml_fd = std::fs::File::open(config_filename).unwrap(); + let config: serde_yaml::Value = serde_yaml::from_reader(yaml_fd).unwrap(); + + let vcs_args = config["simv"]["vcs_args"] + .as_str() + .unwrap_or(""); + + let plus_args = config["simv"]["plus_args"] + .as_str() + .unwrap_or(""); + + let coverage_metrics = config["simv"]["coverage_metrics"] + .as_str() + .unwrap_or("tgl"); + + let coverage_directory = config["simv"]["coverage_directory"] + .as_str() + .unwrap_or("Coverage.vdb"); + + let reset_coverage_before_use = config["simv"]["reset_coverage_before_use"] + .as_bool() + .unwrap_or(false); + + let system_timeout_s = config["simv"]["system_timeout_s"] + .as_u64() + .unwrap_or(0); + + let vcs_timeout = config["simv"]["vcs_timeout"] + .as_str() + .unwrap_or("10us"); + + #[cfg(feature = "debug")] + { + println!("simv.vcs_args = {}", vcs_args); + println!("simv.plus_args = {}", plus_args); + println!("simv.vcs_timeout_s = {}", vcs_timeout); + println!("simv.system_timeout_s = {}", system_timeout_s); + println!("simv.coverage_directory = {}", coverage_directory); + println!("simv.coverage_metrics = {}", coverage_metrics); + println!("simv.reset_coverage_before_use = {}", reset_coverage_before_use); + } + + let mut vcs_home = env::current_dir().unwrap().as_os_str().to_str().unwrap().to_string(); + vcs_home.push_str("/build"); + + let src_s = vec![ + format!("{}/simv-chipyard.harness-RocketConfig.daidir", vcs_home), + format!("{}/simv-chipyard.harness-RocketConfig", vcs_home), + format!("{}/iram.elf", vcs_home)]; + + let dst_s = vec![ + format!("{}/simv-chipyard.harness-RocketConfig.daidir", workdir), + format!("{}/simv-chipyard.harness-RocketConfig", workdir), + format!("{}/iram.elf", workdir)]; + + for i in 0..src_s.len() { + #[cfg(feature = "debug")] + println!("Creating symlink from {src} to {dst}", src = &src_s[i], dst = &dst_s[i]); + + match fs::symlink(&src_s[i], &dst_s[i]) { + Err(e) if e.kind() == ErrorKind::AlreadyExists => { + println!("No need to copy {} because file already exists", &src_s[i]); + }, + Ok(_) => {}, + _ => { panic!("Fail to create symbolic link for simv workdir!");} + }; + } + + let src = format!("{}/{}", vcs_home, coverage_directory); + let dst = format!("{}/{}", workdir, coverage_directory); + copy_dir_all(&src, &dst).expect("Unable to copy vdb folder to workdir!"); + + if reset_coverage_before_use == true { + let dst = format!("{}/Virgin_coverage.vdb", workdir); + copy_dir_all(&src, &dst).expect("Unable to create Virgin copy of vdb folder in workdir!"); + } + + return SimvCommandConfigurator{ + // testcase_file_name: testcase_file_name, + workdir : workdir.to_string(), + vcs_args : vcs_args.to_string(), + plus_args : plus_args.to_string(), + coverage_metrics : coverage_metrics.to_string(), + coverage_directory : coverage_directory.to_string(), + reset_coverage_before_use : reset_coverage_before_use, + system_timeout_s : system_timeout_s, + vcs_timeout : vcs_timeout.to_string(), + testcase_buf : OwnedMutSlice::from(testcase_buf), + shm_id : shm_id.to_string(), + seed : seed, + }; + } + + fn generate_testcase(&mut self, input: &I) { + + let target = input.target_bytes(); + let buf = target.as_slice(); + let size = buf.len(); + + if cfg!(feature = "input_injection") { + + self.testcase_buf.as_mut_slice()[..size].copy_from_slice(&vec![0; size]); + self.testcase_buf.as_mut_slice()[0..4].copy_from_slice(&(0x00004297_i32).to_ne_bytes()); + self.testcase_buf.as_mut_slice()[4..8].copy_from_slice(&(0x03c28293_i32).to_ne_bytes()); + self.testcase_buf.as_mut_slice()[8..12].copy_from_slice(&(0x30529073_i32).to_ne_bytes()); + // self.testcase_buf.as_mut_slice()[12..size+12].copy_from_slice(&buf.as_slice()[..size]); + + // endianess swith here from big to little endian + // let &mut slice = &self.testcase_buf.as_mut_slice()[12..size+12]; + for k in (0..buf.as_slice().len()).step_by(4) { + let offset = k+12; + if k+3 < buf.as_slice().len() { + self.testcase_buf.as_mut_slice()[offset] = buf.as_slice()[k+3]; + self.testcase_buf.as_mut_slice()[offset+1] = buf.as_slice()[k+2]; + self.testcase_buf.as_mut_slice()[offset+2] = buf.as_slice()[k+1]; + self.testcase_buf.as_mut_slice()[offset+3] = buf.as_slice()[k]; + } + else if k+1 < buf.as_slice().len() { + self.testcase_buf.as_mut_slice()[offset] = buf.as_slice()[k+1]; + self.testcase_buf.as_mut_slice()[offset+1] = buf.as_slice()[k]; + } + else { + break; + } + } + + } else { + assert!(Path::new("iram.elf").exists()); + + let slice = input.target_bytes(); + let slice = slice.as_slice(); + let riscv_ins = RiscvInstructions::from_le(slice.to_vec()); + + let mut input_filename = String::from(&self.workdir); + input_filename.push_str("/"); + input_filename.push_str("testcase"); + input_filename.push_str(".elf"); + + let mut elf_template = ELF::new("iram.elf").unwrap(); + + elf_template.update(&riscv_ins); + elf_template.write_elf(&input_filename).unwrap(); + } + } + +} + + +#[cfg(feature = "std")] +#[cfg(test)] +mod tests { + + #[test] + fn test_simv_executor() { + } +} + diff --git a/fuzzers/chipyard-vcs-fuzzer/src/testcase.S b/fuzzers/chipyard-vcs-fuzzer/src/testcase.S new file mode 100644 index 0000000..e2174b3 --- /dev/null +++ b/fuzzers/chipyard-vcs-fuzzer/src/testcase.S @@ -0,0 +1,92 @@ +# Copyright 2022 Thales DIS design services SAS +# +# Licensed under the Solderpad Hardware Licence, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# SPDX-License-Identifier: Apache-2.0 WITH SHL-2.0 +# You may obtain a copy of the License at https://solderpad.org/licenses/ +# +# Original Author: Guillaume Chauvon (guillaume.chauvon@thalesgroup.fr) + +#***************************************************************************** +# custom_test_template.S +#----------------------------------------------------------------------------- +# +DRAM_START: + .word 0x90000000 + + .globl main +main: + la t0, exception_entry + csrw mtvec,t0 + la t4, DRAM_START + sw x0, (t4) + jal x0, payload + +.align 2 +exception_entry: + csrr t0, mepc + lb t1, 0(t0) + li a0, 0x3 + and t1, t1, a0 + + // stop the fuzzer if counter reaches threshold + la t4, DRAM_START + lw t5, (t4) + add t5, t5, 1 + li t6, 10 + beq t5, t6, exit + sw t5, (t4) + /* Increment mepc by 2 or 4 depending on whether the instruction at mepc + is compressed or not. */ + bne t1, a0, end_handler_incr_mepc2 + addi t0, t0, 2 +end_handler_incr_mepc2: + addi t0, t0, 2 + csrw mepc, t0 +end_handler_ret: + mret + +//csrr t3, mepc +//csrr t4, 0xfC2 #CSR_MSTATUS_REG_ADDR +//andi t4, t4, 0x00000080 #MSTATUS_IL_MASK +//srli t4, t4, 7 #MSTATUS_IL_SHIFT +//slli t4, t4, 1 #*2 +//add t3, t3, t4 +//add t3, t3, 2 +//csrw mepc, t3 +//la t4, DRAM_START +//lw t5, (t4) +//add t5, t5, 1 +//li t6, 100 +//beq t5, t6, exit +//sw t5, (t4) +//mret +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 +//lui t0,0x101 + +.align 4 +exit: + la t3, end + csrw sepc, t3 + mret +.align 4 +end: + wfi + jal x0, end + +.globl payload +.align 4 +payload: +.rept 1024 + .word 0xDEADBEEF +// .word 0xDEADBEAF +// .word 0xABABABAB +.endr + diff --git a/libpresifuzz_ec/src/manager.rs b/libpresifuzz_ec/src/manager.rs index 1c3b0fc..22402f0 100644 --- a/libpresifuzz_ec/src/manager.rs +++ b/libpresifuzz_ec/src/manager.rs @@ -370,6 +370,7 @@ where // Write the serialized binary data to a file let event_filename = format!("{}/client_{}_{}.event.presifuzz_lock", self.sync_dir, &self.client_id, self.count); + #[cfg(feature = "debug")] println!("sync on dir: {}", self.sync_dir); let mut file = File::create(event_filename).expect("Failed to persist event on systemfile"); @@ -393,6 +394,7 @@ where let new_filename = format!("{}/client_{}.event.stats", self.sync_dir, &self.client_id); let event_filename = format!("{}/client_{}.stats_event.presifuzz_lock", self.sync_dir, &self.client_id); + #[cfg(feature = "debug")] println!("sync on dir: {}", self.sync_dir); if !Path::new(&new_filename).is_file() { @@ -453,6 +455,7 @@ where // Write the serialized binary data to a file let event_filename = format!("{}/client_{}_{}.event.presifuzz_lock", self.sync_dir, &self.client_id, self.count); + #[cfg(feature = "debug")] println!("sync on dir: {}", self.sync_dir); let mut file = File::create(event_filename).expect("Failed to persist event on systemfile"); @@ -476,6 +479,7 @@ where let new_filename = format!("{}/client_{}.event.stats", self.sync_dir, &self.client_id); let event_filename = format!("{}/client_{}.stats_event.presifuzz_lock", self.sync_dir, &self.client_id); + #[cfg(feature = "debug")] println!("sync on dir: {}", self.sync_dir); if !Path::new(&new_filename).is_file() {