From 1c47f12b0ed5c4a742193841ad842ac293c22ea9 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 19:13:33 -0700 Subject: [PATCH 01/17] PR #41 initial dump --- .../java/com/lauriewired/GhidraMCPPlugin.java | 2465 +---------------- .../com/lauriewired/handlers/Handler.java | 18 + .../act/DecompileFunctionByAddress.java | 56 + .../handlers/act/DecompileFunctionByName.java | 46 + .../handlers/act/DisassembleFunction.java | 64 + .../comment/SetDecompilerComment.java | 34 + .../comment/SetDisassemblyComment.java | 34 + .../handlers/get/GetAllClassNames.java | 45 + .../handlers/get/GetAllFunctionNames.java | 41 + .../handlers/get/GetCurrentAddress.java | 32 + .../handlers/get/GetCurrentFunction.java | 47 + .../handlers/get/GetFunctionByAddress.java | 54 + .../handlers/get/GetFunctionXrefs.java | 73 + .../handlers/get/GetXrefsFrom.java | 73 + .../lauriewired/handlers/get/GetXrefsTo.java | 66 + .../handlers/get/ListDefinedData.java | 54 + .../handlers/get/ListDefinedStrings.java | 92 + .../lauriewired/handlers/get/ListExports.java | 49 + .../handlers/get/ListFunctions.java | 40 + .../lauriewired/handlers/get/ListImports.java | 40 + .../handlers/get/ListNamespaces.java | 45 + .../handlers/get/ListSegments.java | 40 + .../lauriewired/handlers/set/RenameData.java | 69 + .../handlers/set/RenameFunction.java | 63 + .../handlers/set/RenameFunctionByAddress.java | 81 + .../handlers/set/RenameVariable.java | 170 ++ .../handlers/set/SearchFunctions.java | 53 + .../handlers/set/SetFunctionPrototype.java | 202 ++ .../handlers/set/SetLocalVariableType.java | 335 +++ .../com/lauriewired/util/GhidraUtils.java | 49 + .../java/com/lauriewired/util/ParseUtils.java | 119 + 31 files changed, 2284 insertions(+), 2365 deletions(-) create mode 100644 src/main/java/com/lauriewired/handlers/Handler.java create mode 100644 src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java create mode 100644 src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java create mode 100644 src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java create mode 100644 src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java create mode 100644 src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java create mode 100644 src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java create mode 100644 src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java create mode 100644 src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java create mode 100644 src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java create mode 100644 src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java create mode 100644 src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java create mode 100644 src/main/java/com/lauriewired/handlers/get/GetXrefsFrom.java create mode 100644 src/main/java/com/lauriewired/handlers/get/GetXrefsTo.java create mode 100644 src/main/java/com/lauriewired/handlers/get/ListDefinedData.java create mode 100644 src/main/java/com/lauriewired/handlers/get/ListDefinedStrings.java create mode 100644 src/main/java/com/lauriewired/handlers/get/ListExports.java create mode 100644 src/main/java/com/lauriewired/handlers/get/ListFunctions.java create mode 100644 src/main/java/com/lauriewired/handlers/get/ListImports.java create mode 100644 src/main/java/com/lauriewired/handlers/get/ListNamespaces.java create mode 100644 src/main/java/com/lauriewired/handlers/get/ListSegments.java create mode 100644 src/main/java/com/lauriewired/handlers/set/RenameData.java create mode 100644 src/main/java/com/lauriewired/handlers/set/RenameFunction.java create mode 100644 src/main/java/com/lauriewired/handlers/set/RenameFunctionByAddress.java create mode 100644 src/main/java/com/lauriewired/handlers/set/RenameVariable.java create mode 100644 src/main/java/com/lauriewired/handlers/set/SearchFunctions.java create mode 100644 src/main/java/com/lauriewired/handlers/set/SetFunctionPrototype.java create mode 100644 src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java create mode 100644 src/main/java/com/lauriewired/util/GhidraUtils.java create mode 100644 src/main/java/com/lauriewired/util/ParseUtils.java diff --git a/src/main/java/com/lauriewired/GhidraMCPPlugin.java b/src/main/java/com/lauriewired/GhidraMCPPlugin.java index c90614b..4b4ea63 100644 --- a/src/main/java/com/lauriewired/GhidraMCPPlugin.java +++ b/src/main/java/com/lauriewired/GhidraMCPPlugin.java @@ -1,2384 +1,119 @@ package com.lauriewired; +import com.lauriewired.handlers.Handler; 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.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.DecompileOptions; -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.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.util.task.TaskMonitorAdapter; -import ghidra.program.model.pcode.HighVariable; -import ghidra.program.model.pcode.Varnode; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.DataTypeConflictHandler; -import ghidra.program.model.data.DataTypeComponent; -import ghidra.program.model.data.DataTypeManager; -import ghidra.program.model.data.Structure; -import ghidra.program.model.data.StructureDataType; -import ghidra.program.model.data.PointerDataType; -import ghidra.program.model.data.Undefined1DataType; -import ghidra.program.model.listing.Variable; -import ghidra.program.model.mem.Memory; -import ghidra.app.decompiler.component.DecompilerUtils; -import ghidra.app.decompiler.ClangToken; import ghidra.framework.options.Options; -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; +import org.reflections.Reflections; -import javax.swing.SwingUtilities; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Constructor; import java.net.InetSocketAddress; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import ghidra.program.model.data.CategoryPath; -import ghidra.app.services.DataTypeManagerService; -import ghidra.util.data.DataTypeParser; -import ghidra.util.data.DataTypeParser.AllowedDataTypes; - -/** - * GhidraMCP Plugin - Model Context Protocol Server for Ghidra - * - * This plugin creates an HTTP server that exposes Ghidra's analysis - * capabilities - * through a RESTful API, enabling AI language models to autonomously perform - * reverse engineering tasks. The plugin integrates with the CodeBrowser tool - * and provides comprehensive access to: - * - * - * - *

Server Lifecycle

- * The HTTP server automatically starts when the plugin is enabled in - * CodeBrowser - * with an active program loaded. The server runs on a configurable port - * (default: 8080) - * and remains active while the CodeBrowser session continues. - * - *

API Endpoints

- * The plugin exposes over 20 REST endpoints for comprehensive binary analysis: - * - * - *

Thread Safety

- * All Ghidra API interactions are properly synchronized using - * SwingUtilities.invokeAndWait() - * to ensure thread safety with Ghidra's event dispatch thread. - * - * @author LaurieWired - * @version 1.3.2 - * @since Ghidra 11.3.2 - * @see ghidra.framework.plugintool.Plugin - * @see com.sun.net.httpserver.HttpServer - */ -@PluginInfo(status = PluginStatus.RELEASED, packageName = ghidra.app.DeveloperPluginPackage.NAME, category = "Developer", shortDescription = "HTTP server plugin", description = "Starts an embedded HTTP server to expose program data. Port configurable via Tool Options.") +@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 { - /** The embedded HTTP server instance that handles all API requests */ - private HttpServer server; - - /** Configuration category name for tool options */ - private static final String OPTION_CATEGORY_NAME = "GhidraMCP HTTP Server"; - - /** Configuration option name for the server address setting */ - private static final String ADDRESS_OPTION_NAME = "Server Address"; - - /** Default address for the HTTP server */ - private static final String DEFAULT_ADDRESS = "127.0.0.1"; - - /** Configuration option name for the server port setting */ - private static final String PORT_OPTION_NAME = "Server Port"; - - /** Configuration option name for the decompile timeout setting */ - private static final String DECOMPILE_TIMEOUT_OPTION_NAME = "Decompile Timeout"; - - /** Default port number for the HTTP server (8080) */ - private static final int DEFAULT_PORT = 8080; - - /** Default decompile timeout in seconds */ - private static final int DEFAULT_DECOMPILE_TIMEOUT = 30; - - private int decompileTimeout; - - /** - * Constructs a new GhidraMCP plugin instance and initializes the HTTP server. - * - * This constructor: - *
    - *
  1. Registers the port configuration option in Ghidra's tool options
  2. - *
  3. Starts the embedded HTTP server on the configured port
  4. - *
  5. Creates all REST API endpoint handlers
  6. - *
- * - * The server will only function properly when: - * - * - * @param tool The Ghidra PluginTool instance that hosts this plugin - * @throws IllegalStateException if the HTTP server fails to start - * @see #startServer() - */ - public GhidraMCPPlugin(PluginTool tool) { - super(tool); - Msg.info(this, "GhidraMCPPlugin loading..."); - - // Register the configuration option - Options options = tool.getOptions(OPTION_CATEGORY_NAME); - options.registerOption(ADDRESS_OPTION_NAME, DEFAULT_ADDRESS, - null, // No help location for now - "The network address the embedded HTTP server will listen on. " + - "Requires Ghidra restart or plugin reload to take effect after changing."); - 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."); - options.registerOption(DECOMPILE_TIMEOUT_OPTION_NAME, DEFAULT_DECOMPILE_TIMEOUT, - null, - "Decompilation timeout. " + - "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!"); - } - - /** - * Initializes and starts the embedded HTTP server with all API endpoints. - * - * This method creates an HTTP server instance and registers handlers for all - * supported REST API endpoints. The server supports: - * - *

Function Analysis Endpoints:

- * - * - *

Symbol Management Endpoints:

- * - * - *

Analysis and Reference Endpoints:

- * - * - *

Commenting and Annotation:

- * - * - * The server runs on a separate thread to avoid blocking Ghidra's UI thread. - * All endpoints return plain text responses with UTF-8 encoding. - * - * @throws IOException if the server cannot bind to the configured port - * @see #sendResponse(HttpExchange, String) - * @see #parseQueryParams(HttpExchange) - */ - private void startServer() throws IOException { - // Read the configured port - Options options = tool.getOptions(OPTION_CATEGORY_NAME); - String listenAddress = options.getString(ADDRESS_OPTION_NAME, DEFAULT_ADDRESS); - 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; - } - - InetSocketAddress inetAddress = new InetSocketAddress(listenAddress, port); - - if (inetAddress.isUnresolved()) { - Msg.error(this, "Failed to resolve listen address."); - return; - } - - server = HttpServer.create(inetAddress, 0); - - // Each listing endpoint uses offset & limit from query params: - server.createContext("/methods", exchange -> { - Map 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 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 params = parsePostParams(exchange); - String response = renameFunction(params.get("oldName"), params.get("newName")) - ? "Renamed successfully" - : "Rename failed"; - sendResponse(exchange, response); - }); - - server.createContext("/renameData", exchange -> { - Map params = parsePostParams(exchange); - renameDataAtAddress(params.get("address"), params.get("newName")); - sendResponse(exchange, "Rename data attempted"); - }); - - server.createContext("/renameVariable", exchange -> { - Map 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 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 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 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 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 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 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 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 qparams = parseQueryParams(exchange); - String address = qparams.get("address"); - sendResponse(exchange, decompileFunctionByAddress(address)); - }); - - server.createContext("/disassemble_function", exchange -> { - Map qparams = parseQueryParams(exchange); - String address = qparams.get("address"); - sendResponse(exchange, disassembleFunction(address)); - }); - - server.createContext("/set_decompiler_comment", exchange -> { - Map 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 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 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 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 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 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 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 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 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.createContext("/create_struct", exchange -> { - Map params = parsePostParams(exchange); - String name = params.get("name"); - String category = params.get("category"); - long size = parseIntOrDefault(params.get("size"), 0); - String membersJson = params.get("members"); // Optional - - if (name == null || name.isEmpty()) { - sendResponse(exchange, "Struct name is required"); - return; - } - sendResponse(exchange, createStruct(name, category, (int) size, membersJson)); - }); - - server.createContext("/add_struct_members", exchange -> { - Map params = parsePostParams(exchange); - String structName = params.get("struct_name"); - String category = params.get("category"); - String membersJson = params.get("members"); - - if (structName == null || membersJson == null) { - sendResponse(exchange, "struct_name and members are required"); - return; - } - sendResponse(exchange, addStructMembers(structName, category, membersJson)); - }); - - server.createContext("/clear_struct", exchange -> { - Map params = parsePostParams(exchange); - String structName = params.get("struct_name"); - String category = params.get("category"); - if (structName == null) { - sendResponse(exchange, "struct_name is required"); - return; - } - sendResponse(exchange, clearStruct(structName, category)); - }); - - server.createContext("/get_struct", exchange -> { - Map qparams = parseQueryParams(exchange); - String structName = qparams.get("name"); - String category = qparams.get("category"); - if (structName == null) { - sendResponse(exchange, "name is required"); - return; - } - sendResponse(exchange, getStruct(structName, category)); - }); - - server.createContext("/get_data_by_label", exchange -> { - Map q = parseQueryParams(exchange); - String label = q.get("label"); - sendResponse(exchange, getDataByLabel(label)); - }); - - server.createContext("/get_bytes", exchange -> { - Map q = parseQueryParams(exchange); - String addrStr = q.get("address"); - int size = parseIntOrDefault(q.get("size"), 1); - sendResponse(exchange, getBytes(addrStr, size)); - }); - - server.createContext("/search_bytes", exchange -> { - Map q = parseQueryParams(exchange); - String bytesHex = q.get("bytes"); - int offset = parseIntOrDefault(q.get("offset"), 0); - int limit = parseIntOrDefault(q.get("limit"), 100); - sendResponse(exchange, searchBytes(bytesHex, offset, limit)); - }); - - 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 - // ---------------------------------------------------------------------------------- - - /** - * Retrieves a paginated list of all function names in the current program. - * - * This method scans the program's function manager to collect all function - * names - * and returns a subset based on the specified offset and limit parameters. - * The results are returned as a newline-separated string. - * - * @param offset Starting index for pagination (0-based) - * @param limit Maximum number of function names to return - * @return Newline-separated string of function names, or error message if no - * program loaded - * @see #paginateList(List, int, int) - * @see FunctionManager#getFunctions(boolean) - */ - private String getAllFunctionNames(int offset, int limit) { - Program program = getCurrentProgram(); - if (program == null) - return "No program loaded"; - - List 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 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 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 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 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 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 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 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 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 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. - // ---------------------------------------------------------------------------------- - - /** - * Decompiles a function by its name and returns the generated C pseudocode. - * - * This method locates a function by name within the current program and uses - * Ghidra's DecompInterface to generate readable C-like pseudocode. The - * decompilation - * process includes: - *
    - *
  • Function signature analysis
  • - *
  • Local variable identification and typing
  • - *
  • Control flow reconstruction
  • - *
  • High-level language construct generation
  • - *
- * - * Thread Safety: This method uses Ghidra's decompiler interface which - * is thread-safe for read operations. - * - * @param name The exact name of the function to decompile (case-sensitive) - * @return The decompiled C pseudocode as a string, or an error message if: - *
    - *
  • No program is currently loaded
  • - *
  • Function with the specified name is not found
  • - *
  • Decompilation fails due to analysis issues
  • - *
- * @see DecompInterface#decompileFunction(Function, int, TaskMonitor) - * @see #decompileFunctionByAddress(String) - */ - private String decompileFunctionByName(String name) { - Program program = getCurrentProgram(); - if (program == null) - return "No program loaded"; - DecompInterface decomp = new DecompInterface(); - DecompileOptions options = new DecompileOptions(); - options.setRespectReadOnly(true); - decomp.setOptions(options); - decomp.openProgram(program); - for (Function func : program.getFunctionManager().getFunctions(true)) { - if (func.getName().equals(name)) { - DecompileResults result = decomp.decompileFunction(func, this.decompileTimeout, - new ConsoleTaskMonitor()); - if (result != null && result.decompileCompleted()) { - return result.getDecompiledFunction().getC(); - } else { - return "Decompilation failed"; - } - } - } - return "Function not found"; - } - - /** - * Renames a function from its current name to a new user-specified name. - * - * This method performs a thread-safe rename operation by: - *
    - *
  1. Locating the function by its current name
  2. - *
  3. Executing the rename within a Ghidra transaction on the Swing EDT
  4. - *
  5. Marking the new name as USER_DEFINED source type
  6. - *
  7. Committing or rolling back the transaction based on success
  8. - *
- * - * Thread Safety: This method uses SwingUtilities.invokeAndWait() to - * ensure - * all Ghidra API calls occur on the Event Dispatch Thread, preventing race - * conditions - * and maintaining data integrity. - * - * Transaction Management: The rename operation is wrapped in a Ghidra - * transaction - * to ensure atomicity - either the rename succeeds completely or is fully - * reverted. - * - * @param oldName The current name of the function to rename (case-sensitive) - * @param newName The desired new name for the function - * @return {@code true} if the function was successfully renamed, - * {@code false} if no program is loaded, function not found, or rename - * failed - * @throws IllegalArgumentException if either parameter is null or empty - * @see Function#setName(String, SourceType) - * @see SourceType#USER_DEFINED - * @see Program#startTransaction(String) - */ - 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, this.decompileTimeout, 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 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, this.decompileTimeout, 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"); - } - - /** - * Result container for function prototype setting operations. - * - * This immutable class encapsulates the outcome of attempting to set a - * function's - * prototype signature, including both success/failure status and detailed error - * information for debugging purposes. - * - *

- * The class is designed to provide comprehensive feedback about prototype - * setting operations, which can be complex due to: - *

    - *
  • C signature parsing requirements
  • - *
  • Data type resolution in various categories
  • - *
  • Parameter count and type validation
  • - *
  • Ghidra's internal function signature constraints
  • - *
