Skip to content

Commit 4b236da

Browse files
committed
[BNTL] Misc improvements
1 parent 20407e2 commit 4b236da

File tree

6 files changed

+150
-118
lines changed

6 files changed

+150
-118
lines changed

plugins/bntl_utils/src/command/diff.rs

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,26 @@
11
use crate::command::OutputDirectoryField;
22
use crate::diff::TILDiff;
3+
use crate::helper::path_to_type_libraries;
34
use binaryninja::background_task::BackgroundTask;
45
use binaryninja::binary_view::BinaryView;
56
use binaryninja::command::Command;
67
use binaryninja::interaction::{Form, FormInputField};
7-
use binaryninja::types::TypeLibrary;
88
use std::path::PathBuf;
99
use std::thread;
1010

1111
pub struct InputFileAField;
1212

1313
impl InputFileAField {
1414
pub fn field() -> FormInputField {
15-
FormInputField::OpenFileName {
16-
prompt: "Library A".to_string(),
17-
// TODO: This is called extension but is really a filter.
18-
extension: Some("*.bntl".to_string()),
15+
FormInputField::DirectoryName {
16+
prompt: "Directory A".to_string(),
1917
default: None,
2018
value: None,
2119
}
2220
}
2321

2422
pub fn from_form(form: &Form) -> Option<PathBuf> {
25-
let field = form.get_field_with_name("Library A")?;
23+
let field = form.get_field_with_name("Directory A")?;
2624
let field_value = field.try_value_string()?;
2725
Some(PathBuf::from(field_value))
2826
}
@@ -32,17 +30,15 @@ pub struct InputFileBField;
3230

3331
impl InputFileBField {
3432
pub fn field() -> FormInputField {
35-
FormInputField::OpenFileName {
36-
prompt: "Library B".to_string(),
37-
// TODO: This is called extension but is really a filter.
38-
extension: Some("*.bntl".to_string()),
33+
FormInputField::DirectoryName {
34+
prompt: "Directory B".to_string(),
3935
default: None,
4036
value: None,
4137
}
4238
}
4339

4440
pub fn from_form(form: &Form) -> Option<PathBuf> {
45-
let field = form.get_field_with_name("Library B")?;
41+
let field = form.get_field_with_name("Directory B")?;
4642
let field_value = field.try_value_string()?;
4743
Some(PathBuf::from(field_value))
4844
}
@@ -63,30 +59,39 @@ impl Diff {
6359
let b_path = InputFileBField::from_form(&form).unwrap();
6460
let output_dir = OutputDirectoryField::from_form(&form).unwrap();
6561

66-
let _bg_task = BackgroundTask::new("Diffing type libraries...", false).enter();
67-
let Some(type_lib_a) = TypeLibrary::load_from_file(&a_path) else {
68-
tracing::error!("Failed to load type library: {}", a_path.display());
69-
return;
70-
};
71-
let Some(type_lib_b) = TypeLibrary::load_from_file(&b_path) else {
72-
tracing::error!("Failed to load type library: {}", b_path.display());
73-
return;
74-
};
62+
let bg_task = BackgroundTask::new("Diffing type libraries...", true).enter();
63+
64+
let b_libraries = path_to_type_libraries(&a_path);
65+
let a_libraries = path_to_type_libraries(&b_path);
66+
// TODO: Make this parallel
67+
for a_lib in &a_libraries {
68+
for b_lib in &b_libraries {
69+
if bg_task.is_cancelled() {
70+
return;
71+
}
7572

76-
let diff_result = match TILDiff::new().diff((&a_path, &type_lib_a), (&b_path, &type_lib_b))
77-
{
78-
Ok(diff_result) => diff_result,
79-
Err(err) => {
80-
tracing::error!("Failed to diff type libraries: {}", err);
81-
return;
73+
if a_lib.name() != b_lib.name() {
74+
continue;
75+
}
76+
77+
bg_task.set_progress_text(&format!("Diffing '{}'...", a_lib.name()));
78+
let diff_result = match TILDiff::new().diff_with_dependencies(
79+
(&a_lib, a_libraries.clone()),
80+
(&b_lib, b_libraries.clone()),
81+
) {
82+
Ok(diff_result) => diff_result,
83+
Err(err) => {
84+
tracing::error!("Failed to diff type libraries: {}", err);
85+
continue;
86+
}
87+
};
88+
tracing::info!("Similarity Ratio: {}", diff_result.ratio);
89+
90+
let output_path = output_dir.join(a_lib.name()).with_extension("diff");
91+
std::fs::write(&output_path, diff_result.diff).unwrap();
92+
tracing::info!("Diff written to: {}", output_path.display());
8293
}
83-
};
84-
tracing::info!("Similarity Ratio: {}", diff_result.ratio);
85-
let output_path = output_dir
86-
.join(type_lib_a.dependency_name())
87-
.with_extension("diff");
88-
std::fs::write(&output_path, diff_result.diff).unwrap();
89-
tracing::info!("Diff written to: {}", output_path.display());
94+
}
9095
}
9196
}
9297

plugins/bntl_utils/src/command/dump.rs

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,59 @@
1-
use crate::command::{InputFileField, OutputDirectoryField};
1+
use crate::command::{InputDirectoryField, OutputDirectoryField};
22
use crate::dump::TILDump;
33
use crate::helper::path_to_type_libraries;
4+
use binaryninja::background_task::BackgroundTask;
45
use binaryninja::binary_view::BinaryView;
56
use binaryninja::command::Command;
67
use binaryninja::interaction::Form;
7-
use binaryninja::types::TypeLibrary;
88

99
pub struct Dump;
1010

11-
impl Command for Dump {
12-
// TODO: We need a command type that does not require a binary view.
13-
fn action(&self, _view: &BinaryView) {
11+
impl Dump {
12+
pub fn execute() {
1413
let mut form = Form::new("Dump to C Header");
1514
// TODO: The choice to select what to include?
16-
form.add_field(InputFileField::field());
15+
form.add_field(InputDirectoryField::field());
1716
form.add_field(OutputDirectoryField::field());
1817
if !form.prompt() {
1918
return;
2019
}
2120
let output_dir = OutputDirectoryField::from_form(&form).unwrap();
22-
let input_path = InputFileField::from_form(&form).unwrap();
21+
let input_path = InputDirectoryField::from_form(&form).unwrap();
2322

24-
let type_lib = match TypeLibrary::load_from_file(&input_path) {
25-
Some(type_lib) => type_lib,
26-
None => {
27-
tracing::error!("Failed to load type library from {}", input_path.display());
28-
return;
29-
}
30-
};
23+
let bg_task = BackgroundTask::new("Dumping type libraries...", true).enter();
3124

32-
// TODO: Currently we collect input path dependencies from the platform and the parent directory.
33-
let dependencies = path_to_type_libraries(input_path.parent().unwrap());
34-
let dump = match TILDump::new().with_type_libs(dependencies).dump(&type_lib) {
35-
Ok(dump) => dump,
36-
Err(err) => {
37-
tracing::error!("Failed to dump type library: {}", err);
25+
let type_libraries = path_to_type_libraries(&input_path);
26+
for type_lib in &type_libraries {
27+
if bg_task.is_cancelled() {
3828
return;
3929
}
40-
};
30+
bg_task.set_progress_text(&format!("Dumping '{}'...", type_lib.name()));
31+
let dump = match TILDump::new()
32+
.with_type_libs(type_libraries.clone())
33+
.dump(&type_lib)
34+
{
35+
Ok(dump) => dump,
36+
Err(err) => {
37+
tracing::error!("Failed to dump type library: {}", err);
38+
return;
39+
}
40+
};
4141

42-
let output_path = output_dir.join(format!("{}.h", type_lib.name()));
43-
if let Err(e) = std::fs::write(&output_path, dump) {
44-
tracing::error!("Failed to write dump to {}: {}", output_path.display(), e);
42+
let output_path = output_dir.join(format!("{}.h", type_lib.name()));
43+
if let Err(e) = std::fs::write(&output_path, dump) {
44+
tracing::error!("Failed to write dump to {}: {}", output_path.display(), e);
45+
}
46+
tracing::info!("Dump written to {}", output_path.display());
4547
}
46-
tracing::info!("Dump written to {}", output_path.display());
48+
}
49+
}
50+
51+
impl Command for Dump {
52+
// TODO: We need a command type that does not require a binary view.
53+
fn action(&self, _view: &BinaryView) {
54+
std::thread::spawn(move || {
55+
Dump::execute();
56+
});
4757
}
4858

4959
fn valid(&self, _view: &BinaryView) -> bool {

plugins/bntl_utils/src/command/validate.rs

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,79 @@
1+
use crate::command::{InputDirectoryField, OutputDirectoryField};
12
use crate::helper::path_to_type_libraries;
23
use crate::validate::TypeLibValidater;
34
use binaryninja::binary_view::{BinaryView, BinaryViewExt};
45
use binaryninja::command::Command;
5-
use binaryninja::interaction::get_open_filename_input;
6+
use binaryninja::interaction::Form;
67
use binaryninja::platform::Platform;
7-
use binaryninja::types::TypeLibrary;
88

99
pub struct Validate;
1010

11-
impl Command for Validate {
12-
fn action(&self, _view: &BinaryView) {
13-
let Some(input_path) =
14-
get_open_filename_input("Select a type library to validate", "*.bntl")
15-
else {
16-
return;
17-
};
18-
19-
let type_lib = match TypeLibrary::load_from_file(&input_path) {
20-
Some(type_lib) => type_lib,
21-
None => {
22-
tracing::error!("Failed to load type library from {}", input_path.display());
23-
return;
24-
}
25-
};
26-
27-
// Type libraries should always have at least one platform associated with them.
28-
if type_lib.platform_names().is_empty() {
29-
tracing::error!("Type library {} has no platforms!", input_path.display());
11+
impl Validate {
12+
pub fn execute() {
13+
let mut form = Form::new("Validate Type Libraries");
14+
form.add_field(InputDirectoryField::field());
15+
form.add_field(OutputDirectoryField::field());
16+
if !form.prompt() {
3017
return;
3118
}
19+
let output_dir = OutputDirectoryField::from_form(&form).unwrap();
20+
let input_path = InputDirectoryField::from_form(&form).unwrap();
3221

33-
// TODO: Currently we collect input path dependencies from the platform and the parent directory.
34-
let dependencies = path_to_type_libraries(input_path.parent().unwrap());
35-
36-
let validator = TypeLibValidater::new().with_type_libraries(dependencies);
37-
// Validate for every platform so that we can find issues in lesser used platforms.
38-
for platform_name in &type_lib.platform_names() {
39-
let Some(platform) = Platform::by_name(platform_name) else {
40-
tracing::error!("Failed to find platform with name {}", platform_name);
41-
continue;
42-
};
43-
let results = validator
44-
.clone()
45-
.with_platform(&platform)
46-
.validate(&type_lib);
47-
if results.issues.is_empty() {
48-
tracing::info!(
49-
"No issues found for type library {} on platform {}",
50-
type_lib.name(),
51-
platform_name
52-
);
22+
let type_libraries = path_to_type_libraries(&input_path);
23+
// TODO: Run this in parallel.
24+
for type_lib in &type_libraries {
25+
// Type libraries should always have at least one platform associated with them.
26+
if type_lib.platform_names().is_empty() {
27+
tracing::error!("Type library {} has no platforms!", input_path.display());
5328
continue;
5429
}
55-
let rendered = match results.render_report() {
56-
Ok(rendered) => rendered,
57-
Err(err) => {
58-
tracing::error!("Failed to render validation report: {}", err);
30+
31+
// TODO: Currently we collect input path dependencies from the platform and the parent directory.
32+
let validator = TypeLibValidater::new().with_type_libraries(type_libraries.clone());
33+
// Validate for every platform so that we can find issues in lesser used platforms.
34+
for platform_name in &type_lib.platform_names() {
35+
let Some(platform) = Platform::by_name(platform_name) else {
36+
tracing::error!("Failed to find platform with name {}", platform_name);
37+
continue;
38+
};
39+
let results = validator
40+
.clone()
41+
.with_platform(&platform)
42+
.validate(&type_lib);
43+
if results.issues.is_empty() {
44+
tracing::info!(
45+
"No issues found for type library {} on platform {}",
46+
type_lib.name(),
47+
platform_name
48+
);
5949
continue;
6050
}
61-
};
62-
let out_path = input_path.with_extension(format!("{}.html", platform_name));
63-
let out_name = format!("{} ({})", type_lib.name(), platform_name);
64-
_view.show_html_report(&out_name, &rendered, "");
65-
if let Err(e) = std::fs::write(out_path, rendered) {
66-
tracing::error!(
67-
"Failed to write validation report to {}: {}",
68-
input_path.display(),
69-
e
70-
);
51+
let rendered = match results.render_report() {
52+
Ok(rendered) => rendered,
53+
Err(err) => {
54+
tracing::error!("Failed to render validation report: {}", err);
55+
continue;
56+
}
57+
};
58+
let out_path = output_dir.with_extension(format!("{}.html", platform_name));
59+
if let Err(e) = std::fs::write(out_path, rendered) {
60+
tracing::error!(
61+
"Failed to write validation report to {}: {}",
62+
output_dir.display(),
63+
e
64+
);
65+
}
7166
}
7267
}
7368
}
69+
}
70+
71+
impl Command for Validate {
72+
fn action(&self, _view: &BinaryView) {
73+
std::thread::spawn(move || {
74+
Validate::execute();
75+
});
76+
}
7477

7578
fn valid(&self, _view: &BinaryView) -> bool {
7679
true

plugins/bntl_utils/src/diff.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::dump::TILDump;
22
use crate::helper::path_to_type_libraries;
3+
use binaryninja::rc::Ref;
34
use binaryninja::types::TypeLibrary;
45
use similar::{Algorithm, TextDiff};
56
use std::path::{Path, PathBuf};
@@ -48,9 +49,17 @@ impl TILDiff {
4849
.parent()
4950
.ok_or_else(|| TILDiffError::InvalidPath(b_path.to_path_buf()))?;
5051

51-
let a_dependencies = path_to_type_libraries(a_parent);
52-
let b_dependencies = path_to_type_libraries(b_parent);
52+
self.diff_with_dependencies(
53+
(&a_type_lib, path_to_type_libraries(a_parent)),
54+
(&b_type_lib, path_to_type_libraries(b_parent)),
55+
)
56+
}
5357

58+
pub fn diff_with_dependencies(
59+
&self,
60+
(a_type_lib, a_dependencies): (&TypeLibrary, Vec<Ref<TypeLibrary>>),
61+
(b_type_lib, b_dependencies): (&TypeLibrary, Vec<Ref<TypeLibrary>>),
62+
) -> Result<DiffResult, TILDiffError> {
5463
let dumped_a = TILDump::new()
5564
.with_type_libs(a_dependencies)
5665
.dump(a_type_lib)
@@ -70,8 +79,8 @@ impl TILDiff {
7079
.unified_diff()
7180
.context_radius(3)
7281
.header(
73-
a_path.to_string_lossy().as_ref(),
74-
b_path.to_string_lossy().as_ref(),
82+
&format!("A/{}", a_type_lib.name()),
83+
&format!("B/{}", b_type_lib.name()),
7584
)
7685
.to_string();
7786

plugins/bntl_utils/src/process.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ impl ProcessedData {
143143
}
144144
}
145145

146+
/// Finalizes the processed data, deduplicating types and pruning empty type libraries.
147+
///
148+
/// The `default_name` should be the library name for which you want deduplicated types to be
149+
/// relocated to. This does not need to be a logical-shared library name like `mylib.dll` as it will
150+
/// be only referenced by other loaded type libraries (it cannot contain named objects).
146151
pub fn finalized(mut self, default_name: &str) -> Self {
147152
self.deduplicate_types(default_name);
148153
// TODO: Run remap.

plugins/bntl_utils/src/validate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ impl TypeLibValidater {
198198
.extend(self.validate_external_references(type_lib));
199199

200200
// TODO: This is currently disabled because it's too slow.
201-
// result.issues.extend(self.validate_source_files(type_lib));
201+
result.issues.extend(self.validate_source_files(type_lib));
202202

203203
result
204204
}

0 commit comments

Comments
 (0)