Skip to content

Commit

Permalink
Implement module dumping for resymc
Browse files Browse the repository at this point in the history
  • Loading branch information
ergrelet committed Mar 6, 2024
1 parent 7a00f41 commit 252ba98
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 49 deletions.
48 changes: 48 additions & 0 deletions resym_core/tests/module_dumping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use std::path::Path;

use resym_core::{pdb_file::PdbFile, pdb_types::PrimitiveReconstructionFlavor};

const TEST_PDB_FILE_PATH: &str = "tests/data/test.pdb";
const TEST_MODULE_INDEX: usize = 27;

#[test]
fn test_module_dumping_by_index_portable() {
test_module_dumping_by_index_internal(
"module_dumping_by_index_portable",
TEST_MODULE_INDEX,
PrimitiveReconstructionFlavor::Portable,
);
}

#[test]
fn test_module_dumping_by_index_microsoft() {
test_module_dumping_by_index_internal(
"module_dumping_by_index_microsoft",
TEST_MODULE_INDEX,
PrimitiveReconstructionFlavor::Microsoft,
);
}

#[test]
fn test_module_dumping_by_index_raw() {
test_module_dumping_by_index_internal(
"module_dumping_by_index_raw",
TEST_MODULE_INDEX,
PrimitiveReconstructionFlavor::Raw,
);
}

