diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/FunctionPurgeAnalysisCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/FunctionPurgeAnalysisCmd.java index 605a427fcd..a16f5847f3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/FunctionPurgeAnalysisCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/FunctionPurgeAnalysisCmd.java @@ -20,12 +20,14 @@ import ghidra.framework.model.DomainObject; import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.lang.Processor; +import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.scalar.Scalar; -import ghidra.program.model.symbol.FlowType; -import ghidra.program.model.symbol.Reference; +import ghidra.program.model.symbol.*; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; +import ghidra.util.exception.InvalidInputException; import ghidra.util.task.TaskMonitor; /** @@ -34,13 +36,17 @@ import ghidra.util.task.TaskMonitor; public class FunctionPurgeAnalysisCmd extends BackgroundCommand { private AddressSetView entryPoints; private Program program; + private PrototypeModel[] nearFarModels = null; + + private static final int STDCALL_FAR = 0; + private static final int CDECL_FAR = 1; + private static final int STDCALL_NEAR = 2; + private static final int CDECL_NEAR = 3; /** * Constructs a new command for analyzing the Stack. * @param entries and address set indicating the entry points of functions that have * stacks to be analyzed. - * @param forceProcessing flag to force processing of stack references even if the stack - * has already been defined. */ public FunctionPurgeAnalysisCmd(AddressSetView entries) { super("Compute Function Purge", true, true, false); @@ -56,12 +62,16 @@ public class FunctionPurgeAnalysisCmd extends BackgroundCommand { program = (Program) obj; Processor processor = program.getLanguage().getProcessor(); - if (program.getLanguage().getDefaultSpace().getSize() > 32 || + int processorSize = program.getLanguage().getDefaultSpace().getSize(); + if (processorSize > 32 || !processor.equals(Processor.findOrPossiblyCreateProcessor("x86"))) { Msg.error(this, "Unsupported operation for language " + program.getLanguage().getLanguageID()); return false; } + if (processorSize < 32) { // For 16-bit x86, prepare to establish near/fear calling convention models + setupNearFarModels(); + } AddressSetView set = entryPoints; @@ -95,6 +105,52 @@ public class FunctionPurgeAnalysisCmd extends BackgroundCommand { return true; } + /** + * For x86 16-bit find the models stdcallnear, stdcallfar, cdeclnear, and cdeclfar so they can + * be applied at the same time function purge is set. + */ + private void setupNearFarModels() { + int countModels = 0; + nearFarModels = new PrototypeModel[4]; + nearFarModels[0] = null; + nearFarModels[1] = null; + nearFarModels[2] = null; + nearFarModels[3] = null; + PrototypeModel[] models = program.getCompilerSpec().getCallingConventions(); + for (PrototypeModel model : models) { + if (model.isMerged()) { + continue; + } + int pos = -1; + if (model.getStackshift() == 4) { + if (model.getExtrapop() == PrototypeModel.UNKNOWN_EXTRAPOP) { + pos = STDCALL_FAR; + } + else if (model.getExtrapop() == 4) { + pos = CDECL_FAR; + } + } + else if (model.getStackshift() == 2) { + if (model.getExtrapop() == PrototypeModel.UNKNOWN_EXTRAPOP) { + pos = STDCALL_NEAR; + } + else if (model.getExtrapop() == 2) { + pos = CDECL_NEAR; + } + } + if (pos >= 0) { + if (nearFarModels[pos] == null) { + nearFarModels[pos] = model; + countModels += 1; + } + } + } + if (countModels < 4) { + Msg.warn(this, + "FunctionPurgeAnalysis is missing full range of near/far prototype models"); + } + } + /** * Analyze a function to build a stack frame based on stack references. * @param function function to be analyzed @@ -104,39 +160,120 @@ public class FunctionPurgeAnalysisCmd extends BackgroundCommand { */ private void analyzeFunction(Function function, TaskMonitor monitor) throws CancelledException { - int purge = -1; - - if (function != null) { - purge = function.getStackPurgeSize(); + if (function == null) { + return; } + int purge = function.getStackPurgeSize(); if (purge == -1 || purge > 128 || purge < -128) { - purge = locatePurgeReturn(program, function, monitor); - // if couldn't find it, don't set it! - if (purge != -1) { - function.setStackPurgeSize(purge); + Instruction purgeInstruction = locatePurgeInstruction(function, monitor); + if (purgeInstruction != null) { + purge = getPurgeValue(purgeInstruction); + // if couldn't find it, don't set it! + if (purge != -1) { + function.setStackPurgeSize(purge); + } + setPrototypeModel(function, purgeInstruction); } } } - private int locatePurgeReturn(Program program, Function func, TaskMonitor monitor) { - AddressSetView body = func.getBody(); + private void setPrototypeModel(Function function, Instruction purgeInstruction) { + if (nearFarModels == null) { + return; + } + if (purgeInstruction.getFlowType().isCall()) { + return; + } + if (function.getSignatureSource() != SourceType.DEFAULT) { + return; + } + PrototypeModel model = null; + try { + byte val = purgeInstruction.getBytes()[0]; + if (val == (byte) 0xc3) { + model = nearFarModels[CDECL_NEAR]; + } + else if (val == (byte) 0xcb) { + model = nearFarModels[CDECL_FAR]; + } + else if (val == (byte) 0xc2) { + model = nearFarModels[STDCALL_NEAR]; + } + else if (val == (byte) 0xca) { + model = nearFarModels[STDCALL_FAR]; + } + } + catch (MemoryAccessException e) { + return; + } + if (model == null) { + return; + } + try { + function.setCallingConvention(model.getName()); + } + catch (InvalidInputException e) { + // Ignore if we can't change it + } + } - int returnPurge = findReturnPurge(program, body); - if (returnPurge != -1) { - return returnPurge; + private Instruction locatePurgeInstruction(Function func, TaskMonitor monitor) { + AddressSetView body = func.getBody(); + Instruction purgeInstruction; + + purgeInstruction = findPurgeInstruction(body); + if (purgeInstruction != null) { + return purgeInstruction; } // look harder, maybe something wrong with body, compute with flow. body = CreateFunctionCmd.getFunctionBody(program, func.getEntryPoint(), monitor); - returnPurge = findReturnPurge(program, body); - - return returnPurge; + return findPurgeInstruction(body); } - private int findReturnPurge(Program program, AddressSetView body) { - int tempPurge; + /** + * Given a terminating instruction, discover the purge value encoded in it + * @param instr is the terminating instruction + * @return the purge value (or -1 if a value can't be found) + */ + private int getPurgeValue(Instruction instr) { + if (instr.getFlowType().isCall()) { + // is an override call-return, terminal/call + // find a reference to a function, and take it's purge + Reference[] referencesFrom = instr.getReferencesFrom(); + for (Reference reference : referencesFrom) { + if (reference.getReferenceType().isFlow()) { + Function functionAt = + program.getFunctionManager().getFunctionAt(reference.getToAddress()); + // don't take the purge of a non-returning function + if (functionAt != null && !functionAt.hasNoReturn()) { + return functionAt.getStackPurgeSize(); + } + } + } + } + else { + int tempPurge = 0; + Scalar scalar = instr.getScalar(0); + if (scalar != null) { + tempPurge = (int) scalar.getSignedValue(); + } + return tempPurge; + } + return -1; + } + + /** + * Find a terminating instruction in the given set of addresses with a purge encoded in it. + * This routine prefers a RET instruction, but if none is available, it will use a + * terminating CALL. + * @param body is the set of addresses to look through + * @return a terminating instruction or null + */ + private Instruction findPurgeInstruction(AddressSetView body) { InstructionIterator iter = program.getListing().getInstructions(body, true); int count = 2048; + Instruction backupPurge = null; while (iter.hasNext() && count > 0) { count--; Instruction instr = iter.next(); @@ -144,33 +281,15 @@ public class FunctionPurgeAnalysisCmd extends BackgroundCommand { FlowType ftype = instr.getFlowType(); if (ftype.isTerminal()) { if (instr.getMnemonicString().compareToIgnoreCase("ret") == 0) { - tempPurge = 0; - Scalar scalar = instr.getScalar(0); - if (scalar != null) { - tempPurge = (int) scalar.getSignedValue(); - return tempPurge; - } - return 0; + return instr; } else if (ftype.isCall()) { - // is an override call-return, terminal/call - // find a reference to a function, and take it's purge - Reference[] referencesFrom = instr.getReferencesFrom(); - for (Reference reference : referencesFrom) { - if (reference.getReferenceType().isFlow()) { - Function functionAt = program.getFunctionManager().getFunctionAt( - reference.getToAddress()); - // don't take the purge of a non-returning function - if (functionAt != null && !functionAt.hasNoReturn()) { - return functionAt.getStackPurgeSize(); - } - } - } + backupPurge = instr; // Use as last resort, if we can't find RET } } } - return -1; + return backupPurge; } } diff --git a/Ghidra/Processors/x86/data/languages/x86-16.cspec b/Ghidra/Processors/x86/data/languages/x86-16.cspec index e8ada927dd..88168e3bb7 100644 --- a/Ghidra/Processors/x86/data/languages/x86-16.cspec +++ b/Ghidra/Processors/x86/data/languages/x86-16.cspec @@ -93,7 +93,7 @@ - + @@ -119,7 +119,7 @@ - +