GhidraMCPPlugin.java•67.4 kB
package com.lauriewired;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.GlobalNamespace;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.program.model.symbol.ReferenceManager;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceIterator;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.HighSymbol;
import ghidra.program.model.pcode.LocalSymbolMap;
import ghidra.program.model.pcode.HighFunctionDBUtil;
import ghidra.program.model.pcode.HighFunctionDBUtil.ReturnCommitOption;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.CodeViewerService;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.PseudoDisassembler;
import ghidra.app.cmd.function.SetVariableNameCmd;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.listing.LocalVariableImpl;
import ghidra.program.model.listing.ParameterImpl;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.util.ProgramLocation;
import ghidra.util.Msg;
import ghidra.util.task.ConsoleTaskMonitor;
import ghidra.util.task.TaskMonitor;
import ghidra.program.model.pcode.HighVariable;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.data.Undefined1DataType;
import ghidra.program.model.listing.Variable;
import ghidra.app.decompiler.component.DecompilerUtils;
import ghidra.app.decompiler.ClangToken;
import ghidra.framework.options.Options;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import javax.swing.SwingUtilities;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = ghidra.app.DeveloperPluginPackage.NAME,
category = PluginCategoryNames.ANALYSIS,
shortDescription = "HTTP server plugin",
description = "Starts an embedded HTTP server to expose program data. Port configurable via Tool Options."
)
public class GhidraMCPPlugin extends Plugin {
private HttpServer server;
private static final String OPTION_CATEGORY_NAME = "GhidraMCP HTTP Server";
private static final String PORT_OPTION_NAME = "Server Port";
private static final int DEFAULT_PORT = 8080;
public GhidraMCPPlugin(PluginTool tool) {
super(tool);
Msg.info(this, "GhidraMCPPlugin loading...");
// Register the configuration option
Options options = tool.getOptions(OPTION_CATEGORY_NAME);
options.registerOption(PORT_OPTION_NAME, DEFAULT_PORT,
null, // No help location for now
"The network port number the embedded HTTP server will listen on. " +
"Requires Ghidra restart or plugin reload to take effect after changing.");
try {
startServer();
}
catch (IOException e) {
Msg.error(this, "Failed to start HTTP server", e);
}
Msg.info(this, "GhidraMCPPlugin loaded!");
}
private void startServer() throws IOException {
// Read the configured port
Options options = tool.getOptions(OPTION_CATEGORY_NAME);
int port = options.getInt(PORT_OPTION_NAME, DEFAULT_PORT);
// Stop existing server if running (e.g., if plugin is reloaded)
if (server != null) {
Msg.info(this, "Stopping existing HTTP server before starting new one.");
server.stop(0);
server = null;
}
server = HttpServer.create(new InetSocketAddress(port), 0);
// Each listing endpoint uses offset & limit from query params:
server.createContext("/methods", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, getAllFunctionNames(offset, limit));
});
server.createContext("/classes", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, getAllClassNames(offset, limit));
});
server.createContext("/decompile", exchange -> {
String name = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
sendResponse(exchange, decompileFunctionByName(name));
});
server.createContext("/renameFunction", exchange -> {
Map<String, String> params = parsePostParams(exchange);
String response = renameFunction(params.get("oldName"), params.get("newName"))
? "Renamed successfully" : "Rename failed";
sendResponse(exchange, response);
});
server.createContext("/renameData", exchange -> {
Map<String, String> params = parsePostParams(exchange);
renameDataAtAddress(params.get("address"), params.get("newName"));
sendResponse(exchange, "Rename data attempted");
});
server.createContext("/renameVariable", exchange -> {
Map<String, String> params = parsePostParams(exchange);
String functionName = params.get("functionName");
String oldName = params.get("oldName");
String newName = params.get("newName");
String result = renameVariableInFunction(functionName, oldName, newName);
sendResponse(exchange, result);
});
server.createContext("/segments", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, listSegments(offset, limit));
});
server.createContext("/imports", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, listImports(offset, limit));
});
server.createContext("/exports", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, listExports(offset, limit));
});
server.createContext("/namespaces", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, listNamespaces(offset, limit));
});
server.createContext("/data", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, listDefinedData(offset, limit));
});
server.createContext("/searchFunctions", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
String searchTerm = qparams.get("query");
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, searchFunctionsByName(searchTerm, offset, limit));
});
// New API endpoints based on requirements
server.createContext("/get_function_by_address", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
String address = qparams.get("address");
sendResponse(exchange, getFunctionByAddress(address));
});
server.createContext("/get_current_address", exchange -> {
sendResponse(exchange, getCurrentAddress());
});
server.createContext("/get_current_function", exchange -> {
sendResponse(exchange, getCurrentFunction());
});
server.createContext("/list_functions", exchange -> {
sendResponse(exchange, listFunctions());
});
server.createContext("/decompile_function", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
String address = qparams.get("address");
sendResponse(exchange, decompileFunctionByAddress(address));
});
server.createContext("/disassemble_function", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
String address = qparams.get("address");
sendResponse(exchange, disassembleFunction(address));
});
server.createContext("/set_decompiler_comment", exchange -> {
Map<String, String> params = parsePostParams(exchange);
String address = params.get("address");
String comment = params.get("comment");
boolean success = setDecompilerComment(address, comment);
sendResponse(exchange, success ? "Comment set successfully" : "Failed to set comment");
});
server.createContext("/set_disassembly_comment", exchange -> {
Map<String, String> params = parsePostParams(exchange);
String address = params.get("address");
String comment = params.get("comment");
boolean success = setDisassemblyComment(address, comment);
sendResponse(exchange, success ? "Comment set successfully" : "Failed to set comment");
});
server.createContext("/rename_function_by_address", exchange -> {
Map<String, String> params = parsePostParams(exchange);
String functionAddress = params.get("function_address");
String newName = params.get("new_name");
boolean success = renameFunctionByAddress(functionAddress, newName);
sendResponse(exchange, success ? "Function renamed successfully" : "Failed to rename function");
});
server.createContext("/set_function_prototype", exchange -> {
Map<String, String> params = parsePostParams(exchange);
String functionAddress = params.get("function_address");
String prototype = params.get("prototype");
// Call the set prototype function and get detailed result
PrototypeResult result = setFunctionPrototype(functionAddress, prototype);
if (result.isSuccess()) {
// Even with successful operations, include any warning messages for debugging
String successMsg = "Function prototype set successfully";
if (!result.getErrorMessage().isEmpty()) {
successMsg += "\n\nWarnings/Debug Info:\n" + result.getErrorMessage();
}
sendResponse(exchange, successMsg);
} else {
// Return the detailed error message to the client
sendResponse(exchange, "Failed to set function prototype: " + result.getErrorMessage());
}
});
server.createContext("/set_local_variable_type", exchange -> {
Map<String, String> params = parsePostParams(exchange);
String functionAddress = params.get("function_address");
String variableName = params.get("variable_name");
String newType = params.get("new_type");
// Capture detailed information about setting the type
StringBuilder responseMsg = new StringBuilder();
responseMsg.append("Setting variable type: ").append(variableName)
.append(" to ").append(newType)
.append(" in function at ").append(functionAddress).append("\n\n");
// Attempt to find the data type in various categories
Program program = getCurrentProgram();
if (program != null) {
DataTypeManager dtm = program.getDataTypeManager();
DataType directType = findDataTypeByNameInAllCategories(dtm, newType);
if (directType != null) {
responseMsg.append("Found type: ").append(directType.getPathName()).append("\n");
} else if (newType.startsWith("P") && newType.length() > 1) {
String baseTypeName = newType.substring(1);
DataType baseType = findDataTypeByNameInAllCategories(dtm, baseTypeName);
if (baseType != null) {
responseMsg.append("Found base type for pointer: ").append(baseType.getPathName()).append("\n");
} else {
responseMsg.append("Base type not found for pointer: ").append(baseTypeName).append("\n");
}
} else {
responseMsg.append("Type not found directly: ").append(newType).append("\n");
}
}
// Try to set the type
boolean success = setLocalVariableType(functionAddress, variableName, newType);
String successMsg = success ? "Variable type set successfully" : "Failed to set variable type";
responseMsg.append("\nResult: ").append(successMsg);
sendResponse(exchange, responseMsg.toString());
});
server.createContext("/xrefs_to", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
String address = qparams.get("address");
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, getXrefsTo(address, offset, limit));
});
server.createContext("/xrefs_from", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
String address = qparams.get("address");
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, getXrefsFrom(address, offset, limit));
});
server.createContext("/function_xrefs", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
String name = qparams.get("name");
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
sendResponse(exchange, getFunctionXrefs(name, offset, limit));
});
server.createContext("/strings", exchange -> {
Map<String, String> qparams = parseQueryParams(exchange);
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
String filter = qparams.get("filter");
sendResponse(exchange, listDefinedStrings(offset, limit, filter));
});
server.setExecutor(null);
new Thread(() -> {
try {
server.start();
Msg.info(this, "GhidraMCP HTTP server started on port " + port);
} catch (Exception e) {
Msg.error(this, "Failed to start HTTP server on port " + port + ". Port might be in use.", e);
server = null; // Ensure server isn't considered running
}
}, "GhidraMCP-HTTP-Server").start();
}
// ----------------------------------------------------------------------------------
// Pagination-aware listing methods
// ----------------------------------------------------------------------------------
private String getAllFunctionNames(int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
List<String> names = new ArrayList<>();
for (Function f : program.getFunctionManager().getFunctions(true)) {
names.add(f.getName());
}
return paginateList(names, offset, limit);
}
private String getAllClassNames(int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
Set<String> classNames = new HashSet<>();
for (Symbol symbol : program.getSymbolTable().getAllSymbols(true)) {
Namespace ns = symbol.getParentNamespace();
if (ns != null && !ns.isGlobal()) {
classNames.add(ns.getName());
}
}
// Convert set to list for pagination
List<String> sorted = new ArrayList<>(classNames);
Collections.sort(sorted);
return paginateList(sorted, offset, limit);
}
private String listSegments(int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
List<String> lines = new ArrayList<>();
for (MemoryBlock block : program.getMemory().getBlocks()) {
lines.add(String.format("%s: %s - %s", block.getName(), block.getStart(), block.getEnd()));
}
return paginateList(lines, offset, limit);
}
private String listImports(int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
List<String> lines = new ArrayList<>();
for (Symbol symbol : program.getSymbolTable().getExternalSymbols()) {
lines.add(symbol.getName() + " -> " + symbol.getAddress());
}
return paginateList(lines, offset, limit);
}
private String listExports(int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
SymbolTable table = program.getSymbolTable();
SymbolIterator it = table.getAllSymbols(true);
List<String> lines = new ArrayList<>();
while (it.hasNext()) {
Symbol s = it.next();
// On older Ghidra, "export" is recognized via isExternalEntryPoint()
if (s.isExternalEntryPoint()) {
lines.add(s.getName() + " -> " + s.getAddress());
}
}
return paginateList(lines, offset, limit);
}
private String listNamespaces(int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
Set<String> namespaces = new HashSet<>();
for (Symbol symbol : program.getSymbolTable().getAllSymbols(true)) {
Namespace ns = symbol.getParentNamespace();
if (ns != null && !(ns instanceof GlobalNamespace)) {
namespaces.add(ns.getName());
}
}
List<String> sorted = new ArrayList<>(namespaces);
Collections.sort(sorted);
return paginateList(sorted, offset, limit);
}
private String listDefinedData(int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
List<String> lines = new ArrayList<>();
for (MemoryBlock block : program.getMemory().getBlocks()) {
DataIterator it = program.getListing().getDefinedData(block.getStart(), true);
while (it.hasNext()) {
Data data = it.next();
if (block.contains(data.getAddress())) {
String label = data.getLabel() != null ? data.getLabel() : "(unnamed)";
String valRepr = data.getDefaultValueRepresentation();
lines.add(String.format("%s: %s = %s",
data.getAddress(),
escapeNonAscii(label),
escapeNonAscii(valRepr)
));
}
}
}
return paginateList(lines, offset, limit);
}
private String searchFunctionsByName(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<String> matches = new ArrayList<>();
for (Function func : program.getFunctionManager().getFunctions(true)) {
String name = func.getName();
// simple substring match
if (name.toLowerCase().contains(searchTerm.toLowerCase())) {
matches.add(String.format("%s @ %s", name, func.getEntryPoint()));
}
}
Collections.sort(matches);
if (matches.isEmpty()) {
return "No functions matching '" + searchTerm + "'";
}
return paginateList(matches, offset, limit);
}
// ----------------------------------------------------------------------------------
// Logic for rename, decompile, etc.
// ----------------------------------------------------------------------------------
private String decompileFunctionByName(String name) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
DecompInterface decomp = new DecompInterface();
decomp.openProgram(program);
for (Function func : program.getFunctionManager().getFunctions(true)) {
if (func.getName().equals(name)) {
DecompileResults result =
decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
if (result != null && result.decompileCompleted()) {
return result.getDecompiledFunction().getC();
} else {
return "Decompilation failed";
}
}
}
return "Function not found";
}
private boolean renameFunction(String oldName, String newName) {
Program program = getCurrentProgram();
if (program == null) return false;
AtomicBoolean successFlag = new AtomicBoolean(false);
try {
SwingUtilities.invokeAndWait(() -> {
int tx = program.startTransaction("Rename function via HTTP");
try {
for (Function func : program.getFunctionManager().getFunctions(true)) {
if (func.getName().equals(oldName)) {
func.setName(newName, SourceType.USER_DEFINED);
successFlag.set(true);
break;
}
}
}
catch (Exception e) {
Msg.error(this, "Error renaming function", e);
}
finally {
successFlag.set(program.endTransaction(tx, successFlag.get()));
}
});
}
catch (InterruptedException | InvocationTargetException e) {
Msg.error(this, "Failed to execute rename on Swing thread", e);
}
return successFlag.get();
}
private void renameDataAtAddress(String addressStr, String newName) {
Program program = getCurrentProgram();
if (program == null) return;
try {
SwingUtilities.invokeAndWait(() -> {
int tx = program.startTransaction("Rename data");
try {
Address addr = program.getAddressFactory().getAddress(addressStr);
Listing listing = program.getListing();
Data data = listing.getDefinedDataAt(addr);
if (data != null) {
SymbolTable symTable = program.getSymbolTable();
Symbol symbol = symTable.getPrimarySymbol(addr);
if (symbol != null) {
symbol.setName(newName, SourceType.USER_DEFINED);
} else {
symTable.createLabel(addr, newName, SourceType.USER_DEFINED);
}
}
}
catch (Exception e) {
Msg.error(this, "Rename data error", e);
}
finally {
program.endTransaction(tx, true);
}
});
}
catch (InterruptedException | InvocationTargetException e) {
Msg.error(this, "Failed to execute rename data on Swing thread", e);
}
}
private String renameVariableInFunction(String functionName, String oldVarName, String newVarName) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
DecompInterface decomp = new DecompInterface();
decomp.openProgram(program);
Function func = null;
for (Function f : program.getFunctionManager().getFunctions(true)) {
if (f.getName().equals(functionName)) {
func = f;
break;
}
}
if (func == null) {
return "Function not found";
}
DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
if (result == null || !result.decompileCompleted()) {
return "Decompilation failed";
}
HighFunction highFunction = result.getHighFunction();
if (highFunction == null) {
return "Decompilation failed (no high function)";
}
LocalSymbolMap localSymbolMap = highFunction.getLocalSymbolMap();
if (localSymbolMap == null) {
return "Decompilation failed (no local symbol map)";
}
HighSymbol highSymbol = null;
Iterator<HighSymbol> symbols = localSymbolMap.getSymbols();
while (symbols.hasNext()) {
HighSymbol symbol = symbols.next();
String symbolName = symbol.getName();
if (symbolName.equals(oldVarName)) {
highSymbol = symbol;
}
if (symbolName.equals(newVarName)) {
return "Error: A variable with name '" + newVarName + "' already exists in this function";
}
}
if (highSymbol == null) {
return "Variable not found";
}
boolean commitRequired = checkFullCommit(highSymbol, highFunction);
final HighSymbol finalHighSymbol = highSymbol;
final Function finalFunction = func;
AtomicBoolean successFlag = new AtomicBoolean(false);
try {
SwingUtilities.invokeAndWait(() -> {
int tx = program.startTransaction("Rename variable");
try {
if (commitRequired) {
HighFunctionDBUtil.commitParamsToDatabase(highFunction, false,
ReturnCommitOption.NO_COMMIT, finalFunction.getSignatureSource());
}
HighFunctionDBUtil.updateDBVariable(
finalHighSymbol,
newVarName,
null,
SourceType.USER_DEFINED
);
successFlag.set(true);
}
catch (Exception e) {
Msg.error(this, "Failed to rename variable", e);
}
finally {
successFlag.set(program.endTransaction(tx, true));
}
});
} catch (InterruptedException | InvocationTargetException e) {
String errorMsg = "Failed to execute rename on Swing thread: " + e.getMessage();
Msg.error(this, errorMsg, e);
return errorMsg;
}
return successFlag.get() ? "Variable renamed" : "Failed to rename variable";
}
/**
* Copied from AbstractDecompilerAction.checkFullCommit, it's protected.
* Compare the given HighFunction's idea of the prototype with the Function's idea.
* Return true if there is a difference. If a specific symbol is being changed,
* it can be passed in to check whether or not the prototype is being affected.
* @param highSymbol (if not null) is the symbol being modified
* @param hfunction is the given HighFunction
* @return true if there is a difference (and a full commit is required)
*/
protected static boolean checkFullCommit(HighSymbol highSymbol, HighFunction hfunction) {
if (highSymbol != null && !highSymbol.isParameter()) {
return false;
}
Function function = hfunction.getFunction();
Parameter[] parameters = function.getParameters();
LocalSymbolMap localSymbolMap = hfunction.getLocalSymbolMap();
int numParams = localSymbolMap.getNumParams();
if (numParams != parameters.length) {
return true;
}
for (int i = 0; i < numParams; i++) {
HighSymbol param = localSymbolMap.getParamSymbol(i);
if (param.getCategoryIndex() != i) {
return true;
}
VariableStorage storage = param.getStorage();
// Don't compare using the equals method so that DynamicVariableStorage can match
if (0 != storage.compareTo(parameters[i].getVariableStorage())) {
return true;
}
}
return false;
}
// ----------------------------------------------------------------------------------
// New methods to implement the new functionalities
// ----------------------------------------------------------------------------------
/**
* Get function by address
*/
private String getFunctionByAddress(String addressStr) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
if (addressStr == null || addressStr.isEmpty()) return "Address is required";
try {
Address addr = program.getAddressFactory().getAddress(addressStr);
Function func = program.getFunctionManager().getFunctionAt(addr);
if (func == null) return "No function found at address " + addressStr;
return String.format("Function: %s at %s\nSignature: %s\nEntry: %s\nBody: %s - %s",
func.getName(),
func.getEntryPoint(),
func.getSignature(),
func.getEntryPoint(),
func.getBody().getMinAddress(),
func.getBody().getMaxAddress());
} catch (Exception e) {
return "Error getting function: " + e.getMessage();
}
}
/**
* Get current address selected in Ghidra GUI
*/
private String getCurrentAddress() {
CodeViewerService service = tool.getService(CodeViewerService.class);
if (service == null) return "Code viewer service not available";
ProgramLocation location = service.getCurrentLocation();
return (location != null) ? location.getAddress().toString() : "No current location";
}
/**
* Get current function selected in Ghidra GUI
*/
private String getCurrentFunction() {
CodeViewerService service = tool.getService(CodeViewerService.class);
if (service == null) return "Code viewer service not available";
ProgramLocation location = service.getCurrentLocation();
if (location == null) return "No current location";
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
Function func = program.getFunctionManager().getFunctionContaining(location.getAddress());
if (func == null) return "No function at current location: " + location.getAddress();
return String.format("Function: %s at %s\nSignature: %s",
func.getName(),
func.getEntryPoint(),
func.getSignature());
}
/**
* List all functions in the database
*/
private String listFunctions() {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
StringBuilder result = new StringBuilder();
for (Function func : program.getFunctionManager().getFunctions(true)) {
result.append(String.format("%s at %s\n",
func.getName(),
func.getEntryPoint()));
}
return result.toString();
}
/**
* Gets a function at the given address or containing the address
* @return the function or null if not found
*/
private Function getFunctionForAddress(Program program, Address addr) {
Function func = program.getFunctionManager().getFunctionAt(addr);
if (func == null) {
func = program.getFunctionManager().getFunctionContaining(addr);
}
return func;
}
/**
* Decompile a function at the given address
*/
private String decompileFunctionByAddress(String addressStr) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
if (addressStr == null || addressStr.isEmpty()) return "Address is required";
try {
Address addr = program.getAddressFactory().getAddress(addressStr);
Function func = getFunctionForAddress(program, addr);
if (func == null) return "No function found at or containing address " + addressStr;
DecompInterface decomp = new DecompInterface();
decomp.openProgram(program);
DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
return (result != null && result.decompileCompleted())
? result.getDecompiledFunction().getC()
: "Decompilation failed";
} catch (Exception e) {
return "Error decompiling function: " + e.getMessage();
}
}
/**
* Get assembly code for a function
*/
private String disassembleFunction(String addressStr) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
if (addressStr == null || addressStr.isEmpty()) return "Address is required";
try {
Address addr = program.getAddressFactory().getAddress(addressStr);
Function func = getFunctionForAddress(program, addr);
if (func == null) return "No function found at or containing address " + addressStr;
StringBuilder result = new StringBuilder();
Listing listing = program.getListing();
Address start = func.getEntryPoint();
Address end = func.getBody().getMaxAddress();
InstructionIterator instructions = listing.getInstructions(start, true);
while (instructions.hasNext()) {
Instruction instr = instructions.next();
if (instr.getAddress().compareTo(end) > 0) {
break; // Stop if we've gone past the end of the function
}
String comment = listing.getComment(CodeUnit.EOL_COMMENT, instr.getAddress());
comment = (comment != null) ? "; " + comment : "";
result.append(String.format("%s: %s %s\n",
instr.getAddress(),
instr.toString(),
comment));
}
return result.toString();
} catch (Exception e) {
return "Error disassembling function: " + e.getMessage();
}
}
/**
* Set a comment using the specified comment type (PRE_COMMENT or EOL_COMMENT)
*/
private boolean setCommentAtAddress(String addressStr, String comment, int commentType, String transactionName) {
Program program = getCurrentProgram();
if (program == null) return false;
if (addressStr == null || addressStr.isEmpty() || comment == null) return false;
AtomicBoolean success = new AtomicBoolean(false);
try {
SwingUtilities.invokeAndWait(() -> {
int tx = program.startTransaction(transactionName);
try {
Address addr = program.getAddressFactory().getAddress(addressStr);
program.getListing().setComment(addr, commentType, comment);
success.set(true);
} catch (Exception e) {
Msg.error(this, "Error setting " + transactionName.toLowerCase(), e);
} finally {
success.set(program.endTransaction(tx, success.get()));
}
});
} catch (InterruptedException | InvocationTargetException e) {
Msg.error(this, "Failed to execute " + transactionName.toLowerCase() + " on Swing thread", e);
}
return success.get();
}
/**
* Set a comment for a given address in the function pseudocode
*/
private boolean setDecompilerComment(String addressStr, String comment) {
return setCommentAtAddress(addressStr, comment, CodeUnit.PRE_COMMENT, "Set decompiler comment");
}
/**
* Set a comment for a given address in the function disassembly
*/
private boolean setDisassemblyComment(String addressStr, String comment) {
return setCommentAtAddress(addressStr, comment, CodeUnit.EOL_COMMENT, "Set disassembly comment");
}
/**
* Class to hold the result of a prototype setting operation
*/
private static class PrototypeResult {
private final boolean success;
private final String errorMessage;
public PrototypeResult(boolean success, String errorMessage) {
this.success = success;
this.errorMessage = errorMessage;
}
public boolean isSuccess() {
return success;
}
public String getErrorMessage() {
return errorMessage;
}
}
/**
* Rename a function by its address
*/
private boolean renameFunctionByAddress(String functionAddrStr, String newName) {
Program program = getCurrentProgram();
if (program == null) return false;
if (functionAddrStr == null || functionAddrStr.isEmpty() ||
newName == null || newName.isEmpty()) {
return false;
}
AtomicBoolean success = new AtomicBoolean(false);
try {
SwingUtilities.invokeAndWait(() -> {
performFunctionRename(program, functionAddrStr, newName, success);
});
} catch (InterruptedException | InvocationTargetException e) {
Msg.error(this, "Failed to execute rename function on Swing thread", e);
}
return success.get();
}
/**
* Helper method to perform the actual function rename within a transaction
*/
private void performFunctionRename(Program program, String functionAddrStr, String newName, AtomicBoolean success) {
int tx = program.startTransaction("Rename function by address");
try {
Address addr = program.getAddressFactory().getAddress(functionAddrStr);
Function func = getFunctionForAddress(program, addr);
if (func == null) {
Msg.error(this, "Could not find function at address: " + functionAddrStr);
return;
}
func.setName(newName, SourceType.USER_DEFINED);
success.set(true);
} catch (Exception e) {
Msg.error(this, "Error renaming function by address", e);
} finally {
program.endTransaction(tx, success.get());
}
}
/**
* Set a function's prototype with proper error handling using ApplyFunctionSignatureCmd
*/
private PrototypeResult setFunctionPrototype(String functionAddrStr, String prototype) {
// Input validation
Program program = getCurrentProgram();
if (program == null) return new PrototypeResult(false, "No program loaded");
if (functionAddrStr == null || functionAddrStr.isEmpty()) {
return new PrototypeResult(false, "Function address is required");
}
if (prototype == null || prototype.isEmpty()) {
return new PrototypeResult(false, "Function prototype is required");
}
final StringBuilder errorMessage = new StringBuilder();
final AtomicBoolean success = new AtomicBoolean(false);
try {
SwingUtilities.invokeAndWait(() ->
applyFunctionPrototype(program, functionAddrStr, prototype, success, errorMessage));
} catch (InterruptedException | InvocationTargetException e) {
String msg = "Failed to set function prototype on Swing thread: " + e.getMessage();
errorMessage.append(msg);
Msg.error(this, msg, e);
}
return new PrototypeResult(success.get(), errorMessage.toString());
}
/**
* Helper method that applies the function prototype within a transaction
*/
private void applyFunctionPrototype(Program program, String functionAddrStr, String prototype,
AtomicBoolean success, StringBuilder errorMessage) {
try {
// Get the address and function
Address addr = program.getAddressFactory().getAddress(functionAddrStr);
Function func = getFunctionForAddress(program, addr);
if (func == null) {
String msg = "Could not find function at address: " + functionAddrStr;
errorMessage.append(msg);
Msg.error(this, msg);
return;
}
Msg.info(this, "Setting prototype for function " + func.getName() + ": " + prototype);
// Store original prototype as a comment for reference
addPrototypeComment(program, func, prototype);
// Use ApplyFunctionSignatureCmd to parse and apply the signature
parseFunctionSignatureAndApply(program, addr, prototype, success, errorMessage);
} catch (Exception e) {
String msg = "Error setting function prototype: " + e.getMessage();
errorMessage.append(msg);
Msg.error(this, msg, e);
}
}
/**
* Add a comment showing the prototype being set
*/
private void addPrototypeComment(Program program, Function func, String prototype) {
int txComment = program.startTransaction("Add prototype comment");
try {
program.getListing().setComment(
func.getEntryPoint(),
CodeUnit.PLATE_COMMENT,
"Setting prototype: " + prototype
);
} finally {
program.endTransaction(txComment, true);
}
}
/**
* Parse and apply the function signature with error handling
*/
private void parseFunctionSignatureAndApply(Program program, Address addr, String prototype,
AtomicBoolean success, StringBuilder errorMessage) {
// Use ApplyFunctionSignatureCmd to parse and apply the signature
int txProto = program.startTransaction("Set function prototype");
try {
// Get data type manager
DataTypeManager dtm = program.getDataTypeManager();
// Get data type manager service
ghidra.app.services.DataTypeManagerService dtms =
tool.getService(ghidra.app.services.DataTypeManagerService.class);
// Create function signature parser
ghidra.app.util.parser.FunctionSignatureParser parser =
new ghidra.app.util.parser.FunctionSignatureParser(dtm, dtms);
// Parse the prototype into a function signature
ghidra.program.model.data.FunctionDefinitionDataType sig = parser.parse(null, prototype);
if (sig == null) {
String msg = "Failed to parse function prototype";
errorMessage.append(msg);
Msg.error(this, msg);
return;
}
// Create and apply the command
ghidra.app.cmd.function.ApplyFunctionSignatureCmd cmd =
new ghidra.app.cmd.function.ApplyFunctionSignatureCmd(
addr, sig, SourceType.USER_DEFINED);
// Apply the command to the program
boolean cmdResult = cmd.applyTo(program, new ConsoleTaskMonitor());
if (cmdResult) {
success.set(true);
Msg.info(this, "Successfully applied function signature");
} else {
String msg = "Command failed: " + cmd.getStatusMsg();
errorMessage.append(msg);
Msg.error(this, msg);
}
} catch (Exception e) {
String msg = "Error applying function signature: " + e.getMessage();
errorMessage.append(msg);
Msg.error(this, msg, e);
} finally {
program.endTransaction(txProto, success.get());
}
}
/**
* Set a local variable's type using HighFunctionDBUtil.updateDBVariable
*/
private boolean setLocalVariableType(String functionAddrStr, String variableName, String newType) {
// Input validation
Program program = getCurrentProgram();
if (program == null) return false;
if (functionAddrStr == null || functionAddrStr.isEmpty() ||
variableName == null || variableName.isEmpty() ||
newType == null || newType.isEmpty()) {
return false;
}
AtomicBoolean success = new AtomicBoolean(false);
try {
SwingUtilities.invokeAndWait(() ->
applyVariableType(program, functionAddrStr, variableName, newType, success));
} catch (InterruptedException | InvocationTargetException e) {
Msg.error(this, "Failed to execute set variable type on Swing thread", e);
}
return success.get();
}
/**
* Helper method that performs the actual variable type change
*/
private void applyVariableType(Program program, String functionAddrStr,
String variableName, String newType, AtomicBoolean success) {
try {
// Find the function
Address addr = program.getAddressFactory().getAddress(functionAddrStr);
Function func = getFunctionForAddress(program, addr);
if (func == null) {
Msg.error(this, "Could not find function at address: " + functionAddrStr);
return;
}
DecompileResults results = decompileFunction(func, program);
if (results == null || !results.decompileCompleted()) {
return;
}
ghidra.program.model.pcode.HighFunction highFunction = results.getHighFunction();
if (highFunction == null) {
Msg.error(this, "No high function available");
return;
}
// Find the symbol by name
HighSymbol symbol = findSymbolByName(highFunction, variableName);
if (symbol == null) {
Msg.error(this, "Could not find variable '" + variableName + "' in decompiled function");
return;
}
// Get high variable
HighVariable highVar = symbol.getHighVariable();
if (highVar == null) {
Msg.error(this, "No HighVariable found for symbol: " + variableName);
return;
}
Msg.info(this, "Found high variable for: " + variableName +
" with current type " + highVar.getDataType().getName());
// Find the data type
DataTypeManager dtm = program.getDataTypeManager();
DataType dataType = resolveDataType(dtm, newType);
if (dataType == null) {
Msg.error(this, "Could not resolve data type: " + newType);
return;
}
Msg.info(this, "Using data type: " + dataType.getName() + " for variable " + variableName);
// Apply the type change in a transaction
updateVariableType(program, symbol, dataType, success);
} catch (Exception e) {
Msg.error(this, "Error setting variable type: " + e.getMessage());
}
}
/**
* Find a high symbol by name in the given high function
*/
private HighSymbol findSymbolByName(ghidra.program.model.pcode.HighFunction highFunction, String variableName) {
Iterator<HighSymbol> symbols = highFunction.getLocalSymbolMap().getSymbols();
while (symbols.hasNext()) {
HighSymbol s = symbols.next();
if (s.getName().equals(variableName)) {
return s;
}
}
return null;
}
/**
* Decompile a function and return the results
*/
private DecompileResults decompileFunction(Function func, Program program) {
// Set up decompiler for accessing the decompiled function
DecompInterface decomp = new DecompInterface();
decomp.openProgram(program);
decomp.setSimplificationStyle("decompile"); // Full decompilation
// Decompile the function
DecompileResults results = decomp.decompileFunction(func, 60, new ConsoleTaskMonitor());
if (!results.decompileCompleted()) {
Msg.error(this, "Could not decompile function: " + results.getErrorMessage());
return null;
}
return results;
}
/**
* Apply the type update in a transaction
*/
private void updateVariableType(Program program, HighSymbol symbol, DataType dataType, AtomicBoolean success) {
int tx = program.startTransaction("Set variable type");
try {
// Use HighFunctionDBUtil to update the variable with the new type
HighFunctionDBUtil.updateDBVariable(
symbol, // The high symbol to modify
symbol.getName(), // Keep original name
dataType, // The new data type
SourceType.USER_DEFINED // Mark as user-defined
);
success.set(true);
Msg.info(this, "Successfully set variable type using HighFunctionDBUtil");
} catch (Exception e) {
Msg.error(this, "Error setting variable type: " + e.getMessage());
} finally {
program.endTransaction(tx, success.get());
}
}
/**
* Get all references to a specific address (xref to)
*/
private String getXrefsTo(String addressStr, int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
if (addressStr == null || addressStr.isEmpty()) return "Address is required";
try {
Address addr = program.getAddressFactory().getAddress(addressStr);
ReferenceManager refManager = program.getReferenceManager();
ReferenceIterator refIter = refManager.getReferencesTo(addr);
List<String> refs = new ArrayList<>();
while (refIter.hasNext()) {
Reference ref = refIter.next();
Address fromAddr = ref.getFromAddress();
RefType refType = ref.getReferenceType();
Function fromFunc = program.getFunctionManager().getFunctionContaining(fromAddr);
String funcInfo = (fromFunc != null) ? " in " + fromFunc.getName() : "";
refs.add(String.format("From %s%s [%s]", fromAddr, funcInfo, refType.getName()));
}
return paginateList(refs, offset, limit);
} catch (Exception e) {
return "Error getting references to address: " + e.getMessage();
}
}
/**
* Get all references from a specific address (xref from)
*/
private String getXrefsFrom(String addressStr, int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
if (addressStr == null || addressStr.isEmpty()) return "Address is required";
try {
Address addr = program.getAddressFactory().getAddress(addressStr);
ReferenceManager refManager = program.getReferenceManager();
Reference[] references = refManager.getReferencesFrom(addr);
List<String> refs = new ArrayList<>();
for (Reference ref : references) {
Address toAddr = ref.getToAddress();
RefType refType = ref.getReferenceType();
String targetInfo = "";
Function toFunc = program.getFunctionManager().getFunctionAt(toAddr);
if (toFunc != null) {
targetInfo = " to function " + toFunc.getName();
} else {
Data data = program.getListing().getDataAt(toAddr);
if (data != null) {
targetInfo = " to data " + (data.getLabel() != null ? data.getLabel() : data.getPathName());
}
}
refs.add(String.format("To %s%s [%s]", toAddr, targetInfo, refType.getName()));
}
return paginateList(refs, offset, limit);
} catch (Exception e) {
return "Error getting references from address: " + e.getMessage();
}
}
/**
* Get all references to a specific function by name
*/
private String getFunctionXrefs(String functionName, int offset, int limit) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
if (functionName == null || functionName.isEmpty()) return "Function name is required";
try {
List<String> refs = new ArrayList<>();
FunctionManager funcManager = program.getFunctionManager();
for (Function function : funcManager.getFunctions(true)) {
if (function.getName().equals(functionName)) {
Address entryPoint = function.getEntryPoint();
ReferenceIterator refIter = program.getReferenceManager().getReferencesTo(entryPoint);
while (refIter.hasNext()) {
Reference ref = refIter.next();
Address fromAddr = ref.getFromAddress();
RefType refType = ref.getReferenceType();
Function fromFunc = funcManager.getFunctionContaining(fromAddr);
String funcInfo = (fromFunc != null) ? " in " + fromFunc.getName() : "";
refs.add(String.format("From %s%s [%s]", fromAddr, funcInfo, refType.getName()));
}
}
}
if (refs.isEmpty()) {
return "No references found to function: " + functionName;
}
return paginateList(refs, offset, limit);
} catch (Exception e) {
return "Error getting function references: " + e.getMessage();
}
}
/**
* List all defined strings in the program with their addresses
*/
private String listDefinedStrings(int offset, int limit, String filter) {
Program program = getCurrentProgram();
if (program == null) return "No program loaded";
List<String> lines = new ArrayList<>();
DataIterator dataIt = program.getListing().getDefinedData(true);
while (dataIt.hasNext()) {
Data data = dataIt.next();
if (data != null && isStringData(data)) {
String value = data.getValue() != null ? data.getValue().toString() : "";
if (filter == null || value.toLowerCase().contains(filter.toLowerCase())) {
String escapedValue = escapeString(value);
lines.add(String.format("%s: \"%s\"", data.getAddress(), escapedValue));
}
}
}
return paginateList(lines, offset, limit);
}
/**
* Check if the given data is a string type
*/
private boolean isStringData(Data data) {
if (data == null) return false;
DataType dt = data.getDataType();
String typeName = dt.getName().toLowerCase();
return typeName.contains("string") || typeName.contains("char") || typeName.equals("unicode");
}
/**
* Escape special characters in a string for display
*/
private String escapeString(String input) {
if (input == null) return "";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if (c >= 32 && c < 127) {
sb.append(c);
} else if (c == '\n') {
sb.append("\\n");
} else if (c == '\r') {
sb.append("\\r");
} else if (c == '\t') {
sb.append("\\t");
} else {
sb.append(String.format("\\x%02x", (int)c & 0xFF));
}
}
return sb.toString();
}
/**
* Resolves a data type by name, handling common types and pointer types
* @param dtm The data type manager
* @param typeName The type name to resolve
* @return The resolved DataType, or null if not found
*/
private DataType resolveDataType(DataTypeManager dtm, String typeName) {
// First try to find exact match in all categories
DataType dataType = findDataTypeByNameInAllCategories(dtm, typeName);
if (dataType != null) {
Msg.info(this, "Found exact data type match: " + dataType.getPathName());
return dataType;
}
// Check for Windows-style pointer types (PXXX)
if (typeName.startsWith("P") && typeName.length() > 1) {
String baseTypeName = typeName.substring(1);
// Special case for PVOID
if (baseTypeName.equals("VOID")) {
return new PointerDataType(dtm.getDataType("/void"));
}
// Try to find the base type
DataType baseType = findDataTypeByNameInAllCategories(dtm, baseTypeName);
if (baseType != null) {
return new PointerDataType(baseType);
}
Msg.warn(this, "Base type not found for " + typeName + ", defaulting to void*");
return new PointerDataType(dtm.getDataType("/void"));
}
// Handle common built-in types
switch (typeName.toLowerCase()) {
case "int":
case "long":
return dtm.getDataType("/int");
case "uint":
case "unsigned int":
case "unsigned long":
case "dword":
return dtm.getDataType("/uint");
case "short":
return dtm.getDataType("/short");
case "ushort":
case "unsigned short":
case "word":
return dtm.getDataType("/ushort");
case "char":
case "byte":
return dtm.getDataType("/char");
case "uchar":
case "unsigned char":
return dtm.getDataType("/uchar");
case "longlong":
case "__int64":
return dtm.getDataType("/longlong");
case "ulonglong":
case "unsigned __int64":
return dtm.getDataType("/ulonglong");
case "bool":
case "boolean":
return dtm.getDataType("/bool");
case "void":
return dtm.getDataType("/void");
default:
// Try as a direct path
DataType directType = dtm.getDataType("/" + typeName);
if (directType != null) {
return directType;
}
// Fallback to int if we couldn't find it
Msg.warn(this, "Unknown type: " + typeName + ", defaulting to int");
return dtm.getDataType("/int");
}
}
/**
* Find a data type by name in all categories/folders of the data type manager
* This searches through all categories rather than just the root
*/
private DataType findDataTypeByNameInAllCategories(DataTypeManager dtm, String typeName) {
// Try exact match first
DataType result = searchByNameInAllCategories(dtm, typeName);
if (result != null) {
return result;
}
// Try lowercase
return searchByNameInAllCategories(dtm, typeName.toLowerCase());
}
/**
* Helper method to search for a data type by name in all categories
*/
private DataType searchByNameInAllCategories(DataTypeManager dtm, String name) {
// Get all data types from the manager
Iterator<DataType> allTypes = dtm.getAllDataTypes();
while (allTypes.hasNext()) {
DataType dt = allTypes.next();
// Check if the name matches exactly (case-sensitive)
if (dt.getName().equals(name)) {
return dt;
}
// For case-insensitive, we want an exact match except for case
if (dt.getName().equalsIgnoreCase(name)) {
return dt;
}
}
return null;
}
// ----------------------------------------------------------------------------------
// Utility: parse query params, parse post params, pagination, etc.
// ----------------------------------------------------------------------------------
/**
* Parse query parameters from the URL, e.g. ?offset=10&limit=100
*/
private Map<String, String> parseQueryParams(HttpExchange exchange) {
Map<String, String> result = new HashMap<>();
String query = exchange.getRequestURI().getQuery(); // e.g. offset=10&limit=100
if (query != null) {
String[] pairs = query.split("&");
for (String p : pairs) {
String[] kv = p.split("=");
if (kv.length == 2) {
// URL decode parameter values
try {
String key = URLDecoder.decode(kv[0], StandardCharsets.UTF_8);
String value = URLDecoder.decode(kv[1], StandardCharsets.UTF_8);
result.put(key, value);
} catch (Exception e) {
Msg.error(this, "Error decoding URL parameter", e);
}
}
}
}
return result;
}
/**
* Parse post body form params, e.g. oldName=foo&newName=bar
*/
private Map<String, String> parsePostParams(HttpExchange exchange) throws IOException {
byte[] body = exchange.getRequestBody().readAllBytes();
String bodyStr = new String(body, StandardCharsets.UTF_8);
Map<String, String> params = new HashMap<>();
for (String pair : bodyStr.split("&")) {
String[] kv = pair.split("=");
if (kv.length == 2) {
// URL decode parameter values
try {
String key = URLDecoder.decode(kv[0], StandardCharsets.UTF_8);
String value = URLDecoder.decode(kv[1], StandardCharsets.UTF_8);
params.put(key, value);
} catch (Exception e) {
Msg.error(this, "Error decoding URL parameter", e);
}
}
}
return params;
}
/**
* Convert a list of strings into one big newline-delimited string, applying offset & limit.
*/
private String paginateList(List<String> items, int offset, int limit) {
int start = Math.max(0, offset);
int end = Math.min(items.size(), offset + limit);
if (start >= items.size()) {
return ""; // no items in range
}
List<String> sub = items.subList(start, end);
return String.join("\n", sub);
}
/**
* Parse an integer from a string, or return defaultValue if null/invalid.
*/
private int parseIntOrDefault(String val, int defaultValue) {
if (val == null) return defaultValue;
try {
return Integer.parseInt(val);
}
catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Escape non-ASCII chars to avoid potential decode issues.
*/
private String escapeNonAscii(String input) {
if (input == null) return "";
StringBuilder sb = new StringBuilder();
for (char c : input.toCharArray()) {
if (c >= 32 && c < 127) {
sb.append(c);
}
else {
sb.append("\\x");
sb.append(Integer.toHexString(c & 0xFF));
}
}
return sb.toString();
}
public Program getCurrentProgram() {
ProgramManager pm = tool.getService(ProgramManager.class);
return pm != null ? pm.getCurrentProgram() : null;
}
private void sendResponse(HttpExchange exchange, String response) throws IOException {
byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
exchange.sendResponseHeaders(200, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
@Override
public void dispose() {
if (server != null) {
Msg.info(this, "Stopping GhidraMCP HTTP server...");
server.stop(1); // Stop with a small delay (e.g., 1 second) for connections to finish
server = null; // Nullify the reference
Msg.info(this, "GhidraMCP HTTP server stopped.");
}
super.dispose();
}
}