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 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> 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 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 symbolIter = highFunc.getLocalSymbolMap().getSymbols(); while (symbolIter.hasNext()) { HighSymbol symbol = symbolIter.next(); if (!symbol.isParameter()) { // Only list locals Map 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> 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> 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 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 symbolIter = highFunc.getLocalSymbolMap().getSymbols(); while (symbolIter.hasNext()) { HighSymbol symbol = symbolIter.next(); if (symbol.getName().toLowerCase().contains(lowerSearchTerm)) { Map 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> 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 }