- * - * Usage Example: - * - *
{@code
-	 * PrototypeResult result = setFunctionPrototype("0x401000", "int foo(char* str, int len)");
-	 * if (result.isSuccess()) {
-	 * 	System.out.println("Prototype set successfully");
-	 * } else {
-	 * 	System.err.println("Failed: " + result.getErrorMessage());
-	 * }
-	 * }
- * - * @see #setFunctionPrototype(String, String) - * @since 1.3.0 - */ - 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 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, this.decompileTimeout, 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 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 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 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 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(); - } - - /** Retrieve address and data value by specifying a label name */ - private String getDataByLabel(String label) { - Program program = getCurrentProgram(); - if (program == null) - return "No program loaded"; - if (label == null || label.isEmpty()) - return "Label is required"; - - SymbolTable st = program.getSymbolTable(); - SymbolIterator it = st.getSymbols(label); - if (!it.hasNext()) - return "Label not found: " + label; - - StringBuilder sb = new StringBuilder(); - while (it.hasNext()) { - Symbol s = it.next(); - Address a = s.getAddress(); - Data d = program.getListing().getDefinedDataAt(a); - String v = (d != null) ? escapeString(String.valueOf(d.getDefaultValueRepresentation())) - : "(no defined data)"; - sb.append(String.format("%s -> %s : %s%n", label, a, v)); - } - return sb.toString(); - } - - /** Read memory by specifying address and size (results in Hexdump style) */ - private String getBytes(String addressStr, int size) { - Program program = getCurrentProgram(); - if (program == null) - return "No program loaded"; - if (addressStr == null || addressStr.isEmpty()) - return "Address is required"; - if (size <= 0) - return "Size must be > 0"; - - try { - Address addr = program.getAddressFactory().getAddress(addressStr); - byte[] buf = new byte[size]; - int read = program.getMemory().getBytes(addr, buf); - return hexdump(addr, buf, read); - } catch (Exception e) { - return "Error reading memory: " + e.getMessage(); - } - } - - /** Scan the entire program to search for a byte sequence */ - private String searchBytes(String bytesHex, int offset, int limit) { - Program program = getCurrentProgram(); - if (program == null) - return "No program loaded"; - if (bytesHex == null || bytesHex.isEmpty()) - return "Byte sequence required"; - - byte[] needle; - try { - needle = decodeHex(bytesHex); - } catch (IllegalArgumentException e) { - return "Invalid hex string: " + bytesHex; - } - - Memory mem = program.getMemory(); - List hits = new ArrayList<>(); - - Address cur = mem.getMinAddress(); - while (cur != null && hits.size() < offset + limit) { - Address found = mem.findBytes(cur, needle, null, true, TaskMonitorAdapter.DUMMY_MONITOR); - if (found == null) - break; - hits.add(found.toString()); - - cur = found.add(1); - } - - return paginateList(hits, offset, limit); - } - - private String hexdump(Address base, byte[] buf, int len) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < len; i += 16) { - sb.append(String.format("%s ", base.add(i))); - for (int j = 0; j < 16 && (i + j) < len; j++) { - sb.append(String.format("%02X ", buf[i + j])); - } - sb.append('\n'); - } - return sb.toString(); - } - - private byte[] decodeHex(String hex) { - hex = hex.replaceAll("\\s+", ""); - if (hex.length() % 2 != 0) - throw new IllegalArgumentException(); - byte[] out = new byte[hex.length() / 2]; - for (int i = 0; i < out.length; i++) { - out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); - } - return out; - } - - /** - * 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) { - DataTypeManagerService dtms = tool.getService(DataTypeManagerService.class); - DataTypeManager[] managers = dtms.getDataTypeManagers(); - DataType dt = null; - - List managerList = new ArrayList<>(); - for (DataTypeManager manager : managers) { - if (manager != dtm) - managerList.add(manager); - } - managerList.addFirst(dtm); - - DataTypeParser parser = null; - - for (DataTypeManager manager : managerList) { - try { - parser = new DataTypeParser(manager, null, null, AllowedDataTypes.ALL); - dt = parser.parse(typeName); - if (dt != null) { - return dt; // Found a successful parse, return - } - } catch (Exception e) { - // Continue to next manager if this one fails - } - } - - // 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 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; - } - - private static class StructMember { - String name; - String type; - String comment; - double offset = -1; // Use double to handle GSON parsing number as double - } - - private String createStruct(String name, String category, int size, String membersJson) { - Program program = getCurrentProgram(); - if (program == null) - return "No program loaded"; - - final AtomicReference result = new AtomicReference<>(); - try { - SwingUtilities.invokeAndWait(() -> { - int txId = program.startTransaction("Create Struct"); - boolean success = false; - try { - DataTypeManager dtm = program.getDataTypeManager(); - CategoryPath path = new CategoryPath(category == null ? "/" : category); - - if (dtm.getDataType(path, name) != null) { - result.set("Error: Struct " + name + " already exists in category " + path); - return; - } - StructureDataType newStruct = new StructureDataType(path, name, size, dtm); - - StringBuilder responseBuilder = new StringBuilder( - "Struct " + name + " created successfully in category " + path); - - if (membersJson != null && !membersJson.isEmpty()) { - Gson gson = new Gson(); - StructMember[] members = gson.fromJson(membersJson, StructMember[].class); - - int membersAdded = 0; - for (StructMember member : members) { - DataType memberDt = resolveDataType(dtm, member.type); - if (memberDt == null) { - responseBuilder.append("\nError: Could not resolve data type '").append(member.type) - .append("' for member '").append(member.name) - .append("'. Aborting further member creation."); - break; - } - - if (member.offset != -1) { - newStruct.insertAtOffset((int) member.offset, memberDt, -1, member.name, - member.comment); - } else { - newStruct.add(memberDt, member.name, member.comment); - } - membersAdded++; - } - responseBuilder.append("\nAdded ").append(membersAdded).append(" members."); - } - dtm.addDataType(newStruct, DataTypeConflictHandler.DEFAULT_HANDLER); - result.set(responseBuilder.toString()); - success = true; - } catch (Exception e) { - result.set("Error: Failed to create struct: " + e.getMessage()); - } finally { - program.endTransaction(txId, success); - } - }); - } catch (InterruptedException | InvocationTargetException e) { - return "Error: Failed to execute create struct on Swing thread: " + e.getMessage(); - } - return result.get(); - } - - private String addStructMembers(String structName, String category, String membersJson) { - Program program = getCurrentProgram(); - if (program == null) - return "No program loaded"; - - final AtomicReference result = new AtomicReference<>(); - try { - SwingUtilities.invokeAndWait(() -> { - int txId = program.startTransaction("Add Struct Member"); - boolean success = false; - try { - DataTypeManager dtm = program.getDataTypeManager(); - CategoryPath path = new CategoryPath(category == null ? "/" : category); - DataType dt = dtm.getDataType(path, structName); - - if (dt == null || !(dt instanceof Structure)) { - result.set("Error: Struct " + structName + " not found in category " + path); - return; - } - Structure struct = (Structure) dt; - - StringBuilder responseBuilder = new StringBuilder(); - - if (membersJson != null && !membersJson.isEmpty()) { - Gson gson = new Gson(); - StructMember[] members = gson.fromJson(membersJson, StructMember[].class); - - int membersAdded = 0; - for (StructMember member : members) { - DataType memberDt = resolveDataType(dtm, member.type); - if (memberDt == null) { - responseBuilder.append("\nError: Could not resolve data type '").append(member.type) - .append("' for member '").append(member.name) - .append("'. Aborting further member creation."); - break; - } - - if (member.offset != -1) { - struct.insertAtOffset((int) member.offset, memberDt, -1, member.name, member.comment); - } else { - struct.add(memberDt, member.name, member.comment); - } - membersAdded++; - } - responseBuilder.append("\nAdded ").append(membersAdded).append(" members."); - result.set(responseBuilder.toString()); - success = membersAdded > 0; - } - - } catch (Exception e) { - result.set("Error: Failed to add member to struct: " + e.getMessage()); - } finally { - program.endTransaction(txId, success); - } - }); - } catch (InterruptedException | InvocationTargetException e) { - return "Error: Failed to execute add struct member on Swing thread: " + e.getMessage(); - } - return result.get(); - } - - private String clearStruct(String structName, String category) { - Program program = getCurrentProgram(); - if (program == null) - return "No program loaded"; - - final AtomicReference result = new AtomicReference<>(); - try { - SwingUtilities.invokeAndWait(() -> { - int txId = program.startTransaction("Clear Struct"); - boolean success = false; - try { - DataTypeManager dtm = program.getDataTypeManager(); - CategoryPath path = new CategoryPath(category == null ? "/" : category); - DataType dt = dtm.getDataType(path, structName); - if (dt == null || !(dt instanceof Structure)) { - result.set("Error: Struct " + structName + " not found in category " + path); - return; - } - Structure struct = (Structure) dt; - if (struct.isNotYetDefined()) { - result.set("Struct " + structName + " is empty, nothing to clear."); - success = true; // Not an error state - return; - } - struct.deleteAll(); - result.set("Struct " + structName + " cleared."); - success = true; - } catch (Exception e) { - result.set("Error: Failed to clear struct: " + e.getMessage()); - } finally { - program.endTransaction(txId, success); - } - }); - } catch (InterruptedException | InvocationTargetException e) { - return "Error: Failed to execute clear struct on Swing thread: " + e.getMessage(); - } - return result.get(); - } - - private String getStruct(String structName, String category) { - Program program = getCurrentProgram(); - if (program == null) - return "No program loaded"; - - DataTypeManager dtm = program.getDataTypeManager(); - CategoryPath path = new CategoryPath(category == null ? "/" : category); - DataType dt = dtm.getDataType(path, structName); - - if (dt == null || !(dt instanceof Structure)) { - return "Error: Struct " + structName + " not found in category " + path; - } - - Structure struct = (Structure) dt; - - Map structRepr = new HashMap<>(); - structRepr.put("name", struct.getName()); - structRepr.put("category", struct.getCategoryPath().getPath()); - structRepr.put("size", struct.getLength()); - structRepr.put("isNotYetDefined", struct.isNotYetDefined()); - - List> membersList = new ArrayList<>(); - for (DataTypeComponent component : struct.getDefinedComponents()) { - Map memberMap = new HashMap<>(); - memberMap.put("name", component.getFieldName()); - memberMap.put("type", component.getDataType().getName()); - memberMap.put("offset", component.getOffset()); - memberMap.put("size", component.getLength()); - memberMap.put("comment", component.getComment()); - membersList.add(memberMap); - } - structRepr.put("members", membersList); - - Gson gson = new Gson(); - return gson.toJson(structRepr); - } - - // ---------------------------------------------------------------------------------- - // Utility: parse query params, parse post params, pagination, etc. - // ---------------------------------------------------------------------------------- - - /** - * Parses HTTP query parameters from the request URL into a key-value map. - * - * This method extracts query parameters from the URL (the portion after the '?' - * character) - * and decodes them using UTF-8 encoding. It handles URL-encoded parameter - * values and - * properly decodes special characters. - * - * Supported Format: {@code ?offset=10&limit=100&filter=test%20string} - * - * Example Usage: - * - *
{@code
-	 * // For URL: /methods?offset=50&limit=25
-	 * Map params = parseQueryParams(exchange);
-	 * int offset = parseIntOrDefault(params.get("offset"), 0); // Returns 50
-	 * int limit = parseIntOrDefault(params.get("limit"), 100); // Returns 25
-	 * }
- * - * @param exchange The HTTP exchange containing the request with query - * parameters - * @return A map of parameter names to their decoded values. Empty map if no - * parameters exist. - * Parameters without values are ignored. - * @see URLDecoder#decode(String, Charset) - * @see #parseIntOrDefault(String, int) - */ - private Map parseQueryParams(HttpExchange exchange) { - Map 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 parsePostParams(HttpExchange exchange) throws IOException { - byte[] body = exchange.getRequestBody().readAllBytes(); - String bodyStr = new String(body, StandardCharsets.UTF_8); - Map 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; - } - - /** - * Applies pagination to a list of strings and returns a formatted result. - * - * This utility method implements standard pagination behavior by: - *
    - *
  1. Validating and constraining the offset to valid bounds
  2. - *
  3. Calculating the end index based on offset + limit
  4. - *
  5. Extracting the requested subset of items
  6. - *
  7. Joining the results with newline characters
  8. - *
- * - * Pagination Logic: - *
    - *
  • If offset >= list size, returns empty string
  • - *
  • If offset + limit exceeds list size, returns items from offset to - * end
  • - *
  • Negative offsets are treated as 0
  • - *
- * - * Example: - * - *
{@code
-	 * List functions = Arrays.asList("main", "printf", "malloc", "free", "exit");
-	 * String page1 = paginateList(functions, 0, 2); // Returns "main\nprintf"
-	 * String page2 = paginateList(functions, 2, 2); // Returns "malloc\nfree"
-	 * String page3 = paginateList(functions, 4, 2); // Returns "exit"
-	 * }
- * - * @param items The complete list of string items to paginate - * @param offset Starting index for pagination (0-based, negative values treated - * as 0) - * @param limit Maximum number of items to return - * @return Newline-separated string of the requested items, or empty string if - * no items in range - * @see List#subList(int, int) - */ - private String paginateList(List 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 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(); - } - - /** - * Retrieves the currently active program from Ghidra's program manager. - * - * This method provides safe access to the current program being analyzed in - * the CodeBrowser. It handles cases where no program is loaded or the - * ProgramManager service is unavailable. - * - * Usage Pattern: This method is called by virtually every API endpoint - * to ensure operations are performed on the correct program context. - * - * @return The currently loaded Program instance, or {@code null} if: - *
    - *
  • No program is currently loaded in CodeBrowser
  • - *
  • ProgramManager service is not available
  • - *
  • Plugin is not properly initialized
  • - *
- * @see ProgramManager#getCurrentProgram() - * @see PluginTool#getService(Class) - */ - 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); - } - } - - /** - * Cleanly shuts down the HTTP server and releases plugin resources. - * - * This method is automatically called by Ghidra when: - *
    - *
  • The plugin is disabled in the CodeBrowser configuration
  • - *
  • The CodeBrowser tool is closed
  • - *
  • Ghidra is shutting down
  • - *
  • The plugin is being reloaded
  • - *
- * - * Shutdown Process: - *
    - *
  1. Stops the HTTP server with a 1-second grace period for active - * connections
  2. - *
  3. Nullifies the server reference to prevent further use
  4. - *
  5. Calls the parent dispose method to clean up plugin infrastructure
  6. - *
