GP-3941 New boolean correlation test

This commit is contained in:
caheckman 2024-01-05 22:44:44 +00:00
parent ad532036ab
commit 8f3328856c
2 changed files with 137 additions and 323 deletions

View file

@ -17,206 +17,7 @@
namespace ghidra {
ConditionMarker::ConditionMarker(void)
{
initop = (PcodeOp *)0;
basevn = (Varnode *)0;
boolvn = (Varnode *)0;
bool2vn = (Varnode *)0;
bool3vn = (Varnode *)0;
binaryop = (PcodeOp *)0;
}
/// Any marks on Varnodes in the root expression are cleared
ConditionMarker::~ConditionMarker(void)
{
basevn->clearMark();
if (boolvn != (Varnode *)0)
boolvn->clearMark();
if (bool2vn != (Varnode *)0)
bool2vn->clearMark();
if (bool3vn != (Varnode *)0)
bool3vn->clearMark();
if (binaryop != (PcodeOp *)0) {
binaryop->getIn(0)->clearMark();
binaryop->getIn(1)->clearMark();
}
}
/// Starting with the CBRANCH, the key Varnodes in the expression producing
/// the boolean value are marked. BOOL_NEGATE operations are traversed, but
/// otherwise only one level of operator is walked.
/// \param op is the root CBRANCH operation
void ConditionMarker::setupInitOp(PcodeOp *op)
{
initop = op;
basevn = op->getIn(1);
Varnode *curvn = basevn;
curvn->setMark();
if (curvn->isWritten()) {
PcodeOp *tmp = curvn->getDef();
if (tmp->code() == CPUI_BOOL_NEGATE) {
boolvn = tmp->getIn(0);
curvn = boolvn;
curvn->setMark();
}
}
if (curvn->isWritten()) {
PcodeOp *tmp = curvn->getDef();
if (tmp->isBoolOutput()&&(tmp->getEvalType()==PcodeOp::binary)) {
binaryop = tmp;
Varnode *binvn = binaryop->getIn(0);
if (!binvn->isConstant()) {
if (binvn->isWritten()) {
PcodeOp *negop = binvn->getDef();
if (negop->code() == CPUI_BOOL_NEGATE) {
if (!negop->getIn(0)->isConstant()) {
bool2vn = negop->getIn(0);
bool2vn->setMark();
}
}
}
binvn->setMark();
}
binvn = binaryop->getIn(1);
if (!binvn->isConstant()) {
if (binvn->isWritten()) {
PcodeOp *negop = binvn->getDef();
if (negop->code() == CPUI_BOOL_NEGATE) {
if (!negop->getIn(0)->isConstant()) {
bool3vn = negop->getIn(0);
bool3vn->setMark();
}
}
}
binvn->setMark();
}
}
}
}
/// Walk the tree rooted at the given p-code op, looking for things marked in
/// the tree rooted at \b initop. Trim everything but BOOL_NEGATE operations,
/// one MULTIEQUAL, and one binary boolean operation. If there is a Varnode
/// in common with the root expression, this is returned, and the tree traversal
/// state holds the path from the boolean value to the common Varnode.
/// \param op is the given CBRANCH op to compare
/// \return the Varnode in common with the root expression or NULL
Varnode *ConditionMarker::findMatch(PcodeOp *op)
{
PcodeOp *curop;
// FlowBlock *bl = op->getParent();
state = 0;
Varnode *curvn = op->getIn(1);
multion = false;
binon = false;
matchflip = op->isBooleanFlip();
for(;;) {
if (curvn->isMark()) return curvn;
bool popstate = true;
if (curvn->isWritten()) {
curop = curvn->getDef();
if (curop->code() == CPUI_BOOL_NEGATE) {
curvn = curop->getIn(0);
if (!binon) // Only flip if we haven't seen binop yet, as binops get compared directly
matchflip = !matchflip;
popstate = false;
}
// else if (curop->code() == CPUI_MULTIEQUAL) {
// if ((curop->getParent()==bl)&&(!multion)) {
// opstate[state] = curop;
// slotstate[state] = 0;
// flipstate[state] = matchflip;
// state += 1;
// curvn = curop->Input(0);
// multion = true;
// popstate = false;
// }
// }
else if (curop->isBoolOutput()&&(curop->getEvalType()==PcodeOp::binary)) {
if (!binon) {
opstate[state] = curop;
slotstate[state] = 0;
flipstate[state] = matchflip;
state += 1;
curvn = curop->getIn(0);
binon = true;
popstate = false;
}
}
}
if (popstate) {
while(state > 0) {
curop = opstate[state-1];
matchflip = flipstate[state-1];
slotstate[state-1] += 1;
if (slotstate[state-1] < curop->numInput()) {
curvn = curop->getIn(slotstate[state-1]);
break;
}
state -= 1;
if (opstate[state]->code() == CPUI_MULTIEQUAL)
multion = false;
else
binon = false;
}
if (state==0) break;
}
}
return (Varnode *)0;
}
/// \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 ConditionMarker::varnodeSame(Varnode *a,Varnode *b)
{
if (a == b) return true;
if (a->isConstant() && b->isConstant())
return (a->getOffset() == b->getOffset());
return false;
}
/// \brief Do the given boolean Varnodes always hold complementary values
///
/// Test if they are constants, 1 and 0, or if one is the direct BOOL_NEGATE of the other.
/// \param a is the first Varnode to compare
/// \param b is the second Varnode to compare
/// \return \b true if the Varnodes (always) hold complementary values
bool ConditionMarker::varnodeComplement(Varnode *a,Varnode *b)
{
if (a->isConstant() && b->isConstant()) {
uintb vala = a->getOffset();
uintb valb = b->getOffset();
if ((vala==0)&&(valb==1)) return true;
if ((vala==1)&&(valb==0)) return true;
return false;
}
PcodeOp *negop;
if (a->isWritten()) {
negop = a->getDef();
if (negop->code() == CPUI_BOOL_NEGATE)
if (negop->getIn(0) == b)
return true;
}
if (b->isWritten()) {
negop = b->getDef();
if (negop->code() == CPUI_BOOL_NEGATE)
if (negop->getIn(0) == a)
return true;
}
return false;
}
const int4 BooleanExpressionMatch::maxDepth = 1;
/// \brief Test if two operations with same opcode produce complementary boolean values
///
@ -225,7 +26,7 @@ bool ConditionMarker::varnodeComplement(Varnode *a,Varnode *b)
/// \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 ConditionMarker::sameOpComplement(PcodeOp *bin1op,PcodeOp *bin2op)
bool BooleanExpressionMatch::sameOpComplement(PcodeOp *bin1op,PcodeOp *bin2op)
{
OpCode opcode = bin1op->code();
@ -256,108 +57,138 @@ bool ConditionMarker::sameOpComplement(PcodeOp *bin1op,PcodeOp *bin2op)
return false;
}
/// \brief Check if given p-code ops are complements where one is an BOOL_AND and the other is an BOOL_OR
/// \brief Do the given Varnodes hold the same value, possibly as constants
///
/// \param bin1op is the first PcodeOp
/// \param bin2op is the second
/// \return \b true if the p-code ops produce complementary values
bool ConditionMarker::andOrComplement(PcodeOp *bin1op,PcodeOp *bin2op)
/// \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 (bin1op->code() == CPUI_BOOL_AND) {
if (bin2op->code() != CPUI_BOOL_OR) return false;
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 if (bin1op->code() == CPUI_BOOL_OR) {
if (bin2op->code() != CPUI_BOOL_AND) return false;
}
else
return false;
// Reaching here, one is AND and one is OR
if (varnodeComplement( bin1op->getIn(0), bin2op->getIn(0))) {
if (varnodeComplement( bin1op->getIn(1), bin2op->getIn(1)))
return true;
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 if (varnodeComplement( bin1op->getIn(0), bin2op->getIn(1))) {
if (varnodeComplement( bin1op->getIn(1), bin2op->getIn(0)))
return true;
else {
pair2 = evaluate(op1->getIn(1),op2->getIn(1),depth-1);
}
return false;
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;
}
/// \brief Determine if the two boolean expressions always produce the same or complementary values
///
/// A common Varnode in the two expressions is given. If the boolean expressions are
/// uncorrelated, \b false is returned, otherwise \b true is returned. If the expressions
/// are correlated but always hold opposite values, the field \b matchflip is set to \b true.
/// \param vn is the common Varnode
/// \return \b true if the expressions are correlated
bool ConditionMarker::finalJudgement(Varnode *vn)
{
if (initop->isBooleanFlip())
matchflip = !matchflip;
if ((vn == basevn)&&(!binon)) // No binary operation involved
return true;
if (boolvn != (Varnode *)0)
matchflip = !matchflip;
if ((vn == boolvn)&&(!binon)) // Negations involved
return true;
if ((binaryop == (PcodeOp *)0)||(!binon))
return false; // Conditions don't match
// Both conditions used binary op
PcodeOp *binary2op = (PcodeOp *)0;
for(int4 i=0;i<state;++i) { // Find the binary op
binary2op = opstate[i];
if (binary2op->isBoolOutput()) break;
}
// Check if the binary ops are exactly the same
if (binaryop->code() == binary2op->code()) {
if (varnodeSame(binaryop->getIn(0),binary2op->getIn(0)) &&
varnodeSame(binaryop->getIn(1),binary2op->getIn(1)))
return true;
if (sameOpComplement(binaryop,binary2op)) {
matchflip = !matchflip;
return true;
else { // Must be CPUI_BOOL_AND and CPUI_BOOL_OR
if (pair1 == complementary && pair2 == complementary)
return complementary; // De Morgan's Law
}
return false;
}
// If not, check if the binary ops are complements of one another
matchflip = !matchflip;
if (andOrComplement(binaryop,binary2op))
return true;
}
}
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 (binaryop->code() != get_booleanflip(binary2op->code(),reorder))
return false;
if (opc1 != get_booleanflip(opc2,reorder))
return uncorrelated;
if (reorder) slot2 = 1;
if (!varnodeSame(binaryop->getIn(slot1),binary2op->getIn(slot2)))
return false;
if (!varnodeSame(binaryop->getIn(1-slot1),binary2op->getIn(1-slot2)))
return false;
return true;
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 ConditionMarker::verifyCondition(PcodeOp *op,PcodeOp *iop)
bool BooleanExpressionMatch::verifyCondition(PcodeOp *op, PcodeOp *iop)
{
setupInitOp(iop);
Varnode *matchvn = findMatch(op);
if (matchvn == (Varnode *)0) return false;
if (!finalJudgement(matchvn)) return false;
// Make final determination of what MULTIEQUAL slot is used
if (!multion)
multislot = -1;
else {
for(int4 i=0;i<state;++i)
if (opstate[i]->code()==CPUI_MULTIEQUAL) {
multislot = slotstate[i];
break;
}
}
int4 res = evaluate(op->getIn(1), iop->getIn(1), maxDepth);
if (res == uncorrelated)
return false;
matchflip = (res == complementary);
if (op->isBooleanFlip())
matchflip = !matchflip;
if (iop->isBooleanFlip())
matchflip = !matchflip;
return true;
}
@ -428,7 +259,7 @@ bool ConditionalExecution::verifySameCondition(void)
if (init_cbranch == (PcodeOp *)0) return false;
if (init_cbranch->code() != CPUI_CBRANCH) return false;
ConditionMarker tester;
BooleanExpressionMatch tester;
if (!tester.verifyCondition(cbranch,init_cbranch))
return false;
@ -1057,7 +888,7 @@ int4 RuleOrPredicate::applyOp(PcodeOp *op,Funcdata &data)
if (branch0.zeroBlock == branch1.zeroBlock) return 0; // zero sets must be along different paths
}
else { // Make sure cbranches have shared condition and the different zero sets have complementary paths
ConditionMarker condmarker;
BooleanExpressionMatch condmarker;
if (!condmarker.verifyCondition(branch0.cbranch,branch1.cbranch)) return 0;
if (condmarker.getMultiSlot() != -1) return 0;
branch0.discoverPathIsTrue();

View file

@ -27,41 +27,24 @@ 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.
///
/// The expression computing the root boolean value for one CBRANCH is marked out
/// by setupInitOp(). For the other CBRANCH, findMatch() tries to find common Varnode
/// in its boolean expression and then maps a critical path from the Varnode to the final boolean.
/// Assuming the common Varnode exists, the method finalJudgement() decides if the two boolean values
/// are the same, uncorrelated, or complements of one another.
class ConditionMarker {
PcodeOp *initop; ///< The root CBRANCH operation to compare against
Varnode *basevn; ///< The boolean Varnode on which the root CBRANCH keys
Varnode *boolvn; ///< If \b basevn is defined by BOOL_NEGATE, this is the unnegated Varnode
Varnode *bool2vn; ///< If the first param to \b binaryop is defined by BOOL_NEGATE, this is the unnegated Varnode
Varnode *bool3vn; ///< If the second param to \b binaryop is defined by BOOL_NEGATE, this is the unnegated Varnode
PcodeOp *binaryop; ///< The binary operator producing the root boolean (if non-null)
/// 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
int4 state; ///< Depth of critical path
PcodeOp *opstate[2]; ///< p-code operations along the critical path
bool flipstate[2]; ///< Boolean negation along the critical path
int4 slotstate[2]; ///< Input Varnode to follow to stay on critical path
bool multion; ///< True if MULTIEQUAL used in condition
bool binon; ///< True if a binary operator is used in condition
int4 multislot; ///< Input slot of MULTIEQUAL on critical path, -1 if no MULTIEQUAL
void setupInitOp(PcodeOp *op); ///< Map out the root boolean expression
Varnode *findMatch(PcodeOp *op); ///< Find a matching Varnode in the root expression producing the given CBRANCH boolean
bool sameOpComplement(PcodeOp *bin1op, PcodeOp *bin2op);
bool andOrComplement(PcodeOp *bin1op, PcodeOp *bin2op);
bool finalJudgement(Varnode *vn);
public:
ConditionMarker(void); ///< Constructor
~ConditionMarker(void); ///< Destructor
bool verifyCondition(PcodeOp *op, PcodeOp *iop); ///< Perform the correlation test on two CBRANCH operations
int4 getMultiSlot(void) const { return multislot; } ///< Get the MULTIEQUAL slot in the critical path
bool getFlip(void) const { return matchflip; } ///< Return \b true is the expressions are anti-correlated
static bool sameOpComplement(PcodeOp *bin1op, PcodeOp *bin2op);
static bool varnodeSame(Varnode *a,Varnode *b);
static bool varnodeComplement(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
bool getFlip(void) const { return matchflip; } ///< Return \b true if the expressions are anti-correlated
};
/// \brief A class for simplifying a series of conditionally executed statements.