Subvariable flow into switch

This commit is contained in:
caheckman 2020-02-13 11:30:45 -05:00
parent e6f09b141b
commit 936f541e64
7 changed files with 164 additions and 67 deletions

View file

@ -484,6 +484,24 @@ inline uintb pcode_left(uintb val,int4 sa) {
return val << sa;
}
/// \brief Calculate smallest mask that covers the given value
///
/// Calculcate a mask that covers either the least significant byte, uint2, uint4, or uint8,
/// whatever is smallest.
/// \param val is the given value
/// \return the minimal mask
inline uintb minimalmask(uintb val)
{
if (val > 0xffffffff)
return ~((uintb)0);
if (val > 0xffff)
return 0xffffffff;
if (val > 0xff)
return 0xffff;
return 0xff;
}
extern bool signbit_negative(uintb val,int4 size); ///< Return true if the sign-bit is set
extern uintb calc_mask(int4 size); ///< Calculate a mask for a given byte size
extern uintb uintb_negate(uintb in,int4 size); ///< Negate the \e sized value

View file

@ -3373,7 +3373,7 @@ void ActionDeadCode::markConsumedParameters(FuncCallSpecs *fc,vector<Varnode *>
if (vn->isAutoLive())
consumeVal = ~((uintb)0);
else
consumeVal = minimalMask(vn->getNZMask());
consumeVal = minimalmask(vn->getNZMask());
pushConsumed(consumeVal,vn,worklist);
}
}
@ -3398,7 +3398,7 @@ uintb ActionDeadCode::gatherConsumedReturn(Funcdata &data)
if (returnOp->isDead()) continue;
if (returnOp->numInput() > 1) {
Varnode *vn = returnOp->getIn(1);
consumeVal |= minimalMask(vn->getNZMask());
consumeVal |= minimalmask(vn->getNZMask());
}
}
return consumeVal;
@ -3455,11 +3455,21 @@ int4 ActionDeadCode::apply(Funcdata &data)
continue;
}
else if (!op->isAssignment()) {
if (op->code() == CPUI_RETURN) {
OpCode opc = op->code();
if (opc == CPUI_RETURN) {
pushConsumed(~((uintb)0),op->getIn(0),worklist);
for(i=1;i<op->numInput();++i)
pushConsumed(returnConsume,op->getIn(i),worklist);
}
else if (opc == CPUI_BRANCHIND) {
JumpTable *jt = data.findJumpTable(op);
uintb mask;
if (jt != (JumpTable *)0)
mask = jt->getSwitchVarConsume();
else
mask = ~((uintb)0);
pushConsumed(mask,op->getIn(0),worklist);
}
else {
for(i=0;i<op->numInput();++i)
pushConsumed(~((uintb)0),op->getIn(i),worklist);

View file

@ -543,7 +543,6 @@ class ActionDeadCode : public Action {
static bool neverConsumed(Varnode *vn,Funcdata &data);
static void markConsumedParameters(FuncCallSpecs *fc,vector<Varnode *> &worklist);
static uintb gatherConsumedReturn(Funcdata &data);
static uintb minimalMask(uintb val); ///< Calculate smallest mask that covers the given value
public:
ActionDeadCode(const string &g) : Action(0,"deadcode",g) {} ///< Constructor
virtual Action *clone(const ActionGroupList &grouplist) const {
@ -1077,20 +1076,4 @@ public:
inline bool TermOrder::additiveCompare(const PcodeOpEdge *op1,const PcodeOpEdge *op2) {
return (-1 == op1->getVarnode()->termOrder(op2->getVarnode())); }
/// Calculcate a mask that covers either the least significant byte, uint2, uint4, or uint8,
/// whatever is smallest.
/// \param val is the given value
/// \return the minimal mask
inline uintb ActionDeadCode::minimalMask(uintb val)
{
if (val > 0xffffffff)
return ~((uintb)0);
if (val > 0xffff)
return 0xffffffff;
if (val > 0xff)
return 0xffff;
return 0xff;
}
#endif

View file

@ -1219,23 +1219,18 @@ void JumpBasic::buildLabels(Funcdata *fd,vector<Address> &addresstable,vector<ui
}
}
void JumpBasic::foldInNormalization(Funcdata *fd,PcodeOp *indop)
Varnode *JumpBasic::foldInNormalization(Funcdata *fd,PcodeOp *indop)
{ // Assume normalized and unnormalized values are found
// Fold normalization pcode into indirect branch
// Treat unnormalized value as input CPUI_BRANCHIND
// so it becomes literally the C switch statement
{
// Set the BRANCHIND input to be the unnormalized switch variable, so
// all the intervening code to calculate the final address is eliminated as dead.
fd->opSetInput(indop,switchvn,0);
return switchvn;
}
bool JumpBasic::foldInGuards(Funcdata *fd,JumpTable *jump)
{ // We now think of the BRANCHIND as encompassing
// the guard function, so we "disarm" the guard
// instructions by making the guard condition
// always false. If the simplification removes
// the unusable branches, we are left with only
// one path through the switch
{
bool change = false;
for(int4 i=0;i<selectguards.size();++i) {
PcodeOp *cbranch = selectguards[i].getBranch();
@ -1825,7 +1820,7 @@ void JumpAssisted::buildLabels(Funcdata *fd,vector<Address> &addresstable,vector
label.push_back(0xBAD1ABE1); // Add fake label to match the defaultAddress
}
void JumpAssisted::foldInNormalization(Funcdata *fd,PcodeOp *indop)
Varnode *JumpAssisted::foldInNormalization(Funcdata *fd,PcodeOp *indop)
{
// Replace all outputs of jumpassist op with switchvn (including BRANCHIND)
@ -1837,6 +1832,7 @@ void JumpAssisted::foldInNormalization(Funcdata *fd,PcodeOp *indop)
fd->opSetInput(op,switchvn,0);
}
fd->opDestroy(assistOp); // Get rid of the assist op (it has served its purpose)
return switchvn;
}
bool JumpAssisted::foldInGuards(Funcdata *fd,JumpTable *jump)
@ -1972,6 +1968,7 @@ JumpTable::JumpTable(Architecture *g,Address ad)
jmodel = (JumpModel *)0;
origmodel = (JumpModel *)0;
indirect = (PcodeOp *)0;
switchVarConsume = ~((uintb)0);
mostcommon = ~((uint4)0);
maxtablesize = 1024;
maxaddsub = 1;
@ -1988,6 +1985,7 @@ JumpTable::JumpTable(const JumpTable *op2)
jmodel = (JumpModel *)0;
origmodel = (JumpModel *)0;
indirect = (PcodeOp *)0;
switchVarConsume = ~((uintb)0);
mostcommon = ~((uint4)0);
maxtablesize = op2->maxtablesize;
maxaddsub = op2->maxaddsub;
@ -2081,7 +2079,7 @@ void JumpTable::addBlockToSwitch(BlockBasic *bl,uintb lab)
void JumpTable::switchOver(const FlowInfo &flow)
{ // Convert absolute addresses to block indices
{
FlowBlock *parent,*tmpbl;
uint4 pos;
int4 i,j,count,maxcount;
@ -2116,6 +2114,28 @@ void JumpTable::switchOver(const FlowInfo &flow)
}
}
/// Eliminate any code involved in actually computing the destination address so
/// it looks like the CPUI_BRANCHIND operation does it all internally.
/// \param fd is the function containing \b this switch
void JumpTable::foldInNormalization(Funcdata *fd)
{
Varnode *switchvn = jmodel->foldInNormalization(fd,indirect);
if (switchvn != (Varnode *)0) {
// If possible, mark up the switch variable as not fully consumed so that
// subvariable flow can truncate it.
switchVarConsume = minimalmask(switchvn->getNZMask());
if (switchVarConsume >= calc_mask(switchvn->getSize())) { // If mask covers everything
if (switchvn->isWritten()) {
PcodeOp *op = switchvn->getDef();
if (op->code() == CPUI_INT_SEXT) { // Check for a signed extension
switchVarConsume = calc_mask(op->getIn(0)->getSize()); // Assume the extension is not consumed
}
}
}
}
}
void JumpTable::trivialSwitchOver(void)
{
@ -2259,6 +2279,7 @@ void JumpTable::clear(void)
label.clear();
loadpoints.clear();
indirect = (PcodeOp *)0;
switchVarConsume = ~((uintb)0);
recoverystage = 0;
// -opaddress- -maxtablesize- -maxaddsub- -maxleftright- -maxext- -collectloads- are permanent
}

View file

@ -192,7 +192,22 @@ public:
virtual void buildAddresses(Funcdata *fd,PcodeOp *indop,vector<Address> &addresstable,vector<LoadTable> *loadpoints) const=0;
virtual void findUnnormalized(uint4 maxaddsub,uint4 maxleftright,uint4 maxext)=0;
virtual void buildLabels(Funcdata *fd,vector<Address> &addresstable,vector<uintb> &label,const JumpModel *orig) const=0;
virtual void foldInNormalization(Funcdata *fd,PcodeOp *indop)=0;
/// \brief Do normalization of the given switch specific to \b this model.
///
/// The PcodeOp machinery is removed so it looks like the CPUI_BRANCHIND simply takes the
/// switch variable as an input Varnode and automatically interprets its values to reach
/// the correct destination.
/// \param fd is the function containing the switch
/// \param indop is the given switch as a CPUI_BRANCHIND
/// \return the Varnode holding the final unnormalized switch variable
virtual Varnode *foldInNormalization(Funcdata *fd,PcodeOp *indop)=0;
/// \brief Eliminate any \e guard code involved in computing the switch destination
///
/// We now think of the BRANCHIND as encompassing any guard function.
/// \param fd is the function containing the switch
/// \param jump is the JumpTable owning \b this model.
virtual bool foldInGuards(Funcdata *fd,JumpTable *jump)=0;
virtual bool sanityCheck(Funcdata *fd,PcodeOp *indop,vector<Address> &addresstable)=0;
virtual JumpModel *clone(JumpTable *jt) const=0;
@ -213,7 +228,7 @@ public:
virtual void buildAddresses(Funcdata *fd,PcodeOp *indop,vector<Address> &addresstable,vector<LoadTable> *loadpoints) const;
virtual void findUnnormalized(uint4 maxaddsub,uint4 maxleftright,uint4 maxext) {}
virtual void buildLabels(Funcdata *fd,vector<Address> &addresstable,vector<uintb> &label,const JumpModel *orig) const;
virtual void foldInNormalization(Funcdata *fd,PcodeOp *indop) {}
virtual Varnode *foldInNormalization(Funcdata *fd,PcodeOp *indop) { return (Varnode *)0; }
virtual bool foldInGuards(Funcdata *fd,JumpTable *jump) { return false; }
virtual bool sanityCheck(Funcdata *fd,PcodeOp *indop,vector<Address> &addresstable) { return true; }
virtual JumpModel *clone(JumpTable *jt) const;
@ -241,6 +256,16 @@ protected:
void findSmallestNormal(uint4 matchsize);
void findNormalized(Funcdata *fd,BlockBasic *rootbl,int4 pathout,uint4 matchsize,uint4 maxtablesize);
void markFoldableGuards();
/// \brief Eliminate the given guard to \b this switch
///
/// We \e disarm the guard instructions by making the guard condition
/// always \b false. If the simplification removes the unusable branches,
/// we are left with only one path through the switch.
/// \param fd is the function containing the switch
/// \param guard is a description of the particular guard mechanism
/// \param jump is the JumpTable owning \b this model
/// \return \b true if a change was made to data-flow
virtual bool foldInOneGuard(Funcdata *fd,GuardRecord &guard,JumpTable *jump);
public:
JumpBasic(JumpTable *jt) : JumpModel(jt) { jrange = (JumpValuesRange *)0; }
@ -253,7 +278,7 @@ public:
virtual void buildAddresses(Funcdata *fd,PcodeOp *indop,vector<Address> &addresstable,vector<LoadTable> *loadpoints) const;
virtual void findUnnormalized(uint4 maxaddsub,uint4 maxleftright,uint4 maxext);
virtual void buildLabels(Funcdata *fd,vector<Address> &addresstable,vector<uintb> &label,const JumpModel *orig) const;
virtual void foldInNormalization(Funcdata *fd,PcodeOp *indop);
virtual Varnode *foldInNormalization(Funcdata *fd,PcodeOp *indop);
virtual bool foldInGuards(Funcdata *fd,JumpTable *jump);
virtual bool sanityCheck(Funcdata *fd,PcodeOp *indop,vector<Address> &addresstable);
virtual JumpModel *clone(JumpTable *jt) const;
@ -341,45 +366,54 @@ public:
virtual void buildAddresses(Funcdata *fd,PcodeOp *indop,vector<Address> &addresstable,vector<LoadTable> *loadpoints) const;
virtual void findUnnormalized(uint4 maxaddsub,uint4 maxleftright,uint4 maxext) {}
virtual void buildLabels(Funcdata *fd,vector<Address> &addresstable,vector<uintb> &label,const JumpModel *orig) const;
virtual void foldInNormalization(Funcdata *fd,PcodeOp *indop);
virtual Varnode *foldInNormalization(Funcdata *fd,PcodeOp *indop);
virtual bool foldInGuards(Funcdata *fd,JumpTable *jump);
virtual bool sanityCheck(Funcdata *fd,PcodeOp *indop,vector<Address> &addresstable) { return true; }
virtual JumpModel *clone(JumpTable *jt) const;
virtual void clear(void) { assistOp = (PcodeOp *)0; switchvn = (Varnode *)0; }
};
/// \brief A map from values to control-flow targets within a function
///
/// A JumpTable is attached to a specific CPUI_BRANCHIND and encapsulates all
/// the information necessary to model the indirect jump as a \e switch statement.
/// It knows how to map from specific switch variable values to the destination
/// \e case block and how to label the value.
class JumpTable {
Architecture *glb; // Architecture under which this jumptable operates
JumpModel *jmodel,*origmodel;
vector<Address> addresstable; // Raw addresses in the jumptable
vector<uint4> blocktable; // Addresses converted to basic blocks
vector<uintb> label;
vector<LoadTable> loadpoints;
Address opaddress; // Absolute address of op
PcodeOp *indirect; // INDIRECT op referring to this jump table
uint4 mostcommon; // Most common position in table
uint4 maxtablesize; // Maximum table size we allow to be built (sanity check)
uint4 maxaddsub; // Maximum ADDs or SUBs to normalize
uint4 maxleftright; // Maximum shifts to normalize
uint4 maxext; // Maximum extensions to normalize
int4 recoverystage; // 0=no stages, 1=needs additional stage, 2=complete
bool collectloads;
void recoverModel(Funcdata *fd);
Architecture *glb; ///< Architecture under which this jump-table operates
JumpModel *jmodel; ///< Current model of how the jump table is implemented in code
JumpModel *origmodel; ///< Initial jump table model, which may be incomplete
vector<Address> addresstable; ///< Raw addresses in the jump-table
vector<uint4> blocktable; ///< Addresses converted to basic blocks
vector<uintb> label; ///< The case label for each explicit target
vector<LoadTable> loadpoints; ///< Any recovered in-memory data for the jump-table
Address opaddress; ///< Absolute address of the INDIRECT jump
PcodeOp *indirect; ///< CPUI_INDIRECT op referring linked to \b this jump-table
uintb switchVarConsume; ///< Bits of the switch variable being consumed
uint4 mostcommon; ///< Index of the most common position in table, prior to deduping
uint4 maxtablesize; ///< Maximum table size we allow to be built (sanity check)
uint4 maxaddsub; ///< Maximum ADDs or SUBs to normalize
uint4 maxleftright; ///< Maximum shifts to normalize
uint4 maxext; ///< Maximum extensions to normalize
int4 recoverystage; ///< 0=no stages, 1=needs additional stage, 2=complete
bool collectloads; ///< Set to \b true if information about in-memory model data is/should be collected
void recoverModel(Funcdata *fd); ///< Attempt recovery of the jump-table model
void trivialSwitchOver(void);
void sanityCheck(Funcdata *fd);
void sanityCheck(Funcdata *fd); ///< Perform sanity check on recovered address targets
uint4 block2Position(const FlowBlock *bl) const;
static bool isReachable(PcodeOp *op);
public:
JumpTable(Architecture *g,Address ad=Address());
JumpTable(const JumpTable *op2);
~JumpTable(void);
bool isSwitchedOver(void) const { return !blocktable.empty(); }
bool isRecovered(void) const { return !addresstable.empty(); }
bool isLabelled(void) const { return !label.empty(); }
bool isOverride(void) const;
JumpTable(Architecture *g,Address ad=Address()); ///< Constructor
JumpTable(const JumpTable *op2); ///< Copy constructor
~JumpTable(void); ///< Destructor
bool isSwitchedOver(void) const { return !blocktable.empty(); } ///< Return \b true if addresses converted to basic-blocks
bool isRecovered(void) const { return !addresstable.empty(); } ///< Return \b true if a model has been recovered
bool isLabelled(void) const { return !label.empty(); } ///< Return \b true if \e case labels are computed
bool isOverride(void) const; ///< Return \b true if \b this table was manually overridden
bool isPossibleMultistage(void) const { return (addresstable.size()==1); }
int4 getStage(void) const { return recoverystage; }
int4 numEntries(void) const { return addresstable.size(); }
uintb getSwitchVarConsume(void) const { return switchVarConsume; } ///< Get bits of switch variable consumed by \b this table
int4 getMostCommon(void) const { return mostcommon; }
const Address &getOpAddress(void) const { return opaddress; }
PcodeOp *getIndirectOp(void) const { return indirect; }
@ -395,10 +429,10 @@ public:
void setMostCommonBlock(uint4 bl) { mostcommon = bl; }
void setLoadCollect(bool val) { collectloads = val; }
void addBlockToSwitch(BlockBasic *bl,uintb lab);
void switchOver(const FlowInfo &flow);
uintb getLabelByIndex(int4 index) const { return label[index]; }
void foldInNormalization(Funcdata *fd) { jmodel->foldInNormalization(fd,indirect); }
bool foldInGuards(Funcdata *fd) { return jmodel->foldInGuards(fd,this); }
void switchOver(const FlowInfo &flow); ///< Convert absolute addresses to block indices
uintb getLabelByIndex(int4 index) const { return label[index]; } ///< Given a \e case index, get its label
void foldInNormalization(Funcdata *fd); ///< Hide the normalization code for the switch
bool foldInGuards(Funcdata *fd) { return jmodel->foldInGuards(fd,this); } ///< Hide any guard code for \b this switch
void recoverAddresses(Funcdata *fd);
void recoverMultistage(Funcdata *fd);
bool recoverLabels(Funcdata *fd);

View file

@ -340,6 +340,28 @@ bool SubvariableFlow::tryCallReturnPull(PcodeOp *op,ReplaceVarnode *rvn)
return true;
}
/// \brief Determine if the subgraph variable can act as a switch variable for the given BRANCHIND
///
/// We query the JumpTable associated with the BRANCHIND to see if its switch variable
/// can be trimmed as indicated by the logical flow.
/// \param op is the given BRANCHIND op
/// \param rvn is the subgraph variable flowing to the BRANCHIND
/// \return \b true if the switch variable can be successfully trimmed to its logical size
bool SubvariableFlow::trySwitchPull(PcodeOp *op,ReplaceVarnode *rvn)
{
if ((rvn->mask & 1) == 0) return false; // Logical value must be justified
if ((rvn->vn->getConsume()&~rvn->mask)!=0) // If there's something outside the mask being consumed
return false; // we can't trim
patchlist.push_back(PatchRecord());
patchlist.back().type = 2;
patchlist.back().pullop = op;
patchlist.back().in1 = rvn;
patchlist.back().slot = 0;
pullcount += 1; // A true terminal modification
return true;
}
/// Try to trace the logical variable through descendant Varnodes
/// creating new nodes in the logical subgraph and updating the worklist.
/// \param rvn is the given subgraph variable to trace
@ -573,6 +595,10 @@ bool SubvariableFlow::traceForward(ReplaceVarnode *rvn)
if (!tryReturnPull(op,rvn,slot)) return false;
hcount += 1;
break;
case CPUI_BRANCHIND:
if (!trySwitchPull(op, rvn)) return false;
hcount += 1;
break;
case CPUI_BOOL_NEGATE:
case CPUI_BOOL_AND:
case CPUI_BOOL_OR:
@ -850,6 +876,10 @@ bool SubvariableFlow::traceForwardSext(ReplaceVarnode *rvn)
if (!tryReturnPull(op,rvn,slot)) return false;
hcount += 1;
break;
case CPUI_BRANCHIND:
if (!trySwitchPull(op,rvn)) return false;
hcount += 1;
break;
default:
return false;
}
@ -1331,7 +1361,7 @@ void SubvariableFlow::doReplacement(void)
fd->opSetInput(pullop,getReplaceVarnode((*piter).in1),0);
fd->opSetInput(pullop,getReplaceVarnode((*piter).in2),1);
}
else if (type == 2) { // A call parameter or return value
else if (type == 2) { // A call parameter, return value, or switch variable
fd->opSetInput(pullop,getReplaceVarnode((*piter).in1),(*piter).slot);
}
else if (type == 3) {

View file

@ -62,7 +62,7 @@ class SubvariableFlow {
/// \brief Operation with a new logical value as (part of) input, but output Varnode is unchanged
class PatchRecord {
friend class SubvariableFlow;
int4 type; ///< 0=COPY 1=compare 2=call 3=AND/SHIFT
int4 type; ///< 0=COPY 1=compare 2=call/return/branchind 3=AND/SHIFT
PcodeOp *pullop; ///< Op being affected
ReplaceVarnode *in1; ///< The logical variable input
ReplaceVarnode *in2; ///< (optional second parameter)
@ -91,6 +91,7 @@ class SubvariableFlow {
bool tryCallPull(PcodeOp *op,ReplaceVarnode *rvn,int4 slot);
bool tryReturnPull(PcodeOp *op,ReplaceVarnode *rvn,int4 slot);
bool tryCallReturnPull(PcodeOp *op,ReplaceVarnode *rvn);
bool trySwitchPull(PcodeOp *op,ReplaceVarnode *rvn);
bool traceForward(ReplaceVarnode *rvn); ///< Trace the logical data-flow forward for the given subgraph variable
bool traceBackward(ReplaceVarnode *rvn); ///< Trace the logical data-flow backward for the given subgraph variable
bool traceForwardSext(ReplaceVarnode *rvn); ///< Trace logical data-flow forward assuming sign-extensions