From cb913e6a91931c4678360fbf04e9da0adc164954 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Fri, 21 Jan 2022 12:42:49 -0500 Subject: [PATCH] GP-1653 Allow PrototypeModel aliases --- .../src/decompile/cpp/architecture.cc | 32 ++++- .../src/decompile/cpp/architecture.hh | 1 + .../Decompiler/src/decompile/cpp/fspec.hh | 1 + .../data/languages/compiler_spec.rxg | 7 + .../program/model/lang/BasicCompilerSpec.java | 114 ++++++++++------ .../program/model/lang/PrototypeModel.java | 124 +++++++++++++++++- 6 files changed, 234 insertions(+), 45 deletions(-) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc index 8392b9b360..e9c7344752 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc @@ -747,6 +747,7 @@ void Architecture::parseDynamicRule(const Element *el) /// This handles the \ and \ tags. It builds the /// ProtoModel object based on the tag and makes it available generally to the decompiler. /// \param el is the XML tag element +/// \return the new ProtoModel object ProtoModel *Architecture::parseProto(const Element *el) { @@ -1100,6 +1101,26 @@ void Architecture::parseAggressiveTrim(const Element *el) } } +/// Clone the named ProtoModel, attaching it to another name. +/// \param aliasName is the new name to assign +/// \param parentName is the name of the parent model +void Architecture::createModelAlias(const string &aliasName,const string &parentName) + +{ + map::const_iterator iter = protoModels.find(parentName); + if (iter == protoModels.end()) + throw LowlevelError("Requesting non-existent prototype model: "+parentName); + ProtoModel *model = (*iter).second; + if (model->isMerged()) + throw LowlevelError("Cannot make alias of merged model: "+parentName); + if (model->getAliasParent() != (const ProtoModel *)0) + throw LowlevelError("Cannot make alias of an alias: "+parentName); + iter = protoModels.find(aliasName); + if (iter != protoModels.end()) + throw LowlevelError("Duplicate ProtoModel name: "+aliasName); + protoModels[aliasName] = new ProtoModel(aliasName,*model); +} + /// This looks for the \ tag and and sets configuration /// parameters based on it. /// \param store is the document store holding the tag @@ -1215,6 +1236,12 @@ void Architecture::parseCompilerConfig(DocumentStorage &store) parseDeadcodeDelay(*iter); else if (elname == "inferptrbounds") parseInferPtrBounds(*iter); + else if (elname == "modelalias") { + const Element *el = *iter; + string aliasName = el->getAttributeValue("name"); + string parentName = el->getAttributeValue("parent"); + createModelAlias(aliasName, parentName); + } } el = store.getTag("specextensions"); // Look for any user-defined configuration document @@ -1224,7 +1251,7 @@ void Architecture::parseCompilerConfig(DocumentStorage &store) const string &elname( (*iter)->getName() ); if (elname == "prototype") parseProto(*iter); - else if (elname == "callfixup") { + else if (elname == "callfixup") { pcodeinjectlib->restoreXmlInject(archid+" : compiler spec", (*iter)->getAttributeValue("name"), InjectPayload::CALLFIXUP_TYPE, *iter); } @@ -1253,8 +1280,7 @@ void Architecture::parseCompilerConfig(DocumentStorage &store) // We must have a __thiscall calling convention map::iterator miter = protoModels.find("__thiscall"); if (miter == protoModels.end()) { // If __thiscall doesn't exist we clone it off of the default - ProtoModel *thismodel = new ProtoModel("__thiscall",*defaultfp); - protoModels["__thiscall"] = thismodel; + createModelAlias("__thiscall",defaultfp->getName()); } userops.setDefaults(this); initializeSegments(); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh index 53817b583e..1e42862ef0 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh @@ -258,6 +258,7 @@ protected: void fillinReadOnlyFromLoader(void); ///< Load info about read-only sections void initializeSegments(); ///< Set up segment resolvers void cacheAddrSpaceProperties(void); ///< Calculate some frequently used space properties and cache them + void createModelAlias(const string &aliasName,const string &parentName); ///< Create name alias for a ProtoModel void parseProcessorConfig(DocumentStorage &store); ///< Apply processor specific configuration void parseCompilerConfig(DocumentStorage &store); ///< Apply compiler specific configuration diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh index a44d9f07c9..f614c29085 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh @@ -672,6 +672,7 @@ public: virtual ~ProtoModel(void); ///< Destructor const string &getName(void) const { return name; } ///< Get the name of the prototype model Architecture *getArch(void) const { return glb; } ///< Get the owning Architecture + const ProtoModel *getAliasParent(void) const { return compatModel; } ///< Return \e model \b this is an alias of (or null) uint4 hasEffect(const Address &addr,int4 size) const; ///< Determine side-effect of \b this on the given memory range int4 getExtraPop(void) const { return extrapop; } ///< Get the stack-pointer \e extrapop for \b this model void setExtraPop(int4 ep) { extrapop = ep; } ///< Set the stack-pointer \e extrapop diff --git a/Ghidra/Framework/SoftwareModeling/data/languages/compiler_spec.rxg b/Ghidra/Framework/SoftwareModeling/data/languages/compiler_spec.rxg index b88a4de1c3..477dcd1194 100644 --- a/Ghidra/Framework/SoftwareModeling/data/languages/compiler_spec.rxg +++ b/Ghidra/Framework/SoftwareModeling/data/languages/compiler_spec.rxg @@ -263,6 +263,13 @@ + + + + + + + diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java index 2d6bffc6d2..274e7d9361 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java @@ -36,6 +36,7 @@ import ghidra.program.model.pcode.AddressXML; import ghidra.program.model.pcode.Varnode; import ghidra.util.Msg; import ghidra.util.SystemUtilities; +import ghidra.util.exception.DuplicateNameException; import ghidra.util.xml.SpecXmlUtils; import ghidra.xml.*; @@ -58,7 +59,6 @@ public class BasicCompilerSpec implements CompilerSpec { protected PrototypeModel evalCalledModel; // Default model used to evaluate a called function protected PrototypeModel[] allmodels; // All models protected PrototypeModel[] models; // All models excluding merge models - private boolean copiedThisModel; // true if __thiscall is copied from default model private Register stackPointer; // Register holding the stack pointer private AddressSpace stackSpace; private AddressSpace stackBaseSpace; @@ -88,9 +88,11 @@ public class BasicCompilerSpec implements CompilerSpec { * @throws XmlParseException for badly formed XML * @throws SAXException for syntax errors in the XML * @throws IOException for errors accessing the stream + * @throws DuplicateNameException if there exists more than one PrototypeModel with the same name */ public BasicCompilerSpec(CompilerSpecDescription description, SleighLanguage language, - InputStream stream) throws XmlParseException, SAXException, IOException { + InputStream stream) + throws XmlParseException, SAXException, IOException, DuplicateNameException { this.description = description; this.language = language; buildInjectLibrary(); @@ -140,7 +142,7 @@ public class BasicCompilerSpec implements CompilerSpec { } } } - catch (IOException | SAXException | XmlParseException e) { + catch (IOException | SAXException | XmlParseException | DuplicateNameException e) { parseException = e; } @@ -167,7 +169,6 @@ public class BasicCompilerSpec implements CompilerSpec { evalCalledModel = op2.evalCalledModel; defaultModel = op2.defaultModel; allmodels = op2.allmodels; - copiedThisModel = op2.copiedThisModel; globalSet = op2.globalSet; // May need to clone if \ tag becomes user extendable joinSpace = op2.joinSpace; // AddressSpace is immutable models = op2.models; @@ -219,7 +220,8 @@ public class BasicCompilerSpec implements CompilerSpec { return errHandler; } - private void initialize(String srcName, XmlPullParser parser) throws XmlParseException { + private void initialize(String srcName, XmlPullParser parser) + throws XmlParseException, DuplicateNameException { this.sourceName = srcName; spaceBases = null; extraRanges = null; @@ -236,7 +238,6 @@ public class BasicCompilerSpec implements CompilerSpec { funcPtrAlign = 0; deadCodeDelay = null; inferPtrBounds = null; - copiedThisModel = false; restoreXml(parser); } @@ -280,28 +281,6 @@ public class BasicCompilerSpec implements CompilerSpec { } } - private void addThisCallConventionIfMissing(List modelList, - String defaultName) { - if (defaultName == null) { - return; - } - boolean foundThisCall = false; - PrototypeModel defModel = null; - for (PrototypeModel model : modelList) { - if (CALLING_CONVENTION_thiscall.equals(model.name)) { - foundThisCall = true; - } - if (defaultName.equals(model.name)) { - defModel = model; - } - } - if (defModel != null && !foundThisCall) { - PrototypeModel thisModel = new PrototypeModel(CALLING_CONVENTION_thiscall, defModel); - modelList.add(thisModel); - copiedThisModel = true; - } - } - @Override public void applyContextSettings(DefaultProgramContext programContext) { for (ContextSetting cs : ctxsetting) { @@ -467,21 +446,28 @@ public class BasicCompilerSpec implements CompilerSpec { /** * Establish cross referencing to prototype models. - * All xrefs are regenerated from a single complete list of PrototypeModels + * All xrefs are regenerated from a single complete list of PrototypeModels. + * If there are PrototypeModels with duplicate names, return an example name. + * Return null otherwise * * @param modelList is the complete list of models * @param defaultName is the name to use for the default model (or null) * @param evalCurrent is the name to use for evaluating the current function (or null) * @param evalCalled is the name to use for evaluating called functions (or null) + * @return a PrototypeModel name that was duplicated or null */ - protected void modelXrefs(List modelList, String defaultName, + protected String modelXrefs(List modelList, String defaultName, String evalCurrent, String evalCalled) { + String foundDuplicate = null; buildModelArrays(modelList); callingConventionMap = new HashMap<>(); for (PrototypeModel model : models) { String name = model.getName(); if (name != null) { - callingConventionMap.put(name, model); + PrototypeModel previous = callingConventionMap.put(name, model); + if (previous != null) { + foundDuplicate = name; + } } } @@ -500,6 +486,7 @@ public class BasicCompilerSpec implements CompilerSpec { evalCalledModel = evalmodel; } } + return foundDuplicate; } @Override @@ -538,10 +525,6 @@ public class BasicCompilerSpec implements CompilerSpec { if (model == defaultModel) { continue; // Already emitted } - if (copiedThisModel && model.hasThisPointer() && - model.name.equals(CALLING_CONVENTION_thiscall)) { - continue; // Don't need to emit the copy - } model.saveXml(buffer, pcodeInject); } if (evalCurrentModel != null && evalCurrentModel != defaultModel) { @@ -571,10 +554,12 @@ public class BasicCompilerSpec implements CompilerSpec { * Initialize this object from an XML stream. A single \ tag is expected. * @param parser is the XML stream * @throws XmlParseException for badly formed XML + * @throws DuplicateNameException if we parse more than one PrototypeModel with the same name */ - private void restoreXml(XmlPullParser parser) throws XmlParseException { + private void restoreXml(XmlPullParser parser) throws XmlParseException, DuplicateNameException { List modelList = new ArrayList<>(); boolean seenDefault = false; + boolean seenThisCall = false; String defaultName = null; String evalCurrentPrototype = null; String evalCalledPrototype = null; @@ -617,12 +602,28 @@ public class BasicCompilerSpec implements CompilerSpec { defaultName = model.name; seenDefault = true; } + if (model.getName().equals(CALLING_CONVENTION_thiscall)) { + seenThisCall = true; + } } else if (name.equals("prototype")) { PrototypeModel model = addPrototypeModel(modelList, parser); if (defaultName == null) { defaultName = model.name; } + if (model.getName().equals(CALLING_CONVENTION_thiscall)) { + seenThisCall = true; + } + } + else if (name.equals("modelalias")) { + XmlElement el = parser.start(); + String aliasName = el.getAttribute("name"); + String parentName = el.getAttribute("parent"); + parser.end(el); + createModelAlias(aliasName, parentName, modelList); + if (aliasName.equals(CALLING_CONVENTION_thiscall)) { + seenThisCall = true; + } } else if (name.equals("resolveprototype")) { addPrototypeModel(modelList, parser); @@ -683,8 +684,14 @@ public class BasicCompilerSpec implements CompilerSpec { language.getDefaultSpace().getSize(), language.getDefaultSpace().getAddressableUnitSize(), AddressSpace.TYPE_STACK, 0); } - addThisCallConventionIfMissing(modelList, defaultName); - modelXrefs(modelList, defaultName, evalCurrentPrototype, evalCalledPrototype); + if (!seenThisCall) { + createModelAlias(CALLING_CONVENTION_thiscall, defaultName, modelList); + } + String dupName = + modelXrefs(modelList, defaultName, evalCurrentPrototype, evalCalledPrototype); + if (dupName != null) { + throw new DuplicateNameException("Multiple prototype models with the name: " + dupName); + } } private void saveProperties(StringBuilder buffer) { @@ -995,6 +1002,35 @@ public class BasicCompilerSpec implements CompilerSpec { // } // } + /** + * Clone the named PrototypeModel, attaching it to another name. + * @param aliasName is the new name + * @param parentName is the name of the PrototypeModel to clone + * @param modelList is the container + * @throws XmlParseException if the parent model cannot be established + */ + private void createModelAlias(String aliasName, String parentName, + List modelList) throws XmlParseException { + PrototypeModel parentModel = null; + for (PrototypeModel model : modelList) { + if (parentName.equals(model.getName())) { + parentModel = model; + break; + } + } + if (parentModel == null) { + throw new XmlParseException("Parent for model alias does not exist: " + parentName); + } + if (parentModel.isMerged()) { + throw new XmlParseException("Cannot make alias of merged model: " + parentName); + } + if (parentModel.getAliasParent() != null) { + throw new XmlParseException("Cannot make alias of an alias: " + parentName); + } + PrototypeModel newModel = new PrototypeModel(aliasName, parentModel); + modelList.add(newModel); + } + private PrototypeModel addPrototypeModel(List modelList, XmlPullParser parser) throws XmlParseException { PrototypeModel model; @@ -1116,7 +1152,7 @@ public class BasicCompilerSpec implements CompilerSpec { return false; } BasicCompilerSpec other = (BasicCompilerSpec) obj; - if (aggressiveTrim != other.aggressiveTrim || copiedThisModel != other.copiedThisModel) { + if (aggressiveTrim != other.aggressiveTrim) { return false; } if (!dataOrganization.isEquivalent(other.dataOrganization)) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModel.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModel.java index 766e09456e..de07c76f37 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModel.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PrototypeModel.java @@ -17,6 +17,7 @@ package ghidra.program.model.lang; import java.util.ArrayList; +import ghidra.program.database.SpecExtension; import ghidra.program.model.address.*; import ghidra.program.model.data.*; import ghidra.program.model.listing.*; @@ -31,9 +32,6 @@ import ghidra.xml.*; * A function calling convention model. * Formal specification of how a compiler passes * arguments between functions. - * - * - * */ public class PrototypeModel { public static final int UNKNOWN_EXTRAPOP = 0x8000; @@ -50,6 +48,7 @@ public class PrototypeModel { private Varnode[] killedbycall; // Memory ranges definitely affected by calls private Varnode[] returnaddress; // Memory used to store the return address private Varnode[] likelytrash; // Memory likely to be meaningless on input + private PrototypeModel compatModel; // The model this is an alias of private AddressSet localRange; // Range on the stack considered for local storage private AddressSet paramRange; // Range on the stack considered for parameter storage private InputListType inputListType = InputListType.STANDARD; @@ -59,6 +58,16 @@ public class PrototypeModel { private boolean hasUponEntry; // Does this have an uponentry injection private boolean hasUponReturn; // Does this have an uponreturn injection + /** + * Create a named alias of another PrototypeModel. + * All elements of the original model are copied except: + * 1) The name + * 2) The generic calling convention (which is based on name) + * 3) The hasThis property (which allows __thiscall to alias something else) + * 4) The "fact of" the model being an alias + * @param name is the name of the alias + * @param model is the other PrototypeModel + */ public PrototypeModel(String name, PrototypeModel model) { this.name = name; isExtension = false; @@ -71,6 +80,7 @@ public class PrototypeModel { killedbycall = model.killedbycall; returnaddress = model.returnaddress; likelytrash = model.likelytrash; + compatModel = model; localRange = new AddressSet(model.localRange); paramRange = new AddressSet(model.paramRange); hasThis = model.hasThis || name.equals(CompilerSpec.CALLING_CONVENTION_thiscall); @@ -91,6 +101,7 @@ public class PrototypeModel { killedbycall = null; returnaddress = null; likelytrash = null; + compatModel = null; localRange = null; paramRange = null; genericCallingConvention = GenericCallingConvention.unknown; @@ -100,6 +111,10 @@ public class PrototypeModel { hasUponReturn = false; } + /** + * Get the generic calling convention enum associated with this + * @return the enum + */ public GenericCallingConvention getGenericCallingConvention() { return genericCallingConvention; } @@ -144,6 +159,12 @@ public class PrototypeModel { return returnaddress; } + /** + * If this returns true, it indicates this model is an artificial merge of other models. + * A merged model can be used as part of the analysis process when attempting to distinguish + * between different possible models for an unknown function. + * @return true if this model is an artificial merge of other models + */ public boolean isMerged() { return false; } @@ -155,30 +176,58 @@ public class PrototypeModel { return isExtension; } + /** + * @return the formal name of the model + */ public String getName() { return name; } + /** + * Returns the number of extra bytes popped from the stack when a function that uses + * this model returns to its caller. This is usually just the number of bytes used to + * store the return value, but some conventions may do additional clean up of stack parameters. + * A special value of UNKNOWN_EXTRAPOP indicates that the number of bytes is unknown. + * @return the number of extra bytes popped + */ public int getExtrapop() { return extrapop; } + /** + * @return the number of bytes on the stack used, by this model, to store the return value + */ public int getStackshift() { return stackshift; } + /** + * @return true if this model has an implied "this" parameter for referencing class data + */ public boolean hasThisPointer() { return hasThis; } + /** + * @return true if this model is used specifically for class constructors + */ public boolean isConstructor() { return isConstruct; } + /** + * @return the allocation strategy for this model + */ public InputListType getInputListType() { return inputListType; } + /** + * Return true if this model has specific p-code injections associated with it + * (either an "uponentry" or "uponreturn" payload), + * which are used to decompile functions with this model. + * @return true if this model uses p-code injections + */ public boolean hasInjection() { return hasUponEntry || hasUponReturn; } @@ -334,6 +383,14 @@ public class PrototypeModel { return finalres; } + /** + * If this is an alias of another model, return that model. Otherwise null is returned. + * @return the parent model or null + */ + public PrototypeModel getAliasParent() { + return compatModel; + } + /** * If a PrototypeModel fails to parse (from XML) a substitute model may be provided, in which * case this method returns true. In all other cases this method returns false; @@ -359,7 +416,19 @@ public class PrototypeModel { } } + /** + * Marshal this object as XML to an output buffer + * @param buffer is the output buffer + * @param injectLibrary is a library containing any inject payloads associated with the model + */ public void saveXml(StringBuilder buffer, PcodeInjectLibrary injectLibrary) { + if (compatModel != null) { + buffer.append("\n"); + return; + } buffer.append("