mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 02:09:44 +02:00
931 lines
31 KiB
C++
931 lines
31 KiB
C++
/* ###
|
|
* IP: GHIDRA
|
|
*
|
|
* 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.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include "condexe.hh"
|
|
|
|
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)
|
|
return false;
|
|
matchflip = (res == complementary);
|
|
if (op->isBooleanFlip())
|
|
matchflip = !matchflip;
|
|
if (iop->isBooleanFlip())
|
|
matchflip = !matchflip;
|
|
return true;
|
|
}
|
|
|
|
/// \brief Calculate boolean array of all address spaces that have had a heritage pass run.
|
|
///
|
|
/// Used to test if all the links out of the iblock have been calculated.
|
|
void ConditionalExecution::buildHeritageArray(void)
|
|
|
|
{
|
|
heritageyes.clear();
|
|
Architecture *glb = fd->getArch();
|
|
heritageyes.resize(glb->numSpaces(),false);
|
|
for(int4 i=0;i<glb->numSpaces();++i) {
|
|
AddrSpace *spc = glb->getSpace(i);
|
|
if (spc == (AddrSpace *)0) continue;
|
|
int4 index = spc->getIndex();
|
|
if (!spc->isHeritaged()) continue;
|
|
if (fd->numHeritagePasses(spc) > 0)
|
|
heritageyes[index] = true; // At least one pass has been performed on the space
|
|
}
|
|
}
|
|
|
|
/// \brief Test the most basic requirements on \b iblock
|
|
///
|
|
/// The block must have 2 \b in edges and 2 \b out edges and a final CBRANCH op.
|
|
/// \return \b true if \b iblock matches basic requirements
|
|
bool ConditionalExecution::testIBlock(void)
|
|
|
|
{
|
|
if (iblock->sizeIn() != 2) return false;
|
|
if (iblock->sizeOut() != 2) return false;
|
|
cbranch = iblock->lastOp();
|
|
if (cbranch == (PcodeOp *)0) return false;
|
|
if (cbranch->code() != CPUI_CBRANCH) return false;
|
|
return true;
|
|
}
|
|
|
|
/// \return \b true if configuration between \b initblock and \b iblock is correct
|
|
bool ConditionalExecution::findInitPre(void)
|
|
|
|
{
|
|
FlowBlock *tmp = iblock->getIn(prea_inslot);
|
|
FlowBlock *last = iblock;
|
|
while((tmp->sizeOut()==1)&&(tmp->sizeIn()==1)) {
|
|
last = tmp;
|
|
tmp = tmp->getIn(0);
|
|
}
|
|
if (tmp->sizeOut() != 2) return false;
|
|
initblock = (BlockBasic *)tmp;
|
|
tmp = iblock->getIn(1-prea_inslot);
|
|
while((tmp->sizeOut()==1)&&(tmp->sizeIn()==1))
|
|
tmp = tmp->getIn(0);
|
|
if (tmp != initblock) return false;
|
|
if (initblock == iblock) return false;
|
|
|
|
init2a_true = (initblock->getTrueOut() == last);
|
|
|
|
return true;
|
|
}
|
|
|
|
/// The conditions must always have the same value or always have
|
|
/// complementary values.
|
|
/// \return \b true if the conditions are correlated
|
|
bool ConditionalExecution::verifySameCondition(void)
|
|
|
|
{
|
|
PcodeOp *init_cbranch = initblock->lastOp();
|
|
if (init_cbranch == (PcodeOp *)0) return false;
|
|
if (init_cbranch->code() != CPUI_CBRANCH) return false;
|
|
|
|
BooleanExpressionMatch tester;
|
|
if (!tester.verifyCondition(cbranch,init_cbranch))
|
|
return false;
|
|
|
|
if (tester.getFlip())
|
|
init2a_true = !init2a_true;
|
|
int4 multislot = tester.getMultiSlot();
|
|
if (multislot != -1) {
|
|
// This is a direct split
|
|
directsplit = true;
|
|
posta_outslot = (multislot == prea_inslot) ? 0 : 1;
|
|
if (init2a_true)
|
|
posta_outslot = 1 - posta_outslot;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// The given Varnode is defined by a MULTIEQUAL in \b iblock which must be removed.
|
|
/// Test if this is possible/advisable given a specific p-code op that reads the Varnode
|
|
/// \param vn is the given Varnode
|
|
/// \param op is the given PcodeOp reading the Varnode
|
|
/// \return \b false if it is not possible to move the defining op (because of the given op)
|
|
bool ConditionalExecution::testMultiRead(Varnode *vn,PcodeOp *op)
|
|
|
|
{
|
|
if (op->getParent() == iblock) {
|
|
if (!directsplit) {
|
|
if (op->code() == CPUI_COPY) // The COPY is tested separately
|
|
return true; // If the COPY's output reads can be altered, then -vn- can be altered
|
|
return false;
|
|
}
|
|
}
|
|
if (op->code() == CPUI_RETURN) {
|
|
if ((op->numInput() < 2)||(op->getIn(1) != vn)) return false; // Only test for flow thru to return value
|
|
returnop.push_back(op); // mark that CPUI_RETURN needs special handling
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// The given Varnode is defined by an operation in \b iblock which must be removed.
|
|
/// Test if this is possible/advisable given a specific p-code op that reads the Varnode
|
|
/// \param vn is the given Varnode
|
|
/// \param op is the given PcodeOp reading the Varnode
|
|
/// \return \b false if it is not possible to move the defining op (because of the given op)
|
|
bool ConditionalExecution::testOpRead(Varnode *vn,PcodeOp *op)
|
|
|
|
{
|
|
if (op->getParent() == iblock) return true;
|
|
if ((op->code() == CPUI_RETURN)&&(!directsplit)) {
|
|
if ((op->numInput() < 2)||(op->getIn(1) != vn)) return false; // Only test for flow thru to return value
|
|
PcodeOp *copyop = vn->getDef();
|
|
if (copyop->code() == CPUI_COPY) {
|
|
// Ordinarily, if -vn- is produced by a COPY we want to return false here because the propagation
|
|
// hasn't had time to happen here. But if the flow is into a RETURN this can't propagate, so
|
|
// we allow this as a read that can be altered. (We have to move the COPY)
|
|
Varnode *invn = copyop->getIn(0);
|
|
if (!invn->isWritten()) return false;
|
|
PcodeOp *upop = invn->getDef();
|
|
if ((upop->getParent() == iblock)&&(upop->code() != CPUI_MULTIEQUAL))
|
|
return false;
|
|
returnop.push_back(op);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// \brief Prebuild a replacement MULTIEQUAL for output Varnode of the given PcodeOp in \b posta_block
|
|
///
|
|
/// The new op will hold the same data-flow as the original Varnode once a new
|
|
/// edge into \b posta_block is created.
|
|
/// \param op is the given PcodeOp
|
|
void ConditionalExecution::predefineDirectMulti(PcodeOp *op)
|
|
|
|
{
|
|
PcodeOp *newop = fd->newOp(posta_block->sizeIn()+1,posta_block->getStart());
|
|
Varnode *outvn = op->getOut();
|
|
Varnode *newoutvn;
|
|
newoutvn = fd->newVarnodeOut(outvn->getSize(),outvn->getAddr(),newop);
|
|
fd->opSetOpcode(newop,CPUI_MULTIEQUAL);
|
|
Varnode *vn;
|
|
int4 inslot = iblock->getOutRevIndex(posta_outslot);
|
|
for(int4 i=0;i<posta_block->sizeIn();++i) {
|
|
if (i==inslot)
|
|
vn = op->getIn(1-camethruposta_slot);
|
|
else
|
|
vn = newoutvn;
|
|
fd->opSetInput(newop,vn,i);
|
|
}
|
|
fd->opSetInput(newop,op->getIn(camethruposta_slot),posta_block->sizeIn());
|
|
fd->opInsertBegin(newop,posta_block);
|
|
|
|
// Cache this new data flow holder
|
|
replacement[posta_block->getIndex()] = newoutvn;
|
|
}
|
|
|
|
/// In the \e direct \e split case, MULTIEQUALs in the body block (\b posta_block)
|
|
/// must update their flow to account for \b iblock being removed and a new
|
|
/// block flowing into the body block.
|
|
void ConditionalExecution::adjustDirectMulti(void)
|
|
|
|
{
|
|
list<PcodeOp *>::const_iterator iter;
|
|
PcodeOp *op;
|
|
iter = posta_block->beginOp();
|
|
int4 inslot = iblock->getOutRevIndex(posta_outslot);
|
|
while(iter != posta_block->endOp()) {
|
|
op = *iter++;
|
|
if (op->code() != CPUI_MULTIEQUAL) continue;
|
|
Varnode *vn = op->getIn(inslot);
|
|
if (vn->isWritten()&&(vn->getDef()->getParent() == iblock)) {
|
|
if (vn->getDef()->code() != CPUI_MULTIEQUAL)
|
|
throw LowlevelError("Cannot push non-trivial operation");
|
|
// Flow that stays in iblock, comes from modified side
|
|
fd->opSetInput(op,vn->getDef()->getIn(1-camethruposta_slot),inslot);
|
|
// Flow from unmodified side, forms new branch
|
|
vn = vn->getDef()->getIn(camethruposta_slot);
|
|
}
|
|
fd->opInsertInput(op,vn,op->numInput());
|
|
}
|
|
}
|
|
|
|
/// \brief Create a MULTIEQUAL in the given block that will hold data-flow from the given PcodeOp
|
|
///
|
|
/// A new MULTIEQUAL is created whose inputs are the output of the given PcodeOp
|
|
/// \param op is the PcodeOp whose output will get held
|
|
/// \param bl is the block that will contain the new MULTIEQUAL
|
|
/// \return the output Varnode of the new MULTIEQUAL
|
|
Varnode *ConditionalExecution::getNewMulti(PcodeOp *op,BlockBasic *bl)
|
|
|
|
{
|
|
PcodeOp *newop = fd->newOp(bl->sizeIn(),bl->getStart());
|
|
Varnode *outvn = op->getOut();
|
|
Varnode *newoutvn;
|
|
// Using the original outvn address may cause merge conflicts
|
|
// newoutvn = fd->newVarnodeOut(outvn->getSize(),outvn->getAddr(),newop);
|
|
newoutvn = fd->newUniqueOut(outvn->getSize(),newop);
|
|
fd->opSetOpcode(newop,CPUI_MULTIEQUAL);
|
|
|
|
// We create NEW references to outvn, these refs will get put
|
|
// at the end of the dependency list and will get handled in
|
|
// due course
|
|
for(int4 i=0;i<bl->sizeIn();++i)
|
|
fd->opSetInput(newop,outvn,i);
|
|
|
|
fd->opInsertBegin(newop,bl);
|
|
return newoutvn;
|
|
}
|
|
|
|
/// \brief Find a replacement Varnode for the output of the given PcodeOp that is read in the given block
|
|
///
|
|
/// The replacement Varnode must be valid for everything below (dominated) by the block.
|
|
/// If we can't find a replacement, create one (as a MULTIEQUAL) in the given
|
|
/// block (creating recursion through input blocks). Any new Varnode created is
|
|
/// cached in the \b replacement array so it can get picked up by other calls to this function
|
|
/// for different blocks.
|
|
/// \param op is the given PcodeOp whose output we must replace
|
|
/// \param bl is the given basic block (containing a read of the Varnode)
|
|
/// \return the replacement Varnode
|
|
Varnode *ConditionalExecution::getReplacementRead(PcodeOp *op,BlockBasic *bl)
|
|
|
|
{
|
|
map<int4,Varnode *>::const_iterator iter;
|
|
iter = replacement.find(bl->getIndex());
|
|
if (iter != replacement.end())
|
|
return (*iter).second;
|
|
BlockBasic *curbl = bl;
|
|
// Flow must eventually come through iblock
|
|
while(curbl->getImmedDom() != iblock) {
|
|
curbl = (BlockBasic *)curbl->getImmedDom(); // Get immediate dominator
|
|
if (curbl == (FlowBlock *)0)
|
|
throw LowlevelError("Conditional execution: Could not find dominator");
|
|
}
|
|
iter = replacement.find(curbl->getIndex());
|
|
if (iter != replacement.end()) {
|
|
replacement[bl->getIndex()] = (*iter).second;
|
|
return (*iter).second;
|
|
}
|
|
Varnode *res;
|
|
if (curbl->sizeIn()==1) {
|
|
// Since dominator is iblock, In(0) must be iblock
|
|
// Figure what side of -iblock- we came through
|
|
int4 slot = (curbl->getInRevIndex(0) == posta_outslot) ? camethruposta_slot : 1-camethruposta_slot;
|
|
res = op->getIn(slot);
|
|
}
|
|
else
|
|
res = getNewMulti(op,curbl);
|
|
replacement[curbl->getIndex()] = res;
|
|
if (curbl != bl)
|
|
replacement[bl->getIndex()] = res;
|
|
return res;
|
|
}
|
|
|
|
/// The data-flow for the given op is reproduced in the new control-flow configuration.
|
|
/// After completion of this method, the op can be removed.
|
|
/// \param op is the given PcodeOp
|
|
void ConditionalExecution::doReplacement(PcodeOp *op)
|
|
|
|
{
|
|
if (op->code() == CPUI_COPY) {
|
|
if (op->getOut()->hasNoDescend()) // Verify that this has been dealt with by fixReturnOp
|
|
return;
|
|
// It could be a COPY internal to iblock, we need to remove it like any other op
|
|
}
|
|
replacement.clear();
|
|
if (directsplit)
|
|
predefineDirectMulti(op);
|
|
Varnode *vn = op->getOut();
|
|
list<PcodeOp *>::const_iterator iter = vn->beginDescend();
|
|
while(iter != vn->endDescend()) {
|
|
PcodeOp *readop = *iter;
|
|
int4 slot = readop->getSlot(vn);
|
|
BlockBasic *bl = readop->getParent();
|
|
Varnode *rvn;
|
|
if (bl == iblock) {
|
|
if (directsplit)
|
|
fd->opSetInput(readop,op->getIn(1-camethruposta_slot),slot); // We know op is MULTIEQUAL
|
|
else
|
|
fd->opUnsetInput(readop,slot);
|
|
}
|
|
else {
|
|
if (readop->code() == CPUI_MULTIEQUAL) {
|
|
BlockBasic *inbl = (BlockBasic *)bl->getIn(slot);
|
|
if (inbl == iblock) {
|
|
int4 s = (bl->getInRevIndex(slot) == posta_outslot) ? camethruposta_slot : 1-camethruposta_slot;
|
|
rvn = op->getIn(s);
|
|
}
|
|
else
|
|
rvn = getReplacementRead(op,inbl);
|
|
}
|
|
else
|
|
rvn = getReplacementRead(op,bl);
|
|
fd->opSetInput(readop,rvn,slot);
|
|
}
|
|
// The last descendant is now gone
|
|
iter = vn->beginDescend();
|
|
}
|
|
}
|
|
|
|
/// \brief Reproduce COPY data-flow into RETURN ops affected by the removal of \b iblock
|
|
void ConditionalExecution::fixReturnOp(void)
|
|
|
|
{
|
|
for(int4 i=0;i<returnop.size();++i) {
|
|
PcodeOp *retop = returnop[i];
|
|
Varnode *retvn = retop->getIn(1);
|
|
PcodeOp *iblockop = retvn->getDef();
|
|
Varnode *invn;
|
|
if (iblockop->code() == CPUI_COPY)
|
|
invn = iblockop->getIn(0); // This must either be from MULTIEQUAL or something written outside of iblock
|
|
else
|
|
invn = retvn;
|
|
PcodeOp *newcopyop = fd->newOp(1,retop->getAddr());
|
|
fd->opSetOpcode(newcopyop,CPUI_COPY);
|
|
Varnode *outvn = fd->newVarnodeOut(retvn->getSize(),retvn->getAddr(),newcopyop); // Preserve the CPUI_RETURN storage address
|
|
fd->opSetInput(newcopyop,invn,0);
|
|
fd->opSetInput(retop,outvn,1);
|
|
fd->opInsertBefore(newcopyop,retop);
|
|
}
|
|
}
|
|
|
|
/// \param op is the PcodeOp within \b iblock to test
|
|
/// \return \b true if it is removable
|
|
bool ConditionalExecution::testRemovability(PcodeOp *op)
|
|
|
|
{
|
|
list<PcodeOp *>::const_iterator iter;
|
|
PcodeOp *readop;
|
|
Varnode *vn;
|
|
|
|
if (op->code() == CPUI_MULTIEQUAL) {
|
|
vn = op->getOut();
|
|
for(iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
|
|
readop = *iter;
|
|
if (!testMultiRead(vn,readop))
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
if (op->isFlowBreak() || op->isCall()) return false;
|
|
if ((op->code()==CPUI_LOAD)||(op->code()==CPUI_STORE))
|
|
return false;
|
|
if (op->code()==CPUI_INDIRECT) return false;
|
|
|
|
vn = op->getOut();
|
|
if (vn != (Varnode *)0) {
|
|
bool hasnodescend = true;
|
|
for(iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
|
|
readop = *iter;
|
|
if (!testOpRead(vn,readop))
|
|
return false;
|
|
hasnodescend = false;
|
|
}
|
|
if (hasnodescend && (!heritageyes[vn->getSpace()->getIndex()])) // Check if heritage is performed for this varnode's space
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// The \b iblock has been fixed. Test all control-flow conditions, and test removability
|
|
/// of all ops in the \b iblock.
|
|
/// \return \b true if the configuration can be modified
|
|
bool ConditionalExecution::verify(void)
|
|
|
|
{
|
|
prea_inslot = 0;
|
|
posta_outslot = 0;
|
|
directsplit = false;
|
|
|
|
if (!testIBlock()) return false;
|
|
if (!findInitPre()) return false;
|
|
if (!verifySameCondition()) return false;
|
|
|
|
// Cache some useful values
|
|
iblock2posta_true = (posta_outslot == 1);
|
|
camethruposta_slot = (init2a_true==iblock2posta_true) ? prea_inslot : 1-prea_inslot;
|
|
posta_block = (BlockBasic *)iblock->getOut(posta_outslot);
|
|
postb_block = (BlockBasic *)iblock->getOut(1-posta_outslot);
|
|
|
|
returnop.clear();
|
|
list<PcodeOp *>::const_iterator iter;
|
|
iter = iblock->endOp();
|
|
if (iter != iblock->beginOp())
|
|
--iter; // Skip branch
|
|
while(iter != iblock->beginOp()) {
|
|
--iter;
|
|
if (!testRemovability( *iter ))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Set up for testing ConditionalExecution on multiple iblocks
|
|
/// \param f is the function to do testing on
|
|
ConditionalExecution::ConditionalExecution(Funcdata *f)
|
|
|
|
{
|
|
fd = f;
|
|
buildHeritageArray(); // Cache an array depending on the particular heritage pass
|
|
}
|
|
|
|
/// The given block is tested as a possible \b iblock. If this configuration
|
|
/// works and is not a \b directsplit, \b true is returned.
|
|
/// If the configuration works as a \b directsplit, then recursively check that
|
|
/// its \b posta_block works as an \b iblock. If it does work, keep this
|
|
/// \b iblock, otherwise revert to the \b directsplit configuration. In either
|
|
/// case return \b true. Processing the \b directsplit first may prevent
|
|
/// posta_block from being an \b iblock.
|
|
/// \param ib is the trial \b iblock
|
|
/// \return \b true if (some) configuration is recognized and can be modified
|
|
bool ConditionalExecution::trial(BlockBasic *ib)
|
|
|
|
{
|
|
iblock = ib;
|
|
if (!verify()) return false;
|
|
|
|
PcodeOp *cbranch_copy;
|
|
BlockBasic *initblock_copy;
|
|
BlockBasic *iblock_copy;
|
|
int4 prea_inslot_copy;
|
|
bool init2a_true_copy;
|
|
bool iblock2posta_true_copy;
|
|
int4 camethruposta_slot_copy;
|
|
int4 posta_outslot_copy;
|
|
BlockBasic *posta_block_copy;
|
|
BlockBasic *postb_block_copy;
|
|
bool directsplit_copy;
|
|
|
|
for(;;) {
|
|
if (!directsplit) return true;
|
|
// Save off the data for current iblock
|
|
cbranch_copy = cbranch;
|
|
initblock_copy = initblock;
|
|
iblock_copy = iblock;
|
|
prea_inslot_copy = prea_inslot;
|
|
init2a_true_copy = init2a_true;
|
|
iblock2posta_true_copy = iblock2posta_true;
|
|
camethruposta_slot_copy = camethruposta_slot;
|
|
posta_outslot_copy = posta_outslot;
|
|
posta_block_copy = posta_block;
|
|
postb_block_copy = postb_block;
|
|
directsplit_copy = directsplit;
|
|
|
|
iblock = posta_block;
|
|
if (!verify()) {
|
|
cbranch = cbranch_copy;
|
|
initblock = initblock_copy;
|
|
iblock = iblock_copy;
|
|
prea_inslot = prea_inslot_copy;
|
|
init2a_true = init2a_true_copy;
|
|
iblock2posta_true = iblock2posta_true_copy;
|
|
camethruposta_slot = camethruposta_slot_copy;
|
|
posta_outslot = posta_outslot_copy;
|
|
posta_block = posta_block_copy;
|
|
postb_block = postb_block_copy;
|
|
directsplit = directsplit_copy;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// We assume the last call to verify() returned \b true
|
|
void ConditionalExecution::execute(void)
|
|
|
|
{
|
|
list<PcodeOp *>::iterator iter;
|
|
PcodeOp *op;
|
|
|
|
fixReturnOp(); // Patch any data-flow thru to CPUI_RETURN
|
|
if (!directsplit) {
|
|
iter = iblock->beginOp();
|
|
while(iter != iblock->endOp()) {
|
|
op = *iter++;
|
|
if (!op->isBranch())
|
|
doReplacement(op); // Remove all read refs of op
|
|
fd->opDestroy(op); // Then destroy op
|
|
}
|
|
fd->removeFromFlowSplit(iblock,(posta_outslot != camethruposta_slot));
|
|
}
|
|
else {
|
|
adjustDirectMulti();
|
|
iter = iblock->beginOp();
|
|
while(iter != iblock->endOp()) {
|
|
op = *iter++;
|
|
if (op->code() == CPUI_MULTIEQUAL) { // Only adjust MULTIEQUALs
|
|
doReplacement(op);
|
|
fd->opDestroy(op);
|
|
}
|
|
// Branch stays, other operations stay
|
|
}
|
|
fd->switchEdge(iblock->getIn(camethruposta_slot),iblock,posta_block);
|
|
}
|
|
}
|
|
|
|
int4 ActionConditionalExe::apply(Funcdata &data)
|
|
|
|
{
|
|
bool changethisround;
|
|
int4 numhits = 0;
|
|
int4 i;
|
|
|
|
if (data.hasUnreachableBlocks()) // Conditional execution elimination logic may not work with unreachable blocks
|
|
return 0;
|
|
ConditionalExecution condexe(&data);
|
|
const BlockGraph &bblocks( data.getBasicBlocks() );
|
|
|
|
do {
|
|
changethisround = false;
|
|
for(i=0;i<bblocks.getSize();++i) {
|
|
BlockBasic *bb = (BlockBasic *)bblocks.getBlock(i);
|
|
if (condexe.trial(bb)) {
|
|
condexe.execute(); // Adjust dataflow
|
|
numhits += 1;
|
|
changethisround = true;
|
|
}
|
|
}
|
|
} while(changethisround);
|
|
count += numhits; // Number of changes
|
|
return 0;
|
|
}
|
|
|
|
/// \brief Check if \b vn is produced by a 2-branch MULTIEQUAL, one side of which is a zero constant
|
|
///
|
|
/// \param vn is the given Varnode
|
|
/// \return \b true if the expression producing \b vn matches the form
|
|
bool RuleOrPredicate::MultiPredicate::discoverZeroSlot(Varnode *vn)
|
|
|
|
{
|
|
if (!vn->isWritten()) return false;
|
|
op = vn->getDef();
|
|
if (op->code() != CPUI_MULTIEQUAL) return false;
|
|
if (op->numInput() != 2) return false;
|
|
for(zeroSlot=0;zeroSlot<2;++zeroSlot) {
|
|
Varnode *tmpvn = op->getIn(zeroSlot);
|
|
if (!tmpvn->isWritten()) continue;
|
|
PcodeOp *copyop = tmpvn->getDef();
|
|
if (copyop->code() != CPUI_COPY) continue; // Multiequal must have CPUI_COPY input
|
|
Varnode *zerovn = copyop->getIn(0);
|
|
if (!zerovn->isConstant()) continue;
|
|
if (zerovn->getOffset() != 0) continue; // which copies #0
|
|
otherVn = op->getIn(1-zeroSlot); // store off varnode from other path
|
|
if (otherVn->isFree()) return false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// \brief Find CBRANCH operation that determines whether zero is set or not
|
|
///
|
|
/// Assuming that \b op is a 2-branch MULTIEQUAL as per discoverZeroSlot(),
|
|
/// try to find a single CBRANCH whose two \b out edges correspond to the
|
|
/// \b in edges of the MULTIEQUAL. In this case, the boolean expression
|
|
/// controlling the CBRANCH is also controlling whether zero flows into
|
|
/// the MULTIEQUAL output Varnode.
|
|
/// \return \b true if a single controlling CBRANCH is found
|
|
bool RuleOrPredicate::MultiPredicate::discoverCbranch(void)
|
|
|
|
{
|
|
const FlowBlock *baseBlock = op->getParent();
|
|
zeroBlock = baseBlock->getIn(zeroSlot);
|
|
const FlowBlock *otherBlock = baseBlock->getIn(1-zeroSlot);
|
|
if (zeroBlock->sizeOut() == 1) {
|
|
if (zeroBlock->sizeIn() != 1) return false;
|
|
condBlock = zeroBlock->getIn(0);
|
|
}
|
|
else if (zeroBlock->sizeOut() == 2)
|
|
condBlock = zeroBlock;
|
|
else
|
|
return false;
|
|
if (condBlock->sizeOut() != 2) return false;
|
|
if (otherBlock->sizeOut() == 1) {
|
|
if (otherBlock->sizeIn() != 1) return false;
|
|
if (condBlock != otherBlock->getIn(0)) return false;
|
|
}
|
|
else if (otherBlock->sizeOut() == 2) {
|
|
if (condBlock != otherBlock) return false;
|
|
}
|
|
else
|
|
return false;
|
|
cbranch = condBlock->lastOp();
|
|
if (cbranch == (PcodeOp *)0) return false;
|
|
if (cbranch->code() != CPUI_CBRANCH) return false;
|
|
return true;
|
|
}
|
|
|
|
/// \brief Does the \b condBlock \b true outgoing edge flow to the block that sets zero
|
|
///
|
|
/// The \b zeroPathIsTrue variable is set based on the current configuration
|
|
void RuleOrPredicate::MultiPredicate::discoverPathIsTrue(void)
|
|
|
|
{
|
|
if (condBlock->getTrueOut() == zeroBlock)
|
|
zeroPathIsTrue = true;
|
|
else if (condBlock->getFalseOut() == zeroBlock)
|
|
zeroPathIsTrue = false;
|
|
else { // condBlock must be zeroBlock
|
|
zeroPathIsTrue = (condBlock->getTrueOut() == op->getParent()); // True if "true" path does not override zero set
|
|
}
|
|
}
|
|
|
|
/// \brief Verify that CBRANCH boolean expression is either (\b vn == 0) or (\b vn != 0)
|
|
///
|
|
/// Modify \b zeroPathIsTrue so that if it is \b true, then: A \b vn value equal to zero,
|
|
/// causes execution to flow to where the output of MULTIEQUAL is set to zero.
|
|
/// \param vn is the given Varnode
|
|
/// \return \b true if the boolean expression has a matching form
|
|
bool RuleOrPredicate::MultiPredicate::discoverConditionalZero(Varnode *vn)
|
|
|
|
{
|
|
Varnode *boolvn = cbranch->getIn(1);
|
|
if (!boolvn->isWritten()) return false;
|
|
PcodeOp *compareop = boolvn->getDef();
|
|
OpCode opc = compareop->code();
|
|
if (opc == CPUI_INT_NOTEQUAL) // Verify that CBRANCH depends on INT_NOTEQUAL
|
|
zeroPathIsTrue = !zeroPathIsTrue;
|
|
else if (opc != CPUI_INT_EQUAL) // or INT_EQUAL
|
|
return false;
|
|
Varnode *a1 = compareop->getIn(0);
|
|
Varnode *a2 = compareop->getIn(1);
|
|
Varnode *zerovn;
|
|
if (a1 == vn) // Verify one side of compare is vn
|
|
zerovn = a2;
|
|
else if (a2 == vn)
|
|
zerovn = a1;
|
|
else
|
|
return false;
|
|
if (!zerovn->isConstant()) return false;
|
|
if (zerovn->getOffset() != 0) return false; // Verify we are comparing to zero
|
|
if (cbranch->isBooleanFlip())
|
|
zeroPathIsTrue = !zeroPathIsTrue;
|
|
return true;
|
|
}
|
|
|
|
void RuleOrPredicate::getOpList(vector<uint4> &oplist) const
|
|
|
|
{
|
|
oplist.push_back(CPUI_INT_OR);
|
|
oplist.push_back(CPUI_INT_XOR);
|
|
}
|
|
|
|
/// \brief Check for the \e alternate form, tmp1 = (val2 == 0) ? val1 : 0;
|
|
///
|
|
/// We know we have the basic form
|
|
/// \code
|
|
/// tmp1 = cond ? val1 : 0;
|
|
/// result = tmp1 | other;
|
|
/// \endcode
|
|
/// So we just need to check that \b other plays the role of \b val2.
|
|
/// If we match the \e alternate form, perform the simplification
|
|
/// \param vn is the candidate \b other Varnode
|
|
/// \param branch holds the basic form
|
|
/// \param op is the INT_OR p-code op
|
|
/// \param data is the function being analyzed
|
|
/// \return 1 if the form was matched and simplified, 0 otherwise
|
|
int4 RuleOrPredicate::checkSingle(Varnode *vn,MultiPredicate &branch,PcodeOp *op,Funcdata &data)
|
|
|
|
{
|
|
if (vn->isFree()) return 0;
|
|
if (!branch.discoverCbranch()) return 0;
|
|
if (branch.op->getOut()->loneDescend() != op) return 0; // Must only be one use of MULTIEQUAL, because we rewrite it
|
|
branch.discoverPathIsTrue();
|
|
if (!branch.discoverConditionalZero(vn)) return 0;
|
|
if (branch.zeroPathIsTrue) return 0; // true condition (vn == 0) must not go to zero set
|
|
data.opSetInput(branch.op,vn,branch.zeroSlot);
|
|
data.opRemoveInput(op,1);
|
|
data.opSetOpcode(op,CPUI_COPY);
|
|
data.opSetInput(op,branch.op->getOut(),0);
|
|
return 1;
|
|
}
|
|
|
|
int4 RuleOrPredicate::applyOp(PcodeOp *op,Funcdata &data)
|
|
|
|
{
|
|
MultiPredicate branch0;
|
|
MultiPredicate branch1;
|
|
bool test0 = branch0.discoverZeroSlot(op->getIn(0));
|
|
bool test1 = branch1.discoverZeroSlot(op->getIn(1));
|
|
if ((test0==false) && (test1==false)) return 0;
|
|
if (!test0) // branch1 has MULTIEQUAL form, but branch0 does not
|
|
return checkSingle(op->getIn(0),branch1,op,data);
|
|
else if (!test1) // branch0 has MULTIEQUAL form, but branch1 does not
|
|
return checkSingle(op->getIn(1),branch0,op,data);
|
|
if (!branch0.discoverCbranch()) return 0;
|
|
if (!branch1.discoverCbranch()) return 0;
|
|
if (branch0.condBlock == branch1.condBlock) {
|
|
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
|
|
BooleanExpressionMatch condmarker;
|
|
if (!condmarker.verifyCondition(branch0.cbranch,branch1.cbranch)) return 0;
|
|
if (condmarker.getMultiSlot() != -1) return 0;
|
|
branch0.discoverPathIsTrue();
|
|
branch1.discoverPathIsTrue();
|
|
bool finalBool = branch0.zeroPathIsTrue == branch1.zeroPathIsTrue;
|
|
if (condmarker.getFlip())
|
|
finalBool = !finalBool;
|
|
if (finalBool) return 0; // One path hits both zero sets, they must be on different paths
|
|
}
|
|
int4 order = branch0.op->compareOrder(branch1.op);
|
|
if (order == 0) return 0; // can this happen?
|
|
BlockBasic *finalBlock;
|
|
bool slot0SetsBranch0; // True if non-zero setting of branch0 flows throw slot0
|
|
if (order < 0) { // branch1 happens after
|
|
finalBlock = branch1.op->getParent();
|
|
slot0SetsBranch0 = branch1.zeroSlot == 0;
|
|
}
|
|
else { // branch0 happens after
|
|
finalBlock = branch0.op->getParent();
|
|
slot0SetsBranch0 = branch0.zeroSlot == 1;
|
|
}
|
|
PcodeOp *newMulti = data.newOp(2,finalBlock->getStart());
|
|
data.opSetOpcode(newMulti,CPUI_MULTIEQUAL);
|
|
if (slot0SetsBranch0) {
|
|
data.opSetInput(newMulti,branch0.otherVn,0);
|
|
data.opSetInput(newMulti,branch1.otherVn,1);
|
|
}
|
|
else {
|
|
data.opSetInput(newMulti,branch1.otherVn,0);
|
|
data.opSetInput(newMulti,branch0.otherVn,1);
|
|
}
|
|
Varnode *newvn = data.newUniqueOut(branch0.otherVn->getSize(),newMulti);
|
|
data.opInsertBegin(newMulti,finalBlock);
|
|
data.opRemoveInput(op,1);
|
|
data.opSetInput(op,newvn,0);
|
|
data.opSetOpcode(op,CPUI_COPY);
|
|
return 1;
|
|
}
|
|
|
|
} // End namespace ghidra
|