mcghidra/src/main/java/eu/starsong/ghidra/endpoints/VariableEndpoints.java
2025-04-10 14:42:53 +02:00

266 lines
13 KiB
Java

package eu.starsong.ghidra.endpoints;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import eu.starsong.ghidra.util.TransactionHelper;
import eu.starsong.ghidra.util.TransactionHelper.TransactionException;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Parameter;
import ghidra.program.model.listing.Program;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.HighFunctionDBUtil;
import ghidra.program.model.pcode.HighSymbol;
import ghidra.program.model.pcode.LocalSymbolMap;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolIterator;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.program.model.symbol.SymbolType;
import ghidra.util.Msg;
import ghidra.util.task.ConsoleTaskMonitor;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.SwingUtilities;
import java.lang.reflect.InvocationTargetException;
public class VariableEndpoints extends AbstractEndpoint {
// Updated constructor to accept port
public VariableEndpoints(Program program, int port) {
super(program, port); // Call super constructor
}
@Override
public void registerEndpoints(HttpServer server) {
server.createContext("/variables", this::handleGlobalVariables);
// Note: /functions/{name}/variables is handled within FunctionEndpoints for now
// to keep related logic together until full refactor.
// If needed, we can create a more complex routing mechanism later.
}
private void handleGlobalVariables(HttpExchange exchange) throws IOException {
try {
if ("GET".equals(exchange.getRequestMethod())) {
Map<String, String> qparams = parseQueryParams(exchange);
int offset = parseIntOrDefault(qparams.get("offset"), 0);
int limit = parseIntOrDefault(qparams.get("limit"), 100);
String search = qparams.get("search"); // Renamed from 'query' for clarity
Object resultData;
if (search != null && !search.isEmpty()) {
resultData = searchVariables(search, offset, limit);
} else {
resultData = listVariables(offset, limit);
}
// Check if helper returned an error object
if (resultData instanceof JsonObject && !((JsonObject)resultData).get("success").getAsBoolean()) {
sendJsonResponse(exchange, (JsonObject)resultData, 400); // Use base sendJsonResponse
} else {
sendSuccessResponse(exchange, resultData); // Use success helper
}
} else {
sendErrorResponse(exchange, 405, "Method Not Allowed");
}
} catch (Exception e) {
Msg.error(this, "Error in /variables endpoint", e);
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
}
}
// --- Methods moved from GhydraMCPPlugin ---
private JsonObject listVariables(int offset, int limit) {
if (currentProgram == null) {
return createErrorResponse("No program loaded", 400);
}
List<Map<String, String>> variables = new ArrayList<>();
// Get global variables
SymbolTable symbolTable = currentProgram.getSymbolTable();
for (Symbol symbol : symbolTable.getDefinedSymbols()) {
if (symbol.isGlobal() && !symbol.isExternal() &&
symbol.getSymbolType() != SymbolType.FUNCTION &&
symbol.getSymbolType() != SymbolType.LABEL) {
Map<String, String> varInfo = new HashMap<>();
varInfo.put("name", symbol.getName());
varInfo.put("address", symbol.getAddress().toString());
varInfo.put("type", "global");
varInfo.put("dataType", getDataTypeName(currentProgram, symbol.getAddress()));
variables.add(varInfo);
}
}
// Get local variables from all functions (Consider performance implications)
DecompInterface decomp = null;
try {
decomp = new DecompInterface();
if (!decomp.openProgram(currentProgram)) {
Msg.error(this, "listVariables: Failed to open program with decompiler.");
} else {
for (Function function : currentProgram.getFunctionManager().getFunctions(true)) {
try {
DecompileResults results = decomp.decompileFunction(function, 30, new ConsoleTaskMonitor());
if (results != null && results.decompileCompleted()) {
HighFunction highFunc = results.getHighFunction();
if (highFunc != null) {
Iterator<HighSymbol> symbolIter = highFunc.getLocalSymbolMap().getSymbols();
while (symbolIter.hasNext()) {
HighSymbol symbol = symbolIter.next();
if (!symbol.isParameter()) { // Only list locals
Map<String, String> varInfo = new HashMap<>();
varInfo.put("name", symbol.getName());
varInfo.put("type", "local");
varInfo.put("function", function.getName());
Address pcAddr = symbol.getPCAddress();
varInfo.put("address", pcAddr != null ? pcAddr.toString() : "N/A");
varInfo.put("dataType", symbol.getDataType() != null ? symbol.getDataType().getName() : "unknown");
variables.add(varInfo);
}
}
}
}
} catch (Exception e) {
Msg.error(this, "listVariables: Error processing function " + function.getName(), e);
}
}
}
} catch (Exception e) {
Msg.error(this, "listVariables: Error during local variable processing", e);
} finally {
if (decomp != null) {
decomp.dispose();
}
}
Collections.sort(variables, Comparator.comparing(a -> a.get("name")));
int start = Math.max(0, offset);
int end = Math.min(variables.size(), offset + limit);
List<Map<String, String>> paginated = variables.subList(start, end);
return createSuccessResponse(paginated); // Keep using internal helper for now
}
private JsonObject searchVariables(String searchTerm, int offset, int limit) {
if (currentProgram == null) {
return createErrorResponse("No program loaded", 400); // Keep using internal helper
}
if (searchTerm == null || searchTerm.isEmpty()) {
return createErrorResponse("Search term is required", 400); // Keep using internal helper
}
List<Map<String, String>> matchedVars = new ArrayList<>();
String lowerSearchTerm = searchTerm.toLowerCase();
// Search global variables
SymbolTable symbolTable = currentProgram.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(lowerSearchTerm)) {
Map<String, String> varInfo = new HashMap<>();
varInfo.put("name", symbol.getName());
varInfo.put("address", symbol.getAddress().toString());
varInfo.put("type", "global");
varInfo.put("dataType", getDataTypeName(currentProgram, symbol.getAddress()));
matchedVars.add(varInfo);
}
}
// Search local variables
DecompInterface decomp = null;
try {
decomp = new DecompInterface();
if (decomp.openProgram(currentProgram)) {
for (Function function : currentProgram.getFunctionManager().getFunctions(true)) {
try {
DecompileResults results = decomp.decompileFunction(function, 30, new ConsoleTaskMonitor());
if (results != null && results.decompileCompleted()) {
HighFunction highFunc = results.getHighFunction();
if (highFunc != null) {
Iterator<HighSymbol> symbolIter = highFunc.getLocalSymbolMap().getSymbols();
while (symbolIter.hasNext()) {
HighSymbol symbol = symbolIter.next();
if (symbol.getName().toLowerCase().contains(lowerSearchTerm)) {
Map<String, String> varInfo = new HashMap<>();
varInfo.put("name", symbol.getName());
varInfo.put("function", function.getName());
varInfo.put("type", symbol.isParameter() ? "parameter" : "local");
Address pcAddr = symbol.getPCAddress();
varInfo.put("address", pcAddr != null ? pcAddr.toString() : "N/A");
varInfo.put("dataType", symbol.getDataType() != null ? symbol.getDataType().getName() : "unknown");
matchedVars.add(varInfo);
}
}
}
}
} catch (Exception e) {
Msg.warn(this, "searchVariables: Error processing function " + function.getName(), e);
}
}
} else {
Msg.error(this, "searchVariables: Failed to open program with decompiler.");
}
} catch (Exception e) {
Msg.error(this, "searchVariables: Error during local variable search", e);
} finally {
if (decomp != null) {
decomp.dispose();
}
}
Collections.sort(matchedVars, Comparator.comparing(a -> a.get("name")));
int start = Math.max(0, offset);
int end = Math.min(matchedVars.size(), offset + limit);
List<Map<String, String>> paginated = matchedVars.subList(start, end);
return createSuccessResponse(paginated); // Keep using internal helper
}
// --- Helper Methods (Keep internal for now, refactor later if needed) ---
private String getDataTypeName(Program program, Address address) {
// This might be better in GhidraUtil if used elsewhere
ghidra.program.model.listing.Data data = program.getListing().getDataAt(address);
if (data == null) return "undefined";
DataType dt = data.getDataType();
return dt != null ? dt.getName() : "unknown";
}
// Keep internal response helpers for now, as they differ slightly from AbstractEndpoint's
private JsonObject createSuccessResponse(Object resultData) {
JsonObject response = new JsonObject();
response.addProperty("success", true);
response.add("result", gson.toJsonTree(resultData));
// These helpers don't add id/instance/_links, unlike ResponseBuilder
return response;
}
private JsonObject createErrorResponse(String errorMessage, int statusCode) {
JsonObject response = new JsonObject();
response.addProperty("success", false);
response.addProperty("error", errorMessage);
response.addProperty("status_code", statusCode);
return response;
}
// parseIntOrDefault is inherited from AbstractEndpoint
}