GP-1653 Allow PrototypeModel aliases

This commit is contained in:
caheckman 2022-01-21 12:42:49 -05:00
parent 22a5950d08
commit cb913e6a91
6 changed files with 234 additions and 45 deletions

View File

@ -747,6 +747,7 @@ void Architecture::parseDynamicRule(const Element *el)
/// This handles the \<prototype> and \<resolveprototype> 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<string,ProtoModel *>::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 \<processor_spec> 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<string,ProtoModel *>::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();

View File

@ -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

View File

@ -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

View File

@ -263,6 +263,13 @@
</element>
</zeroOrMore>
<zeroOrMore>
<element name="modelalias">
<attribute name="name"/>
<attribute name="parent"/>
</element>
</zeroOrMore>
<optional>
<element name="eval_current_prototype">
<attribute name="name"/>

View File

@ -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 \<global> 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<PrototypeModel> 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<PrototypeModel> modelList, String defaultName,
protected String modelXrefs(List<PrototypeModel> 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 \<compiler_spec> 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<PrototypeModel> 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<PrototypeModel> 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<PrototypeModel> 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)) {

View File

@ -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("<modelalias");
SpecXmlUtils.encodeStringAttribute(buffer, "name", name);
SpecXmlUtils.encodeStringAttribute(buffer, "parent", compatModel.name);
buffer.append("/>\n");
return;
}
buffer.append("<prototype");
SpecXmlUtils.encodeStringAttribute(buffer, "name", name);
if (extrapop != PrototypeModel.UNKNOWN_EXTRAPOP) {
@ -516,11 +585,20 @@ public class PrototypeModel {
return name + "@@inject_uponreturn";
}
/**
* Restore the model from an XML stream.
* @param parser is the XML parser (initialized to the start of the stream)
* @param cspec is the parent compiler specification owning the model
* @throws XmlParseException is there are problems parsing the XML
*/
public void restoreXml(XmlPullParser parser, CompilerSpec cspec) throws XmlParseException {
inputParams = null;
outputParams = null;
XmlElement protoElement = parser.start();
name = protoElement.getAttribute("name");
if (!SpecExtension.isValidFormalName(name)) {
throw new XmlParseException("Prototype name uses illegal characters");
}
extrapop = PrototypeModel.UNKNOWN_EXTRAPOP;
String extpopStr = protoElement.getAttribute("extrapop");
if (!extpopStr.equals("unknown")) {
@ -597,22 +675,57 @@ public class PrototypeModel {
parser.end(protoElement);
}
/**
* Determine if the given address range is possible input parameter storage for this model.
* If it is, "true" is returned, and additional information about the parameter's
* position is passed back in the provided record.
* @param loc is the starting address of the range
* @param size is the size of the range in bytes
* @param res is the pass-back record
* @return true if the range is a possible parameter
*/
public boolean possibleInputParamWithSlot(Address loc, int size, ParamList.WithSlotRec res) {
return inputParams.possibleParamWithSlot(loc, size, res);
}
/**
* Determine if the given address range is possible return value storage for this model.
* If it is, "true" is returned, and additional information about the storage
* position is passed back in the provided record.
* @param loc is the starting address of the range
* @param size is the size of the range in bytes
* @param res is the pass-back record
* @return true if the range is possible return value storage
*/
public boolean possibleOutputParamWithSlot(Address loc, int size, ParamList.WithSlotRec res) {
return outputParams.possibleParamWithSlot(loc, size, res);
}
/**
* Assuming the model allows open ended storage of parameters on the stack,
* return the byte alignment required for individual stack parameters.
* @return the stack alignment in bytes
*/
public int getStackParameterAlignment() {
return inputParams.getStackParameterAlignment();
}
/**
* Return the byte offset where the first input parameter on the stack is allocated.
* The value is relative to the incoming stack pointer of the called function.
* For normal stacks, this is the offset of the first byte in the first parameter.
* For reverse stacks, this is the offset immediately after the last byte of the parameter.
* @return the byte offset of the first param
*/
public Long getStackParameterOffset() {
return inputParams.getStackParameterOffset();
}
/**
* Get a list of all input storage locations consisting of a single register
* @param prog is the current Program
* @return a VariableStorage ojbect for each register
*/
public VariableStorage[] getPotentialInputRegisterStorage(Program prog) {
return inputParams.getPotentialRegisterStorage(prog);
}
@ -659,6 +772,11 @@ public class PrototypeModel {
if (!SystemUtilities.isArrayEqual(likelytrash, obj.likelytrash)) {
return false;
}
String compatName = (compatModel != null) ? compatModel.getName() : "";
String compatNameOp2 = (obj.compatModel != null) ? obj.compatModel.getName() : "";
if (!compatName.equals(compatNameOp2)) {
return false;
}
if (!SystemUtilities.isEqual(localRange, obj.localRange)) {
return false;
}