GP-4370 Internal Storage

This commit is contained in:
caheckman 2024-04-19 16:54:30 +00:00
parent f1e2c8db04
commit 05818c5c3a
17 changed files with 219 additions and 63 deletions

View file

@ -3857,54 +3857,6 @@ uintb ActionDeadCode::gatherConsumedReturn(Funcdata &data)
return consumeVal;
}
/// \brief Determine if the given Varnode may eventually collapse to a constant
///
/// Recursively check if the Varnode is either:
/// - Copied from a constant
/// - The result of adding constants
/// - Loaded from a pointer that is a constant
///
/// \param vn is the given Varnode
/// \param addCount is the number of CPUI_INT_ADD operations seen so far
/// \param loadCount is the number of CPUI_LOAD operations seen so far
/// \return \b true if the Varnode (might) collapse to a constant
bool ActionDeadCode::isEventualConstant(Varnode *vn,int4 addCount,int4 loadCount)
{
if (vn->isConstant()) return true;
if (!vn->isWritten()) return false;
PcodeOp *op = vn->getDef();
while(op->code() == CPUI_COPY) {
vn = op->getIn(0);
if (vn->isConstant()) return true;
if (!vn->isWritten()) return false;
op = vn->getDef();
}
switch(op->code()) {
case CPUI_INT_ADD:
if (addCount > 0) return false;
if (!isEventualConstant(op->getIn(0),addCount+1,loadCount))
return false;
return isEventualConstant(op->getIn(1),addCount+1,loadCount);
case CPUI_LOAD:
if (loadCount > 0) return false;
return isEventualConstant(op->getIn(1),0,loadCount+1);
case CPUI_INT_LEFT:
case CPUI_INT_RIGHT:
case CPUI_INT_SRIGHT:
case CPUI_INT_MULT:
if (!op->getIn(1)->isConstant())
return false;
return isEventualConstant(op->getIn(0),addCount,loadCount);
case CPUI_INT_ZEXT:
case CPUI_INT_SEXT:
return isEventualConstant(op->getIn(0),addCount,loadCount);
default:
break;
}
return false;
}
/// \brief Check if there are any unconsumed LOADs that may be from volatile addresses.
///
/// It may be too early to remove certain LOAD operations even though their result isn't
@ -3927,7 +3879,7 @@ bool ActionDeadCode::lastChanceLoad(Funcdata &data,vector<Varnode *> &worklist)
if (op->isDead()) continue;
Varnode *vn = op->getOut();
if (vn->isConsumeVacuous()) continue;
if (isEventualConstant(op->getIn(1), 0, 0)) {
if (op->getIn(1)->isEventualConstant(3, 1)) {
pushConsumed(~(uintb)0, vn, worklist);
vn->setAutoLiveHold();
res = true;
@ -4817,6 +4769,37 @@ int4 ActionPrototypeWarnings::apply(Funcdata &data)
return 0;
}
int4 ActionInternalStorage::apply(Funcdata &data)
{
FuncProto &proto( data.getFuncProto() );
vector<VarnodeData>::const_iterator iter = proto.internalBegin();
vector<VarnodeData>::const_iterator enditer = proto.internalEnd();
while(iter != enditer) {
Address addr = (*iter).getAddr();
int4 sz = (*iter).size;
++iter;
VarnodeLocSet::const_iterator viter = data.beginLoc(sz, addr);
VarnodeLocSet::const_iterator endviter = data.endLoc(sz, addr);
while(viter != endviter) {
Varnode *vn = *viter;
++viter;
list<PcodeOp *>::const_iterator oiter = vn->beginDescend();
while(oiter != vn->endDescend()) {
PcodeOp *op = *oiter;
++oiter;
if (op->code() == CPUI_STORE) {
if (vn->isEventualConstant(3,0)) {
op->setStoreUnmapped();
}
}
}
}
}
return 0;
}
#ifdef TYPEPROP_DEBUG
/// \brief Log a particular data-type propagation action.
///
@ -5399,6 +5382,7 @@ void ActionDatabase::universalAction(Architecture *conf)
actmainloop->addAction( new ActionHeritage("base") );
actmainloop->addAction( new ActionParamDouble("protorecovery") );
actmainloop->addAction( new ActionSegmentize("base"));
actmainloop->addAction( new ActionInternalStorage("base") );
actmainloop->addAction( new ActionForceGoto("blockrecovery") );
actmainloop->addAction( new ActionDirectWrite("protorecovery_a", true) );
actmainloop->addAction( new ActionDirectWrite("protorecovery_b", false) );

View file

@ -555,7 +555,6 @@ class ActionDeadCode : public Action {
static bool neverConsumed(Varnode *vn,Funcdata &data);
static void markConsumedParameters(FuncCallSpecs *fc,vector<Varnode *> &worklist);
static uintb gatherConsumedReturn(Funcdata &data);
static bool isEventualConstant(Varnode *vn,int4 addCount,int4 loadCount);
static bool lastChanceLoad(Funcdata &data,vector<Varnode *> &worklist);
public:
ActionDeadCode(const string &g) : Action(0,"deadcode",g) {} ///< Constructor
@ -1036,6 +1035,19 @@ public:
virtual int4 apply(Funcdata &data);
};
/// \brief Check for constants getting written to the stack from \e internal \e storage registers
///
/// The constant is internal to the compiler and its storage location on the stack should not be addressable.
class ActionInternalStorage : public Action {
public:
ActionInternalStorage(const string &g) : Action(rule_onceperfunc,"internalstorage",g) {} ///< Constructor
virtual Action *clone(const ActionGroupList &grouplist) const {
if (!grouplist.contains(getGroup())) return (Action *)0;
return new ActionInternalStorage(getGroup());
}
virtual int4 apply(Funcdata &data);
};
/// \brief A class that holds a data-type traversal state during type propagation
///
/// For a given Varnode, this class iterates all the possible edges its

View file

@ -49,6 +49,7 @@ ElementId ELEM_RESOLVEPROTOTYPE = ElementId("resolveprototype",170);
ElementId ELEM_RETPARAM = ElementId("retparam",171);
ElementId ELEM_RETURNSYM = ElementId("returnsym",172);
ElementId ELEM_UNAFFECTED = ElementId("unaffected",173);
ElementId ELEM_INTERNAL_STORAGE = ElementId("internal_storage",286);
/// \brief Find a ParamEntry matching the given storage Varnode
///
@ -2324,6 +2325,7 @@ ProtoModel::ProtoModel(const string &nm,const ProtoModel &op2)
effectlist = op2.effectlist;
likelytrash = op2.likelytrash;
internalstorage = op2.internalstorage;
injectUponEntry = op2.injectUponEntry;
injectUponReturn = op2.injectUponReturn;
@ -2515,6 +2517,7 @@ void ProtoModel::decode(Decoder &decoder)
injectUponEntry = -1;
injectUponReturn = -1;
likelytrash.clear();
internalstorage.clear();
uint4 elemId = decoder.openElement(ELEM_PROTOTYPE);
for(;;) {
uint4 attribId = decoder.getNextAttributeId();
@ -2612,6 +2615,14 @@ void ProtoModel::decode(Decoder &decoder)
}
decoder.closeElement(subId);
}
else if (subId == ELEM_INTERNAL_STORAGE) {
decoder.openElement();
while(decoder.peekElement() != 0) {
internalstorage.emplace_back();
internalstorage.back().decode(decoder);
}
decoder.closeElement(subId);
}
else if (subId == ELEM_PCODE) {
int4 injectId = glb->pcodeinjectlib->decodeInject("Protomodel : "+name, name,
InjectPayload::CALLMECHANISM_TYPE,decoder);
@ -2631,6 +2642,7 @@ void ProtoModel::decode(Decoder &decoder)
}
sort(effectlist.begin(),effectlist.end(),EffectRecord::compareByAddress);
sort(likelytrash.begin(),likelytrash.end());
sort(internalstorage.begin(),internalstorage.end());
if (!sawlocalrange)
defaultLocalRange();
if (!sawparamrange)
@ -2740,19 +2752,20 @@ void ProtoModelMerged::intersectEffects(const vector<EffectRecord> &efflist)
effectlist.swap(newlist);
}
/// The \e likely-trash locations are intersected. Anything in \b this that is not also in the
/// given \e likely-trash list is removed.
/// \param trashlist is the given \e likely-trash list
void ProtoModelMerged::intersectLikelyTrash(const vector<VarnodeData> &trashlist)
/// The intersection of two containers of register Varnodes is calculated, and the result is
/// placed in the first container, replacing the original contents. The containers must already be sorted.
/// \param regList1 is the first container
/// \param regList2 is the second container
void ProtoModelMerged::intersectRegisters(vector<VarnodeData> &regList1,const vector<VarnodeData> &regList2)
{
vector<VarnodeData> newlist;
int4 i=0;
int4 j=0;
while((i<likelytrash.size())&&(j<trashlist.size())) {
const VarnodeData &trs1( likelytrash[i] );
const VarnodeData &trs2( trashlist[j] );
while((i<regList1.size())&&(j<regList2.size())) {
const VarnodeData &trs1( regList1[i] );
const VarnodeData &trs2( regList2[j] );
if (trs1 < trs2)
i += 1;
@ -2764,7 +2777,7 @@ void ProtoModelMerged::intersectLikelyTrash(const vector<VarnodeData> &trashlist
j += 1;
}
}
likelytrash = newlist;
regList1.swap(newlist);
}
/// \param model is the new prototype model to add to the merge
@ -2795,7 +2808,8 @@ void ProtoModelMerged::foldIn(ProtoModel *model)
if ((injectUponEntry != model->injectUponEntry)||(injectUponReturn != model->injectUponReturn))
throw LowlevelError("Cannot merge prototype models with different inject ids");
intersectEffects(model->effectlist);
intersectLikelyTrash(model->likelytrash);
intersectRegisters(likelytrash,model->likelytrash);
intersectRegisters(internalstorage,model->internalstorage);
// Take the union of the localrange and paramrange
set<Range>::const_iterator iter;
for(iter=model->localrange.begin();iter!=model->localrange.end();++iter)

View file

@ -58,6 +58,7 @@ extern ElementId ELEM_RESOLVEPROTOTYPE; ///< Marshaling element \<resolveprototy
extern ElementId ELEM_RETPARAM; ///< Marshaling element \<retparam>
extern ElementId ELEM_RETURNSYM; ///< Marshaling element \<returnsym>
extern ElementId ELEM_UNAFFECTED; ///< Marshaling element \<unaffected>
extern ElementId ELEM_INTERNAL_STORAGE; ///< Marshaling element \<internal_storage>
/// \brief Exception thrown when a prototype can't be modeled properly
struct ParamUnassignedError : public LowlevelError {
@ -749,6 +750,7 @@ class ProtoModel {
const ProtoModel *compatModel; ///< The model \b this is a copy of
vector<EffectRecord> effectlist; ///< List of side-effects
vector<VarnodeData> likelytrash; ///< Storage locations potentially carrying \e trash values
vector<VarnodeData> internalstorage; ///< Registers that hold internal compiler constants
int4 injectUponEntry; ///< Id of injection to perform at beginning of function (-1 means not used)
int4 injectUponReturn; ///< Id of injection to perform after a call to this function (-1 means not used)
RangeList localrange; ///< Memory range(s) of space-based locals
@ -834,6 +836,8 @@ public:
vector<EffectRecord>::const_iterator effectEnd(void) const { return effectlist.end(); } ///< Get an iterator to the last EffectRecord
vector<VarnodeData>::const_iterator trashBegin(void) const { return likelytrash.begin(); } ///< Get an iterator to the first \e likelytrash
vector<VarnodeData>::const_iterator trashEnd(void) const { return likelytrash.end(); } ///< Get an iterator to the last \e likelytrash
vector<VarnodeData>::const_iterator internalBegin(void) const { return internalstorage.begin(); } ///< Get an iterator to the first \e internalstorage
vector<VarnodeData>::const_iterator internalEnd(void) const { return internalstorage.end(); } ///< Get an iterator to the last \e internalstorage
/// \brief Characterize whether the given range overlaps parameter storage
///
@ -1068,7 +1072,7 @@ public:
class ProtoModelMerged : public ProtoModel {
vector<ProtoModel *> modellist; ///< Constituent models being merged
void intersectEffects(const vector<EffectRecord> &efflist); ///< Fold EffectRecords into \b this model
void intersectLikelyTrash(const vector<VarnodeData> &trashlist); ///< Fold \e likelytrash locations into \b this model
static void intersectRegisters(vector<VarnodeData> &regList1,const vector<VarnodeData> &regList2);
public:
ProtoModelMerged(Architecture *g) : ProtoModel(g) {} ///< Constructor
virtual ~ProtoModelMerged(void) {} ///< Destructor
@ -1539,6 +1543,8 @@ public:
vector<EffectRecord>::const_iterator effectEnd(void) const; ///< Get iterator to end of EffectRecord list
vector<VarnodeData>::const_iterator trashBegin(void) const; ///< Get iterator to front of \e likelytrash list
vector<VarnodeData>::const_iterator trashEnd(void) const; ///< Get iterator to end of \e likelytrash list
vector<VarnodeData>::const_iterator internalBegin(void) const { return model->internalBegin(); } ///< Get iterator to front of \e internalstorage list
vector<VarnodeData>::const_iterator internalEnd(void) const { return model->internalEnd(); } ///< Get iterator to end of \e internalstorage list
int4 characterizeAsInputParam(const Address &addr,int4 size) const;
int4 characterizeAsOutput(const Address &addr,int4 size) const;
bool possibleInputParam(const Address &addr,int4 size) const;

View file

@ -1972,6 +1972,8 @@ int4 AncestorRealistic::enterNode(void)
if (!vn->isDirectWrite())
return pop_fail;
}
if (op->isStoreUnmapped())
return pop_fail;
op = vn->getDef();
if (op == (PcodeOp *)0) break;
OpCode opc = op->code();

View file

@ -1266,6 +1266,6 @@ ElementId ELEM_VAL = ElementId("val",8);
ElementId ELEM_VALUE = ElementId("value",9);
ElementId ELEM_VOID = ElementId("void",10);
ElementId ELEM_UNKNOWN = ElementId("XMLunknown",286); // Number serves as next open index
ElementId ELEM_UNKNOWN = ElementId("XMLunknown",287); // Number serves as next open index
} // End namespace ghidra

View file

@ -114,7 +114,8 @@ public:
stop_type_propagation = 0x40, ///< Stop data-type propagation into output from descendants
hold_output = 0x80, ///< Output varnode (of call) should not be removed if it is unread
concat_root = 0x100, ///< Output of \b this is root of a CONCAT tree
no_indirect_collapse = 0x200 ///< Do not collapse \b this INDIRECT (via RuleIndirectCollapse)
no_indirect_collapse = 0x200, ///< Do not collapse \b this INDIRECT (via RuleIndirectCollapse)
store_unmapped = 0x400 ///< If STORE collapses to a stack Varnode, force it to be unmapped
};
private:
TypeOp *opcode; ///< Pointer to class providing behavioral details of the operation
@ -221,6 +222,8 @@ public:
void setStopCopyPropagation(void) { flags |= no_copy_propagation; } ///< Stop COPY propagation through inputs
bool noIndirectCollapse(void) const { return ((addlflags & no_indirect_collapse)!=0); } ///< Check if INDIRECT collapse is possible
void setNoIndirectCollapse(void) { addlflags |= no_indirect_collapse; } ///< Prevent collapse of INDIRECT
bool isStoreUnmapped(void) const { return ((addlflags & store_unmapped)!=0); } ///< Is STORE location supposed to be unmapped
void setStoreUnmapped(void) const { addlflags |= store_unmapped; } ///< Mark that STORE location should be unmapped
/// \brief Return \b true if this LOADs or STOREs from a dynamic \e spacebase pointer
bool usesSpacebasePtr(void) const { return ((flags&PcodeOp::spacebase_ptr)!=0); }
uintm getCseHash(void) const; ///< Return hash indicating possibility of common subexpression elimination

View file

@ -4010,6 +4010,9 @@ int4 RuleStoreVarnode::applyOp(PcodeOp *op,Funcdata &data)
data.opRemoveInput(op,1);
data.opRemoveInput(op,0);
data.opSetOpcode(op, CPUI_COPY );
if (op->isStoreUnmapped()) {
data.getScopeLocal()->markNotMapped(baseoff, offoff, size, false);
}
return 1;
}

