mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-653 added support for user-defined compiler spec extensions
This commit is contained in:
parent
cce6807470
commit
ed82c2cb34
2915 changed files with 333939 additions and 4451 deletions
|
@ -27,7 +27,7 @@
|
|||
vector<ArchitectureCapability *> ArchitectureCapability::thelist;
|
||||
|
||||
const uint4 ArchitectureCapability::majorversion = 4;
|
||||
const uint4 ArchitectureCapability::minorversion = 0;
|
||||
const uint4 ArchitectureCapability::minorversion = 1;
|
||||
|
||||
/// This builds a list of just the ArchitectureCapability extensions
|
||||
void ArchitectureCapability::initialize(void)
|
||||
|
@ -1218,6 +1218,26 @@ void Architecture::parseCompilerConfig(DocumentStorage &store)
|
|||
else if (elname == "inferptrbounds")
|
||||
parseInferPtrBounds(*iter);
|
||||
}
|
||||
|
||||
el = store.getTag("specextensions"); // Look for any user-defined configuration document
|
||||
if (el != (const Element *)0) {
|
||||
const List &userlist(el->getChildren());
|
||||
for(iter=userlist.begin();iter!=userlist.end();++iter) {
|
||||
const string &elname( (*iter)->getName() );
|
||||
if (elname == "prototype")
|
||||
parseProto(*iter);
|
||||
else if (elname == "callfixup") {
|
||||
pcodeinjectlib->restoreXmlInject(archid+" : compiler spec", (*iter)->getAttributeValue("name"),
|
||||
InjectPayload::CALLFIXUP_TYPE, *iter);
|
||||
}
|
||||
else if (elname == "callotherfixup") {
|
||||
userops.parseCallOtherFixup(*iter,this);
|
||||
}
|
||||
else if (elname == "global")
|
||||
globaltags.push_back(*iter);
|
||||
}
|
||||
}
|
||||
|
||||
// <global> tags instantiate the base symbol table
|
||||
// They need to know about all spaces, so it must come
|
||||
// after parsing of <stackpointer> and <spacebase>
|
||||
|
|
|
@ -1852,7 +1852,7 @@ int4 ActionReturnRecovery::apply(Funcdata &data)
|
|||
int4 slot = trial.getSlot();
|
||||
vn = op->getIn(slot);
|
||||
if (ancestorReal.execute(op,slot,&trial,false))
|
||||
if (data.ancestorOpUse(maxancestor,vn,op,trial))
|
||||
if (data.ancestorOpUse(maxancestor,vn,op,trial,0))
|
||||
trial.markActive(); // This varnode sees active use as a parameter
|
||||
count += 1;
|
||||
}
|
||||
|
@ -4305,6 +4305,8 @@ bool ActionInferTypes::propagateGoodEdge(PcodeOp *op,int4 inslot,int4 outslot,Va
|
|||
case CPUI_MULTIEQUAL:
|
||||
if ((inslot!=-1)&&(outslot!=-1)) return false; // Must propagate input <-> output
|
||||
break;
|
||||
case CPUI_INT_SLESS:
|
||||
case CPUI_INT_SLESSEQUAL:
|
||||
case CPUI_INT_LESS:
|
||||
case CPUI_INT_LESSEQUAL:
|
||||
if ((inslot==-1)||(outslot==-1)) return false; // Must propagate input <-> input
|
||||
|
@ -4400,6 +4402,11 @@ bool ActionInferTypes::propagateTypeEdge(TypeFactory *typegrp,PcodeOp *op,int4 i
|
|||
else
|
||||
newtype = alttype;
|
||||
break;
|
||||
case CPUI_INT_SLESS:
|
||||
case CPUI_INT_SLESSEQUAL:
|
||||
if (alttype->getMetatype() != TYPE_INT) return false; // Only propagate signed things
|
||||
newtype = alttype;
|
||||
break;
|
||||
case CPUI_NEW:
|
||||
{
|
||||
Varnode *invn = op->getIn(0);
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
implement these models provides the quickest inroad into
|
||||
obtaining an overall understanding of the code.
|
||||
|
||||
We list all these fundemental classes here, loosely grouped
|
||||
We list all these fundamental classes here, loosely grouped
|
||||
as follows. There is one set of classes that describe the
|
||||
\e Syntax \e Trees, which are built up from the original p-code,
|
||||
and transformed during the decompiler's simplification process.
|
||||
|
@ -43,18 +43,18 @@
|
|||
- AddrSpace
|
||||
- A place within the reverse engineering model where data
|
||||
can be stored. The typical address spaces are \b ram,
|
||||
modeling the main databus of a processor, and \b register,
|
||||
modeling a processors on board registers. Data is stored a
|
||||
byte at a time at \b offsets within the AddrSpace.
|
||||
modeling the main databus of a processor, and \b register,
|
||||
modeling a processor's on board registers. Data is stored a
|
||||
byte at a time at \b offsets within the AddrSpace.
|
||||
.
|
||||
- Address
|
||||
- An AddrSpace and an offset within the space forms the
|
||||
Address of the byte at that offset.
|
||||
Address of the byte at that offset.
|
||||
.
|
||||
- Varnode
|
||||
- A contiguous set of bytes, given by an Address and a size,
|
||||
encoding a single value in the model. In terms of SSA
|
||||
syntax tree, a Varnode is also a node in the tree.
|
||||
encoding a single value in the model. In terms of SSA
|
||||
syntax tree, a Varnode is also a node in the tree.
|
||||
.
|
||||
- SeqNum
|
||||
- A \e sequence \e number that extends Address for distinguishing PcodeOps
|
||||
|
@ -161,14 +161,14 @@
|
|||
and local scope.
|
||||
|
||||
\code
|
||||
string & getName(); // get name of function
|
||||
Address & getAddress(); // get Address of function's entry point
|
||||
int4 numCalls(); // number of subfunctions called by this function
|
||||
string & getName(); // get name of function
|
||||
Address & getAddress(); // get Address of function's entry point
|
||||
int4 numCalls(); // number of subfunctions called by this function
|
||||
FuncCallSpecs *getCallSpecs(int4 i); // get specs for one of the subfunctions
|
||||
BlockGraph & getBasicBlocks(); // get the collection of basic blocks
|
||||
BlockGraph & getBasicBlocks(); // get the collection of basic blocks
|
||||
|
||||
iterator beginLoc(Address &); // Search for Varnodes in tree
|
||||
iterator beginLoc(int4,Address &); // based on the Varnode's address
|
||||
iterator beginLoc(int4,Address &); // based on the Varnode's address
|
||||
iterator beginLoc(int4,Address &,Address &,uintm);
|
||||
iterator beginDef(uint4,Address &); // Search for Varnode based on the
|
||||
// address of its defining operation
|
||||
|
@ -221,14 +221,14 @@
|
|||
array, and structure qualifiers.
|
||||
|
||||
\code
|
||||
class TypePointer : public Datatype { // pointer to (some other type)
|
||||
Datatype *getBase(); // get Datatype being pointed to
|
||||
class TypePointer : public Datatype { // pointer to (some other type)
|
||||
Datatype *getBase(); // get Datatype being pointed to
|
||||
};
|
||||
class TypeArray : public Datatype { // array of (some other type)
|
||||
Datatype *getBase(); // get Datatype of array element
|
||||
class TypeArray : public Datatype { // array of (some other type)
|
||||
Datatype *getBase(); // get Datatype of array element
|
||||
};
|
||||
class TypeStruct : public Datatype { // structure with fields of (some other types)
|
||||
TypeField *getField(int4,int4,int4 *); // get Datatype of a field
|
||||
class TypeStruct : public Datatype { // structure with fields of (some other types)
|
||||
TypeField *getField(int4,int4,int4 *); // get Datatype of a field
|
||||
};
|
||||
\endcode
|
||||
|
||||
|
@ -237,12 +237,12 @@
|
|||
This is a container for Datatypes.
|
||||
|
||||
\code
|
||||
Datatype *findByName(string &); // find a Datatype by name
|
||||
Datatype *getTypeVoid(); // retrieve common types
|
||||
Datatype *findByName(string &); // find a Datatype by name
|
||||
Datatype *getTypeVoid(); // retrieve common types
|
||||
Datatype *getTypeChar();
|
||||
Datatype *getBase(int4 size,type_metatype);
|
||||
Datatype *getTypePointer(int4,Datatype *,uint4); // get a pointer to another type
|
||||
Datatype *getTypeArray(int4,Datatype *); // get an array of another type
|
||||
Datatype *getTypePointer(int4,Datatype *,uint4); // get a pointer to another type
|
||||
Datatype *getTypeArray(int4,Datatype *); // get an array of another type
|
||||
\endcode
|
||||
|
||||
\section classhighvariable HighVariable
|
||||
|
@ -257,7 +257,7 @@
|
|||
\code
|
||||
int4 numInstances(); // get number of different Varnodes associated
|
||||
// with this variable.
|
||||
Varnode * getInstance(int4); // get (one of the) Varnodes associated with
|
||||
Varnode * getInstance(int4); // get (one of the) Varnodes associated with
|
||||
// this variable.
|
||||
Datatype * getType(); // get Datatype of this variable
|
||||
Symbol * getSymbol(); // get Symbol associated with this variable
|
||||
|
@ -274,11 +274,11 @@
|
|||
lives in a scope, has a name, and has a Datatype.
|
||||
|
||||
\code
|
||||
string & getName(); // get the name of the symbol
|
||||
Datatype * getType(); // get the Datatype of the symbol
|
||||
Scope * getScope(); // get the scope containing the symbol
|
||||
string & getName(); // get the name of the symbol
|
||||
Datatype * getType(); // get the Datatype of the symbol
|
||||
Scope * getScope(); // get the scope containing the symbol
|
||||
SymbolEntry * getFirstWholeMap(); // get the (first) SymbolEntry associated
|
||||
// with this symbol
|
||||
// with this symbol
|
||||
\endcode
|
||||
|
||||
\section classsymbolentry SymbolEntry
|
||||
|
@ -300,16 +300,16 @@
|
|||
This is a container for symbols.
|
||||
|
||||
\code
|
||||
SymbolEntry *findAddr(Address &,Address &); // find a Symbol by address
|
||||
SymbolEntry *findAddr(Address &,Address &); // find a Symbol by address
|
||||
SymbolEntry *findContainer(Address &,int4,Address &); // find containing symbol
|
||||
Funcdata * findFunction(Address &); // find a function by entry address
|
||||
Symbol * findByName(string &); // find a Symbol by name
|
||||
SymbolEntry *queryByAddr(Address &,Address &); // search for symbols across multiple scopes
|
||||
Funcdata * findFunction(Address &); // find a function by entry address
|
||||
Symbol * findByName(string &); // find a Symbol by name
|
||||
SymbolEntry *queryByAddr(Address &,Address &); // search for symbols across multiple scopes
|
||||
SymbolEntry *queryContainer(Address &,int4,Address &);
|
||||
Funcdata * queryFunction(Address &);
|
||||
Scope * discoverScope(Address &,int4,Address &); // discover scope of an address
|
||||
string & getName(); // get name of scope
|
||||
Scope * getParent(); // get parent scope
|
||||
string & getName(); // get name of scope
|
||||
Scope * getParent(); // get parent scope
|
||||
\endcode
|
||||
|
||||
\section classdatabase Database
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -38,7 +37,7 @@
|
|||
the main code workflow.
|
||||
|
||||
The library provides its own Register
|
||||
Transfer Languate (RTL), referred to internally as \b p-code,
|
||||
Transfer Language (RTL), referred to internally as \b p-code,
|
||||
which is designed specifically for reverse engineering
|
||||
applications. The disassembly of processor specific machine-code
|
||||
languages, and subsequent translation into \b p-code, forms
|
||||
|
@ -275,7 +274,7 @@
|
|||
about the variables it analyzes, as this kind of
|
||||
information is generally not present in the input
|
||||
binary. Some information can be gathered about a
|
||||
variable, based on the instructions it is used in (.i.e
|
||||
variable, based on the instructions it is used in (i.e.
|
||||
if it is used in a floating point instruction). Other
|
||||
information about type might be available from header
|
||||
files or from the user. Once this is gathered, the
|
||||
|
@ -301,7 +300,7 @@
|
|||
compiler would, but to simplify and normalize for
|
||||
easier understanding and recognition by human analysts
|
||||
(and follow on machine processing). Typical examples
|
||||
of transforms include, copy propagation, constant
|
||||
of transforms include: copy propagation, constant
|
||||
propagation, collecting terms, cancellation of
|
||||
operators and other algebraic simplifications, undoing
|
||||
multiplication and division optimizations, commuting
|
||||
|
@ -373,7 +372,7 @@
|
|||
|
||||
Even after the initial merging of variables in phase 1,
|
||||
there are generally still too many for normal C code. So
|
||||
the decompiler, does additional, more speculative merging.
|
||||
the decompiler does additional, more speculative merging.
|
||||
It first tries to merge the inputs and outputs of copy
|
||||
operations, and then the inputs and outputs of more
|
||||
general operations. And finally, merging is attempted on
|
||||
|
|
|
@ -3174,17 +3174,20 @@ void FuncProto::setOutputLock(bool val)
|
|||
store->getOutput()->setTypeLock(val);
|
||||
}
|
||||
|
||||
/// This value can be used as a hint as to how much of the return value is important and
|
||||
/// is used to inform the dead code \e consume algorithm.
|
||||
/// \param val is the estimated number of bytes or 0
|
||||
/// \return \b true if the value was changed
|
||||
/// Provide a hint as to how many bytes of the return value are important.
|
||||
/// The smallest hint is used to inform the dead-code removal algorithm.
|
||||
/// \param val is the hint (number of bytes or 0 for all bytes)
|
||||
/// \return \b true if the smallest hint has changed
|
||||
bool FuncProto::setReturnBytesConsumed(int4 val)
|
||||
|
||||
{
|
||||
int4 oldVal = returnBytesConsumed;
|
||||
if (oldVal == 0 || val < oldVal)
|
||||
if (val == 0)
|
||||
return false;
|
||||
if (returnBytesConsumed == 0 || val < returnBytesConsumed) {
|
||||
returnBytesConsumed = val;
|
||||
return (oldVal != val);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// \brief Assuming \b this prototype is locked, calculate the \e extrapop
|
||||
|
@ -4744,7 +4747,7 @@ void FuncCallSpecs::checkInputTrialUse(Funcdata &data,AliasChecker &aliascheck)
|
|||
trial.markNoUse();
|
||||
}
|
||||
else if (ancestorReal.execute(op,slot,&trial,false)) {
|
||||
if (data.ancestorOpUse(maxancestor,vn,op,trial))
|
||||
if (data.ancestorOpUse(maxancestor,vn,op,trial,0))
|
||||
trial.markActive();
|
||||
else
|
||||
trial.markInactive();
|
||||
|
@ -4754,7 +4757,7 @@ void FuncCallSpecs::checkInputTrialUse(Funcdata &data,AliasChecker &aliascheck)
|
|||
}
|
||||
else {
|
||||
if (ancestorReal.execute(op,slot,&trial,true)) {
|
||||
if (data.ancestorOpUse(maxancestor,vn,op,trial)) {
|
||||
if (data.ancestorOpUse(maxancestor,vn,op,trial,0)) {
|
||||
trial.markActive();
|
||||
if (trial.hasCondExeEffect())
|
||||
activeinput.markNeedsFinalCheck();
|
||||
|
|
|
@ -314,7 +314,7 @@ public:
|
|||
enum {
|
||||
unaffected = 1, ///< The sub-function does not change the value at all
|
||||
killedbycall = 2, ///< The memory is changed and is completely unrelated to its original value
|
||||
return_address = 3, ///< The memory is being used to pass back a return value from the sub-function
|
||||
return_address = 3, ///< The memory is being used to store the return address
|
||||
unknown_effect = 4 ///< An unknown effect (indicates the absence of an EffectRecord)
|
||||
};
|
||||
private:
|
||||
|
|
|
@ -58,6 +58,11 @@ class Funcdata {
|
|||
baddata_present = 0x800, ///< Set if function flowed into bad data
|
||||
double_precis_on = 0x1000 ///< Set if we are performing double precision recovery
|
||||
};
|
||||
enum {
|
||||
traverse_actionalt = 1, ///< Alternate path traverses a solid action or \e non-incidental COPY
|
||||
traverse_indirect = 2, ///< Main path traverses an INDIRECT
|
||||
traverse_indirectalt = 4 ///< Alternate path traverses an INDIRECT
|
||||
};
|
||||
uint4 flags; ///< Boolean properties associated with \b this function
|
||||
uint4 clean_up_index; ///< Creation index of first Varnode created after start of cleanup
|
||||
uint4 high_level_index; ///< Creation index of first Varnode created after HighVariables are created
|
||||
|
@ -116,6 +121,7 @@ class Funcdata {
|
|||
void nodeSplitCloneVarnode(PcodeOp *op,PcodeOp *newop);
|
||||
void nodeSplitRawDuplicate(BlockBasic *b,BlockBasic *bprime);
|
||||
void nodeSplitInputPatch(BlockBasic *b,BlockBasic *bprime,int4 inedge);
|
||||
static bool isAlternatePathValid(const Varnode *vn,uint4 flags);
|
||||
static bool descendantsOutside(Varnode *vn);
|
||||
static void saveVarnodeXml(ostream &s,VarnodeLocSet::const_iterator iter,VarnodeLocSet::const_iterator enditer);
|
||||
static bool checkIndirectUse(Varnode *vn);
|
||||
|
@ -363,9 +369,9 @@ public:
|
|||
|
||||
HighVariable *findHigh(const string &name) const; ///< Find a high-level variable by name
|
||||
void mapGlobals(void); ///< Make sure there is a Symbol entry for all global Varnodes
|
||||
bool checkCallDoubleUse(const PcodeOp *opmatch,const PcodeOp *op,const Varnode *vn,const ParamTrial &trial) const;
|
||||
bool onlyOpUse(const Varnode *invn,const PcodeOp *opmatch,const ParamTrial &trial) const;
|
||||
bool ancestorOpUse(int4 maxlevel,const Varnode *invn,const PcodeOp *op,ParamTrial &trial) const;
|
||||
bool checkCallDoubleUse(const PcodeOp *opmatch,const PcodeOp *op,const Varnode *vn,uint4 flags,const ParamTrial &trial) const;
|
||||
bool onlyOpUse(const Varnode *invn,const PcodeOp *opmatch,const ParamTrial &trial,uint4 mainFlags) const;
|
||||
bool ancestorOpUse(int4 maxlevel,const Varnode *invn,const PcodeOp *op,ParamTrial &trial,uint4 mainFlags) const;
|
||||
bool syncVarnodesWithSymbols(const ScopeLocal *lm,bool typesyes);
|
||||
void transferVarnodeProperties(Varnode *vn,Varnode *newVn,int4 lsbOffset);
|
||||
bool fillinReadOnly(Varnode *vn); ///< Replace the given Varnode with its (constant) value in the load image
|
||||
|
|
|
@ -189,7 +189,7 @@ Varnode *Funcdata::newVarnodeSpace(AddrSpace *spc)
|
|||
|
||||
{
|
||||
Datatype *ct = glb->types->getBase(sizeof(spc),TYPE_UNKNOWN);
|
||||
|
||||
|
||||
Varnode *vn = vbank.create(sizeof(spc),glb->createConstFromSpace(spc),ct);
|
||||
assignHigh(vn);
|
||||
return vn;
|
||||
|
@ -357,7 +357,7 @@ Varnode *Funcdata::setInputVarnode(Varnode *vn)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vn = vbank.setInput(vn);
|
||||
setVarnodeProperties(vn);
|
||||
uint4 effecttype = funcp.hasEffect(vn->getAddr(),vn->getSize());
|
||||
|
@ -393,7 +393,7 @@ void Funcdata::adjustInputVarnodes(const Address &addr,int4 size)
|
|||
throw LowlevelError("Cannot properly adjust input varnodes");
|
||||
inlist.push_back(vn);
|
||||
}
|
||||
|
||||
|
||||
for(uint4 i=0;i<inlist.size();++i) {
|
||||
Varnode *vn = inlist[i];
|
||||
int4 sa = addr.justifiedContain(size,vn->getAddr(),vn->getSize(),false);
|
||||
|
@ -405,7 +405,7 @@ void Funcdata::adjustInputVarnodes(const Address &addr,int4 size)
|
|||
Varnode *newvn = newVarnodeOut(vn->getSize(),vn->getAddr(),subop);
|
||||
// newvn must not be free in order to give all vn's descendants
|
||||
opInsertBegin(subop,(BlockBasic *)bblocks.getBlock(0));
|
||||
totalReplace(vn,newvn);
|
||||
totalReplace(vn,newvn);
|
||||
deleteVarnode(vn); // Get rid of old input before creating new input
|
||||
inlist[i] = newvn;
|
||||
}
|
||||
|
@ -546,7 +546,7 @@ bool Funcdata::fillinReadOnly(Varnode *vn)
|
|||
vn->clearFlags(Varnode::readonly); // Treat as writeable
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (vn->getSpace()->isBigEndian()) { // Big endian
|
||||
res = 0;
|
||||
for(int4 i=0;i<vn->getSize();++i) {
|
||||
|
@ -1472,6 +1472,30 @@ void Funcdata::mapGlobals(void)
|
|||
warningHeader("Globals starting with '_' overlap smaller symbols at the same address");
|
||||
}
|
||||
|
||||
/// \brief Return \b true if the alternate path looks more valid than the main path.
|
||||
///
|
||||
/// Two different paths from a common Varnode each terminate at a CALL, CALLIND, or RETURN.
|
||||
/// Evaluate which path most likely represents actual parameter/return value passing,
|
||||
/// based on traversal information about each path.
|
||||
/// \param vn is the Varnode terminating the \e alternate path
|
||||
/// \param flags indicates traversals for both paths
|
||||
/// \return \b true if the alternate path is preferred
|
||||
bool Funcdata::isAlternatePathValid(const Varnode *vn,uint4 flags)
|
||||
|
||||
{
|
||||
if ((flags & (traverse_indirect | traverse_indirectalt)) == traverse_indirect)
|
||||
// If main path traversed an INDIRECT but the alternate did not
|
||||
return true; // Main path traversed INDIRECT, alternate did not
|
||||
if ((flags & (traverse_indirect | traverse_indirectalt)) == traverse_indirectalt)
|
||||
return false; // Alternate path traversed INDIRECT, main did not
|
||||
if ((flags & traverse_actionalt) != 0)
|
||||
return true; // Alternate path traversed a dedicated COPY
|
||||
if (vn->loneDescend() == (PcodeOp*)0) return false;
|
||||
const PcodeOp *op = vn->getDef();
|
||||
if (op == (PcodeOp*)0) return true;
|
||||
return !op->isMarker(); // MULTIEQUAL or INDIRECT indicates multiple values
|
||||
}
|
||||
|
||||
/// \brief Test for legitimate double use of a parameter trial
|
||||
///
|
||||
/// The given trial is a \e putative input to first CALL, but can also trace its data-flow
|
||||
|
@ -1480,9 +1504,10 @@ void Funcdata::mapGlobals(void)
|
|||
/// \param opmatch is the first CALL linked to the trial
|
||||
/// \param op is the second CALL
|
||||
/// \param vn is the Varnode parameter for the second CALL
|
||||
/// \param flags indicates what p-code ops were crossed to reach \e vn
|
||||
/// \param trial is the given parameter trial
|
||||
/// \return \b true for a legitimate double use
|
||||
bool Funcdata::checkCallDoubleUse(const PcodeOp *opmatch,const PcodeOp *op,const Varnode *vn,const ParamTrial &trial) const
|
||||
bool Funcdata::checkCallDoubleUse(const PcodeOp *opmatch,const PcodeOp *op,const Varnode *vn,uint4 flags,const ParamTrial &trial) const
|
||||
|
||||
{
|
||||
int4 j = op->getSlot(vn);
|
||||
|
@ -1508,10 +1533,16 @@ bool Funcdata::checkCallDoubleUse(const PcodeOp *opmatch,const PcodeOp *op,const
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (fc->isInputActive()) {
|
||||
const ParamTrial &curtrial( fc->getActiveInput()->getTrialForInputVarnode(j) );
|
||||
if ((!curtrial.isChecked())||(!curtrial.isActive())) return true;
|
||||
if (curtrial.isChecked()) {
|
||||
if (curtrial.isActive())
|
||||
return false;
|
||||
}
|
||||
else if (isAlternatePathValid(vn,flags))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1523,28 +1554,31 @@ bool Funcdata::checkCallDoubleUse(const PcodeOp *opmatch,const PcodeOp *op,const
|
|||
/// \param invn is the given Varnode
|
||||
/// \param opmatch is the putative CALL op using the Varnode for parameter passing
|
||||
/// \param trial is the parameter trial object associated with the Varnode
|
||||
/// \param mainFlags are flags describing traversals along the \e main path, from \e invn to \e opmatch
|
||||
/// \return \b true if the Varnode seems only to be used as parameter to \b opmatch
|
||||
bool Funcdata::onlyOpUse(const Varnode *invn,const PcodeOp *opmatch,const ParamTrial &trial) const
|
||||
bool Funcdata::onlyOpUse(const Varnode *invn,const PcodeOp *opmatch,const ParamTrial &trial,uint4 mainFlags) const
|
||||
|
||||
{
|
||||
vector<const Varnode *> varlist;
|
||||
vector<TraverseNode> varlist;
|
||||
list<PcodeOp *>::const_iterator iter;
|
||||
const Varnode *vn,*subvn;
|
||||
const PcodeOp *op;
|
||||
int4 i;
|
||||
bool res = true;
|
||||
|
||||
varlist.reserve(64);
|
||||
invn->setMark(); // Marks prevent infinite loops
|
||||
varlist.push_back(invn);
|
||||
|
||||
i = 0;
|
||||
while(i < varlist.size()) {
|
||||
vn = varlist[i++];
|
||||
varlist.emplace_back(invn,mainFlags);
|
||||
|
||||
for(i=0;i < varlist.size();++i) {
|
||||
vn = varlist[i].vn;
|
||||
uint4 baseFlags = varlist[i].flags;
|
||||
for(iter=vn->descend.begin();iter!=vn->descend.end();++iter) {
|
||||
op = *iter;
|
||||
if (op == opmatch) {
|
||||
if (op->getIn(trial.getSlot())==vn) continue;
|
||||
}
|
||||
uint4 curFlags = baseFlags;
|
||||
switch(op->code()) {
|
||||
case CPUI_BRANCH: // These ops define a USE of a variable
|
||||
case CPUI_CBRANCH:
|
||||
|
@ -1555,17 +1589,39 @@ bool Funcdata::onlyOpUse(const Varnode *invn,const PcodeOp *opmatch,const ParamT
|
|||
break;
|
||||
case CPUI_CALL:
|
||||
case CPUI_CALLIND:
|
||||
if (checkCallDoubleUse(opmatch,op,vn,trial)) continue;
|
||||
if (checkCallDoubleUse(opmatch,op,vn,curFlags,trial)) continue;
|
||||
res = false;
|
||||
break;
|
||||
case CPUI_INDIRECT:
|
||||
curFlags |= Funcdata::traverse_indirectalt;
|
||||
break;
|
||||
case CPUI_COPY:
|
||||
if ((op->getOut()->getSpace()->getType()!=IPTR_INTERNAL)&&!op->isIncidentalCopy()&&!vn->isIncidentalCopy()) {
|
||||
curFlags |= Funcdata::traverse_actionalt;
|
||||
}
|
||||
break;
|
||||
case CPUI_RETURN:
|
||||
if (opmatch->code()==CPUI_RETURN) { // Are we in a different return
|
||||
if (op->getIn(trial.getSlot())==vn) // But at the same slot
|
||||
continue;
|
||||
}
|
||||
else if (activeoutput != (ParamActive *)0) { // Are we in the middle of analyzing returns
|
||||
if (op->getIn(0) != vn) { // Unless we hold actual return value
|
||||
if (!isAlternatePathValid(vn,curFlags))
|
||||
continue; // Don't consider this a "use"
|
||||
}
|
||||
}
|
||||
res = false;
|
||||
break;
|
||||
case CPUI_MULTIEQUAL:
|
||||
case CPUI_PIECE:
|
||||
case CPUI_SUBPIECE:
|
||||
case CPUI_INT_SEXT:
|
||||
case CPUI_INT_ZEXT:
|
||||
case CPUI_CAST:
|
||||
break;
|
||||
default:
|
||||
curFlags |= Funcdata::traverse_actionalt;
|
||||
break;
|
||||
}
|
||||
if (!res) break;
|
||||
|
@ -1576,7 +1632,7 @@ bool Funcdata::onlyOpUse(const Varnode *invn,const PcodeOp *opmatch,const ParamT
|
|||
break;
|
||||
}
|
||||
if (!subvn->isMark()) {
|
||||
varlist.push_back(subvn);
|
||||
varlist.emplace_back(subvn,curFlags);
|
||||
subvn->setMark();
|
||||
}
|
||||
}
|
||||
|
@ -1584,7 +1640,7 @@ bool Funcdata::onlyOpUse(const Varnode *invn,const PcodeOp *opmatch,const ParamT
|
|||
if (!res) break;
|
||||
}
|
||||
for(i=0;i<varlist.size();++i)
|
||||
varlist[i]->clearMark();
|
||||
varlist[i].vn->clearMark();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -1596,9 +1652,10 @@ bool Funcdata::onlyOpUse(const Varnode *invn,const PcodeOp *opmatch,const ParamT
|
|||
/// \param invn is the given trial Varnode to test
|
||||
/// \param op is the given CALL or RETURN
|
||||
/// \param trial is the associated parameter trial object
|
||||
/// \param mainFlags describes traversals along the path from \e invn to \e op
|
||||
/// \return \b true if the Varnode is only used for the CALL/RETURN
|
||||
bool Funcdata::ancestorOpUse(int4 maxlevel,const Varnode *invn,
|
||||
const PcodeOp *op,ParamTrial &trial) const
|
||||
const PcodeOp *op,ParamTrial &trial,uint4 mainFlags) const
|
||||
|
||||
{
|
||||
int4 i;
|
||||
|
@ -1610,9 +1667,9 @@ bool Funcdata::ancestorOpUse(int4 maxlevel,const Varnode *invn,
|
|||
if (!invn->isTypeLock()) return false;
|
||||
// If the input is typelocked
|
||||
// this is as good as being written
|
||||
return onlyOpUse(invn,op,trial); // Test if varnode is only used in op
|
||||
return onlyOpUse(invn,op,trial,mainFlags); // Test if varnode is only used in op
|
||||
}
|
||||
|
||||
|
||||
const PcodeOp *def = invn->getDef();
|
||||
switch(def->code()) {
|
||||
case CPUI_INDIRECT:
|
||||
|
@ -1620,7 +1677,7 @@ bool Funcdata::ancestorOpUse(int4 maxlevel,const Varnode *invn,
|
|||
// as an "only use"
|
||||
if (def->isIndirectCreation())
|
||||
return false;
|
||||
return ancestorOpUse(maxlevel-1,def->getIn(0),op,trial);
|
||||
return ancestorOpUse(maxlevel-1,def->getIn(0),op,trial,mainFlags | Funcdata::traverse_indirect);
|
||||
case CPUI_MULTIEQUAL:
|
||||
// Check if there is any ancestor whose only
|
||||
// use is in this op
|
||||
|
@ -1628,7 +1685,7 @@ bool Funcdata::ancestorOpUse(int4 maxlevel,const Varnode *invn,
|
|||
def->setMark(); // Mark that this MULTIEQUAL is on the path
|
||||
// Note: onlyOpUse is using Varnode::setMark
|
||||
for(i=0;i<def->numInput();++i) {
|
||||
if (ancestorOpUse(maxlevel-1,def->getIn(i),op,trial)) {
|
||||
if (ancestorOpUse(maxlevel-1,def->getIn(i),op,trial, mainFlags)) {
|
||||
def->clearMark();
|
||||
return true;
|
||||
}
|
||||
|
@ -1637,13 +1694,12 @@ bool Funcdata::ancestorOpUse(int4 maxlevel,const Varnode *invn,
|
|||
return false;
|
||||
case CPUI_COPY:
|
||||
if ((invn->getSpace()->getType()==IPTR_INTERNAL)||def->isIncidentalCopy()||def->getIn(0)->isIncidentalCopy()) {
|
||||
if (!ancestorOpUse(maxlevel-1,def->getIn(0),op,trial)) return false;
|
||||
return true;
|
||||
return ancestorOpUse(maxlevel-1,def->getIn(0),op,trial,mainFlags);
|
||||
}
|
||||
break;
|
||||
case CPUI_PIECE:
|
||||
// Concatenation tends to be artificial, so recurse through the least significant part
|
||||
return ancestorOpUse(maxlevel-1,def->getIn(1),op,trial);
|
||||
return ancestorOpUse(maxlevel-1,def->getIn(1),op,trial,mainFlags);
|
||||
case CPUI_SUBPIECE:
|
||||
// This is a rather kludgy way to get around where a DIV (or other similar) instruction
|
||||
// causes a register that looks like the high precision piece of the function return
|
||||
|
@ -1664,7 +1720,7 @@ bool Funcdata::ancestorOpUse(int4 maxlevel,const Varnode *invn,
|
|||
break;
|
||||
}
|
||||
// This varnode must be top ancestor at this point
|
||||
return onlyOpUse(invn,op,trial); // Test if varnode is only used in op
|
||||
return onlyOpUse(invn,op,trial,mainFlags); // Test if varnode is only used in op
|
||||
}
|
||||
|
||||
/// \return \b true if there are two input flows, one of which is a normal \e solid flow
|
||||
|
|
|
@ -284,7 +284,7 @@ void ArchitectureGhidra::buildSpecFile(DocumentStorage &store)
|
|||
istringstream cstream(cspecxml);
|
||||
doc = store.parseDocument(cstream);
|
||||
store.registerTag(doc->getRoot());
|
||||
|
||||
|
||||
istringstream tstream(tspecxml);
|
||||
doc = store.parseDocument(tstream);
|
||||
store.registerTag(doc->getRoot());
|
||||
|
@ -293,10 +293,10 @@ void ArchitectureGhidra::buildSpecFile(DocumentStorage &store)
|
|||
doc = store.parseDocument(corestream);
|
||||
store.registerTag(doc->getRoot());
|
||||
|
||||
pspecxml = ""; // Strings aren't used again free memory
|
||||
cspecxml = "";
|
||||
tspecxml = "";
|
||||
corespecxml = "";
|
||||
pspecxml.clear(); // Strings aren't used again free memory
|
||||
cspecxml.clear();
|
||||
tspecxml.clear();
|
||||
corespecxml.clear();
|
||||
}
|
||||
|
||||
void ArchitectureGhidra::postSpecFile(void)
|
||||
|
|
|
@ -82,7 +82,8 @@ class ArchitectureGhidra : public Architecture {
|
|||
virtual void postSpecFile(void);
|
||||
virtual void resolveArchitecture(void);
|
||||
public:
|
||||
ArchitectureGhidra(const string &pspec,const string &cspec,const string &tspec,const string &corespec,istream &i,ostream &o);
|
||||
ArchitectureGhidra(const string &pspec,const string &cspec,const string &tspec,const string &corespec,
|
||||
istream &i,ostream &o);
|
||||
const string &getWarnings(void) const { return warnings; } ///< Get warnings produced by the last decompilation
|
||||
void clearWarnings(void) { warnings.clear(); } ///< Clear warnings
|
||||
Document *getRegister(const string ®name); ///< Retrieve a register description given a name
|
||||
|
|
|
@ -174,6 +174,10 @@ void RegisterProgram::rawAction(void)
|
|||
}
|
||||
}
|
||||
ghidra = new ArchitectureGhidra(pspec,cspec,tspec,corespec,sin,sout);
|
||||
pspec.clear();
|
||||
cspec.clear();
|
||||
tspec.clear();
|
||||
corespec.clear();
|
||||
|
||||
DocumentStorage store; // temp storage of initialization xml docs
|
||||
ghidra->init(store);
|
||||
|
|
|
@ -130,6 +130,45 @@ FlowBlock *PriorityQueue::extract(void)
|
|||
return res;
|
||||
}
|
||||
|
||||
/// Initialize heritage state information for a particular address space
|
||||
/// \param spc is the address space
|
||||
HeritageInfo::HeritageInfo(AddrSpace *spc)
|
||||
|
||||
{
|
||||
if (spc == (AddrSpace *)0) {
|
||||
space = (AddrSpace *)0;
|
||||
delay = 0;
|
||||
deadcodedelay = 0;
|
||||
hasCallPlaceholders = false;
|
||||
}
|
||||
else if (!spc->isHeritaged()) {
|
||||
space = (AddrSpace *)0;
|
||||
delay = spc->getDelay();
|
||||
deadcodedelay = spc->getDeadcodeDelay();
|
||||
hasCallPlaceholders = false;
|
||||
}
|
||||
else {
|
||||
space = spc;
|
||||
delay = spc->getDelay();
|
||||
deadcodedelay = spc->getDeadcodeDelay();
|
||||
hasCallPlaceholders = (spc->getType() == IPTR_SPACEBASE);
|
||||
}
|
||||
deadremoved = 0;
|
||||
warningissued = false;
|
||||
loadGuardSearch = false;
|
||||
}
|
||||
|
||||
void HeritageInfo::reset(void)
|
||||
|
||||
{
|
||||
// Leave any override intact: deadcodedelay = delay;
|
||||
deadremoved = 0;
|
||||
if (space != (AddrSpace *)0)
|
||||
hasCallPlaceholders = (space->getType() == IPTR_SPACEBASE);
|
||||
warningissued = false;
|
||||
loadGuardSearch = false;
|
||||
}
|
||||
|
||||
/// Instantiate the heritage manager for a particular function.
|
||||
/// \param data is the function
|
||||
Heritage::Heritage(Funcdata *data)
|
||||
|
@ -1180,16 +1219,10 @@ void Heritage::guardCalls(uint4 flags,const Address &addr,int4 size,vector<Varno
|
|||
uintb off = addr.getOffset();
|
||||
bool tryregister = true;
|
||||
if (spc->getType() == IPTR_SPACEBASE) {
|
||||
if (fc->getStackPlaceholderSlot() < 0) { // Any stack resolution is complete (or never started)
|
||||
if (fc->getSpacebaseOffset() != FuncCallSpecs::offset_unknown)
|
||||
off = spc->wrapOffset(off - fc->getSpacebaseOffset());
|
||||
else
|
||||
tryregister = false; // Do not attempt to register this stack loc as a trial
|
||||
}
|
||||
else { // Stack has not been resolved, so we need to abort
|
||||
fc->abortSpacebaseRelative(*fd);
|
||||
tryregister = false;
|
||||
}
|
||||
if (fc->getSpacebaseOffset() != FuncCallSpecs::offset_unknown)
|
||||
off = spc->wrapOffset(off - fc->getSpacebaseOffset());
|
||||
else
|
||||
tryregister = false; // Do not attempt to register this stack loc as a trial
|
||||
}
|
||||
Address transAddr(spc,off); // Address relative to callee's stack
|
||||
if (tryregister) {
|
||||
|
@ -1695,6 +1728,19 @@ static void verify_dfs(const vector<FlowBlock *> &list,vector<vector<FlowBlock *
|
|||
}
|
||||
#endif
|
||||
|
||||
/// Assuming we are just about to do heritage on an address space,
|
||||
/// clear any placeholder LOADs associated with it on CALLs.
|
||||
/// \param info is state for the specific address space
|
||||
void Heritage::clearStackPlaceholders(HeritageInfo *info)
|
||||
|
||||
{
|
||||
int4 numCalls = fd->numCalls();
|
||||
for(int4 i=0;i<numCalls;++i) {
|
||||
fd->getCallSpecs(i)->abortSpacebaseRelative(*fd);
|
||||
}
|
||||
info->hasCallPlaceholders = false; // Mark that clear has taken place
|
||||
}
|
||||
|
||||
/// \brief Perform one level of Varnode splitting to match a JoinRecord
|
||||
///
|
||||
/// Split all the pieces in \b lastcombo, putting them into \b nextlev in order,
|
||||
|
@ -2288,16 +2334,9 @@ void Heritage::buildInfoList(void)
|
|||
{
|
||||
if (!infolist.empty()) return;
|
||||
const AddrSpaceManager *manage = fd->getArch();
|
||||
infolist.resize(manage->numSpaces());
|
||||
for(int4 i=0;i<manage->numSpaces();++i) {
|
||||
AddrSpace *spc = manage->getSpace(i);
|
||||
if (spc == (AddrSpace *)0)
|
||||
infolist[i].set((AddrSpace *)0,0,0);
|
||||
else if (!spc->isHeritaged())
|
||||
infolist[i].set((AddrSpace *)0,spc->getDelay(),spc->getDeadcodeDelay());
|
||||
else
|
||||
infolist[i].set(spc,spc->getDelay(),spc->getDeadcodeDelay());
|
||||
}
|
||||
infolist.reserve(manage->numSpaces());
|
||||
for(int4 i=0;i<manage->numSpaces();++i)
|
||||
infolist.emplace_back(manage->getSpace(i));
|
||||
}
|
||||
|
||||
/// From any address space that is active for this pass, free Varnodes are collected
|
||||
|
@ -2328,6 +2367,9 @@ void Heritage::heritage(void)
|
|||
info = &infolist[i];
|
||||
if (!info->isHeritaged()) continue;
|
||||
if (pass < info->delay) continue; // It is too soon to heritage this space
|
||||
if (info->hasCallPlaceholders)
|
||||
clearStackPlaceholders(info);
|
||||
|
||||
if (!info->loadGuardSearch) {
|
||||
info->loadGuardSearch = true;
|
||||
if (discoverIndexedStackPointers(info->space,freeStores,true)) {
|
||||
|
|
|
@ -90,11 +90,11 @@ class HeritageInfo {
|
|||
int4 deadremoved; ///< >0 if Varnodes in this space have been eliminated
|
||||
bool loadGuardSearch; ///< \b true if the search for LOAD ops to guard has been performed
|
||||
bool warningissued; ///< \b true if warning issued previously
|
||||
void set(AddrSpace *spc,int4 dl,int4 dcdl) {
|
||||
space=spc; delay=dl; deadcodedelay=dcdl; deadremoved=0; warningissued=false; loadGuardSearch = false; } ///< Set all fields
|
||||
bool hasCallPlaceholders; ///< \b true for the \e stack space, if stack placeholders have not been removed
|
||||
bool isHeritaged(void) const { return (space != (AddrSpace *)0); } ///< Return \b true if heritage is performed on this space
|
||||
void reset(void) {
|
||||
deadremoved = 0; deadcodedelay = delay; warningissued = false; loadGuardSearch = false; } ///< Reset
|
||||
void reset(void); ///< Reset the state
|
||||
public:
|
||||
HeritageInfo(AddrSpace *spc); ///< Constructor
|
||||
};
|
||||
|
||||
/// \brief Description of a LOAD operation that needs to be guarded
|
||||
|
@ -222,6 +222,7 @@ class Heritage {
|
|||
/// \brief Get the heritage status for the given address space
|
||||
const HeritageInfo *getInfo(AddrSpace *spc) const { return &(infolist[spc->getIndex()]); }
|
||||
|
||||
void clearStackPlaceholders(HeritageInfo *info); ///< Clear remaining stack placeholder LOADs on any call
|
||||
void splitJoinLevel(vector<Varnode *> &lastcombo,vector<Varnode *> &nextlev,JoinRecord *joinrec);
|
||||
void splitJoinRead(Varnode *vn,JoinRecord *joinrec);
|
||||
void splitJoinWrite(Varnode *vn,JoinRecord *joinrec);
|
||||
|
|
|
@ -293,6 +293,23 @@ int4 PcodeInjectLibrarySleigh::registerDynamicInject(InjectPayload *payload)
|
|||
return id;
|
||||
}
|
||||
|
||||
/// \brief Force a payload to be dynamic for debug purposes
|
||||
///
|
||||
/// Debug information may include inject information for payloads that aren't dynamic.
|
||||
/// We substitute a dynamic payload so that analysis uses the debug info to inject, rather
|
||||
/// than the hard-coded payload information.
|
||||
/// \param injectid is the id of the payload to treat dynamic
|
||||
/// \return the new dynamic payload object
|
||||
InjectPayloadDynamic *PcodeInjectLibrarySleigh::forceDebugDynamic(int4 injectid)
|
||||
|
||||
{
|
||||
InjectPayload *oldPayload = injection[injectid];
|
||||
InjectPayloadDynamic *newPayload = new InjectPayloadDynamic(glb,oldPayload->getName(),oldPayload->getType());
|
||||
delete oldPayload;
|
||||
injection[injectid] = newPayload;
|
||||
return newPayload;
|
||||
}
|
||||
|
||||
void PcodeInjectLibrarySleigh::parseInject(InjectPayload *payload)
|
||||
|
||||
{
|
||||
|
@ -399,9 +416,10 @@ void PcodeInjectLibrarySleigh::restoreDebug(const Element *el)
|
|||
s.unsetf(ios::dec | ios::hex | ios::oct);
|
||||
s >> type;
|
||||
int4 id = getPayloadId(type,name);
|
||||
InjectPayloadDynamic *payload = (InjectPayloadDynamic *)getPayload(id);
|
||||
if (payload->getSource() != "dynamic")
|
||||
throw LowlevelError("Mismatch with debug inject XML");
|
||||
InjectPayloadDynamic *payload = dynamic_cast<InjectPayloadDynamic *>(getPayload(id));
|
||||
if (payload == (InjectPayloadDynamic *)0) {
|
||||
payload = forceDebugDynamic(id);
|
||||
}
|
||||
payload->restoreEntry(subel);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ class PcodeInjectLibrarySleigh : public PcodeInjectLibrary {
|
|||
vector<OpBehavior *> inst;
|
||||
InjectContextSleigh contextCache;
|
||||
int4 registerDynamicInject(InjectPayload *payload);
|
||||
InjectPayloadDynamic *forceDebugDynamic(int4 injectid);
|
||||
void parseInject(InjectPayload *payload);
|
||||
protected:
|
||||
virtual int4 allocateInject(const string &sourceName,const string &name,int4 type);
|
||||
|
|
|
@ -216,10 +216,10 @@ void SegmentOp::restoreXml(const Element *el)
|
|||
throw LowlevelError("Bad segment pattern tag: "+subel->getName());
|
||||
}
|
||||
if (injectId < 0)
|
||||
throw LowlevelError("Missing <execute> child in <segmentop> tag");
|
||||
throw LowlevelError("Missing <pcode> child in <segmentop> tag");
|
||||
InjectPayload *payload = glb->pcodeinjectlib->getPayload(injectId);
|
||||
if (payload->sizeOutput() != 1)
|
||||
throw LowlevelError("<execute> child of <segmentop> tag must declare one <output>");
|
||||
throw LowlevelError("<pcode> child of <segmentop> tag must declare one <output>");
|
||||
if (payload->sizeInput() == 1) {
|
||||
innerinsize = payload->getInput(0).getSize();
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ void SegmentOp::restoreXml(const Element *el)
|
|||
innerinsize = payload->getInput(1).getSize();
|
||||
}
|
||||
else
|
||||
throw LowlevelError("<execute> child of <segmentop> tag must declare one or two <input> tags");
|
||||
throw LowlevelError("<pcode> child of <segmentop> tag must declare one or two <input> tags");
|
||||
}
|
||||
|
||||
/// \param g is the Architecture owning this set of jump assist scripts
|
||||
|
|
|
@ -376,6 +376,13 @@ public:
|
|||
#endif
|
||||
};
|
||||
|
||||
/// \brief Node for a forward traversal of a Varnode expression
|
||||
struct TraverseNode {
|
||||
const Varnode *vn; ///< Varnode at the point of traversal
|
||||
uint4 flags; ///< Flags associated with the node
|
||||
TraverseNode(const Varnode *v,uint4 f) { vn = v; flags = f; }
|
||||
};
|
||||
|
||||
bool contiguous_test(Varnode *vn1,Varnode *vn2); ///< Test if Varnodes are pieces of a whole
|
||||
Varnode *findContiguousWhole(Funcdata &data,Varnode *vn1,
|
||||
Varnode *vn2); ///< Retrieve the whole Varnode given pieces
|
||||
|
|
|
@ -127,6 +127,12 @@ void XmlArchitecture::restoreXml(DocumentStorage &store)
|
|||
++iter;
|
||||
}
|
||||
}
|
||||
if (iter != list.end()) {
|
||||
if ((*iter)->getName() == "specextensions") {
|
||||
store.registerTag(*iter);
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
if (iter!=list.end()) {
|
||||
if ((*iter)->getName() == "coretypes") {
|
||||
store.registerTag(*iter);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue