SymbolManager.java•13.1 kB
package com.ghidramcp.services;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.DecompileResults;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.*;
import ghidra.program.model.pcode.HighFunctionDBUtil.ReturnCommitOption;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.task.ConsoleTaskMonitor;
import javax.swing.SwingUtilities;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Service for renaming operations (functions, data, variables)
*/
public class SymbolManager {
private final FunctionNavigator navigator;
private final int decompileTimeout;
public SymbolManager(FunctionNavigator navigator, int decompileTimeout) {
this.navigator = navigator;
this.decompileTimeout = decompileTimeout;
}
/**
* Rename a function by old and new names
* @param oldName Current function name
* @param newName New function name
* @return true if successful
*/
public boolean renameFunction(String oldName, String newName) {
if (navigator == null) return false;
Program program = navigator.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)) {
// Always use applyNamespaceAndName to handle namespace correctly
applyNamespaceAndName(program, func, newName);
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();
}
/**
* Rename a function by address
* @param functionAddrStr Function address as string
* @param newName New function name
* @return true if successful
*/
public boolean renameFunctionByAddress(String functionAddrStr, String newName) {
if (navigator == null) return false;
Program program = navigator.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();
}
/**
* Rename data at a given address
* @param addressStr Address as string
* @param newName New name for the data
* @return true if successful
*/
public boolean renameDataAtAddress(String addressStr, String newName) {
if (navigator == null) return false;
Program program = navigator.getCurrentProgram();
if (program == null) return false;
AtomicBoolean success = new AtomicBoolean(false);
try {
SwingUtilities.invokeAndWait(() -> {
int tx = program.startTransaction("Rename data");
try {
Address addr = program.getAddressFactory().getAddress(addressStr);
if (addr == null) {
Msg.error(this, "Invalid address: " + addressStr);
return;
}
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);
}
success.set(true);
} else {
Msg.error(this, "No data found at address: " + addressStr);
}
}
catch (Exception e) {
Msg.error(this, "Rename data error", e);
}
finally {
program.endTransaction(tx, success.get());
}
});
}
catch (InterruptedException | InvocationTargetException e) {
Msg.error(this, "Failed to execute rename data on Swing thread", e);
}
return success.get();
}
/**
* Rename a variable within a function
* @param functionName Function name
* @param oldVarName Current variable name
* @param newVarName New variable name
* @return Status message
*/
public String renameVariableInFunction(String functionName, String oldVarName, String newVarName) {
if (navigator == null) return "No program loaded";
Program program = navigator.getCurrentProgram();
if (program == null) return "No program loaded";
DecompInterface decomp = new DecompInterface();
DecompileOptions options = new DecompileOptions();
decomp.setOptions(options);
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());
decomp.flushCache();
if (result == null || !result.decompileCompleted()) {
return "Decompilation failed";
}
HighFunction highFunction = result.getHighFunction();
if (highFunction == null) {
return "Decompilation failed (no high function)";
}
LocalSymbolMap localSymbolMap = highFunction.getLocalSymbolMap();
if (localSymbolMap == null) {
return "Decompilation failed (no local symbol map)";
}
HighSymbol highSymbol = null;
Iterator<HighSymbol> symbols = localSymbolMap.getSymbols();
while (symbols.hasNext()) {
HighSymbol symbol = symbols.next();
String symbolName = symbol.getName();
if (symbolName.equals(oldVarName)) {
highSymbol = symbol;
}
if (symbolName.equals(newVarName)) {
return "Error: A variable with name '" + newVarName + "' already exists in this function";
}
}
if (highSymbol == null) {
return "Variable not found";
}
boolean commitRequired = checkFullCommit(highSymbol, highFunction);
final HighSymbol finalHighSymbol = highSymbol;
final Function finalFunction = func;
AtomicBoolean successFlag = new AtomicBoolean(false);
try {
SwingUtilities.invokeAndWait(() -> {
int tx = program.startTransaction("Rename variable");
try {
if (commitRequired) {
HighFunctionDBUtil.commitParamsToDatabase(highFunction, false,
ReturnCommitOption.NO_COMMIT, finalFunction.getSignatureSource());
}
HighFunctionDBUtil.updateDBVariable(
finalHighSymbol,
newVarName,
null,
SourceType.USER_DEFINED
);
successFlag.set(true);
}
catch (Exception e) {
Msg.error(this, "Failed to rename variable", e);
}
finally {
successFlag.set(program.endTransaction(tx, true));
}
});
} catch (InterruptedException | InvocationTargetException e) {
String errorMsg = "Failed to execute rename on Swing thread: " + e.getMessage();
Msg.error(this, errorMsg, e);
return errorMsg;
}
return successFlag.get() ? "Variable renamed" : "Failed to rename variable";
}
/**
* Helper method to perform function rename in 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 = navigator.getFunctionForAddress(program, addr);
if (func == null) {
Msg.error(this, "Could not find function at address: " + functionAddrStr);
return;
}
// Always use applyNamespaceAndName to handle namespace correctly
applyNamespaceAndName(program, func, newName);
success.set(true);
} catch (Exception e) {
Msg.error(this, "Error renaming function by address", e);
} finally {
program.endTransaction(tx, success.get());
}
}
/**
* Apply namespace and name to a function when the name contains :: notation
* @param program The program
* @param func The function to rename
* @param fullName The full name including namespace (e.g., "A::B" or "A::B::C")
*/
private void applyNamespaceAndName(Program program, Function func, String fullName) throws Exception {
SymbolTable symbolTable = program.getSymbolTable();
// Split by :: to separate namespace path from function name
String[] parts = fullName.split("::");
if (parts.length < 2) {
// No namespace qualifier, move to global namespace
Symbol funcSymbol = func.getSymbol();
funcSymbol.setNamespace(program.getGlobalNamespace());
funcSymbol.setName(fullName, SourceType.USER_DEFINED);
return;
}
// Last part is the function name, everything before is namespace path
String functionName = parts[parts.length - 1];
// Build the namespace hierarchy
Namespace currentNamespace = program.getGlobalNamespace();
for (int i = 0; i < parts.length - 1; i++) {
currentNamespace = symbolTable.getOrCreateNameSpace(
currentNamespace,
parts[i],
SourceType.USER_DEFINED
);
}
// Get the function's symbol and update it
Symbol funcSymbol = func.getSymbol();
funcSymbol.setNamespace(currentNamespace);
funcSymbol.setName(functionName, SourceType.USER_DEFINED);
}
/**
* Check if a full commit is required when renaming variables
*/
public 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;
}
}