mirror of
https://github.com/encounter/ghidra-cli.git
synced 2026-07-10 03:18:56 -07:00
refactor: update CLI commands and remove deprecated quick analysis feature
This commit is contained in:
+40
-79
@@ -105,9 +105,6 @@ pub enum Commands {
|
||||
/// Set default values
|
||||
SetDefault(SetDefaultArgs),
|
||||
|
||||
/// Quick analysis (import + analyze + summary)
|
||||
Quick(QuickArgs),
|
||||
|
||||
/// Program summary
|
||||
Summary(SummaryArgs),
|
||||
|
||||
@@ -129,9 +126,46 @@ pub enum Commands {
|
||||
/// Analyze a program
|
||||
Analyze(AnalyzeArgs),
|
||||
|
||||
/// Daemon management commands
|
||||
#[command(subcommand)]
|
||||
Daemon(DaemonCommands),
|
||||
/// Start the bridge
|
||||
Start {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
/// Program name to load
|
||||
#[arg(long)]
|
||||
program: Option<String>,
|
||||
},
|
||||
|
||||
/// Stop the bridge
|
||||
Stop {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
},
|
||||
|
||||
/// Restart the bridge
|
||||
Restart {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
/// Program name to load
|
||||
#[arg(long)]
|
||||
program: Option<String>,
|
||||
},
|
||||
|
||||
/// Show bridge status
|
||||
Status {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
},
|
||||
|
||||
/// Ping the bridge
|
||||
Ping {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
},
|
||||
|
||||
/// Download and setup Ghidra automatically
|
||||
Setup(SetupArgs),
|
||||
@@ -683,13 +717,6 @@ pub struct SetDefaultArgs {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct QuickArgs {
|
||||
pub binary: String,
|
||||
#[arg(long)]
|
||||
pub project: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct SummaryArgs {
|
||||
#[command(flatten)]
|
||||
@@ -759,72 +786,6 @@ pub struct QueryOptions {
|
||||
pub json: bool,
|
||||
}
|
||||
|
||||
/// Daemon management commands
|
||||
#[derive(Subcommand, Clone, Serialize, Deserialize, Debug)]
|
||||
pub enum DaemonCommands {
|
||||
/// Start the daemon
|
||||
Start {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
|
||||
/// Program name to load
|
||||
#[arg(long)]
|
||||
program: Option<String>,
|
||||
|
||||
/// Port to listen on (default: auto-select)
|
||||
#[arg(long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Run in foreground (don't daemonize)
|
||||
#[arg(long)]
|
||||
foreground: bool,
|
||||
},
|
||||
|
||||
/// Stop the daemon
|
||||
Stop {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
},
|
||||
|
||||
/// Restart the daemon
|
||||
Restart {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
|
||||
/// Program name to load
|
||||
#[arg(long)]
|
||||
program: Option<String>,
|
||||
|
||||
/// Port to listen on (default: auto-select)
|
||||
#[arg(long)]
|
||||
port: Option<u16>,
|
||||
},
|
||||
|
||||
/// Get daemon status
|
||||
Status {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
},
|
||||
|
||||
/// Ping the daemon
|
||||
Ping {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
},
|
||||
|
||||
/// Clear the cache
|
||||
ClearCache {
|
||||
/// Project path
|
||||
#[arg(long)]
|
||||
project: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Arguments for the setup command
|
||||
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct SetupArgs {
|
||||
|
||||
@@ -21,6 +21,8 @@ pub enum BridgeStartMode {
|
||||
Import { binary_path: String },
|
||||
/// Open an existing program in the project
|
||||
Process { program_name: String },
|
||||
/// Open the project without loading a specific program
|
||||
Project,
|
||||
}
|
||||
|
||||
/// Embedded Java bridge script
|
||||
@@ -208,6 +210,9 @@ pub fn start_bridge(
|
||||
BridgeStartMode::Process { program_name } => {
|
||||
cmd.arg("-process").arg(program_name).arg("-noanalysis");
|
||||
}
|
||||
BridgeStartMode::Project => {
|
||||
cmd.arg("-process").arg("-noanalysis");
|
||||
}
|
||||
}
|
||||
|
||||
// Add Java bridge script args
|
||||
|
||||
@@ -53,9 +53,11 @@ import java.util.Iterator;
|
||||
public class GhidraCliBridge extends GhidraScript {
|
||||
|
||||
private Gson gson = new GsonBuilder().serializeNulls().create();
|
||||
private long startTime;
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
startTime = System.currentTimeMillis();
|
||||
// Get port file path from script arguments
|
||||
String[] scriptArgs = getScriptArgs();
|
||||
if (scriptArgs.length < 1) {
|
||||
@@ -239,6 +241,8 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
case "script_list": return handleScriptList();
|
||||
// Batch
|
||||
case "batch": return handleBatch(args);
|
||||
// Bridge info
|
||||
case "bridge_info": return handleBridgeInfo();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -316,6 +320,28 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
return result;
|
||||
}
|
||||
|
||||
private JsonObject handleBridgeInfo() {
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("has_current_program", currentProgram != null);
|
||||
if (currentProgram != null) {
|
||||
result.addProperty("current_program", currentProgram.getName());
|
||||
}
|
||||
result.addProperty("uptime_ms", System.currentTimeMillis() - startTime);
|
||||
|
||||
Project project = state.getProject();
|
||||
if (project != null) {
|
||||
result.addProperty("project_name", project.getName());
|
||||
try {
|
||||
ProjectData projectData = project.getProjectData();
|
||||
DomainFolder rootFolder = projectData.getRootFolder();
|
||||
result.addProperty("program_count", rootFolder.getFiles().length);
|
||||
} catch (Exception e) {
|
||||
result.addProperty("program_count", 0);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private JsonObject handleProgramInfo() {
|
||||
if (currentProgram == null) {
|
||||
return errorResult("No program loaded");
|
||||
@@ -596,8 +622,7 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
ReferenceManager refMgr = currentProgram.getReferenceManager();
|
||||
FunctionManager fm = currentProgram.getFunctionManager();
|
||||
|
||||
Reference[] refs = refMgr.getReferencesTo(addr);
|
||||
for (Reference ref : refs) {
|
||||
for (Reference ref : refMgr.getReferencesTo(addr)) {
|
||||
Address fromAddr = ref.getFromAddress();
|
||||
Function fromFunc = fm.getFunctionContaining(fromAddr);
|
||||
Function toFunc = fm.getFunctionContaining(addr);
|
||||
@@ -740,7 +765,10 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
private JsonObject handleAnalyze(JsonObject args) {
|
||||
String programName = getArgString(args, "program");
|
||||
if (programName == null || programName.isEmpty()) {
|
||||
return errorResult("No program name provided");
|
||||
if (currentProgram == null) {
|
||||
return errorResult("No program loaded. Use 'open_program' or 'import' first.");
|
||||
}
|
||||
programName = currentProgram.getName();
|
||||
}
|
||||
|
||||
if (currentProgram == null) {
|
||||
@@ -758,35 +786,10 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
}
|
||||
|
||||
try {
|
||||
// Try Ghidra 12+ import path first, fall back to older path
|
||||
Class<?> aamClass;
|
||||
try {
|
||||
aamClass = Class.forName("ghidra.app.plugin.core.analysis.AutoAnalysisManager");
|
||||
} catch (ClassNotFoundException e) {
|
||||
aamClass = Class.forName("ghidra.app.cmd.analysis.AutoAnalysisManager");
|
||||
}
|
||||
|
||||
java.lang.reflect.Method getManager = aamClass.getMethod("getAnalysisManager", ghidra.program.model.listing.Program.class);
|
||||
Object autoMgr = getManager.invoke(null, currentProgram);
|
||||
|
||||
if (autoMgr == null) {
|
||||
return errorResult("Could not get AutoAnalysisManager");
|
||||
}
|
||||
|
||||
TaskMonitor mon = new ConsoleTaskMonitor();
|
||||
|
||||
// Schedule full re-analysis
|
||||
java.lang.reflect.Method reAnalyze = aamClass.getMethod("reAnalyzeAll", Address.class);
|
||||
reAnalyze.invoke(autoMgr, (Address) null);
|
||||
|
||||
java.lang.reflect.Method startAnalysis = aamClass.getMethod("startAnalysis", TaskMonitor.class);
|
||||
startAnalysis.invoke(autoMgr, mon);
|
||||
|
||||
// Poll until analysis completes
|
||||
java.lang.reflect.Method isAnalyzing = aamClass.getMethod("isAnalyzing");
|
||||
while ((Boolean) isAnalyzing.invoke(autoMgr)) {
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
// Use GhidraScript's built-in analyzeAll which works across Ghidra versions
|
||||
analyzeAll(currentProgram);
|
||||
|
||||
// Save the program
|
||||
try {
|
||||
@@ -795,9 +798,11 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
// Best effort
|
||||
}
|
||||
|
||||
FunctionManager fm = currentProgram.getFunctionManager();
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("status", "success");
|
||||
result.addProperty("program", programName);
|
||||
result.addProperty("function_count", fm.getFunctionCount());
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -826,12 +831,48 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
prog.addProperty("type", domainFile.getContentType());
|
||||
prog.addProperty("version", domainFile.getVersion());
|
||||
prog.addProperty("current", isCurrent);
|
||||
|
||||
// Add analysis metadata
|
||||
if (isCurrent && currentProgram != null) {
|
||||
// For current program, use live data
|
||||
FunctionManager fm = currentProgram.getFunctionManager();
|
||||
int funcCount = fm.getFunctionCount();
|
||||
prog.addProperty("function_count", funcCount);
|
||||
prog.addProperty("analyzed", funcCount > 1);
|
||||
prog.addProperty("executable_format", currentProgram.getExecutableFormat());
|
||||
} else {
|
||||
// For other programs, use DomainFile metadata
|
||||
try {
|
||||
java.util.Map<String, String> metadata = domainFile.getMetadata();
|
||||
if (metadata != null) {
|
||||
String funcCountStr = metadata.get("# of Functions");
|
||||
int funcCount = 0;
|
||||
if (funcCountStr != null) {
|
||||
try { funcCount = Integer.parseInt(funcCountStr.trim()); }
|
||||
catch (NumberFormatException ignored) {}
|
||||
}
|
||||
prog.addProperty("function_count", funcCount);
|
||||
prog.addProperty("analyzed", funcCount > 1);
|
||||
String exeFmt = metadata.get("Executable Format");
|
||||
if (exeFmt != null) {
|
||||
prog.addProperty("executable_format", exeFmt);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// metadata not available for this file
|
||||
}
|
||||
}
|
||||
|
||||
programs.add(prog);
|
||||
}
|
||||
|
||||
JsonObject result = new JsonObject();
|
||||
result.add("programs", programs);
|
||||
result.addProperty("count", programs.size());
|
||||
result.addProperty("has_current_program", currentProgram != null);
|
||||
if (currentProgram != null) {
|
||||
result.addProperty("current_program_name", currentProgram.getName());
|
||||
}
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -1178,10 +1219,9 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
|
||||
ReferenceManager refMgr = currentProgram.getReferenceManager();
|
||||
Address targetAddr = targetFunc.getEntryPoint();
|
||||
Reference[] refs = refMgr.getReferencesTo(targetAddr);
|
||||
JsonArray results = new JsonArray();
|
||||
|
||||
for (Reference ref : refs) {
|
||||
for (Reference ref : refMgr.getReferencesTo(targetAddr)) {
|
||||
if (ref.getReferenceType().isCall()) {
|
||||
Address fromAddr = ref.getFromAddress();
|
||||
Function callerFunc = fm.getFunctionContaining(fromAddr);
|
||||
@@ -1262,8 +1302,10 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
Address funcAddr = func.getEntryPoint();
|
||||
long funcSize = func.getBody().getNumAddresses();
|
||||
|
||||
Reference[] refs = refMgr.getReferencesTo(funcAddr);
|
||||
int xrefCount = refs.length;
|
||||
int xrefCount = 0;
|
||||
for (Reference ref : refMgr.getReferencesTo(funcAddr)) {
|
||||
xrefCount++;
|
||||
}
|
||||
|
||||
JsonArray reasons = new JsonArray();
|
||||
|
||||
@@ -1953,8 +1995,7 @@ public class GhidraCliBridge extends GhidraScript {
|
||||
if (visited.contains(funcAddrStr)) return;
|
||||
visited.add(funcAddrStr);
|
||||
|
||||
Reference[] refs = refMgr.getReferencesTo(func.getEntryPoint());
|
||||
for (Reference ref : refs) {
|
||||
for (Reference ref : refMgr.getReferencesTo(func.getEntryPoint())) {
|
||||
if (ref.getReferenceType().isCall()) {
|
||||
Address fromAddr = ref.getFromAddress();
|
||||
Function callerFunc = fm.getFunctionContaining(fromAddr);
|
||||
|
||||
@@ -94,6 +94,11 @@ impl BridgeClient {
|
||||
self.send_command("status", None)
|
||||
}
|
||||
|
||||
/// Get bridge info (current program, project name, program count, uptime).
|
||||
pub fn bridge_info(&self) -> Result<serde_json::Value> {
|
||||
self.send_command("bridge_info", None)
|
||||
}
|
||||
|
||||
/// List functions.
|
||||
pub fn list_functions(
|
||||
&self,
|
||||
|
||||
+56
-125
@@ -9,14 +9,14 @@ mod ipc;
|
||||
mod query;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands, DaemonCommands};
|
||||
use cli::{Cli, Commands};
|
||||
use config::Config;
|
||||
use error::GhidraError;
|
||||
use format::{auto_detect_format, DefaultFormatter, Formatter, OutputFormat};
|
||||
use ghidra::bridge::{self, BridgeStartMode, BridgeStatus};
|
||||
use ghidra::GhidraClient;
|
||||
use ipc::client::BridgeClient;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::Layer;
|
||||
@@ -66,7 +66,11 @@ fn main() {
|
||||
.unwrap();
|
||||
rt.block_on(run_setup(cli))
|
||||
}
|
||||
Commands::Daemon(_) => handle_daemon_command_dispatch(cli),
|
||||
Commands::Start { .. }
|
||||
| Commands::Stop { .. }
|
||||
| Commands::Restart { .. }
|
||||
| Commands::Status { .. }
|
||||
| Commands::Ping { .. } => handle_bridge_command(cli),
|
||||
_ => run_command(cli),
|
||||
};
|
||||
|
||||
@@ -101,7 +105,6 @@ fn requires_bridge(command: &Commands) -> bool {
|
||||
command,
|
||||
Commands::Import(_)
|
||||
| Commands::Analyze(_)
|
||||
| Commands::Quick(_)
|
||||
| Commands::Query(_)
|
||||
| Commands::Decompile(_)
|
||||
| Commands::Function(_)
|
||||
@@ -130,7 +133,6 @@ fn extract_project_from_command(command: &Commands) -> Option<String> {
|
||||
match command {
|
||||
Commands::Import(args) => args.project.clone(),
|
||||
Commands::Analyze(args) => args.project.clone(),
|
||||
Commands::Quick(args) => args.project.clone(),
|
||||
Commands::Query(args) => args.project.clone(),
|
||||
Commands::Summary(args) => args.options.project.clone(),
|
||||
Commands::Decompile(args) => args.options.project.clone(),
|
||||
@@ -403,96 +405,37 @@ fn run_with_bridge(cli: Cli) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
Commands::Quick(args) => {
|
||||
let binary_path = PathBuf::from(&args.binary);
|
||||
if !binary_path.exists() {
|
||||
anyhow::bail!("Binary not found: {}", args.binary);
|
||||
}
|
||||
|
||||
eprintln!("Quick analysis of {}...", args.binary);
|
||||
|
||||
// Reuse running bridge instead of re-importing in a fresh one
|
||||
let (client, port) = if let Some(port) = bridge::is_bridge_running(&project_path) {
|
||||
_ => {
|
||||
// For all bridge commands (including Analyze), ensure bridge is running
|
||||
let client = if let Some(port) = bridge::is_bridge_running(&project_path) {
|
||||
let client = BridgeClient::new(port);
|
||||
verify_bridge(&client)?;
|
||||
eprintln!("[1/3] Importing binary...");
|
||||
let result = client.import_binary(&args.binary, None)?;
|
||||
// import result has the newly-imported program name
|
||||
let program_name = result
|
||||
.get("program")
|
||||
.and_then(|p| p.as_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
client.open_program(&program_name)?;
|
||||
(client, port)
|
||||
client
|
||||
} else {
|
||||
eprintln!("[1/3] Importing binary...");
|
||||
let port = bridge::ensure_bridge_running(
|
||||
&project_path,
|
||||
&ghidra_install_dir,
|
||||
BridgeStartMode::Import {
|
||||
binary_path: args.binary.clone(),
|
||||
},
|
||||
)?;
|
||||
let client = BridgeClient::new(port);
|
||||
client.program_info()?;
|
||||
(client, port)
|
||||
};
|
||||
|
||||
eprintln!("[2/3] Running analysis...");
|
||||
client.analyze()?;
|
||||
eprintln!("[3/3] Done!");
|
||||
eprintln!("Analysis complete. The bridge is running on port {}.", port);
|
||||
eprintln!();
|
||||
eprintln!("Run queries like:");
|
||||
eprintln!(" ghidra function list");
|
||||
eprintln!(" ghidra decompile main");
|
||||
eprintln!(" ghidra summary");
|
||||
|
||||
let info = client.program_info()?;
|
||||
json!({
|
||||
"command": "quick",
|
||||
"program": info.get("name").and_then(|n| n.as_str()).unwrap_or("unknown"),
|
||||
"status": "success",
|
||||
"port": port,
|
||||
"data": info
|
||||
})
|
||||
}
|
||||
|
||||
// Analyze uses the generic dispatch path via execute_via_bridge
|
||||
|
||||
_ => {
|
||||
// For query commands (including Analyze), ensure bridge is running
|
||||
if bridge::is_bridge_running(&project_path).is_none() {
|
||||
// Need a program name to start the bridge
|
||||
let program = extract_program_from_command(&cli.command)
|
||||
// Auto-start bridge - use specific program if available, otherwise project mode
|
||||
let mode = if let Some(program) = extract_program_from_command(&cli.command)
|
||||
.or_else(|| config.get_default_program())
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"No bridge running and no default program configured.\n\
|
||||
Import a binary first: ghidra import <binary>\n\
|
||||
Or set a default: ghidra set-default program <name>"
|
||||
)
|
||||
})?;
|
||||
{
|
||||
BridgeStartMode::Process {
|
||||
program_name: program,
|
||||
}
|
||||
} else {
|
||||
BridgeStartMode::Project
|
||||
};
|
||||
|
||||
eprintln!("Starting Ghidra bridge...");
|
||||
let port = bridge::ensure_bridge_running(
|
||||
&project_path,
|
||||
&ghidra_install_dir,
|
||||
BridgeStartMode::Process {
|
||||
program_name: program,
|
||||
},
|
||||
mode,
|
||||
)?;
|
||||
eprintln!("Bridge ready.");
|
||||
let client = BridgeClient::new(port);
|
||||
execute_via_bridge(&client, &cli.command)?
|
||||
} else {
|
||||
let client = connect_to_bridge(&project_path)?;
|
||||
verify_bridge(&client)?;
|
||||
BridgeClient::new(port)
|
||||
};
|
||||
|
||||
// Switch to requested program if it differs from the bridge's current program
|
||||
if let Some(requested_program) = extract_program_from_command(&cli.command) {
|
||||
let info = client.program_info()?;
|
||||
// Switch to requested program if it differs from the bridge's current program
|
||||
if let Some(requested_program) = extract_program_from_command(&cli.command) {
|
||||
if let Ok(info) = client.program_info() {
|
||||
let current = info
|
||||
.get("name")
|
||||
.and_then(|n| n.as_str())
|
||||
@@ -500,10 +443,12 @@ fn run_with_bridge(cli: Cli) -> anyhow::Result<()> {
|
||||
if current != requested_program {
|
||||
client.open_program(&requested_program)?;
|
||||
}
|
||||
} else {
|
||||
client.open_program(&requested_program)?;
|
||||
}
|
||||
|
||||
execute_via_bridge(&client, &cli.command)?
|
||||
}
|
||||
|
||||
execute_via_bridge(&client, &cli.command)?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -765,33 +710,18 @@ fn execute_via_bridge(
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch daemon (bridge management) commands.
|
||||
fn handle_daemon_command_dispatch(cli: Cli) -> anyhow::Result<()> {
|
||||
/// Dispatch bridge management commands (top-level start/stop/restart/status/ping).
|
||||
fn handle_bridge_command(cli: Cli) -> anyhow::Result<()> {
|
||||
match cli.command {
|
||||
Commands::Daemon(cmd) => match cmd {
|
||||
DaemonCommands::Start {
|
||||
project,
|
||||
program,
|
||||
port: _,
|
||||
foreground: _,
|
||||
} => handle_bridge_start(project, program),
|
||||
DaemonCommands::Stop { project } => handle_bridge_stop(project),
|
||||
DaemonCommands::Restart {
|
||||
project,
|
||||
program,
|
||||
port: _,
|
||||
} => {
|
||||
handle_bridge_stop(project.clone())?;
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
handle_bridge_start(project, program)
|
||||
}
|
||||
DaemonCommands::Status { project } => handle_bridge_status(project),
|
||||
DaemonCommands::Ping { project } => handle_bridge_ping(project),
|
||||
DaemonCommands::ClearCache { project: _ } => {
|
||||
println!("Cache is managed by the bridge process");
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
Commands::Start { project, program } => handle_bridge_start(project, program),
|
||||
Commands::Stop { project } => handle_bridge_stop(project),
|
||||
Commands::Restart { project, program } => {
|
||||
handle_bridge_stop(project.clone())?;
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
handle_bridge_start(project, program)
|
||||
}
|
||||
Commands::Status { project } => handle_bridge_status(project),
|
||||
Commands::Ping { project } => handle_bridge_ping(project),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -823,12 +753,10 @@ fn handle_bridge_start(project: Option<String>, program: Option<String>) -> anyh
|
||||
// Determine start mode
|
||||
let mode = if let Some(prog) = program {
|
||||
BridgeStartMode::Process { program_name: prog }
|
||||
} else {
|
||||
// Need a program name
|
||||
let prog = config.get_default_program().ok_or_else(|| {
|
||||
anyhow::anyhow!("No program specified. Use --program <name> or set a default.")
|
||||
})?;
|
||||
} else if let Some(prog) = config.get_default_program() {
|
||||
BridgeStartMode::Process { program_name: prog }
|
||||
} else {
|
||||
BridgeStartMode::Project
|
||||
};
|
||||
|
||||
println!("Starting bridge for project: {}", project_path.display());
|
||||
@@ -866,6 +794,17 @@ fn handle_bridge_status(project: Option<String>) -> anyhow::Result<()> {
|
||||
println!(" PID: {}", pid);
|
||||
println!(" Port: {}", port);
|
||||
println!(" Project: {}", project_path.display());
|
||||
|
||||
// Try to get extended info from the bridge
|
||||
let client = BridgeClient::new(port);
|
||||
if let Ok(info) = client.bridge_info() {
|
||||
if let Some(prog) = info.get("current_program").and_then(|v| v.as_str()) {
|
||||
println!(" Current program: {}", prog);
|
||||
}
|
||||
if let Some(count) = info.get("program_count").and_then(|v| v.as_u64()) {
|
||||
println!(" Programs: {}", count);
|
||||
}
|
||||
}
|
||||
}
|
||||
BridgeStatus::Stopped => {
|
||||
println!("No bridge running for project: {}", project_path.display());
|
||||
@@ -944,7 +883,7 @@ async fn run_setup(cli: Cli) -> anyhow::Result<()> {
|
||||
let client = GhidraClient::new(config)?;
|
||||
if client.verify_installation().is_ok() {
|
||||
println!("Verification passed!");
|
||||
println!("\nYou can now run: ghidra quick <binary>");
|
||||
println!("\nYou can now run: ghidra import <binary> --project <name>");
|
||||
} else {
|
||||
println!("Verification failed - analyzeHeadless not found");
|
||||
println!(" The installation may be incomplete.");
|
||||
@@ -1197,14 +1136,6 @@ fn verify_bridge(client: &BridgeClient) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Connect to a running bridge for a project.
|
||||
fn connect_to_bridge(project_path: &Path) -> anyhow::Result<BridgeClient> {
|
||||
let port = bridge::is_bridge_running(project_path).ok_or_else(|| {
|
||||
anyhow::anyhow!("Bridge not running for project: {}", project_path.display())
|
||||
})?;
|
||||
Ok(BridgeClient::new(port))
|
||||
}
|
||||
|
||||
/// Resolve a project name to its full path on disk.
|
||||
fn resolve_project_path(project: &Option<String>, config: &Config) -> anyhow::Result<PathBuf> {
|
||||
let project_name = project
|
||||
|
||||
@@ -122,7 +122,6 @@ impl DaemonTestHarness {
|
||||
let mut cmd =
|
||||
assert_cmd::Command::cargo_bin("ghidra").expect("Failed to find ghidra binary");
|
||||
let result = cmd
|
||||
.arg("daemon")
|
||||
.arg("start")
|
||||
.arg("--project")
|
||||
.arg(project)
|
||||
|
||||
+3
-36
@@ -25,7 +25,6 @@ fn test_daemon_start() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("status")
|
||||
.assert()
|
||||
.success();
|
||||
@@ -47,7 +46,6 @@ fn test_daemon_status() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("status")
|
||||
.assert()
|
||||
.success()
|
||||
@@ -70,7 +68,6 @@ fn test_daemon_ping() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("ping")
|
||||
.assert()
|
||||
.success();
|
||||
@@ -78,28 +75,6 @@ fn test_daemon_ping() {
|
||||
drop(harness);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_daemon_clear_cache() {
|
||||
require_ghidra!();
|
||||
|
||||
ensure_test_project(TEST_PROJECT, TEST_PROGRAM);
|
||||
|
||||
let harness =
|
||||
DaemonTestHarness::new(TEST_PROJECT, TEST_PROGRAM).expect("Failed to start daemon");
|
||||
|
||||
Command::cargo_bin("ghidra")
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("clear-cache")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
drop(harness);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_daemon_lifecycle() {
|
||||
@@ -114,7 +89,6 @@ fn test_daemon_lifecycle() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("status")
|
||||
.assert()
|
||||
.success()
|
||||
@@ -124,7 +98,6 @@ fn test_daemon_lifecycle() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("ping")
|
||||
.assert()
|
||||
.success();
|
||||
@@ -133,7 +106,6 @@ fn test_daemon_lifecycle() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("stop")
|
||||
.assert()
|
||||
.success();
|
||||
@@ -153,7 +125,6 @@ fn test_daemon_stop() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("stop")
|
||||
.assert()
|
||||
.success();
|
||||
@@ -162,11 +133,10 @@ fn test_daemon_stop() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("status")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("No daemon running"));
|
||||
.stdout(predicate::str::contains("No bridge running"));
|
||||
|
||||
drop(harness);
|
||||
}
|
||||
@@ -185,7 +155,6 @@ fn test_daemon_restart() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("restart")
|
||||
.arg("--program")
|
||||
.arg(TEST_PROGRAM)
|
||||
@@ -196,7 +165,6 @@ fn test_daemon_restart() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("stop")
|
||||
.assert()
|
||||
.success();
|
||||
@@ -218,13 +186,12 @@ fn test_daemon_start_when_running() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("start")
|
||||
.arg("--program")
|
||||
.arg(TEST_PROGRAM)
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("already running"));
|
||||
.success()
|
||||
.stdout(predicate::str::contains("already running"));
|
||||
|
||||
drop(harness);
|
||||
}
|
||||
|
||||
@@ -240,29 +240,3 @@ fn test_import_existing_program() {
|
||||
.success();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_quick() {
|
||||
require_ghidra!();
|
||||
|
||||
let project = unique_project_name("quick");
|
||||
let binary = common::fixture_binary();
|
||||
|
||||
Command::cargo_bin("ghidra")
|
||||
.unwrap()
|
||||
.arg("quick")
|
||||
.arg(binary.to_str().unwrap())
|
||||
.arg("--project")
|
||||
.arg(&project)
|
||||
.timeout(std::time::Duration::from_secs(300))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
Command::cargo_bin("ghidra")
|
||||
.unwrap()
|
||||
.arg("project")
|
||||
.arg("delete")
|
||||
.arg(&project)
|
||||
.assert()
|
||||
.success();
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ fn test_stale_files_cleaned_on_restart() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("ping")
|
||||
.timeout(Duration::from_secs(30))
|
||||
.assert()
|
||||
@@ -52,7 +51,6 @@ fn test_stale_files_cleaned_on_restart() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("ping")
|
||||
.timeout(Duration::from_secs(30))
|
||||
.assert()
|
||||
@@ -80,7 +78,6 @@ fn test_recovery_after_crash() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("ping")
|
||||
.timeout(Duration::from_secs(30))
|
||||
.assert()
|
||||
@@ -102,7 +99,6 @@ fn test_recovery_after_crash() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("ping")
|
||||
.timeout(Duration::from_secs(30))
|
||||
.assert()
|
||||
@@ -126,7 +122,6 @@ fn test_bridge_not_ready_error() {
|
||||
.unwrap()
|
||||
.arg("--project")
|
||||
.arg(TEST_PROJECT)
|
||||
.arg("daemon")
|
||||
.arg("ping")
|
||||
.timeout(Duration::from_secs(30))
|
||||
.assert()
|
||||
|
||||
Reference in New Issue
Block a user