diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc index 6110093336..a79e17dedc 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/block.cc @@ -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 &varArray) + +{ + Varnode *vn = varArray[0]; + PcodeOp *op; + list::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;inumInput();++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 &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;iisWritten()) 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 { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh index 556789694e..1844f8a715 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/block.hh @@ -413,6 +413,8 @@ public: list::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 &varArray); ///< Find MULTIEQUAL with given inputs + static bool liftVerifyUnroll(vector &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 diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.cc index 63126e06e3..1c032adad8 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.cc @@ -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;inumInput();++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 &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;isizeIn();++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 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;jfindMultiequal(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) { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh index eafb098972..4c9b6d552d 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh @@ -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 &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 ///