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("
+