GP-4263 added Edit Signature Override action to decompiler (Help still needed)

This commit is contained in:
ghidra1 2024-01-31 16:11:19 -05:00
parent ad7694a7a9
commit a4f7bb24b9
8 changed files with 243 additions and 93 deletions

View file

@ -905,6 +905,9 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
OverridePrototypeAction overrideSigAction = new OverridePrototypeAction(); OverridePrototypeAction overrideSigAction = new OverridePrototypeAction();
setGroupInfo(overrideSigAction, functionGroup, subGroupPosition++); setGroupInfo(overrideSigAction, functionGroup, subGroupPosition++);
EditPrototypeOverrideAction editOverrideSigAction = new EditPrototypeOverrideAction();
setGroupInfo(editOverrideSigAction, functionGroup, subGroupPosition++);
DeletePrototypeOverrideAction deleteSigAction = new DeletePrototypeOverrideAction(); DeletePrototypeOverrideAction deleteSigAction = new DeletePrototypeOverrideAction();
setGroupInfo(deleteSigAction, functionGroup, subGroupPosition++); setGroupInfo(deleteSigAction, functionGroup, subGroupPosition++);
@ -1144,6 +1147,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
addLocalAction(editDataTypeAction); addLocalAction(editDataTypeAction);
addLocalAction(specifyCProtoAction); addLocalAction(specifyCProtoAction);
addLocalAction(overrideSigAction); addLocalAction(overrideSigAction);
addLocalAction(editOverrideSigAction);
addLocalAction(deleteSigAction); addLocalAction(deleteSigAction);
addLocalAction(renameFunctionAction); addLocalAction(renameFunctionAction);
addLocalAction(renameLabelAction); addLocalAction(renameLabelAction);

View file