- * - * Thread Safety: This method can be called from any thread and safely - * handles concurrent access to the server instance. - * - * @see HttpServer#stop(int) - * @see Plugin#dispose() - */ - @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(); - } + 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; + private static final HashMap routes = new HashMap<>(); + + 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); + + Reflections reflections = new Reflections("com.lauriewired.handlers"); + Set> subclasses = reflections.getSubTypesOf(Handler.class); + for (Class clazz: subclasses) { + System.out.println(clazz.getName()); + try { + Constructor constructor = clazz.getConstructor(PluginTool.class); + Handler handler = (Handler) constructor.newInstance(tool); + if (routes.containsKey(handler.getPath())) { + Msg.error(this, "Handler class " + clazz.getName() + " already registered, skipped."); + continue; + } + routes.put(handler.getPath(), handler); + server.createContext(handler.getPath(), exchange -> { + try { + handler.handle(exchange); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } catch (NoSuchMethodException e) { + Msg.error(this, "Handler class " + clazz.getName() + + " doesn't have constructor xxx(PluginTool tool), skipped."); + } catch (Exception e) { + e.printStackTrace(); + } + } + + 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(); + } + + + @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(); + } } diff --git a/src/main/java/com/lauriewired/handlers/Handler.java b/src/main/java/com/lauriewired/handlers/Handler.java new file mode 100644 index 0000000..0a5e30d --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/Handler.java @@ -0,0 +1,18 @@ +package com.lauriewired.handlers; + +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; + +public abstract class Handler { + protected final PluginTool tool; + protected final String path; + + protected Handler(PluginTool tool, String path) { + this.tool = tool; + this.path = path; + } + + public String getPath() { return path; } + + public abstract void handle(HttpExchange exchange) throws Exception; +} diff --git a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java new file mode 100644 index 0000000..e682d4e --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java @@ -0,0 +1,56 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileResults; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.task.ConsoleTaskMonitor; + +import java.io.IOException; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.parseQueryParams; +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class DecompileFunctionByAddress extends Handler { + public DecompileFunctionByAddress(PluginTool tool) { + super(tool, "/decompile_function"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String address = qparams.get("address"); + sendResponse(exchange, decompileFunctionByAddress(address)); + } + + /** + * Decompile a function at the given address + */ + private String decompileFunctionByAddress(String addressStr) { + Program program = getCurrentProgram(tool); + 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.getListing().getFunctionContaining(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(); + } + } +} diff --git a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java new file mode 100644 index 0000000..3fbcc4d --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java @@ -0,0 +1,46 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileResults; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.task.ConsoleTaskMonitor; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class DecompileFunctionByName extends Handler { + public DecompileFunctionByName(PluginTool tool) { + super(tool, "/decompile"); + } + + public void handle(HttpExchange exchange) throws IOException { + String name = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + sendResponse(exchange, generateResponse(name)); + } + + private String generateResponse(String name) { + Program program = getCurrentProgram(tool); + 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"; + } +} diff --git a/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java b/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java new file mode 100644 index 0000000..572a13d --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java @@ -0,0 +1,64 @@ +package com.lauriewired.handlers.act; +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.*; + +import java.io.IOException; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.parseQueryParams; +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class DisassembleFunction extends Handler { + public DisassembleFunction(PluginTool tool) { + super(tool, "/disassemble_function"); + } + + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String address = qparams.get("address"); + sendResponse(exchange, disassembleFunction(address)); + } + + /** + * Get assembly code for a function + */ + private String disassembleFunction(String addressStr) { + Program program = getCurrentProgram(tool); + 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.getListing().getFunctionContaining(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(); + } + } +} diff --git a/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java b/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java new file mode 100644 index 0000000..3a5bee3 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java @@ -0,0 +1,34 @@ +package com.lauriewired.handlers.comment; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.CodeUnit; + +import java.util.Map; + +import static com.lauriewired.util.GhidraUtils.setCommentAtAddress; +import static com.lauriewired.util.ParseUtils.parsePostParams; +import static com.lauriewired.util.ParseUtils.sendResponse; + +public final class SetDecompilerComment extends Handler { + public SetDecompilerComment(PluginTool tool) { + super(tool, "/set_decompiler_comment"); + } + + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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"); + } + + /** + * Set a comment for a given address in the function pseudocode + */ + private boolean setDecompilerComment(String addressStr, String comment) { + return setCommentAtAddress(tool, addressStr, comment, CodeUnit.PRE_COMMENT, "Set decompiler comment"); + } +} diff --git a/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java b/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java new file mode 100644 index 0000000..966444a --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java @@ -0,0 +1,34 @@ +package com.lauriewired.handlers.comment; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.CodeUnit; + +import java.util.Map; + +import static com.lauriewired.util.GhidraUtils.setCommentAtAddress; +import static com.lauriewired.util.ParseUtils.parsePostParams; +import static com.lauriewired.util.ParseUtils.sendResponse; + +public final class SetDisassemblyComment extends Handler { + public SetDisassemblyComment(PluginTool tool) { + super(tool, "/set_disassembly_comment"); + } + + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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"); + } + + /** + * Set a comment for a given address in the function disassembly + */ + private boolean setDisassemblyComment(String addressStr, String comment) { + return setCommentAtAddress(tool, addressStr, comment, CodeUnit.EOL_COMMENT, "Set disassembly comment"); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java b/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java new file mode 100644 index 0000000..18acec9 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java @@ -0,0 +1,45 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Namespace; +import ghidra.program.model.symbol.Symbol; + +import java.io.IOException; +import java.util.*; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class GetAllClassNames extends Handler { + public GetAllClassNames(PluginTool tool) { + super(tool, "/classes"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, generateResponse(offset, limit)); + } + + private String generateResponse(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + + Set 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 sorted = new ArrayList<>(classNames); + Collections.sort(sorted); + return paginateList(sorted, offset, limit); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java b/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java new file mode 100644 index 0000000..cfbbd8a --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java @@ -0,0 +1,41 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static com.lauriewired.util.ParseUtils.parseIntOrDefault; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class GetAllFunctionNames extends Handler { + public GetAllFunctionNames(PluginTool tool) { + super(tool, "/methods"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, generateResponse(offset, limit)); + } + + private String generateResponse(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + + List names = new ArrayList<>(); + for (Function f : program.getFunctionManager().getFunctions(true)) { + names.add(f.getName()); + } + return paginateList(names, offset, limit); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java b/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java new file mode 100644 index 0000000..aef4f06 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java @@ -0,0 +1,32 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.app.services.CodeViewerService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.util.ProgramLocation; + +import java.io.IOException; + +import static com.lauriewired.util.ParseUtils.sendResponse; + +public final class GetCurrentAddress extends Handler { + public GetCurrentAddress(PluginTool tool) { + super(tool, "/get_current_address"); + } + + public void handle(HttpExchange exchange) throws IOException { + sendResponse(exchange, getCurrentAddress()); + } + + /** + * 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"; + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java b/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java new file mode 100644 index 0000000..f9fb5b5 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java @@ -0,0 +1,47 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.app.services.CodeViewerService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; + +import java.io.IOException; + +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class GetCurrentFunction extends Handler { + public GetCurrentFunction(PluginTool tool) { + super(tool, "/get_current_function"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + sendResponse(exchange, getCurrentFunction()); + } + + /** + * 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(tool); + 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()); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java b/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java new file mode 100644 index 0000000..ab52523 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java @@ -0,0 +1,54 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +import java.io.IOException; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.parseQueryParams; +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class GetFunctionByAddress extends Handler { + public GetFunctionByAddress(PluginTool tool) { + super(tool, "/get_function_by_address"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String address = qparams.get("address"); + sendResponse(exchange, getFunctionByAddress(address)); + } + + /** + * Get function by address + */ + private String getFunctionByAddress(String addressStr) { + Program program = getCurrentProgram(tool); + 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(); + } + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java b/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java new file mode 100644 index 0000000..be7a510 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java @@ -0,0 +1,73 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.FunctionManager; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.ReferenceIterator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class GetFunctionXrefs extends Handler { + public GetFunctionXrefs(PluginTool tool) { + super(tool, "/function_xrefs"); + } + + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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)); + } + + /** + * Get all references to a specific function by name + */ + private String getFunctionXrefs(String functionName, int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + if (functionName == null || functionName.isEmpty()) return "Function name is required"; + + try { + List 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(); + } + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/GetXrefsFrom.java b/src/main/java/com/lauriewired/handlers/get/GetXrefsFrom.java new file mode 100644 index 0000000..87d0e08 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetXrefsFrom.java @@ -0,0 +1,73 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.ReferenceManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class GetXrefsFrom extends Handler { + public GetXrefsFrom(PluginTool tool) { + super(tool, "/xrefs_from"); + } + + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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)); + } + + /** + * Get all references from a specific address (xref from) + */ + private String getXrefsFrom(String addressStr, int offset, int limit) { + Program program = getCurrentProgram(tool); + 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 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(); + } + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/GetXrefsTo.java b/src/main/java/com/lauriewired/handlers/get/GetXrefsTo.java new file mode 100644 index 0000000..bd89ac3 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetXrefsTo.java @@ -0,0 +1,66 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.ReferenceIterator; +import ghidra.program.model.symbol.ReferenceManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class GetXrefsTo extends Handler { + public GetXrefsTo(PluginTool tool) { + super(tool, "/xrefs_to"); + } + + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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)); + } + + /** + * Get all references to a specific address (xref to) + */ + private String getXrefsTo(String addressStr, int offset, int limit) { + Program program = getCurrentProgram(tool); + 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 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(); + } + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/ListDefinedData.java b/src/main/java/com/lauriewired/handlers/get/ListDefinedData.java new file mode 100644 index 0000000..644e22d --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/ListDefinedData.java @@ -0,0 +1,54 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.DataIterator; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class ListDefinedData extends Handler { + public ListDefinedData(PluginTool tool) { + super(tool, "/data"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listDefinedData(offset, limit)); + } + + private String listDefinedData(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + + List 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); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/ListDefinedStrings.java b/src/main/java/com/lauriewired/handlers/get/ListDefinedStrings.java new file mode 100644 index 0000000..7a75d7f --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/ListDefinedStrings.java @@ -0,0 +1,92 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.DataIterator; +import ghidra.program.model.listing.Program; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class ListDefinedStrings extends Handler { + public ListDefinedStrings(PluginTool tool) { + super(tool, "/strings"); + } + + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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)); + } + + /** + * List all defined strings in the program with their addresses + */ + private String listDefinedStrings(int offset, int limit, String filter) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + + List 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(); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/ListExports.java b/src/main/java/com/lauriewired/handlers/get/ListExports.java new file mode 100644 index 0000000..aee63b6 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/ListExports.java @@ -0,0 +1,49 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolIterator; +import ghidra.program.model.symbol.SymbolTable; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class ListExports extends Handler { + public ListExports(PluginTool tool) { + super(tool, "/imports"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listExports(offset, limit)); + } + + private String listExports(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + + SymbolTable table = program.getSymbolTable(); + SymbolIterator it = table.getAllSymbols(true); + + List 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); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/ListFunctions.java b/src/main/java/com/lauriewired/handlers/get/ListFunctions.java new file mode 100644 index 0000000..7e54c9d --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/ListFunctions.java @@ -0,0 +1,40 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +import java.io.IOException; + +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class ListFunctions extends Handler { + public ListFunctions(PluginTool tool) { + super(tool, "/list_functions"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + sendResponse(exchange, listFunctions()); + } + + /** + * List all functions in the database + */ + private String listFunctions() { + Program program = getCurrentProgram(tool); + 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(); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/ListImports.java b/src/main/java/com/lauriewired/handlers/get/ListImports.java new file mode 100644 index 0000000..b36e4d4 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/ListImports.java @@ -0,0 +1,40 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Symbol; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class ListImports extends Handler { + public ListImports(PluginTool tool) { + super(tool, "/imports"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listImports(offset, limit)); + } + + private String listImports(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + + List lines = new ArrayList<>(); + for (Symbol symbol : program.getSymbolTable().getExternalSymbols()) { + lines.add(symbol.getName() + " -> " + symbol.getAddress()); + } + return paginateList(lines, offset, limit); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/ListNamespaces.java b/src/main/java/com/lauriewired/handlers/get/ListNamespaces.java new file mode 100644 index 0000000..c76c195 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/ListNamespaces.java @@ -0,0 +1,45 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.GlobalNamespace; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Namespace; +import ghidra.program.model.symbol.Symbol; + +import java.io.IOException; +import java.util.*; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class ListNamespaces extends Handler { + public ListNamespaces(PluginTool tool) { + super(tool, "/namespaces"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listNamespaces(offset, limit)); + } + + private String listNamespaces(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + + Set 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 sorted = new ArrayList<>(namespaces); + Collections.sort(sorted); + return paginateList(sorted, offset, limit); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/ListSegments.java b/src/main/java/com/lauriewired/handlers/get/ListSegments.java new file mode 100644 index 0000000..9af085d --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/ListSegments.java @@ -0,0 +1,40 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class ListSegments extends Handler { + public ListSegments(PluginTool tool) { + super(tool, "/segments"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listSegments(offset, limit)); + } + + private String listSegments(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + + List 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); + } +} diff --git a/src/main/java/com/lauriewired/handlers/set/RenameData.java b/src/main/java/com/lauriewired/handlers/set/RenameData.java new file mode 100644 index 0000000..4ae65e0 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/set/RenameData.java @@ -0,0 +1,69 @@ +package com.lauriewired.handlers.set; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Listing; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolTable; +import ghidra.util.Msg; + +import javax.swing.*; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.parsePostParams; +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class RenameData extends Handler { + public RenameData(PluginTool tool) { + super(tool, "/renameData"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + renameDataAtAddress(params.get("address"), params.get("newName")); + sendResponse(exchange, "Rename data attempted"); + } + + private void renameDataAtAddress(String addressStr, String newName) { + Program program = getCurrentProgram(tool); + 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); + } + } +} diff --git a/src/main/java/com/lauriewired/handlers/set/RenameFunction.java b/src/main/java/com/lauriewired/handlers/set/RenameFunction.java new file mode 100644 index 0000000..e2d7d4a --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/set/RenameFunction.java @@ -0,0 +1,63 @@ +package com.lauriewired.handlers.set; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.Msg; + +import javax.swing.*; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.lauriewired.util.ParseUtils.parsePostParams; +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class RenameFunction extends Handler { + public RenameFunction(PluginTool tool) { + super(tool, "/renameFunction"); + } + + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String response = rename(params.get("oldName"), params.get("newName")) + ? "Renamed successfully" : "Rename failed"; + sendResponse(exchange, response); + } + + private boolean rename(String oldName, String newName) { + Program program = getCurrentProgram(tool); + 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 { + program.endTransaction(tx, successFlag.get()); + } + }); + } + catch (InterruptedException | InvocationTargetException e) { + Msg.error(this, "Failed to execute rename on Swing thread", e); + } + return successFlag.get(); + } +} diff --git a/src/main/java/com/lauriewired/handlers/set/RenameFunctionByAddress.java b/src/main/java/com/lauriewired/handlers/set/RenameFunctionByAddress.java new file mode 100644 index 0000000..f13345c --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/set/RenameFunctionByAddress.java @@ -0,0 +1,81 @@ +package com.lauriewired.handlers.set; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.Msg; + +import javax.swing.*; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.lauriewired.util.ParseUtils.parsePostParams; +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class RenameFunctionByAddress extends Handler { + public RenameFunctionByAddress(PluginTool tool) { + super(tool, "/rename_function_by_address"); + } + + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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"); + } + + /** + * Rename a function by its address + */ + private boolean renameFunctionByAddress(String functionAddrStr, String newName) { + Program program = getCurrentProgram(tool); + 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 = program.getListing().getFunctionContaining(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()); + } + } +} diff --git a/src/main/java/com/lauriewired/handlers/set/RenameVariable.java b/src/main/java/com/lauriewired/handlers/set/RenameVariable.java new file mode 100644 index 0000000..05fa4c4 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/set/RenameVariable.java @@ -0,0 +1,170 @@ +package com.lauriewired.handlers.set; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileResults; +import ghidra.framework.plugintool.PluginTool; +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.util.Msg; +import ghidra.util.task.ConsoleTaskMonitor; + +import javax.swing.*; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.lauriewired.util.ParseUtils.parsePostParams; +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class RenameVariable extends Handler { + public RenameVariable(PluginTool tool) { + super(tool, "/renameVariable"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map 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); + } + + private String renameVariableInFunction(String functionName, String oldVarName, String newVarName) { + Program program = getCurrentProgram(tool); + 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 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, + HighFunctionDBUtil.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 { + 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; + } +} diff --git a/src/main/java/com/lauriewired/handlers/set/SearchFunctions.java b/src/main/java/com/lauriewired/handlers/set/SearchFunctions.java new file mode 100644 index 0000000..3011c1a --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/set/SearchFunctions.java @@ -0,0 +1,53 @@ +package com.lauriewired.handlers.set; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class SearchFunctions extends Handler { + public SearchFunctions(PluginTool tool) { + super(tool, "/searchFunctions"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map 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)); + } + + private String searchFunctionsByName(String searchTerm, int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) return "No program loaded"; + if (searchTerm == null || searchTerm.isEmpty()) return "Search term is required"; + + List 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); + } +} diff --git a/src/main/java/com/lauriewired/handlers/set/SetFunctionPrototype.java b/src/main/java/com/lauriewired/handlers/set/SetFunctionPrototype.java new file mode 100644 index 0000000..cc041f9 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/set/SetFunctionPrototype.java @@ -0,0 +1,202 @@ +package com.lauriewired.handlers.set; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.Msg; +import ghidra.util.task.ConsoleTaskMonitor; + +import javax.swing.*; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.lauriewired.util.ParseUtils.parsePostParams; +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class SetFunctionPrototype extends Handler { + public SetFunctionPrototype(PluginTool tool) { + super(tool, "/set_function_prototype"); + } + + /** + * 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; + } + } + + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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()); + } + } + + /** + * Set a function's prototype with proper error handling using ApplyFunctionSignatureCmd + */ + private PrototypeResult setFunctionPrototype(String functionAddrStr, String prototype) { + // Input validation + Program program = getCurrentProgram(tool); + 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 = program.getListing().getFunctionContaining(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()); + } + } +} diff --git a/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java new file mode 100644 index 0000000..9b7ed0f --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java @@ -0,0 +1,335 @@ +package com.lauriewired.handlers.set; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileResults; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.PointerDataType; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.HighFunctionDBUtil; +import ghidra.program.model.pcode.HighSymbol; +import ghidra.program.model.pcode.HighVariable; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.Msg; +import ghidra.util.task.ConsoleTaskMonitor; + +import javax.swing.*; +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import static com.lauriewired.util.ParseUtils.parsePostParams; +import static com.lauriewired.util.ParseUtils.sendResponse; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class SetLocalVariableType extends Handler { + public SetLocalVariableType(PluginTool tool) { + super(tool, "/set_local_variable_type"); + } + + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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(tool); + 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()); + } + + /** + * Set a local variable's type using HighFunctionDBUtil.updateDBVariable + */ + private boolean setLocalVariableType(String functionAddrStr, String variableName, String newType) { + // Input validation + Program program = getCurrentProgram(tool); + 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 = program.getListing().getFunctionContaining(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 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 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; + } + + /** + * 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 high symbol by name in the given high function + */ + private HighSymbol findSymbolByName(ghidra.program.model.pcode.HighFunction highFunction, String variableName) { + Iterator 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()); + } + } +} diff --git a/src/main/java/com/lauriewired/util/GhidraUtils.java b/src/main/java/com/lauriewired/util/GhidraUtils.java new file mode 100644 index 0000000..cb85053 --- /dev/null +++ b/src/main/java/com/lauriewired/util/GhidraUtils.java @@ -0,0 +1,49 @@ +package com.lauriewired.util; + +import ghidra.app.services.ProgramManager; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; + +import javax.swing.*; +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class GhidraUtils { + public static Program getCurrentProgram(PluginTool tool) { + ProgramManager pm = tool.getService(ProgramManager.class); + return pm != null ? pm.getCurrentProgram() : null; + } + + public static boolean setCommentAtAddress(PluginTool tool, + String addressStr, String comment, int commentType, String transactionName) { + Program program = getCurrentProgram(tool); + 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(GhidraUtils.class, "Error setting " + transactionName.toLowerCase(), e); + } finally { + program.endTransaction(tx, success.get()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + Msg.error(GhidraUtils.class, + "Failed to execute " + transactionName.toLowerCase() + " on Swing thread", e); + } + + return success.get(); + } +} diff --git a/src/main/java/com/lauriewired/util/ParseUtils.java b/src/main/java/com/lauriewired/util/ParseUtils.java new file mode 100644 index 0000000..3522fdb --- /dev/null +++ b/src/main/java/com/lauriewired/util/ParseUtils.java @@ -0,0 +1,119 @@ +package com.lauriewired.util; + +import com.sun.net.httpserver.HttpExchange; +import ghidra.util.Msg; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class ParseUtils { + /** + * Parse query parameters from the URL, e.g. ?offset=10&limit=100 + */ + public static Map parseQueryParams(HttpExchange exchange) { + Map 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(ParseUtils.class, "Error decoding URL parameter", e); + } + } + } + } + return result; + } + + /** + * Parse post body form params, e.g. oldName=foo&newName=bar + */ + public static Map parsePostParams(HttpExchange exchange) throws IOException { + byte[] body = exchange.getRequestBody().readAllBytes(); + String bodyStr = new String(body, StandardCharsets.UTF_8); + Map 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(ParseUtils.class, "Error decoding URL parameter", e); + } + } + } + return params; + } + + /** + * Convert a list of strings into one big newline-delimited string, applying offset & limit. + */ + public static String paginateList(List 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 sub = items.subList(start, end); + return String.join("\n", sub); + } + + /** + * Parse an integer from a string, or return defaultValue if null/invalid. + */ + public static 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. + */ + public static 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(); + } + + /** + * Send responses + */ + public static 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); + } + } +} From 6dca971235e200f36b7649518404e1466a8177d3 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 19:23:53 -0700 Subject: [PATCH 02/17] Update pom.xml --- pom.xml | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7411225..f6c38a8 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,12 @@ ${project.basedir}/lib/Gui.jar + + org.reflections + reflections + 0.10.2 + + junit @@ -85,6 +91,12 @@ + + 21 + 21 + UTF-8 + + @@ -93,13 +105,37 @@ maven-compiler-plugin 3.13.0 - 8 - 8 + 21 + 21 -Xlint:-options + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + GhidraMCP + + + + + ghidra:* + + + + + + From d1500a7e0dabdb808706f722b12ac295d58c6872 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 20:56:47 -0700 Subject: [PATCH 03/17] document everything and apply PRs 74 and 62 --- .../java/com/lauriewired/GhidraMCPPlugin.java | 361 +++++++--- .../com/lauriewired/handlers/Handler.java | 46 +- .../act/DecompileFunctionByAddress.java | 93 ++- .../handlers/act/DecompileFunctionByName.java | 76 +- .../handlers/act/DisassembleFunction.java | 108 +-- .../comment/SetDecompilerComment.java | 53 +- .../comment/SetDisassemblyComment.java | 54 +- .../handlers/get/GetAllClassNames.java | 76 +- .../handlers/get/GetAllFunctionNames.java | 63 +- .../handlers/get/GetCurrentAddress.java | 48 +- .../handlers/get/GetCurrentFunction.java | 82 ++- .../handlers/get/GetFunctionByAddress.java | 87 ++- .../handlers/get/GetFunctionXrefs.java | 104 +-- .../handlers/get/GetXrefsFrom.java | 105 +-- .../lauriewired/handlers/get/GetXrefsTo.java | 94 ++- .../handlers/get/ListDefinedData.java | 85 ++- .../handlers/get/ListDefinedStrings.java | 153 ++-- .../lauriewired/handlers/get/ListExports.java | 84 ++- .../handlers/get/ListFunctions.java | 68 +- .../lauriewired/handlers/get/ListImports.java | 64 +- .../handlers/get/ListNamespaces.java | 73 +- .../handlers/get/ListSegments.java | 64 +- .../lauriewired/handlers/set/RenameData.java | 104 +-- .../handlers/set/RenameFunction.java | 99 ++- .../handlers/set/RenameFunctionByAddress.java | 126 ++-- .../handlers/set/RenameVariable.java | 297 ++++---- .../handlers/set/SearchFunctions.java | 98 ++- .../handlers/set/SetFunctionPrototype.java | 407 ++++++----- .../handlers/set/SetLocalVariableType.java | 660 ++++++++++-------- .../com/lauriewired/util/GhidraUtils.java | 78 ++- .../java/com/lauriewired/util/ParseUtils.java | 198 +++--- 31 files changed, 2499 insertions(+), 1609 deletions(-) diff --git a/src/main/java/com/lauriewired/GhidraMCPPlugin.java b/src/main/java/com/lauriewired/GhidraMCPPlugin.java index 4b4ea63..f8f0b07 100644 --- a/src/main/java/com/lauriewired/GhidraMCPPlugin.java +++ b/src/main/java/com/lauriewired/GhidraMCPPlugin.java @@ -17,103 +17,270 @@ import java.net.InetSocketAddress; import java.util.*; -@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." -) +/** + * GhidraMCP Plugin - Model Context Protocol Server for Ghidra + * + * This plugin creates an HTTP server that exposes Ghidra's analysis + * capabilities + * through a RESTful API, enabling AI language models to autonomously perform + * reverse engineering tasks. The plugin integrates with the CodeBrowser tool + * and provides comprehensive access to: + * + *
    + *
  • Function decompilation and analysis
  • + *
  • Symbol and variable management
  • + *
  • Memory and data structure examination
  • + *
  • Cross-reference analysis
  • + *
  • Binary annotation and commenting
  • + *
+ * + *

Server Lifecycle

+ * The HTTP server automatically starts when the plugin is enabled in + * CodeBrowser + * with an active program loaded. The server runs on a configurable port + * (default: 8080) + * and remains active while the CodeBrowser session continues. + * + *

API Endpoints

+ * The plugin exposes over 20 REST endpoints for comprehensive binary analysis: + *
    + *
  • /methods - List all functions with pagination
  • + *
  • /decompile - Decompile functions by name or address
  • + *
  • /renameFunction - Rename functions and variables
  • + *
  • /xrefs_to - Analyze cross-references
  • + *
  • /strings - Extract and filter string data
  • + *
+ * + *

Thread Safety

+ * All Ghidra API interactions are properly synchronized using + * SwingUtilities.invokeAndWait() + * to ensure thread safety with Ghidra's event dispatch thread. + * + * @author LaurieWired + * @version 1.3.2 + * @since Ghidra 11.3.2 + * @see ghidra.framework.plugintool.Plugin + * @see com.sun.net.httpserver.HttpServer + */ +@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; - private static final HashMap routes = new HashMap<>(); - - 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); - - Reflections reflections = new Reflections("com.lauriewired.handlers"); - Set> subclasses = reflections.getSubTypesOf(Handler.class); - for (Class clazz: subclasses) { - System.out.println(clazz.getName()); - try { - Constructor constructor = clazz.getConstructor(PluginTool.class); - Handler handler = (Handler) constructor.newInstance(tool); - if (routes.containsKey(handler.getPath())) { - Msg.error(this, "Handler class " + clazz.getName() + " already registered, skipped."); - continue; - } - routes.put(handler.getPath(), handler); - server.createContext(handler.getPath(), exchange -> { - try { - handler.handle(exchange); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } catch (NoSuchMethodException e) { - Msg.error(this, "Handler class " + clazz.getName() + - " doesn't have constructor xxx(PluginTool tool), skipped."); - } catch (Exception e) { - e.printStackTrace(); - } - } - - 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(); - } - - - @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(); - } + /** The embedded HTTP server instance that handles all API requests */ + private HttpServer server; + + /** Configuration category name for tool options */ + private static final String OPTION_CATEGORY_NAME = "GhidraMCP HTTP Server"; + + /** Configuration option name for the server address setting */ + private static final String ADDRESS_OPTION_NAME = "Server Address"; + + /** Default address for the HTTP server */ + private static final String DEFAULT_ADDRESS = "127.0.0.1"; + + /** Configuration option name for the server port setting */ + private static final String PORT_OPTION_NAME = "Server Port"; + + /** Configuration option name for the decompile timeout setting */ + private static final String DECOMPILE_TIMEOUT_OPTION_NAME = "Decompile Timeout"; + + /** Default port number for the HTTP server (8080) */ + private static final int DEFAULT_PORT = 8080; + + /** Default decompile timeout in seconds */ + private static final int DEFAULT_DECOMPILE_TIMEOUT = 30; + + /** HashMap to store all registered API routes */ + private static final HashMap routes = new HashMap<>(); + + /** The timeout for decompilation requests in seconds */ + private int decompileTimeout; + + /** + * Constructs a new GhidraMCP plugin instance and initializes the HTTP server. + * + * This constructor: + *
    + *
  1. Registers the port configuration option in Ghidra's tool options
  2. + *
  3. Starts the embedded HTTP server on the configured port
  4. + *
  5. Creates all REST API endpoint handlers
  6. + *
+ * + * The server will only function properly when: + *
    + *
  • A program is loaded in the current CodeBrowser session
  • + *
  • The plugin is enabled in the Developer tools configuration
  • + *
+ * + * @param tool The Ghidra PluginTool instance that hosts this plugin + * @throws IllegalStateException if the HTTP server fails to start + * @see #startServer() + */ + public GhidraMCPPlugin(PluginTool tool) { + super(tool); + Msg.info(this, "GhidraMCPPlugin loading..."); + + // Register the configuration option + Options options = tool.getOptions(OPTION_CATEGORY_NAME); + options.registerOption(ADDRESS_OPTION_NAME, DEFAULT_ADDRESS, + null, // No help location for now + "The network address the embedded HTTP server will listen on. " + + "Requires Ghidra restart or plugin reload to take effect after changing."); + 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."); + options.registerOption(DECOMPILE_TIMEOUT_OPTION_NAME, DEFAULT_DECOMPILE_TIMEOUT, + null, + "Decompilation timeout. " + + "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!"); + } + + /** + * Initializes and starts the embedded HTTP server with all API endpoints. + * + * This method creates an HTTP server instance and registers handlers for all + * supported REST API endpoints. The server supports: + * + *

Function Analysis Endpoints:

+ *
    + *
  • GET /methods - List functions with pagination
  • + *
  • POST /decompile - Decompile function by name
  • + *
  • GET /decompile_function?address=0x... - Decompile by + * address
  • + *
  • GET /disassemble_function?address=0x... - Get assembly + * listing
  • + *
+ * + *

Symbol Management Endpoints:

+ *
    + *
  • POST /renameFunction - Rename functions
  • + *
  • POST /renameVariable - Rename local variables
  • + *
  • POST /set_function_prototype - Set function signatures
  • + *
+ * + *

Analysis and Reference Endpoints:

+ *
    + *
  • GET /xrefs_to?address=0x... - Find references to + * address
  • + *
  • GET /xrefs_from?address=0x... - Find references from + * address
  • + *
  • GET /strings - List string data with filtering
  • + *
+ * + *

Commenting and Annotation:

+ *
    + *
  • POST /set_decompiler_comment - Add pseudocode comments
  • + *
  • POST /set_disassembly_comment - Add assembly comments
  • + *
+ * + * The server runs on a separate thread to avoid blocking Ghidra's UI thread. + * All endpoints return plain text responses with UTF-8 encoding. + * + * @throws IOException if the server cannot bind to the configured port + * @see #sendResponse(HttpExchange, String) + * @see #parseQueryParams(HttpExchange) + */ + private void startServer() throws IOException { + // Read the configured port + Options options = tool.getOptions(OPTION_CATEGORY_NAME); + String listenAddress = options.getString(ADDRESS_OPTION_NAME, DEFAULT_ADDRESS); + 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; + } + + InetSocketAddress inetAddress = new InetSocketAddress(listenAddress, port); + + if (inetAddress.isUnresolved()) { + Msg.error(this, "Failed to resolve listen address."); + return; + } + + server = HttpServer.create(inetAddress, 0); + + Reflections reflections = new Reflections("com.lauriewired.handlers"); + Set> subclasses = reflections.getSubTypesOf(Handler.class); + for (Class clazz : subclasses) { + System.out.println(clazz.getName()); + try { + Constructor constructor = clazz.getConstructor(PluginTool.class); + Handler handler = (Handler) constructor.newInstance(tool); + if (routes.containsKey(handler.getPath())) { + Msg.error(this, "Handler class " + clazz.getName() + " already registered, skipped."); + continue; + } + routes.put(handler.getPath(), handler); + server.createContext(handler.getPath(), exchange -> { + try { + handler.handle(exchange); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } catch (NoSuchMethodException e) { + Msg.error(this, "Handler class " + clazz.getName() + + " doesn't have constructor xxx(PluginTool tool), skipped."); + } catch (Exception e) { + e.printStackTrace(); + } + } + + 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(); + } + + /** + * Cleanly shuts down the HTTP server and releases plugin resources. + * + * This method is automatically called by Ghidra when: + *
    + *
  • The plugin is disabled in the CodeBrowser configuration
  • + *
  • The CodeBrowser tool is closed
  • + *
  • Ghidra is shutting down
  • + *
  • The plugin is being reloaded
  • + *
