Skip to content

[Rust] Type-safe workflow and activity configuration #7254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 22 additions & 23 deletions plugins/svd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,9 @@ use crate::settings::LoadSettings;
use binaryninja::binary_view::{BinaryView, BinaryViewBase, BinaryViewExt};
use binaryninja::command::Command;
use binaryninja::logger::Logger;
use binaryninja::workflow::{Activity, AnalysisContext, Workflow};
use binaryninja::workflow::{activity, Activity, AnalysisContext, Workflow};
use log::LevelFilter;

pub const LOADER_ACTIVITY_NAME: &str = "analysis.svd.loader";
const LOADER_ACTIVITY_CONFIG: &str = r#"{
"name": "analysis.svd.loader",
"title" : "SVD Loader",
"description": "This analysis step applies SVD info to the view...",
"eligibility": {
"auto": {},
"runOnce": true
}
}"#;

struct LoadSVDFile;

impl Command for LoadSVDFile {
Expand Down Expand Up @@ -62,19 +51,25 @@ impl Command for LoadSVDFile {
#[allow(non_snake_case)]
#[cfg(not(feature = "demo"))]
pub extern "C" fn CorePluginInit() -> bool {
plugin_init();
if plugin_init().is_err() {
log::error!("Failed to initialize SVD plug-in");
return false;
}
true
}

#[no_mangle]
#[allow(non_snake_case)]
#[cfg(feature = "demo")]
pub extern "C" fn SVDPluginInit() -> bool {
plugin_init();
if plugin_init().is_err() {
log::error!("Failed to initialize SVD plug-in");
return false;
}
true
}

fn plugin_init() {
fn plugin_init() -> Result<(), ()> {
Logger::new("SVD").with_level(LevelFilter::Debug).init();

binaryninja::command::register_command(
Expand Down Expand Up @@ -113,12 +108,16 @@ fn plugin_init() {
};

// Register new workflow activity to load svd information.
let old_module_meta_workflow = Workflow::instance("core.module.metaAnalysis");
let module_meta_workflow = old_module_meta_workflow.clone_to("core.module.metaAnalysis");
let loader_activity = Activity::new_with_action(LOADER_ACTIVITY_CONFIG, loader_activity);
module_meta_workflow
.register_activity(&loader_activity)
.unwrap();
module_meta_workflow.insert("core.module.loadDebugInfo", [LOADER_ACTIVITY_NAME]);
module_meta_workflow.register().unwrap();
let loader_config = activity::Config::action(
"analysis.svd.loader",
"SVD Loader",
"This analysis step applies SVD info to the view...",
)
.eligibility(activity::Eligibility::auto().run_once(true));
let loader_activity = Activity::new_with_action(loader_config, loader_activity);
Workflow::cloned("core.module.metaAnalysis")
.ok_or(())?
.activity_before(&loader_activity, "core.module.loadDebugInfo")?
.register()?;
Ok(())
}
5 changes: 4 additions & 1 deletion plugins/warp/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ pub extern "C" fn CorePluginInit() -> bool {
// Register our highlight render layer.
HighlightRenderLayer::register();

workflow::insert_workflow();
if workflow::insert_workflow().is_err() {
log::error!("Failed to register WARP workflow");
return false;
}

// TODO: Make the retrieval of containers wait on this to be done.
// TODO: We could also have a mechanism for lazily loading the files using the chunk header target.
Expand Down
98 changes: 43 additions & 55 deletions plugins/warp/src/plugin/workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,15 @@ use binaryninja::background_task::BackgroundTask;
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
use binaryninja::command::Command;
use binaryninja::settings::{QueryOptions, Settings};
use binaryninja::workflow::{Activity, AnalysisContext, Workflow};
use binaryninja::workflow::{activity, Activity, AnalysisContext, Workflow, WorkflowBuilder};
use itertools::Itertools;
use std::collections::HashMap;
use std::time::Instant;
use warp::r#type::class::function::{Location, RegisterLocation, StackLocation};
use warp::signature::function::{Function, FunctionGUID};
use warp::target::Target;

pub const APPLY_ACTIVITY_NAME: &str = "analysis.warp.apply";
const APPLY_ACTIVITY_CONFIG: &str = r#"{
"name": "analysis.warp.apply",
"title" : "WARP Apply Matched",
"description": "This analysis step applies WARP info to matched functions...",
"eligibility": {
"auto": {},
"runOnce": false
}
}"#;

pub const MATCHER_ACTIVITY_NAME: &str = "analysis.warp.matcher";
const MATCHER_ACTIVITY_CONFIG: &str = r#"{
"name": "analysis.warp.matcher",
"title" : "WARP Matcher",
"description": "This analysis step attempts to find matching WARP functions after the initial analysis is complete...",
"eligibility": {
"auto": {},
"runOnce": true
},
"dependencies": {
"downstream": ["core.module.update"]
}
}"#;

pub const GUID_ACTIVITY_NAME: &str = "analysis.warp.guid";
const GUID_ACTIVITY_CONFIG: &str = r#"{
"name": "analysis.warp.guid",
"title" : "WARP GUID Generator",
"description": "This analysis step generates the GUID for all analyzed functions...",
"eligibility": {
"auto": {},
"runOnce": false
}
}"#;

pub struct RunMatcher;

Expand Down Expand Up @@ -189,7 +155,7 @@ pub fn run_matcher(view: &BinaryView) {
view.update_analysis();
}

pub fn insert_workflow() {
pub fn insert_workflow() -> Result<(), ()> {
// TODO: Note: because of symbol persistence function symbol is applied in `insert_cached_function_match`.
// TODO: Comments are also applied there, they are "user" like, persisted and make undo actions.
// "Hey look, it's a plier" ~ Josh 2025
Expand Down Expand Up @@ -259,29 +225,51 @@ pub fn insert_workflow() {
}
};

let guid_activity = Activity::new_with_action(GUID_ACTIVITY_CONFIG, guid_activity);
let apply_activity = Activity::new_with_action(APPLY_ACTIVITY_CONFIG, apply_activity);
let guid_config = activity::Config::action(
GUID_ACTIVITY_NAME,
"WARP GUID Generator",
"This analysis step generates the GUID for all analyzed functions...",
)
.eligibility(activity::Eligibility::auto().run_once(false));
let guid_activity = Activity::new_with_action(&guid_config, guid_activity);

let apply_config = activity::Config::action(
"analysis.warp.apply",
"WARP Apply Matched",
"This analysis step applies WARP info to matched functions...",
)
.eligibility(activity::Eligibility::auto().run_once(false));
let apply_activity = Activity::new_with_action(&apply_config, apply_activity);

let add_function_activities = |workflow: &Workflow| {
let new_workflow = workflow.clone_to(&workflow.name());
new_workflow.register_activity(&guid_activity).unwrap();
new_workflow.register_activity(&apply_activity).unwrap();
new_workflow.insert_after("core.function.runFunctionRecognizers", [GUID_ACTIVITY_NAME]);
new_workflow.insert_after("core.function.generateMediumLevelIL", [APPLY_ACTIVITY_NAME]);
new_workflow.register().unwrap();
let add_function_activities = |workflow: Option<WorkflowBuilder>| -> Result<(), ()> {
let Some(workflow) = workflow else {
return Ok(());
};

workflow
.activity_after(&guid_activity, "core.function.runFunctionRecognizers")?
.activity_after(&apply_activity, "core.function.generateMediumLevelIL")?
.register()?;
Ok(())
};

add_function_activities(&Workflow::instance("core.function.metaAnalysis"));
add_function_activities(Workflow::cloned("core.function.metaAnalysis"))?;
// TODO: Remove this once the objectivec workflow is registered on the meta workflow.
add_function_activities(&Workflow::instance("core.function.objectiveC"));
add_function_activities(Workflow::cloned("core.function.objectiveC"))?;

let old_module_meta_workflow = Workflow::instance("core.module.metaAnalysis");
let module_meta_workflow = old_module_meta_workflow.clone_to("core.module.metaAnalysis");
let matcher_activity = Activity::new_with_action(MATCHER_ACTIVITY_CONFIG, matcher_activity);
let matcher_config = activity::Config::action(
"analysis.warp.matcher",
"WARP Matcher",
"This analysis step attempts to find matching WARP functions after the initial analysis is complete...",
)
.eligibility(activity::Eligibility::auto().run_once(true))
// Matcher activity must have core.module.update as subactivity otherwise analysis will sometimes never retrigger.
module_meta_workflow
.register_activity(&matcher_activity)
.unwrap();
module_meta_workflow.insert("core.module.finishUpdate", [MATCHER_ACTIVITY_NAME]);
module_meta_workflow.register().unwrap();
.downstream_dependencies(["core.module.update"]);
let matcher_activity = Activity::new_with_action(&matcher_config, matcher_activity);
Workflow::cloned("core.module.metaAnalysis")
.ok_or(())?
.activity_before(&matcher_activity, "core.module.finishUpdate")?
.register()?;

Ok(())
}
2 changes: 1 addition & 1 deletion rust/examples/workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fn main() {
binaryninja::headless::Session::new().expect("Failed to initialize session");

println!("Registering workflow...");
let old_meta_workflow = Workflow::instance("core.function.metaAnalysis");
let old_meta_workflow = Workflow::get("core.function.metaAnalysis");
let meta_workflow = old_meta_workflow.clone_to("core.function.metaAnalysis");
let activity = Activity::new_with_action(RUST_ACTIVITY_CONFIG, example_activity);
meta_workflow.register_activity(&activity).unwrap();
Expand Down
Loading
Loading