Skip to content

Commit

Permalink
lib/utils/debug: add option to read output of Ghidra plugin from file
Browse files Browse the repository at this point in the history
The output of the Ghidra plugin (and Ghidra in general) is
non-deterministic, even on the same Ghidra version. Probably this is
due to analysis timeouts.

Furthermore, running Ghidra takes a significant portion of the analysis
time.

Therefore, we can create reproducibility as well as speedups during
development by saving the plugin output (using `--debug pcode-raw`) and
replaying it later (via `--pcode-raw /path/to/file`).

Signed-off-by: Valentin Obst <[email protected]>
  • Loading branch information
Valentin Obst committed Jun 14, 2024
1 parent 563f9d7 commit cf3c238
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 31 deletions.
18 changes: 16 additions & 2 deletions src/caller/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ struct CmdlineArgs {
/// The current behavior of this flag is unstable and subject to change.
#[arg(long, hide(true))]
debug: Option<CliDebugMode>,

/// Read the saved output of the Pcode Extractor plugin from a file instead
/// of invoking Ghidra.
#[arg(long, hide(true))]
pcode_raw: Option<String>,
}

impl From<&CmdlineArgs> for debug::Settings {
Expand All @@ -108,15 +113,24 @@ impl From<&CmdlineArgs> for debug::Settings {
None => debug::Stage::default(),
Some(mode) => mode.into(),
};
let verbose = if args.verbose {
let verbosity = if args.verbose {
debug::Verbosity::Verbose
} else if args.quiet {
debug::Verbosity::Quiet
} else {
debug::Verbosity::default()
};

debug::Settings::new(stage, verbose)
let mut builder = debug::SettingsBuilder::default()
.set_stage(stage)
.set_verbosity(verbosity)
.set_termination_policy(debug::TerminationPolicy::EarlyExit);

if let Some(pcode_raw) = &args.pcode_raw {
builder = builder.set_saved_pcode_raw(PathBuf::from(pcode_raw.clone()));
}

builder.build()
}
}

Expand Down
51 changes: 42 additions & 9 deletions src/cwe_checker_lib/src/utils/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#![allow(dead_code)]
#![allow(missing_docs)]

use std::path::PathBuf;

#[derive(PartialEq, Eq, Copy, Clone, Debug, Default)]
/// Stages of the analysis that can be debugged separately.
#[non_exhaustive]
Expand Down Expand Up @@ -48,29 +50,60 @@ pub enum Verbosity {
/// Selects whether the analysis is aborted after reaching the point of
/// interest.
#[non_exhaustive]
enum TerminationPolicy {
pub enum TerminationPolicy {
KeepRunning,
#[default]
EarlyExit,
Panic,
}

#[derive(PartialEq, Eq, Copy, Clone, Default, Debug)]
#[derive(PartialEq, Eq, Clone, Default, Debug)]
/// Configuration of the debugging behavior.
pub struct Settings {
stage: Stage,
verbose: Verbosity,
terminate: TerminationPolicy,
saved_pcode_raw: Option<PathBuf>,
}

#[derive(PartialEq, Eq, Clone, Default, Debug)]
pub struct SettingsBuilder {
inner: Settings,
}

impl SettingsBuilder {
pub fn build(self) -> Settings {
self.inner
}

pub fn set_stage(mut self, stage: Stage) -> Self {
self.inner.stage = stage;

self
}

pub fn set_verbosity(mut self, verbosity: Verbosity) -> Self {
self.inner.verbose = verbosity;

self
}

pub fn set_termination_policy(mut self, policy: TerminationPolicy) -> Self {
self.inner.terminate = policy;

self
}

pub fn set_saved_pcode_raw(mut self, saved_pcode_raw: PathBuf) -> Self {
self.inner.saved_pcode_raw = Some(saved_pcode_raw);

self
}
}

impl Settings {
/// Returns a new settings object.
pub fn new(stage: Stage, verbose: Verbosity) -> Self {
Self {
stage,
verbose,
terminate: TerminationPolicy::default(),
}
pub fn get_saved_pcode_raw(&self) -> Option<PathBuf> {
self.saved_pcode_raw.clone()
}

/// Returns true iff the `stage` is being debugged.
Expand Down
46 changes: 26 additions & 20 deletions src/cwe_checker_lib/src/utils/ghidra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,32 @@ pub fn get_project_from_ghidra(
bare_metal_config_opt: Option<BareMetalConfig>,
debug_settings: &debug::Settings,
) -> Result<(Project, Vec<LogMessage>), Error> {
let tmp_folder = get_tmp_folder()?;
// We add a timestamp suffix to file names
// so that if two instances of the cwe_checker are running in parallel on the same file
// they do not interfere with each other.
let timestamp_suffix = format!(
"{:?}",
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis()
);
// Create a unique name for the pipe
let fifo_path = tmp_folder.join(format!("pcode_{timestamp_suffix}.pipe"));
let ghidra_command = generate_ghidra_call_command(
file_path,
&fifo_path,
&timestamp_suffix,
&bare_metal_config_opt,
)?;
let pcode_project = execute_ghidra(ghidra_command, &fifo_path, debug_settings)?;
let pcode_project = if let Some(saved_pcode_raw) = debug_settings.get_saved_pcode_raw() {
let file = std::fs::File::open(saved_pcode_raw)
.expect("Failed to open saved output of Pcode Extractor plugin.");
serde_json::from_reader(std::io::BufReader::new(file))?
} else {
let tmp_folder = get_tmp_folder()?;
// We add a timestamp suffix to file names
// so that if two instances of the cwe_checker are running in parallel on the same file
// they do not interfere with each other.
let timestamp_suffix = format!(
"{:?}",
std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis()
);
// Create a unique name for the pipe
let fifo_path = tmp_folder.join(format!("pcode_{timestamp_suffix}.pipe"));
let ghidra_command = generate_ghidra_call_command(
file_path,
&fifo_path,
&timestamp_suffix,
&bare_metal_config_opt,
)?;
execute_ghidra(ghidra_command, &fifo_path, debug_settings)?
};

parse_pcode_project_to_ir_project(pcode_project, binary, &bare_metal_config_opt)
}
Expand Down

0 comments on commit cf3c238

Please sign in to comment.