Expand functionality
Find variables, rename and retype them Additionally merge changes from https://github.com/LaurieWired/GhidraMCP/pull/16 and https://github.com/LaurieWired/GhidraMCP/pull/18
This commit is contained in:
parent
1bfdf74554
commit
399c76b29a
52
CHANGELOG.md
Normal file
52
CHANGELOG.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added docstrings for all @mcp.tool functions
|
||||||
|
- Variable manipulation tools (rename/retype variables)
|
||||||
|
- New endpoints for function variable management
|
||||||
|
- Dynamic version output in API responses
|
||||||
|
- Enhanced function analysis capabilities
|
||||||
|
- Support for searching variables by name
|
||||||
|
- New tools for working with function variables:
|
||||||
|
- get_function_by_address
|
||||||
|
- get_current_address
|
||||||
|
- get_current_function
|
||||||
|
- decompile_function_by_address
|
||||||
|
- disassemble_function
|
||||||
|
- set_decompiler_comment
|
||||||
|
- set_disassembly_comment
|
||||||
|
- rename_local_variable
|
||||||
|
- rename_function_by_address
|
||||||
|
- set_function_prototype
|
||||||
|
- set_local_variable_type
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved version handling in build system
|
||||||
|
- Reorganized imports in bridge_mcp_hydra.py
|
||||||
|
- Updated MANIFEST.MF with more detailed description
|
||||||
|
|
||||||
|
## [1.2.0] - 2024-06-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Enhanced function analysis capabilities
|
||||||
|
- Additional variable manipulation tools
|
||||||
|
- Support for multiple Ghidra instances
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved error handling in API calls
|
||||||
|
- Optimized performance for large binaries
|
||||||
|
|
||||||
|
## [1.0.0] - 2024-03-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Initial release of GhydraMCP bridge
|
||||||
|
- Basic Ghidra instance management tools
|
||||||
|
- Function analysis tools
|
||||||
|
- Variable manipulation tools
|
||||||
@ -6,12 +6,14 @@
|
|||||||
# ]
|
# ]
|
||||||
# ///
|
# ///
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import requests
|
|
||||||
import threading
|
import threading
|
||||||
from typing import Dict
|
import time
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import requests
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
# Track active Ghidra instances (port -> info dict)
|
# Track active Ghidra instances (port -> info dict)
|
||||||
@ -251,6 +253,7 @@ def _discover_instances(port_range, host=None, timeout=0.5) -> dict:
|
|||||||
# Updated tool implementations with port parameter
|
# Updated tool implementations with port parameter
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def list_functions(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
def list_functions(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
||||||
"""List all functions with pagination"""
|
"""List all functions with pagination"""
|
||||||
@ -278,30 +281,175 @@ def update_data(port: int = DEFAULT_GHIDRA_PORT, address: str = "", new_name: st
|
|||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def list_segments(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
def list_segments(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
||||||
|
"""List all memory segments in the current program with pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port: Ghidra instance port (default: 8192)
|
||||||
|
offset: Pagination offset (default: 0)
|
||||||
|
limit: Maximum number of segments to return (default: 100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of segment information strings
|
||||||
|
"""
|
||||||
return safe_get(port, "segments", {"offset": offset, "limit": limit})
|
return safe_get(port, "segments", {"offset": offset, "limit": limit})
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def list_imports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
def list_imports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
||||||
|
"""List all imported symbols with pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port: Ghidra instance port (default: 8192)
|
||||||
|
offset: Pagination offset (default: 0)
|
||||||
|
limit: Maximum number of imports to return (default: 100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of import information strings
|
||||||
|
"""
|
||||||
return safe_get(port, "symbols/imports", {"offset": offset, "limit": limit})
|
return safe_get(port, "symbols/imports", {"offset": offset, "limit": limit})
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def list_exports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
def list_exports(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
||||||
|
"""List all exported symbols with pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port: Ghidra instance port (default: 8192)
|
||||||
|
offset: Pagination offset (default: 0)
|
||||||
|
limit: Maximum number of exports to return (default: 100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of export information strings
|
||||||
|
"""
|
||||||
return safe_get(port, "symbols/exports", {"offset": offset, "limit": limit})
|
return safe_get(port, "symbols/exports", {"offset": offset, "limit": limit})
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def list_namespaces(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
def list_namespaces(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
||||||
|
"""List all namespaces in the current program with pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port: Ghidra instance port (default: 8192)
|
||||||
|
offset: Pagination offset (default: 0)
|
||||||
|
limit: Maximum number of namespaces to return (default: 100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of namespace information strings
|
||||||
|
"""
|
||||||
return safe_get(port, "namespaces", {"offset": offset, "limit": limit})
|
return safe_get(port, "namespaces", {"offset": offset, "limit": limit})
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def list_data_items(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
def list_data_items(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100) -> list:
|
||||||
|
"""List all defined data items with pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port: Ghidra instance port (default: 8192)
|
||||||
|
offset: Pagination offset (default: 0)
|
||||||
|
limit: Maximum number of data items to return (default: 100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of data item information strings
|
||||||
|
"""
|
||||||
return safe_get(port, "data", {"offset": offset, "limit": limit})
|
return safe_get(port, "data", {"offset": offset, "limit": limit})
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def search_functions_by_name(port: int = DEFAULT_GHIDRA_PORT, query: str = "", offset: int = 0, limit: int = 100) -> list:
|
def search_functions_by_name(port: int = DEFAULT_GHIDRA_PORT, query: str = "", offset: int = 0, limit: int = 100) -> list:
|
||||||
|
"""Search for functions by name with pagination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port: Ghidra instance port (default: 8192)
|
||||||
|
query: Search string to match against function names
|
||||||
|
offset: Pagination offset (default: 0)
|
||||||
|
limit: Maximum number of functions to return (default: 100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of matching function information strings or error message if query is empty
|
||||||
|
"""
|
||||||
if not query:
|
if not query:
|
||||||
return ["Error: query string is required"]
|
return ["Error: query string is required"]
|
||||||
return safe_get(port, "functions", {"query": query, "offset": offset, "limit": limit})
|
return safe_get(port, "functions", {"query": query, "offset": offset, "limit": limit})
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_function_by_address(port: int = DEFAULT_GHIDRA_PORT, address: str = "") -> str:
|
||||||
|
"""
|
||||||
|
Get a function by its address.
|
||||||
|
"""
|
||||||
|
return "\n".join(safe_get(port, "get_function_by_address", {"address": address}))
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_current_address(port: int = DEFAULT_GHIDRA_PORT) -> str:
|
||||||
|
"""
|
||||||
|
Get the address currently selected by the user.
|
||||||
|
"""
|
||||||
|
return "\n".join(safe_get(port, "get_current_address"))
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def get_current_function(port: int = DEFAULT_GHIDRA_PORT) -> str:
|
||||||
|
"""
|
||||||
|
Get the function currently selected by the user.
|
||||||
|
"""
|
||||||
|
return "\n".join(safe_get(port, "get_current_function"))
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def list_functions(port: int = DEFAULT_GHIDRA_PORT) -> list:
|
||||||
|
"""
|
||||||
|
List all functions in the database.
|
||||||
|
"""
|
||||||
|
return safe_get(port, "list_functions")
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def decompile_function_by_address(port: int = DEFAULT_GHIDRA_PORT, address: str = "") -> str:
|
||||||
|
"""
|
||||||
|
Decompile a function at the given address.
|
||||||
|
"""
|
||||||
|
return "\n".join(safe_get(port, "decompile_function", {"address": address}))
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def disassemble_function(port: int = DEFAULT_GHIDRA_PORT, address: str = "") -> list:
|
||||||
|
"""
|
||||||
|
Get assembly code (address: instruction; comment) for a function.
|
||||||
|
"""
|
||||||
|
return safe_get(port, "disassemble_function", {"address": address})
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def set_decompiler_comment(port: int = DEFAULT_GHIDRA_PORT, address: str = "", comment: str = "") -> str:
|
||||||
|
"""
|
||||||
|
Set a comment for a given address in the function pseudocode.
|
||||||
|
"""
|
||||||
|
return safe_post(port, "set_decompiler_comment", {"address": address, "comment": comment})
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def set_disassembly_comment(port: int = DEFAULT_GHIDRA_PORT, address: str = "", comment: str = "") -> str:
|
||||||
|
"""
|
||||||
|
Set a comment for a given address in the function disassembly.
|
||||||
|
"""
|
||||||
|
return safe_post(port, "set_disassembly_comment", {"address": address, "comment": comment})
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def rename_local_variable(port: int = DEFAULT_GHIDRA_PORT, function_address: str = "", old_name: str = "", new_name: str = "") -> str:
|
||||||
|
"""
|
||||||
|
Rename a local variable in a function.
|
||||||
|
"""
|
||||||
|
return safe_post(port, "rename_local_variable", {"function_address": function_address, "old_name": old_name, "new_name": new_name})
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def rename_function_by_address(port: int = DEFAULT_GHIDRA_PORT, function_address: str = "", new_name: str = "") -> str:
|
||||||
|
"""
|
||||||
|
Rename a function by its address.
|
||||||
|
"""
|
||||||
|
return safe_post(port, "rename_function_by_address", {"function_address": function_address, "new_name": new_name})
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def set_function_prototype(port: int = DEFAULT_GHIDRA_PORT, function_address: str = "", prototype: str = "") -> str:
|
||||||
|
"""
|
||||||
|
Set a function's prototype.
|
||||||
|
"""
|
||||||
|
return safe_post(port, "set_function_prototype", {"function_address": function_address, "prototype": prototype})
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def set_local_variable_type(port: int = DEFAULT_GHIDRA_PORT, function_address: str = "", variable_name: str = "", new_type: str = "") -> str:
|
||||||
|
"""
|
||||||
|
Set a local variable's type.
|
||||||
|
"""
|
||||||
|
return safe_post(port, "set_local_variable_type", {"function_address": function_address, "variable_name": variable_name, "new_type": new_type})
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def list_variables(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100, search: str = "") -> list:
|
def list_variables(port: int = DEFAULT_GHIDRA_PORT, offset: int = 0, limit: int = 100, search: str = "") -> list:
|
||||||
"""List global variables with optional search"""
|
"""List global variables with optional search"""
|
||||||
@ -339,10 +487,6 @@ def retype_variable(port: int = DEFAULT_GHIDRA_PORT, function: str = "", name: s
|
|||||||
encoded_var = quote(name)
|
encoded_var = quote(name)
|
||||||
return safe_put(port, f"functions/{encoded_function}/variables/{encoded_var}", {"dataType": data_type})
|
return safe_put(port, f"functions/{encoded_function}/variables/{encoded_var}", {"dataType": data_type})
|
||||||
|
|
||||||
# Handle graceful shutdown
|
|
||||||
import signal
|
|
||||||
import os
|
|
||||||
|
|
||||||
def handle_sigint(signum, frame):
|
def handle_sigint(signum, frame):
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|
||||||
|
|||||||
23
pom.xml
23
pom.xml
@ -21,7 +21,14 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Ghidra JARs as system-scoped dependencies for runtime -->
|
<!-- JSON handling -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.googlecode.json-simple</groupId>
|
||||||
|
<artifactId>json-simple</artifactId>
|
||||||
|
<version>1.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Ghidra JARs as system-scoped dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ghidra</groupId>
|
<groupId>ghidra</groupId>
|
||||||
<artifactId>Generic</artifactId>
|
<artifactId>Generic</artifactId>
|
||||||
@ -72,13 +79,6 @@
|
|||||||
<systemPath>${ghidra.jar.location}/Base.jar</systemPath>
|
<systemPath>${ghidra.jar.location}/Base.jar</systemPath>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JSON Simple for JSON handling -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.googlecode.json-simple</groupId>
|
|
||||||
<artifactId>json-simple</artifactId>
|
|
||||||
<version>1.1.1</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Test dependencies -->
|
<!-- Test dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
@ -265,7 +265,14 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<failOnWarning>false</failOnWarning>
|
<failOnWarning>false</failOnWarning>
|
||||||
<ignoredUnusedDeclaredDependencies>
|
<ignoredUnusedDeclaredDependencies>
|
||||||
|
<ignoredUnusedDeclaredDependency>ghidra:Generic</ignoredUnusedDeclaredDependency>
|
||||||
|
<ignoredUnusedDeclaredDependency>ghidra:SoftwareModeling</ignoredUnusedDeclaredDependency>
|
||||||
|
<ignoredUnusedDeclaredDependency>ghidra:Project</ignoredUnusedDeclaredDependency>
|
||||||
<ignoredUnusedDeclaredDependency>ghidra:Docking</ignoredUnusedDeclaredDependency>
|
<ignoredUnusedDeclaredDependency>ghidra:Docking</ignoredUnusedDeclaredDependency>
|
||||||
|
<ignoredUnusedDeclaredDependency>ghidra:Decompiler</ignoredUnusedDeclaredDependency>
|
||||||
|
<ignoredUnusedDeclaredDependency>ghidra:Utility</ignoredUnusedDeclaredDependency>
|
||||||
|
<ignoredUnusedDeclaredDependency>ghidra:Base</ignoredUnusedDeclaredDependency>
|
||||||
|
<ignoredUnusedDeclaredDependency>junit:junit</ignoredUnusedDeclaredDependency>
|
||||||
</ignoredUnusedDeclaredDependencies>
|
</ignoredUnusedDeclaredDependencies>
|
||||||
<ignoredSystemDependencies>
|
<ignoredSystemDependencies>
|
||||||
<ignoredSystemDependency>ghidra:*</ignoredSystemDependency>
|
<ignoredSystemDependency>ghidra:*</ignoredSystemDependency>
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import ghidra.program.model.pcode.HighSymbol;
|
|||||||
import ghidra.program.model.pcode.VarnodeAST;
|
import ghidra.program.model.pcode.VarnodeAST;
|
||||||
import ghidra.program.model.pcode.HighFunction;
|
import ghidra.program.model.pcode.HighFunction;
|
||||||
import ghidra.program.model.pcode.HighFunctionDBUtil;
|
import ghidra.program.model.pcode.HighFunctionDBUtil;
|
||||||
|
import ghidra.program.model.pcode.LocalSymbolMap;
|
||||||
|
import ghidra.program.model.pcode.HighFunctionDBUtil.ReturnCommitOption;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
import ghidra.app.decompiler.DecompInterface;
|
import ghidra.app.decompiler.DecompInterface;
|
||||||
import ghidra.app.decompiler.DecompileResults;
|
import ghidra.app.decompiler.DecompileResults;
|
||||||
@ -46,6 +48,23 @@ import java.util.concurrent.atomic.*;
|
|||||||
// For JSON response handling
|
// For JSON response handling
|
||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
|
|
||||||
|
import ghidra.app.services.CodeViewerService;
|
||||||
|
import ghidra.app.util.PseudoDisassembler;
|
||||||
|
import ghidra.app.cmd.function.SetVariableNameCmd;
|
||||||
|
import ghidra.program.model.symbol.SourceType;
|
||||||
|
import ghidra.program.model.listing.LocalVariableImpl;
|
||||||
|
import ghidra.program.model.listing.ParameterImpl;
|
||||||
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
|
import ghidra.util.exception.InvalidInputException;
|
||||||
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import ghidra.program.model.pcode.Varnode;
|
||||||
|
import ghidra.program.model.data.PointerDataType;
|
||||||
|
import ghidra.program.model.data.Undefined1DataType;
|
||||||
|
import ghidra.program.model.listing.Variable;
|
||||||
|
import ghidra.app.decompiler.component.DecompilerUtils;
|
||||||
|
import ghidra.app.decompiler.ClangToken;
|
||||||
|
|
||||||
@PluginInfo(
|
@PluginInfo(
|
||||||
status = PluginStatus.RELEASED,
|
status = PluginStatus.RELEASED,
|
||||||
packageName = ghidra.app.DeveloperPluginPackage.NAME,
|
packageName = ghidra.app.DeveloperPluginPackage.NAME,
|
||||||
@ -421,7 +440,7 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||||||
|
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
for (Function f : program.getFunctionManager().getFunctions(true)) {
|
for (Function f : program.getFunctionManager().getFunctions(true)) {
|
||||||
names.add(f.getName());
|
names.add(f.getName() + " @ " + f.getEntryPoint());
|
||||||
}
|
}
|
||||||
return paginateList(names, offset, limit);
|
return paginateList(names, offset, limit);
|
||||||
}
|
}
|
||||||
@ -723,75 +742,128 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String renameVariable(String functionName, String oldName, String newName) {
|
private String renameVariable(String functionName, String oldName, String newName) {
|
||||||
if (oldName == null || oldName.isEmpty() || newName == null || newName.isEmpty()) {
|
|
||||||
return "Both old and new variable names are required";
|
|
||||||
}
|
|
||||||
|
|
||||||
Program program = getCurrentProgram();
|
Program program = getCurrentProgram();
|
||||||
if (program == null) return "No program loaded";
|
if (program == null) return "No program loaded";
|
||||||
|
|
||||||
AtomicReference<String> result = new AtomicReference<>("Variable rename failed");
|
DecompInterface decomp = new DecompInterface();
|
||||||
|
decomp.openProgram(program);
|
||||||
|
|
||||||
|
Function func = null;
|
||||||
|
for (Function f : program.getFunctionManager().getFunctions(true)) {
|
||||||
|
if (f.getName().equals(functionName)) {
|
||||||
|
func = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (func == null) {
|
||||||
|
return "Function not found";
|
||||||
|
}
|
||||||
|
|
||||||
|
DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
|
||||||
|
if (result == null || !result.decompileCompleted()) {
|
||||||
|
return "Decompilation failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
HighFunction highFunction = result.getHighFunction();
|
||||||
|
if (highFunction == null) {
|
||||||
|
return "Decompilation failed (no high function)";
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalSymbolMap localSymbolMap = highFunction.getLocalSymbolMap();
|
||||||
|
if (localSymbolMap == null) {
|
||||||
|
return "Decompilation failed (no local symbol map)";
|
||||||
|
}
|
||||||
|
|
||||||
|
HighSymbol highSymbol = null;
|
||||||
|
Iterator<HighSymbol> symbols = localSymbolMap.getSymbols();
|
||||||
|
while (symbols.hasNext()) {
|
||||||
|
HighSymbol symbol = symbols.next();
|
||||||
|
String symbolName = symbol.getName();
|
||||||
|
|
||||||
|
if (symbolName.equals(oldName)) {
|
||||||
|
highSymbol = symbol;
|
||||||
|
}
|
||||||
|
if (symbolName.equals(newName)) {
|
||||||
|
return "Error: A variable with name '" + newName + "' 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 {
|
try {
|
||||||
SwingUtilities.invokeAndWait(() -> {
|
SwingUtilities.invokeAndWait(() -> {
|
||||||
int tx = program.startTransaction("Rename variable via HTTP");
|
int tx = program.startTransaction("Rename variable");
|
||||||
try {
|
try {
|
||||||
Function function = findFunctionByName(program, functionName);
|
if (commitRequired) {
|
||||||
if (function == null) {
|
HighFunctionDBUtil.commitParamsToDatabase(highFunction, false,
|
||||||
result.set("Function not found: " + functionName);
|
ReturnCommitOption.NO_COMMIT, finalFunction.getSignatureSource());
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
HighFunctionDBUtil.updateDBVariable(
|
||||||
// Initialize decompiler
|
finalHighSymbol,
|
||||||
DecompInterface decomp = new DecompInterface();
|
newName,
|
||||||
decomp.openProgram(program);
|
null,
|
||||||
DecompileResults decompRes = decomp.decompileFunction(function, 30, new ConsoleTaskMonitor());
|
SourceType.USER_DEFINED
|
||||||
|
);
|
||||||
if (decompRes == null || !decompRes.decompileCompleted()) {
|
successFlag.set(true);
|
||||||
result.set("Failed to decompile function: " + functionName);
|
}
|
||||||
return;
|
catch (Exception e) {
|
||||||
}
|
Msg.error(this, "Failed to rename variable", e);
|
||||||
|
}
|
||||||
HighFunction highFunction = decompRes.getHighFunction();
|
finally {
|
||||||
if (highFunction == null) {
|
|
||||||
result.set("Failed to get high function");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the variable by name
|
|
||||||
HighSymbol targetSymbol = null;
|
|
||||||
Iterator<HighSymbol> symbolIter = highFunction.getLocalSymbolMap().getSymbols();
|
|
||||||
while (symbolIter.hasNext()) {
|
|
||||||
HighSymbol symbol = symbolIter.next();
|
|
||||||
if (symbol.getName().equals(oldName)) {
|
|
||||||
targetSymbol = symbol;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetSymbol == null) {
|
|
||||||
result.set("Variable not found: " + oldName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename the variable
|
|
||||||
HighFunctionDBUtil.updateDBVariable(targetSymbol, newName, targetSymbol.getDataType(),
|
|
||||||
SourceType.USER_DEFINED);
|
|
||||||
|
|
||||||
result.set("Variable renamed from '" + oldName + "' to '" + newName + "'");
|
|
||||||
} catch (Exception e) {
|
|
||||||
Msg.error(this, "Error renaming variable", e);
|
|
||||||
result.set("Error: " + e.getMessage());
|
|
||||||
} finally {
|
|
||||||
program.endTransaction(tx, true);
|
program.endTransaction(tx, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (InterruptedException | InvocationTargetException e) {
|
} catch (InterruptedException | InvocationTargetException e) {
|
||||||
Msg.error(this, "Failed to execute on Swing thread", e);
|
String errorMsg = "Failed to execute rename on Swing thread: " + e.getMessage();
|
||||||
result.set("Error: " + 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.get();
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String retypeVariable(String functionName, String varName, String dataTypeName) {
|
private String retypeVariable(String functionName, String varName, String dataTypeName) {
|
||||||
@ -830,12 +902,13 @@ public class GhydraMCPPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the variable by name
|
// Find the variable by name - must match exactly and be in current scope
|
||||||
HighSymbol targetSymbol = null;
|
HighSymbol targetSymbol = null;
|
||||||
Iterator<HighSymbol> symbolIter = highFunction.getLocalSymbolMap().getSymbols();
|
Iterator<HighSymbol> symbolIter = highFunction.getLocalSymbolMap().getSymbols();
|
||||||
while (symbolIter.hasNext()) {
|
while (symbolIter.hasNext()) {
|
||||||
HighSymbol symbol = symbolIter.next();
|
HighSymbol symbol = symbolIter.next();
|
||||||
if (symbol.getName().equals(varName)) {
|
if (symbol.getName().equals(varName) &&
|
||||||
|
symbol.getPCAddress().equals(function.getEntryPoint())) {
|
||||||
targetSymbol = symbol;
|
targetSymbol = symbol;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
GHIDRA_MODULE_NAME=GhydraMCP
|
Manifest-Version: 1.0
|
||||||
GHIDRA_MODULE_DESC=A multi-headed REST interface for Ghidra for use with MCP agents.
|
GHIDRA_MODULE_NAME: GhydraMCP
|
||||||
|
GHIDRA_MODULE_DESC: A multi-headed REST interface for Ghidra for use with MCP agents.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user