@ -159,7 +159,7 @@ public abstract class AbstractDecompilerAction extends DockingAction {
} }
/** /**
* Get the function corresponding to the specified decompiler context. * Get the function corresponding to the specified decompiler context token.
* *
* @param context decompiler action context * @param context decompiler action context
* @return the function associated with the current context token or null if none identified. * @return the function associated with the current context token or null if none identified.

View file

@ -16,16 +16,13 @@
package ghidra.app.plugin.core.decompile.actions; package ghidra.app.plugin.core.decompile.actions;
import docking.action.MenuData; import docking.action.MenuData;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.plugin.core.decompile.DecompilerActionContext; import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.program.database.symbol.CodeSymbol;
import ghidra.program.model.data.ProgramBasedDataTypeManager;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighFunction; import ghidra.program.model.pcode.DataTypeSymbol;
import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.HighFunctionDBUtil;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.Symbol;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.UndefinedFunction; import ghidra.util.UndefinedFunction;
@ -37,38 +34,6 @@ public class DeletePrototypeOverrideAction extends AbstractDecompilerAction {
setPopupMenuData(new MenuData(new String[] { "Remove Signature Override" }, "Decompile")); setPopupMenuData(new MenuData(new String[] { "Remove Signature Override" }, "Decompile"));
} }
public static CodeSymbol getSymbol(Function func, ClangToken tokenAtCursor) {
if (tokenAtCursor == null) {
return null;
}
Namespace overspace = HighFunction.findOverrideSpace(func);
if (overspace == null) {
return null;
}
PcodeOp op = OverridePrototypeAction.getCallOp(func.getProgram(), tokenAtCursor);
if (op == null) {
return null;
}
SymbolTable symtab = func.getProgram().getSymbolTable();
SymbolIterator iter = symtab.getSymbolsAsIterator(op.getSeqnum().getTarget());
while (iter.hasNext()) {
Symbol sym = iter.next();
if (!sym.getName().startsWith("prt")) {
continue;
}
if (!(sym instanceof CodeSymbol)) {
continue;
}
if (!sym.getParentNamespace().equals(overspace)) {
continue;
}
return (CodeSymbol) sym;
}
return null;
}
@Override @Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
@ -77,22 +42,26 @@ public class DeletePrototypeOverrideAction extends AbstractDecompilerAction {
return false; return false;
} }
return getSymbol(function, context.getTokenAtCursor()) != null; return OverridePrototypeAction.getSymbol(function, context.getTokenAtCursor()) != null;
} }
@Override @Override
protected void decompilerActionPerformed(DecompilerActionContext context) { protected void decompilerActionPerformed(DecompilerActionContext context) {
Function func = context.getFunction(); Function func = context.getFunction();
CodeSymbol sym = getSymbol(func, context.getTokenAtCursor()); Symbol sym = OverridePrototypeAction.getSymbol(func, context.getTokenAtCursor());
if (sym == null) {
return;
}
Program program = func.getProgram(); Program program = func.getProgram();
SymbolTable symtab = program.getSymbolTable();
ProgramBasedDataTypeManager dtm = program.getDataTypeManager();
int txId = program.startTransaction("Remove Override Signature"); int txId = program.startTransaction("Remove Override Signature");
try { try {
symtab.removeSymbolSpecial(sym); DataTypeSymbol dts = HighFunctionDBUtil.readOverride(sym);
sym.delete();
dts.cleanupUnusedOverride();
} }
finally { finally {
program.endTransaction(txId, true); program.endTransaction(txId, true);
} }
} }
} }

View file

@ -0,0 +1,108 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.decompile.actions;
import docking.action.MenuData;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.util.HelpTopics;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.FunctionDefinition;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.*;
import ghidra.util.exception.DuplicateNameException;
public class EditPrototypeOverrideAction extends AbstractDecompilerAction {
public EditPrototypeOverrideAction() {
super("Edit Signature Override");
setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionEditOverride"));
setPopupMenuData(new MenuData(new String[] { "Edit Signature Override" }, "Decompile"));
}
@Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
Function function = context.getFunction();
if (function == null || function instanceof UndefinedFunction) {
return false;
}
Symbol sym = OverridePrototypeAction.getSymbol(function, context.getTokenAtCursor());
if (sym == null) {
return false;
}
return HighFunctionDBUtil.readOverride(sym) != null;
}
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
Function function = context.getFunction();
Symbol sym = OverridePrototypeAction.getSymbol(function, context.getTokenAtCursor());
if (sym == null) {
return;
}
DataTypeSymbol dts = HighFunctionDBUtil.readOverride(sym);
if (dts == null) {
return;
}
Function func = context.getFunction();
Program program = func.getProgram();
PcodeOp op = OverridePrototypeAction.getCallOp(program, context.getTokenAtCursor());
Function calledFunc = null;
if (op != null) {
calledFunc = OverridePrototypeAction.getCalledFunction(program, op);
}
FunctionDefinition updatedFuncDef = null;
try {
// Copy is used for edit so we can adjust name
FunctionDefinition funcDef =
(FunctionDefinition) dts.getDataType().copy(program.getDataTypeManager());
funcDef.setName(calledFunc != null ? calledFunc.getName() : "func");
updatedFuncDef = OverridePrototypeAction.editSignature(context, calledFunc,
funcDef.getPrototypeString());
if (updatedFuncDef == null) {
return;
}
// TODO: should use comparison to see if funcDef was changed.
// Should be able to use equals method after fixing category and name, however
// it does not check param names.
}
catch (InvalidNameException | DuplicateNameException e) {
Msg.error(this, "Unexpected error", e);
}
int transaction = program.startTransaction("Override Signature");
try {
Address addr = sym.getAddress();
sym.delete(); // delete old marker symbol
HighFunctionDBUtil.writeOverride(func, addr, updatedFuncDef);
dts.cleanupUnusedOverride();
}
catch (Exception e) {
Msg.showError(getClass(), context.getDecompilerPanel(), "Override Signature Failed",
"Error overriding signature: " + e);
}
finally {
program.endTransaction(transaction, true);
}
}
}

View file

@ -28,8 +28,7 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.*; import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.*;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException; import ghidra.util.exception.InvalidInputException;
@ -48,7 +47,7 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
* @param tokenAtCursor is the point in the window the user has selected * @param tokenAtCursor is the point in the window the user has selected
* @return the PcodeOp or null * @return the PcodeOp or null
*/ */
public static PcodeOp getCallOp(Program program, ClangToken tokenAtCursor) { static PcodeOp getCallOp(Program program, ClangToken tokenAtCursor) {
if (tokenAtCursor == null) { if (tokenAtCursor == null) {
return null; return null;
} }
@ -76,6 +75,38 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
return null; return null;
} }
static Symbol getSymbol(Function func, ClangToken tokenAtCursor) {
if (tokenAtCursor == null) {
return null;
}
Namespace overspace = HighFunction.findOverrideSpace(func);
if (overspace == null) {
return null;
}
PcodeOp op = getCallOp(func.getProgram(), tokenAtCursor);
if (op == null) {
return null;
}
SymbolTable symtab = func.getProgram().getSymbolTable();
SymbolIterator iter = symtab.getSymbolsAsIterator(op.getSeqnum().getTarget());
while (iter.hasNext()) {
Symbol sym = iter.next();
if (sym.getSymbolType() != SymbolType.LABEL) {
continue;
}
if (!sym.getParentNamespace().equals(overspace)) {
continue;
}
if (!sym.getName().startsWith("prt")) {
continue;
}
return sym;
}
return null;
}
private static PcodeOp getOpForAddress(Program program, Address addr, ClangToken token) { private static PcodeOp getOpForAddress(Program program, Address addr, ClangToken token) {
ClangFunction cfunc = token.getClangFunction(); ClangFunction cfunc = token.getClangFunction();
@ -114,7 +145,7 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
return opCode == PcodeOp.CALL || opCode == PcodeOp.CALLIND; return opCode == PcodeOp.CALL || opCode == PcodeOp.CALLIND;
} }
private Function getCalledFunction(Program program, PcodeOp op) { static Function getCalledFunction(Program program, PcodeOp op) {
if (op.getOpcode() != PcodeOp.CALL) { if (op.getOpcode() != PcodeOp.CALL) {
return null; return null;
} }
@ -226,8 +257,7 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
} }
// don't enable if override already in place // don't enable if override already in place
return DeletePrototypeOverrideAction.getSymbol(context.getFunction(), return getSymbol(context.getFunction(), context.getTokenAtCursor()) == null;
context.getTokenAtCursor()) == null;
} }
@Override @Override
@ -235,10 +265,10 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
Function func = context.getFunction(); Function func = context.getFunction();
Program program = func.getProgram(); Program program = func.getProgram();
PcodeOp op = getCallOp(program, context.getTokenAtCursor()); PcodeOp op = getCallOp(program, context.getTokenAtCursor());
Function calledfunc = getCalledFunction(program, op); Function calledFunc = getCalledFunction(program, op);
boolean varargs = false; boolean varargs = false;
if (calledfunc != null) { if (calledFunc != null) {
varargs = calledfunc.hasVarArgs(); varargs = calledFunc.hasVarArgs();
} }
if ((op.getOpcode() == PcodeOp.CALL) && !varargs) { if ((op.getOpcode() == PcodeOp.CALL) && !varargs) {
if (OptionDialog.showOptionDialog(context.getDecompilerPanel(), if (OptionDialog.showOptionDialog(context.getDecompilerPanel(),
@ -250,26 +280,21 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
return; return;
} }
} }
Address addr = op.getSeqnum().getTarget();
String name = "func"; // Default if we don't have a real name
String conv = program.getCompilerSpec().getDefaultCallingConvention().getName();
if (calledfunc != null) {
name = calledfunc.getName();
conv = calledfunc.getCallingConventionName();
}
String signature = generateSignature(op, name, calledfunc); String name = "func"; // Default if we don't have a real name
PluginTool tool = context.getTool(); if (calledFunc != null) {
ProtoOverrideDialog dialog = name = calledFunc.getName();
new ProtoOverrideDialog(tool, calledfunc != null ? calledfunc : func, signature, conv); }
tool.showDialog(dialog); String signature = generateSignature(op, name, calledFunc);
FunctionDefinition fdef = dialog.getFunctionDefinition();
FunctionDefinition fdef = editSignature(context, calledFunc, signature);
if (fdef == null) { if (fdef == null) {
return; return;
} }
int transaction = program.startTransaction("Override Signature"); int transaction = program.startTransaction("Override Signature");
boolean commit = false; boolean commit = false;
try { try {
Address addr = op.getSeqnum().getTarget();
HighFunctionDBUtil.writeOverride(func, addr, fdef); HighFunctionDBUtil.writeOverride(func, addr, fdef);
commit = true; commit = true;
} }
@ -282,13 +307,30 @@ public class OverridePrototypeAction extends AbstractDecompilerAction {
} }
} }
static FunctionDefinition editSignature(DecompilerActionContext context, Function calledFunc,
String signature) {
Function func = context.getFunction();
Program program = func.getProgram();
PluginTool tool = context.getTool();
String conv = program.getCompilerSpec().getDefaultCallingConvention().getName();
if (calledFunc != null) {
conv = calledFunc.getCallingConventionName();
}
ProtoOverrideDialog dialog =
new ProtoOverrideDialog(tool, calledFunc != null ? calledFunc : func, signature, conv);
tool.showDialog(dialog);
return dialog.getFunctionDefinition();
}
/** /**
* <code>ProtoOverrideDialog</code> provides the ability to edit the * <code>ProtoOverrideDialog</code> provides the ability to edit the
* function signature associated with a specific function definition override * function signature associated with a specific function definition override
* at a sub-function callsite. * at a sub-function callsite.
* Use of this editor requires the presence of the tool-based datatype manager service. * Use of this editor requires the presence of the tool-based datatype manager service.
*/ */
private class ProtoOverrideDialog extends EditFunctionSignatureDialog { private static class ProtoOverrideDialog extends EditFunctionSignatureDialog {
private FunctionDefinition functionDefinition; private FunctionDefinition functionDefinition;
private final String initialSignature; private final String initialSignature;
private final String initialConvention; private final String initialConvention;

View file

@ -139,34 +139,23 @@ public class SpecifyCPrototypeAction extends AbstractDecompilerAction {
return fsig; return fsig;
} }
/**
* Get function affected by specified action context
*
* @param function is the current decompiled function which will be the default if no other
* function identified by context token.
* @param context decompiler action context
* @return the function associated with the current context token. If no function corresponds
* to context token the decompiled function will be returned.
*/
private Function getFunction(Function function, DecompilerActionContext context) {
// try to look up the function that is at the current cursor location
// If there isn't one, just use the function we are in.
Function tokenFunction = getFunction(context);
return tokenFunction != null ? tokenFunction : function;
}
@Override @Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) { protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
Function function = context.getFunction(); Function decompiledFunction = context.getFunction();
if (function instanceof UndefinedFunction) { Function func = getFunction(context);
if (func == null || (func instanceof UndefinedFunction)) {
return false; return false;
} }
return getFunction(function, context) != null; if (func != decompiledFunction && OverridePrototypeAction.getSymbol(decompiledFunction,
context.getTokenAtCursor()) != null) {
return false; // disable action for sub-function call w/ override
}
return true;
} }
@Override @Override
protected void decompilerActionPerformed(DecompilerActionContext context) { protected void decompilerActionPerformed(DecompilerActionContext context) {
Function function = getFunction(context.getFunction(), context); Function function = getFunction(context);
PluginTool tool = context.getTool(); PluginTool tool = context.getTool();
DataTypeManagerService service = tool.getService(DataTypeManagerService.class); DataTypeManagerService service = tool.getService(DataTypeManagerService.class);

View file

@ -21,10 +21,12 @@ import generic.hash.SimpleCRC32;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.listing.FunctionSignature; import ghidra.program.model.listing.FunctionSignature;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException; import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
public class DataTypeSymbol { public class DataTypeSymbol {
private Symbol sym; // Traditional symbol object private Symbol sym; // Traditional symbol object
@ -105,14 +107,12 @@ public class DataTypeSymbol {
public static void deleteSymbols(String nmroot, Address addr, SymbolTable symtab, public static void deleteSymbols(String nmroot, Address addr, SymbolTable symtab,
Namespace space) throws InvalidInputException { Namespace space) throws InvalidInputException {
ArrayList<Symbol> dellist = new ArrayList<Symbol>(); ArrayList<Symbol> dellist = new ArrayList<Symbol>();
SymbolIterator iter = symtab.getSymbols(space); for (Symbol sym : symtab.getSymbols(addr)) {
while (iter.hasNext()) {
Symbol sym = iter.next();
if (!sym.getName().startsWith(nmroot)) if (!sym.getName().startsWith(nmroot))
continue; continue;
if (sym.getSymbolType() != SymbolType.LABEL) if (sym.getSymbolType() != SymbolType.LABEL)
continue; continue;
if (!addr.equals(sym.getAddress())) if (space.equals(sym.getParentNamespace()))
continue; continue;
if (sym.hasReferences()) if (sym.hasReferences())
throw new InvalidInputException("DataTypeSymbol has a reference"); throw new InvalidInputException("DataTypeSymbol has a reference");
@ -123,6 +123,34 @@ public class DataTypeSymbol {
} }
} }
public void cleanupUnusedOverride() {
if (sym == null) {
throw new RuntimeException("not instantiated with readSymbol method");
}
// NOTE: Although the symbol may have just been deleted its name will still be
// be accesible within its retained DB record.
String overrideName = sym.getName(); // override marker symbol
Program program = sym.getProgram();
SymbolTable symbolTable = program.getSymbolTable();
String prefix = nmroot + "_";
String hashSuffix = "_" + extractHash(overrideName);
for (Symbol s : symbolTable.scanSymbolsByName(prefix)) {
String n = s.getName();
if (!n.startsWith(prefix)) {
break; // stop scan
}
if (s.getSymbolType() == SymbolType.LABEL && n.endsWith(hashSuffix) &&
HighFunction.isOverrideNamespace(s.getParentNamespace())) {
return; // do nothing if any symbol found
}
}
// remove unused override signature
program.getDataTypeManager().remove(getDataType(), TaskMonitor.DUMMY);
}
public static DataTypeSymbol readSymbol(String cat, Symbol s) { public static DataTypeSymbol readSymbol(String cat, Symbol s) {
if (s.getSymbolType() != SymbolType.LABEL) { if (s.getSymbolType() != SymbolType.LABEL) {
throw new IllegalArgumentException("Expected CODE symbol"); throw new IllegalArgumentException("Expected CODE symbol");

View file

@ -40,6 +40,8 @@ import ghidra.util.exception.InvalidInputException;
*/ */
public class HighFunction extends PcodeSyntaxTree { public class HighFunction extends PcodeSyntaxTree {
public final static String DECOMPILER_TAG_MAP = "decompiler_tags"; public final static String DECOMPILER_TAG_MAP = "decompiler_tags";
public final static String OVERRIDE_NAMESPACE_NAME = "override";
private Function func; // The traditional function object private Function func; // The traditional function object
private Language language; private Language language;
private CompilerSpec compilerSpec; private CompilerSpec compilerSpec;
@ -485,14 +487,22 @@ public class HighFunction extends PcodeSyntaxTree {
} }
} }
public static boolean isOverrideNamespace(Namespace namespace) {
if (!OVERRIDE_NAMESPACE_NAME.equals(namespace.getName())) {
return false;
}
Namespace parent = namespace.getParentNamespace();
return (parent instanceof Function);
}
public static Namespace findOverrideSpace(Function func) { public static Namespace findOverrideSpace(Function func) {
SymbolTable symtab = func.getProgram().getSymbolTable(); SymbolTable symtab = func.getProgram().getSymbolTable();
return findNamespace(symtab, func, "override"); return findNamespace(symtab, func, OVERRIDE_NAMESPACE_NAME);
} }
public static Namespace findCreateOverrideSpace(Function func) { public static Namespace findCreateOverrideSpace(Function func) {
SymbolTable symtab = func.getProgram().getSymbolTable(); SymbolTable symtab = func.getProgram().getSymbolTable();
return findCreateNamespace(symtab, func, "override"); return findCreateNamespace(symtab, func, OVERRIDE_NAMESPACE_NAME);
} }
public static Namespace findNamespace(SymbolTable symtab, Namespace parent, String name) { public static Namespace findNamespace(SymbolTable symtab, Namespace parent, String name) {