fn test_module_dumping_by_index_internal(
snapshot_name: &str,
module_index: usize,
primitives_flavor: PrimitiveReconstructionFlavor,
) {
let mut pdb_file =
PdbFile::load_from_file(Path::new(TEST_PDB_FILE_PATH)).expect("load test.pdb");

let module_dump = pdb_file
.reconstruct_module_by_index(module_index, primitives_flavor)
.unwrap_or_else(|_| panic!("module dumping"));

insta::assert_snapshot!(snapshot_name, module_dump);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: resym_core/tests/module_dumping.rs
expression: module_dump
---
using namespace std;
using PUWSTR_C = const WCHAR*;
using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3;
PULONGLONG (__local_stdio_scanf_options)(); // CodeSize=8
ULONGLONG_OptionsStorage;
VOID (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: resym_core/tests/module_dumping.rs
expression: module_dump
---
using namespace std;
using PUWSTR_C = const wchar_t*;
using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3;
uint64_t* (__local_stdio_scanf_options)(); // CodeSize=8
uint64_t_OptionsStorage;
void (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: resym_core/tests/module_dumping.rs
expression: module_dump
---
using namespace std;
using PUWSTR_C = const wchar_t*;
using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3;
unsigned __int64* (__local_stdio_scanf_options)(); // CodeSize=8
unsigned __int64_OptionsStorage;
void (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69
248 changes: 199 additions & 49 deletions resymc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,21 @@ fn main() -> Result<()> {
use_regex,
output_file_path,
),
ResymOptions::DumpModule {
pdb_path,
module_id,
output_file_path,
primitive_types_flavor,
print_header,
highlight_syntax,
} => app.dump_module_command(
pdb_path,
module_id,
primitive_types_flavor.unwrap_or(PrimitiveReconstructionFlavor::Portable),
print_header,
highlight_syntax,
output_file_path,
),
}
}

Expand Down Expand Up @@ -219,6 +234,24 @@ enum ResymOptions {
#[structopt(short = "r", long)]
use_regex: bool,
},
/// Dump module from a given PDB file
DumpModule {
/// Path to the PDB file
pdb_path: PathBuf,
/// ID of the module to dump
module_id: usize,
/// Path of the output file
output_file_path: Option<PathBuf>,
/// Representation of primitive types
#[structopt(short = "f", long)]
primitive_types_flavor: Option<PrimitiveReconstructionFlavor>,
/// Print header
#[structopt(short = "h", long)]
print_header: bool,
/// Highlight C++ output
#[structopt(short = "H", long)]
highlight_syntax: bool,
},
}

/// Struct that represents our CLI application.
Expand Down Expand Up @@ -338,26 +371,23 @@ impl ResymcApp {
if let FrontendCommand::ReconstructTypeResult(reconstructed_type_result) =
self.frontend_controller.rx_ui.recv()?
{
match reconstructed_type_result {
Err(err) => Err(err.into()),
Ok(reconstructed_type) => {
// Dump output
if let Some(output_file_path) = output_file_path {
let mut output_file = File::create(output_file_path)?;
output_file.write_all(reconstructed_type.as_bytes())?;
} else if highlight_syntax {
let theme = CodeTheme::default();
if let Some(colorized_reconstructed_type) =
highlight_code(&theme, &reconstructed_type, None)
{
println!("{colorized_reconstructed_type}");
}
} else {
println!("{reconstructed_type}");
}
Ok(())
let reconstructed_type = reconstructed_type_result?;
// Dump output
if let Some(output_file_path) = output_file_path {
let mut output_file = File::create(output_file_path)?;
output_file.write_all(reconstructed_type.as_bytes())?;
} else if highlight_syntax {
let theme = CodeTheme::default();
if let Some(colorized_reconstructed_type) =
highlight_code(&theme, &reconstructed_type, None)
{
println!("{colorized_reconstructed_type}");
}
} else {
println!("{reconstructed_type}");
}

Ok(())
} else {
Err(anyhow!("Invalid response received from the backend?"))
}
Expand Down Expand Up @@ -426,36 +456,33 @@ impl ResymcApp {
if let FrontendCommand::DiffResult(reconstructed_type_diff_result) =
self.frontend_controller.rx_ui.recv()?
{
match reconstructed_type_diff_result {
Err(err) => Err(err.into()),
Ok(reconstructed_type_diff) => {
// Dump output
if let Some(output_file_path) = output_file_path {
let mut output_file = File::create(output_file_path)?;
output_file.write_all(reconstructed_type_diff.data.as_bytes())?;
} else if highlight_syntax {
let theme = CodeTheme::default();
let line_descriptions =
reconstructed_type_diff
.metadata
.iter()
.fold(vec![], |mut acc, e| {
acc.push(e.1);
acc
});
if let Some(colorized_reconstructed_type) = highlight_code(
&theme,
&reconstructed_type_diff.data,
Some(line_descriptions),
) {
println!("{colorized_reconstructed_type}");
}
} else {
println!("{}", reconstructed_type_diff.data);
}
Ok(())
let reconstructed_type_diff = reconstructed_type_diff_result?;
// Dump output
if let Some(output_file_path) = output_file_path {
let mut output_file = File::create(output_file_path)?;
output_file.write_all(reconstructed_type_diff.data.as_bytes())?;
} else if highlight_syntax {
let theme = CodeTheme::default();
let line_descriptions =
reconstructed_type_diff
.metadata
.iter()
.fold(vec![], |mut acc, e| {
acc.push(e.1);
acc
});
if let Some(colorized_reconstructed_type) = highlight_code(
&theme,
&reconstructed_type_diff.data,
Some(line_descriptions),
) {
println!("{colorized_reconstructed_type}");
}
} else {
println!("{}", reconstructed_type_diff.data);
}

Ok(())
} else {
Err(anyhow!("Invalid response received from the backend?"))
}
Expand Down Expand Up @@ -489,11 +516,11 @@ impl ResymcApp {
use_regex,
))?;
// Wait for the backend to finish listing modules
if let FrontendCommand::UpdateModuleList(module_list) =
if let FrontendCommand::UpdateModuleList(module_list_result) =
self.frontend_controller.rx_ui.recv()?
{
// Dump output
let module_list = module_list?;
let module_list = module_list_result?;
if let Some(output_file_path) = output_file_path {
let mut output_file = File::create(output_file_path)?;
for (module_path, module_id) in module_list {
Expand All @@ -504,6 +531,61 @@ impl ResymcApp {
println!("Mod {module_id:04} | '{module_path}'");
}
}

Ok(())
} else {
Err(anyhow!("Invalid response received from the backend?"))
}
}

fn dump_module_command(
&self,
pdb_path: PathBuf,
module_id: usize,
primitive_types_flavor: PrimitiveReconstructionFlavor,
print_header: bool,
highlight_syntax: bool,
output_file_path: Option<PathBuf>,
) -> Result<()> {
// Request the backend to load the PDB
self.backend
.send_command(BackendCommand::LoadPDBFromPath(PDB_MAIN_SLOT, pdb_path))?;
// Wait for the backend to finish loading the PDB
if let FrontendCommand::LoadPDBResult(result) = self.frontend_controller.rx_ui.recv()? {
if let Err(err) = result {
return Err(anyhow!("Failed to load PDB: {}", err));
}
} else {
return Err(anyhow!("Invalid response received from the backend?"));
}

// Queue a request for the backend to reconstruct the given module
self.backend
.send_command(BackendCommand::ReconstructModuleByIndex(
PDB_MAIN_SLOT,
module_id,
primitive_types_flavor,
print_header,
))?;
// Wait for the backend to finish filtering types
if let FrontendCommand::ReconstructModuleResult(reconstructed_module) =
self.frontend_controller.rx_ui.recv()?
{
let reconstructed_module = reconstructed_module?;
// Dump output
if let Some(output_file_path) = output_file_path {
let mut output_file = File::create(output_file_path)?;
output_file.write_all(reconstructed_module.as_bytes())?;
} else if highlight_syntax {
let theme = CodeTheme::default();
if let Some(colorized_reconstructed_type) =
highlight_code(&theme, &reconstructed_module, None)
{
println!("{colorized_reconstructed_type}");
}
} else {
println!("{reconstructed_module}");
}
Ok(())
} else {
Err(anyhow!("Invalid response received from the backend?"))
Expand Down Expand Up @@ -610,7 +692,7 @@ mod tests {
let app = ResymcApp::new().expect("ResymcApp creation failed");
let pdb_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(TEST_PDB_FILE_PATH);
let tmp_dir =
TempDir::new("list_types_command_file_successful").expect("TempDir creation failed");
TempDir::new("list_modules_command_file_successful").expect("TempDir creation failed");
let output_path = tmp_dir.path().join("output.txt");
// The command should succeed
assert!(app
Expand All @@ -633,4 +715,72 @@ mod tests {
)
);
}

#[test]
fn dump_module_command_invalid_pdb_path() {
let app = ResymcApp::new().expect("ResymcApp creation failed");
let pdb_path = PathBuf::new();
// The command should fail
assert!(app
.dump_module_command(
pdb_path,
9, // exe_main.obj
PrimitiveReconstructionFlavor::Microsoft,
false,
false,
None
)
.is_err());
}

#[test]
fn dump_module_command_stdio_successful() {
let app = ResymcApp::new().expect("ResymcApp creation failed");
let pdb_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(TEST_PDB_FILE_PATH);
// The command should succeed
assert!(app
.dump_module_command(
pdb_path,
9, // exe_main.obj
PrimitiveReconstructionFlavor::Microsoft,
false,
false,
None
)
.is_ok());
}

#[test]
fn dump_module_command_file_successful() {
let app = ResymcApp::new().expect("ResymcApp creation failed");
let pdb_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(TEST_PDB_FILE_PATH);
let tmp_dir =
TempDir::new("dump_module_command_file_successful").expect("TempDir creation failed");
let output_path = tmp_dir.path().join("output.txt");
// The command should succeed
assert!(app
.dump_module_command(
pdb_path,
27, // default_local_stdio_options.obj
PrimitiveReconstructionFlavor::Portable,
false,
false,
Some(output_path.clone()),
)
.is_ok());

// Check output file's content
let output = fs::read_to_string(output_path).expect("Failed to read output file");
assert_eq!(
output,
concat!(
"using namespace std;\n",
"using PUWSTR_C = const wchar_t*;\n",
"using TP_CALLBACK_ENVIRON_V3 = _TP_CALLBACK_ENVIRON_V3;\n",
"uint64_t* (__local_stdio_scanf_options)(); // CodeSize=8\n",
"uint64_t_OptionsStorage;\n",
"void (__scrt_initialize_default_local_stdio_options)(); // CodeSize=69\n",
)
);
}
}

0 comments on commit 252ba98

Please sign in to comment.