View file

@ -831,6 +831,55 @@ bool Varnode::isConstantExtended(uint8 *val) const
return false;
}
/// Recursively check if the Varnode is either:
/// - Copied or extended from a constant
/// - The result of arithmetic or logical operations on constants
/// - Loaded from a pointer that is a constant
///
/// \param maxBinary is the maximum depth of binary operations to inspect (before giving up)
/// \param maxLoad is the maximum number of CPUI_LOAD operations to allow in a sequence
/// \return \b true if the Varnode (might) collapse to a constant
bool Varnode::isEventualConstant(int4 maxBinary,int4 maxLoad) const
{
const Varnode *curVn = this;
while(!curVn->isConstant()) {
if (!curVn->isWritten()) return false;
const PcodeOp *op = curVn->getDef();
switch(op->code()) {
case CPUI_LOAD:
if (maxLoad == 0) return false;
maxLoad -= 1;
curVn = op->getIn(1);
break;
case CPUI_INT_ADD:
case CPUI_INT_SUB:
case CPUI_INT_XOR:
case CPUI_INT_OR:
case CPUI_INT_AND:
if (maxBinary == 0) return false;
if (!op->getIn(0)->isEventualConstant(maxBinary-1,maxLoad))
return false;
return op->getIn(1)->isEventualConstant(maxBinary-1,maxLoad);
case CPUI_INT_ZEXT:
case CPUI_INT_SEXT:
case CPUI_COPY:
curVn = op->getIn(0);
break;
case CPUI_INT_LEFT:
case CPUI_INT_RIGHT:
case CPUI_INT_SRIGHT:
case CPUI_INT_MULT:
if (!op->getIn(1)->isConstant()) return false;
curVn = op->getIn(0);
break;
default:
return false;
}
}
return true;
}
/// Make an initial determination of the Datatype of this Varnode. If a Datatype is already
/// set and locked return it. Otherwise look through all the read PcodeOps and the write PcodeOp
/// to determine if the Varnode is getting used as an \b int, \b float, or \b pointer, etc.

View file

@ -291,6 +291,8 @@ public:
}
bool isConstantExtended(uint8 *val) const; ///< Is \b this an (extended) constant
bool isEventualConstant(int4 maxBinary,int4 maxLoad) const; ///< Will \b this Varnode ultimately collapse to a constant
/// Return \b true if this Varnode is linked into the SSA tree
bool isHeritageKnown(void) const { return ((flags&(Varnode::insert|Varnode::constant|Varnode::annotation))!=0); }
bool isTypeLock(void) const { return ((flags&Varnode::typelock)!=0); } ///< Does \b this have a locked Datatype?