mirror of
https://github.com/encounter/ghidra-cli.git
synced 2026-07-10 03:18:56 -07:00
refactor: Remove HeadlessExecutor in favor of daemon-only architecture
HeadlessExecutor spawned a new Ghidra process per command, which was slow. The daemon maintains a persistent connection and is now the only way to execute queries. Changes: - Delete src/ghidra/headless.rs entirely - Remove dead handler functions that used HeadlessExecutor: - handle_query, handle_function_command, handle_decompile, - handle_decompile_impl, handle_strings_command, - handle_memory_command, handle_dump_command, handle_summary - Update handle_quick to inform user about daemon requirement - Query::execute replaced with Query::process_results for post-processing of daemon results All query commands (function list, decompile, strings, memory, etc.) now require the daemon to be running. The CLI prints clear instructions when daemon is not available. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
3db0af7a3c
commit
0470caf015
@@ -1,195 +0,0 @@
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use serde_json::Value as JsonValue;
|
||||
use crate::error::{GhidraError, Result};
|
||||
use super::GhidraClient;
|
||||
use super::scripts;
|
||||
|
||||
#[deprecated(
|
||||
since = "0.2.0",
|
||||
note = "Use daemon for query operations. HeadlessExecutor spawns a new Ghidra process per command, which is slow. The daemon maintains a persistent connection."
|
||||
)]
|
||||
pub struct HeadlessExecutor<'a> {
|
||||
client: &'a GhidraClient,
|
||||
}
|
||||
|
||||
impl<'a> HeadlessExecutor<'a> {
|
||||
pub fn new(client: &'a GhidraClient) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
pub fn execute_script(
|
||||
&self,
|
||||
project_name: &str,
|
||||
program_name: &str,
|
||||
script_content: &str,
|
||||
script_name: &str,
|
||||
args: &[String],
|
||||
) -> Result<JsonValue> {
|
||||
// Save script to disk
|
||||
let scripts_dir = self.get_scripts_dir()?;
|
||||
let script_path = scripts::save_script(script_name, script_content, &scripts_dir)?;
|
||||
|
||||
// Execute script
|
||||
let output = self.run_ghidra_script(project_name, program_name, &script_path, args)?;
|
||||
|
||||
// Parse JSON output
|
||||
let json: JsonValue = serde_json::from_str(&output)
|
||||
.map_err(|e| GhidraError::ExecutionFailed(format!("Failed to parse script output: {}", e)))?;
|
||||
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
fn run_ghidra_script(
|
||||
&self,
|
||||
project_name: &str,
|
||||
program_name: &str,
|
||||
script_path: &Path,
|
||||
args: &[String],
|
||||
) -> Result<String> {
|
||||
let project_path = self.client.get_project_path(project_name);
|
||||
let headless = self.client.get_headless_script();
|
||||
|
||||
let mut cmd = Command::new(&headless);
|
||||
|
||||
cmd.arg(project_path.to_str().unwrap())
|
||||
.arg(project_name)
|
||||
.arg("-process")
|
||||
.arg(program_name)
|
||||
.arg("-noanalysis")
|
||||
.arg("-scriptPath")
|
||||
.arg(script_path.parent().unwrap().to_str().unwrap())
|
||||
.arg("-postScript")
|
||||
.arg(script_path.file_name().unwrap().to_str().unwrap());
|
||||
|
||||
for arg in args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
// Capture output
|
||||
let output = cmd.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(GhidraError::ExecutionFailed(format!("Script execution failed: {}", stderr)));
|
||||
}
|
||||
|
||||
// Extract JSON from output (Ghidra adds some logging we need to skip)
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let json_output = self.extract_json_from_output(&stdout)?;
|
||||
|
||||
Ok(json_output)
|
||||
}
|
||||
|
||||
fn extract_json_from_output(&self, output: &str) -> Result<String> {
|
||||
// Use marker-based extraction for reliable JSON parsing
|
||||
const START_MARKER: &str = "---GHIDRA_CLI_START---";
|
||||
const END_MARKER: &str = "---GHIDRA_CLI_END---";
|
||||
|
||||
let start = output.find(START_MARKER)
|
||||
.ok_or_else(|| GhidraError::ExecutionFailed(
|
||||
"Missing start marker in script output".to_string()
|
||||
))?
|
||||
+ START_MARKER.len();
|
||||
|
||||
let end = output.find(END_MARKER)
|
||||
.ok_or_else(|| GhidraError::ExecutionFailed(
|
||||
"Missing end marker in script output".to_string()
|
||||
))?;
|
||||
|
||||
Ok(output[start..end].trim().to_string())
|
||||
}
|
||||
|
||||
fn get_scripts_dir(&self) -> Result<std::path::PathBuf> {
|
||||
let config_dir = dirs::config_dir()
|
||||
.ok_or_else(|| GhidraError::ConfigError("Could not determine config directory".to_string()))?;
|
||||
|
||||
let scripts_dir = config_dir.join("ghidra-cli").join("scripts");
|
||||
|
||||
if !scripts_dir.exists() {
|
||||
std::fs::create_dir_all(&scripts_dir)?;
|
||||
}
|
||||
|
||||
Ok(scripts_dir)
|
||||
}
|
||||
|
||||
pub fn list_functions(&self, project_name: &str, program_name: &str) -> Result<JsonValue> {
|
||||
self.execute_script(
|
||||
project_name,
|
||||
program_name,
|
||||
scripts::get_list_functions_script(),
|
||||
"list_functions",
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn decompile_function(&self, project_name: &str, program_name: &str, address: &str) -> Result<JsonValue> {
|
||||
self.execute_script(
|
||||
project_name,
|
||||
program_name,
|
||||
scripts::get_decompile_function_script(),
|
||||
"decompile_function",
|
||||
&[address.to_string()],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn list_strings(&self, project_name: &str, program_name: &str) -> Result<JsonValue> {
|
||||
self.execute_script(
|
||||
project_name,
|
||||
program_name,
|
||||
scripts::get_list_strings_script(),
|
||||
"list_strings",
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn list_imports(&self, project_name: &str, program_name: &str) -> Result<JsonValue> {
|
||||
self.execute_script(
|
||||
project_name,
|
||||
program_name,
|
||||
scripts::get_list_imports_script(),
|
||||
"list_imports",
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn list_exports(&self, project_name: &str, program_name: &str) -> Result<JsonValue> {
|
||||
self.execute_script(
|
||||
project_name,
|
||||
program_name,
|
||||
scripts::get_list_exports_script(),
|
||||
"list_exports",
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_memory_map(&self, project_name: &str, program_name: &str) -> Result<JsonValue> {
|
||||
self.execute_script(
|
||||
project_name,
|
||||
program_name,
|
||||
scripts::get_memory_map_script(),
|
||||
"memory_map",
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_program_info(&self, project_name: &str, program_name: &str) -> Result<JsonValue> {
|
||||
self.execute_script(
|
||||
project_name,
|
||||
program_name,
|
||||
scripts::get_program_info_script(),
|
||||
"program_info",
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_xrefs_to(&self, project_name: &str, program_name: &str, address: &str) -> Result<JsonValue> {
|
||||
self.execute_script(
|
||||
project_name,
|
||||
program_name,
|
||||
scripts::get_xrefs_to_script(),
|
||||
"xrefs_to",
|
||||
&[address.to_string()],
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod bridge;
|
||||
pub mod headless;
|
||||
pub mod data;
|
||||
pub mod scripts;
|
||||
pub mod setup;
|
||||
|
||||
+21
-332
@@ -9,15 +9,13 @@ mod ipc;
|
||||
mod query;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands, DaemonCommands, QueryArgs, QueryOptions, SetupArgs};
|
||||
use cli::{Cli, Commands, DaemonCommands, SetupArgs};
|
||||
use config::Config;
|
||||
use daemon::process::{get_data_dir, get_running_daemon_info, ensure_not_running};
|
||||
use daemon::rpc as daemon_rpc;
|
||||
use daemon::{DaemonConfig, run as run_daemon};
|
||||
use error::{GhidraError, Result};
|
||||
use format::OutputFormat;
|
||||
use ghidra::GhidraClient;
|
||||
use query::{Query, DataType, FieldSelector, SortKey};
|
||||
use std::path::PathBuf;
|
||||
use tracing::info;
|
||||
|
||||
@@ -52,22 +50,27 @@ async fn main() {
|
||||
|
||||
fn run(cli: Cli) -> anyhow::Result<()> {
|
||||
match cli.command {
|
||||
Commands::Query(args) => handle_query(args),
|
||||
// Non-daemon commands
|
||||
Commands::Init => handle_init(),
|
||||
Commands::Doctor => handle_doctor(),
|
||||
Commands::Version => handle_version(),
|
||||
Commands::Import(args) => handle_import(args),
|
||||
Commands::Analyze(args) => handle_analyze(args),
|
||||
Commands::Summary(args) => handle_summary(args.options),
|
||||
Commands::Quick(args) => handle_quick(args),
|
||||
Commands::Function(cmd) => handle_function_command(cmd),
|
||||
Commands::Strings(cmd) => handle_strings_command(cmd),
|
||||
Commands::Memory(cmd) => handle_memory_command(cmd),
|
||||
Commands::Dump(cmd) => handle_dump_command(cmd),
|
||||
Commands::Decompile(args) => handle_decompile(args),
|
||||
Commands::Config(cmd) => handle_config_command(cmd),
|
||||
Commands::SetDefault(args) => handle_set_default(args),
|
||||
Commands::Project(args) => handle_project_command(args.command),
|
||||
// Commands requiring daemon are handled by run_with_daemon_check
|
||||
Commands::Query(_)
|
||||
| Commands::Summary(_)
|
||||
| Commands::Function(_)
|
||||
| Commands::Strings(_)
|
||||
| Commands::Memory(_)
|
||||
| Commands::Dump(_)
|
||||
| Commands::Decompile(_)
|
||||
| Commands::XRef(_) => {
|
||||
unreachable!("Daemon-required commands should go through run_with_daemon_check")
|
||||
}
|
||||
_ => {
|
||||
println!("Command not yet implemented");
|
||||
Ok(())
|
||||
@@ -538,280 +541,6 @@ fn daemonize_windows(daemon_config: DaemonConfig, port: Option<u16>) -> anyhow::
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_query(args: QueryArgs) -> anyhow::Result<()> {
|
||||
let config = Config::load()?;
|
||||
let client = GhidraClient::new(config.clone())?;
|
||||
|
||||
// Resolve program and project
|
||||
let program = resolve_program(&args.program, &config)?;
|
||||
let project = resolve_project(&args.project, &config, &program)?;
|
||||
|
||||
// Parse data type
|
||||
let data_type = DataType::from_str(&args.data_type)?;
|
||||
|
||||
// Build query
|
||||
let mut query = Query::new(data_type);
|
||||
|
||||
// Add filter if provided
|
||||
if let Some(filter_str) = args.filter {
|
||||
let filter = filter::Filter::parse(&filter_str)?;
|
||||
query = query.with_filter(filter);
|
||||
}
|
||||
|
||||
// Add format
|
||||
let format = if args.json {
|
||||
OutputFormat::Json
|
||||
} else if let Some(fmt) = args.format {
|
||||
OutputFormat::from_str(&fmt)?
|
||||
} else {
|
||||
// Auto-detect based on TTY
|
||||
format::auto_detect_format(atty::is(atty::Stream::Stdout))
|
||||
};
|
||||
query = query.with_format(format);
|
||||
|
||||
// Add limit
|
||||
if let Some(limit) = args.limit {
|
||||
query = query.with_limit(limit);
|
||||
}
|
||||
|
||||
// Add offset
|
||||
if let Some(offset) = args.offset {
|
||||
query = query.with_offset(offset);
|
||||
}
|
||||
|
||||
// Add sort
|
||||
if let Some(sort_str) = args.sort {
|
||||
let sort_keys = SortKey::parse(&sort_str);
|
||||
query.sort = Some(sort_keys);
|
||||
}
|
||||
|
||||
// Add field selection
|
||||
if let Some(fields_str) = args.fields {
|
||||
let selector = FieldSelector::parse(&fields_str)?;
|
||||
query.fields = Some(selector);
|
||||
}
|
||||
|
||||
// Count only?
|
||||
if args.count {
|
||||
query = query.count_only();
|
||||
}
|
||||
|
||||
// Execute query
|
||||
let result = query.execute(&client, &project, &program)?;
|
||||
println!("{}", result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_function_command(cmd: cli::FunctionCommands) -> anyhow::Result<()> {
|
||||
use cli::FunctionCommands;
|
||||
|
||||
match cmd {
|
||||
FunctionCommands::List(opts) => {
|
||||
let args = QueryArgs {
|
||||
data_type: "functions".to_string(),
|
||||
program: opts.program,
|
||||
project: opts.project,
|
||||
filter: opts.filter,
|
||||
fields: opts.fields,
|
||||
format: opts.format,
|
||||
limit: opts.limit,
|
||||
offset: opts.offset,
|
||||
sort: opts.sort,
|
||||
count: opts.count,
|
||||
json: opts.json,
|
||||
};
|
||||
handle_query(args)
|
||||
}
|
||||
FunctionCommands::Decompile(args) => {
|
||||
// Decompile specific function
|
||||
let query_args = QueryArgs {
|
||||
data_type: "functions".to_string(),
|
||||
program: args.options.program,
|
||||
project: args.options.project,
|
||||
filter: Some(format!("address={} OR name={}", args.target, args.target)),
|
||||
fields: args.options.fields,
|
||||
format: args.options.format,
|
||||
limit: Some(1),
|
||||
offset: None,
|
||||
sort: None,
|
||||
count: false,
|
||||
json: args.options.json,
|
||||
};
|
||||
handle_decompile_impl(args.target, query_args)
|
||||
}
|
||||
_ => {
|
||||
println!("Function subcommand not yet implemented");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_decompile(args: cli::DecompileArgs) -> anyhow::Result<()> {
|
||||
let query_args = QueryArgs {
|
||||
data_type: "functions".to_string(),
|
||||
program: args.options.program,
|
||||
project: args.options.project,
|
||||
filter: None,
|
||||
fields: None,
|
||||
format: Some("c".to_string()),
|
||||
limit: None,
|
||||
offset: None,
|
||||
sort: None,
|
||||
count: false,
|
||||
json: false,
|
||||
};
|
||||
handle_decompile_impl(args.target, query_args)
|
||||
}
|
||||
|
||||
fn handle_decompile_impl(target: String, args: QueryArgs) -> anyhow::Result<()> {
|
||||
let config = Config::load()?;
|
||||
let client = GhidraClient::new(config.clone())?;
|
||||
|
||||
let program = resolve_program(&args.program, &config)?;
|
||||
let project = resolve_project(&args.project, &config, &program)?;
|
||||
|
||||
// Use headless executor to decompile
|
||||
let executor = ghidra::headless::HeadlessExecutor::new(&client);
|
||||
let result = executor.decompile_function(&project, &program, &target)?;
|
||||
|
||||
// Format output
|
||||
if let Some(code) = result.get("code") {
|
||||
if let Some(code_str) = code.as_str() {
|
||||
println!("{}", code_str);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&result)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_strings_command(cmd: cli::StringsCommands) -> anyhow::Result<()> {
|
||||
use cli::StringsCommands;
|
||||
|
||||
match cmd {
|
||||
StringsCommands::List(opts) => {
|
||||
let args = QueryArgs {
|
||||
data_type: "strings".to_string(),
|
||||
program: opts.program,
|
||||
project: opts.project,
|
||||
filter: opts.filter,
|
||||
fields: opts.fields,
|
||||
format: opts.format,
|
||||
limit: opts.limit,
|
||||
offset: opts.offset,
|
||||
sort: opts.sort,
|
||||
count: opts.count,
|
||||
json: opts.json,
|
||||
};
|
||||
handle_query(args)
|
||||
}
|
||||
_ => {
|
||||
println!("Strings subcommand not yet implemented");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_memory_command(cmd: cli::MemoryCommands) -> anyhow::Result<()> {
|
||||
use cli::MemoryCommands;
|
||||
|
||||
match cmd {
|
||||
MemoryCommands::Map(opts) => {
|
||||
let args = QueryArgs {
|
||||
data_type: "memory".to_string(),
|
||||
program: opts.program,
|
||||
project: opts.project,
|
||||
filter: opts.filter,
|
||||
fields: opts.fields,
|
||||
format: opts.format,
|
||||
limit: opts.limit,
|
||||
offset: opts.offset,
|
||||
sort: opts.sort,
|
||||
count: opts.count,
|
||||
json: opts.json,
|
||||
};
|
||||
handle_query(args)
|
||||
}
|
||||
_ => {
|
||||
println!("Memory subcommand not yet implemented");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_dump_command(cmd: cli::DumpCommands) -> anyhow::Result<()> {
|
||||
use cli::DumpCommands;
|
||||
|
||||
match cmd {
|
||||
DumpCommands::Imports(opts) => {
|
||||
let args = QueryArgs {
|
||||
data_type: "imports".to_string(),
|
||||
program: opts.program,
|
||||
project: opts.project,
|
||||
filter: opts.filter,
|
||||
fields: opts.fields,
|
||||
format: opts.format,
|
||||
limit: opts.limit,
|
||||
offset: opts.offset,
|
||||
sort: opts.sort,
|
||||
count: opts.count,
|
||||
json: opts.json,
|
||||
};
|
||||
handle_query(args)
|
||||
}
|
||||
DumpCommands::Exports(opts) => {
|
||||
let args = QueryArgs {
|
||||
data_type: "exports".to_string(),
|
||||
program: opts.program,
|
||||
project: opts.project,
|
||||
filter: opts.filter,
|
||||
fields: opts.fields,
|
||||
format: opts.format,
|
||||
limit: opts.limit,
|
||||
offset: opts.offset,
|
||||
sort: opts.sort,
|
||||
count: opts.count,
|
||||
json: opts.json,
|
||||
};
|
||||
handle_query(args)
|
||||
}
|
||||
DumpCommands::Functions(opts) => {
|
||||
let args = QueryArgs {
|
||||
data_type: "functions".to_string(),
|
||||
program: opts.program,
|
||||
project: opts.project,
|
||||
filter: opts.filter,
|
||||
fields: opts.fields,
|
||||
format: opts.format,
|
||||
limit: opts.limit,
|
||||
offset: opts.offset,
|
||||
sort: opts.sort,
|
||||
count: opts.count,
|
||||
json: opts.json,
|
||||
};
|
||||
handle_query(args)
|
||||
}
|
||||
DumpCommands::Strings(opts) => {
|
||||
let args = QueryArgs {
|
||||
data_type: "strings".to_string(),
|
||||
program: opts.program,
|
||||
project: opts.project,
|
||||
filter: opts.filter,
|
||||
fields: opts.fields,
|
||||
format: opts.format,
|
||||
limit: opts.limit,
|
||||
offset: opts.offset,
|
||||
sort: opts.sort,
|
||||
count: opts.count,
|
||||
json: opts.json,
|
||||
};
|
||||
handle_query(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_init() -> anyhow::Result<()> {
|
||||
println!("Ghidra CLI Initialization");
|
||||
println!("========================\n");
|
||||
@@ -958,39 +687,6 @@ fn handle_analyze(args: cli::AnalyzeArgs) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_summary(opts: QueryOptions) -> anyhow::Result<()> {
|
||||
let config = Config::load()?;
|
||||
let client = GhidraClient::new(config.clone())?;
|
||||
|
||||
let program = resolve_program(&opts.program, &config)?;
|
||||
let project = resolve_project(&opts.project, &config, &program)?;
|
||||
|
||||
println!("Program Summary");
|
||||
println!("===============\n");
|
||||
|
||||
// Get program info
|
||||
let executor = ghidra::headless::HeadlessExecutor::new(&client);
|
||||
let info = executor.get_program_info(&project, &program)?;
|
||||
|
||||
if let Some(name) = info.get("name") {
|
||||
println!("Name: {}", name);
|
||||
}
|
||||
if let Some(format) = info.get("executable_format") {
|
||||
println!("Format: {}", format);
|
||||
}
|
||||
if let Some(lang) = info.get("language") {
|
||||
println!("Language: {}", lang);
|
||||
}
|
||||
if let Some(count) = info.get("function_count") {
|
||||
println!("Functions: {}", count);
|
||||
}
|
||||
if let Some(count) = info.get("instruction_count") {
|
||||
println!("Instructions: {}", count);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_quick(args: cli::QuickArgs) -> anyhow::Result<()> {
|
||||
let config = Config::load()?;
|
||||
let client = GhidraClient::new(config.clone())?;
|
||||
@@ -1008,21 +704,14 @@ fn handle_quick(args: cli::QuickArgs) -> anyhow::Result<()> {
|
||||
println!("[2/3] Running analysis...");
|
||||
client.analyze_program(&project, &program_name)?;
|
||||
|
||||
// Summary
|
||||
println!("[3/3] Generating summary...\n");
|
||||
let opts = QueryOptions {
|
||||
program: Some(program_name),
|
||||
project: Some(project),
|
||||
filter: None,
|
||||
fields: None,
|
||||
format: None,
|
||||
limit: None,
|
||||
offset: None,
|
||||
sort: None,
|
||||
count: false,
|
||||
json: false,
|
||||
};
|
||||
handle_summary(opts)?;
|
||||
// Done
|
||||
println!("[3/3] Done!\n");
|
||||
println!("Analysis complete. To query the binary, start the daemon:");
|
||||
println!(" ghidra daemon start --project {} --program {}", project, program_name);
|
||||
println!("\nThen run queries like:");
|
||||
println!(" ghidra function list");
|
||||
println!(" ghidra decompile main");
|
||||
println!(" ghidra summary");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+9
-32
@@ -1,9 +1,9 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use serde_json::Value as JsonValue;
|
||||
use crate::error::{GhidraError, Result};
|
||||
use crate::filter::Filter;
|
||||
use crate::format::{OutputFormat, Formatter, DefaultFormatter};
|
||||
use crate::ghidra::GhidraClient;
|
||||
use crate::ghidra::headless::HeadlessExecutor;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DataType {
|
||||
@@ -97,39 +97,16 @@ impl Query {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn execute(&self, client: &GhidraClient, project: &str, program: &str) -> Result<String> {
|
||||
let executor = HeadlessExecutor::new(client);
|
||||
|
||||
// Fetch data based on type
|
||||
let data = match self.data_type {
|
||||
DataType::Functions => executor.list_functions(project, program)?,
|
||||
DataType::Strings => executor.list_strings(project, program)?,
|
||||
DataType::Imports => executor.list_imports(project, program)?,
|
||||
DataType::Exports => executor.list_exports(project, program)?,
|
||||
DataType::Memory => executor.get_memory_map(project, program)?,
|
||||
_ => {
|
||||
return Err(GhidraError::Other(format!(
|
||||
"Data type {:?} not yet implemented",
|
||||
self.data_type
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
// Data should be an array
|
||||
let data_array = match data {
|
||||
JsonValue::Array(arr) => arr,
|
||||
_ => {
|
||||
return Err(GhidraError::ExecutionFailed(
|
||||
"Expected array from Ghidra script".to_string()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
/// Process query results from pre-fetched data.
|
||||
///
|
||||
/// Note: Data fetching is now handled by the daemon via IPC.
|
||||
/// This method only handles filtering, field selection, sorting, and formatting.
|
||||
pub fn process_results(&self, data: Vec<JsonValue>) -> Result<String> {
|
||||
// Apply filter
|
||||
let filtered = if let Some(filter) = &self.filter {
|
||||
self.apply_filter(&data_array, filter)?
|
||||
self.apply_filter(&data, filter)?
|
||||
} else {
|
||||
data_array
|
||||
data
|
||||
};
|
||||
|
||||
// Apply field selection
|
||||
|
||||
Reference in New Issue
Block a user