+ * + * Shutdown Process: + *
    + *
  1. Stops the HTTP server with a 1-second grace period for active + * connections
  2. + *
  3. Nullifies the server reference to prevent further use
  4. + *
  5. Calls the parent dispose method to clean up plugin infrastructure
  6. + *
+ * + * Thread Safety: This method can be called from any thread and safely + * handles concurrent access to the server instance. + * + * @see HttpServer#stop(int) + * @see Plugin#dispose() + */ + @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(); + } } diff --git a/src/main/java/com/lauriewired/handlers/Handler.java b/src/main/java/com/lauriewired/handlers/Handler.java index 0a5e30d..1ed4b64 100644 --- a/src/main/java/com/lauriewired/handlers/Handler.java +++ b/src/main/java/com/lauriewired/handlers/Handler.java @@ -3,16 +3,46 @@ import com.sun.net.httpserver.HttpExchange; import ghidra.framework.plugintool.PluginTool; +/** + * Abstract class representing a handler for HTTP requests in a Ghidra + * PluginTool. + * Subclasses must implement the handle method to define how requests are + * processed. + */ public abstract class Handler { - protected final PluginTool tool; - protected final String path; + /** The PluginTool instance this handler is associated with. */ + protected final PluginTool tool; - protected Handler(PluginTool tool, String path) { - this.tool = tool; - this.path = path; - } + /** The path this handler will respond to. */ + protected final String path; - public String getPath() { return path; } + /** + * Constructs a new Handler with the specified PluginTool and path. + * + * @param tool the PluginTool instance this handler is associated with + * @param path the path this handler will respond to + */ + protected Handler(PluginTool tool, String path) { + this.tool = tool; + this.path = path; + } - public abstract void handle(HttpExchange exchange) throws Exception; + /** + * Gets the path this handler will respond to. + * + * @return the path + */ + public String getPath() { + return path; + } + + /** + * Handles an HTTP request. + * Subclasses must implement this method to define how requests are + * processed. + * + * @param exchange the HttpExchange object representing the HTTP request + * @throws Exception if an error occurs while handling the request + */ + public abstract void handle(HttpExchange exchange) throws Exception; } diff --git a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java index e682d4e..68d2b34 100644 --- a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java +++ b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java @@ -17,40 +17,63 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to decompile a function by its address in the current program. + * This handler responds to HTTP requests with the decompiled C code of the function + * at the specified address. + */ public final class DecompileFunctionByAddress extends Handler { - public DecompileFunctionByAddress(PluginTool tool) { - super(tool, "/decompile_function"); - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - String address = qparams.get("address"); - sendResponse(exchange, decompileFunctionByAddress(address)); - } - - /** - * Decompile a function at the given address - */ - private String decompileFunctionByAddress(String addressStr) { - Program program = getCurrentProgram(tool); - 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.getListing().getFunctionContaining(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(); - } - } + /** + * Constructor for the DecompileFunctionByAddress handler + * + * @param tool the PluginTool instance to interact with Ghidra + */ + public DecompileFunctionByAddress(PluginTool tool) { + super(tool, "/decompile_function"); + } + + /** + * Handles HTTP requests to decompile a function by its address. + * Expects a query parameter "address" with the function's address. + * + * @param exchange the HttpExchange object representing the HTTP request + * @throws IOException if an I/O error occurs during handling + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String address = qparams.get("address"); + sendResponse(exchange, decompileFunctionByAddress(address)); + } + + /** + * Decompiles the function at the specified address in the current program. + * + * @param addressStr the address of the function to decompile + * @return the decompiled C code or an error message + */ + private String decompileFunctionByAddress(String addressStr) { + Program program = getCurrentProgram(tool); + 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.getListing().getFunctionContaining(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(); + } + } } diff --git a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java index 3fbcc4d..5e2f884 100644 --- a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java +++ b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java @@ -15,32 +15,58 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to decompile a function by its name. + * Expects the function name in the request body. + */ public final class DecompileFunctionByName extends Handler { - public DecompileFunctionByName(PluginTool tool) { - super(tool, "/decompile"); - } + /** + * Constructs a new DecompileFunctionByName handler. + * + * @param tool The Ghidra plugin tool instance. + */ + public DecompileFunctionByName(PluginTool tool) { + super(tool, "/decompile"); + } - public void handle(HttpExchange exchange) throws IOException { - String name = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); - sendResponse(exchange, generateResponse(name)); - } + /** + * Handles the HTTP request to decompile a function by its name. + * Reads the function name from the request body and returns the decompiled C + * pseudocode. + * + * @param exchange The HTTP exchange containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + String name = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8); + sendResponse(exchange, generateResponse(name)); + } - private String generateResponse(String name) { - Program program = getCurrentProgram(tool); - 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"; - } + /** + * Generates the decompiled C pseudocode for the function with the specified + * name. + * + * @param name The name of the function to decompile. + * @return The decompiled C pseudocode or an error message if the function is + * not found. + */ + private String generateResponse(String name) { + Program program = getCurrentProgram(tool); + 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"; + } } diff --git a/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java b/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java index 572a13d..c60c8f1 100644 --- a/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java +++ b/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java @@ -1,4 +1,5 @@ package com.lauriewired.handlers.act; + import com.lauriewired.handlers.Handler; import com.sun.net.httpserver.HttpExchange; import ghidra.framework.plugintool.PluginTool; @@ -12,53 +13,78 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for disassembling a function at a given address in Ghidra + * + * This handler responds to HTTP requests to disassemble a function + * and returns the assembly code as a string. + */ public final class DisassembleFunction extends Handler { - public DisassembleFunction(PluginTool tool) { - super(tool, "/disassemble_function"); - } + /** + * Constructor for the DisassembleFunction handler + * + * @param tool the Ghidra plugin tool instance + */ + public DisassembleFunction(PluginTool tool) { + super(tool, "/disassemble_function"); + } - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - String address = qparams.get("address"); - sendResponse(exchange, disassembleFunction(address)); - } + /** + * Handles HTTP requests to disassemble a function at a specified address + * + * @param exchange the HTTP exchange containing the request + * @throws IOException if an I/O error occurs during handling + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String address = qparams.get("address"); + sendResponse(exchange, disassembleFunction(address)); + } - /** - * Get assembly code for a function - */ - private String disassembleFunction(String addressStr) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; - if (addressStr == null || addressStr.isEmpty()) return "Address is required"; + /** + * Disassembles the function at the specified address and returns the assembly + * code + * + * @param addressStr the address of the function to disassemble + * @return a string containing the disassembled function code + */ + private String disassembleFunction(String addressStr) { + Program program = getCurrentProgram(tool); + 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.getListing().getFunctionContaining(addr); - if (func == null) return "No function found at or containing address " + addressStr; + try { + Address addr = program.getAddressFactory().getAddress(addressStr); + Function func = program.getListing().getFunctionContaining(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(); + 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 : ""; + 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)); - } + 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(); - } - } + return result.toString(); + } catch (Exception e) { + return "Error disassembling function: " + e.getMessage(); + } + } } diff --git a/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java b/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java index 3a5bee3..0832d34 100644 --- a/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java +++ b/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java @@ -11,24 +11,43 @@ import static com.lauriewired.util.ParseUtils.parsePostParams; import static com.lauriewired.util.ParseUtils.sendResponse; +/** + * Handler for setting a decompiler comment in Ghidra + * This handler processes HTTP requests to set comments on decompiled code + */ public final class SetDecompilerComment extends Handler { - public SetDecompilerComment(PluginTool tool) { - super(tool, "/set_decompiler_comment"); - } + /** + * Constructor for the SetDecompilerComment handler + * + * @param tool The Ghidra PluginTool instance + */ + public SetDecompilerComment(PluginTool tool) { + super(tool, "/set_decompiler_comment"); + } - @Override - public void handle(HttpExchange exchange) throws Exception { - Map 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"); - } + /** + * Handles HTTP POST requests to set a decompiler comment + * + * @param exchange The HTTP exchange containing the request and response + * @throws Exception If an error occurs while processing the request + */ + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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"); + } - /** - * Set a comment for a given address in the function pseudocode - */ - private boolean setDecompilerComment(String addressStr, String comment) { - return setCommentAtAddress(tool, addressStr, comment, CodeUnit.PRE_COMMENT, "Set decompiler comment"); - } + /** + * Sets a decompiler comment at the specified address + * + * @param addressStr The address as a string where the comment should be set + * @param comment The comment to set + * @return true if the comment was set successfully, false otherwise + */ + private boolean setDecompilerComment(String addressStr, String comment) { + return setCommentAtAddress(tool, addressStr, comment, CodeUnit.PRE_COMMENT, "Set decompiler comment"); + } } diff --git a/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java b/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java index 966444a..f6c59f4 100644 --- a/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java +++ b/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java @@ -11,24 +11,44 @@ import static com.lauriewired.util.ParseUtils.parsePostParams; import static com.lauriewired.util.ParseUtils.sendResponse; +/** + * Handler for setting a comment in the disassembly at a specific address. + * Expects POST request with parameters: address and comment. + */ public final class SetDisassemblyComment extends Handler { - public SetDisassemblyComment(PluginTool tool) { - super(tool, "/set_disassembly_comment"); - } + /** + * Constructor for the SetDisassemblyComment handler. + * + * @param tool the Ghidra PluginTool instance + */ + public SetDisassemblyComment(PluginTool tool) { + super(tool, "/set_disassembly_comment"); + } - @Override - public void handle(HttpExchange exchange) throws Exception { - Map 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"); - } + /** + * Handles the HTTP request to set a disassembly comment. + * Expects a POST request with parameters: address and comment. + * + * @param exchange the HTTP exchange containing the request + * @throws Exception if an error occurs while handling the request + */ + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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"); + } - /** - * Set a comment for a given address in the function disassembly - */ - private boolean setDisassemblyComment(String addressStr, String comment) { - return setCommentAtAddress(tool, addressStr, comment, CodeUnit.EOL_COMMENT, "Set disassembly comment"); - } + /** + * Sets a disassembly comment at the specified address. + * + * @param addressStr the address as a string + * @param comment the comment to set + * @return true if the comment was set successfully, false otherwise + */ + private boolean setDisassemblyComment(String addressStr, String comment) { + return setCommentAtAddress(tool, addressStr, comment, CodeUnit.EOL_COMMENT, "Set disassembly comment"); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java b/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java index 18acec9..9367163 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java +++ b/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java @@ -13,33 +13,59 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to get all class names in the current program. + * Supports pagination via 'offset' and 'limit' query parameters. + */ public final class GetAllClassNames extends Handler { - public GetAllClassNames(PluginTool tool) { - super(tool, "/classes"); - } + /** + * Constructor for the GetAllClassNames handler. + * + * @param tool The PluginTool instance to use for accessing the current program. + */ + public GetAllClassNames(PluginTool tool) { + super(tool, "/classes"); + } - @Override - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, generateResponse(offset, limit)); - } + /** + * Parses the query parameters from the HTTP request and returns a response + * containing + * all class names in the current program, with optional pagination. + * + * @param exchange The HttpExchange object representing the HTTP request. + * @throws IOException If an I/O error occurs while handling the request. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, generateResponse(offset, limit)); + } - private String generateResponse(int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; + /** + * Generates a response containing all class names in the current program, + * with optional pagination. + * + * @param offset The starting index for pagination. + * @param limit The maximum number of class names to return. + * @return A string containing the paginated list of class names. + */ + private String generateResponse(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; - Set 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 sorted = new ArrayList<>(classNames); - Collections.sort(sorted); - return paginateList(sorted, offset, limit); - } + Set 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 sorted = new ArrayList<>(classNames); + Collections.sort(sorted); + return paginateList(sorted, offset, limit); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java b/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java index cfbbd8a..3a6ba82 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java +++ b/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java @@ -15,27 +15,52 @@ import static com.lauriewired.util.ParseUtils.parseIntOrDefault; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to get all function names in the current program. + * + * Example usage: GET /methods?offset=0&limit=100 + */ public final class GetAllFunctionNames extends Handler { - public GetAllFunctionNames(PluginTool tool) { - super(tool, "/methods"); - } + /** + * Constructor for the GetAllFunctionNames handler. + * + * @param tool the PluginTool instance + */ + public GetAllFunctionNames(PluginTool tool) { + super(tool, "/methods"); + } - @Override - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, generateResponse(offset, limit)); - } + /** + * Handles the HTTP request to get all function names. + * + * @param exchange the HttpExchange instance containing the request + * @throws IOException if an I/O error occurs + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, generateResponse(offset, limit)); + } - private String generateResponse(int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; + /** + * Generates a paginated response containing all function names in the current + * program. + * + * @param offset the starting index for pagination + * @param limit the maximum number of function names to return + * @return a string containing the paginated list of function names + */ + private String generateResponse(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; - List names = new ArrayList<>(); - for (Function f : program.getFunctionManager().getFunctions(true)) { - names.add(f.getName()); - } - return paginateList(names, offset, limit); - } + List names = new ArrayList<>(); + for (Function f : program.getFunctionManager().getFunctions(true)) { + names.add(f.getName()); + } + return paginateList(names, offset, limit); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java b/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java index aef4f06..9370a52 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java +++ b/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java @@ -10,23 +10,41 @@ import static com.lauriewired.util.ParseUtils.sendResponse; +/** + * Handler to get the current address from the CodeViewerService + */ public final class GetCurrentAddress extends Handler { - public GetCurrentAddress(PluginTool tool) { - super(tool, "/get_current_address"); - } + /** + * Constructor for GetCurrentAddress handler + * + * @param tool PluginTool instance to access Ghidra services + */ + public GetCurrentAddress(PluginTool tool) { + super(tool, "/get_current_address"); + } - public void handle(HttpExchange exchange) throws IOException { - sendResponse(exchange, getCurrentAddress()); - } + /** + * Handle HTTP request to get current address + * + * @param exchange HttpExchange instance containing request and response + * @throws IOException if an I/O error occurs + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + sendResponse(exchange, getCurrentAddress()); + } - /** - * 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"; + /** + * Retrieves the current address from the CodeViewerService + * + * @return String representation of the current address or an error message + */ + 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"; - } + ProgramLocation location = service.getCurrentLocation(); + return (location != null) ? location.getAddress().toString() : "No current location"; + } } diff --git a/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java b/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java index f9fb5b5..2fa62fc 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java +++ b/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java @@ -13,35 +13,57 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to get the current function in Ghidra GUI. + * Responds with the function name, entry point, and signature. + */ public final class GetCurrentFunction extends Handler { - public GetCurrentFunction(PluginTool tool) { - super(tool, "/get_current_function"); - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - sendResponse(exchange, getCurrentFunction()); - } - - /** - * 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(tool); - 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()); - } + /** + * Constructor for the GetCurrentFunction handler. + * + * @param tool The Ghidra PluginTool instance. + */ + public GetCurrentFunction(PluginTool tool) { + super(tool, "/get_current_function"); + } + + /** + * Handles the HTTP request to get the current function. + * + * @param exchange The HTTP exchange containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + sendResponse(exchange, getCurrentFunction()); + } + + /** + * Retrieves the current function at the current location in the Ghidra GUI. + * + * @return A string containing the function name, entry point, and signature, + * or an error message if no function is found or if there are issues. + */ + 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(tool); + 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()); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java b/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java index ab52523..40a5e30 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java +++ b/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java @@ -14,41 +14,56 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to get function details by address + */ public final class GetFunctionByAddress extends Handler { - public GetFunctionByAddress(PluginTool tool) { - super(tool, "/get_function_by_address"); - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - String address = qparams.get("address"); - sendResponse(exchange, getFunctionByAddress(address)); - } - - /** - * Get function by address - */ - private String getFunctionByAddress(String addressStr) { - Program program = getCurrentProgram(tool); - 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(); - } - } + public GetFunctionByAddress(PluginTool tool) { + super(tool, "/get_function_by_address"); + } + + /** + * Handle HTTP GET request to retrieve function details by address + * + * @param exchange the HTTP exchange + * @throws IOException if an I/O error occurs + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String address = qparams.get("address"); + sendResponse(exchange, getFunctionByAddress(address)); + } + + /** + * Retrieves function details by address + * + * @param addressStr the address as a string + * @return a string containing function details or an error message + */ + private String getFunctionByAddress(String addressStr) { + Program program = getCurrentProgram(tool); + 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(); + } + } } diff --git a/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java b/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java index be7a510..4856d26 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java +++ b/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java @@ -18,56 +18,74 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to get all references to a specific function by name. + * Expects query parameters: name, offset, limit + */ public final class GetFunctionXrefs extends Handler { - public GetFunctionXrefs(PluginTool tool) { - super(tool, "/function_xrefs"); - } + public GetFunctionXrefs(PluginTool tool) { + super(tool, "/function_xrefs"); + } - @Override - public void handle(HttpExchange exchange) throws Exception { - Map 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)); - } + /** + * Handles the HTTP request to get function cross-references. + * Expects query parameters: name, offset, limit + * + * @param exchange the HTTP exchange containing the request + * @throws Exception if an error occurs while processing the request + */ + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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)); + } - /** - * Get all references to a specific function by name - */ - private String getFunctionXrefs(String functionName, int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; - if (functionName == null || functionName.isEmpty()) return "Function name is required"; + /** + * Retrieves cross-references to a function by its name. + * + * @param functionName the name of the function to find references for + * @param offset the starting index for pagination + * @param limit the maximum number of results to return + * @return a string containing the references or an error message + */ + private String getFunctionXrefs(String functionName, int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + if (functionName == null || functionName.isEmpty()) + return "Function name is required"; - try { - List 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); + try { + List 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(); + 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() : ""; + Function fromFunc = funcManager.getFunctionContaining(fromAddr); + String funcInfo = (fromFunc != null) ? " in " + fromFunc.getName() : ""; - refs.add(String.format("From %s%s [%s]", fromAddr, funcInfo, refType.getName())); - } - } - } + refs.add(String.format("From %s%s [%s]", fromAddr, funcInfo, refType.getName())); + } + } + } - if (refs.isEmpty()) { - return "No references found to function: " + functionName; - } + 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(); - } - } + return paginateList(refs, offset, limit); + } catch (Exception e) { + return "Error getting function references: " + e.getMessage(); + } + } } diff --git a/src/main/java/com/lauriewired/handlers/get/GetXrefsFrom.java b/src/main/java/com/lauriewired/handlers/get/GetXrefsFrom.java index 87d0e08..54c7735 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetXrefsFrom.java +++ b/src/main/java/com/lauriewired/handlers/get/GetXrefsFrom.java @@ -18,56 +18,75 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** Handler for getting cross-references from a specific address */ public final class GetXrefsFrom extends Handler { - public GetXrefsFrom(PluginTool tool) { - super(tool, "/xrefs_from"); - } + /** + * Constructor for the GetXrefsFrom handler. + * + * @param tool The PluginTool instance to use for accessing the current program. + */ + public GetXrefsFrom(PluginTool tool) { + super(tool, "/xrefs_from"); + } - @Override - public void handle(HttpExchange exchange) throws Exception { - Map 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)); - } + /** + * Handles the HTTP request to get cross-references from a specific address. + * + * @param exchange The HttpExchange object containing the request and response. + * @throws Exception If an error occurs while processing the request. + */ + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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)); + } - /** - * Get all references from a specific address (xref from) - */ - private String getXrefsFrom(String addressStr, int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; - if (addressStr == null || addressStr.isEmpty()) return "Address is required"; + /** + * Get references from a specific address in the current program. + * + * @param addressStr The address to get references from. + * @param offset The offset for pagination. + * @param limit The maximum number of references to return. + * @return A string containing the references or an error message. + */ + private String getXrefsFrom(String addressStr, int offset, int limit) { + Program program = getCurrentProgram(tool); + 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(); + try { + Address addr = program.getAddressFactory().getAddress(addressStr); + ReferenceManager refManager = program.getReferenceManager(); - Reference[] references = refManager.getReferencesFrom(addr); + Reference[] references = refManager.getReferencesFrom(addr); - List refs = new ArrayList<>(); - for (Reference ref : references) { - Address toAddr = ref.getToAddress(); - RefType refType = ref.getReferenceType(); + List 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()); - } - } + 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())); - } + 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(); - } - } + return paginateList(refs, offset, limit); + } catch (Exception e) { + return "Error getting references from address: " + e.getMessage(); + } + } } diff --git a/src/main/java/com/lauriewired/handlers/get/GetXrefsTo.java b/src/main/java/com/lauriewired/handlers/get/GetXrefsTo.java index bd89ac3..a604592 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetXrefsTo.java +++ b/src/main/java/com/lauriewired/handlers/get/GetXrefsTo.java @@ -18,49 +18,71 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to get all references to a specific address in the current program. + * Example usage: /xrefs_to?address=0x00401000&offset=0&limit=100 + */ public final class GetXrefsTo extends Handler { - public GetXrefsTo(PluginTool tool) { - super(tool, "/xrefs_to"); - } + /** + * Constructor for the GetXrefsTo handler. + * + * @param tool the Ghidra plugin tool + */ + public GetXrefsTo(PluginTool tool) { + super(tool, "/xrefs_to"); + } - @Override - public void handle(HttpExchange exchange) throws Exception { - Map 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)); - } + /** + * Handles the HTTP request to get cross-references to a specific address. + * + * @param exchange the HTTP exchange containing the request + * @throws Exception if an error occurs while processing the request + */ + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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)); + } - /** - * Get all references to a specific address (xref to) - */ - private String getXrefsTo(String addressStr, int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; - if (addressStr == null || addressStr.isEmpty()) return "Address is required"; + /** + * Retrieves cross-references to a specific address in the current program. + * + * @param addressStr the address to get references to + * @param offset the offset for pagination + * @param limit the maximum number of results to return + * @return a string representation of the references found + */ + private String getXrefsTo(String addressStr, int offset, int limit) { + Program program = getCurrentProgram(tool); + 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(); + try { + Address addr = program.getAddressFactory().getAddress(addressStr); + ReferenceManager refManager = program.getReferenceManager(); - ReferenceIterator refIter = refManager.getReferencesTo(addr); + ReferenceIterator refIter = refManager.getReferencesTo(addr); - List refs = new ArrayList<>(); - while (refIter.hasNext()) { - Reference ref = refIter.next(); - Address fromAddr = ref.getFromAddress(); - RefType refType = ref.getReferenceType(); + List 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() : ""; + 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())); - } + 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(); - } - } + return paginateList(refs, offset, limit); + } catch (Exception e) { + return "Error getting references to address: " + e.getMessage(); + } + } } diff --git a/src/main/java/com/lauriewired/handlers/get/ListDefinedData.java b/src/main/java/com/lauriewired/handlers/get/ListDefinedData.java index 644e22d..153c2c1 100644 --- a/src/main/java/com/lauriewired/handlers/get/ListDefinedData.java +++ b/src/main/java/com/lauriewired/handlers/get/ListDefinedData.java @@ -16,39 +16,62 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for listing defined data in the current program. + * + * Example usage: GET /data?offset=0&limit=100 + */ public final class ListDefinedData extends Handler { - public ListDefinedData(PluginTool tool) { - super(tool, "/data"); - } + /** + * Constructs a new ListDefinedData handler. + * + * @param tool The PluginTool instance to use for accessing the current program. + */ + public ListDefinedData(PluginTool tool) { + super(tool, "/data"); + } - @Override - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listDefinedData(offset, limit)); - } + /** + * Handles the HTTP request to list defined data. + * + * @param exchange The HTTP exchange containing the request. + * @throws IOException If an I/O error occurs. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listDefinedData(offset, limit)); + } - private String listDefinedData(int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; + /** + * Lists defined data in the current program, paginated by offset and limit. + * + * @param offset The starting index for pagination. + * @param limit The maximum number of items to return. + * @return A string representation of the defined data, formatted for display. + */ + private String listDefinedData(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; - List 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); - } + List 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); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/ListDefinedStrings.java b/src/main/java/com/lauriewired/handlers/get/ListDefinedStrings.java index 7a75d7f..b279171 100644 --- a/src/main/java/com/lauriewired/handlers/get/ListDefinedStrings.java +++ b/src/main/java/com/lauriewired/handlers/get/ListDefinedStrings.java @@ -15,78 +15,107 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to list all defined strings in the current program + * Supports pagination and filtering by string content + */ public final class ListDefinedStrings extends Handler { - public ListDefinedStrings(PluginTool tool) { - super(tool, "/strings"); - } + /** + * Constructor for ListDefinedStrings handler + * + * @param tool the PluginTool instance to use for accessing the current program + */ + public ListDefinedStrings(PluginTool tool) { + super(tool, "/strings"); + } - @Override - public void handle(HttpExchange exchange) throws Exception { - Map 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)); - } + /** + * Handle HTTP GET requests to list defined strings + * + * @param exchange the HTTP exchange containing the request + * @throws Exception if an error occurs while processing the request + */ + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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)); + } - /** - * List all defined strings in the program with their addresses - */ - private String listDefinedStrings(int offset, int limit, String filter) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; + /** + * List all defined strings in the program with their addresses + * + * @param offset the starting index for pagination + * @param limit the maximum number of results to return + * @param filter optional filter to apply to string values + * @return a formatted string containing the list of defined strings + */ + private String listDefinedStrings(int offset, int limit, String filter) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; - List lines = new ArrayList<>(); - DataIterator dataIt = program.getListing().getDefinedData(true); + List lines = new ArrayList<>(); + DataIterator dataIt = program.getListing().getDefinedData(true); - while (dataIt.hasNext()) { - Data data = dataIt.next(); + while (dataIt.hasNext()) { + Data data = dataIt.next(); - if (data != null && isStringData(data)) { - String value = data.getValue() != null ? data.getValue().toString() : ""; + 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)); - } - } - } + 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); - } + return paginateList(lines, offset, limit); + } - /** - * Check if the given data is a string type - */ - private boolean isStringData(Data data) { - if (data == null) return false; + /** + * Check if the given data is a string type + * + * @param data the Data object to check + * @return true if the data is a string type, false otherwise + */ + 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"); - } + 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 ""; + /** + * Escape special characters in a string for safe display + * + * @param input the string to escape + * @return the escaped string + */ + 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(); - } + 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(); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/ListExports.java b/src/main/java/com/lauriewired/handlers/get/ListExports.java index aee63b6..1271c30 100644 --- a/src/main/java/com/lauriewired/handlers/get/ListExports.java +++ b/src/main/java/com/lauriewired/handlers/get/ListExports.java @@ -16,34 +16,60 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for listing all exports in the current program. + * Exports are symbols that are external entry points, typically functions. + * + * Example usage: + * GET /imports?offset=0&limit=100 + */ public final class ListExports extends Handler { - public ListExports(PluginTool tool) { - super(tool, "/imports"); - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listExports(offset, limit)); - } - - private String listExports(int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; - - SymbolTable table = program.getSymbolTable(); - SymbolIterator it = table.getAllSymbols(true); - - List 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); - } + /** + * Constructor for ListExports handler. + * + * @param tool the PluginTool instance to interact with Ghidra + */ + public ListExports(PluginTool tool) { + super(tool, "/imports"); + } + + /** + * Handles the HTTP request to list exports. + * + * @param exchange the HttpExchange instance containing the request + * @throws IOException if an I/O error occurs + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listExports(offset, limit)); + } + + /** + * Lists all exports in the current program, paginated by offset and limit. + * + * @param offset the starting index for pagination + * @param limit the maximum number of exports to return + * @return a string representation of the exports, formatted for pagination + */ + private String listExports(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + SymbolTable table = program.getSymbolTable(); + SymbolIterator it = table.getAllSymbols(true); + + List 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); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/ListFunctions.java b/src/main/java/com/lauriewired/handlers/get/ListFunctions.java index 7e54c9d..ffb36df 100644 --- a/src/main/java/com/lauriewired/handlers/get/ListFunctions.java +++ b/src/main/java/com/lauriewired/handlers/get/ListFunctions.java @@ -11,30 +11,48 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to list all functions in the current program. + * Responds with a list of function names and their entry points. + */ public final class ListFunctions extends Handler { - public ListFunctions(PluginTool tool) { - super(tool, "/list_functions"); - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - sendResponse(exchange, listFunctions()); - } - - /** - * List all functions in the database - */ - private String listFunctions() { - Program program = getCurrentProgram(tool); - 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(); - } + /** + * Constructor for ListFunctions handler. + * + * @param tool the PluginTool instance + */ + public ListFunctions(PluginTool tool) { + super(tool, "/list_functions"); + } + + /** + * Handles the HTTP request to list functions. + * + * @param exchange the HttpExchange instance + * @throws IOException if an I/O error occurs + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + sendResponse(exchange, listFunctions()); + } + + /** + * Lists all functions in the current program. + * + * @return a string containing the names and entry points of all functions + */ + private String listFunctions() { + Program program = getCurrentProgram(tool); + 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(); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/ListImports.java b/src/main/java/com/lauriewired/handlers/get/ListImports.java index b36e4d4..5e1e4f3 100644 --- a/src/main/java/com/lauriewired/handlers/get/ListImports.java +++ b/src/main/java/com/lauriewired/handlers/get/ListImports.java @@ -14,27 +14,53 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for listing all external symbols (imports) in the current program. + * Responds with a paginated list of imports in the format: + * "symbolName -> symbolAddress". + */ public final class ListImports extends Handler { - public ListImports(PluginTool tool) { - super(tool, "/imports"); - } + /** + * Constructor for ListImports handler. + * + * @param tool the PluginTool instance to use for accessing the current program. + */ + public ListImports(PluginTool tool) { + super(tool, "/imports"); + } - @Override - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listImports(offset, limit)); - } + /** + * Handles the HTTP request to list imports. + * Expects query parameters 'offset' and 'limit' for pagination. + * + * @param exchange the HttpExchange instance containing the request and + * response. + * @throws IOException if an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listImports(offset, limit)); + } - private String listImports(int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; + /** + * Lists all external symbols (imports) in the current program, paginated. + * + * @param offset the starting index for pagination. + * @param limit the maximum number of results to return. + * @return a string containing the paginated list of imports. + */ + private String listImports(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; - List lines = new ArrayList<>(); - for (Symbol symbol : program.getSymbolTable().getExternalSymbols()) { - lines.add(symbol.getName() + " -> " + symbol.getAddress()); - } - return paginateList(lines, offset, limit); - } + List lines = new ArrayList<>(); + for (Symbol symbol : program.getSymbolTable().getExternalSymbols()) { + lines.add(symbol.getName() + " -> " + symbol.getAddress()); + } + return paginateList(lines, offset, limit); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/ListNamespaces.java b/src/main/java/com/lauriewired/handlers/get/ListNamespaces.java index c76c195..3a85b98 100644 --- a/src/main/java/com/lauriewired/handlers/get/ListNamespaces.java +++ b/src/main/java/com/lauriewired/handlers/get/ListNamespaces.java @@ -14,32 +14,57 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for listing namespaces in the current program. + * + * Example usage: + * GET /namespaces?offset=0&limit=100 + */ public final class ListNamespaces extends Handler { - public ListNamespaces(PluginTool tool) { - super(tool, "/namespaces"); - } + /** + * Constructor for the ListNamespaces handler. + * + * @param tool the PluginTool instance + */ + public ListNamespaces(PluginTool tool) { + super(tool, "/namespaces"); + } - @Override - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listNamespaces(offset, limit)); - } + /** + * Handles the HTTP request to list namespaces. + * + * @param exchange the HttpExchange instance containing the request + * @throws IOException if an I/O error occurs + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listNamespaces(offset, limit)); + } - private String listNamespaces(int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; + /** + * Lists namespaces in the current program, paginated by offset and limit. + * + * @param offset the starting index for pagination + * @param limit the maximum number of namespaces to return + * @return a string representation of the paginated list of namespaces + */ + private String listNamespaces(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; - Set 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 sorted = new ArrayList<>(namespaces); - Collections.sort(sorted); - return paginateList(sorted, offset, limit); - } + Set 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 sorted = new ArrayList<>(namespaces); + Collections.sort(sorted); + return paginateList(sorted, offset, limit); + } } diff --git a/src/main/java/com/lauriewired/handlers/get/ListSegments.java b/src/main/java/com/lauriewired/handlers/get/ListSegments.java index 9af085d..7da65ee 100644 --- a/src/main/java/com/lauriewired/handlers/get/ListSegments.java +++ b/src/main/java/com/lauriewired/handlers/get/ListSegments.java @@ -14,27 +14,53 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for listing memory segments in the current program. + * Responds with a list of memory blocks, paginated by offset and limit. + */ public final class ListSegments extends Handler { - public ListSegments(PluginTool tool) { - super(tool, "/segments"); - } + /** + * Constructor for ListSegments handler. + * + * @param tool the PluginTool instance to use for accessing the current program. + */ + public ListSegments(PluginTool tool) { + super(tool, "/segments"); + } - @Override - public void handle(HttpExchange exchange) throws IOException { - Map qparams = parseQueryParams(exchange); - int offset = parseIntOrDefault(qparams.get("offset"), 0); - int limit = parseIntOrDefault(qparams.get("limit"), 100); - sendResponse(exchange, listSegments(offset, limit)); - } + /** + * Handles the HTTP request to list memory segments. + * Expects query parameters 'offset' and 'limit' for pagination. + * + * @param exchange the HttpExchange instance containing the request and + * response. + * @throws IOException if an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, listSegments(offset, limit)); + } - private String listSegments(int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; + /** + * Lists memory segments in the current program, paginated by offset and limit. + * + * @param offset the starting index for pagination. + * @param limit the maximum number of segments to return. + * @return a string representation of the memory segments, formatted for + * pagination. + */ + private String listSegments(int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; - List 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); - } + List 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); + } } diff --git a/src/main/java/com/lauriewired/handlers/set/RenameData.java b/src/main/java/com/lauriewired/handlers/set/RenameData.java index 4ae65e0..4189791 100644 --- a/src/main/java/com/lauriewired/handlers/set/RenameData.java +++ b/src/main/java/com/lauriewired/handlers/set/RenameData.java @@ -21,49 +21,71 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for renaming data at a specific address in the current program. + * Expects POST parameters: "address" (the address of the data) and "newName" + * (the new name). + */ public final class RenameData extends Handler { - public RenameData(PluginTool tool) { - super(tool, "/renameData"); - } + /** + * Constructs a new RenameData handler. + * + * @param tool the PluginTool instance to use for program access + */ + public RenameData(PluginTool tool) { + super(tool, "/renameData"); + } - @Override - public void handle(HttpExchange exchange) throws IOException { - Map params = parsePostParams(exchange); - renameDataAtAddress(params.get("address"), params.get("newName")); - sendResponse(exchange, "Rename data attempted"); - } + /** + * Handles the HTTP request to rename data at a specified address. + * Expects POST parameters "address" and "newName". + * + * @param exchange the HttpExchange object containing the request + * @throws IOException if an I/O error occurs + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + renameDataAtAddress(params.get("address"), params.get("newName")); + sendResponse(exchange, "Rename data attempted"); + } - private void renameDataAtAddress(String addressStr, String newName) { - Program program = getCurrentProgram(tool); - if (program == null) return; + /** + * Renames the data at the specified address in the current program. + * If the data exists, it updates its name; otherwise, it creates a new label. + * + * @param addressStr the address of the data as a string + * @param newName the new name for the data + */ + private void renameDataAtAddress(String addressStr, String newName) { + Program program = getCurrentProgram(tool); + 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); - } - } + 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); + } + } } diff --git a/src/main/java/com/lauriewired/handlers/set/RenameFunction.java b/src/main/java/com/lauriewired/handlers/set/RenameFunction.java index e2d7d4a..08025e3 100644 --- a/src/main/java/com/lauriewired/handlers/set/RenameFunction.java +++ b/src/main/java/com/lauriewired/handlers/set/RenameFunction.java @@ -18,46 +18,69 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for renaming a function in the current program. + * Expects POST parameters: oldName and newName. + */ public final class RenameFunction extends Handler { - public RenameFunction(PluginTool tool) { - super(tool, "/renameFunction"); - } + /** + * Constructor for RenameFunction handler. + * + * @param tool the PluginTool instance to interact with Ghidra + */ + public RenameFunction(PluginTool tool) { + super(tool, "/renameFunction"); + } - public void handle(HttpExchange exchange) throws IOException { - Map params = parsePostParams(exchange); - String response = rename(params.get("oldName"), params.get("newName")) - ? "Renamed successfully" : "Rename failed"; - sendResponse(exchange, response); - } + /** + * Handles the HTTP request to rename a function. + * Expects parameters "oldName" and "newName" in the POST request. + * + * @param exchange the HttpExchange object containing the request + * @throws IOException if an I/O error occurs + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String response = rename(params.get("oldName"), params.get("newName")) + ? "Renamed successfully" + : "Rename failed"; + sendResponse(exchange, response); + } - private boolean rename(String oldName, String newName) { - Program program = getCurrentProgram(tool); - if (program == null) return false; + /** + * Renames a function in the current program. + * + * @param oldName the current name of the function + * @param newName the new name to set for the function + * @return true if the rename was successful, false otherwise + */ + private boolean rename(String oldName, String newName) { + Program program = getCurrentProgram(tool); + 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 { - program.endTransaction(tx, successFlag.get()); - } - }); - } - catch (InterruptedException | InvocationTargetException e) { - Msg.error(this, "Failed to execute rename on Swing thread", e); - } - return successFlag.get(); - } + 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 { + program.endTransaction(tx, successFlag.get()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + Msg.error(this, "Failed to execute rename on Swing thread", e); + } + return successFlag.get(); + } } diff --git a/src/main/java/com/lauriewired/handlers/set/RenameFunctionByAddress.java b/src/main/java/com/lauriewired/handlers/set/RenameFunctionByAddress.java index f13345c..bcbaac2 100644 --- a/src/main/java/com/lauriewired/handlers/set/RenameFunctionByAddress.java +++ b/src/main/java/com/lauriewired/handlers/set/RenameFunctionByAddress.java @@ -18,64 +18,88 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to rename a function by its address + */ public final class RenameFunctionByAddress extends Handler { - public RenameFunctionByAddress(PluginTool tool) { - super(tool, "/rename_function_by_address"); - } + /** + * Constructor for the RenameFunctionByAddress handler + * + * @param tool the PluginTool instance + */ + public RenameFunctionByAddress(PluginTool tool) { + super(tool, "/rename_function_by_address"); + } - @Override - public void handle(HttpExchange exchange) throws Exception { - Map 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"); - } + /** + * Handle the HTTP request to rename a function by its address + * + * @param exchange the HttpExchange instance containing the request + * @throws Exception if an error occurs during processing + */ + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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"); + } - /** - * Rename a function by its address - */ - private boolean renameFunctionByAddress(String functionAddrStr, String newName) { - Program program = getCurrentProgram(tool); - if (program == null) return false; - if (functionAddrStr == null || functionAddrStr.isEmpty() || - newName == null || newName.isEmpty()) { - return false; - } + /** + * Renames a function by its address + * + * @param functionAddrStr the address of the function as a string + * @param newName the new name for the function + * @return true if the rename was successful, false otherwise + */ + private boolean renameFunctionByAddress(String functionAddrStr, String newName) { + Program program = getCurrentProgram(tool); + if (program == null) + return false; + if (functionAddrStr == null || functionAddrStr.isEmpty() || + newName == null || newName.isEmpty()) { + return false; + } - AtomicBoolean success = new AtomicBoolean(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); - } + 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(); - } + 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 = program.getListing().getFunctionContaining(addr); + /** + * Performs the function renaming operation on the Swing thread + * + * @param program the current program + * @param functionAddrStr the address of the function as a string + * @param newName the new name for the function + * @param success an AtomicBoolean to indicate success or failure + */ + 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 = program.getListing().getFunctionContaining(addr); - if (func == null) { - Msg.error(this, "Could not find function at address: " + functionAddrStr); - return; - } + 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()); - } - } + 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()); + } + } } diff --git a/src/main/java/com/lauriewired/handlers/set/RenameVariable.java b/src/main/java/com/lauriewired/handlers/set/RenameVariable.java index 05fa4c4..6e28ed0 100644 --- a/src/main/java/com/lauriewired/handlers/set/RenameVariable.java +++ b/src/main/java/com/lauriewired/handlers/set/RenameVariable.java @@ -28,143 +28,164 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for renaming a variable in a function. + * Expects POST parameters: functionName, oldName, newName. + * Returns a message indicating success or failure. + */ public final class RenameVariable extends Handler { - public RenameVariable(PluginTool tool) { - super(tool, "/renameVariable"); - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - Map 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); - } - - private String renameVariableInFunction(String functionName, String oldVarName, String newVarName) { - Program program = getCurrentProgram(tool); - 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 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, - HighFunctionDBUtil.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 { - 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; - } + /** + * Constructor for the RenameVariable handler. + * + * @param tool the PluginTool instance + */ + public RenameVariable(PluginTool tool) { + super(tool, "/renameVariable"); + } + + /** + * Handles the HTTP request to rename a variable in a function. + * + * @param exchange the HttpExchange object containing the request + * @throws IOException if an I/O error occurs + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map 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); + } + + /** + * Renames a variable in the specified function. + * + * @param functionName the name of the function containing the variable + * @param oldVarName the current name of the variable to rename + * @param newVarName the new name for the variable + * @return a message indicating success or failure + */ + private String renameVariableInFunction(String functionName, String oldVarName, String newVarName) { + Program program = getCurrentProgram(tool); + 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 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, + HighFunctionDBUtil.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 { + 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"; + } + + /** + * Checks if a full commit is required for the variable renaming operation. + * + * @param highSymbol the HighSymbol representing the variable + * @param hfunction the HighFunction containing the variable + * @return true if a full commit is required, false otherwise + */ + 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; + } } diff --git a/src/main/java/com/lauriewired/handlers/set/SearchFunctions.java b/src/main/java/com/lauriewired/handlers/set/SearchFunctions.java index 3011c1a..043d74c 100644 --- a/src/main/java/com/lauriewired/handlers/set/SearchFunctions.java +++ b/src/main/java/com/lauriewired/handlers/set/SearchFunctions.java @@ -15,39 +15,69 @@ import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for searching functions by name in the current program. + * Expects query parameters: query (search term), offset, limit. + */ public final class SearchFunctions extends Handler { - public SearchFunctions(PluginTool tool) { - super(tool, "/searchFunctions"); - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - Map 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)); - } - - private String searchFunctionsByName(String searchTerm, int offset, int limit) { - Program program = getCurrentProgram(tool); - if (program == null) return "No program loaded"; - if (searchTerm == null || searchTerm.isEmpty()) return "Search term is required"; - - List 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); - } + /** + * Constructor for SearchFunctions handler. + * + * @param tool the PluginTool instance to use for accessing the current program. + */ + public SearchFunctions(PluginTool tool) { + super(tool, "/searchFunctions"); + } + + /** + * Handles HTTP GET requests to search for functions by name. + * Expects query parameters: + * - query: the search term (required) + * - offset: pagination offset (default 0) + * - limit: maximum number of results to return (default 100) + * + * @param exchange the HttpExchange object containing the request and response. + * @throws IOException if an I/O error occurs. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map 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)); + } + + /** + * Searches for functions in the current program by name. + * Returns a paginated list of matching functions. + * + * @param searchTerm the term to search for in function names. + * @param offset the pagination offset. + * @param limit the maximum number of results to return. + * @return a string containing the results or an error message. + */ + private String searchFunctionsByName(String searchTerm, int offset, int limit) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + if (searchTerm == null || searchTerm.isEmpty()) + return "Search term is required"; + + List 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); + } } diff --git a/src/main/java/com/lauriewired/handlers/set/SetFunctionPrototype.java b/src/main/java/com/lauriewired/handlers/set/SetFunctionPrototype.java index cc041f9..3e4616f 100644 --- a/src/main/java/com/lauriewired/handlers/set/SetFunctionPrototype.java +++ b/src/main/java/com/lauriewired/handlers/set/SetFunctionPrototype.java @@ -21,182 +21,235 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler to set a function prototype in Ghidra + * This handler processes HTTP requests to set a function prototype based on the + * provided address and prototype string. + */ public final class SetFunctionPrototype extends Handler { - public SetFunctionPrototype(PluginTool tool) { - super(tool, "/set_function_prototype"); - } - - /** - * 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; - } - } - - @Override - public void handle(HttpExchange exchange) throws Exception { - Map 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()); - } - } - - /** - * Set a function's prototype with proper error handling using ApplyFunctionSignatureCmd - */ - private PrototypeResult setFunctionPrototype(String functionAddrStr, String prototype) { - // Input validation - Program program = getCurrentProgram(tool); - 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 = program.getListing().getFunctionContaining(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()); - } - } + /** + * Constructor for the SetFunctionPrototype handler + * + * @param tool The Ghidra plugin tool instance + */ + public SetFunctionPrototype(PluginTool tool) { + super(tool, "/set_function_prototype"); + } + + /** + * Result class to encapsulate success/failure and error messages + */ + private static class PrototypeResult { + /** Indicates if the prototype was set successfully */ + private final boolean success; + + /** Detailed error message if the operation failed */ + private final String errorMessage; + + /** + * Constructor for PrototypeResult + * + * @param success Indicates if the operation was successful + * @param errorMessage Detailed error message if applicable + */ + public PrototypeResult(boolean success, String errorMessage) { + this.success = success; + this.errorMessage = errorMessage; + } + + /** + * Getters for success status and error message + */ + public boolean isSuccess() { + return success; + } + + /** + * Get the error message if the operation failed + * + * @return Error message or empty string if successful + */ + public String getErrorMessage() { + return errorMessage; + } + } + + /** + * Handle the HTTP request to set a function prototype + * + * @param exchange The HTTP exchange containing the request and response + * @throws Exception If an error occurs during processing + */ + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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()); + } + } + + /** + * Set the function prototype for a given function address + * + * @param functionAddrStr The address of the function as a string + * @param prototype The prototype string to set + * @return PrototypeResult indicating success or failure with error message + */ + private PrototypeResult setFunctionPrototype(String functionAddrStr, String prototype) { + // Input validation + Program program = getCurrentProgram(tool); + 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()); + } + + /** + * Apply the function prototype in a Swing thread to avoid blocking the UI + * + * @param program The current program + * @param functionAddrStr The address of the function as a string + * @param prototype The prototype string to set + * @param success Atomic boolean to indicate success + * @param errorMessage StringBuilder to collect error messages + */ + 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 = program.getListing().getFunctionContaining(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 to the function indicating the prototype being set + * + * @param program The current program + * @param func The function to add the comment to + * @param prototype The prototype string 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 the function signature from the prototype string and apply it to the + * function + * + * @param program The current program + * @param addr The address of the function + * @param prototype The prototype string to set + * @param success Atomic boolean to indicate success + * @param errorMessage StringBuilder to collect error messages + */ + 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()); + } + } } diff --git a/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java index 9b7ed0f..622b4f0 100644 --- a/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java +++ b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java @@ -28,308 +28,362 @@ import static com.lauriewired.util.ParseUtils.sendResponse; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for setting the type of a local variable in a function. + * This handler allows users to specify a function address, variable name, + * and the new type they want to set for that variable. + */ public final class SetLocalVariableType extends Handler { - public SetLocalVariableType(PluginTool tool) { - super(tool, "/set_local_variable_type"); - } - - @Override - public void handle(HttpExchange exchange) throws Exception { - Map 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(tool); - 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()); - } - - /** - * Set a local variable's type using HighFunctionDBUtil.updateDBVariable - */ - private boolean setLocalVariableType(String functionAddrStr, String variableName, String newType) { - // Input validation - Program program = getCurrentProgram(tool); - 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 = program.getListing().getFunctionContaining(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 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 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; - } - - /** - * 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 high symbol by name in the given high function - */ - private HighSymbol findSymbolByName(ghidra.program.model.pcode.HighFunction highFunction, String variableName) { - Iterator 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()); - } - } + /** + * Constructor for the SetLocalVariableType handler. + * + * @param tool The PluginTool instance to use for accessing the current program. + */ + public SetLocalVariableType(PluginTool tool) { + super(tool, "/set_local_variable_type"); + } + + /** + * Handles the HTTP request to set a local variable's type. + * + * @param exchange The HttpExchange object containing the request and response. + * @throws Exception If an error occurs while processing the request. + */ + @Override + public void handle(HttpExchange exchange) throws Exception { + Map 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(tool); + 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()); + } + + /** + * Sets the type of a local variable in a function. + * + * @param functionAddrStr The address of the function as a string. + * @param variableName The name of the variable to change. + * @param newType The new type to set for the variable. + * @return true if the type was set successfully, false otherwise. + */ + private boolean setLocalVariableType(String functionAddrStr, String variableName, String newType) { + // Input validation + Program program = getCurrentProgram(tool); + 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(); + } + + /** + * Applies the new type to the specified variable in the given function. + * This method is run on the Swing thread to ensure UI updates are safe. + * + * @param program The current program. + * @param functionAddrStr The address of the function as a string. + * @param variableName The name of the variable to change. + * @param newType The new type to set for the variable. + * @param success AtomicBoolean to indicate if the operation was + * successful. + */ + 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 = program.getListing().getFunctionContaining(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()); + } + } + + /** + * Helper method to find a data type by name in all categories, handling case + * sensitivity. + * + * @param dtm The data type manager to search in. + * @param typeName The name of the type to find. + * @return The DataType if found, or null if not found. + */ + 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()); + } + + /** + * Searches for a data type by name in all categories of the DataTypeManager. + * This method checks both exact and case-insensitive matches. + * + * @param dtm The DataTypeManager to search in. + * @param name The name of the data type to search for. + * @return The DataType if found, or null if not found. + */ + private DataType searchByNameInAllCategories(DataTypeManager dtm, String name) { + // Get all data types from the manager + Iterator 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; + } + + /** + * Resolves the data type from the given type name, handling pointers and common + * built-in types. + * + * @param dtm The DataTypeManager to use for resolving types. + * @param typeName The name of the type 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 symbol by name in the local symbol map of the high function. + * + * @param highFunction The high function to search in. + * @param variableName The name of the variable to find. + * @return The HighSymbol if found, or null if not found. + */ + private HighSymbol findSymbolByName(ghidra.program.model.pcode.HighFunction highFunction, String variableName) { + Iterator symbols = highFunction.getLocalSymbolMap().getSymbols(); + while (symbols.hasNext()) { + HighSymbol s = symbols.next(); + if (s.getName().equals(variableName)) { + return s; + } + } + return null; + } + + /** + * Decompile the function to access its high-level representation. + * + * @param func The function to decompile. + * @param program The current program. + * @return The DecompileResults containing the decompiled function. + */ + 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; + } + + /** + * Update the variable type in the database using HighFunctionDBUtil. + * + * @param program The current program. + * @param symbol The high symbol representing the variable. + * @param dataType The new data type to set for the variable. + * @param success AtomicBoolean to indicate if the operation was successful. + */ + 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()); + } + } } diff --git a/src/main/java/com/lauriewired/util/GhidraUtils.java b/src/main/java/com/lauriewired/util/GhidraUtils.java index cb85053..f092709 100644 --- a/src/main/java/com/lauriewired/util/GhidraUtils.java +++ b/src/main/java/com/lauriewired/util/GhidraUtils.java @@ -12,38 +12,50 @@ import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** Utility class for Ghidra-related functions */ public final class GhidraUtils { - public static Program getCurrentProgram(PluginTool tool) { - ProgramManager pm = tool.getService(ProgramManager.class); - return pm != null ? pm.getCurrentProgram() : null; - } - - public static boolean setCommentAtAddress(PluginTool tool, - String addressStr, String comment, int commentType, String transactionName) { - Program program = getCurrentProgram(tool); - 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(GhidraUtils.class, "Error setting " + transactionName.toLowerCase(), e); - } finally { - program.endTransaction(tx, success.get()); - } - }); - } catch (InterruptedException | InvocationTargetException e) { - Msg.error(GhidraUtils.class, - "Failed to execute " + transactionName.toLowerCase() + " on Swing thread", e); - } - - return success.get(); - } + /** + * Gets the current program from the specified plugin tool. + * + * @param tool the plugin tool + * @return the current program, or null if not available + */ + public static Program getCurrentProgram(PluginTool tool) { + ProgramManager pm = tool.getService(ProgramManager.class); + return pm != null ? pm.getCurrentProgram() : null; + } + + /** + * Set a comment using the specified comment type (PRE_COMMENT or EOL_COMMENT) + */ + public static boolean setCommentAtAddress(PluginTool tool, + String addressStr, String comment, int commentType, String transactionName) { + Program program = getCurrentProgram(tool); + 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(GhidraUtils.class, "Error setting " + transactionName.toLowerCase(), e); + } finally { + program.endTransaction(tx, success.get()); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + Msg.error(GhidraUtils.class, + "Failed to execute " + transactionName.toLowerCase() + " on Swing thread", e); + } + + return success.get(); + } } diff --git a/src/main/java/com/lauriewired/util/ParseUtils.java b/src/main/java/com/lauriewired/util/ParseUtils.java index 3522fdb..aff2638 100644 --- a/src/main/java/com/lauriewired/util/ParseUtils.java +++ b/src/main/java/com/lauriewired/util/ParseUtils.java @@ -11,109 +11,111 @@ import java.util.List; import java.util.Map; +/** Utility class for parsing HTTP request parameters */ public final class ParseUtils { - /** - * Parse query parameters from the URL, e.g. ?offset=10&limit=100 - */ - public static Map parseQueryParams(HttpExchange exchange) { - Map 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(ParseUtils.class, "Error decoding URL parameter", e); - } - } - } - } - return result; - } + /** + * Parse query parameters from the URL, e.g. ?offset=10&limit=100 + */ + public static Map parseQueryParams(HttpExchange exchange) { + Map 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(ParseUtils.class, "Error decoding URL parameter", e); + } + } + } + } + return result; + } - /** - * Parse post body form params, e.g. oldName=foo&newName=bar - */ - public static Map parsePostParams(HttpExchange exchange) throws IOException { - byte[] body = exchange.getRequestBody().readAllBytes(); - String bodyStr = new String(body, StandardCharsets.UTF_8); - Map 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(ParseUtils.class, "Error decoding URL parameter", e); - } - } - } - return params; - } + /** + * Parse post body form params, e.g. oldName=foo&newName=bar + */ + public static Map parsePostParams(HttpExchange exchange) throws IOException { + byte[] body = exchange.getRequestBody().readAllBytes(); + String bodyStr = new String(body, StandardCharsets.UTF_8); + Map 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(ParseUtils.class, "Error decoding URL parameter", e); + } + } + } + return params; + } - /** - * Convert a list of strings into one big newline-delimited string, applying offset & limit. - */ - public static String paginateList(List items, int offset, int limit) { - int start = Math.max(0, offset); - int end = Math.min(items.size(), offset + limit); + /** + * Convert a list of strings into one big newline-delimited string, applying + * offset & limit. + */ + public static String paginateList(List 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 sub = items.subList(start, end); - return String.join("\n", sub); - } + if (start >= items.size()) { + return ""; // no items in range + } + List sub = items.subList(start, end); + return String.join("\n", sub); + } - /** - * Parse an integer from a string, or return defaultValue if null/invalid. - */ - public static int parseIntOrDefault(String val, int defaultValue) { - if (val == null) return defaultValue; - try { - return Integer.parseInt(val); - } - catch (NumberFormatException e) { - return defaultValue; - } - } + /** + * Parse an integer from a string, or return defaultValue if null/invalid. + */ + public static 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. - */ - public static 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(); - } + /** + * Escape non-ASCII chars to avoid potential decode issues. + */ + public static 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(); + } - /** - * Send responses - */ - public static 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); - } - } + /** + * Send responses + */ + public static 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); + } + } } From beee6b1f6d0d216a7ff0d4882be24c999b306ddb Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 20:57:41 -0700 Subject: [PATCH 04/17] Update AppTest.java --- src/test/java/com/lauriewired/AppTest.java | 48 ++++++++++------------ 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/test/java/com/lauriewired/AppTest.java b/src/test/java/com/lauriewired/AppTest.java index 77b1a97..a39d69e 100644 --- a/src/test/java/com/lauriewired/AppTest.java +++ b/src/test/java/com/lauriewired/AppTest.java @@ -7,32 +7,28 @@ /** * Unit test for simple App. */ -public class AppTest - extends TestCase -{ - /** - * Create the test case - * - * @param testName name of the test case - */ - public AppTest( String testName ) - { - super( testName ); - } +public class AppTest + extends TestCase { + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest(String testName) { + super(testName); + } - /** - * @return the suite of tests being tested - */ - public static Test suite() - { - return new TestSuite( AppTest.class ); - } + /** + * @return the suite of tests being tested + */ + public static Test suite() { + return new TestSuite(AppTest.class); + } - /** - * Rigourous Test :-) - */ - public void testApp() - { - assertTrue( true ); - } + /** + * Rigourous Test :-) + */ + public void testApp() { + assertTrue(true); + } } From c7318105f671f44860ed973a0929691b4dbe4c42 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 21:02:41 -0700 Subject: [PATCH 05/17] Apply PR #56 --- .../com/lauriewired/handlers/act/DecompileFunctionByName.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java index 5e2f884..9dd7d30 100644 --- a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java +++ b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java @@ -3,6 +3,7 @@ import com.lauriewired.handlers.Handler; import com.sun.net.httpserver.HttpExchange; import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileOptions; import ghidra.app.decompiler.DecompileResults; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.Function; @@ -56,6 +57,9 @@ private String generateResponse(String name) { if (program == null) return "No program loaded"; DecompInterface decomp = new DecompInterface(); + DecompileOptions options = new DecompileOptions(); + options.setRespectReadOnly(true); + decomp.setOptions(options); decomp.openProgram(program); for (Function func : program.getFunctionManager().getFunctions(true)) { if (func.getName().equals(name)) { From b28df52f4d0206a3fa3033b9862ccbeb1c597a3b Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 21:21:23 -0700 Subject: [PATCH 06/17] Create GetDataByLabel.java --- .../handlers/get/GetDataByLabel.java | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java diff --git a/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java new file mode 100644 index 0000000..71e9948 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java @@ -0,0 +1,71 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; + +import java.io.IOException; +import java.util.*; + +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler to retrieve data associated with a specific label in the current program. + * It responds with the address and value of the data defined at that label. + */ +public final class GetDataByLabel extends Handler { + /** + * Constructor for the GetDataByLabel handler. + * + * @param tool The PluginTool instance to use for accessing the current program. + */ + public GetDataByLabel(PluginTool tool) { + super("/get_data_by_label"); + } + + /** + * Handles the HTTP request to retrieve data by label. + * + * @param exchange The HttpExchange object containing the request and response. + * @throws IOException If an I/O error occurs during handling. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String label = qparams.get("label"); + sendResponse(exchange, getDataByLabel(label)); + } + + /** + * Retrieves data associated with the specified label in the current program. + * + * @param label The label to search for in the current program. + * @return A string containing the address and value of the data defined at that label, + * or an error message if the label is not found or no program is loaded. + */ + private String getDataByLabel(String label) { + Program program = getCurrentProgram(); + if (program == null) + return "No program loaded"; + if (label == null || label.isEmpty()) + return "Label is required"; + + SymbolTable st = program.getSymbolTable(); + Symbol it = st.getSymbol(label); + if (!it.hasNext()) + return "Label not found: " + label; + + StringBuilder sb = new StringBuilder(); + while (it.hasNext()) { + Symbol s = it.next(); + Address a = s.getAddress(); + Data d = program.getListing().getDefinedDataAt(a); + String v = (d != null) ? escapeString(String.valueOf(d.getDefaultValueRepresentation())) : "(no defined data)"; + sb.append(String.format("%s -> %s : %s%n", label, a, v)); + } + return sb.toString(); + } +} \ No newline at end of file From be2612ca626f80f1a05a687c1939171ab09af8fd Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 21:27:27 -0700 Subject: [PATCH 07/17] Create GetBytes.java --- .../lauriewired/handlers/get/GetBytes.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/main/java/com/lauriewired/handlers/get/GetBytes.java diff --git a/src/main/java/com/lauriewired/handlers/get/GetBytes.java b/src/main/java/com/lauriewired/handlers/get/GetBytes.java new file mode 100644 index 0000000..d5c1c00 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetBytes.java @@ -0,0 +1,67 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; + +import java.io.IOException; +import java.util.*; + +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler to get bytes from a specified address in the current program. + * Expects query parameters: address=
and size=. + */ +public final class GetBytes extends Handler { + /** + * Constructor for the GetBytes handler. + * + * @param tool The PluginTool instance to use. + */ + public GetBytes(PluginTool tool) { + super("/get_bytes"); + } + + /** + * Parses the query parameters from the HTTP exchange. + * + * @param exchange The HTTP exchange containing the request. + * @return A map of query parameters. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String addrStr = qparams.get("address"); + int size = parseIntOrDefault(qparams.get("size"), 1); + sendResponse(exchange, getBytes(addrStr, size)); + } + + /** + * Gets the bytes from the specified address in the current program. + * + * @param addressStr The address to read from. + * @param size The number of bytes to read. + * @return A string representation of the bytes in hex format. + */ + private String getBytes(String addressStr, int size) { + Program program = getCurrentProgram(); + if (program == null) + return "No program loaded"; + if (addressStr == null || addressStr.isEmpty()) + return "Address is required"; + if (size <= 0) + return "Size must be > 0"; + + try { + Address addr = program.getAddressFactory().getAddress(addressStr); + byte[] buf = new byte[size]; + int read = program.getMemory().getBytes(addr, buf); + return hexdump(addr, buf, read); + } catch (Exception e) { + return "Error reading memory: " + e.getMessage(); + } + } +} \ No newline at end of file From e5ffa7982b2ef035bc7d12911c22ff9436657e31 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 21:37:03 -0700 Subject: [PATCH 08/17] SearchBytes + search package --- .../lauriewired/handlers/get/GetBytes.java | 1 + .../handlers/get/GetDataByLabel.java | 1 + .../handlers/search/SearchBytes.java | 84 +++++++++++++++++++ .../{set => search}/SearchFunctions.java | 2 +- 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/lauriewired/handlers/search/SearchBytes.java rename src/main/java/com/lauriewired/handlers/{set => search}/SearchFunctions.java (98%) diff --git a/src/main/java/com/lauriewired/handlers/get/GetBytes.java b/src/main/java/com/lauriewired/handlers/get/GetBytes.java index d5c1c00..43f0e64 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetBytes.java +++ b/src/main/java/com/lauriewired/handlers/get/GetBytes.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.util.*; +import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; /** diff --git a/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java index 71e9948..a76aa68 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java +++ b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.util.*; +import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; /** diff --git a/src/main/java/com/lauriewired/handlers/search/SearchBytes.java b/src/main/java/com/lauriewired/handlers/search/SearchBytes.java new file mode 100644 index 0000000..96d5e95 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/search/SearchBytes.java @@ -0,0 +1,84 @@ +package com.lauriewired.handlers.search; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.util.task.TaskMonitorAdapter; + +import java.io.IOException; +import java.util.*; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +/** + * Handler for searching for byte sequences in the current program's memory. + * Expects a hex string of bytes to search for, with optional pagination + * parameters. + */ +public final class SearchBytes extends Handler { + /** + * Constructor for the SearchBytes handler. + * + * @param tool The PluginTool instance to use. + */ + public SearchBytes(PluginTool tool) { + super("/search_bytes"); + } + + /** + * Parses the query parameters from the HTTP request. + * + * @param exchange The HttpExchange containing the request. + * @return A map of query parameters. + */ + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String bytesHex = qparams.get("bytes"); + int offset = parseIntOrDefault(qparams.get("offset"), 0); + int limit = parseIntOrDefault(qparams.get("limit"), 100); + sendResponse(exchange, searchBytes(bytesHex, offset, limit)); + } + + /** + * Searches for the specified byte sequence in the current program's memory. + * + * @param bytesHex The hex string of bytes to search for. + * @param offset The starting index for pagination. + * @param limit The maximum number of results to return. + * @return A string containing the search results, formatted for pagination. + */ + private String searchBytes(String bytesHex, int offset, int limit) { + Program program = getCurrentProgram(); + if (program == null) + return "No program loaded"; + if (bytesHex == null || bytesHex.isEmpty()) + return "Byte sequence required"; + + byte[] needle; + try { + needle = decodeHex(bytesHex); + } catch (IllegalArgumentException e) { + return "Invalid hex string: " + bytesHex; + } + + Memory mem = program.getMemory(); + List hits = new ArrayList<>(); + + Address cur = mem.getMinAddress(); + while (cur != null && hits.size() < offset + limit) { + Address found = mem.findBytes(cur, needle, null, true, TaskMonitorAdapter.DUMMY_MONITOR); + if (found == null) + break; + hits.add(found.toString()); + + cur = found.add(1); + } + + return paginateList(hits, offset, limit); + } +} \ No newline at end of file diff --git a/src/main/java/com/lauriewired/handlers/set/SearchFunctions.java b/src/main/java/com/lauriewired/handlers/search/SearchFunctions.java similarity index 98% rename from src/main/java/com/lauriewired/handlers/set/SearchFunctions.java rename to src/main/java/com/lauriewired/handlers/search/SearchFunctions.java index 043d74c..d4fc855 100644 --- a/src/main/java/com/lauriewired/handlers/set/SearchFunctions.java +++ b/src/main/java/com/lauriewired/handlers/search/SearchFunctions.java @@ -1,4 +1,4 @@ -package com.lauriewired.handlers.set; +package com.lauriewired.handlers.search; import com.lauriewired.handlers.Handler; import com.sun.net.httpserver.HttpExchange; From 7fed42467c730fc9d9723ffa768c3abf9047c2e1 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 22:19:31 -0700 Subject: [PATCH 09/17] structs part 1 --- .../handlers/act/AddStructMembers.java | 102 ++++++++++++++++++ .../handlers/act/CreateStruct.java | 97 +++++++++++++++++ .../lauriewired/handlers/get/GetStruct.java | 29 +++++ .../lauriewired/handlers/set/ClearStruct.java | 29 +++++ .../handlers/set/SetLocalVariableType.java | 81 -------------- .../com/lauriewired/util/GhidraUtils.java | 58 +++++++++- .../java/com/lauriewired/util/ParseUtils.java | 51 +++++++-- .../com/lauriewired/util/StructUtils.java | 34 ++++++ 8 files changed, 390 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/lauriewired/handlers/act/AddStructMembers.java create mode 100644 src/main/java/com/lauriewired/handlers/act/CreateStruct.java create mode 100644 src/main/java/com/lauriewired/handlers/get/GetStruct.java create mode 100644 src/main/java/com/lauriewired/handlers/set/ClearStruct.java create mode 100644 src/main/java/com/lauriewired/util/StructUtils.java diff --git a/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java b/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java new file mode 100644 index 0000000..0f11790 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java @@ -0,0 +1,102 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.Structure; + +import com.google.gson.Gson; + +import javax.swing.SwingUtilities; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.lauriewired.util.ParseUtils.*; +import static com.lauriewired.util.StructUtils.StructMember; +import ghidra.program.model.data.CategoryPath; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class AddStructMembers extends Handler { + public AddStructMembers(PluginTool tool) { + super("/add_struct_members"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String structName = params.get("struct_name"); + String category = params.get("category"); + String membersJson = params.get("members"); + + if (structName == null || membersJson == null) { + sendResponse(exchange, "struct_name and members are required"); + return; + } + sendResponse(exchange, addStructMembers(structName, category, membersJson)); + } + + private String addStructMembers(String structName, String category, String membersJson) { + Program program = getCurrentProgram(); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Add Struct Member"); + boolean success = false; + try { + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath(category == null ? "/" : category); + DataType dt = dtm.getDataType(path, structName); + + if (dt == null || !(dt instanceof Structure)) { + result.set("Error: Struct " + structName + " not found in category " + path); + return; + } + Structure struct = (Structure) dt; + + StringBuilder responseBuilder = new StringBuilder(); + + if (membersJson != null && !membersJson.isEmpty()) { + Gson gson = new Gson(); + StructMember[] members = gson.fromJson(membersJson, StructMember[].class); + + int membersAdded = 0; + for (StructMember member : members) { + DataType memberDt = resolveDataType(dtm, member.type); + if (memberDt == null) { + responseBuilder.append("\nError: Could not resolve data type '").append(member.type) + .append("' for member '").append(member.name) + .append("'. Aborting further member creation."); + break; + } + + if (member.offset != -1) { + struct.insertAtOffset((int) member.offset, memberDt, -1, member.name, member.comment); + } else { + struct.add(memberDt, member.name, member.comment); + } + membersAdded++; + } + responseBuilder.append("\nAdded ").append(membersAdded).append(" members."); + result.set(responseBuilder.toString()); + success = membersAdded > 0; + } + + } catch (Exception e) { + result.set("Error: Failed to add member to struct: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute add struct member on Swing thread: " + e.getMessage(); + } + return result.get(); + } +} diff --git a/src/main/java/com/lauriewired/handlers/act/CreateStruct.java b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java new file mode 100644 index 0000000..5141a0d --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java @@ -0,0 +1,97 @@ +package com.lauriewired.handlers.act; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.StructureDataType; + +import java.lang.reflect.InvocationTargetException; +import java.io.IOException; +import java.util.*; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class CreateStruct extends Handler { + public CreateStruct(PluginTool tool) { + super("/create_struct"); + } + + @Override + public void handle(HttpExchange exchange) throws IOExcep + , String> params = parsePostParam + e = params.get("name"); + egory = params.get("category"); + = parseIntOrDefault(params.get("size"), 0); + + + ll || name.isEmpty()) { + exchang + + + sendResponse(exchange, createStruct(name, category, (int) size, membersJson)); + } + + private String createStruct(String name, String category, int size, String membersJson) { + Program program = getCurrentProgram(); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Create Struct"); + boolean success = false; + try { + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath(category == null ? "/" : category); + + if (dtm.getDataType(path, name) != null) { + result.set("Error: Struct " + name + " already exists in category " + path); + return; + } + StructureDataType newStruct = new StructureDataType(path, name, size, dtm); + + StringBuilder responseBuilder = new StringBuilder( + "Struct " + name + " created successfully in category " + path); + + if (membersJson != null && !membersJson.isEmpty()) { + Gson gson = new Gson(); + StructMember[] members = gson.fromJson(membersJson, StructMember[].class); + + int membersAdded = 0; + for (StructMember member : members) { + DataType memberDt = resolveDataType(dtm, member.type); + if (memberDt == null) { + responseBuilder.append("\nError: Could not resolve data type '").append(member.type) + .append("' for member '").append(member.name) + .append("'. Aborting further member creation."); + break; + } + + if (member.offset != -1) { + newStruct.insertAtOffset((int) member.offset, memberDt, -1, member.name, + member.comment); + } else { + newStruct.add(memberDt, member.name, member.comment); + } + membersAdded++; + } + responseBuilder.append("\nAdded ").append(membersAdded).append(" members."); + } + dtm.addDataType(newStruct, DataTypeConflictHandler.DEFAULT_HANDLER); + result.set(responseBuilder.toString()); + success = true; + } catch (Exception e) { + result.set("Error: Failed to create struct: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute create struct on Swing thread: " + e.getMessage(); + } + return result.get(); + } +} diff --git a/src/main/java/com/lauriewired/handlers/get/GetStruct.java b/src/main/java/com/lauriewired/handlers/get/GetStruct.java new file mode 100644 index 0000000..8bf2f55 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/get/GetStruct.java @@ -0,0 +1,29 @@ +package com.lauriewired.handlers.get; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; + +import java.io.IOException; +import java.util.*; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class GetStruct extends Handler { + public GetStruct(PluginTool tool) { + super("/get_struct"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map qparams = parseQueryParams(exchange); + String structName = qparams.get("name"); + String category = qparams.get("category"); + if (structName == null) { + sendResponse(exchange, "name is required"); + return; + } + sendResponse(exchange, getStruct(structName, category)); + } +} diff --git a/src/main/java/com/lauriewired/handlers/set/ClearStruct.java b/src/main/java/com/lauriewired/handlers/set/ClearStruct.java new file mode 100644 index 0000000..1504c99 --- /dev/null +++ b/src/main/java/com/lauriewired/handlers/set/ClearStruct.java @@ -0,0 +1,29 @@ +package com.lauriewired.handlers.set; + +import com.lauriewired.handlers.Handler; +import com.sun.net.httpserver.HttpExchange; +import ghidra.framework.plugintool.PluginTool; + +import java.io.IOException; +import java.util.*; + +import static com.lauriewired.util.ParseUtils.*; +import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; + +public final class ClearStruct extends Handler { + public ClearStruct(PluginTool tool) { + super("/clear_struct"); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String structName = params.get("struct_name"); + String category = params.get("category"); + if (structName == null) { + sendResponse(exchange, "struct_name is required"); + return; + } + sendResponse(exchange, clearStruct(structName, category)); + } +} diff --git a/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java index 622b4f0..e161658 100644 --- a/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java +++ b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java @@ -236,87 +236,6 @@ private DataType searchByNameInAllCategories(DataTypeManager dtm, String name) { return null; } - /** - * Resolves the data type from the given type name, handling pointers and common - * built-in types. - * - * @param dtm The DataTypeManager to use for resolving types. - * @param typeName The name of the type 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 symbol by name in the local symbol map of the high function. * diff --git a/src/main/java/com/lauriewired/util/GhidraUtils.java b/src/main/java/com/lauriewired/util/GhidraUtils.java index f092709..1816e0a 100644 --- a/src/main/java/com/lauriewired/util/GhidraUtils.java +++ b/src/main/java/com/lauriewired/util/GhidraUtils.java @@ -2,6 +2,8 @@ import ghidra.app.services.ProgramManager; import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.util.Msg; @@ -11,8 +13,15 @@ import java.util.concurrent.atomic.AtomicBoolean; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +import ghidra.program.model.data.DataTypeManagerService; +import ghidra.program.model.data.DataTypeParser; +import ghidra.util.data.DataTypeParser.AllowedDataTypes; -/** Utility class for Ghidra-related functions */ +/** + * Utility class for Ghidra-related operations. + * Provides methods to interact with the current program, resolve data types, + * and set comments at specific addresses. + */ public final class GhidraUtils { /** * Gets the current program from the specified plugin tool. @@ -26,7 +35,52 @@ public static Program getCurrentProgram(PluginTool tool) { } /** - * Set a comment using the specified comment type (PRE_COMMENT or EOL_COMMENT) + * 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 + */ + public static DataType resolveDataType(DataTypeManager dtm, String typeName) { + DataTypeManagerService dtms = tool.getService(DataTypeManagerService.class); + DataTypeManager[] managers = dtms.getDataTypeManagers(); + DataType dt = null; + + List managerList = new ArrayList<>(); + for (DataTypeManager manager : managers) { + if (manager != dtm) + managerList.add(manager); + } + managerList.addFirst(dtm); + + DataTypeParser parser = null; + + for (DataTypeManager manager : managerList) { + try { + parser = new DataTypeParser(manager, null, null, AllowedDataTypes.ALL); + dt = parser.parse(typeName); + if (dt != null) { + return dt; // Found a successful parse, return + } + } catch (Exception e) { + // Continue to next manager if this one fails + } + } + + // Fallback to int if we couldn't find it + Msg.warn(this, "Unknown type: " + typeName + ", defaulting to int"); + return dtm.getDataType("/int"); + } + + /** + * Sets a comment at the specified address in the current program. + * + * @param tool the plugin tool + * @param addressStr the address as a string + * @param comment the comment to set + * @param commentType the type of comment (e.g., CodeUnit.PLATE_COMMENT) + * @param transactionName the name of the transaction for logging + * @return true if successful, false otherwise */ public static boolean setCommentAtAddress(PluginTool tool, String addressStr, String comment, int commentType, String transactionName) { diff --git a/src/main/java/com/lauriewired/util/ParseUtils.java b/src/main/java/com/lauriewired/util/ParseUtils.java index aff2638..ec4997c 100644 --- a/src/main/java/com/lauriewired/util/ParseUtils.java +++ b/src/main/java/com/lauriewired/util/ParseUtils.java @@ -11,10 +11,22 @@ import java.util.List; import java.util.Map; -/** Utility class for parsing HTTP request parameters */ +/** + * Utility methods for parsing HTTP requests and responses. + * + * This class provides methods to parse query parameters, post body parameters, + * paginate lists, parse integers with defaults, escape non-ASCII characters, + * and send HTTP responses. + */ public final class ParseUtils { /** - * Parse query parameters from the URL, e.g. ?offset=10&limit=100 + * Parse query parameters from the request URI. + * + * @param exchange The HttpExchange object containing the request. + * @return A map of query parameters where the key is the parameter name + * and the value is the parameter value. + * For example, for a query string "offset=10&limit=100", + * the map will contain {"offset": "10", "limit": "100"} */ public static Map parseQueryParams(HttpExchange exchange) { Map result = new HashMap<>(); @@ -39,7 +51,13 @@ public static Map parseQueryParams(HttpExchange exchange) { } /** - * Parse post body form params, e.g. oldName=foo&newName=bar + * Parse POST parameters from the request body. + * + * @param exchange The HttpExchange object containing the request. + * @return A map of POST parameters where the key is the parameter name + * and the value is the parameter value. + * For example, for a body "offset=10&limit=100", + * the map will contain {"offset": "10", "limit": "100"} */ public static Map parsePostParams(HttpExchange exchange) throws IOException { byte[] body = exchange.getRequestBody().readAllBytes(); @@ -62,8 +80,13 @@ public static Map parsePostParams(HttpExchange exchange) throws } /** - * Convert a list of strings into one big newline-delimited string, applying - * offset & limit. + * Paginate a list of items based on offset and limit. + * + * @param items The list of items to paginate. + * @param offset The starting index for pagination. + * @param limit The maximum number of items to return. + * @return A string containing the paginated items, each on a new line. + * If the offset is beyond the list size, returns an empty string. */ public static String paginateList(List items, int offset, int limit) { int start = Math.max(0, offset); @@ -77,7 +100,11 @@ public static String paginateList(List items, int offset, int limit) { } /** - * Parse an integer from a string, or return defaultValue if null/invalid. + * Parse an integer from a string, returning a default value if parsing fails. + * + * @param val The string to parse. + * @param defaultValue The default value to return if parsing fails. + * @return The parsed integer or the default value if parsing fails. */ public static int parseIntOrDefault(String val, int defaultValue) { if (val == null) @@ -90,7 +117,11 @@ public static int parseIntOrDefault(String val, int defaultValue) { } /** - * Escape non-ASCII chars to avoid potential decode issues. + * Escape non-ASCII characters in a string. + * + * @param input The input string to escape. + * @return A string where non-ASCII characters are replaced with their + * hexadecimal representation, e.g. "\xFF" for 255. */ public static String escapeNonAscii(String input) { if (input == null) @@ -108,7 +139,11 @@ public static String escapeNonAscii(String input) { } /** - * Send responses + * Send a plain text response to the HTTP exchange. + * + * @param exchange The HttpExchange object to send the response to. + * @param response The response string to send. + * @throws IOException If an I/O error occurs while sending the response. */ public static void sendResponse(HttpExchange exchange, String response) throws IOException { byte[] bytes = response.getBytes(StandardCharsets.UTF_8); diff --git a/src/main/java/com/lauriewired/util/StructUtils.java b/src/main/java/com/lauriewired/util/StructUtils.java new file mode 100644 index 0000000..9857e61 --- /dev/null +++ b/src/main/java/com/lauriewired/util/StructUtils.java @@ -0,0 +1,34 @@ +package com.lauriewired.util; + +/** + * Utility class for handling structures and their members. + * This class provides a representation of a structure member with its name, + * type, comment, and offset. + */ +public final class StructUtils { + /** + * Represents a member of a structure. + */ + public static class StructMember { + /** + * The name of the member. + */ + String name; + + /** + * The type of the member. + */ + String type; + + /** + * The comment for the member. + */ + String comment; + + /** + * The offset of the member in the structure. + * Initialized to -1 to indicate that it has not been set. + */ + double offset = -1; // Use double to handle GSON parsing number as double + } +} From 3d7f48e646c9e2178d8123aa404e9f2651a26594 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Mon, 28 Jul 2025 22:23:43 -0700 Subject: [PATCH 10/17] fix VSCode eviscerating CreateStruct, thanks Microsoft... --- .../handlers/act/CreateStruct.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/lauriewired/handlers/act/CreateStruct.java b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java index 5141a0d..dfc7162 100644 --- a/src/main/java/com/lauriewired/handlers/act/CreateStruct.java +++ b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java @@ -18,18 +18,17 @@ public CreateStruct(PluginTool tool) { super("/create_struct"); } - @Override - public void handle(HttpExchange exchange) throws IOExcep - , String> params = parsePostParam - e = params.get("name"); - egory = params.get("category"); - = parseIntOrDefault(params.get("size"), 0); + public void handle(HttpExchange exchange) throws IOException { + Map params = parsePostParams(exchange); + String name = params.get("name"); + String category = params.get("category"); + long size = parseIntOrDefault(params.get("size"), 0); + String membersJson = params.get("members"); // Optional - - ll || name.isEmpty()) { - exchang - - + if (name == null || name.isEmpty()) { + sendResponse(exchange, "Struct name is required"); + return; + } sendResponse(exchange, createStruct(name, category, (int) size, membersJson)); } From b22ab0167ccec548378524a0cea9b5f8c7000b22 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Tue, 29 Jul 2025 00:24:03 -0700 Subject: [PATCH 11/17] CreateStruct finished --- src/main/java/com/lauriewired/GhidraMCPPlugin.java | 2 +- .../java/com/lauriewired/handlers/act/CreateStruct.java | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/lauriewired/GhidraMCPPlugin.java b/src/main/java/com/lauriewired/GhidraMCPPlugin.java index f8f0b07..b29e353 100644 --- a/src/main/java/com/lauriewired/GhidraMCPPlugin.java +++ b/src/main/java/com/lauriewired/GhidraMCPPlugin.java @@ -57,7 +57,7 @@ * to ensure thread safety with Ghidra's event dispatch thread. * * @author LaurieWired - * @version 1.3.2 + * @version 2.0 * @since Ghidra 11.3.2 * @see ghidra.framework.plugintool.Plugin * @see com.sun.net.httpserver.HttpServer diff --git a/src/main/java/com/lauriewired/handlers/act/CreateStruct.java b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java index dfc7162..18ae630 100644 --- a/src/main/java/com/lauriewired/handlers/act/CreateStruct.java +++ b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java @@ -3,14 +3,21 @@ import com.lauriewired.handlers.Handler; import com.sun.net.httpserver.HttpExchange; import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.StructureDataType; -import java.lang.reflect.InvocationTargetException; +import com.google.gson.Gson; + +import javax.swing.SwingUtilities; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import static com.lauriewired.util.ParseUtils.*; +import static com.lauriewired.util.StructUtils.StructMember; +import ghidra.program.model.data.CategoryPath; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; public final class CreateStruct extends Handler { From 654388c1db0db389e60bbb429a15ec89df289f9f Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Tue, 29 Jul 2025 12:37:34 -0700 Subject: [PATCH 12/17] add hexdump() to ParseUtils + error fixes --- .../handlers/act/AddStructMembers.java | 8 ++++--- .../handlers/act/CreateStruct.java | 9 +++++--- .../lauriewired/handlers/get/GetBytes.java | 4 ++-- .../handlers/get/GetDataByLabel.java | 18 +++++++++------- .../lauriewired/handlers/get/GetStruct.java | 2 +- .../handlers/search/SearchBytes.java | 4 ++-- .../lauriewired/handlers/set/ClearStruct.java | 2 +- .../lauriewired/handlers/set/SetBytes.java | 0 .../com/lauriewired/util/GhidraUtils.java | 12 ++++++----- .../java/com/lauriewired/util/ParseUtils.java | 21 +++++++++++++++++++ .../com/lauriewired/util/StructUtils.java | 8 +++---- 11 files changed, 60 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/lauriewired/handlers/set/SetBytes.java diff --git a/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java b/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java index 0f11790..5f1e97b 100644 --- a/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java +++ b/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java @@ -6,6 +6,7 @@ import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; import com.google.gson.Gson; @@ -15,6 +16,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import static com.lauriewired.util.GhidraUtils.*; import static com.lauriewired.util.ParseUtils.*; import static com.lauriewired.util.StructUtils.StructMember; import ghidra.program.model.data.CategoryPath; @@ -22,7 +24,7 @@ public final class AddStructMembers extends Handler { public AddStructMembers(PluginTool tool) { - super("/add_struct_members"); + super(tool, "/add_struct_members"); } @Override @@ -40,7 +42,7 @@ public void handle(HttpExchange exchange) throws IOException { } private String addStructMembers(String structName, String category, String membersJson) { - Program program = getCurrentProgram(); + Program program = getCurrentProgram(tool); if (program == null) return "No program loaded"; @@ -68,7 +70,7 @@ private String addStructMembers(String structName, String category, String membe int membersAdded = 0; for (StructMember member : members) { - DataType memberDt = resolveDataType(dtm, member.type); + DataType memberDt = resolveDataType(tool, dtm, member.type); if (memberDt == null) { responseBuilder.append("\nError: Could not resolve data type '").append(member.type) .append("' for member '").append(member.name) diff --git a/src/main/java/com/lauriewired/handlers/act/CreateStruct.java b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java index 18ae630..a54e784 100644 --- a/src/main/java/com/lauriewired/handlers/act/CreateStruct.java +++ b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java @@ -4,8 +4,10 @@ import com.sun.net.httpserver.HttpExchange; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeConflictHandler; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.StructureDataType; +import ghidra.program.model.listing.Program; import com.google.gson.Gson; @@ -15,6 +17,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicReference; +import static com.lauriewired.util.GhidraUtils.*; import static com.lauriewired.util.ParseUtils.*; import static com.lauriewired.util.StructUtils.StructMember; import ghidra.program.model.data.CategoryPath; @@ -22,7 +25,7 @@ public final class CreateStruct extends Handler { public CreateStruct(PluginTool tool) { - super("/create_struct"); + super(tool, "/create_struct"); } public void handle(HttpExchange exchange) throws IOException { @@ -40,7 +43,7 @@ public void handle(HttpExchange exchange) throws IOException { } private String createStruct(String name, String category, int size, String membersJson) { - Program program = getCurrentProgram(); + Program program = getCurrentProgram(tool); if (program == null) return "No program loaded"; @@ -68,7 +71,7 @@ private String createStruct(String name, String category, int size, String membe int membersAdded = 0; for (StructMember member : members) { - DataType memberDt = resolveDataType(dtm, member.type); + DataType memberDt = resolveDataType(tool, dtm, member.type); if (memberDt == null) { responseBuilder.append("\nError: Could not resolve data type '").append(member.type) .append("' for member '").append(member.name) diff --git a/src/main/java/com/lauriewired/handlers/get/GetBytes.java b/src/main/java/com/lauriewired/handlers/get/GetBytes.java index 43f0e64..dc76557 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetBytes.java +++ b/src/main/java/com/lauriewired/handlers/get/GetBytes.java @@ -23,7 +23,7 @@ public final class GetBytes extends Handler { * @param tool The PluginTool instance to use. */ public GetBytes(PluginTool tool) { - super("/get_bytes"); + super(tool, "/get_bytes"); } /** @@ -48,7 +48,7 @@ public void handle(HttpExchange exchange) throws IOException { * @return A string representation of the bytes in hex format. */ private String getBytes(String addressStr, int size) { - Program program = getCurrentProgram(); + Program program = getCurrentProgram(tool); if (program == null) return "No program loaded"; if (addressStr == null || addressStr.isEmpty()) diff --git a/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java index a76aa68..1e2692c 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java +++ b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java @@ -14,7 +14,8 @@ import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; /** - * Handler to retrieve data associated with a specific label in the current program. + * Handler to retrieve data associated with a specific label in the current + * program. * It responds with the address and value of the data defined at that label. */ public final class GetDataByLabel extends Handler { @@ -24,7 +25,7 @@ public final class GetDataByLabel extends Handler { * @param tool The PluginTool instance to use for accessing the current program. */ public GetDataByLabel(PluginTool tool) { - super("/get_data_by_label"); + super(tool, "/get_data_by_label"); } /** @@ -44,16 +45,18 @@ public void handle(HttpExchange exchange) throws IOException { * Retrieves data associated with the specified label in the current program. * * @param label The label to search for in the current program. - * @return A string containing the address and value of the data defined at that label, - * or an error message if the label is not found or no program is loaded. + * @return A string containing the address and value of the data defined at that + * label, + * or an error message if the label is not found or no program is + * loaded. */ private String getDataByLabel(String label) { - Program program = getCurrentProgram(); + Program program = getCurrentProgram(tool); if (program == null) return "No program loaded"; if (label == null || label.isEmpty()) return "Label is required"; - + SymbolTable st = program.getSymbolTable(); Symbol it = st.getSymbol(label); if (!it.hasNext()) @@ -64,7 +67,8 @@ private String getDataByLabel(String label) { Symbol s = it.next(); Address a = s.getAddress(); Data d = program.getListing().getDefinedDataAt(a); - String v = (d != null) ? escapeString(String.valueOf(d.getDefaultValueRepresentation())) : "(no defined data)"; + String v = (d != null) ? escapeString(String.valueOf(d.getDefaultValueRepresentation())) + : "(no defined data)"; sb.append(String.format("%s -> %s : %s%n", label, a, v)); } return sb.toString(); diff --git a/src/main/java/com/lauriewired/handlers/get/GetStruct.java b/src/main/java/com/lauriewired/handlers/get/GetStruct.java index 8bf2f55..1ef39ec 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetStruct.java +++ b/src/main/java/com/lauriewired/handlers/get/GetStruct.java @@ -12,7 +12,7 @@ public final class GetStruct extends Handler { public GetStruct(PluginTool tool) { - super("/get_struct"); + super(tool, "/get_struct"); } @Override diff --git a/src/main/java/com/lauriewired/handlers/search/SearchBytes.java b/src/main/java/com/lauriewired/handlers/search/SearchBytes.java index 96d5e95..f0d08c7 100644 --- a/src/main/java/com/lauriewired/handlers/search/SearchBytes.java +++ b/src/main/java/com/lauriewired/handlers/search/SearchBytes.java @@ -26,7 +26,7 @@ public final class SearchBytes extends Handler { * @param tool The PluginTool instance to use. */ public SearchBytes(PluginTool tool) { - super("/search_bytes"); + super(tool, "/search_bytes"); } /** @@ -53,7 +53,7 @@ public void handle(HttpExchange exchange) throws IOException { * @return A string containing the search results, formatted for pagination. */ private String searchBytes(String bytesHex, int offset, int limit) { - Program program = getCurrentProgram(); + Program program = getCurrentProgram(tool); if (program == null) return "No program loaded"; if (bytesHex == null || bytesHex.isEmpty()) diff --git a/src/main/java/com/lauriewired/handlers/set/ClearStruct.java b/src/main/java/com/lauriewired/handlers/set/ClearStruct.java index 1504c99..62f7902 100644 --- a/src/main/java/com/lauriewired/handlers/set/ClearStruct.java +++ b/src/main/java/com/lauriewired/handlers/set/ClearStruct.java @@ -12,7 +12,7 @@ public final class ClearStruct extends Handler { public ClearStruct(PluginTool tool) { - super("/clear_struct"); + super(tool, "/clear_struct"); } @Override diff --git a/src/main/java/com/lauriewired/handlers/set/SetBytes.java b/src/main/java/com/lauriewired/handlers/set/SetBytes.java new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/lauriewired/util/GhidraUtils.java b/src/main/java/com/lauriewired/util/GhidraUtils.java index 1816e0a..dd0a81e 100644 --- a/src/main/java/com/lauriewired/util/GhidraUtils.java +++ b/src/main/java/com/lauriewired/util/GhidraUtils.java @@ -10,11 +10,12 @@ import javax.swing.*; import java.lang.reflect.InvocationTargetException; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; +import ghidra.app.services.DataTypeManagerService; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; -import ghidra.program.model.data.DataTypeManagerService; -import ghidra.program.model.data.DataTypeParser; +import ghidra.util.data.DataTypeParser; import ghidra.util.data.DataTypeParser.AllowedDataTypes; /** @@ -36,12 +37,13 @@ public static Program getCurrentProgram(PluginTool tool) { /** * Resolves a data type by name, handling common types and pointer types - * + * + * @param tool The plugin tool to use for services * @param dtm The data type manager * @param typeName The type name to resolve * @return The resolved DataType, or null if not found */ - public static DataType resolveDataType(DataTypeManager dtm, String typeName) { + public static DataType resolveDataType(PluginTool tool, DataTypeManager dtm, String typeName) { DataTypeManagerService dtms = tool.getService(DataTypeManagerService.class); DataTypeManager[] managers = dtms.getDataTypeManagers(); DataType dt = null; @@ -68,7 +70,7 @@ public static DataType resolveDataType(DataTypeManager dtm, String typeName) { } // Fallback to int if we couldn't find it - Msg.warn(this, "Unknown type: " + typeName + ", defaulting to int"); + Msg.warn(GhidraUtils.class, "Unknown type: " + typeName + ", defaulting to int"); return dtm.getDataType("/int"); } diff --git a/src/main/java/com/lauriewired/util/ParseUtils.java b/src/main/java/com/lauriewired/util/ParseUtils.java index ec4997c..e5b73de 100644 --- a/src/main/java/com/lauriewired/util/ParseUtils.java +++ b/src/main/java/com/lauriewired/util/ParseUtils.java @@ -1,6 +1,7 @@ package com.lauriewired.util; import com.sun.net.httpserver.HttpExchange; +import ghidra.program.model.address.Address; import ghidra.util.Msg; import java.io.IOException; @@ -153,4 +154,24 @@ public static void sendResponse(HttpExchange exchange, String response) throws I os.write(bytes); } } + + /** + * Generate a hexdump of a byte array starting from a given base address. + * + * @param base The base address to start the hexdump from. + * @param buf The byte array to generate the hexdump for. + * @param len The number of bytes to include in the hexdump. + * @return A string representation of the hexdump. + */ + public static String hexdump(Address base, byte[] buf, int len) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i += 16) { + sb.append(String.format("%s ", base.add(i))); + for (int j = 0; j < 16 && (i + j) < len; j++) { + sb.append(String.format("%02X ", buf[i + j])); + } + sb.append('\n'); + } + return sb.toString(); + } } diff --git a/src/main/java/com/lauriewired/util/StructUtils.java b/src/main/java/com/lauriewired/util/StructUtils.java index 9857e61..cda5abe 100644 --- a/src/main/java/com/lauriewired/util/StructUtils.java +++ b/src/main/java/com/lauriewired/util/StructUtils.java @@ -13,22 +13,22 @@ public static class StructMember { /** * The name of the member. */ - String name; + public String name; /** * The type of the member. */ - String type; + public String type; /** * The comment for the member. */ - String comment; + public String comment; /** * The offset of the member in the structure. * Initialized to -1 to indicate that it has not been set. */ - double offset = -1; // Use double to handle GSON parsing number as double + public double offset = -1; // Use double to handle GSON parsing number as double } } From 79a55f6400ab19bdaaae4f91c9faff94c46f49df Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Tue, 29 Jul 2025 12:51:49 -0700 Subject: [PATCH 13/17] decodeHex added --- .../handlers/get/GetDataByLabel.java | 4 ++-- .../handlers/set/SetLocalVariableType.java | 3 +-- .../java/com/lauriewired/util/ParseUtils.java | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java index 1e2692c..e9ecdb4 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java +++ b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java @@ -58,7 +58,7 @@ private String getDataByLabel(String label) { return "Label is required"; SymbolTable st = program.getSymbolTable(); - Symbol it = st.getSymbol(label); + SymbolIterator it = st.getSymbols(label); if (!it.hasNext()) return "Label not found: " + label; @@ -67,7 +67,7 @@ private String getDataByLabel(String label) { Symbol s = it.next(); Address a = s.getAddress(); Data d = program.getListing().getDefinedDataAt(a); - String v = (d != null) ? escapeString(String.valueOf(d.getDefaultValueRepresentation())) + String v = (d != null) ? escapeNonAscii(String.valueOf(d.getDefaultValueRepresentation())) : "(no defined data)"; sb.append(String.format("%s -> %s : %s%n", label, a, v)); } diff --git a/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java index e161658..0cb929b 100644 --- a/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java +++ b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java @@ -24,8 +24,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; -import static com.lauriewired.util.ParseUtils.parsePostParams; -import static com.lauriewired.util.ParseUtils.sendResponse; +import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; /** diff --git a/src/main/java/com/lauriewired/util/ParseUtils.java b/src/main/java/com/lauriewired/util/ParseUtils.java index e5b73de..aa10ae8 100644 --- a/src/main/java/com/lauriewired/util/ParseUtils.java +++ b/src/main/java/com/lauriewired/util/ParseUtils.java @@ -174,4 +174,22 @@ public static String hexdump(Address base, byte[] buf, int len) { } return sb.toString(); } + + /** + * Decode a hexadecimal string into a byte array. + * + * @param hex The hexadecimal string to decode. + * @return A byte array representing the decoded hexadecimal string. + * @throws IllegalArgumentException If the input string is not a valid hex string. + */ + public static byte[] decodeHex(String hex) { + hex = hex.replaceAll("\\s+", ""); + if (hex.length() % 2 != 0) + throw new IllegalArgumentException(); + byte[] out = new byte[hex.length() / 2]; + for (int i = 0; i < out.length; i++) { + out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return out; + } } From 83ad9566d32abdcf795e439e68cd3f1805764473 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Tue, 29 Jul 2025 12:59:54 -0700 Subject: [PATCH 14/17] Update ClearStruct.java --- .../lauriewired/handlers/set/ClearStruct.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/main/java/com/lauriewired/handlers/set/ClearStruct.java b/src/main/java/com/lauriewired/handlers/set/ClearStruct.java index 62f7902..1a63f01 100644 --- a/src/main/java/com/lauriewired/handlers/set/ClearStruct.java +++ b/src/main/java/com/lauriewired/handlers/set/ClearStruct.java @@ -3,18 +3,41 @@ import com.lauriewired.handlers.Handler; import com.sun.net.httpserver.HttpExchange; import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.Structure; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import static com.lauriewired.util.ParseUtils.*; +import ghidra.program.model.data.CategoryPath; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for clearing the contents of a structure in Ghidra. + * This handler processes requests to clear a specified structure by name and + * category. + */ public final class ClearStruct extends Handler { + /** + * Constructs a new ClearStruct handler. + * + * @param tool the PluginTool instance to use for program operations + */ public ClearStruct(PluginTool tool) { super(tool, "/clear_struct"); } + /** + * Handles HTTP requests to clear a structure. + * Expects POST parameters: struct_name (required), category (optional). + * + * @param exchange the HttpExchange object containing the request + * @throws IOException if an I/O error occurs + */ @Override public void handle(HttpExchange exchange) throws IOException { Map params = parsePostParams(exchange); @@ -26,4 +49,50 @@ public void handle(HttpExchange exchange) throws IOException { } sendResponse(exchange, clearStruct(structName, category)); } + + /** + * Clears the contents of a structure. + * + * @param structName the name of the structure to clear + * @param category the category of the structure + * @return a message indicating the result of the operation + */ + private String clearStruct(String structName, String category) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + final AtomicReference result = new AtomicReference<>(); + try { + SwingUtilities.invokeAndWait(() -> { + int txId = program.startTransaction("Clear Struct"); + boolean success = false; + try { + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath(category == null ? "/" : category); + DataType dt = dtm.getDataType(path, structName); + if (dt == null || !(dt instanceof Structure)) { + result.set("Error: Struct " + structName + " not found in category " + path); + return; + } + Structure struct = (Structure) dt; + if (struct.isNotYetDefined()) { + result.set("Struct " + structName + " is empty, nothing to clear."); + success = true; // Not an error state + return; + } + struct.deleteAll(); + result.set("Struct " + structName + " cleared."); + success = true; + } catch (Exception e) { + result.set("Error: Failed to clear struct: " + e.getMessage()); + } finally { + program.endTransaction(txId, success); + } + }); + } catch (InterruptedException | InvocationTargetException e) { + return "Error: Failed to execute clear struct on Swing thread: " + e.getMessage(); + } + return result.get(); + } } From 67730960eadd9e69ca1446e4d7cbe2952c338175 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Tue, 29 Jul 2025 13:03:18 -0700 Subject: [PATCH 15/17] Update GetStruct.java --- .../lauriewired/handlers/get/GetStruct.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/main/java/com/lauriewired/handlers/get/GetStruct.java b/src/main/java/com/lauriewired/handlers/get/GetStruct.java index 1ef39ec..febbe40 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetStruct.java +++ b/src/main/java/com/lauriewired/handlers/get/GetStruct.java @@ -3,18 +3,40 @@ import com.lauriewired.handlers.Handler; import com.sun.net.httpserver.HttpExchange; import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.Structure; + +import com.google.gson.Gson; import java.io.IOException; import java.util.*; import static com.lauriewired.util.ParseUtils.*; +import ghidra.program.model.data.CategoryPath; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; +/** + * Handler for retrieving details of a structure by its name and category. + * Expects query parameters: name (required), category (optional). + */ public final class GetStruct extends Handler { + /** + * Constructor for the GetStruct handler. + * + * @param tool the PluginTool instance to use for accessing the current program. + */ public GetStruct(PluginTool tool) { super(tool, "/get_struct"); } + /** + * Handles the HTTP request to retrieve structure details. + * + * @param exchange the HttpExchange object containing the request and response. + * @throws IOException if an I/O error occurs during handling. + */ @Override public void handle(HttpExchange exchange) throws IOException { Map qparams = parseQueryParams(exchange); @@ -26,4 +48,50 @@ public void handle(HttpExchange exchange) throws IOException { } sendResponse(exchange, getStruct(structName, category)); } + + /** + * Retrieves the structure details as a JSON string. + * + * @param structName the name of the structure to retrieve. + * @param category the category path where the structure is located + * (optional). + * @return a JSON representation of the structure or an error message if not + * found. + */ + private String getStruct(String structName, String category) { + Program program = getCurrentProgram(tool); + if (program == null) + return "No program loaded"; + + DataTypeManager dtm = program.getDataTypeManager(); + CategoryPath path = new CategoryPath(category == null ? "/" : category); + DataType dt = dtm.getDataType(path, structName); + + if (dt == null || !(dt instanceof Structure)) { + return "Error: Struct " + structName + " not found in category " + path; + } + + Structure struct = (Structure) dt; + + Map structRepr = new HashMap<>(); + structRepr.put("name", struct.getName()); + structRepr.put("category", struct.getCategoryPath().getPath()); + structRepr.put("size", struct.getLength()); + structRepr.put("isNotYetDefined", struct.isNotYetDefined()); + + List> membersList = new ArrayList<>(); + for (DataTypeComponent component : struct.getDefinedComponents()) { + Map memberMap = new HashMap<>(); + memberMap.put("name", component.getFieldName()); + memberMap.put("type", component.getDataType().getName()); + memberMap.put("offset", component.getOffset()); + memberMap.put("size", component.getLength()); + memberMap.put("comment", component.getComment()); + membersList.add(memberMap); + } + structRepr.put("members", membersList); + + Gson gson = new Gson(); + return gson.toJson(structRepr); + } } From 1d096a0f174787e42a27dcd70b016b9a3f1f7c46 Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Tue, 29 Jul 2025 13:10:55 -0700 Subject: [PATCH 16/17] all errors fixed --- src/main/java/com/lauriewired/handlers/get/GetStruct.java | 1 + src/main/java/com/lauriewired/handlers/set/ClearStruct.java | 2 ++ .../com/lauriewired/handlers/set/SetLocalVariableType.java | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/lauriewired/handlers/get/GetStruct.java b/src/main/java/com/lauriewired/handlers/get/GetStruct.java index febbe40..b4e5530 100644 --- a/src/main/java/com/lauriewired/handlers/get/GetStruct.java +++ b/src/main/java/com/lauriewired/handlers/get/GetStruct.java @@ -7,6 +7,7 @@ import ghidra.program.model.data.DataTypeComponent; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; import com.google.gson.Gson; diff --git a/src/main/java/com/lauriewired/handlers/set/ClearStruct.java b/src/main/java/com/lauriewired/handlers/set/ClearStruct.java index 1a63f01..cdb26f1 100644 --- a/src/main/java/com/lauriewired/handlers/set/ClearStruct.java +++ b/src/main/java/com/lauriewired/handlers/set/ClearStruct.java @@ -6,7 +6,9 @@ import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; +import javax.swing.SwingUtilities; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.*; diff --git a/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java index 0cb929b..5a46375 100644 --- a/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java +++ b/src/main/java/com/lauriewired/handlers/set/SetLocalVariableType.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import static com.lauriewired.util.GhidraUtils.resolveDataType; import static com.lauriewired.util.ParseUtils.*; import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram; @@ -174,7 +175,7 @@ private void applyVariableType(Program program, String functionAddrStr, // Find the data type DataTypeManager dtm = program.getDataTypeManager(); - DataType dataType = resolveDataType(dtm, newType); + DataType dataType = resolveDataType(tool, dtm, newType); if (dataType == null) { Msg.error(this, "Could not resolve data type: " + newType); From fd1267741cd3a36845a515f8678e238024e944aa Mon Sep 17 00:00:00 2001 From: DaCodeChick Date: Tue, 29 Jul 2025 13:24:52 -0700 Subject: [PATCH 17/17] Delete SetBytes.java --- src/main/java/com/lauriewired/handlers/set/SetBytes.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/java/com/lauriewired/handlers/set/SetBytes.java diff --git a/src/main/java/com/lauriewired/handlers/set/SetBytes.java b/src/main/java/com/lauriewired/handlers/set/SetBytes.java deleted file mode 100644 index e69de29..0000000