mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-2980 Analysis for unrolled switch guards
This commit is contained in:
parent
e100c84085
commit
6f49dc939e
4 changed files with 196 additions and 18 deletions
|
@ -2616,6 +2616,70 @@ bool BlockBasic::noInterveningStatement(PcodeOp *first,int4 path,PcodeOp *last)
|
|||
return false;
|
||||
}
|
||||
|
||||
/// If there exists a CPUI_MULTIEQUAL PcodeOp in the given basic block that takes this exact list of Varnodes
|
||||
/// as its inputs, return that PcodeOp. Otherwise return null.
|
||||
/// \param varArray is the exact list of Varnodes
|
||||
/// \param bl is the basic block
|
||||
/// \return the MULTIEQUAL or null
|
||||
PcodeOp *BlockBasic::findMultiequal(const vector<Varnode *> &varArray)
|
||||
|
||||
{
|
||||
Varnode *vn = varArray[0];
|
||||
PcodeOp *op;
|
||||
list<PcodeOp *>::const_iterator iter = vn->beginDescend();
|
||||
for(;;) {
|
||||
op = *iter;
|
||||
if (op->code() == CPUI_MULTIEQUAL && op->getParent() == this)
|
||||
break;
|
||||
++iter;
|
||||
if (iter == vn->endDescend())
|
||||
return (PcodeOp *)0;
|
||||
}
|
||||
for(int4 i=0;i<op->numInput();++i) {
|
||||
if (op->getIn(i) != varArray[i])
|
||||
return (PcodeOp *)0;
|
||||
}
|
||||
return op;
|
||||
}
|
||||
|
||||
/// Each Varnode must be defined by a PcodeOp with the same OpCode. The Varnode, within the array, is replaced
|
||||
/// with the input Varnode in the indicated slot.
|
||||
/// \param varArray is the given array of Varnodes
|
||||
/// \param slot is the indicated slot
|
||||
/// \return \true if all the Varnodes are defined in the same way
|
||||
bool BlockBasic::liftVerifyUnroll(vector<Varnode *> &varArray,int4 slot)
|
||||
|
||||
{
|
||||
OpCode opc;
|
||||
Varnode *cvn;
|
||||
Varnode *vn = varArray[0];
|
||||
if (!vn->isWritten()) return false;
|
||||
PcodeOp *op = vn->getDef();
|
||||
opc = op->code();
|
||||
if (op->numInput() == 2) {
|
||||
cvn = op->getIn(1-slot);
|
||||
if (!cvn->isConstant()) return false;
|
||||
}
|
||||
else
|
||||
cvn = (Varnode *)0;
|
||||
varArray[0] = op->getIn(slot);
|
||||
for(int4 i=1;i<varArray.size();++i) {
|
||||
vn = varArray[i];
|
||||
if (!vn->isWritten()) return false;
|
||||
op = vn->getDef();
|
||||
if (op->code() != opc) return false;
|
||||
|
||||
if (cvn != (Varnode *)0) {
|
||||
Varnode *cvn2 = op->getIn(1-slot);
|
||||
if (!cvn2->isConstant()) return false;
|
||||
if (cvn->getSize() != cvn2->getSize()) return false;
|
||||
if (cvn->getOffset() != cvn2->getOffset()) return false;
|
||||
}
|
||||
varArray[i] = op->getIn(slot);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BlockCopy::printHeader(ostream &s) const
|
||||
|
||||
{
|
||||
|
|
|
@ -413,6 +413,8 @@ public:
|
|||
list<PcodeOp *>::const_iterator endOp(void) const { return op.end(); } ///< Return an iterator to the end of the PcodeOps
|
||||
bool emptyOp(void) const { return op.empty(); } ///< Return \b true if \b block contains no operations
|
||||
static bool noInterveningStatement(PcodeOp *first,int4 path,PcodeOp *last);
|
||||
PcodeOp *findMultiequal(const vector<Varnode *> &varArray); ///< Find MULTIEQUAL with given inputs
|
||||
static bool liftVerifyUnroll(vector<Varnode *> &varArray,int4 slot); ///< Verify given Varnodes are defined with same PcodeOp
|
||||
};
|
||||
|
||||
/// \brief This class is used to mirror the BlockBasic objects in the fixed control-flow graph for a function
|
||||
|
|
|
@ -502,6 +502,46 @@ uintb JumpBasic::backup2Switch(Funcdata *fd,uintb output,Varnode *outvn,Varnode
|
|||
return output;
|
||||
}
|
||||
|
||||
/// If the Varnode has a restricted range due to masking via INT_AND, the maximum value of this range is returned.
|
||||
/// Otherwise, 0 is returned, indicating that the Varnode can take all possible values.
|
||||
/// \param vn is the given Varnode
|
||||
/// \return the maximum value or 0
|
||||
uintb JumpBasic::getMaxValue(Varnode *vn)
|
||||
|
||||
{
|
||||
uintb maxValue = 0; // 0 indicates maximum possible value
|
||||
if (!vn->isWritten())
|
||||
return maxValue;
|
||||
PcodeOp *op = vn->getDef();
|
||||
if (op->code() == CPUI_INT_AND) {
|
||||
Varnode *constvn = op->getIn(1);
|
||||
if (constvn->isConstant()) {
|
||||
maxValue = coveringmask( constvn->getOffset() );
|
||||
maxValue = (maxValue + 1) & calc_mask(vn->getSize());
|
||||
}
|
||||
}
|
||||
else if (op->code() == CPUI_MULTIEQUAL) { // Its possible the AND is duplicated across multiple blocks
|
||||
int4 i;
|
||||
for(i=0;i<op->numInput();++i) {
|
||||
Varnode *subvn = op->getIn(i);
|
||||
if (!subvn->isWritten()) break;
|
||||
PcodeOp *andOp = subvn->getDef();
|
||||
if (andOp->code() != CPUI_INT_AND) break;
|
||||
Varnode *constvn = andOp->getIn(1);
|
||||
if (!constvn->isConstant()) break;
|
||||
if (maxValue < constvn->getOffset())
|
||||
maxValue = constvn->getOffset();
|
||||
}
|
||||
if (i == op->numInput()) {
|
||||
maxValue = coveringmask( maxValue );
|
||||
maxValue = (maxValue + 1) & calc_mask(vn->getSize());
|
||||
}
|
||||
else
|
||||
maxValue = 0;
|
||||
}
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
/// \brief Calculate the initial set of Varnodes that might be switch variables
|
||||
///
|
||||
/// Paths that terminate at the given PcodeOp are calculated and organized
|
||||
|
@ -566,7 +606,8 @@ static bool matching_constants(Varnode *vn1,Varnode *vn2)
|
|||
/// \param path is the specific branch to take from the CBRANCH to reach the switch
|
||||
/// \param rng is the range of values causing the switch path to be taken
|
||||
/// \param v is the Varnode holding the value controlling the CBRANCH
|
||||
GuardRecord::GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &rng,Varnode *v)
|
||||
/// \param unroll is \b true if the guard is duplicated across multiple blocks
|
||||
GuardRecord::GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &rng,Varnode *v,bool unr)
|
||||
|
||||
{
|
||||
cbranch = bOp;
|
||||
|
@ -575,6 +616,7 @@ GuardRecord::GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &
|
|||
range = rng;
|
||||
vn = v;
|
||||
baseVn = quasiCopy(v,bitsPreserved); // Look for varnode whose bits are copied
|
||||
unrolled = unr;
|
||||
}
|
||||
|
||||
/// \brief Determine if \b this guard applies to the given Varnode
|
||||
|
@ -1020,7 +1062,12 @@ void JumpBasic::analyzeGuards(BlockBasic *bl,int4 pathout)
|
|||
else {
|
||||
pathout = -1; // Make sure not to use pathout next time around
|
||||
for(;;) {
|
||||
if (bl->sizeIn() != 1) return; // Assume only 1 path to switch
|
||||
if (bl->sizeIn() != 1) {
|
||||
if (bl->sizeIn() > 1)
|
||||
checkUnrolledGuard(bl, maxpullback, usenzmask);
|
||||
return;
|
||||
}
|
||||
// Only 1 flow path to the switch
|
||||
prevbl = (BlockBasic *)bl->getIn(0);
|
||||
if (prevbl->sizeOut() != 1) break; // Is it possible to deviate from switch path in this block
|
||||
bl = prevbl; // If not, back up to next block
|
||||
|
@ -1077,17 +1124,7 @@ void JumpBasic::calcRange(Varnode *vn,CircleRange &rng) const
|
|||
else if (vn->isWritten() && vn->getDef()->isBoolOutput())
|
||||
rng = CircleRange(0,2,1,1); // Only 0 or 1 possible
|
||||
else { // Should we go ahead and use nzmask in all cases?
|
||||
uintb maxValue = 0; // Every possible value
|
||||
if (vn->isWritten()) {
|
||||
PcodeOp *andop = vn->getDef();
|
||||
if (andop->code() == CPUI_INT_AND) {
|
||||
Varnode *constvn = andop->getIn(1);
|
||||
if (constvn->isConstant()) {
|
||||
maxValue = coveringmask( constvn->getOffset() );
|
||||
maxValue = (maxValue + 1) & calc_mask(vn->getSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
uintb maxValue = getMaxValue(vn);
|
||||
stride = getStride(vn);
|
||||
rng = CircleRange(0,maxValue,vn->getSize(),stride);
|
||||
}
|
||||
|
@ -1203,8 +1240,9 @@ void JumpBasic::markFoldableGuards(void)
|
|||
int4 bitsPreserved;
|
||||
Varnode *baseVn = GuardRecord::quasiCopy(vn, bitsPreserved);
|
||||
for(int4 i=0;i<selectguards.size();++i) {
|
||||
if (selectguards[i].valueMatch(vn,baseVn,bitsPreserved)==0) {
|
||||
selectguards[i].clear(); // Indicate this is not a true guard
|
||||
GuardRecord &guardRecord(selectguards[i]);
|
||||
if (guardRecord.valueMatch(vn,baseVn,bitsPreserved)==0 || guardRecord.isUnrolled()) {
|
||||
guardRecord.clear(); // Indicate this guard was not used or should not be folded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1243,6 +1281,75 @@ bool JumpBasic::flowsOnlyToModel(Varnode *vn,PcodeOp *trailOp)
|
|||
return true;
|
||||
}
|
||||
|
||||
/// All CBRANCHs in addition to flowing to the given block, must also flow to another common block,
|
||||
/// and each boolean value must select between the given block and the common block in the same way.
|
||||
/// If this flow exists, \b true is returned and the boolean Varnode inputs to each CBRANCH are passed back.
|
||||
/// \param varArray will hold the input Varnodes being passed back
|
||||
/// \param bl is the given block
|
||||
/// \return \b true if the common CBRANCH flow exists across all incoming blocks
|
||||
bool JumpBasic::checkCommonCbranch(vector<Varnode *> &varArray,BlockBasic *bl)
|
||||
|
||||
{
|
||||
BlockBasic *curBlock = (BlockBasic *)bl->getIn(0);
|
||||
PcodeOp *op = curBlock->lastOp();
|
||||
if (op == (PcodeOp *)0 || op->code() != CPUI_CBRANCH)
|
||||
return false;
|
||||
int4 outslot = bl->getInRevIndex(0);
|
||||
bool isOpFlip = op->isBooleanFlip();
|
||||
varArray.push_back(op->getIn(1)); // Pass back boolean input to CBRANCH
|
||||
for(int4 i=1;i<bl->sizeIn();++i) {
|
||||
curBlock = (BlockBasic *)bl->getIn(i);
|
||||
op = curBlock->lastOp();
|
||||
if (op == (PcodeOp *)0 || op->code() != CPUI_CBRANCH)
|
||||
return false; // All blocks must end with CBRANCH
|
||||
if (op->isBooleanFlip() != isOpFlip)
|
||||
return false;
|
||||
if (outslot != bl->getInRevIndex(i))
|
||||
return false; // Boolean value must have some meaning
|
||||
varArray.push_back(op->getIn(1)); // Pass back boolean input to CBRANCH
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \brief Check for a guard that has been unrolled across multiple blocks
|
||||
///
|
||||
/// A guard calculation can be duplicated across multiple blocks that all branch to the basic block
|
||||
/// performing the final BRANCHIND. In this case, the switch variable is also duplicated across multiple Varnodes
|
||||
/// that are all inputs to a MULTIEQUAL whose output is used for the final BRANCHIND calculation. This method
|
||||
/// looks for this situation and creates a GuardRecord associated with this MULTIEQUAL output.
|
||||
/// \param bl is the basic block on the path to the switch with multiple incoming flows
|
||||
/// \param maxpullback is the maximum number of times to pull back from the guard CBRANCH to the putative switch variable
|
||||
/// \param usenzmask is \b true if the NZMASK should be used as part of the pull-back operation
|
||||
void JumpBasic::checkUnrolledGuard(BlockBasic *bl,int4 maxpullback,bool usenzmask)
|
||||
|
||||
{
|
||||
vector<Varnode *> varArray;
|
||||
if (!checkCommonCbranch(varArray,bl))
|
||||
return;
|
||||
int4 indpath = bl->getInRevIndex(0);
|
||||
bool toswitchval = (indpath == 1);
|
||||
PcodeOp *cbranch = ((BlockBasic *)bl->getIn(0))->lastOp();
|
||||
if (cbranch->isBooleanFlip())
|
||||
toswitchval = !toswitchval;
|
||||
CircleRange rng(toswitchval);
|
||||
int4 indpathstore = bl->getIn(0)->getFlipPath() ? 1-indpath : indpath;
|
||||
PcodeOp *readOp = cbranch;
|
||||
for(int4 j=0;j<maxpullback;++j) {
|
||||
PcodeOp *multiOp = bl->findMultiequal(varArray);
|
||||
if (multiOp != (PcodeOp *)0) {
|
||||
selectguards.push_back(GuardRecord(cbranch,readOp,indpathstore,rng,multiOp->getOut(),true));
|
||||
}
|
||||
Varnode *markup; // Throw away markup information
|
||||
Varnode *vn = varArray[0];
|
||||
if (!vn->isWritten()) break;
|
||||
PcodeOp *readOp = vn->getDef();
|
||||
vn = rng.pullBack(readOp,&markup,usenzmask);
|
||||
if (vn == (Varnode *)0) break;
|
||||
if (rng.isEmpty()) break;
|
||||
if (!BlockBasic::liftVerifyUnroll(varArray, readOp->getSlot(vn))) break;
|
||||
}
|
||||
}
|
||||
|
||||
bool JumpBasic::foldInOneGuard(Funcdata *fd,GuardRecord &guard,JumpTable *jump)
|
||||
|
||||
{
|
||||
|
|
|
@ -141,13 +141,15 @@ class JumpTable;
|
|||
class GuardRecord {
|
||||
PcodeOp *cbranch; ///< PcodeOp CBRANCH the branches around the switch
|
||||
PcodeOp *readOp; ///< The immediate PcodeOp causing the restriction
|
||||
int4 indpath; ///< Specific CBRANCH path going to the switch
|
||||
CircleRange range; ///< Range of values causing the CBRANCH to take the path to the switch
|
||||
Varnode *vn; ///< The Varnode being restricted
|
||||
Varnode *baseVn; ///< Value being (quasi)copied to the Varnode
|
||||
int4 indpath; ///< Specific CBRANCH path going to the switch
|
||||
int4 bitsPreserved; ///< Number of bits copied (all other bits are zero)
|
||||
CircleRange range; ///< Range of values causing the CBRANCH to take the path to the switch
|
||||
bool unrolled; ///< \b true if guarding CBRANCH is duplicated across multiple blocks
|
||||
public:
|
||||
GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &rng,Varnode *v); ///< Constructor
|
||||
GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &rng,Varnode *v,bool unr=false); ///< Constructor
|
||||
bool isUnrolled(void) const { return unrolled; } ///< Is \b this guard duplicated across multiple blocks
|
||||
PcodeOp *getBranch(void) const { return cbranch; } ///< Get the CBRANCH associated with \b this guard
|
||||
PcodeOp *getReadOp(void) const { return readOp; } ///< Get the PcodeOp immediately causing the restriction
|
||||
int4 getPath(void) const { return indpath; } ///< Get the specific path index going towards the switch
|
||||
|
@ -364,6 +366,7 @@ protected:
|
|||
static bool ispoint(Varnode *vn); ///< Is it possible for the given Varnode to be a switch variable?
|
||||
static int4 getStride(Varnode *vn); ///< Get the step/stride associated with the Varnode
|
||||
static uintb backup2Switch(Funcdata *fd,uintb output,Varnode *outvn,Varnode *invn);
|
||||
static uintb getMaxValue(Varnode *vn); ///< Get maximum value associated with the given Varnode
|
||||
void findDeterminingVarnodes(PcodeOp *op,int4 slot);
|
||||
void analyzeGuards(BlockBasic *bl,int4 pathout);
|
||||
void calcRange(Varnode *vn,CircleRange &rng) const;
|
||||
|
@ -372,6 +375,8 @@ protected:
|
|||
void markFoldableGuards();
|
||||
void markModel(bool val); ///< Mark (or unmark) all PcodeOps involved in the model
|
||||
bool flowsOnlyToModel(Varnode *vn,PcodeOp *trailOp); ///< Check if the given Varnode flows to anything other than \b this model
|
||||
bool checkCommonCbranch(vector<Varnode *> &varArray,BlockBasic *bl); ///< Check that all incoming blocks end with a CBRANCH
|
||||
void checkUnrolledGuard(BlockBasic *bl,int4 maxpullback,bool usenzmask);
|
||||
|
||||
/// \brief Eliminate the given guard to \b this switch
|
||||
///
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue