diff --git a/bridge_mcp_hydra.py b/bridge_mcp_hydra.py
index 210825d..d59c834 100644
--- a/bridge_mcp_hydra.py
+++ b/bridge_mcp_hydra.py
@@ -302,6 +302,43 @@ def search_functions_by_name(port: int = DEFAULT_GHIDRA_PORT, query: str = "", o
return ["Error: query string is required"]
return safe_get(port, "functions", {"query": query, "offset": offset, "limit": limit})
+@mcp.tool()
+def list_variables(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100, search: str = "") -> list:
+ """List global variables with optional search"""
+ params = {"offset": offset, "limit": limit}
+ if search:
+ params["search"] = search
+ return safe_get(port, "variables", params)
+
+@mcp.tool()
+def list_function_variables(port: int = DEFAULT_GHIDRA_PORT, function: str = "") -> str:
+ """List variables in a specific function"""
+ if not function:
+ return "Error: function name is required"
+
+ encoded_name = quote(function)
+ return safe_get(port, f"functions/{encoded_name}/variables", {})
+
+@mcp.tool()
+def rename_variable(port: int = DEFAULT_GHIDRA_PORT, function: str = "", name: str = "", new_name: str = "") -> str:
+ """Rename a variable in a function"""
+ if not function or not name or not new_name:
+ return "Error: function, name, and new_name parameters are required"
+
+ encoded_function = quote(function)
+ encoded_var = quote(name)
+ return safe_put(port, f"functions/{encoded_function}/variables/{encoded_var}", {"newName": new_name})
+
+@mcp.tool()
+def retype_variable(port: int = DEFAULT_GHIDRA_PORT, function: str = "", name: str = "", data_type: str = "") -> str:
+ """Change the data type of a variable in a function"""
+ if not function or not name or not data_type:
+ return "Error: function, name, and data_type parameters are required"
+
+ encoded_function = quote(function)
+ encoded_var = quote(name)
+ return safe_put(port, f"functions/{encoded_function}/variables/{encoded_var}", {"dataType": data_type})
+
# Handle graceful shutdown
import signal
import os
diff --git a/pom.xml b/pom.xml
index 6f18125..14e0cad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
eu.starsong.ghidra
GhydraMCP
jar
- 1.1
+ ${revision}
GhydraMCP
https://github.com/teal-bauer/GhydraMCP
@@ -16,6 +16,8 @@
${project.basedir}/lib
true
true
+ dev-SNAPSHOT
+ yyyyMMdd-HHmmss
@@ -101,6 +103,56 @@
+
+
+ io.github.git-commit-id
+ git-commit-id-maven-plugin
+ 5.0.0
+
+
+ get-git-info
+ initialize
+
+ revision
+
+
+
+
+ true
+ ${project.build.outputDirectory}/git.properties
+
+ git.commit.id.abbrev
+ git.commit.time
+ git.closest.tag.name
+ git.build.version
+
+ full
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.4.0
+
+
+ set-revision-from-git
+ initialize
+
+ regex-property
+
+
+ revision
+ ${git.commit.id.abbrev}-${maven.build.timestamp}
+ .*
+ $0
+ false
+
+
+
+
+
maven-jar-plugin
@@ -108,6 +160,9 @@
src/main/resources/META-INF/MANIFEST.MF
+
+ ${revision}
+
GhydraMCP
@@ -239,4 +294,4 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java b/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java
index b5b78ce..89a8533 100644
--- a/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java
+++ b/src/main/java/eu/starsong/ghidra/GhydraMCPPlugin.java
@@ -4,13 +4,24 @@ import ghidra.framework.plugintool.*;
import ghidra.framework.main.ApplicationLevelPlugin;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.GlobalNamespace;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.pcode.HighVariable;
+import ghidra.program.model.pcode.HighSymbol;
+import ghidra.program.model.pcode.VarnodeAST;
+import ghidra.program.model.pcode.HighFunction;
+import ghidra.program.model.pcode.HighFunctionDBUtil;
import ghidra.program.model.symbol.*;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
+import ghidra.app.decompiler.ClangNode;
+import ghidra.app.decompiler.ClangTokenGroup;
+import ghidra.app.decompiler.ClangVariableToken;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.ProgramManager;
+import ghidra.app.util.demangler.DemanglerUtil;
import ghidra.framework.model.Project;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.PluginInfo;
@@ -105,25 +116,69 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
server.createContext("/functions/", exchange -> {
String path = exchange.getRequestURI().getPath();
- String name = path.substring(path.lastIndexOf('/') + 1);
+
+ // Handle sub-paths: /functions/{name}
+ // or /functions/{name}/variables
+ String[] pathParts = path.split("/");
+
+ if (pathParts.length < 3) {
+ exchange.sendResponseHeaders(400, -1); // Bad Request
+ return;
+ }
+
+ String functionName = pathParts[2];
try {
- name = java.net.URLDecoder.decode(name, StandardCharsets.UTF_8.name());
+ functionName = java.net.URLDecoder.decode(functionName, StandardCharsets.UTF_8.name());
} catch (Exception e) {
Msg.error(this, "Failed to decode function name", e);
exchange.sendResponseHeaders(400, -1); // Bad Request
return;
}
- if ("GET".equals(exchange.getRequestMethod())) {
- sendResponse(exchange, decompileFunctionByName(name));
- } else if ("PUT".equals(exchange.getRequestMethod())) {
- Map params = parsePostParams(exchange);
- String newName = params.get("newName");
- String response = renameFunction(name, newName)
- ? "Renamed successfully" : "Rename failed";
- sendResponse(exchange, response);
+ // Check if we're dealing with a variables request
+ if (pathParts.length > 3 && "variables".equals(pathParts[3])) {
+ if ("GET".equals(exchange.getRequestMethod())) {
+ // List all variables in function
+ sendResponse(exchange, listVariablesInFunction(functionName));
+ } else if ("PUT".equals(exchange.getRequestMethod()) && pathParts.length > 4) {
+ // Handle operations on a specific variable
+ String variableName = pathParts[4];
+ try {
+ variableName = java.net.URLDecoder.decode(variableName, StandardCharsets.UTF_8.name());
+ } catch (Exception e) {
+ Msg.error(this, "Failed to decode variable name", e);
+ exchange.sendResponseHeaders(400, -1);
+ return;
+ }
+
+ Map params = parsePostParams(exchange);
+ if (params.containsKey("newName")) {
+ // Rename variable
+ String result = renameVariable(functionName, variableName, params.get("newName"));
+ sendResponse(exchange, result);
+ } else if (params.containsKey("dataType")) {
+ // Retype variable
+ String result = retypeVariable(functionName, variableName, params.get("dataType"));
+ sendResponse(exchange, result);
+ } else {
+ sendResponse(exchange, "Missing required parameter: newName or dataType");
+ }
+ } else {
+ exchange.sendResponseHeaders(405, -1); // Method Not Allowed
+ }
} else {
- exchange.sendResponseHeaders(405, -1); // Method Not Allowed
+ // Simple function operations
+ if ("GET".equals(exchange.getRequestMethod())) {
+ sendResponse(exchange, decompileFunctionByName(functionName));
+ } else if ("PUT".equals(exchange.getRequestMethod())) {
+ Map params = parsePostParams(exchange);
+ String newName = params.get("newName");
+ String response = renameFunction(functionName, newName)
+ ? "Renamed successfully" : "Rename failed";
+ sendResponse(exchange, response);
+ } else {
+ exchange.sendResponseHeaders(405, -1); // Method Not Allowed
+ }
}
});
@@ -201,6 +256,24 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
exchange.sendResponseHeaders(405, -1); // Method Not Allowed
}
});
+
+ // Global variables endpoint
+ server.createContext("/variables", exchange -> {
+ if ("GET".equals(exchange.getRequestMethod())) {
+ Map qparams = parseQueryParams(exchange);
+ int offset = parseIntOrDefault(qparams.get("offset"), 0);
+ int limit = parseIntOrDefault(qparams.get("limit"), 100);
+ String search = qparams.get("search");
+
+ if (search != null && !search.isEmpty()) {
+ sendResponse(exchange, searchVariables(search, offset, limit));
+ } else {
+ sendResponse(exchange, listGlobalVariables(offset, limit));
+ }
+ } else {
+ exchange.sendResponseHeaders(405, -1); // Method Not Allowed
+ }
+ });
// Instance management endpoints
server.createContext("/instances", exchange -> {
@@ -563,6 +636,373 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
Msg.error(this, "Failed to execute rename data on Swing thread", e);
}
}
+
+ // ----------------------------------------------------------------------------------
+ // New variable handling methods
+ // ----------------------------------------------------------------------------------
+
+ private String listVariablesInFunction(String functionName) {
+ Program program = getCurrentProgram();
+ if (program == null) return "No program loaded";
+
+ DecompInterface decomp = new DecompInterface();
+ try {
+ if (!decomp.openProgram(program)) {
+ return "Failed to initialize decompiler";
+ }
+
+ Function function = findFunctionByName(program, functionName);
+ if (function == null) {
+ return "Function not found: " + functionName;
+ }
+
+ DecompileResults results = decomp.decompileFunction(function, 30, new ConsoleTaskMonitor());
+ if (results == null || !results.decompileCompleted()) {
+ return "Failed to decompile function: " + functionName;
+ }
+
+ // Get high-level pcode representation for the function
+ HighFunction highFunction = results.getHighFunction();
+ if (highFunction == null) {
+ return "Failed to get high function for: " + functionName;
+ }
+
+ // Get local variables
+ List variables = new ArrayList<>();
+ Iterator symbolIter = highFunction.getLocalSymbolMap().getSymbols();
+ while (symbolIter.hasNext()) {
+ HighSymbol symbol = symbolIter.next();
+ if (symbol.getHighVariable() != null) {
+ DataType dt = symbol.getDataType();
+ String dtName = dt != null ? dt.getName() : "unknown";
+ variables.add(String.format("%s: %s @ %s",
+ symbol.getName(), dtName, symbol.getPCAddress()));
+ }
+ }
+
+ // Get parameters
+ List parameters = new ArrayList<>();
+ // In older Ghidra versions, we need to filter symbols to find parameters
+ symbolIter = highFunction.getLocalSymbolMap().getSymbols();
+ while (symbolIter.hasNext()) {
+ HighSymbol symbol = symbolIter.next();
+ if (symbol.isParameter()) {
+ DataType dt = symbol.getDataType();
+ String dtName = dt != null ? dt.getName() : "unknown";
+ parameters.add(String.format("%s: %s (parameter)",
+ symbol.getName(), dtName));
+ }
+ }
+
+ // Format the response
+ StringBuilder sb = new StringBuilder();
+ sb.append("Function: ").append(functionName).append("\n\n");
+
+ sb.append("Parameters:\n");
+ if (parameters.isEmpty()) {
+ sb.append(" none\n");
+ } else {
+ for (String param : parameters) {
+ sb.append(" ").append(param).append("\n");
+ }
+ }
+
+ sb.append("\nLocal Variables:\n");
+ if (variables.isEmpty()) {
+ sb.append(" none\n");
+ } else {
+ for (String var : variables) {
+ sb.append(" ").append(var).append("\n");
+ }
+ }
+
+ return sb.toString();
+ } finally {
+ decomp.dispose();
+ }
+ }
+
+ private String renameVariable(String functionName, String oldName, String newName) {
+ if (oldName == null || oldName.isEmpty() || newName == null || newName.isEmpty()) {
+ return "Both old and new variable names are required";
+ }
+
+ Program program = getCurrentProgram();
+ if (program == null) return "No program loaded";
+
+ AtomicReference result = new AtomicReference<>("Variable rename failed");
+
+ try {
+ SwingUtilities.invokeAndWait(() -> {
+ int tx = program.startTransaction("Rename variable via HTTP");
+ try {
+ Function function = findFunctionByName(program, functionName);
+ if (function == null) {
+ result.set("Function not found: " + functionName);
+ return;
+ }
+
+ // Initialize decompiler
+ DecompInterface decomp = new DecompInterface();
+ decomp.openProgram(program);
+ DecompileResults decompRes = decomp.decompileFunction(function, 30, new ConsoleTaskMonitor());
+
+ if (decompRes == null || !decompRes.decompileCompleted()) {
+ result.set("Failed to decompile function: " + functionName);
+ return;
+ }
+
+ HighFunction highFunction = decompRes.getHighFunction();
+ if (highFunction == null) {
+ result.set("Failed to get high function");
+ return;
+ }
+
+ // Find the variable by name
+ HighSymbol targetSymbol = null;
+ Iterator symbolIter = highFunction.getLocalSymbolMap().getSymbols();
+ while (symbolIter.hasNext()) {
+ HighSymbol symbol = symbolIter.next();
+ if (symbol.getName().equals(oldName)) {
+ targetSymbol = symbol;
+ break;
+ }
+ }
+
+ if (targetSymbol == null) {
+ result.set("Variable not found: " + oldName);
+ return;
+ }
+
+ // Rename the variable
+ HighFunctionDBUtil.updateDBVariable(targetSymbol, newName, targetSymbol.getDataType(),
+ SourceType.USER_DEFINED);
+
+ result.set("Variable renamed from '" + oldName + "' to '" + newName + "'");
+ } catch (Exception e) {
+ Msg.error(this, "Error renaming variable", e);
+ result.set("Error: " + e.getMessage());
+ } finally {
+ program.endTransaction(tx, true);
+ }
+ });
+ } catch (InterruptedException | InvocationTargetException e) {
+ Msg.error(this, "Failed to execute on Swing thread", e);
+ result.set("Error: " + e.getMessage());
+ }
+
+ return result.get();
+ }
+
+ private String retypeVariable(String functionName, String varName, String dataTypeName) {
+ if (varName == null || varName.isEmpty() || dataTypeName == null || dataTypeName.isEmpty()) {
+ return "Both variable name and data type are required";
+ }
+
+ Program program = getCurrentProgram();
+ if (program == null) return "No program loaded";
+
+ AtomicReference result = new AtomicReference<>("Variable retype failed");
+
+ try {
+ SwingUtilities.invokeAndWait(() -> {
+ int tx = program.startTransaction("Retype variable via HTTP");
+ try {
+ Function function = findFunctionByName(program, functionName);
+ if (function == null) {
+ result.set("Function not found: " + functionName);
+ return;
+ }
+
+ // Initialize decompiler
+ DecompInterface decomp = new DecompInterface();
+ decomp.openProgram(program);
+ DecompileResults decompRes = decomp.decompileFunction(function, 30, new ConsoleTaskMonitor());
+
+ if (decompRes == null || !decompRes.decompileCompleted()) {
+ result.set("Failed to decompile function: " + functionName);
+ return;
+ }
+
+ HighFunction highFunction = decompRes.getHighFunction();
+ if (highFunction == null) {
+ result.set("Failed to get high function");
+ return;
+ }
+
+ // Find the variable by name
+ HighSymbol targetSymbol = null;
+ Iterator symbolIter = highFunction.getLocalSymbolMap().getSymbols();
+ while (symbolIter.hasNext()) {
+ HighSymbol symbol = symbolIter.next();
+ if (symbol.getName().equals(varName)) {
+ targetSymbol = symbol;
+ break;
+ }
+ }
+
+ if (targetSymbol == null) {
+ result.set("Variable not found: " + varName);
+ return;
+ }
+
+ // Find the data type by name
+ DataType dataType = findDataType(program, dataTypeName);
+ if (dataType == null) {
+ result.set("Data type not found: " + dataTypeName);
+ return;
+ }
+
+ // Retype the variable
+ HighFunctionDBUtil.updateDBVariable(targetSymbol, targetSymbol.getName(), dataType,
+ SourceType.USER_DEFINED);
+
+ result.set("Variable '" + varName + "' retyped to '" + dataTypeName + "'");
+ } catch (Exception e) {
+ Msg.error(this, "Error retyping variable", e);
+ result.set("Error: " + e.getMessage());
+ } finally {
+ program.endTransaction(tx, true);
+ }
+ });
+ } catch (InterruptedException | InvocationTargetException e) {
+ Msg.error(this, "Failed to execute on Swing thread", e);
+ result.set("Error: " + e.getMessage());
+ }
+
+ return result.get();
+ }
+
+ private String listGlobalVariables(int offset, int limit) {
+ Program program = getCurrentProgram();
+ if (program == null) return "No program loaded";
+
+ List globalVars = new ArrayList<>();
+ SymbolTable symbolTable = program.getSymbolTable();
+ SymbolIterator it = symbolTable.getSymbolIterator();
+
+ while (it.hasNext()) {
+ Symbol symbol = it.next();
+ // Check for globals - look for symbols that are in global space and not functions
+ if (symbol.isGlobal() &&
+ symbol.getSymbolType() != SymbolType.FUNCTION &&
+ symbol.getSymbolType() != SymbolType.LABEL) {
+ globalVars.add(String.format("%s @ %s",
+ symbol.getName(), symbol.getAddress()));
+ }
+ }
+
+ Collections.sort(globalVars);
+ return paginateList(globalVars, offset, limit);
+ }
+
+ private String searchVariables(String searchTerm, int offset, int limit) {
+ Program program = getCurrentProgram();
+ if (program == null) return "No program loaded";
+ if (searchTerm == null || searchTerm.isEmpty()) return "Search term is required";
+
+ List matchedVars = new ArrayList<>();
+
+ // Search global variables
+ SymbolTable symbolTable = program.getSymbolTable();
+ SymbolIterator it = symbolTable.getSymbolIterator();
+ while (it.hasNext()) {
+ Symbol symbol = it.next();
+ if (symbol.isGlobal() &&
+ symbol.getSymbolType() != SymbolType.FUNCTION &&
+ symbol.getSymbolType() != SymbolType.LABEL &&
+ symbol.getName().toLowerCase().contains(searchTerm.toLowerCase())) {
+ matchedVars.add(String.format("%s @ %s (global)",
+ symbol.getName(), symbol.getAddress()));
+ }
+ }
+
+ // Search local variables in functions
+ DecompInterface decomp = new DecompInterface();
+ try {
+ if (decomp.openProgram(program)) {
+ for (Function function : program.getFunctionManager().getFunctions(true)) {
+ DecompileResults results = decomp.decompileFunction(function, 30, new ConsoleTaskMonitor());
+ if (results != null && results.decompileCompleted()) {
+ HighFunction highFunc = results.getHighFunction();
+ if (highFunc != null) {
+ // Check each local variable and parameter
+ Iterator symbolIter = highFunc.getLocalSymbolMap().getSymbols();
+ while (symbolIter.hasNext()) {
+ HighSymbol symbol = symbolIter.next();
+ if (symbol.getName().toLowerCase().contains(searchTerm.toLowerCase())) {
+ if (symbol.isParameter()) {
+ matchedVars.add(String.format("%s in %s (parameter)",
+ symbol.getName(), function.getName()));
+ } else {
+ matchedVars.add(String.format("%s in %s @ %s (local)",
+ symbol.getName(), function.getName(), symbol.getPCAddress()));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ decomp.dispose();
+ }
+
+ Collections.sort(matchedVars);
+
+ if (matchedVars.isEmpty()) {
+ return "No variables matching '" + searchTerm + "'";
+ }
+ return paginateList(matchedVars, offset, limit);
+ }
+
+ // ----------------------------------------------------------------------------------
+ // Helper methods
+ // ----------------------------------------------------------------------------------
+
+ private Function findFunctionByName(Program program, String name) {
+ if (program == null || name == null || name.isEmpty()) {
+ return null;
+ }
+
+ for (Function function : program.getFunctionManager().getFunctions(true)) {
+ if (function.getName().equals(name)) {
+ return function;
+ }
+ }
+ return null;
+ }
+
+ private DataType findDataType(Program program, String name) {
+ if (program == null || name == null || name.isEmpty()) {
+ return null;
+ }
+
+ DataTypeManager dtm = program.getDataTypeManager();
+
+ // First try direct lookup
+ DataType dt = dtm.getDataType("/" + name);
+ if (dt != null) {
+ return dt;
+ }
+
+ // Try built-in types by simple name
+ dt = dtm.findDataType(name);
+ if (dt != null) {
+ return dt;
+ }
+
+ // Try to find a matching type by name only
+ Iterator dtIter = dtm.getAllDataTypes();
+ while (dtIter.hasNext()) {
+ DataType type = dtIter.next();
+ if (type.getName().equals(name)) {
+ return type;
+ }
+ }
+
+ return null;
+ }
// ----------------------------------------------------------------------------------
// Utility: parse query params, parse post params, pagination, etc.
@@ -711,4 +1151,4 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
activeInstances.remove(port);
super.dispose();
}
-}
+}
\ No newline at end of file