diff --git a/README.md b/README.md
index b433378..a8f10d2 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Ghidra CLI
+# Ghidra CLI
A high-performance Rust CLI for automating Ghidra reverse engineering tasks, designed for both direct usage and AI agent integration (like Claude Code).
@@ -78,9 +78,9 @@ ghidra find string "password"
# Get cross-references
ghidra x-ref to 0x401000
-# Generate call graph
-ghidra graph calls main --depth 3
-```
+# Generate call graph
+ghidra graph callers main --depth 3
+```
## Commands
@@ -103,11 +103,11 @@ ghidra disasm
--instructions 20 # Disassemble instructions
### Symbols & Types
```bash
-ghidra symbol list # List symbols
-ghidra symbol create # Create symbol
-ghidra symbol rename # Rename symbol
-ghidra type list # List data types
-ghidra type get # Get type details
+ghidra symbol list # List symbols
+ghidra symbol create # Create symbol
+ghidra symbol rename # Rename symbol
+ghidra type list # List data types
+ghidra type get # Get type details
```
### Cross-References
@@ -125,27 +125,31 @@ ghidra find crypto # Find crypto constants
ghidra find interesting # Find interesting patterns
```
-### Call Graphs
-```bash
-ghidra graph calls # Full call graph
-ghidra graph callers # Who calls this?
-ghidra graph callees # What does this call?
-ghidra graph export dot # Export to DOT format
-```
+### Call Graphs
+```bash
+ghidra graph calls # Full call graph
+ghidra graph callers # Who calls this? (--depth optional)
+ghidra graph callees # What does this call? (--depth optional)
+ghidra graph export dot # Export to DOT format
+```
-### Binary Patching
-```bash
-ghidra patch bytes "90 90" # Patch bytes
-ghidra patch nop --count 5 # NOP out instructions
-ghidra patch export # Export as patch file
-```
+### Binary Patching
+```bash
+ghidra patch bytes "90 90" # Patch bytes
+ghidra patch nop --count 5 # NOP out instructions
+ghidra patch export -o patched.bin # Export patched binary
+```
+
+Note: `patch nop --count` is currently parsed by the CLI, but runtime uses single-address NOP behavior.
-### Comments
-```bash
-ghidra comment get # Get comment
-ghidra comment set "note" # Set comment
-ghidra comment list # List all comments
-```
+### Comments
+```bash
+ghidra comment get # Get comment
+ghidra comment set "note" --comment-type EOL # Set comment
+ghidra comment list # List all comments
+```
+
+Note: `--comment-type` currently falls back to `EOL` due client/bridge argument key mismatch.
### Scripts
```bash
@@ -247,8 +251,8 @@ Example workflow with an AI agent:
2. `ghidra find interesting` - AI analyzes suspicious patterns
3. `ghidra decompile ` - AI examines specific functions
4. `ghidra x-ref to ` - AI traces data flow
-5. `ghidra patch nop ` - AI patches anti-debug code
-6. `ghidra patch export` - Export patched binary
+5. `ghidra patch nop ` - AI patches anti-debug code
+6. `ghidra patch export -o patched.bin` - Export patched binary
## Troubleshooting
diff --git a/docs/plan-complete-stub-commands.md b/docs/plan-complete-stub-commands.md
index 35f55d9..38482cf 100644
--- a/docs/plan-complete-stub-commands.md
+++ b/docs/plan-complete-stub-commands.md
@@ -1,5 +1,8 @@
# Plan: Complete All Stub Commands
+> Historical planning document: parts of this plan assume an older daemon-centric architecture and may not match the current implementation.
+> For current behavior, use `README.md`, `AGENTS.md`, and the CLI help output.
+
## Overview
ghidra-cli has 39 stub commands that output "not yet implemented". This plan implements all stub commands using a hybrid approach: grouped by category with shared helpers per group. Each category becomes a deployable unit with its own Ghidra bridge Python scripts, daemon routing, and E2E tests.
diff --git a/docs/plan-e2e-tests.md b/docs/plan-e2e-tests.md
index a9f4903..9d60264 100644
--- a/docs/plan-e2e-tests.md
+++ b/docs/plan-e2e-tests.md
@@ -1,5 +1,8 @@
# E2E Test Coverage Plan
+> Historical planning document: this plan captures prior design decisions and may not match current test architecture exactly.
+> For current test behavior and commands, see `tests/README.md`.
+
## Overview
This plan addresses the critical E2E test coverage gap in ghidra-cli. Currently only 4 of 60+ CLI commands have active tests. The plan implements a modular test structure with daemon lifecycle management, enabling comprehensive testing of all CLI functionality including the 51+ untested commands.
diff --git a/docs/plan-prod.md b/docs/plan-prod.md
index ff5b393..c159af3 100644
--- a/docs/plan-prod.md
+++ b/docs/plan-prod.md
@@ -1,5 +1,8 @@
# Ghidra-CLI Open Source Release Plan (Daemon-Only Architecture)
+> Historical planning document: this does not reflect the current implementation.
+> Current architecture is direct CLI-to-Java bridge (`GhidraCliBridge.java`) as documented in `README.md` and `AGENTS.md`.
+
## Overview
Prepare ghidra-cli for open source release with a **daemon-only architecture**. Binary analysis is slow enough that a persistent Ghidra process (via daemon) is always preferable to spawning new processes per command.
diff --git a/src/cli.rs b/src/cli.rs
index fefcf76..551f610 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -312,12 +312,25 @@ pub enum FunctionCommands {
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
pub struct FunctionGetArgs {
- /// Function address or name
- pub target: String,
+ /// Function target (name/address/FUN_...)
+ #[arg(value_name = "TARGET", required_unless_present = "target")]
+ pub positional_target: Option,
+ /// Function target (name | 0xaddr | FUN_)
+ #[arg(long = "target", value_name = "TARGET")]
+ pub target: Option,
#[command(flatten)]
pub options: QueryOptions,
}
+impl FunctionGetArgs {
+ pub fn resolved_target(&self) -> &str {
+ self.target
+ .as_deref()
+ .or(self.positional_target.as_deref())
+ .expect("clap should ensure target is provided")
+ }
+}
+
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
pub struct RenameArgs {
pub old_name: String,
@@ -436,11 +449,25 @@ pub enum XRefCommands {
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
pub struct XRefArgs {
- pub address: String,
+ /// XRef target (name | 0xaddr | FUN_)
+ #[arg(value_name = "TARGET", required_unless_present = "target")]
+ pub positional_target: Option,
+ /// XRef target (name | 0xaddr | FUN_)
+ #[arg(long = "target", value_name = "TARGET")]
+ pub target: Option,
#[command(flatten)]
pub options: QueryOptions,
}
+impl XRefArgs {
+ pub fn resolved_target(&self) -> &str {
+ self.target
+ .as_deref()
+ .or(self.positional_target.as_deref())
+ .expect("clap should ensure target is provided")
+ }
+}
+
#[derive(Subcommand, Clone, Serialize, Deserialize, Debug)]
pub enum TypeCommands {
/// List data types
@@ -555,11 +582,25 @@ pub struct FindFunctionArgs {
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
pub struct FindCallsArgs {
- pub function: String,
+ /// Function target (name | 0xaddr | FUN_)
+ #[arg(value_name = "TARGET", required_unless_present = "target")]
+ pub positional_target: Option,
+ /// Function target (name | 0xaddr | FUN_)
+ #[arg(long = "target", value_name = "TARGET")]
+ pub target: Option,
#[command(flatten)]
pub options: QueryOptions,
}
+impl FindCallsArgs {
+ pub fn resolved_target(&self) -> &str {
+ self.target
+ .as_deref()
+ .or(self.positional_target.as_deref())
+ .expect("clap should ensure target is provided")
+ }
+}
+
#[derive(Subcommand, Clone, Serialize, Deserialize, Debug)]
pub enum GraphCommands {
/// Call graph
@@ -576,13 +617,27 @@ pub enum GraphCommands {
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
pub struct GraphFunctionArgs {
- pub function: String,
+ /// Function target (name | 0xaddr | FUN_)
+ #[arg(value_name = "TARGET", required_unless_present = "target")]
+ pub positional_target: Option,
+ /// Function target (name | 0xaddr | FUN_)
+ #[arg(long = "target", value_name = "TARGET")]
+ pub target: Option,
#[arg(long)]
pub depth: Option,
#[command(flatten)]
pub options: QueryOptions,
}
+impl GraphFunctionArgs {
+ pub fn resolved_target(&self) -> &str {
+ self.target
+ .as_deref()
+ .or(self.positional_target.as_deref())
+ .expect("clap should ensure target is provided")
+ }
+}
+
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
pub struct GraphExportArgs {
/// Export format (e.g., dot, json)
@@ -594,14 +649,33 @@ pub struct GraphExportArgs {
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
pub struct DecompileArgs {
- pub target: String,
+ /// Function target (name | 0xaddr | FUN_)
+ #[arg(value_name = "TARGET", required_unless_present = "target")]
+ pub positional_target: Option,
+ /// Function target (name | 0xaddr | FUN_)
+ #[arg(long = "target", value_name = "TARGET")]
+ pub target: Option,
#[command(flatten)]
pub options: QueryOptions,
}
+impl DecompileArgs {
+ pub fn resolved_target(&self) -> &str {
+ self.target
+ .as_deref()
+ .or(self.positional_target.as_deref())
+ .expect("clap should ensure target is provided")
+ }
+}
+
#[derive(Args, Clone, Serialize, Deserialize, Debug)]
pub struct DisasmArgs {
- pub address: String,
+ /// Disassembly target (name | 0xaddr | FUN_)
+ #[arg(value_name = "TARGET", required_unless_present = "target")]
+ pub positional_target: Option,
+ /// Disassembly target (name | 0xaddr | FUN_)
+ #[arg(long = "target", value_name = "TARGET")]
+ pub target: Option,
/// Number of instructions to disassemble
#[arg(long = "instructions", short = 'n')]
pub num_instructions: Option,
@@ -609,6 +683,15 @@ pub struct DisasmArgs {
pub options: QueryOptions,
}
+impl DisasmArgs {
+ pub fn resolved_target(&self) -> &str {
+ self.target
+ .as_deref()
+ .or(self.positional_target.as_deref())
+ .expect("clap should ensure target is provided")
+ }
+}
+
#[derive(Subcommand, Clone, Serialize, Deserialize, Debug)]
pub enum DiffCommands {
/// Compare two programs
@@ -838,3 +921,30 @@ pub struct SetupArgs {
#[arg(long)]
pub force: bool,
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn parses_decompile_target_flag() {
+ let cli = Cli::try_parse_from(["ghidra", "decompile", "--target", "FUN_00401000"])
+ .expect("decompile --target should parse");
+ match cli.command {
+ Commands::Decompile(args) => assert_eq!(args.resolved_target(), "FUN_00401000"),
+ _ => panic!("expected decompile command"),
+ }
+ }
+
+ #[test]
+ fn parses_function_get_positional_target() {
+ let cli = Cli::try_parse_from(["ghidra", "function", "get", "main"])
+ .expect("function get positional target should parse");
+ match cli.command {
+ Commands::Function(FunctionCommands::Get(args)) => {
+ assert_eq!(args.resolved_target(), "main");
+ }
+ _ => panic!("expected function get command"),
+ }
+ }
+}
diff --git a/src/ghidra/scripts/GhidraCliBridge.java b/src/ghidra/scripts/GhidraCliBridge.java
index 2b3cb5b..3669ad4 100644
--- a/src/ghidra/scripts/GhidraCliBridge.java
+++ b/src/ghidra/scripts/GhidraCliBridge.java
@@ -30,6 +30,7 @@ import ghidra.util.task.TaskMonitor;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
@@ -188,6 +189,10 @@ public class GhidraCliBridge extends GhidraScript {
case "ping": return handlePing();
case "program_info": return handleProgramInfo();
case "list_functions": return handleListFunctions(args);
+ case "get_function": return handleGetFunction(args);
+ case "rename_function": return handleRenameFunction(args);
+ case "create_function": return handleCreateFunction(args);
+ case "delete_function": return handleDeleteFunction(args);
case "decompile": return handleDecompile(args);
case "list_strings": return handleListStrings(args);
case "list_imports": return handleListImports();
@@ -479,6 +484,242 @@ public class GhidraCliBridge extends GhidraScript {
return result;
}
+ private JsonObject functionToJson(Function func) {
+ JsonObject funcData = new JsonObject();
+ funcData.addProperty("name", func.getName());
+ funcData.addProperty("address", func.getEntryPoint().toString());
+ funcData.addProperty("size", func.getBody().getNumAddresses());
+ funcData.addProperty("entry_point", func.getEntryPoint().toString());
+
+ String sig = null;
+ try {
+ sig = func.getPrototypeString(false, false);
+ } catch (Exception e) {
+ // ignore
+ }
+ if (sig != null) {
+ funcData.addProperty("signature", sig);
+ } else {
+ funcData.add("signature", JsonNull.INSTANCE);
+ }
+
+ funcData.addProperty("calling_convention", func.getCallingConventionName());
+
+ String comment = func.getComment();
+ if (comment != null) {
+ funcData.addProperty("comment", comment);
+ } else {
+ funcData.add("comment", JsonNull.INSTANCE);
+ }
+
+ return funcData;
+ }
+
+ private String buildFunctionTargetHint(String target) {
+ if (currentProgram == null || target == null || target.isEmpty()) {
+ return "Function not found";
+ }
+
+ String query = target.toLowerCase();
+ List containsMatches = new ArrayList<>();
+ List fuzzyMatches = new ArrayList<>();
+ FunctionIterator iter = currentProgram.getFunctionManager().getFunctions(true);
+
+ while (iter.hasNext()) {
+ Function func = iter.next();
+ String name = func.getName();
+ String lname = name.toLowerCase();
+
+ if (lname.contains(query)) {
+ containsMatches.add(name);
+ } else if (query.length() >= 3 && levenshteinDistance(lname, query) <= 3) {
+ fuzzyMatches.add(name);
+ }
+ }
+
+ Collections.sort(containsMatches);
+ Collections.sort(fuzzyMatches);
+
+ List suggestions = new ArrayList<>();
+ for (String name : containsMatches) {
+ suggestions.add(name);
+ if (suggestions.size() >= 5) break;
+ }
+ if (suggestions.size() < 5) {
+ for (String name : fuzzyMatches) {
+ if (!suggestions.contains(name)) suggestions.add(name);
+ if (suggestions.size() >= 5) break;
+ }
+ }
+
+ StringBuilder hint = new StringBuilder();
+ hint.append("Cannot resolve function target: ").append(target)
+ .append(". Try: ghidra function list --filter ").append(target);
+ if (!suggestions.isEmpty()) {
+ hint.append(". Closest matches: ").append(String.join(", ", suggestions));
+ }
+ return hint.toString();
+ }
+
+ private int levenshteinDistance(String a, String b) {
+ int n = a.length();
+ int m = b.length();
+ int[][] dp = new int[n + 1][m + 1];
+
+ for (int i = 0; i <= n; i++) dp[i][0] = i;
+ for (int j = 0; j <= m; j++) dp[0][j] = j;
+
+ for (int i = 1; i <= n; i++) {
+ for (int j = 1; j <= m; j++) {
+ int cost = a.charAt(i - 1) == b.charAt(j - 1) ? 0 : 1;
+ dp[i][j] = Math.min(
+ Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1),
+ dp[i - 1][j - 1] + cost
+ );
+ }
+ }
+ return dp[n][m];
+ }
+
+ private JsonObject handleGetFunction(JsonObject args) {
+ if (currentProgram == null) return errorResult("No program loaded");
+
+ String target = getArgString(args, "address");
+ if (target == null || target.isEmpty()) {
+ return errorResult("Function target required");
+ }
+
+ Address addr = resolveAddress(target);
+ if (addr == null) {
+ return errorResult(buildFunctionTargetHint(target));
+ }
+
+ Function func = currentProgram.getFunctionManager().getFunctionContaining(addr);
+ if (func == null) {
+ return errorResult("No function at target " + target + ". Try: ghidra function list --filter " + target);
+ }
+ return functionToJson(func);
+ }
+
+ private JsonObject handleRenameFunction(JsonObject args) {
+ if (currentProgram == null) return errorResult("No program loaded");
+
+ String oldTarget = getArgString(args, "old_name");
+ String newName = getArgString(args, "new_name");
+ if (oldTarget == null || newName == null || oldTarget.isEmpty() || newName.isEmpty()) {
+ return errorResult("old_name and new_name required");
+ }
+
+ try {
+ Function func = findFunctionByNameOrAddress(oldTarget);
+ if (func == null) {
+ return errorResult(buildFunctionTargetHint(oldTarget));
+ }
+
+ int txId = currentProgram.startTransaction("Rename function");
+ try {
+ String oldName = func.getName();
+ func.setName(newName, SourceType.USER_DEFINED);
+ currentProgram.endTransaction(txId, true);
+
+ JsonObject result = new JsonObject();
+ result.addProperty("status", "renamed");
+ result.addProperty("old_name", oldName);
+ result.addProperty("new_name", newName);
+ result.addProperty("address", func.getEntryPoint().toString());
+ return result;
+ } catch (Exception e) {
+ currentProgram.endTransaction(txId, false);
+ throw e;
+ }
+ } catch (Exception e) {
+ return errorResult("Failed to rename function: " + e.getMessage());
+ }
+ }
+
+ private JsonObject handleCreateFunction(JsonObject args) {
+ if (currentProgram == null) return errorResult("No program loaded");
+
+ String target = getArgString(args, "address");
+ String requestedName = getArgString(args, "name");
+ if (target == null || target.isEmpty()) {
+ return errorResult("Function target required");
+ }
+
+ try {
+ Address addr = resolveAddress(target);
+ if (addr == null) {
+ return errorResult("Invalid function target: " + target + ". Expected address/symbol/FUN_.");
+ }
+
+ FunctionManager fm = currentProgram.getFunctionManager();
+ if (fm.getFunctionContaining(addr) != null) {
+ return errorResult("Function already exists at " + addr.toString());
+ }
+
+ String functionName = (requestedName == null || requestedName.isEmpty())
+ ? ("FUN_" + addr.toString().replace(":", ""))
+ : requestedName;
+
+ int txId = currentProgram.startTransaction("Create function");
+ try {
+ Function created = fm.createFunction(functionName, addr, null, SourceType.USER_DEFINED);
+ if (created == null) {
+ currentProgram.endTransaction(txId, false);
+ return errorResult("Failed to create function at " + addr.toString());
+ }
+ currentProgram.endTransaction(txId, true);
+
+ JsonObject result = new JsonObject();
+ result.addProperty("status", "created");
+ result.addProperty("name", created.getName());
+ result.addProperty("address", created.getEntryPoint().toString());
+ return result;
+ } catch (Exception e) {
+ currentProgram.endTransaction(txId, false);
+ throw e;
+ }
+ } catch (Exception e) {
+ return errorResult("Failed to create function: " + e.getMessage());
+ }
+ }
+
+ private JsonObject handleDeleteFunction(JsonObject args) {
+ if (currentProgram == null) return errorResult("No program loaded");
+
+ String target = getArgString(args, "address");
+ if (target == null || target.isEmpty()) {
+ return errorResult("Function target required");
+ }
+
+ try {
+ FunctionManager fm = currentProgram.getFunctionManager();
+ Function func = findFunctionByNameOrAddress(target);
+ if (func == null) {
+ return errorResult(buildFunctionTargetHint(target));
+ }
+
+ Address entry = func.getEntryPoint();
+ String name = func.getName();
+ int txId = currentProgram.startTransaction("Delete function");
+ try {
+ fm.removeFunction(entry);
+ currentProgram.endTransaction(txId, true);
+ } catch (Exception e) {
+ currentProgram.endTransaction(txId, false);
+ throw e;
+ }
+
+ JsonObject result = new JsonObject();
+ result.addProperty("status", "deleted");
+ result.addProperty("name", name);
+ result.addProperty("address", entry.toString());
+ return result;
+ } catch (Exception e) {
+ return errorResult("Failed to delete function: " + e.getMessage());
+ }
+ }
+
private JsonObject handleDecompile(JsonObject args) {
if (currentProgram == null) {
return errorResult("No program loaded");
@@ -491,7 +732,7 @@ public class GhidraCliBridge extends GhidraScript {
Address addr = resolveAddress(addrStr);
if (addr == null) {
- return errorResult("Cannot resolve address or function name: " + addrStr);
+ return errorResult(buildFunctionTargetHint(addrStr));
}
FunctionManager fm = currentProgram.getFunctionManager();
@@ -672,7 +913,7 @@ public class GhidraCliBridge extends GhidraScript {
Address addr = resolveAddress(addrStr);
if (addr == null) {
- return errorResult("Cannot resolve address or function name: " + addrStr);
+ return errorResult(buildFunctionTargetHint(addrStr));
}
JsonArray xrefs = new JsonArray();
@@ -719,7 +960,7 @@ public class GhidraCliBridge extends GhidraScript {
Address addr = resolveAddress(addrStr);
if (addr == null) {
- return errorResult("Cannot resolve address or function name: " + addrStr);
+ return errorResult(buildFunctionTargetHint(addrStr));
}
JsonArray xrefs = new JsonArray();
@@ -1310,7 +1551,7 @@ public class GhidraCliBridge extends GhidraScript {
Function targetFunc = findFunctionByNameOrAddress(functionTarget);
if (targetFunc == null) {
- return errorResult("Function not found: " + functionTarget);
+ return errorResult(buildFunctionTargetHint(functionTarget));
}
ReferenceManager refMgr = currentProgram.getReferenceManager();
@@ -2100,7 +2341,7 @@ public class GhidraCliBridge extends GhidraScript {
int depth = getArgInt(args, "depth", 1);
Function targetFunc = findFunctionByNameOrAddress(funcName);
- if (targetFunc == null) return errorResult("Function not found: " + funcName);
+ if (targetFunc == null) return errorResult(buildFunctionTargetHint(funcName));
ReferenceManager refMgr = currentProgram.getReferenceManager();
FunctionManager fm = currentProgram.getFunctionManager();
@@ -2151,7 +2392,7 @@ public class GhidraCliBridge extends GhidraScript {
int depth = getArgInt(args, "depth", 1);
Function targetFunc = findFunctionByNameOrAddress(funcName);
- if (targetFunc == null) return errorResult("Function not found: " + funcName);
+ if (targetFunc == null) return errorResult(buildFunctionTargetHint(funcName));
ReferenceManager refMgr = currentProgram.getReferenceManager();
FunctionManager fm = currentProgram.getFunctionManager();
@@ -2300,8 +2541,8 @@ public class GhidraCliBridge extends GhidraScript {
Function func1 = findFunctionByNameOrAddress(func1Target);
Function func2 = findFunctionByNameOrAddress(func2Target);
- if (func1 == null) return errorResult("Function not found: " + func1Target);
- if (func2 == null) return errorResult("Function not found: " + func2Target);
+ if (func1 == null) return errorResult(buildFunctionTargetHint(func1Target));
+ if (func2 == null) return errorResult(buildFunctionTargetHint(func2Target));
DecompInterface decompiler = new DecompInterface();
try {
diff --git a/src/main.rs b/src/main.rs
index e0d37d3..5dfe5b2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -628,20 +628,25 @@ fn execute_via_bridge(
"memory" => client.memory_map(),
other => anyhow::bail!("Query type '{}' not supported", other),
},
- Commands::Decompile(args) => client.decompile(args.target.clone()),
+ Commands::Decompile(args) => client.decompile(args.resolved_target().to_string()),
Commands::Function(cmd) => {
use cli::FunctionCommands;
match cmd {
FunctionCommands::List(opts) => {
client.list_functions(opts.limit.or(default_limit), opts.filter.clone())
}
- FunctionCommands::Decompile(args) => client.decompile(args.target.clone()),
- FunctionCommands::Get(args) => {
- client.send_command("get_function", Some(json!({"address": args.target})))
+ FunctionCommands::Decompile(args) => {
+ client.decompile(args.resolved_target().to_string())
}
- FunctionCommands::Disasm(args) => client.disasm(&args.target, None),
- FunctionCommands::Calls(args) => client.find_calls(&args.target),
- FunctionCommands::XRefs(args) => client.xrefs_to(args.target.clone()),
+ FunctionCommands::Get(args) => {
+ client.send_command(
+ "get_function",
+ Some(json!({"address": args.resolved_target()})),
+ )
+ }
+ FunctionCommands::Disasm(args) => client.disasm(args.resolved_target(), None),
+ FunctionCommands::Calls(args) => client.find_calls(args.resolved_target()),
+ FunctionCommands::XRefs(args) => client.xrefs_to(args.resolved_target().to_string()),
FunctionCommands::Rename(args) => client.send_command(
"rename_function",
Some(json!({
@@ -659,7 +664,7 @@ fn execute_via_bridge(
FunctionCommands::Delete(args) => client.send_command(
"delete_function",
Some(json!({
- "address": args.target,
+ "address": args.resolved_target(),
})),
),
}
@@ -712,8 +717,8 @@ fn execute_via_bridge(
Commands::XRef(cmd) => {
use cli::XRefCommands;
match cmd {
- XRefCommands::To(args) => client.xrefs_to(args.address.clone()),
- XRefCommands::From(args) => client.xrefs_from(args.address.clone()),
+ XRefCommands::To(args) => client.xrefs_to(args.resolved_target().to_string()),
+ XRefCommands::From(args) => client.xrefs_from(args.resolved_target().to_string()),
XRefCommands::List(_) => client.send_command("xrefs_list", None),
}
}
@@ -777,8 +782,12 @@ fn execute_via_bridge(
use cli::GraphCommands;
match cmd {
GraphCommands::Calls(opts) => client.graph_calls(opts.limit.or(default_limit)),
- GraphCommands::Callers(args) => client.graph_callers(&args.function, args.depth),
- GraphCommands::Callees(args) => client.graph_callees(&args.function, args.depth),
+ GraphCommands::Callers(args) => {
+ client.graph_callers(args.resolved_target(), args.depth)
+ }
+ GraphCommands::Callees(args) => {
+ client.graph_callees(args.resolved_target(), args.depth)
+ }
GraphCommands::Export(args) => client.graph_export(&args.format),
}
}
@@ -788,7 +797,7 @@ fn execute_via_bridge(
FindCommands::String(args) => client.find_string(&args.pattern),
FindCommands::Bytes(args) => client.find_bytes(&args.hex),
FindCommands::Function(args) => client.find_function(&args.pattern),
- FindCommands::Calls(args) => client.find_calls(&args.function),
+ FindCommands::Calls(args) => client.find_calls(args.resolved_target()),
FindCommands::Crypto(_) => client.find_crypto(),
FindCommands::Interesting(_) => client.find_interesting(),
}
@@ -819,7 +828,7 @@ fn execute_via_bridge(
ScriptCommands::List => client.script_list(),
}
}
- Commands::Disasm(args) => client.disasm(&args.address, args.num_instructions),
+ Commands::Disasm(args) => client.disasm(args.resolved_target(), args.num_instructions),
Commands::Batch(args) => {
// Read batch file and execute each command locally
let content = std::fs::read_to_string(&args.script_file)
diff --git a/tests/README.md b/tests/README.md
index db1961e..30dc5c9 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -76,7 +76,7 @@ cargo test --test command_tests test_version
Run tests that don't need Ghidra:
```bash
-cargo test --test e2e --test command_tests --test output_format_integration
+cargo test --test e2e --test output_format_integration
```
## Test Requirements