summaryrefslogtreecommitdiff
path: root/plugins/warp/src/plugin
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2026-03-13 12:24:18 -0700
committerMason Reed <35282038+emesare@users.noreply.github.com>2026-03-24 18:46:48 -0700
commit3c88b11e5df33116580ac008e36092775df66135 (patch)
treecd64fbe149f05683ddabcccd0db7b87356f1f8cf /plugins/warp/src/plugin
parentf325aa7b6026a1daef84931baeb1f50d7da10c10 (diff)
[WARP] Improved UX and API
- Exposes WARP type objects directly - Adds processor API (for generating warp files directly) - Adds file and chunk API - Misc cleanup - Simplified the amount of commands - Replaced the "Create" commands with a purpose built processor dialog - Added a native QT viewer for WARP files - Simplified committing to a remote with a purpose built commit dialog
Diffstat (limited to 'plugins/warp/src/plugin')
-rw-r--r--plugins/warp/src/plugin/commit.rs149
-rw-r--r--plugins/warp/src/plugin/create.rs265
-rw-r--r--plugins/warp/src/plugin/debug.rs49
-rw-r--r--plugins/warp/src/plugin/ffi.rs3
-rw-r--r--plugins/warp/src/plugin/ffi/container.rs61
-rw-r--r--plugins/warp/src/plugin/ffi/file.rs137
-rw-r--r--plugins/warp/src/plugin/ffi/function.rs19
-rw-r--r--plugins/warp/src/plugin/ffi/processor.rs187
-rw-r--r--plugins/warp/src/plugin/ffi/ty.rs75
-rw-r--r--plugins/warp/src/plugin/file.rs41
-rw-r--r--plugins/warp/src/plugin/function.rs102
-rw-r--r--plugins/warp/src/plugin/project.rs286
12 files changed, 428 insertions, 946 deletions
diff --git a/plugins/warp/src/plugin/commit.rs b/plugins/warp/src/plugin/commit.rs
deleted file mode 100644
index 4e8a9fee..00000000
--- a/plugins/warp/src/plugin/commit.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-//! Commit file to a source.
-
-use crate::cache::container::cached_containers;
-use crate::container::{SourceId, SourcePath};
-use crate::plugin::create::OpenFileField;
-use binaryninja::command::GlobalCommand;
-use binaryninja::interaction::{Form, FormInputField};
-use warp::chunk::ChunkKind;
-use warp::WarpFile;
-
-pub struct SelectedSourceField {
- sources: Vec<(SourceId, SourcePath)>,
-}
-
-impl SelectedSourceField {
- pub fn field(&self) -> FormInputField {
- FormInputField::Choice {
- prompt: "Selected Source".to_string(),
- choices: self
- .sources
- .iter()
- .map(|(id, path)| {
- // For display purposes we only want to show the last path item.
- let path_name = path
- .to_string()
- .rsplit_once('/')
- .map_or(path.to_string(), |(_, last_path_item)| {
- last_path_item.to_string()
- });
- // TODO: Probably have a truncation limit here, this is just for display after all.
- format!("{} ({})", path_name, id)
- })
- .collect(),
- default: None,
- value: 0,
- }
- }
-
- pub fn from_form(&self, form: &Form) -> Option<SourceId> {
- let field = form.get_field_with_name("Selected Source")?;
- let field_value = field.try_value_index()?;
- self.sources.get(field_value).map(|(id, _)| *id)
- }
-}
-
-pub struct CommitFile;
-
-impl CommitFile {
- pub fn selected_source_field() -> SelectedSourceField {
- let mut writable_sources = Vec::new();
- for container in cached_containers() {
- if let Ok(container) = container.read() {
- for source in container.sources().unwrap_or_default() {
- if let Ok(true) = container.is_source_writable(&source) {
- if let Ok(source_path) = container.source_path(&source) {
- writable_sources.push((source, source_path));
- }
- }
- }
- }
- }
- SelectedSourceField {
- sources: writable_sources,
- }
- }
-
- pub fn execute() -> Option<()> {
- let mut form = Form::new("Commit File");
-
- // Users are going to get confused between this and adding functions to a source then commiting.
- // So we should make it clear with a label, and also probably deprecate this command and replace it with "add functions to source" and "commit source".
- form.add_field(FormInputField::Label {
- prompt: "Commits a WARP file to an existing source, this is primarily used for committing to network containers".to_string()
- });
-
- form.add_field(OpenFileField::field());
- let source_field = Self::selected_source_field();
- form.add_field(source_field.field());
-
- if !form.prompt() {
- return None;
- }
-
- let open_file_path = OpenFileField::from_form(&form)?;
- let source_id = source_field.from_form(&form)?;
- tracing::info!("Committing file to source: {}", source_id);
-
- let bytes = std::fs::read(open_file_path).ok()?;
- let Some(warp_file) = WarpFile::from_bytes(&bytes) else {
- tracing::error!("Failed to parse warp file!");
- return None;
- };
-
- for container in cached_containers() {
- let Ok(mut container) = container.write() else {
- continue;
- };
-
- if let Ok(true) = container.is_source_writable(&source_id) {
- // TODO: We need to find a sane way to do this procedure through the FFI.
- for chunk in &warp_file.chunks {
- match &chunk.kind {
- ChunkKind::Signature(sc) => {
- let functions: Vec<_> = sc.functions().collect();
- tracing::info!(
- "Adding {} functions to source: {}",
- functions.len(),
- source_id
- );
- if let Err(e) = container.add_functions(
- &chunk.header.target,
- &source_id,
- &functions,
- ) {
- tracing::error!("Failed to add functions to source: {}", e);
- }
- }
- ChunkKind::Type(sc) => {
- let types: Vec<_> = sc.types().collect();
- tracing::info!("Adding {} types to source: {}", types.len(), source_id);
- if let Err(e) = container.add_computed_types(&source_id, &types) {
- tracing::error!("Failed to add types to source: {}", e);
- }
- }
- }
- }
- if let Err(e) = container.commit_source(&source_id) {
- tracing::error!("Failed to commit source: {}", e);
- }
- tracing::info!("Committed file to source: {}", source_id);
- return Some(());
- }
- }
-
- Some(())
- }
-}
-
-impl GlobalCommand for CommitFile {
- fn action(&self) {
- std::thread::spawn(move || {
- Self::execute();
- });
- }
-
- fn valid(&self) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/create.rs b/plugins/warp/src/plugin/create.rs
deleted file mode 100644
index 5f5527a7..00000000
--- a/plugins/warp/src/plugin/create.rs
+++ /dev/null
@@ -1,265 +0,0 @@
-use crate::processor::{
- new_processing_state_background_thread, CompressionTypeField, FileDataKindField,
- IncludedFunctionsField, SaveReportToDiskField, WarpFileProcessor,
-};
-use crate::report::{ReportGenerator, ReportKindField};
-use crate::{user_signature_dir, INCLUDE_TAG_NAME};
-use binaryninja::background_task::BackgroundTask;
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::command::{Command, GlobalCommand};
-use binaryninja::file_metadata::FileMetadata;
-use binaryninja::interaction::form::{Form, FormInputField};
-use binaryninja::interaction::{MessageBoxButtonResult, MessageBoxButtonSet, MessageBoxIcon};
-use binaryninja::rc::Ref;
-use std::path::PathBuf;
-use std::thread;
-use warp::chunk::Chunk;
-use warp::WarpFile;
-
-pub struct SaveFileField;
-
-impl SaveFileField {
- pub fn field(view: &BinaryView) -> FormInputField {
- let file = view.file();
- let default_name = match file.project_file() {
- None => {
- // Not in a project, use the file name directly.
- file.display_name()
- }
- Some(project_file) => project_file.name(),
- };
- let signature_dir = user_signature_dir();
- let default_file_path = signature_dir.join(&default_name).with_extension("warp");
- FormInputField::SaveFileName {
- prompt: "File Path".to_string(),
- // TODO: This is called extension but is really a filter.
- extension: Some("*.warp".to_string()),
- default: Some(default_file_path.to_string_lossy().to_string()),
- value: None,
- }
- }
-
- pub fn from_form(form: &Form) -> Option<PathBuf> {
- let field = form.get_field_with_name("File Path")?;
- let field_value = field.try_value_string()?;
- Some(PathBuf::from(field_value))
- }
-}
-
-pub struct OpenFileField;
-
-impl OpenFileField {
- pub fn field() -> FormInputField {
- FormInputField::OpenFileName {
- prompt: "Input File Path".to_string(),
- extension: None,
- default: None,
- value: None,
- }
- }
-
- pub fn from_form(form: &Form) -> Option<PathBuf> {
- let field = form.get_field_with_name("Input File Path")?;
- let field_value = field.try_value_string()?;
- Some(PathBuf::from(field_value))
- }
-}
-
-pub struct CreateFromCurrentView;
-
-impl CreateFromCurrentView {
- pub fn execute(view: Ref<BinaryView>, external_file: bool) -> Option<()> {
- // Prompt the user first so that they can go do other things and not worry about a popup.
- let mut form = Form::new("Create From View");
-
- if external_file {
- form.add_field(OpenFileField::field());
- }
-
- form.add_field(SaveFileField::field(&view));
-
- let fd_field = FileDataKindField::default();
- form.add_field(fd_field.to_field());
-
- let compression_field = CompressionTypeField::default();
- form.add_field(compression_field.to_field());
-
- let mut included_field = IncludedFunctionsField::default();
- // If the view has the include tag, we better set the default to the selected functions.
- if view.tag_type_by_name(INCLUDE_TAG_NAME).is_some() {
- included_field = IncludedFunctionsField::Selected;
- }
- form.add_field(included_field.to_field());
-
- let report_field = ReportKindField::default();
- form.add_field(report_field.to_field());
- let report_to_disk_field = SaveReportToDiskField::default();
- form.add_field(report_to_disk_field.to_field());
-
- if !form.prompt() {
- return None;
- }
- let compression_type = CompressionTypeField::from_form(&form).unwrap_or_default();
- let file_path = SaveFileField::from_form(&form)?;
- let file_data_kind = FileDataKindField::from_form(&form).unwrap_or_default();
- let file_included_functions = IncludedFunctionsField::from_form(&form).unwrap_or_default();
- let report_kind = ReportKindField::from_form(&form).unwrap_or_default();
- let save_report_to_disk = SaveReportToDiskField::from_form(&form).unwrap_or_default();
- let open_file_path = OpenFileField::from_form(&form);
-
- // If we already have a file, prompt the user if they want to add the data.
- let mut existing_chunks = Vec::new();
- if file_path.exists() {
- let prompt_result = binaryninja::interaction::show_message_box(
- "Keep existing file data?",
- "The file already exists. Do you want to keep the existing data?",
- MessageBoxButtonSet::YesNoCancelButtonSet,
- MessageBoxIcon::QuestionIcon,
- );
-
- match prompt_result {
- MessageBoxButtonResult::NoButton => {
- // User wants to overwrite the file.
- }
- MessageBoxButtonResult::YesButton | MessageBoxButtonResult::OKButton => {
- // User wants to keep the existing data.
- let data = std::fs::read(&file_path).ok()?;
- let existing_file = WarpFile::from_owned_bytes(data)?;
- existing_chunks.extend(existing_file.chunks);
- }
- MessageBoxButtonResult::CancelButton => {
- tracing::info!(
- "User cancelled signature file creation, no operations were performed."
- );
- return None;
- }
- }
- }
-
- let processor = WarpFileProcessor::new()
- .with_compression_type(compression_type)
- .with_file_data(file_data_kind)
- .with_included_functions(file_included_functions);
-
- let file = match open_file_path {
- None => {
- // We are processing the current view. NOT an external file.
- // Reference path is just used for the state tracking. Does not need to be readable.
- let reference_path = file_path.clone();
- processor.process_view(reference_path, &view)
- }
- Some(open_file_path) => {
- // This thread will show the state in a background task.
- let background_task = BackgroundTask::new("Processing started...", true);
- new_processing_state_background_thread(background_task.clone(), processor.state());
- let file = processor.process(open_file_path);
- background_task.finish();
- file
- }
- };
-
- if let Err(err) = file {
- binaryninja::interaction::show_message_box(
- "Failed to create signature file",
- &err.to_string(),
- MessageBoxButtonSet::OKButtonSet,
- MessageBoxIcon::ErrorIcon,
- );
- tracing::error!("Failed to create signature file: {}", err);
- return None;
- }
-
- let background_task = BackgroundTask::new("Creating WARP File...", false);
- let mut file = file.unwrap();
- // Add back the existing chunks if the user selected to keep them.
- if !existing_chunks.is_empty() {
- file.chunks.extend(existing_chunks);
- // TODO: Make merging optional?
- // TODO: Merging can lose chunk data if it goes above the maximum table count.
- // TODO: We should probably solve that in the warp crate itself?
- file.chunks = Chunk::merge(&file.chunks, compression_type.into());
-
- // After merging, we should have at least one chunk. If not, merging actually removed data.
- if file.chunks.len() < 1 {
- tracing::error!(
- "Failed to merge chunks! Please report this, it should not happen."
- );
- return None;
- }
- }
-
- let file_bytes = file.to_bytes();
- let file_size = file_bytes.len();
- if std::fs::write(&file_path, file_bytes).is_err() {
- tracing::error!("Failed to write data to signature file!");
- }
- tracing::info!("Saved signature file to: '{}'", file_path.display());
- background_task.finish();
-
- // Show a report of the generate signatures, if desired.
- let report_generator = ReportGenerator::new();
- if let Some(report_string) = report_generator.report(&report_kind, &file) {
- if save_report_to_disk == SaveReportToDiskField::Yes {
- let report_ext = report_generator
- .report_extension(&report_kind)
- .unwrap_or_default();
- let report_path = file_path.with_extension(report_ext);
- let _ = std::fs::write(report_path, &report_string);
- }
-
- // The ReportWidget uses a QTextBrowser, which cannot render large files very well.
- if file_size > 10000000 {
- tracing::warn!("WARP report file is too large to show in the UI. Please see the report file on disk.");
- } else {
- match report_kind {
- ReportKindField::None => {}
- ReportKindField::Html => {
- view.show_html_report("Generated WARP File", report_string.as_str(), "");
- }
- ReportKindField::Markdown => {
- view.show_markdown_report(
- "Generated WARP File",
- report_string.as_str(),
- "",
- );
- }
- ReportKindField::Json => {
- view.show_plaintext_report("Generated WARP File", report_string.as_str());
- }
- }
- }
- }
-
- Some(())
- }
-}
-
-impl Command for CreateFromCurrentView {
- fn action(&self, view: &BinaryView) {
- let view = view.to_owned();
- thread::spawn(move || {
- CreateFromCurrentView::execute(view, false);
- });
- }
-
- fn valid(&self, _view: &BinaryView) -> bool {
- true
- }
-}
-
-pub struct CreateFromFiles;
-
-impl GlobalCommand for CreateFromFiles {
- fn action(&self) {
- let empty_file_metadata = FileMetadata::new();
- let empty_bv = BinaryView::from_data(&empty_file_metadata, &[]);
- thread::spawn(move || {
- CreateFromCurrentView::execute(empty_bv.to_owned(), true);
- empty_bv.file().close();
- });
- }
-
- fn valid(&self) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/debug.rs b/plugins/warp/src/plugin/debug.rs
deleted file mode 100644
index 4198ea30..00000000
--- a/plugins/warp/src/plugin/debug.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-use crate::cache::container::for_cached_containers;
-use crate::{build_function, cache};
-use binaryninja::binary_view::BinaryView;
-use binaryninja::command::{Command, FunctionCommand};
-use binaryninja::function::Function;
-use binaryninja::object_destructor::ObjectDestructor;
-
-pub struct DebugFunction;
-
-impl FunctionCommand for DebugFunction {
- fn action(&self, _view: &BinaryView, func: &Function) {
- tracing::info!(
- "{:#?}",
- build_function(func, || func.lifted_il().ok(), false)
- );
- }
-
- fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
- true
- }
-}
-
-pub struct DebugCache;
-
-impl Command for DebugCache {
- fn action(&self, _view: &BinaryView) {
- for_cached_containers(|c| {
- tracing::info!("Container: {:#?}", c);
- });
- }
-
- fn valid(&self, _view: &BinaryView) -> bool {
- true
- }
-}
-
-pub struct DebugInvalidateCache;
-
-impl Command for DebugInvalidateCache {
- fn action(&self, view: &BinaryView) {
- let destructor = cache::CacheDestructor {};
- destructor.destruct_view(view);
- tracing::info!("Invalidated all WARP caches...");
- }
-
- fn valid(&self, _view: &BinaryView) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/ffi.rs b/plugins/warp/src/plugin/ffi.rs
index c1f5acb8..bb6fb483 100644
--- a/plugins/warp/src/plugin/ffi.rs
+++ b/plugins/warp/src/plugin/ffi.rs
@@ -1,6 +1,8 @@
mod container;
mod file;
mod function;
+mod processor;
+mod ty;
use binaryninjacore_sys::{
BNBasicBlock, BNBinaryView, BNFunction, BNLowLevelILFunction, BNPlatform,
@@ -48,6 +50,7 @@ pub type BNWARPTypeGUID = TypeGUID;
pub type BNWARPTarget = warp::target::Target;
pub type BNWARPFunction = warp::signature::function::Function;
pub type BNWARPContainer = RwLock<Box<dyn Container>>;
+pub type BNWARPType = warp::r#type::Type;
// TODO: Some sort of callback for loading functions
// TODO: Be able to run matcher for a specific file
diff --git a/plugins/warp/src/plugin/ffi/container.rs b/plugins/warp/src/plugin/ffi/container.rs
index aa0c37d3..09f15d99 100644
--- a/plugins/warp/src/plugin/ffi/container.rs
+++ b/plugins/warp/src/plugin/ffi/container.rs
@@ -3,17 +3,11 @@ use crate::container::disk::DiskContainer;
use crate::container::{
ContainerSearchItem, ContainerSearchItemKind, ContainerSearchQuery, SourcePath, SourceTag,
};
-use crate::convert::{from_bn_type, to_bn_type};
use crate::plugin::ffi::{
BNWARPConstraintGUID, BNWARPContainer, BNWARPFunction, BNWARPFunctionGUID, BNWARPSource,
- BNWARPTarget, BNWARPTypeGUID,
+ BNWARPTarget, BNWARPType, BNWARPTypeGUID,
};
-use binaryninja::architecture::CoreArchitecture;
-use binaryninja::binary_view::BinaryView;
-use binaryninja::rc::Ref;
use binaryninja::string::BnString;
-use binaryninja::types::Type;
-use binaryninjacore_sys::{BNArchitecture, BNBinaryView, BNType};
use std::collections::HashMap;
use std::ffi::{c_char, CStr};
use std::mem::ManuallyDrop;
@@ -98,32 +92,25 @@ pub unsafe extern "C" fn BNWARPContainerSearchItemGetSource(
#[no_mangle]
pub unsafe extern "C" fn BNWARPContainerSearchItemGetType(
- arch: *mut BNArchitecture,
item: *mut BNWARPContainerSearchItem,
-) -> *mut BNType {
- // NOTE: to convert the type, we must have an architecture.
- let arch = match !arch.is_null() {
- true => Some(CoreArchitecture::from_raw(arch)),
- false => None,
- };
-
+) -> *mut BNWARPType {
let item = ManuallyDrop::new(Arc::from_raw(item));
match &item.kind {
ContainerSearchItemKind::Source { .. } => std::ptr::null_mut(),
ContainerSearchItemKind::Function(func) => {
match &func.ty {
None => std::ptr::null_mut(),
- Some(ty) => {
- let bn_ty = to_bn_type(arch, &ty);
- // NOTE: The type ref has been pre-incremented for the caller.
- unsafe { Ref::into_raw(bn_ty) }.handle
+ Some(func_ty) => {
+ let arc_func_ty = Arc::new(func_ty.clone());
+ // NOTE: Freed by BNWARPFreeTypeReference
+ Arc::into_raw(arc_func_ty) as *mut BNWARPType
}
}
}
ContainerSearchItemKind::Type(ty) => {
- let bn_ty = to_bn_type(arch, &ty);
- // NOTE: The type ref has been pre-incremented for the caller.
- unsafe { Ref::into_raw(bn_ty) }.handle
+ let arc_ty = Arc::new(ty.clone());
+ // NOTE: Freed by BNWARPFreeTypeReference
+ Arc::into_raw(arc_ty) as *mut BNWARPType
}
ContainerSearchItemKind::Symbol(_) => std::ptr::null_mut(),
}
@@ -254,7 +241,7 @@ pub unsafe extern "C" fn BNWARPContainerGetSources(
return std::ptr::null_mut();
};
- // NOTE: Leak the sources to be freed by BNWARPFreeSourceList
+ // NOTE: Leak the sources to be freed by BNWARPFreeUUIDList
let boxed_sources: Box<[_]> = container.sources().unwrap_or_default().into_boxed_slice();
*count = boxed_sources.len();
Box::into_raw(boxed_sources) as *mut BNWARPSource
@@ -389,14 +376,11 @@ pub unsafe extern "C" fn BNWARPContainerAddFunctions(
#[no_mangle]
pub unsafe extern "C" fn BNWARPContainerAddTypes(
- view: *mut BNBinaryView,
container: *mut BNWARPContainer,
source: *const BNWARPSource,
- types: *mut *mut BNType,
+ types: *mut *mut BNWARPType,
count: usize,
) -> bool {
- let view = unsafe { BinaryView::from_raw(view) };
-
let arc_container = ManuallyDrop::new(Arc::from_raw(container));
let Ok(mut container) = arc_container.write() else {
return false;
@@ -405,10 +389,11 @@ pub unsafe extern "C" fn BNWARPContainerAddTypes(
let source = unsafe { *source };
let types_ptr = std::slice::from_raw_parts(types, count);
+ // TODO: We have to clone the objects here to make the type checker happy.
+ // TODO: See about avoiding this later.
let types: Vec<_> = types_ptr
.iter()
- .map(|&t| Type::from_raw(t))
- .map(|ty| from_bn_type(&view, &ty, 255))
+ .map(|&t| unsafe { ManuallyDrop::new(Arc::from_raw(t)).as_ref().clone() })
.collect();
container.add_types(&source, &types).is_ok()
}
@@ -476,7 +461,7 @@ pub unsafe extern "C" fn BNWARPContainerGetSourcesWithFunctionGUID(
let guid = unsafe { *guid };
- // NOTE: Leak the sources to be freed by BNWARPFreeSourceList
+ // NOTE: Leak the sources to be freed by BNWARPFreeUUIDList
let boxed_sources: Box<[_]> = container
.sources_with_function_guid(&target, &guid)
.unwrap_or_default()
@@ -498,7 +483,7 @@ pub unsafe extern "C" fn BNWARPContainerGetSourcesWithTypeGUID(
let guid = unsafe { *guid };
- // NOTE: Leak the sources to be freed by BNWARPFreeSourceList
+ // NOTE: Leak the sources to be freed by BNWARPFreeUUIDList
let boxed_sources: Box<[_]> = container
.sources_with_type_guid(&guid)
.unwrap_or_default()
@@ -538,22 +523,17 @@ pub unsafe extern "C" fn BNWARPContainerGetFunctionsWithGUID(
Box::into_raw(raw_boxed_functions) as *mut *mut BNWARPFunction
}
-// TODO: Swap arch to Target?
#[no_mangle]
pub unsafe extern "C" fn BNWARPContainerGetTypeWithGUID(
- arch: *mut BNArchitecture,
container: *mut BNWARPContainer,
source: *const BNWARPSource,
guid: *const BNWARPTypeGUID,
-) -> *mut BNType {
+) -> *mut BNWARPType {
let arc_container = ManuallyDrop::new(Arc::from_raw(container));
let Ok(container) = arc_container.read() else {
return std::ptr::null_mut();
};
- // NOTE: to convert the type, we must have an architecture.
- let arch = CoreArchitecture::from_raw(arch);
-
let source = unsafe { *source };
let guid = unsafe { *guid };
@@ -561,9 +541,10 @@ pub unsafe extern "C" fn BNWARPContainerGetTypeWithGUID(
let Some(ty) = container.type_with_guid(&source, &guid).unwrap_or_default() else {
return std::ptr::null_mut();
};
- let function_type = to_bn_type(Some(arch), &ty);
- // NOTE: The type ref has been pre-incremented for the caller.
- unsafe { Ref::into_raw(function_type) }.handle
+
+ let arc_ty = Arc::new(ty);
+ // NOTE: Freed by BNWARPFreeTypeReference
+ Arc::into_raw(arc_ty) as *mut BNWARPType
}
#[no_mangle]
diff --git a/plugins/warp/src/plugin/ffi/file.rs b/plugins/warp/src/plugin/ffi/file.rs
index 951b5eb2..3abfe5aa 100644
--- a/plugins/warp/src/plugin/ffi/file.rs
+++ b/plugins/warp/src/plugin/ffi/file.rs
@@ -1,11 +1,43 @@
+use crate::plugin::ffi::{BNWARPFunction, BNWARPTarget, BNWARPType};
+use binaryninja::data_buffer::DataBuffer;
+use binaryninjacore_sys::BNDataBuffer;
use std::ffi::c_char;
+use std::mem::ManuallyDrop;
use std::sync::Arc;
-use warp::WarpFile;
+use warp::chunk::ChunkKind;
+use warp::{WarpFile, WarpFileHeader};
-pub type BNWARPFile = WarpFile<'static>;
+/// A [`WarpFile`] wrapper that uses reference counting to manage its chunks lifetime.
+///
+/// Used primarily when passing to the C FFI so that chunks do not need to have a complicated lifetime.
+pub struct RcWarpFile {
+ pub header: WarpFileHeader,
+ pub chunks: Vec<Arc<warp::chunk::Chunk<'static>>>,
+}
+
+impl From<WarpFile<'static>> for RcWarpFile {
+ fn from(file: WarpFile<'static>) -> Self {
+ let chunks = file.chunks.into_iter().map(|c| Arc::new(c)).collect();
+ Self {
+ header: file.header,
+ chunks,
+ }
+ }
+}
+
+impl From<&RcWarpFile> for WarpFile<'static> {
+ fn from(rc_file: &RcWarpFile) -> Self {
+ let chunks = rc_file.chunks.iter().map(|c| (**c).clone()).collect();
+ Self {
+ header: rc_file.header.clone(),
+ chunks,
+ }
+ }
+}
+
+pub type BNWARPFile = RcWarpFile;
-// TODO: At some point we may want to expose chunks directly. For now we will just enumerate all of them.
-// pub type BNWARPChunk = warp::chunk::Chunk<'static>;
+pub type BNWARPChunk = warp::chunk::Chunk<'static>;
// TODO: From bytes as well.
#[no_mangle]
@@ -20,7 +52,78 @@ pub unsafe extern "C" fn BNWARPNewFileFromPath(path: *mut c_char) -> *mut BNWARP
let Some(file) = WarpFile::from_owned_bytes(bytes) else {
return std::ptr::null_mut();
};
- Arc::into_raw(Arc::new(file)) as *mut BNWARPFile
+ let rc_file = RcWarpFile::from(file);
+ Arc::into_raw(Arc::new(rc_file)) as *mut BNWARPFile
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFileGetChunks(
+ file: *mut BNWARPFile,
+ count: *mut usize,
+) -> *mut *mut BNWARPChunk {
+ let arc_file = ManuallyDrop::new(Arc::from_raw(file));
+ *count = arc_file.chunks.len();
+ let boxed_chunks: Box<[_]> = arc_file
+ .chunks
+ .iter()
+ .map(|c| Arc::into_raw(c.clone()))
+ .collect();
+ Box::into_raw(boxed_chunks) as *mut *mut BNWARPChunk
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFileToDataBuffer(file: *mut BNWARPFile) -> *mut BNDataBuffer {
+ let arc_file = ManuallyDrop::new(Arc::from_raw(file));
+ let warp_file = WarpFile::from(arc_file.as_ref());
+ let buffer = DataBuffer::new(&warp_file.to_bytes());
+ buffer.into_raw()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPChunkGetTarget(chunk: *const BNWARPChunk) -> *mut BNWARPTarget {
+ let chunk = unsafe { &*chunk };
+ let chunk_target = chunk.header.target.clone();
+ Arc::into_raw(Arc::new(chunk_target)) as *mut BNWARPTarget
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPChunkGetFunctions(
+ chunk: *const BNWARPChunk,
+ count: *mut usize,
+) -> *mut *mut BNWARPFunction {
+ let chunk = unsafe { &*chunk };
+ match &chunk.kind {
+ ChunkKind::Signature(sc) => {
+ let boxed_funcs: Box<[_]> = sc
+ .functions()
+ .into_iter()
+ .map(|f| Arc::into_raw(Arc::new(f.clone())))
+ .collect();
+ *count = boxed_funcs.len();
+ Box::into_raw(boxed_funcs) as *mut *mut BNWARPFunction
+ }
+ ChunkKind::Type(_) => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPChunkGetTypes(
+ chunk: *const BNWARPChunk,
+ count: *mut usize,
+) -> *mut *mut BNWARPType {
+ let chunk = unsafe { &*chunk };
+ match &chunk.kind {
+ ChunkKind::Signature(_) => std::ptr::null_mut(),
+ ChunkKind::Type(tc) => {
+ let boxed_types: Box<[_]> = tc
+ .types()
+ .into_iter()
+ .map(|t| Arc::into_raw(Arc::new(t.ty.clone())))
+ .collect();
+ *count = boxed_types.len();
+ Box::into_raw(boxed_types) as *mut *mut BNWARPType
+ }
+ }
}
#[no_mangle]
@@ -36,3 +139,27 @@ pub unsafe extern "C" fn BNWARPFreeFileReference(file: *mut BNWARPFile) {
}
Arc::decrement_strong_count(file);
}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewChunkReference(chunk: *mut BNWARPChunk) -> *mut BNWARPChunk {
+ Arc::increment_strong_count(chunk);
+ chunk
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeChunkReference(chunk: *mut BNWARPChunk) {
+ if chunk.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(chunk);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeChunkList(chunks: *mut *mut BNWARPChunk, count: usize) {
+ let chunks_ptr = std::ptr::slice_from_raw_parts_mut(chunks, count);
+ let chunks = unsafe { Box::from_raw(chunks_ptr) };
+ for chunk in chunks {
+ // NOTE: The chunks themselves should also be arc.
+ BNWARPFreeChunkReference(chunk);
+ }
+}
diff --git a/plugins/warp/src/plugin/ffi/function.rs b/plugins/warp/src/plugin/ffi/function.rs
index 138c0a7b..2eff9e4a 100644
--- a/plugins/warp/src/plugin/ffi/function.rs
+++ b/plugins/warp/src/plugin/ffi/function.rs
@@ -1,11 +1,11 @@
use crate::build_function;
use crate::cache::{insert_cached_function_match, try_cached_function_match};
-use crate::convert::{to_bn_symbol_at_address, to_bn_type};
-use crate::plugin::ffi::{BNWARPConstraint, BNWARPFunction, BNWARPFunctionGUID};
+use crate::convert::to_bn_symbol_at_address;
+use crate::plugin::ffi::{BNWARPConstraint, BNWARPFunction, BNWARPFunctionGUID, BNWARPType};
use binaryninja::function::Function;
use binaryninja::rc::Ref;
use binaryninja::string::BnString;
-use binaryninjacore_sys::{BNFunction, BNSymbol, BNType};
+use binaryninjacore_sys::{BNFunction, BNSymbol};
use std::ffi::c_char;
use std::mem::ManuallyDrop;
use std::sync::Arc;
@@ -111,19 +111,14 @@ pub unsafe extern "C" fn BNWARPFunctionGetSymbolName(function: *mut BNWARPFuncti
}
#[no_mangle]
-pub unsafe extern "C" fn BNWARPFunctionGetType(
- function: *mut BNWARPFunction,
- analysis_function: *mut BNFunction,
-) -> *mut BNType {
- let analysis_function = Function::from_raw(analysis_function);
+pub unsafe extern "C" fn BNWARPFunctionGetType(function: *mut BNWARPFunction) -> *mut BNWARPType {
// We do not own function so we should not drop.
let function = ManuallyDrop::new(Arc::from_raw(function));
match &function.ty {
Some(func_ty) => {
- let arch = analysis_function.arch();
- let function_type = to_bn_type(Some(arch), func_ty);
- // NOTE: The type ref has been pre-incremented for the caller.
- unsafe { Ref::into_raw(function_type) }.handle
+ let arc_func_ty = Arc::new(func_ty.clone());
+ // NOTE: Freed by BNWARPFreeTypeReference
+ Arc::into_raw(arc_func_ty) as *mut BNWARPType
}
None => std::ptr::null_mut(),
}
diff --git a/plugins/warp/src/plugin/ffi/processor.rs b/plugins/warp/src/plugin/ffi/processor.rs
new file mode 100644
index 00000000..83b47f5a
--- /dev/null
+++ b/plugins/warp/src/plugin/ffi/processor.rs
@@ -0,0 +1,187 @@
+use crate::plugin::ffi::file::{BNWARPFile, RcWarpFile};
+use crate::processor::{
+ IncludedDataField, IncludedFunctionsField, ProcessingFileState, WarpFileProcessor,
+ WarpFileProcessorEntry,
+};
+use binaryninja::binary_view::BinaryView;
+use binaryninja::project::file::ProjectFile;
+use binaryninja::project::Project;
+use binaryninjacore_sys::{BNBinaryView, BNProject, BNProjectFile};
+use std::ffi::{c_char, CStr, CString};
+use std::mem::ManuallyDrop;
+use std::path::PathBuf;
+use std::ptr::NonNull;
+use std::sync::Arc;
+use warp::chunk::CompressionType;
+
+pub type BNWARPProcessor = WarpFileProcessor;
+
+#[repr(C)]
+pub struct BNWARPProcessorState {
+ cancelled: bool,
+ unprocessed_files_count: usize,
+ processed_files_count: usize,
+ analyzing_files: *mut *mut c_char,
+ analyzing_files_count: usize,
+ processing_files: *mut *mut c_char,
+ processing_files_count: usize,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewProcessor(
+ included_data: IncludedDataField,
+ included_functions: IncludedFunctionsField,
+ worker_count: usize,
+) -> *mut BNWARPProcessor {
+ let processor = WarpFileProcessor::new()
+ .with_file_data(included_data)
+ .with_included_functions(included_functions)
+ .with_compression_type(CompressionType::Zstd)
+ .with_entry_worker_count(worker_count);
+ Box::into_raw(Box::new(processor))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPProcessorAddPath(
+ processor: *mut BNWARPProcessor,
+ path: *const c_char,
+) {
+ let mut processor = ManuallyDrop::new(Box::from_raw(processor));
+ let path_cstr = unsafe { CStr::from_ptr(path) };
+ let path = PathBuf::from(path_cstr.to_str().unwrap());
+ // TODO: Not thread safe.
+ processor.add_entry(WarpFileProcessorEntry::Path(path));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPProcessorAddProject(
+ processor: *mut BNWARPProcessor,
+ project: *mut BNProject,
+) {
+ let mut processor = ManuallyDrop::new(Box::from_raw(processor));
+ let project = Project::from_raw(NonNull::new(project).unwrap());
+ // TODO: Not thread safe.
+ processor.add_entry(WarpFileProcessorEntry::Project(project.to_owned()));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPProcessorAddProjectFile(
+ processor: *mut BNWARPProcessor,
+ project_file: *mut BNProjectFile,
+) {
+ let mut processor = ManuallyDrop::new(Box::from_raw(processor));
+ let project_file = ProjectFile::from_raw(NonNull::new(project_file).unwrap());
+ // TODO: Not thread safe.
+ processor.add_entry(WarpFileProcessorEntry::ProjectFile(project_file.to_owned()));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPProcessorAddBinaryView(
+ processor: *mut BNWARPProcessor,
+ view: *mut BNBinaryView,
+) {
+ let mut processor = ManuallyDrop::new(Box::from_raw(processor));
+ let view = BinaryView::from_raw(view);
+ // TODO: Not thread safe.
+ processor.add_entry(WarpFileProcessorEntry::BinaryView(view.to_owned()));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPProcessorStart(processor: *mut BNWARPProcessor) -> *mut BNWARPFile {
+ let mut processor = ManuallyDrop::new(Box::from_raw(processor));
+ // TODO: Not thread safe.
+ match processor.process_entries() {
+ Ok(file) => {
+ let rc_file = RcWarpFile::from(file);
+ Arc::into_raw(Arc::new(rc_file)) as *mut BNWARPFile
+ }
+ Err(_) => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPProcessorIsCancelled(processor: *mut BNWARPProcessor) -> bool {
+ let processor = ManuallyDrop::new(Box::from_raw(processor));
+ processor
+ .state()
+ .cancelled
+ .load(std::sync::atomic::Ordering::Relaxed)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPProcessorCancel(processor: *mut BNWARPProcessor) {
+ let processor = ManuallyDrop::new(Box::from_raw(processor));
+ processor
+ .state()
+ .cancelled
+ .store(true, std::sync::atomic::Ordering::Relaxed)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPProcessorGetState(
+ processor: *mut BNWARPProcessor,
+) -> BNWARPProcessorState {
+ let processor = ManuallyDrop::new(Box::from_raw(processor));
+ let processor_state = processor.state();
+
+ let mut unprocessed_files_count = 0;
+ let mut processed_files_count = 0;
+ let mut analyzing_files = Vec::new();
+ let mut processing_files = Vec::new();
+
+ for file_state in &processor_state.files {
+ match file_state.value() {
+ ProcessingFileState::Unprocessed => unprocessed_files_count += 1,
+ ProcessingFileState::Analyzing => analyzing_files.push(file_state.key().clone()),
+ ProcessingFileState::Processing => processing_files.push(file_state.key().clone()),
+ ProcessingFileState::Processed => processed_files_count += 1,
+ }
+ }
+
+ let raw_analyzing_files: Box<[_]> = analyzing_files
+ .into_iter()
+ .map(|p| CString::new(p.to_str().unwrap()).unwrap().into_raw())
+ .collect();
+ let raw_analyzing_files_count = raw_analyzing_files.len();
+ let raw_analyzing_files_ptr = Box::into_raw(raw_analyzing_files);
+
+ let raw_processing_files: Box<[_]> = processing_files
+ .into_iter()
+ .map(|p| CString::new(p.to_str().unwrap()).unwrap().into_raw())
+ .collect();
+ let raw_processing_files_count = raw_processing_files.len();
+ let raw_processing_files_ptr = Box::into_raw(raw_processing_files);
+
+ BNWARPProcessorState {
+ cancelled: processor_state
+ .cancelled
+ .load(std::sync::atomic::Ordering::Relaxed),
+ unprocessed_files_count,
+ processed_files_count,
+ analyzing_files: raw_analyzing_files_ptr as *mut *mut c_char,
+ analyzing_files_count: raw_analyzing_files_count,
+ processing_files: raw_processing_files_ptr as *mut *mut c_char,
+ processing_files_count: raw_processing_files_count,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeProcessor(processor: *mut BNWARPProcessor) {
+ let _ = Box::from_raw(processor);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeProcessorState(state: BNWARPProcessorState) {
+ let a_files_ptr =
+ std::ptr::slice_from_raw_parts_mut(state.analyzing_files, state.analyzing_files_count);
+ let a_files_boxed = unsafe { Box::from_raw(a_files_ptr) };
+ for path in a_files_boxed.iter() {
+ let _ = CString::from_raw(*path);
+ }
+ let p_files_ptr =
+ std::ptr::slice_from_raw_parts_mut(state.processing_files, state.processing_files_count);
+ let p_files_boxed = unsafe { Box::from_raw(p_files_ptr) };
+ for path in p_files_boxed.iter() {
+ let _ = CString::from_raw(*path);
+ }
+}
diff --git a/plugins/warp/src/plugin/ffi/ty.rs b/plugins/warp/src/plugin/ffi/ty.rs
new file mode 100644
index 00000000..6a16fdab
--- /dev/null
+++ b/plugins/warp/src/plugin/ffi/ty.rs
@@ -0,0 +1,75 @@
+use crate::convert::{from_bn_type, to_bn_type};
+use crate::plugin::ffi::BNWARPType;
+use binaryninja::architecture::CoreArchitecture;
+use binaryninja::binary_view::BinaryView;
+use binaryninja::file_metadata::FileMetadata;
+use binaryninja::rc::Ref as BnRef;
+use binaryninja::string::BnString;
+use binaryninja::types::Type;
+use binaryninjacore_sys::{BNArchitecture, BNType};
+use std::ffi::c_char;
+use std::mem::ManuallyDrop;
+use std::sync::Arc;
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPGetType(
+ analysis_type: *mut BNType,
+ confidence: u8,
+) -> *mut BNWARPType {
+ let analysis_type = Type::from_raw(analysis_type);
+ // TODO: This will leak a bunch of memory, but we need to remove the view requirement anyways.
+ let binary_view = BinaryView::from_data(&FileMetadata::new(), &[]);
+ let ty = from_bn_type(&binary_view, &analysis_type, confidence);
+ Arc::into_raw(Arc::new(ty)) as *mut BNWARPType
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPTypeGetName(ty: *mut BNWARPType) -> *mut c_char {
+ let ty = ManuallyDrop::new(Arc::from_raw(ty));
+ match ty.name.as_deref() {
+ Some(name) => BnString::into_raw(BnString::new(name)),
+ None => std::ptr::null_mut(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPTypeGetConfidence(ty: *mut BNWARPType) -> u8 {
+ let ty = ManuallyDrop::new(Arc::from_raw(ty));
+ ty.confidence
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPTypeGetAnalysisType(
+ arch: *mut BNArchitecture,
+ ty: *mut BNWARPType,
+) -> *mut BNType {
+ let ty = ManuallyDrop::new(Arc::from_raw(ty));
+ let analysis_ty = match arch.is_null() {
+ true => to_bn_type::<CoreArchitecture>(None, &ty),
+ false => to_bn_type(Some(CoreArchitecture::from_raw(arch)), &ty),
+ };
+ BnRef::into_raw(analysis_ty).handle
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPNewTypeReference(ty: *mut BNWARPType) -> *mut BNWARPType {
+ Arc::increment_strong_count(ty);
+ ty
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeTypeReference(ty: *mut BNWARPType) {
+ if ty.is_null() {
+ return;
+ }
+ Arc::decrement_strong_count(ty);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn BNWARPFreeTypeList(types: *mut *mut BNWARPType, count: usize) {
+ let types_ptr = std::ptr::slice_from_raw_parts_mut(types, count);
+ let types = unsafe { Box::from_raw(types_ptr) };
+ for ty in types {
+ unsafe { BNWARPFreeTypeReference(ty) };
+ }
+}
diff --git a/plugins/warp/src/plugin/file.rs b/plugins/warp/src/plugin/file.rs
deleted file mode 100644
index cd5fb626..00000000
--- a/plugins/warp/src/plugin/file.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-use crate::report::ReportGenerator;
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::command::Command;
-
-pub struct ShowFileReport;
-
-impl Command for ShowFileReport {
- fn action(&self, view: &BinaryView) {
- let view = view.to_owned();
- std::thread::spawn(move || {
- let Some(path) =
- binaryninja::interaction::get_open_filename_input("Select file to show", "*.warp")
- else {
- return;
- };
-
- let Ok(bytes) = std::fs::read(&path) else {
- tracing::error!("Failed to read file: {:?}", path);
- return;
- };
-
- let Some(file) = warp::WarpFile::from_bytes(&bytes) else {
- tracing::error!("Failed to parse file: {:?}", path);
- return;
- };
-
- let report_generator = ReportGenerator::new();
- if let Some(html_string) = report_generator.html_report(&file) {
- view.show_html_report(
- &format!("WARP File: {}", path.to_string_lossy()),
- html_string.as_str(),
- "",
- );
- }
- });
- }
-
- fn valid(&self, _view: &BinaryView) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/function.rs b/plugins/warp/src/plugin/function.rs
index 36db7d55..908bd0db 100644
--- a/plugins/warp/src/plugin/function.rs
+++ b/plugins/warp/src/plugin/function.rs
@@ -1,18 +1,10 @@
-use crate::cache::{
- cached_function_guid, insert_cached_function_match, try_cached_function_guid,
- try_cached_function_match,
-};
+use crate::cache::{insert_cached_function_match, try_cached_function_match};
use crate::{
get_warp_ignore_tag_type, get_warp_include_tag_type, IGNORE_TAG_NAME, INCLUDE_TAG_NAME,
};
-use binaryninja::background_task::BackgroundTask;
-use binaryninja::binary_view::{BinaryView, BinaryViewExt};
-use binaryninja::command::{Command, FunctionCommand};
+use binaryninja::binary_view::BinaryView;
+use binaryninja::command::FunctionCommand;
use binaryninja::function::{Function, FunctionUpdateType};
-use binaryninja::rc::Guard;
-use rayon::iter::ParallelIterator;
-use std::thread;
-use warp::signature::function::FunctionGUID;
pub struct IncludeFunction;
@@ -101,91 +93,3 @@ impl FunctionCommand for RemoveFunction {
try_cached_function_match(func).is_some()
}
}
-
-pub struct CopyFunctionGUID;
-
-impl FunctionCommand for CopyFunctionGUID {
- fn action(&self, _view: &BinaryView, func: &Function) {
- let Some(guid) = cached_function_guid(func, || func.lifted_il().ok()) else {
- tracing::error!("Could not get guid for copied function");
- return;
- };
- tracing::info!(
- "Function GUID for {:?}... {}",
- func.symbol().short_name(),
- guid
- );
- if let Ok(mut clipboard) = arboard::Clipboard::new() {
- let _ = clipboard.set_text(guid.to_string());
- }
- }
-
- fn valid(&self, _view: &BinaryView, _func: &Function) -> bool {
- true
- }
-}
-
-pub struct FindFunctionFromGUID;
-
-impl Command for FindFunctionFromGUID {
- fn action(&self, view: &BinaryView) {
- let Some(guid_str) = binaryninja::interaction::get_text_line_input(
- "Function GUID",
- "Find Function from GUID",
- ) else {
- return;
- };
-
- let Ok(searched_guid) = guid_str.parse::<FunctionGUID>() else {
- tracing::error!("Failed to parse function guid... {}", guid_str);
- return;
- };
-
- tracing::info!("Searching functions for GUID... {}", searched_guid);
- let funcs = view.functions();
- let view = view.to_owned();
- thread::spawn(move || {
- let background_task = BackgroundTask::new(
- &format!("Searching functions for GUID... {}", searched_guid),
- false,
- );
-
- // Only run this for functions which have already generated a GUID.
- let matched: Vec<Guard<Function>> = funcs
- .par_iter()
- .filter(|func| {
- try_cached_function_guid(func).is_some_and(|guid| guid == searched_guid)
- })
- .collect();
-
- if matched.is_empty() {
- tracing::info!("No matches found for GUID... {}", searched_guid);
- } else {
- for func in &matched {
- // Also navigate the user, as that is probably what they want.
- if matched.len() == 1 {
- let current_view = view.file().current_view();
- if view
- .file()
- .navigate_to(&current_view, func.start())
- .is_err()
- {
- tracing::error!(
- "Failed to navigate to found function 0x{:0x} in view {}",
- func.start(),
- current_view
- );
- }
- }
- tracing::info!("Match found at function... 0x{:0x}", func.start());
- }
- }
-
- background_task.finish();
- });
- }
-
- fn valid(&self, _view: &BinaryView) -> bool {
- true
- }
-}
diff --git a/plugins/warp/src/plugin/project.rs b/plugins/warp/src/plugin/project.rs
deleted file mode 100644
index b6081c1f..00000000
--- a/plugins/warp/src/plugin/project.rs
+++ /dev/null
@@ -1,286 +0,0 @@
-use crate::processor::{
- new_processing_state_background_thread, CompressionTypeField, FileDataKindField,
- FileFilterField, ProcessingFileState, RequestAnalysisField, WarpFileProcessor,
-};
-use crate::report::{ReportGenerator, ReportKindField};
-use binaryninja::background_task::BackgroundTask;
-use binaryninja::command::ProjectCommand;
-use binaryninja::interaction::{Form, FormInputField};
-use binaryninja::project::folder::ProjectFolder;
-use binaryninja::project::Project;
-use binaryninja::rc::Ref;
-use binaryninja::worker_thread::{set_worker_thread_count, worker_thread_count};
-use rayon::ThreadPoolBuilder;
-use regex::Regex;
-use std::path::Path;
-use std::thread;
-use std::time::Instant;
-use warp::WarpFile;
-
-pub struct CreateSignaturesForm {
- form: Form,
-}
-
-impl CreateSignaturesForm {
- pub fn new(_project: &Project) -> CreateSignaturesForm {
- let mut form = Form::new("Create Signature File");
- form.add_field(Self::file_data_field());
- form.add_field(Self::file_filter_field());
- form.add_field(Self::generated_report_field());
- form.add_field(Self::compression_type_field());
- form.add_field(Self::save_individual_files_field());
- form.add_field(Self::skip_existing_warp_files_field());
- form.add_field(Self::request_analysis_field());
- form.add_field(Self::processing_thread_count_field());
- Self { form }
- }
-
- pub fn file_data_field() -> FormInputField {
- FileDataKindField::default().to_field()
- }
-
- pub fn file_data_kind(&self) -> FileDataKindField {
- FileDataKindField::from_form(&self.form).unwrap_or_default()
- }
-
- pub fn file_filter_field() -> FormInputField {
- FileFilterField::to_field()
- }
-
- pub fn file_filter(&self) -> Option<Result<Regex, regex::Error>> {
- FileFilterField::from_form(&self.form)
- }
-
- pub fn generated_report_field() -> FormInputField {
- ReportKindField::default().to_field()
- }
-
- pub fn generated_report_kind(&self) -> ReportKindField {
- ReportKindField::from_form(&self.form).unwrap_or_default()
- }
-
- pub fn compression_type_field() -> FormInputField {
- CompressionTypeField::default().to_field()
- }
-
- pub fn compression_type(&self) -> CompressionTypeField {
- CompressionTypeField::from_form(&self.form).unwrap_or_default()
- }
-
- pub fn save_individual_files_field() -> FormInputField {
- FormInputField::Checkbox {
- prompt: "Save individual files".to_string(),
- default: None,
- value: false,
- }
- }
-
- pub fn save_individual_files(&self) -> bool {
- let field = self.form.get_field_with_name("Save individual files");
- let field_value = field.and_then(|f| f.try_value_int()).unwrap_or(0);
- match field_value {
- 1 => true,
- _ => false,
- }
- }
-
- pub fn skip_existing_warp_files_field() -> FormInputField {
- FormInputField::Checkbox {
- prompt: "Skip existing WARP files".to_string(),
- default: Some(true),
- value: false,
- }
- }
-
- pub fn skip_existing_warp_files(&self) -> bool {
- let field = self.form.get_field_with_name("Skip existing WARP files");
- let field_value = field.and_then(|f| f.try_value_int()).unwrap_or(0);
- match field_value {
- 1 => true,
- _ => false,
- }
- }
-
- pub fn request_analysis_field() -> FormInputField {
- RequestAnalysisField::default().to_field()
- }
-
- pub fn request_analysis(&self) -> RequestAnalysisField {
- RequestAnalysisField::from_form(&self.form).unwrap_or_default()
- }
-
- pub fn processing_thread_count_field() -> FormInputField {
- let default = rayon::current_num_threads();
- FormInputField::Integer {
- prompt: "Processing threads".to_string(),
- default: Some(default as i64),
- value: 0,
- }
- }
-
- pub fn processing_thread_count(&self) -> usize {
- let field = self.form.get_field_with_name("Processing threads");
- let worker_thread_count = worker_thread_count();
- field
- .and_then(|f| f.try_value_int())
- .unwrap_or(worker_thread_count as i64)
- .abs() as usize
- }
-
- pub fn prompt(&mut self) -> bool {
- self.form.prompt()
- }
-}
-
-pub struct CreateSignatures;
-
-impl CreateSignatures {
- pub fn execute(project: Ref<Project>) {
- let mut form = CreateSignaturesForm::new(&project);
- if !form.prompt() {
- return;
- }
- let file_data_kind = form.file_data_kind();
- let report_kind = form.generated_report_kind();
- let compression_type = form.compression_type();
- let save_individual_files = form.save_individual_files();
- let skip_existing_warp_files = form.skip_existing_warp_files();
- let request_analysis = form.request_analysis();
- let processing_thread_count = form.processing_thread_count();
-
- // Save the warp file to the project.
- let save_warp_file = move |project: &Project,
- folder: Option<&ProjectFolder>,
- name: &str,
- warp_file: &WarpFile| {
- if project
- .create_file(&warp_file.to_bytes(), folder, name, "")
- .is_err()
- {
- tracing::error!("Failed to create project file!");
- }
-
- let report = ReportGenerator::new();
- if let Some(generated) = report.report(&report_kind, &warp_file) {
- let ext = report.report_extension(&report_kind).unwrap_or_default();
- let file_name = format!("{}_report.{}", name, ext);
- if project
- .create_file(&generated.into_bytes(), folder, &file_name, "Warp file")
- .is_err()
- {
- tracing::error!("Failed to create project file!");
- }
- }
- };
-
- // Optional callback for saving off the individual project files.
- let callback_project = project.clone();
- let save_individual_files_cb = move |path: &Path, file: &WarpFile| {
- if file.chunks.is_empty() {
- tracing::debug!("Skipping empty file: {}", path.display());
- return;
- }
- // The path returned will be the one on disk, so we will go and grab the project for it.
- let Some(project_file) = callback_project.file_by_path(path) else {
- tracing::error!("Failed to find project file for path: {}", path.display());
- return;
- };
- let project_file = project_file.to_owned();
- let file_name = format!("{}.warp", project_file.name());
- let project_folder = project_file.folder();
- save_warp_file(
- &callback_project,
- project_folder.as_deref(),
- &file_name,
- file,
- );
- };
-
- let mut processor = WarpFileProcessor::new()
- .with_file_data(file_data_kind)
- .with_compression_type(compression_type)
- .with_skip_warp_files(skip_existing_warp_files)
- .with_request_analysis(request_analysis == RequestAnalysisField::Yes);
-
- if save_individual_files {
- processor = processor.with_processed_file_callback(save_individual_files_cb);
- }
-
- // Construct the user-supplied file filter. This will filter files in the project only, files
- // in an archive will be considered a part of the archive file.
- if let Some(filter) = form.file_filter() {
- match filter {
- Ok(f) => {
- processor = processor.with_file_filter(f);
- }
- Err(err) => {
- tracing::error!("Failed to parse file filter: {}", err);
- tracing::error!(
- "Consider using a substring instead of a glob pattern, e.g. *.exe => exe"
- );
- return;
- }
- }
- }
-
- // This thread will show the state in a background task.
- let background_task = BackgroundTask::new("Processing started...", true);
- new_processing_state_background_thread(background_task.clone(), processor.state());
-
- let Ok(thread) = ThreadPoolBuilder::new()
- .num_threads(processing_thread_count)
- .build()
- else {
- tracing::error!("Failed to create processing thread pool!");
- return;
- };
-
- // We have to bump the number of worker threads up so that view destruction's and analysis
- // does not halt, using a multiple of three seems good. This is only temporary.
- let previous_worker_thread_count = worker_thread_count();
- let upgraded_thread_count = previous_worker_thread_count * 3;
- if upgraded_thread_count > previous_worker_thread_count {
- tracing::info!(
- "Setting worker thread count to {} for the duration of processing...",
- upgraded_thread_count
- );
- set_worker_thread_count(upgraded_thread_count);
- }
-
- let start = Instant::now();
- thread.scope(|_| match processor.process_project(&project) {
- Ok(warp_file) => {
- save_warp_file(&project, None, "generated.warp", &warp_file);
- }
- Err(e) => {
- tracing::error!("Failed to process project: {}", e);
- }
- });
-
- let processed_file_count = processor
- .state()
- .files_with_state(ProcessingFileState::Processed);
- tracing::info!(
- "Processing {} project files took: {:?}",
- processed_file_count,
- start.elapsed()
- );
- // Reset the worker thread count to the user specified; either way it will not persist.
- set_worker_thread_count(previous_worker_thread_count);
- // Tells the processing state thread to finish.
- background_task.finish();
- }
-}
-
-impl ProjectCommand for CreateSignatures {
- fn action(&self, project: &Project) {
- let project = project.to_owned();
- thread::spawn(move || {
- CreateSignatures::execute(project);
- });
- }
-
- fn valid(&self, _view: &Project) -> bool {
- true
- }
-}