diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index 68656f784e..971b72967e 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -13,6 +13,7 @@ src/decompile/cpp/.gitignore||GHIDRA||||END| src/decompile/cpp/Doxyfile||GHIDRA|||Most of this file is autogenerated by doxygen which falls under the GPL - output from GPL products are NOT GPL! - mjbell4|END| src/decompile/cpp/Makefile||GHIDRA||||END| src/decompile/datatests/boolless.xml||GHIDRA||||END| +src/decompile/datatests/ccmp.xml||GHIDRA||||END| src/decompile/datatests/concat.xml||GHIDRA||||END| src/decompile/datatests/condconst.xml||GHIDRA||||END| src/decompile/datatests/condmulti.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/condexe.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/condexe.cc index 4042af03fe..a4589a5164 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/condexe.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/condexe.cc @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,172 +19,13 @@ namespace ghidra { const int4 BooleanExpressionMatch::maxDepth = 1; -/// \brief Test if two operations with same opcode produce complementary boolean values -/// -/// This only tests for cases where the opcode is INT_LESS or INT_SLESS and one of the -/// inputs is constant. -/// \param bin1op is the first p-code op to compare -/// \param bin2op is the second p-code op to compare -/// \return \b true if the two operations always produce complementary values -bool BooleanExpressionMatch::sameOpComplement(PcodeOp *bin1op,PcodeOp *bin2op) - -{ - OpCode opcode = bin1op->code(); - if ((opcode == CPUI_INT_SLESS)||(opcode==CPUI_INT_LESS)) { - // Basically we test for the scenario like: x < 9 8 < x - int4 constslot = 0; - if (bin1op->getIn(1)->isConstant()) - constslot = 1; - if (!bin1op->getIn(constslot)->isConstant()) return false; - if (!bin2op->getIn(1-constslot)->isConstant()) return false; - if (!varnodeSame(bin1op->getIn(1-constslot),bin2op->getIn(constslot))) return false; - uintb val1 = bin1op->getIn(constslot)->getOffset(); - uintb val2 = bin2op->getIn(1-constslot)->getOffset(); - if (constslot!=0) { - uintb tmp = val2; - val2 = val1; - val1 = tmp; - } - if (val1 + 1 != val2) return false; - if ((val2 == 0)&&(opcode==CPUI_INT_LESS)) return false; // Corner case for unsigned - if (opcode==CPUI_INT_SLESS) { // Corner case for signed - int4 sz = bin1op->getIn(constslot)->getSize(); - if (signbit_negative(val2,sz) && (!signbit_negative(val1,sz))) - return false; - } - return true; - } - return false; -} - -/// \brief Do the given Varnodes hold the same value, possibly as constants -/// -/// \param a is the first Varnode to compare -/// \param b is the second Varnode -/// \return \b true if the Varnodes (always) hold the same value -bool BooleanExpressionMatch::varnodeSame(Varnode *a,Varnode *b) - -{ - if (a == b) return true; - if (a->isConstant() && b->isConstant()) - return (a->getOffset() == b->getOffset()); - return false; -} - -/// \brief Determine if two boolean Varnodes hold related values -/// -/// The values may be the \e same, or opposite of each other (\e complementary). -/// Otherwise the values are \e uncorrelated. The trees constructing each Varnode -/// are examined up to a maximum \b depth. If this is exceeded \e uncorrelated is returned. -/// \param vn1 is the first boolean Varnode -/// \param vn2 is the second boolean Varnode -/// \param depth is the maximum depth to traverse in the evaluation -/// \return the correlation class -int4 BooleanExpressionMatch::evaluate(Varnode *vn1,Varnode *vn2,int4 depth) - -{ - if (vn1 == vn2) return same; - PcodeOp *op1,*op2; - OpCode opc1,opc2; - if (vn1->isWritten()) { - op1 = vn1->getDef(); - opc1 = op1->code(); - if (opc1 == CPUI_BOOL_NEGATE) { - int res = evaluate(op1->getIn(0),vn2,depth); - if (res == same) // Flip same <-> complementary result - res = complementary; - else if (res == complementary) - res = same; - return res; - } - } - else { - op1 = (PcodeOp *)0; // Don't give up before checking if op2 is BOOL_NEGATE - opc1 = CPUI_MAX; - } - if (vn2->isWritten()) { - op2 = vn2->getDef(); - opc2 = op2->code(); - if (opc2 == CPUI_BOOL_NEGATE) { - int4 res = evaluate(vn1,op2->getIn(0),depth); - if (res == same) // Flip same <-> complementary result - res = complementary; - else if (res == complementary) - res = same; - return res; - } - } - else - return uncorrelated; - if (op1 == (PcodeOp *)0) - return uncorrelated; - if (!op1->isBoolOutput() || !op2->isBoolOutput()) - return uncorrelated; - if (depth != 0 && (opc1 == CPUI_BOOL_AND || opc1 == CPUI_BOOL_OR || opc1 == CPUI_BOOL_XOR)) { - if (opc2 == CPUI_BOOL_AND || opc2 == CPUI_BOOL_OR || opc2 == CPUI_BOOL_XOR) { - if (opc1 == opc2 || (opc1 == CPUI_BOOL_AND && opc2 == CPUI_BOOL_OR) || (opc1 == CPUI_BOOL_OR && opc2 == CPUI_BOOL_AND)) { - int4 pair1 = evaluate(op1->getIn(0),op2->getIn(0),depth-1); - int4 pair2; - if (pair1 == uncorrelated) { - pair1 = evaluate(op1->getIn(0),op2->getIn(1),depth-1); // Try other possible pairing (commutative op) - if (pair1 == uncorrelated) - return uncorrelated; - pair2 = evaluate(op1->getIn(1),op2->getIn(0),depth-1); - } - else { - pair2 = evaluate(op1->getIn(1),op2->getIn(1),depth-1); - } - if (pair2 == uncorrelated) - return uncorrelated; - if (opc1 == opc2) { - if (pair1 == same && pair2 == same) - return same; - else if (opc1 == CPUI_BOOL_XOR) { - if (pair1 == complementary && pair2 == complementary) - return same; - return complementary; - } - } - else { // Must be CPUI_BOOL_AND and CPUI_BOOL_OR - if (pair1 == complementary && pair2 == complementary) - return complementary; // De Morgan's Law - } - } - } - } - else { - // Two boolean output ops, compare them directly - if (opc1 == opc2) { - if (varnodeSame(op1->getIn(0),op2->getIn(0)) && varnodeSame(op1->getIn(1),op2->getIn(1))) - return same; - if (sameOpComplement(op1,op2)) { - return complementary; - } - return uncorrelated; - } - // Check if the binary ops are complements of one another - int4 slot1 = 0; - int4 slot2 = 0; - bool reorder; - if (opc1 != get_booleanflip(opc2,reorder)) - return uncorrelated; - if (reorder) slot2 = 1; - if (!varnodeSame(op1->getIn(slot1),op2->getIn(slot2))) - return uncorrelated; - if (!varnodeSame(op1->getIn(1-slot1),op2->getIn(1-slot2))) - return uncorrelated; - return complementary; - } - return uncorrelated; -} - bool BooleanExpressionMatch::verifyCondition(PcodeOp *op, PcodeOp *iop) { - int4 res = evaluate(op->getIn(1), iop->getIn(1), maxDepth); - if (res == uncorrelated) + int4 res = BooleanMatch::evaluate(op->getIn(1), iop->getIn(1), maxDepth); + if (res == BooleanMatch::uncorrelated) return false; - matchflip = (res == complementary); + matchflip = (res == BooleanMatch::complementary); if (op->isBooleanFlip()) matchflip = !matchflip; if (iop->isBooleanFlip()) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/condexe.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/condexe.hh index 873eff41f0..2ac07526af 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/condexe.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/condexe.hh @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,21 +26,9 @@ namespace ghidra { /// /// This class determines if two CBRANCHs share the same condition. It also determines if the conditions /// are complements of each other, and/or they are shared along only one path. -/// -/// Traverse (upto a specific depth) the two boolean expressions consisting of BOOL_AND, BOOL_OR, and -/// BOOL_XOR operations. Leaf operators in the expression can be other operators with boolean output (INT_LESS, -/// INT_SLESS, etc.). class BooleanExpressionMatch { - enum { - same = 1, ///< Pair always hold the same value - complementary = 2, ///< Pair always hold complementary values - uncorrelated = 3 ///< Pair values are uncorrelated - }; static const int4 maxDepth; ///< Maximum depth to trace a boolean expression bool matchflip; ///< True if the compared CBRANCH keys on the opposite boolean value of the root - static bool sameOpComplement(PcodeOp *bin1op, PcodeOp *bin2op); - static bool varnodeSame(Varnode *a,Varnode *b); - static int4 evaluate(Varnode *vn1,Varnode *vn2,int4 depth); public: bool verifyCondition(PcodeOp *op, PcodeOp *iop); ///< Perform the correlation test on two CBRANCH operations int4 getMultiSlot(void) const { return -1; } ///< Get the MULTIEQUAL slot in the critical path diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc index afaf942d4c..a04daff2a5 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc @@ -5472,6 +5472,8 @@ void ActionDatabase::universalAction(Architecture *conf) actprop->addRule( new RuleSlessToLess("analysis") ); actprop->addRule( new RuleZextSless("analysis") ); actprop->addRule( new RuleBitUndistribute("analysis") ); + actprop->addRule( new RuleBooleanUndistribute("analysis") ); + actprop->addRule( new RuleBooleanDedup("analysis") ); actprop->addRule( new RuleBoolZext("analysis") ); actprop->addRule( new RuleBooleanNegate("analysis") ); actprop->addRule( new RuleLogic2Bool("analysis") ); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh index d0f90588a1..a04df11427 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh @@ -123,10 +123,6 @@ class Funcdata { void switchOverJumpTables(const FlowInfo &flow); ///< Convert jump-table addresses to basic block indices void clearJumpTables(void); ///< Clear any jump-table information BlockBasic *nodeSplitBlockEdge(BlockBasic *b,int4 inedge); - PcodeOp *nodeSplitCloneOp(PcodeOp *op); - void nodeSplitCloneVarnode(PcodeOp *op,PcodeOp *newop); - void nodeSplitRawDuplicate(BlockBasic *b,BlockBasic *bprime); - void nodeSplitInputPatch(BlockBasic *b,BlockBasic *bprime,int4 inedge); void sortCallSpecs(void); ///< Sort calls using a dominance based order void deleteCallSpecs(PcodeOp *op); ///< Remove the specification for a particular call @@ -487,6 +483,7 @@ public: Varnode *createStackRef(AddrSpace *spc,uintb off,PcodeOp *op,Varnode *stackptr,bool insertafter); Varnode *opStackLoad(AddrSpace *spc,uintb off,uint4 sz,PcodeOp *op,Varnode *stackptr,bool insertafter); PcodeOp *opStackStore(AddrSpace *spc,uintb off,PcodeOp *op,bool insertafter); + Varnode *opBoolNegate(Varnode *vn,PcodeOp *op,bool insertafter); void opUndoPtradd(PcodeOp *op,bool finalize); ///< Convert a CPUI_PTRADD back into a CPUI_INT_ADD static int4 opFlipInPlaceTest(PcodeOp *op,vector &fliplist); void opFlipInPlaceExecute(vector &fliplist); @@ -618,6 +615,29 @@ public: void setFuncdata(Funcdata *f) { fd = f; } ///< Establish the container for \b this emitter }; +/// \brief Control the cloning of PcodeOps from within a basic block into another block +/// +/// Used for splitting control-flow at a merge point. Can duplicate either a whole basic block, or an expression +/// subset within a basic block. +class CloneBlockOps { + /// \brief Helper class for pairing a p-code op with its clone + struct ClonePair { + PcodeOp *cloneOp; ///< New cloned op + PcodeOp *origOp; ///< Original op that was cloned + ClonePair(PcodeOp *c,PcodeOp *o) { cloneOp = c; origOp = o; } ///< Constructor + }; + Funcdata &data; + vector cloneList; ///< List of cloned ops + map origToClone; ///< Map from original p-code op to its clone + PcodeOp *buildOpClone(PcodeOp *op); ///< Produce a skeleton copy of the given PcodeOp + void buildVarnodeOutput(PcodeOp *origOp,PcodeOp *cloneOp); ///< Clone the output Varnode of the given op onto its clone + void patchInputs(int4 inedge); ///< Set the input Varnodes of all cloned ops +public: + CloneBlockOps(Funcdata &fd) : data(fd) {} ///< Constructor + void cloneBlock(BlockBasic *b,BlockBasic *bprime,int4 inedge); ///< Clone all p-code ops from a block into its copy + Varnode *cloneExpression(vector &ops,PcodeOp *followOp); ///< Clone p-code ops in an expression +}; + /// \brief Helper class for determining if Varnodes can trace their value from a legitimate source /// /// Try to determine if a Varnode (expressed as a particular input to a CALL, CALLIND, or RETURN op) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc index 79791468b3..4caa239adf 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc @@ -833,150 +833,6 @@ BlockBasic *Funcdata::nodeSplitBlockEdge(BlockBasic *b,int4 inedge) return bprime; } -/// \brief Duplicate the given PcodeOp as part of splitting a block -/// -/// Make a basic clone of the p-code op copying its basic control-flow properties -/// \param op is the given PcodeOp -/// \return the cloned op -PcodeOp *Funcdata::nodeSplitCloneOp(PcodeOp *op) - -{ - PcodeOp *dup; - - if (op->isBranch()) { - if (op->code() != CPUI_BRANCH) - throw LowlevelError("Cannot duplicate 2-way or n-way branch in nodeplit"); - return (PcodeOp *)0; - } - dup = newOp(op->numInput(),op->getAddr()); - opSetOpcode(dup,op->code()); - uint4 fl = op->flags & (PcodeOp::startbasic | PcodeOp::nocollapse | - PcodeOp::startmark); - dup->setFlag(fl); - return dup; -} - -/// \brief Duplicate output Varnode of the given p-code op, as part of splitting a block -/// -/// Make a basic clone of the Varnode and its basic flags. The clone is created -/// as an output of a previously cloned PcodeOp. -/// \param op is the given op whose output should be cloned -/// \param newop is the cloned version -void Funcdata::nodeSplitCloneVarnode(PcodeOp *op,PcodeOp *newop) - -{ - Varnode *opvn = op->getOut(); - Varnode *newvn; - - if (opvn == (Varnode *)0) return; - newvn = newVarnodeOut(opvn->getSize(),opvn->getAddr(),newop); - uint4 vflags = opvn->getFlags(); - vflags &= (Varnode::externref | Varnode::volatil | Varnode::incidental_copy | - Varnode::readonly | Varnode::persist | - Varnode::addrtied | Varnode::addrforce); - newvn->setFlags(vflags); -} - -/// \brief Clone all p-code ops from a block into its copy -/// -/// P-code in a basic block is cloned into the split version of the block. -/// Only the output Varnodes are cloned, not the inputs. -/// \param b is the original basic block -/// \param bprime is the cloned block -void Funcdata::nodeSplitRawDuplicate(BlockBasic *b,BlockBasic *bprime) - -{ - PcodeOp *b_op,*prime_op; - list::iterator iter; - - for(iter=b->beginOp();iter!=b->endOp();++iter) { - b_op = *iter; - prime_op = nodeSplitCloneOp(b_op); - if (prime_op == (PcodeOp *)0) continue; - nodeSplitCloneVarnode(b_op,prime_op); - opInsertEnd(prime_op,bprime); - } -} - -/// \brief Patch Varnode inputs to p-code ops in split basic block -/// -/// Map Varnodes that are inputs for PcodeOps in the original basic block to the -/// input slots of the cloned ops in the split block. Constants and code ref Varnodes -/// need to be duplicated, other Varnodes are shared between the ops. This routine -/// also pulls an input Varnode out of riginal MULTIEQUAL ops and adds it back -/// to the cloned MULTIEQUAL ops. -/// \param b is the original basic block -/// \param bprime is the split clone of the block -/// \param inedge is the incoming edge index that was split on -void Funcdata::nodeSplitInputPatch(BlockBasic *b,BlockBasic *bprime,int4 inedge) - -{ - list::iterator biter,piter; - PcodeOp *bop,*pop; - Varnode *bvn,*pvn; - map btop; // Map from b to bprime - vector pind; // pop needing b input - vector bind; // bop giving input - vector pslot; // slot within pop needing b input - - biter = b->beginOp(); - piter = bprime->beginOp(); - - while(piter != bprime->endOp()) { - bop = *biter; - pop = *piter; - btop[bop] = pop; // Establish mapping - if (bop->code() == CPUI_MULTIEQUAL) { - pop->setNumInputs(1); // One edge now goes into bprime - opSetOpcode(pop,CPUI_COPY); - opSetInput(pop,bop->getIn(inedge),0); - opRemoveInput(bop,inedge); // One edge is removed from b - if (bop->numInput() == 1) - opSetOpcode(bop,CPUI_COPY); - } - else if (bop->code() == CPUI_INDIRECT) { - throw LowlevelError("Can't handle INDIRECTs in nodesplit"); - } - else if (bop->isCall()) { - throw LowlevelError("Can't handle CALLs in nodesplit"); - } - else { - for(int4 i=0;inumInput();++i) { - bvn = bop->getIn(i); - if (bvn->isConstant()) - pvn = newConstant(bvn->getSize(),bvn->getOffset()); - else if (bvn->isAnnotation()) - pvn = newCodeRef(bvn->getAddr()); - else if (bvn->isFree()) - throw LowlevelError("Can't handle free varnode in nodesplit"); - else { - if (bvn->isWritten()) { - if (bvn->getDef()->getParent() == b) { - pind.push_back(pop); // Need a cross reference - bind.push_back(bvn->getDef()); - pslot.push_back(i); - continue; - } - else - pvn = bvn; - } - else - pvn = bvn; - } - opSetInput(pop,pvn,i); - } - } - ++piter; - ++biter; - } - - for(int4 i=0;igetOut(),pslot[i]); - } -} - /// \brief Split control-flow into a basic block, duplicating its p-code into a new block /// /// P-code is duplicated into another block, and control-flow is modified so that the new @@ -1000,10 +856,8 @@ void Funcdata::nodeSplit(BlockBasic *b,int4 inedge) // Create duplicate block BlockBasic *bprime = nodeSplitBlockEdge(b,inedge); - // Make copy of b's ops - nodeSplitRawDuplicate(b,bprime); - // Patch up inputs based on split - nodeSplitInputPatch(b,bprime,inedge); + CloneBlockOps cloner(*this); + cloner.cloneBlock(b, bprime, inedge); // Copy b's ops into bprime // We would need to patch outputs here for the more general // case when b has out edges @@ -1087,4 +941,152 @@ void Funcdata::spliceBlockBasic(BlockBasic *bl) structureReset(); } +/// Make a basic clone of the p-code op copying its basic control-flow properties. +/// In the case of a \e branch, the p-code op is not cloned and null is returned. +/// \param op is the given PcodeOp +/// \return the cloned op or null +PcodeOp *CloneBlockOps::buildOpClone(PcodeOp *op) + +{ + PcodeOp *dup; + + if (op->isBranch()) { + if (op->code() != CPUI_BRANCH) + throw LowlevelError("Cannot duplicate 2-way or n-way branch in nodeplit"); + return (PcodeOp *)0; + } + dup = data.newOp(op->numInput(),op->getAddr()); + data.opSetOpcode(dup,op->code()); + uint4 fl = op->flags & (PcodeOp::startbasic | PcodeOp::nocollapse | PcodeOp::startmark | + PcodeOp::nonprinting | PcodeOp::halt | PcodeOp::badinstruction | PcodeOp::unimplemented | + PcodeOp::noreturn | PcodeOp::missing | PcodeOp::indirect_creation | PcodeOp::indirect_store | + PcodeOp::no_indirect_collapse | PcodeOp::calculated_bool | PcodeOp::ptrflow); + dup->setFlag(fl); + fl = op->addlflags & (PcodeOp::special_prop | PcodeOp::special_print | PcodeOp::incidental_copy | + PcodeOp::is_cpool_transformed | PcodeOp::stop_type_propagation | PcodeOp::store_unmapped); + dup->setAdditionalFlag(fl); + + cloneList.emplace_back(dup,op); // Map from clone to orig + origToClone[op] = dup; // Map from orig to clone + return dup; +} + +/// Make a basic clone of the Varnode and its flags. The clone is created +/// as an output of a previously cloned PcodeOp. +/// \param op is the given op whose output should be cloned +/// \param newop is the cloned version +void CloneBlockOps::buildVarnodeOutput(PcodeOp *origOp,PcodeOp *cloneOp) + +{ + Varnode *opvn = origOp->getOut(); + Varnode *newvn; + + if (opvn == (Varnode *)0) return; + newvn = data.newVarnodeOut(opvn->getSize(),opvn->getAddr(),cloneOp); + uint4 vflags = opvn->getFlags(); + vflags &= (Varnode::externref | Varnode::volatil | Varnode::incidental_copy | Varnode::readonly | + Varnode::persist | Varnode::addrtied | Varnode::addrforce | Varnode::nolocalalias | Varnode::spacebase | + Varnode::indirect_creation | Varnode::return_address | Varnode::precislo | Varnode::precishi | + Varnode::incidental_copy); + newvn->setFlags(vflags); + uint2 aflags = opvn->addlflags; + aflags &= (Varnode::writemask | Varnode::ptrflow | Varnode::stack_store); + newvn->addlflags |= aflags; +} + +/// P-code in a basic block is cloned into the split version of the block. +/// \param b is the original basic block +/// \param bprime is the cloned block +/// \param inedge is the incoming edge index that was split on +void CloneBlockOps::cloneBlock(BlockBasic *b,BlockBasic *bprime,int4 inedge) + +{ + PcodeOp *origOp,*cloneOp; + list::iterator iter; + + for(iter=b->beginOp();iter!=b->endOp();++iter) { + origOp = *iter; + cloneOp = buildOpClone(origOp); + if (cloneOp == (PcodeOp *)0) continue; + buildVarnodeOutput(origOp,cloneOp); + data.opInsertEnd(cloneOp,bprime); + } + patchInputs(inedge); +} + +/// P-code in the list is cloned right before the given \b followOp. +/// \param ops is the list of ops to clone +/// \param followOp is the point where the cloned ops are inserted +/// \return the output Varnode of the last cloned op +Varnode *CloneBlockOps::cloneExpression(vector &ops,PcodeOp *followOp) + +{ + PcodeOp *origOp,*cloneOp; + for(int4 i=0;igetOut(); +} + +/// Map Varnodes that are inputs for PcodeOps in the original basic block to the input slots of +/// the cloned ops. Constants and code ref Varnodes need to be duplicated, other Varnodes are shared +/// between the ops. This routine also pulls an input Varnode out of original MULTIEQUAL ops and adds +/// it back to the cloned MULTIEQUAL ops. +/// \param inedge is the incoming edge index that was split on +void CloneBlockOps::patchInputs(int4 inedge) + +{ + for(int4 pos=0;poscode() == CPUI_MULTIEQUAL) { + cloneOp->setNumInputs(1); // One edge now goes into the new block + data.opSetOpcode(cloneOp,CPUI_COPY); + data.opSetInput(cloneOp,origOp->getIn(inedge),0); + data.opRemoveInput(origOp,inedge); // One edge is removed from original block + if (origOp->numInput() == 1) + data.opSetOpcode(origOp,CPUI_COPY); + } + else if (origOp->code() == CPUI_INDIRECT) { + throw LowlevelError("Can't clone INDIRECTs"); + } + else if (origOp->isCall()) { + throw LowlevelError("Can't clone CALLs"); + } + else { + for(int4 i=0;inumInput();++i) { + Varnode *origVn = origOp->getIn(i); + Varnode *cloneVn; + if (origVn->isConstant()) + cloneVn = origVn; + else if (origVn->isAnnotation()) + cloneVn = data.newCodeRef(origVn->getAddr()); + else if (origVn->isFree()) + throw LowlevelError("Can't clone free varnode"); + else { + if (origVn->isWritten()) { + map::const_iterator iter = origToClone.find(origVn->getDef()); + if (iter != origToClone.end()) { + cloneVn = (*iter).second->getOut(); + } + else + cloneVn = origVn; + } + else + cloneVn = origVn; + } + data.opSetInput(cloneOp,cloneVn,i); + } + } + } +} + } // End namespace ghidra diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_op.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_op.cc index 1da5e2acb3..eee8778973 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_op.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_op.cc @@ -526,6 +526,25 @@ Varnode *Funcdata::opStackLoad(AddrSpace *spc,uintb off,uint4 sz,PcodeOp *op,Var return res; } +/// \brief Construct the boolean negation of a given boolean Varnode into a temporary register +/// +/// \param vn is the given Varnode +/// \param op is the point at which to insert the BOOL_NEGATE op +/// \return the result Varnode +Varnode *Funcdata::opBoolNegate(Varnode *vn,PcodeOp *op,bool insertafter) + +{ + PcodeOp *negateop = newOp(1,op->getAddr()); + opSetOpcode(negateop,CPUI_BOOL_NEGATE); + Varnode *resvn = newUniqueOut(1,negateop); + opSetInput(negateop,vn,0); + if (insertafter) + opInsertAfter(negateop,op); + else + opInsertBefore(negateop,op); + return resvn; +} + /// Convert the given CPUI_PTRADD into the equivalent CPUI_INT_ADD. This may involve inserting a /// CPUI_INT_MULT PcodeOp. If finalization is requested and a new PcodeOp is needed, the output /// Varnode is marked as \e implicit and has its data-type set diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/op.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/op.cc index d51460be84..e165951fe6 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/op.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/op.cc @@ -1340,4 +1340,163 @@ bool functionalDifference(Varnode *vn1,Varnode *vn2,int4 depth) return false; } +/// \brief Test if two operations with same opcode produce complementary boolean values +/// +/// This only tests for cases where the opcode is INT_LESS or INT_SLESS and one of the +/// inputs is constant. +/// \param bin1op is the first p-code op to compare +/// \param bin2op is the second p-code op to compare +/// \return \b true if the two operations always produce complementary values +bool BooleanMatch::sameOpComplement(PcodeOp *bin1op,PcodeOp *bin2op) + +{ + OpCode opcode = bin1op->code(); + if ((opcode == CPUI_INT_SLESS)||(opcode==CPUI_INT_LESS)) { + // Basically we test for the scenario like: x < 9 8 < x + int4 constslot = 0; + if (bin1op->getIn(1)->isConstant()) + constslot = 1; + if (!bin1op->getIn(constslot)->isConstant()) return false; + if (!bin2op->getIn(1-constslot)->isConstant()) return false; + if (!varnodeSame(bin1op->getIn(1-constslot),bin2op->getIn(constslot))) return false; + uintb val1 = bin1op->getIn(constslot)->getOffset(); + uintb val2 = bin2op->getIn(1-constslot)->getOffset(); + if (constslot!=0) { + uintb tmp = val2; + val2 = val1; + val1 = tmp; + } + if (val1 + 1 != val2) return false; + if ((val2 == 0)&&(opcode==CPUI_INT_LESS)) return false; // Corner case for unsigned + if (opcode==CPUI_INT_SLESS) { // Corner case for signed + int4 sz = bin1op->getIn(constslot)->getSize(); + if (signbit_negative(val2,sz) && (!signbit_negative(val1,sz))) + return false; + } + return true; + } + return false; +} + +/// \brief Do the given Varnodes hold the same value, possibly as constants +/// +/// \param a is the first Varnode to compare +/// \param b is the second Varnode +/// \return \b true if the Varnodes (always) hold the same value +bool BooleanMatch::varnodeSame(Varnode *a,Varnode *b) + +{ + if (a == b) return true; + if (a->isConstant() && b->isConstant()) + return (a->getOffset() == b->getOffset()); + return false; +} + +/// \brief Determine if two boolean Varnodes hold related values +/// +/// The values may be the \e same, or opposite of each other (\e complementary). +/// Otherwise the values are \e uncorrelated. The trees constructing each Varnode +/// are examined up to a maximum \b depth. If this is exceeded \e uncorrelated is returned. +/// \param vn1 is the first boolean Varnode +/// \param vn2 is the second boolean Varnode +/// \param depth is the maximum depth to traverse in the evaluation +/// \return the correlation class +int4 BooleanMatch::evaluate(Varnode *vn1,Varnode *vn2,int4 depth) + +{ + if (vn1 == vn2) return same; + PcodeOp *op1,*op2; + OpCode opc1,opc2; + if (vn1->isWritten()) { + op1 = vn1->getDef(); + opc1 = op1->code(); + if (opc1 == CPUI_BOOL_NEGATE) { + int res = evaluate(op1->getIn(0),vn2,depth); + if (res == same) // Flip same <-> complementary result + res = complementary; + else if (res == complementary) + res = same; + return res; + } + } + else { + op1 = (PcodeOp *)0; // Don't give up before checking if op2 is BOOL_NEGATE + opc1 = CPUI_MAX; + } + if (vn2->isWritten()) { + op2 = vn2->getDef(); + opc2 = op2->code(); + if (opc2 == CPUI_BOOL_NEGATE) { + int4 res = evaluate(vn1,op2->getIn(0),depth); + if (res == same) // Flip same <-> complementary result + res = complementary; + else if (res == complementary) + res = same; + return res; + } + } + else + return uncorrelated; + if (op1 == (PcodeOp *)0) + return uncorrelated; + if (!op1->isBoolOutput() || !op2->isBoolOutput()) + return uncorrelated; + if (depth != 0 && (opc1 == CPUI_BOOL_AND || opc1 == CPUI_BOOL_OR || opc1 == CPUI_BOOL_XOR)) { + if (opc2 == CPUI_BOOL_AND || opc2 == CPUI_BOOL_OR || opc2 == CPUI_BOOL_XOR) { + if (opc1 == opc2 || (opc1 == CPUI_BOOL_AND && opc2 == CPUI_BOOL_OR) || (opc1 == CPUI_BOOL_OR && opc2 == CPUI_BOOL_AND)) { + int4 pair1 = evaluate(op1->getIn(0),op2->getIn(0),depth-1); + int4 pair2; + if (pair1 == uncorrelated) { + pair1 = evaluate(op1->getIn(0),op2->getIn(1),depth-1); // Try other possible pairing (commutative op) + if (pair1 == uncorrelated) + return uncorrelated; + pair2 = evaluate(op1->getIn(1),op2->getIn(0),depth-1); + } + else { + pair2 = evaluate(op1->getIn(1),op2->getIn(1),depth-1); + } + if (pair2 == uncorrelated) + return uncorrelated; + if (opc1 == opc2) { + if (pair1 == same && pair2 == same) + return same; + else if (opc1 == CPUI_BOOL_XOR) { + if (pair1 == complementary && pair2 == complementary) + return same; + return complementary; + } + } + else { // Must be CPUI_BOOL_AND and CPUI_BOOL_OR + if (pair1 == complementary && pair2 == complementary) + return complementary; // De Morgan's Law + } + } + } + } + else { + // Two boolean output ops, compare them directly + if (opc1 == opc2) { + if (varnodeSame(op1->getIn(0),op2->getIn(0)) && varnodeSame(op1->getIn(1),op2->getIn(1))) + return same; + if (sameOpComplement(op1,op2)) { + return complementary; + } + return uncorrelated; + } + // Check if the binary ops are complements of one another + int4 slot1 = 0; + int4 slot2 = 0; + bool reorder; + if (opc1 != get_booleanflip(opc2,reorder)) + return uncorrelated; + if (reorder) slot2 = 1; + if (!varnodeSame(op1->getIn(slot1),op2->getIn(slot2))) + return uncorrelated; + if (!varnodeSame(op1->getIn(1-slot1),op2->getIn(1-slot2))) + return uncorrelated; + return complementary; + } + return uncorrelated; +} + } // End namespace ghidra diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/op.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/op.hh index 72081ad726..a2d5c984ee 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/op.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/op.hh @@ -63,6 +63,7 @@ public: class PcodeOp { friend class BlockBasic; // Just insert_before, insert_after, setOrder friend class Funcdata; + friend class CloneBlockOps; friend class PcodeOpBank; friend class VarnodeBank; // Only uses setInput public: @@ -365,6 +366,23 @@ extern int4 functionalEqualityLevel(Varnode *vn1,Varnode *vn2,Varnode **res1,Var extern bool functionalEquality(Varnode *vn1,Varnode *vn2); extern bool functionalDifference(Varnode *vn1,Varnode *vn2,int4 depth); +/// \brief Static methods for determining if two boolean expressions are the \b same or \b complementary +/// +/// Traverse (upto a specific depth) the two boolean expressions consisting of BOOL_AND, BOOL_OR, and +/// BOOL_XOR operations. Leaf operators in the expression can be other operators with boolean output (INT_LESS, +/// INT_SLESS, etc.). +class BooleanMatch { + static bool sameOpComplement(PcodeOp *bin1op, PcodeOp *bin2op); + static bool varnodeSame(Varnode *a,Varnode *b); +public: + enum { + same = 1, ///< Pair always hold the same value + complementary = 2, ///< Pair always hold complementary values + uncorrelated = 3 ///< Pair values are uncorrelated + }; + static int4 evaluate(Varnode *vn1,Varnode *vn2,int4 depth); +}; + /// Compare PcodeOps (as pointers) first, then slot /// \param op2 is the other edge to compare with \b this /// \return true if \b this should come before the other PcodeOp diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc index 009570af71..f4b3599703 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.cc @@ -2667,6 +2667,256 @@ int4 RuleBitUndistribute::applyOp(PcodeOp *op,Funcdata &data) return 1; } +/// \class RuleBooleanUndistribute +/// \brief Undo distributed BOOL_AND through INT_NOTEQUAL +/// +/// - `A && B != A && C => A && (B != C)` +/// - `A || B == A || C => A || (B == C)` +/// - `A && B == A && C => !A || (B == C)` +void RuleBooleanUndistribute::getOpList(vector &oplist) const + +{ + oplist.push_back(CPUI_INT_EQUAL); + oplist.push_back(CPUI_INT_NOTEQUAL); +} + +/// \brief Test if the two given Varnodes are matching boolean expressions +/// +/// If the expressions are complementary, \b true is still returned, but the boolean parameter +/// is flipped. +/// \param leftVn is the first given expression to match +/// \param rightVn is the second given expression to match +/// \param rightFlip is flipped if the expressions are complementary +/// \return \b true if the expressions match +bool RuleBooleanUndistribute::isMatch(Varnode *leftVn,Varnode *rightVn,bool &rightFlip) + +{ + int4 val = BooleanMatch::evaluate(leftVn,rightVn,1); + if (val == BooleanMatch::same) + return true; + if (val == BooleanMatch::complementary) { + rightFlip = !rightFlip; + return true; + } + return false; +} + +int4 RuleBooleanUndistribute::applyOp(PcodeOp *op,Funcdata &data) + +{ + Varnode *vn0 = op->getIn(0); + if (!vn0->isWritten()) return 0; + Varnode *vn1 = op->getIn(1); + if (!vn1->isWritten()) return 0; + PcodeOp *op0 = vn0->getDef(); + OpCode opc0 = op0->code(); + if (opc0 != CPUI_BOOL_AND && opc0 != CPUI_BOOL_OR) return 0; + PcodeOp *op1 = vn1->getDef(); + OpCode opc1 = op1->code(); + if (opc1 != CPUI_BOOL_AND && opc1 != CPUI_BOOL_OR) return 0; + Varnode *ins[4]; + ins[0] = op0->getIn(0); + ins[1] = op0->getIn(1); + ins[2] = op1->getIn(0); + ins[3] = op1->getIn(1); + if (ins[0]->isFree() || ins[1]->isFree() || ins[2]->isFree() || ins[3]->isFree()) return 0; + bool isflipped[4]; + isflipped[0] = isflipped[1] = isflipped[2] = isflipped[3] = false; + bool centralEqual = (op->code() == CPUI_INT_EQUAL); + if (opc0 == CPUI_BOOL_OR) { + isflipped[0] = !isflipped[0]; + isflipped[1] = !isflipped[1]; + centralEqual = !centralEqual; + } + if (opc1 == CPUI_BOOL_OR) { + isflipped[2] = !isflipped[2]; + isflipped[3] = !isflipped[3]; + centralEqual = !centralEqual; + } + int4 leftSlot,rightSlot; + if (isMatch(ins[0],ins[2],isflipped[2])) { + leftSlot = 0; + rightSlot = 2; + } + else if (isMatch(ins[0],ins[3],isflipped[3])) { + leftSlot = 0; + rightSlot = 3; + } + else if (isMatch(ins[1],ins[2],isflipped[2])) { + leftSlot = 1; + rightSlot = 2; + } + else if (isMatch(ins[1],ins[3],isflipped[3])) { + leftSlot = 1; + rightSlot = 3; + } + else + return 0; + if (isflipped[leftSlot] != isflipped[rightSlot]) return 0; + OpCode combineOpc; + if (centralEqual) { + combineOpc = CPUI_BOOL_OR; + isflipped[leftSlot] = !isflipped[leftSlot]; + } + else { + combineOpc = CPUI_BOOL_AND; + } + Varnode *finalA = ins[leftSlot]; + if (isflipped[leftSlot]) + finalA = data.opBoolNegate(finalA, op, false); + if (isflipped[1-leftSlot]) + centralEqual = !centralEqual; + if (isflipped[5-rightSlot]) + centralEqual = !centralEqual; + Varnode *finalB = ins[1-leftSlot]; + Varnode *finalC = ins[5-rightSlot]; + PcodeOp *eqOp = data.newOp(2,op->getAddr()); + data.opSetOpcode(eqOp, centralEqual ? CPUI_INT_EQUAL : CPUI_INT_NOTEQUAL); + Varnode *tmp1 = data.newUniqueOut(1, eqOp); + data.opSetInput(eqOp,finalB,0); + data.opSetInput(eqOp,finalC,1); + data.opInsertBefore(eqOp, op); + data.opSetOpcode(op, combineOpc); + data.opSetInput(op,tmp1,1); + data.opSetInput(op,finalA,0); + return 1; +} + +/// \class RuleBooleanDedup +/// \brief Remove duplicate clauses in boolean expressions +/// +/// - `(A && B) || (A && C) => A && (B || C)` +/// - `(A || B) && (A || C) => A || (B && C)` +/// - `(A || B) || (!A && C) => A || (B || C)` +/// - `(A && B) && (A && C) => A && (B && C)` +/// - `(A || B) || (A || C) => A || (B || C)` +void RuleBooleanDedup::getOpList(vector &oplist) const + +{ + oplist.push_back(CPUI_BOOL_AND); + oplist.push_back(CPUI_BOOL_OR); +} + +bool RuleBooleanDedup::isMatch(Varnode *leftVn,Varnode *rightVn,bool &isFlip) + +{ + int4 val = BooleanMatch::evaluate(leftVn,rightVn,1); + if (val == BooleanMatch::same) { + isFlip = false; + return true; + } + if (val == BooleanMatch::complementary) { + isFlip = true; + return true; + } + return false; +} + +int4 RuleBooleanDedup::applyOp(PcodeOp *op,Funcdata &data) + +{ + Varnode *vn0 = op->getIn(0); + if (!vn0->isWritten()) return 0; + Varnode *vn1 = op->getIn(1); + if (!vn1->isWritten()) return 0; + PcodeOp *op0 = vn0->getDef(); + OpCode opc0 = op0->code(); + if (opc0 != CPUI_BOOL_AND && opc0 != CPUI_BOOL_OR) return 0; + PcodeOp *op1 = vn1->getDef(); + OpCode opc1 = op1->code(); + if (opc1 != CPUI_BOOL_AND && opc1 != CPUI_BOOL_OR) return 0; + Varnode *ins[4]; + ins[0] = op0->getIn(0); + ins[1] = op0->getIn(1); + ins[2] = op1->getIn(0); + ins[3] = op1->getIn(1); + if (ins[0]->isFree() || ins[1]->isFree() || ins[2]->isFree() || ins[3]->isFree()) return 0; + bool isflipped = false; + Varnode *leftA,*rightA; + Varnode *leftO,*rightO; + if (isMatch(ins[0],ins[2],isflipped)) { + leftA = ins[0]; + rightA = ins[2]; + leftO = ins[1]; + rightO = ins[3]; + } + else if (isMatch(ins[0],ins[3],isflipped)) { + leftA = ins[0]; + rightA = ins[3]; + leftO = ins[1]; + rightO = ins[2]; + } + else if (isMatch(ins[1],ins[2],isflipped)) { + leftA = ins[1]; + rightA = ins[2]; + leftO = ins[0]; + rightO = ins[3]; + } + else if (isMatch(ins[1],ins[3],isflipped)) { + leftA = ins[1]; + rightA = ins[3]; + leftO = ins[0]; + rightO = ins[2]; + } + else + return 0; + OpCode centralOpc = op->code(); + OpCode bcOpc,finalOpc; + Varnode *finalA; + if (isflipped) { + if (centralOpc == CPUI_BOOL_AND && opc0 == CPUI_BOOL_AND && opc1 == CPUI_BOOL_AND) { + // (A && B) && (!A && C) + data.opSetOpcode(op, CPUI_COPY); + data.opRemoveInput(op, 1); + data.opSetInput(op,data.newConstant(1, 0),0); // Whole expression is false + return 1; + } + if (centralOpc == CPUI_BOOL_OR && opc0 == CPUI_BOOL_OR && opc1 == CPUI_BOOL_OR) { + // (A || B) || (!A || C) + data.opSetOpcode(op, CPUI_COPY); + data.opRemoveInput(op, 1); + data.opSetInput(op,data.newConstant(1, 1),0); // Whole expression is true + return 1; + } + if (centralOpc == CPUI_BOOL_OR && opc0 != opc1) { + // (A || B) || (!A && C) + finalA = (opc0 == CPUI_BOOL_OR) ? leftA : rightA; + finalOpc = CPUI_BOOL_OR; + bcOpc = CPUI_BOOL_OR; + } + else { + return 0; + } + } + else { + if (centralOpc == opc0 && centralOpc == opc1) { + // (A && B) && (A && C) or (A || B) || (A || C) + finalA = leftA; + finalOpc = centralOpc; + bcOpc = centralOpc; + } + else if (opc0 == opc1 && centralOpc != opc0) { + // (A && B) || (A && C) or (A || B) && (A || C) + finalA = leftA; + finalOpc = opc0; + bcOpc = centralOpc; + } + else { + return 0; + } + } + PcodeOp *bcOp = data.newOp(2,op->getAddr()); + Varnode *tmp = data.newUniqueOut(1, bcOp); + data.opSetOpcode(bcOp, bcOpc); + data.opSetInput(bcOp, leftO, 0); + data.opSetInput(bcOp, rightO, 1); + data.opInsertBefore(bcOp, op); + data.opSetOpcode(op, finalOpc); + data.opSetInput(op, finalA, 0); + data.opSetInput(op, tmp, 1); + return 1; +} + /// \class RuleBooleanNegate /// \brief Simplify comparisons with boolean values: `V == false => !V, V == true => V` /// @@ -8925,128 +9175,90 @@ int4 RuleNegateNegate::applyOp(PcodeOp *op,Funcdata &data) return 1; } -/// Check if given Varnode is a boolean value and break down its construction. -/// Varnode is assumed to be an input to a MULTIEQUAL -/// \param vn is the given root Varnode -/// \return \b true if it is a boolean expression -bool RuleConditionalMove::BoolExpress::initialize(Varnode *vn) +/// Check if the given Varnode is a boolean value and return the root of the expression. +/// The Varnode is assumed to be an input to a MULTIEQUAL. +/// \param vn is the given Varnode +/// \return null if the Varnode is not a boolean value, otherwise return the root Varnode of the expression +Varnode *RuleConditionalMove::checkBoolean(Varnode *vn) { - if (!vn->isWritten()) return false; - op = vn->getDef(); - opc = op->code(); - switch(opc) { - case CPUI_COPY: - in0 = op->getIn(0); - if (in0->isConstant()) { - optype = 0; - val = in0->getOffset(); - return ((val & ~((uintb)1)) == 0); + if (!vn->isWritten()) return (Varnode *)0; + PcodeOp *op = vn->getDef(); + if (op->isBoolOutput()) { + return vn; + } + if (op->code() == CPUI_COPY) { + vn = op->getIn(0); + if (vn->isConstant()) { + uintb val = vn->getOffset(); + if ((val & ~((uintb)1)) == 0) + return vn; } - return false; - case CPUI_INT_EQUAL: - case CPUI_INT_NOTEQUAL: - case CPUI_INT_SLESS: - case CPUI_INT_SLESSEQUAL: - case CPUI_INT_LESS: - case CPUI_INT_LESSEQUAL: - case CPUI_INT_CARRY: - case CPUI_INT_SCARRY: - case CPUI_INT_SBORROW: - case CPUI_BOOL_XOR: - case CPUI_BOOL_AND: - case CPUI_BOOL_OR: - case CPUI_FLOAT_EQUAL: - case CPUI_FLOAT_NOTEQUAL: - case CPUI_FLOAT_LESS: - case CPUI_FLOAT_LESSEQUAL: - in0 = op->getIn(0); - in1 = op->getIn(1); - optype = 2; - break; - case CPUI_BOOL_NEGATE: - case CPUI_FLOAT_NAN: - in0 = op->getIn(0); - optype = 1; - break; - default: - return false; } - return true; + return (Varnode *)0; } -/// Evaluate if \b this expression can be easily propagated past a merge point. -/// Also can the Varnode be used past the merge, or does its value need to be reconstructed. -/// \param root is the split point -/// \param branch is the block on which the expression exists and after which is the merge +/// \brief Determine if the given expression can be propagated out of the condition +/// +/// If p-code ops contributing to the expression are contained in a conditional branch, they are collected in +/// \b ops to later be pulled out of the branch (via duplication). +/// \param vn is the root of the given expression +/// \param ops will hold the set of ops that need to be duplicated +/// \param root is the block that performs the conditional branch +/// \param branch is the conditional branch /// \return \b true if the expression can be propagated -bool RuleConditionalMove::BoolExpress::evaluatePropagation(FlowBlock *root,FlowBlock *branch) +bool RuleConditionalMove::gatherExpression(Varnode *vn,vector &ops,FlowBlock *root,FlowBlock *branch) { - mustreconstruct = false; - if (optype==0) return true; // Constants can always be propagated + if (vn->isConstant()) return true; // Constants can always be propagated + if (vn->isFree()) return false; + if (vn->isAddrTied()) return false; if (root == branch) return true; // Can always propagate if there is no branch + if (!vn->isWritten()) return true; + PcodeOp *op = vn->getDef(); if (op->getParent() != branch) return true; // Can propagate if value formed before branch - mustreconstruct = true; // Final op is performed in branch, so it must be reconstructed - if (in0->isFree() && !in0->isConstant()) return false; - if (in0->isWritten() && (in0->getDef()->getParent()==branch)) return false; - if (optype == 2) { - if (in1->isFree() && !in1->isConstant()) return false; - if (in1->isWritten() && (in1->getDef()->getParent()==branch)) return false; + ops.push_back(op); + int4 pos = 0; + while(pos < ops.size()) { + op = ops[pos]; + pos += 1; + if (op->getEvalType() == PcodeOp::special) + return false; + for(int4 i=0;inumInput();++i) { + Varnode *in0 = op->getIn(i); + if (in0->isFree() && !in0->isConstant()) return false; + if (in0->isWritten() && (in0->getDef()->getParent()==branch)) { + if (in0->isAddrTied()) return false; // Don't pull out results that can be indirectly addressed + if (in0->loneDescend() != op) return false; // Don't pull out results with more than one use + if (ops.size() >= 4) return false; + ops.push_back(in0->getDef()); + } + } } return true; } -/// Produce the boolean Varnode to use after the merge. +/// Reproduce the bolean expression resulting in the given Varnode. /// Either reuse the existing Varnode or reconstruct it, /// making sure the expression does not depend on data in the branch. /// \param insertop is point at which any reconstruction should be inserted /// \param data is the function being analyzed /// \return the Varnode representing the boolean expression -Varnode *RuleConditionalMove::BoolExpress::constructBool(PcodeOp *insertop,Funcdata &data) +Varnode *RuleConditionalMove::constructBool(Varnode *vn,PcodeOp *insertop,vector &ops,Funcdata &data) { Varnode *resvn; - if (mustreconstruct) { - PcodeOp *newop = data.newOp(optype,op->getAddr()); // Keep the original address - data.opSetOpcode(newop, opc ); - resvn = data.newUniqueOut(1,newop); - if (in0->isConstant()) - in0 = data.newConstant(in0->getSize(),in0->getOffset()); - data.opSetInput(newop,in0,0); - if (optype == 2) { // Binary op - if (in1->isConstant()) - in1 = data.newConstant(in1->getSize(),in1->getOffset()); - data.opSetInput(newop,in1,1); - } - data.opInsertBefore(newop,insertop); + if (!ops.empty()) { + sort(ops.begin(),ops.end(),compareOp); + CloneBlockOps cloner(data); + resvn = cloner.cloneExpression(ops, insertop); } else { - if (optype == 0) - resvn = data.newConstant(1,val); - else - resvn = op->getOut(); + resvn = vn; } return resvn; } -/// \brief Construct the boolean negation of a given boolean Varnode -/// -/// \param vn is the given Varnode -/// \param op is the point at which to insert the BOOL_NEGATE op -/// \param data is the function being analyzed -/// \return the output of the new op -Varnode *RuleConditionalMove::constructNegate(Varnode *vn,PcodeOp *op,Funcdata &data) - -{ - PcodeOp *negateop = data.newOp(1,op->getAddr()); - data.opSetOpcode(negateop,CPUI_BOOL_NEGATE); - Varnode *resvn = data.newUniqueOut(1,negateop); - data.opSetInput(negateop,vn,0); - data.opInsertBefore(negateop,op); - return resvn; -} - /// \class RuleConditionalMove /// \brief Simplify various conditional move situations /// @@ -9079,16 +9291,16 @@ void RuleConditionalMove::getOpList(vector &oplist) const int4 RuleConditionalMove::applyOp(PcodeOp *op,Funcdata &data) { - BoolExpress bool0; - BoolExpress bool1; BlockBasic *bb; FlowBlock *inblock0,*inblock1; FlowBlock *rootblock0,*rootblock1; if (op->numInput() != 2) return 0; // MULTIEQUAL must have exactly 2 inputs - if (!bool0.initialize(op->getIn(0))) return 0; - if (!bool1.initialize(op->getIn(1))) return 0; + Varnode *bool0 = checkBoolean(op->getIn(0)); + if (bool0 == (Varnode *)0) return 0; + Varnode *bool1 = checkBoolean(op->getIn(1)); + if (bool1 == (Varnode *)0) return 0; // Look for the situation // inblock0 @@ -9120,8 +9332,10 @@ int4 RuleConditionalMove::applyOp(PcodeOp *op,Funcdata &data) if (cbranch == (PcodeOp *)0) return 0; if (cbranch->code() != CPUI_CBRANCH) return 0; - if (!bool0.evaluatePropagation(rootblock0,inblock0)) return 0; - if (!bool1.evaluatePropagation(rootblock0,inblock1)) return 0; + vector opList0; + if (!gatherExpression(bool0,opList0,rootblock0,inblock0)) return 0; + vector opList1; + if (!gatherExpression(bool1,opList1,rootblock0,inblock1)) return 0; bool path0istrue; if (rootblock0 != inblock0) @@ -9131,7 +9345,7 @@ int4 RuleConditionalMove::applyOp(PcodeOp *op,Funcdata &data) if (cbranch->isBooleanFlip()) path0istrue = !path0istrue; - if (!bool0.isConstant() && !bool1.isConstant()) { + if (!bool0->isConstant() && !bool1->isConstant()) { if (inblock0 == rootblock0) { Varnode *boolvn = cbranch->getIn(1); bool andorselect = path0istrue; @@ -9147,8 +9361,8 @@ int4 RuleConditionalMove::applyOp(PcodeOp *op,Funcdata &data) data.opUninsert( op ); data.opSetOpcode(op, opc); data.opInsertBegin(op, bb); - Varnode *firstvn = bool0.constructBool(op,data); - Varnode *secondvn = bool1.constructBool(op,data); + Varnode *firstvn = constructBool(bool0,op,opList0,data); + Varnode *secondvn = constructBool(bool1,op,opList1,data); data.opSetInput(op,firstvn,0); data.opSetInput(op,secondvn,1); return 1; @@ -9168,8 +9382,8 @@ int4 RuleConditionalMove::applyOp(PcodeOp *op,Funcdata &data) OpCode opc = andorselect ? CPUI_BOOL_OR : CPUI_BOOL_AND; data.opSetOpcode(op, opc); data.opInsertBegin(op, bb); - Varnode *firstvn = bool1.constructBool(op,data); - Varnode *secondvn = bool0.constructBool(op,data); + Varnode *firstvn = constructBool(bool1,op,opList1,data); + Varnode *secondvn = constructBool(bool0,op,opList0,data); data.opSetInput(op,firstvn,0); data.opSetInput(op,secondvn,1); return 1; @@ -9180,17 +9394,17 @@ int4 RuleConditionalMove::applyOp(PcodeOp *op,Funcdata &data) // Below here some change is being made data.opUninsert( op ); // Changing from MULTIEQUAL, this should be reinserted int4 sz = op->getOut()->getSize(); - if (bool0.isConstant() && bool1.isConstant()) { - if (bool0.getVal() == bool1.getVal()) { + if (bool0->isConstant() && bool1->isConstant()) { + if (bool0->getOffset() == bool1->getOffset()) { data.opRemoveInput(op,1); data.opSetOpcode(op,CPUI_COPY); - data.opSetInput(op, data.newConstant( sz, bool0.getVal() ), 0 ); + data.opSetInput(op, data.newConstant( sz, bool0->getOffset() ), 0 ); data.opInsertBegin(op,bb); } else { data.opRemoveInput(op,1); Varnode *boolvn = cbranch->getIn(1); - bool needcomplement = ( (bool0.getVal()==0) == path0istrue ); + bool needcomplement = ( (bool0->getOffset()==0) == path0istrue ); if (sz == 1) { if (needcomplement) data.opSetOpcode(op,CPUI_BOOL_NEGATE); @@ -9203,32 +9417,32 @@ int4 RuleConditionalMove::applyOp(PcodeOp *op,Funcdata &data) data.opSetOpcode(op,CPUI_INT_ZEXT); data.opInsertBegin(op,bb); if (needcomplement) - boolvn = constructNegate(boolvn,op,data); + boolvn = data.opBoolNegate(boolvn,op,false); data.opSetInput(op,boolvn,0); } } } - else if (bool0.isConstant()) { - bool needcomplement = (path0istrue != (bool0.getVal()!=0)); - OpCode opc = (bool0.getVal()!=0) ? CPUI_BOOL_OR : CPUI_BOOL_AND; + else if (bool0->isConstant()) { + bool needcomplement = (path0istrue != (bool0->getOffset()!=0)); + OpCode opc = (bool0->getOffset()!=0) ? CPUI_BOOL_OR : CPUI_BOOL_AND; data.opSetOpcode(op,opc); data.opInsertBegin(op,bb); Varnode *boolvn = cbranch->getIn(1); if (needcomplement) - boolvn = constructNegate(boolvn,op,data); - Varnode *body1 = bool1.constructBool(op,data); + boolvn = data.opBoolNegate(boolvn,op,false); + Varnode *body1 = constructBool(bool1,op,opList1,data); data.opSetInput(op,boolvn,0); data.opSetInput(op,body1,1); } else { // bool1 must be constant - bool needcomplement = (path0istrue == (bool1.getVal()!=0)); - OpCode opc = (bool1.getVal()!=0) ? CPUI_BOOL_OR : CPUI_BOOL_AND; + bool needcomplement = (path0istrue == (bool1->getOffset()!=0)); + OpCode opc = (bool1->getOffset()!=0) ? CPUI_BOOL_OR : CPUI_BOOL_AND; data.opSetOpcode(op,opc); data.opInsertBegin(op,bb); Varnode *boolvn = cbranch->getIn(1); if (needcomplement) - boolvn = constructNegate(boolvn,op,data); - Varnode *body0 = bool0.constructBool(op,data); + boolvn = data.opBoolNegate(boolvn,op,false); + Varnode *body0 = constructBool(bool0,op,opList0,data); data.opSetInput(op,boolvn,0); data.opSetInput(op,body0,1); } @@ -9312,6 +9526,11 @@ bool RuleIgnoreNan::checkBackForCompare(Varnode *floatVar,Varnode *root) if (!root->isWritten()) return false; PcodeOp *def1 = root->getDef(); if (!def1->isBoolOutput()) return false; + if (def1->code() == CPUI_BOOL_NEGATE) { + Varnode *vn = def1->getIn(0); + if (!vn->isWritten()) return false; + def1 = vn->getDef(); + } if (def1->getOpcode()->isFloatingPointOp()) { if (def1->numInput() != 2) return false; if (functionalEquality(floatVar, def1->getIn(0))) @@ -9321,7 +9540,7 @@ bool RuleIgnoreNan::checkBackForCompare(Varnode *floatVar,Varnode *root) return false; } OpCode opc = def1->code(); - if (opc != CPUI_BOOL_AND || opc != CPUI_BOOL_OR) + if (opc != CPUI_BOOL_AND && opc != CPUI_BOOL_OR) return false; for(int4 i=0;i<2;++i) { Varnode *vn = def1->getIn(i); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh index 4e9c31a5a8..8ddfba110c 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/ruleaction.hh @@ -547,6 +547,28 @@ public: virtual void getOpList(vector &oplist) const; virtual int4 applyOp(PcodeOp *op,Funcdata &data); }; +class RuleBooleanUndistribute : public Rule { + static bool isMatch(Varnode *leftVn,Varnode *rightVn,bool &rightFlip); +public: + RuleBooleanUndistribute(const string &g) : Rule(g, 0, "booleanundistribute") {} ///< Constructor + virtual Rule *clone(const ActionGroupList &grouplist) const { + if (!grouplist.contains(getGroup())) return (Rule *)0; + return new RuleBooleanUndistribute(getGroup()); + } + virtual void getOpList(vector &oplist) const; + virtual int4 applyOp(PcodeOp *op,Funcdata &data); +}; +class RuleBooleanDedup : public Rule { + static bool isMatch(Varnode *leftVn,Varnode *rightVn,bool &isFlip); +public: + RuleBooleanDedup(const string &g) : Rule(g, 0, "booleandedup") {} ///< Constructor + virtual Rule *clone(const ActionGroupList &grouplist) const { + if (!grouplist.contains(getGroup())) return (Rule *)0; + return new RuleBooleanDedup(getGroup()); + } + virtual void getOpList(vector &oplist) const; + virtual int4 applyOp(PcodeOp *op,Funcdata &data); +}; class RuleBooleanNegate : public Rule { public: RuleBooleanNegate(const string &g) : Rule(g, 0, "booleannegate") {} ///< Constructor @@ -1394,23 +1416,10 @@ public: }; class RuleConditionalMove : public Rule { - /// \brief Class for categorizing and rebuilding a boolean expression - class BoolExpress { - int4 optype; ///< 0=constant 1=unary 2=binary - OpCode opc; ///< OpCode constructing the boolean value - PcodeOp *op; ///< PcodeOp constructing the boolean value - uintb val; ///< Value (if boolean is constant) - Varnode *in0; ///< First input - Varnode *in1; ///< Second input - bool mustreconstruct; ///< Must make a copy of final boolean operation - public: - bool isConstant(void) const { return (optype==0); } ///< Return \b true if boolean is a constant - uintb getVal(void) const { return val; } ///< Get the constant boolean value - bool initialize(Varnode *vn); ///< Initialize based on output Varnode - bool evaluatePropagation(FlowBlock *root,FlowBlock *branch); ///< Can this expression be propagated - Varnode *constructBool(PcodeOp *insertop,Funcdata &data); ///< Construct the expression after the merge - }; - static Varnode *constructNegate(Varnode *vn,PcodeOp *op,Funcdata &data); + static Varnode *checkBoolean(Varnode *vn); ///< Check for boolean expression + static bool gatherExpression(Varnode *vn,vector &ops,FlowBlock *root,FlowBlock *branch); + static Varnode *constructBool(Varnode *vn,PcodeOp *insertop,vector &ops,Funcdata &data); ///< Construct the expression after the merge + static bool compareOp(PcodeOp *op0,PcodeOp *op1) { return op0->getSeqNum().getOrder() < op1->getSeqNum().getOrder(); } public: RuleConditionalMove(const string &g) : Rule( g, 0, "conditionalmove") {} ///< Constructor virtual Rule *clone(const ActionGroupList &grouplist) const { diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc index 01ecc71d4f..d8350e28b1 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/typeop.cc @@ -1315,7 +1315,7 @@ Datatype *TypeOpIntSub::getOutputToken(const PcodeOp *op,CastStrategy *castStrat TypeOpIntCarry::TypeOpIntCarry(TypeFactory *t) : TypeOpFunc(t,CPUI_INT_CARRY,"CARRY",TYPE_BOOL,TYPE_UINT) { - opflags = PcodeOp::binary | PcodeOp::commutative; + opflags = PcodeOp::binary | PcodeOp::commutative | PcodeOp::booloutput; addlflags = arithmetic_op; behave = new OpBehaviorIntCarry(); } @@ -1331,7 +1331,8 @@ string TypeOpIntCarry::getOperatorName(const PcodeOp *op) const TypeOpIntScarry::TypeOpIntScarry(TypeFactory *t) : TypeOpFunc(t,CPUI_INT_SCARRY,"SCARRY",TYPE_BOOL,TYPE_INT) { - opflags = PcodeOp::binary | PcodeOp::commutative; + opflags = PcodeOp::binary | PcodeOp::commutative | PcodeOp::booloutput; + addlflags = arithmetic_op; behave = new OpBehaviorIntScarry(); } @@ -1346,7 +1347,7 @@ string TypeOpIntScarry::getOperatorName(const PcodeOp *op) const TypeOpIntSborrow::TypeOpIntSborrow(TypeFactory *t) : TypeOpFunc(t,CPUI_INT_SBORROW,"SBORROW",TYPE_BOOL,TYPE_INT) { - opflags = PcodeOp::binary; + opflags = PcodeOp::binary | PcodeOp::booloutput; addlflags = arithmetic_op; behave = new OpBehaviorIntSborrow(); } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/varnode.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/varnode.hh index a881505f8d..17ca4995d6 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/varnode.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/varnode.hh @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -157,6 +157,7 @@ private: friend class VarnodeBank; friend class Merge; friend class Funcdata; + friend class CloneBlockOps; void updateCover(void) const; ///< Internal function for update coverage information void calcCover(void) const; ///< Turn on the Cover object for this Varnode void clearCover(void) const; ///< Turn off any coverage information diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/ccmp.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/ccmp.xml new file mode 100644 index 0000000000..fc63cc6307 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/ccmp.xml @@ -0,0 +1,26 @@ + + + + + f403012a +f30300aa600640391ff00071800a497a +4c000054a1320394c0035fd6 + + + + + +if \(ptr\[1\] == 0x3c && val < 10\) +if +SBORROW +false +