Foundation for replacing fold's dead/abstract binary encoder with a real
SSA P-Code frontend. Verified root-cause gap: serializeBinaryIR() emits only
data-discovery nodes (sections/strings/imports/exports); the control-flow
BinaryExprKind values (BasicBlock/CallSite/...) are never constructed, so the
Z3-BB encoder's encodable filter is always empty and encodeBinaryBBs never
runs — hence 0 binary Z3 findings. The P-Code pipeline supersedes that path.
Phase 0 — docs/internal/ghidra-pcode-integration-research.md: P-Code opcode
core, the decompiler C++ embedding API (LoadImage/SleighArchitecture/Funcdata),
the CORE+DECCORE+SLEIGH source sets, no-bison finding, and pypcode as build
reference.
Phase 1 — vendored Ghidra decompiler at pinned commit (Ghidra_12.1_build,
7e89d94e34...) under third_party/ghidra-decompiler/ (cpp + AArch64 specs +
Apache-2.0 LICENSE + VENDOR.md + .gitignore for build artifacts). Validated by
hand on this clang-21/macOS toolchain:
- full SSA engine (CORE+DECCORE+SLEIGH, 80 files incl. heritage/funcdata/
action) compiles clean, -std=c++11 -O2, NO BFD, NO bison (generated
parsers are committed upstream; ruleparse isn't in the lib set);
- sleigh compiler builds; AArch64 .slaspec -> .sla compiles;
- tools/pcode_dump.cpp (custom LoadImage, no BFD) lifts the real arm64
sym._div_by_zero_direct to correct P-Code: `mov w9,#0 -> COPY(const 0)`,
`sdiv -> INT_SDIV(w8,w9)` — divisor zero-ness explicit in the data flow,
so the Phase-3 encoder proves div-by-zero trivially, multi-arch.
Not yet meson-wired (next step) and no fold-side IR/encoder yet (Phase 2-3);
this commit is the validated vendor + smoke tool.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5798 lines
201 KiB
C++
5798 lines
201 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 "coreaction.hh"
|
|
#include "condexe.hh"
|
|
#include "double.hh"
|
|
#include "subflow.hh"
|
|
#include "constseq.hh"
|
|
#include "bitfield.hh"
|
|
|
|
namespace ghidra {
|
|
|
|
/// \brief A stack equation
|
|
struct StackEqn {
|
|
int4 var1; ///< Variable with 1 coefficient
|
|
int4 var2; ///< Variable with -1 coefficient
|
|
int4 rhs; ///< Right hand side of the equation
|
|
static bool compare(const StackEqn &a,const StackEqn &b); ///< Order two equations
|
|
};
|
|
|
|
/// \brief A class that solves for stack-pointer changes across unknown sub-functions
|
|
class StackSolver {
|
|
vector<StackEqn> eqs; ///< Known equations based on operations that explicitly change the stack-pointer
|
|
vector<StackEqn> guess; ///< Guessed equations for underdetermined systems
|
|
vector<Varnode *> vnlist; ///< The indexed set of variables, one for each reference to the stack-pointer
|
|
vector<int4> companion; ///< Index of companion input for variable produced by CPUI_INDIRECT
|
|
Address spacebase; ///< Starting address of the stack-pointer
|
|
vector<int4> soln; ///< Collected solutions (corresponding to array of variables)
|
|
int4 missedvariables; ///< Number of variables for which we are missing an equation
|
|
void duplicate(void); ///< Duplicate each equation, multiplying by -1
|
|
void propagate(int4 varnum,int4 val); ///< Propagate solution for one variable to other variables
|
|
public:
|
|
void solve(void); ///< Solve the system of equations
|
|
void build(const Funcdata &data,AddrSpace *id,int4 spcbase); ///< Build the system of equations
|
|
int4 getNumVariables(void) const { return vnlist.size(); } ///< Get the number of variables in the system
|
|
Varnode *getVariable(int4 i) const { return vnlist[i]; } ///< Get the i-th Varnode variable
|
|
int4 getCompanion(int4 i) const { return companion[i]; } ///< Get the i-th variable's companion index
|
|
int4 getSolution(int4 i) const { return soln[i]; } ///< Get the i-th variable's solution
|
|
};
|
|
|
|
/// \param a is the first equation to compare
|
|
/// \param b is the second
|
|
/// \return true if the first equation comes before the second
|
|
bool StackEqn::compare(const StackEqn &a,const StackEqn &b)
|
|
|
|
{
|
|
return (a.var1<b.var1);
|
|
}
|
|
|
|
/// Given a solution for one variable, look for equations containing the variable
|
|
/// and attempt to solve for the other variable. Continue propagating new
|
|
/// solutions to other equations to find even more solutions. Populate
|
|
/// the \b soln array with the solutions.
|
|
/// \param varnum is the index of the initial variable
|
|
/// \param val is the solution for the variable
|
|
void StackSolver::propagate(int4 varnum,int4 val)
|
|
|
|
{
|
|
if (soln[varnum] != 65535) return; // This variable already specified
|
|
soln[varnum] = val;
|
|
|
|
StackEqn eqn;
|
|
vector<int4> workstack;
|
|
workstack.reserve(soln.size());
|
|
workstack.push_back(varnum);
|
|
vector<StackEqn>::iterator top;
|
|
|
|
while(!workstack.empty()) {
|
|
varnum = workstack.back();
|
|
workstack.pop_back();
|
|
|
|
eqn.var1 = varnum;
|
|
top = lower_bound(eqs.begin(),eqs.end(),eqn,StackEqn::compare);
|
|
while((top!=eqs.end())&&((*top).var1 == varnum)) {
|
|
int4 var2 = (*top).var2;
|
|
if (soln[var2] == 65535) {
|
|
soln[var2] = soln[varnum]-(*top).rhs;
|
|
workstack.push_back(var2);
|
|
}
|
|
++top;
|
|
}
|
|
}
|
|
}
|
|
|
|
void StackSolver::duplicate(void)
|
|
|
|
{
|
|
int4 size,i;
|
|
StackEqn eqn;
|
|
|
|
size = eqs.size();
|
|
for(i=0;i<size;++i) {
|
|
eqn.var1 = eqs[i].var2;
|
|
eqn.var2 = eqs[i].var1;
|
|
eqn.rhs = -eqs[i].rhs;
|
|
eqs.push_back(eqn);
|
|
}
|
|
stable_sort(eqs.begin(),eqs.end(),StackEqn::compare);
|
|
}
|
|
|
|
void StackSolver::solve(void)
|
|
|
|
{
|
|
// Use guesses to resolve subsystems not uniquely determined
|
|
int4 i,size,var1,var2,count,lastcount;
|
|
|
|
soln.clear();
|
|
soln.resize(vnlist.size(),65535); // Initialize solutions vector
|
|
duplicate(); // Duplicate and sort the equations
|
|
|
|
propagate(0,0); // We know one variable
|
|
size = guess.size();
|
|
lastcount = size+2;
|
|
do {
|
|
count = 0;
|
|
for(i=0;i<size;++i) {
|
|
var1 = guess[i].var1;
|
|
var2 = guess[i].var2;
|
|
if ((soln[var1]!=65535)&&(soln[var2]==65535))
|
|
propagate(var2,soln[var1]-guess[i].rhs);
|
|
else if ((soln[var1]==65535)&&(soln[var2]!=65535))
|
|
propagate(var1,soln[var2]+guess[i].rhs);
|
|
else if ((soln[var1]==65535)&&(soln[var2]==65535))
|
|
count += 1;
|
|
}
|
|
if (count == lastcount) break;
|
|
lastcount = count;
|
|
} while(count > 0);
|
|
}
|
|
|
|
/// Collect references to the stack-pointer as variables, and examine their defining PcodeOps
|
|
/// to determine equations and coefficient.
|
|
/// \param data is the function being analyzed
|
|
/// \param id is the \e stack address space
|
|
/// \param spcbase is the index, relative to the stack space, of the stack pointer
|
|
void StackSolver::build(const Funcdata &data,AddrSpace *id,int4 spcbase)
|
|
|
|
{
|
|
const VarnodeData &spacebasedata(id->getSpacebase(spcbase));
|
|
spacebase = Address(spacebasedata.space,spacebasedata.offset);
|
|
VarnodeLocSet::const_iterator begiter,enditer;
|
|
|
|
begiter = data.beginLoc(spacebasedata.size,spacebase);
|
|
enditer = data.endLoc(spacebasedata.size,spacebase);
|
|
|
|
while(begiter != enditer) { // All instances of the spacebase
|
|
if ((*begiter)->isFree()) break;
|
|
vnlist.push_back(*begiter);
|
|
companion.push_back(-1);
|
|
++begiter;
|
|
}
|
|
missedvariables = 0;
|
|
if (vnlist.empty()) return;
|
|
if (!vnlist[0]->isInput())
|
|
throw LowlevelError("Input value of stackpointer is not used");
|
|
|
|
vector<Varnode *>::iterator iter;
|
|
StackEqn eqn;
|
|
for(int4 i=1;i<vnlist.size();++i) {
|
|
Varnode *vn = vnlist[i];
|
|
Varnode *othervn,*constvn;
|
|
PcodeOp *op = vn->getDef();
|
|
|
|
if (op->code() == CPUI_INT_ADD) {
|
|
othervn = op->getIn(0);
|
|
constvn = op->getIn(1);
|
|
if (othervn->isConstant()) {
|
|
constvn = othervn;
|
|
othervn = op->getIn(1);
|
|
}
|
|
if (!constvn->isConstant()) { missedvariables+=1; continue; }
|
|
if (othervn->getAddr() != spacebase) { missedvariables+=1; continue; }
|
|
iter = lower_bound(vnlist.begin(),vnlist.end(),othervn,Varnode::comparePointers);
|
|
eqn.var1 = i;
|
|
eqn.var2 = iter-vnlist.begin();
|
|
eqn.rhs = constvn->getOffset();
|
|
eqs.push_back(eqn);
|
|
}
|
|
else if (op->code() == CPUI_COPY) {
|
|
othervn = op->getIn(0);
|
|
if (othervn->getAddr() != spacebase) { missedvariables+=1; continue; }
|
|
iter = lower_bound(vnlist.begin(),vnlist.end(),othervn,Varnode::comparePointers);
|
|
eqn.var1 = i;
|
|
eqn.var2 = iter-vnlist.begin();
|
|
eqn.rhs = 0;
|
|
eqs.push_back(eqn);
|
|
}
|
|
else if (op->code() == CPUI_INDIRECT) {
|
|
othervn = op->getIn(0);
|
|
if (othervn->getAddr() != spacebase) { missedvariables += 1; continue; }
|
|
iter = lower_bound(vnlist.begin(),vnlist.end(),othervn,Varnode::comparePointers);
|
|
eqn.var1 = i;
|
|
eqn.var2 = iter-vnlist.begin();
|
|
companion[i] = eqn.var2;
|
|
Varnode *iopvn = op->getIn(1);
|
|
if (iopvn->getSpace()->getType()==IPTR_IOP) { // If INDIRECT is due call
|
|
PcodeOp *iop = PcodeOp::getOpFromConst(iopvn->getAddr());
|
|
FuncCallSpecs *fc = data.getCallSpecs(iop); // Look up function proto
|
|
if (fc != (FuncCallSpecs *)0) {
|
|
if (fc->getExtraPop() != ProtoModel::extrapop_unknown) { // Double check that extrapop is unknown
|
|
eqn.rhs = fc->getExtraPop(); // As the deindirect process may have filled it in
|
|
eqs.push_back(eqn);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
eqn.rhs = 4; // Otherwise make a guess
|
|
guess.push_back(eqn);
|
|
}
|
|
else if (op->code() == CPUI_MULTIEQUAL) {
|
|
for(int4 j=0;j<op->numInput();++j) {
|
|
othervn = op->getIn(j);
|
|
if (othervn->getAddr() != spacebase) { missedvariables += 1; continue; }
|
|
iter = lower_bound(vnlist.begin(),vnlist.end(),othervn,Varnode::comparePointers);
|
|
eqn.var1 = i;
|
|
eqn.var2 = iter-vnlist.begin();
|
|
eqn.rhs = 0;
|
|
eqs.push_back(eqn);
|
|
}
|
|
}
|
|
else if (op->code() == CPUI_INT_AND) {
|
|
// This can occur if a function aligns its stack pointer
|
|
othervn = op->getIn(0);
|
|
constvn = op->getIn(1);
|
|
if (othervn->isConstant()) {
|
|
constvn = othervn;
|
|
othervn = op->getIn(1);
|
|
}
|
|
if (!constvn->isConstant()) { missedvariables+=1; continue; }
|
|
if (othervn->getAddr() != spacebase) { missedvariables+=1; continue; }
|
|
iter = lower_bound(vnlist.begin(),vnlist.end(),othervn,Varnode::comparePointers);
|
|
eqn.var1 = i;
|
|
eqn.var2 = iter-vnlist.begin();
|
|
eqn.rhs = 0; // Treat this as a copy
|
|
eqs.push_back(eqn);
|
|
}
|
|
else
|
|
missedvariables += 1;
|
|
}
|
|
}
|
|
|
|
/// \brief Calculate stack-pointer change across \e undetermined sub-functions
|
|
///
|
|
/// If there are sub-functions for which \e extra \e pop is not explicit,
|
|
/// do full linear analysis to (attempt to) recover the values.
|
|
/// \param data is the function to analyze
|
|
/// \param stackspace is the space associated with the stack-pointer
|
|
/// \param spcbase is the index (relative to the stackspace) of the stack-pointer
|
|
void ActionStackPtrFlow::analyzeExtraPop(Funcdata &data,AddrSpace *stackspace,int4 spcbase)
|
|
|
|
{
|
|
ProtoModel *myfp = data.getArch()->evalfp_called;
|
|
if (myfp == (ProtoModel *)0)
|
|
myfp = data.getArch()->defaultfp;
|
|
if (myfp->getExtraPop()!=ProtoModel::extrapop_unknown) return;
|
|
|
|
StackSolver solver;
|
|
try {
|
|
solver.build(data,stackspace,spcbase);
|
|
} catch(LowlevelError &err) {
|
|
ostringstream s;
|
|
s << "Stack frame is not setup normally: " << err.explain;
|
|
data.warningHeader(s.str());
|
|
return;
|
|
}
|
|
if (solver.getNumVariables() == 0) return;
|
|
solver.solve(); // Solve the equations
|
|
|
|
Varnode *invn = solver.getVariable(0);
|
|
bool warningprinted = false;
|
|
|
|
for(int4 i=1;i<solver.getNumVariables();++i) {
|
|
Varnode *vn = solver.getVariable(i);
|
|
int4 soln = solver.getSolution(i);
|
|
if (soln == 65535) {
|
|
if (!warningprinted) {
|
|
data.warningHeader("Unable to track spacebase fully for "+stackspace->getName());
|
|
warningprinted = true;
|
|
}
|
|
continue;
|
|
}
|
|
PcodeOp *op = vn->getDef();
|
|
|
|
if (op->code() == CPUI_INDIRECT) {
|
|
Varnode *iopvn = op->getIn(1);
|
|
if (iopvn->getSpace()->getType()==IPTR_IOP) {
|
|
PcodeOp *iop = PcodeOp::getOpFromConst(iopvn->getAddr());
|
|
FuncCallSpecs *fc = data.getCallSpecs(iop);
|
|
if (fc != (FuncCallSpecs *)0) {
|
|
int4 soln2 = 0;
|
|
int4 comp = solver.getCompanion(i);
|
|
if (comp >= 0)
|
|
soln2 = solver.getSolution(comp);
|
|
fc->setEffectiveExtraPop(soln-soln2);
|
|
}
|
|
}
|
|
}
|
|
vector<Varnode *> paramlist;
|
|
paramlist.push_back(invn);
|
|
int4 sz = invn->getSize();
|
|
paramlist.push_back(data.newConstant(sz,soln&calc_mask(sz)));
|
|
data.opSetOpcode(op,CPUI_INT_ADD);
|
|
data.opSetAllInput(op,paramlist);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/// \brief Is the given Varnode defined as a pointer relative to the stack-pointer?
|
|
///
|
|
/// Return true if -vn- is defined as the stackpointer input plus a constant (or zero)
|
|
/// This works through the general case and the special case when the constant is zero.
|
|
/// The constant value is passed-back to the caller.
|
|
/// \param spcbasein is the Varnode holding the \e input value of the stack-pointer
|
|
/// \param vn is the Varnode to check for relativeness
|
|
/// \param constval is a reference for passing back the constant offset
|
|
/// \return true if \b vn is stack relative
|
|
bool ActionStackPtrFlow::isStackRelative(Varnode *spcbasein,Varnode *vn,uintb &constval)
|
|
|
|
{
|
|
if (spcbasein == vn) {
|
|
constval = 0;
|
|
return true;
|
|
}
|
|
if (!vn->isWritten()) return false;
|
|
PcodeOp *addop = vn->getDef();
|
|
if (addop->code() != CPUI_INT_ADD) return false;
|
|
if (addop->getIn(0) != spcbasein) return false;
|
|
Varnode *constvn = addop->getIn(1);
|
|
if (!constvn->isConstant()) return false;
|
|
constval = constvn->getOffset();
|
|
return true;
|
|
}
|
|
|
|
/// \brief Adjust the LOAD where the stack-pointer alias has been recovered.
|
|
///
|
|
/// We've matched a LOAD with its matching store, now convert the LOAD op to a COPY of what was stored.
|
|
/// \param data is the function being analyzed
|
|
/// \param loadop is the LOAD op to adjust
|
|
/// \param storeop is the matching STORE op
|
|
/// \return true if the adjustment is successful
|
|
bool ActionStackPtrFlow::adjustLoad(Funcdata &data,PcodeOp *loadop,PcodeOp *storeop)
|
|
|
|
{
|
|
Varnode *vn = storeop->getIn(2);
|
|
if (vn->isConstant())
|
|
vn = data.newConstant(vn->getSize(),vn->getOffset());
|
|
else if (vn->isFree())
|
|
return false;
|
|
|
|
data.opRemoveInput(loadop,1);
|
|
data.opSetOpcode(loadop,CPUI_COPY);
|
|
data.opSetInput(loadop,vn,0);
|
|
return true;
|
|
}
|
|
|
|
/// \brief Link LOAD to matching STORE of a constant
|
|
///
|
|
/// Try to find STORE op using same stack relative pointer as a given LOAD op.
|
|
/// If we find it and the STORE stores a constant, change the LOAD to a COPY.
|
|
/// \param data is the function owning the LOAD
|
|
/// \param id is the stackspace
|
|
/// \param spcbasein is the stack-pointer
|
|
/// \param loadop is the given LOAD op
|
|
/// \param constz is the stack relative offset of the LOAD pointer
|
|
/// \return 1 if we successfully change LOAD to COPY, 0 otherwise
|
|
int4 ActionStackPtrFlow::repair(Funcdata &data,AddrSpace *id,Varnode *spcbasein,PcodeOp *loadop,uintb constz)
|
|
|
|
{
|
|
int4 loadsize = loadop->getOut()->getSize();
|
|
BlockBasic *curblock = loadop->getParent();
|
|
list<PcodeOp *>::iterator begiter = curblock->beginOp();
|
|
list<PcodeOp *>::iterator iter = loadop->getBasicIter();
|
|
for(;;) {
|
|
if (iter == begiter) {
|
|
if (curblock->sizeIn() != 1) return 0; // Can trace back to next basic block if only one path
|
|
curblock = (BlockBasic *)curblock->getIn(0);
|
|
begiter = curblock->beginOp();
|
|
iter = curblock->endOp();
|
|
continue;
|
|
}
|
|
else {
|
|
--iter;
|
|
}
|
|
PcodeOp *curop = *iter;
|
|
if (curop->isCall()) return 0; // Don't try to trace aliasing through a call
|
|
if (curop->code() == CPUI_STORE) {
|
|
Varnode *ptrvn = curop->getIn(1);
|
|
Varnode *datavn = curop->getIn(2);
|
|
uintb constnew;
|
|
if (isStackRelative(spcbasein,ptrvn,constnew)) {
|
|
if ((constnew == constz)&&(loadsize == datavn->getSize())) {
|
|
// We found the matching store
|
|
if (adjustLoad(data,loadop,curop))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
else if ((constnew <= constz + (loadsize-1))&&(constnew+(datavn->getSize()-1)>=constz))
|
|
return 0;
|
|
}
|
|
else
|
|
return 0; // Any other kind of STORE we can't solve aliasing
|
|
}
|
|
else {
|
|
Varnode *outvn = curop->getOut();
|
|
if (outvn != (Varnode *)0) {
|
|
if (outvn->getSpace() == id) return 0; // Stack already traced, too late
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Find any stack pointer clogs and pass it on to the repair routines
|
|
///
|
|
/// A stack pointer \b clog is a constant addition to the stack-pointer,
|
|
/// but where the constant comes from the stack.
|
|
/// \param data is the function to analyze
|
|
/// \param id is the stack space
|
|
/// \param spcbase is the index of the stack-pointer relative to the stack space
|
|
/// \return the number of clogs that were repaired
|
|
int4 ActionStackPtrFlow::checkClog(Funcdata &data,AddrSpace *id,int4 spcbase)
|
|
|
|
{
|
|
const VarnodeData &spacebasedata(id->getSpacebase(spcbase));
|
|
Address spacebase = Address(spacebasedata.space,spacebasedata.offset);
|
|
VarnodeLocSet::const_iterator begiter,enditer;
|
|
int4 clogcount = 0;
|
|
|
|
begiter = data.beginLoc(spacebasedata.size,spacebase);
|
|
enditer = data.endLoc(spacebasedata.size,spacebase);
|
|
|
|
Varnode *spcbasein;
|
|
if (begiter == enditer) return clogcount;
|
|
spcbasein = *begiter;
|
|
++begiter;
|
|
if (!spcbasein->isInput()) return clogcount;
|
|
while(begiter != enditer) {
|
|
Varnode *outvn = *begiter;
|
|
++begiter;
|
|
if (!outvn->isWritten()) continue;
|
|
PcodeOp *addop = outvn->getDef();
|
|
if (addop->code() != CPUI_INT_ADD) continue;
|
|
Varnode *y = addop->getIn(1);
|
|
if (!y->isWritten()) continue; // y must not be a constant
|
|
Varnode *x = addop->getIn(0); // is y is not constant than x (in position 0) isn't either
|
|
uintb constx;
|
|
if (!isStackRelative(spcbasein,x,constx)) { // If x is not stack relative
|
|
x = y; // Swap x and y
|
|
y = addop->getIn(0);
|
|
if (!isStackRelative(spcbasein,x,constx)) continue; // Now maybe the new x is stack relative
|
|
}
|
|
PcodeOp *loadop = y->getDef();
|
|
if (loadop->code() == CPUI_INT_MULT) { // If we multiply
|
|
Varnode *constvn = loadop->getIn(1);
|
|
if (!constvn->isConstant()) continue;
|
|
if (constvn->getOffset() != calc_mask(constvn->getSize())) continue; // Must multiply by -1
|
|
y = loadop->getIn(0);
|
|
if (!y->isWritten()) continue;
|
|
loadop = y->getDef();
|
|
}
|
|
if (loadop->code() != CPUI_LOAD) continue;
|
|
Varnode *ptrvn = loadop->getIn(1);
|
|
uintb constz;
|
|
if (!isStackRelative(spcbasein,ptrvn,constz)) continue;
|
|
clogcount += repair(data,id,spcbasein,loadop,constz);
|
|
}
|
|
return clogcount;
|
|
}
|
|
|
|
int4 ActionStackPtrFlow::apply(Funcdata &data)
|
|
|
|
{
|
|
if (analysis_finished)
|
|
return 0;
|
|
if (stackspace == (AddrSpace *)0) {
|
|
analysis_finished = true; // No stack to do analysis on
|
|
return 0;
|
|
}
|
|
int4 numchange = checkClog(data,stackspace,0);
|
|
if (numchange > 0) {
|
|
count += 1;
|
|
}
|
|
if (numchange == 0) {
|
|
analyzeExtraPop(data,stackspace,0);
|
|
analysis_finished = true;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// \brief Examine the PcodeOps using the given Varnode to determine possible lane sizes
|
|
///
|
|
/// Run through the defining op and any descendant ops of the given Varnode, looking for
|
|
/// CPUI_PIECE and CPUI_SUBPIECE. Use these to determine possible lane sizes and
|
|
/// register them with the given LanedRegister object.
|
|
/// \param vn is the given Varnode
|
|
/// \param allowedLanes is used to determine if a putative lane size is allowed
|
|
/// \param checkLanes collects the possible lane sizes
|
|
void ActionLaneDivide::collectLaneSizes(Varnode *vn,const LanedRegister &allowedLanes,LanedRegister &checkLanes)
|
|
|
|
{
|
|
list<PcodeOp *>::const_iterator iter = vn->beginDescend();
|
|
int4 step = 0; // 0 = descendants, 1 = def, 2 = done
|
|
if (iter == vn->endDescend()) {
|
|
step = 1;
|
|
}
|
|
while(step < 2) {
|
|
int4 curSize; // Putative lane size
|
|
if (step == 0) {
|
|
PcodeOp *op = *iter;
|
|
++iter;
|
|
if (iter == vn->endDescend())
|
|
step = 1;
|
|
if (op->code() != CPUI_SUBPIECE) continue; // Is the big register split into pieces
|
|
curSize = op->getOut()->getSize();
|
|
}
|
|
else {
|
|
step = 2;
|
|
if (!vn->isWritten()) continue;
|
|
PcodeOp *op = vn->getDef();
|
|
if (op->code() != CPUI_PIECE) continue; // Is the big register formed from smaller pieces
|
|
curSize = op->getIn(0)->getSize();
|
|
int4 tmpSize = op->getIn(1)->getSize();
|
|
if (tmpSize < curSize)
|
|
curSize = tmpSize;
|
|
}
|
|
if (allowedLanes.allowedLane(curSize))
|
|
checkLanes.addLaneSize(curSize); // Register this possible size
|
|
}
|
|
}
|
|
|
|
/// \brief Search for a likely lane size and try to divide a single Varnode into these lanes
|
|
///
|
|
/// There are different ways to search for a lane size:
|
|
///
|
|
/// Mode 0: Collect putative lane sizes based on the local ops using the Varnode. Attempt
|
|
/// to divide based on each of those lane sizes in turn.
|
|
///
|
|
/// Mode 1: Similar to mode 0, except we allow for SUBPIECE operations that truncate to
|
|
/// variables that are smaller than the lane size.
|
|
///
|
|
/// Mode 2: Attempt to divide based on a default lane size.
|
|
/// \param data is the function being transformed
|
|
/// \param vn is the given single Varnode
|
|
/// \param lanedRegister is acceptable set of lane sizes for the Varnode
|
|
/// \param mode is the lane size search mode (0, 1, or 2)
|
|
/// \return \b true if the Varnode (and its data-flow) was successfully split
|
|
bool ActionLaneDivide::processVarnode(Funcdata &data,Varnode *vn,const LanedRegister &lanedRegister,int4 mode)
|
|
|
|
{
|
|
LanedRegister checkLanes; // Lanes we are going to try, initialized to no lanes
|
|
bool allowDowncast = (mode > 0);
|
|
if (mode < 2)
|
|
collectLaneSizes(vn,lanedRegister,checkLanes);
|
|
else {
|
|
int4 defaultSize = data.getArch()->types->getSizeOfPointer(); // Default lane size
|
|
if (defaultSize != 4)
|
|
defaultSize = 8;
|
|
checkLanes.addLaneSize(defaultSize);
|
|
}
|
|
LanedRegister::const_iterator enditer = checkLanes.end();
|
|
for(LanedRegister::const_iterator iter=checkLanes.begin();iter!=enditer;++iter) {
|
|
int4 curSize = *iter;
|
|
LaneDescription description(lanedRegister.getWholeSize(),curSize); // Lane scheme dictated by curSize
|
|
LaneDivide laneDivide(&data,vn,description,allowDowncast);
|
|
if (laneDivide.doTrace()) {
|
|
laneDivide.apply();
|
|
count += 1; // Indicate a change was made
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int4 ActionLaneDivide::apply(Funcdata &data)
|
|
|
|
{
|
|
data.setLanedRegGenerated();
|
|
map<VarnodeData,const LanedRegister *>::const_iterator iter;
|
|
for(int4 mode=0;mode<3;++mode) {
|
|
bool allStorageProcessed = true;
|
|
for(iter=data.beginLaneAccess();iter!=data.endLaneAccess();++iter) {
|
|
const LanedRegister *lanedReg = (*iter).second;
|
|
Address addr = (*iter).first.getAddr();
|
|
int4 sz = (*iter).first.size;
|
|
VarnodeLocSet::const_iterator viter = data.beginLoc(sz,addr);
|
|
VarnodeLocSet::const_iterator venditer = data.endLoc(sz,addr);
|
|
bool allVarnodesProcessed = true;
|
|
while(viter != venditer) {
|
|
Varnode *vn = *viter;
|
|
if (vn->hasNoDescend()) {
|
|
++viter;
|
|
continue;
|
|
}
|
|
if (processVarnode(data, vn, *lanedReg, mode)) {
|
|
viter = data.beginLoc(sz,addr);
|
|
venditer = data.endLoc(sz, addr); // Recalculate bounds
|
|
allVarnodesProcessed = true;
|
|
}
|
|
else {
|
|
++viter;
|
|
allVarnodesProcessed = false;
|
|
}
|
|
}
|
|
if (!allVarnodesProcessed)
|
|
allStorageProcessed = false;
|
|
}
|
|
if (allStorageProcessed) break;
|
|
}
|
|
data.clearLanedAccessMap();
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionSegmentize::apply(Funcdata &data)
|
|
|
|
{
|
|
int4 numops = data.getArch()->userops.numSegmentOps();
|
|
if (numops==0) return 0;
|
|
if (localcount>0) return 0; // Only perform once
|
|
localcount = 1; // Mark as having performed once
|
|
|
|
vector<Varnode *> bindlist;
|
|
bindlist.push_back((Varnode *)0);
|
|
bindlist.push_back((Varnode *)0);
|
|
|
|
for(int4 i=0;i<numops;++i) {
|
|
SegmentOp *segdef = data.getArch()->userops.getSegmentOp(i);
|
|
if (segdef == (SegmentOp *)0) continue;
|
|
AddrSpace *spc = segdef->getSpace();
|
|
|
|
list<PcodeOp *>::const_iterator iter,enditer;
|
|
iter = data.beginOp(CPUI_CALLOTHER);
|
|
enditer = data.endOp(CPUI_CALLOTHER);
|
|
int4 uindex = segdef->getIndex();
|
|
while(iter != enditer) {
|
|
PcodeOp *segroot = *iter++;
|
|
if (segroot->isDead()) continue;
|
|
if (segroot->getIn(0)->getOffset() != uindex) continue;
|
|
if (!segdef->unify(data,segroot,bindlist)) {
|
|
ostringstream err;
|
|
err << "Segment op in wrong form at ";
|
|
segroot->getAddr().printRaw(err);
|
|
throw LowlevelError(err.str());
|
|
}
|
|
|
|
if (segdef->getNumVariableTerms()==1)
|
|
bindlist[0] = data.newConstant(4,0);
|
|
// Redefine the op as a segmentop
|
|
data.opSetOpcode(segroot,CPUI_SEGMENTOP);
|
|
data.opSetInput(segroot,data.newVarnodeSpace(spc),0);
|
|
data.opSetInput(segroot,bindlist[0],1);
|
|
data.opSetInput(segroot,bindlist[1],2);
|
|
for(int4 j=segroot->numInput()-1;j>2;--j) // Remove anything else
|
|
data.opRemoveInput(segroot,j);
|
|
count += 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionForceGoto::apply(Funcdata &data)
|
|
|
|
{
|
|
data.getOverride().applyForceGoto(data);
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionConstbase::apply(Funcdata &data)
|
|
|
|
{
|
|
if (data.getBasicBlocks().getSize()==0) return 0; // No blocks
|
|
// Get start block, which is constructed to have nothing
|
|
// falling into it
|
|
BlockBasic *bb = (BlockBasic *)data.getBasicBlocks().getBlock(0);
|
|
|
|
int4 injectid = data.getFuncProto().getInjectUponEntry();
|
|
if (injectid >= 0) {
|
|
InjectPayload *payload = data.getArch()->pcodeinjectlib->getPayload(injectid);
|
|
data.doLiveInject(payload,bb->getStart(),bb,bb->beginOp());
|
|
}
|
|
|
|
const TrackedSet trackset( data.getArch()->context->getTrackedSet(data.getAddress()));
|
|
|
|
for(int4 i=0;i<trackset.size();++i) {
|
|
const TrackedContext &ctx(trackset[i]);
|
|
|
|
Address addr(ctx.loc.space,ctx.loc.offset);
|
|
PcodeOp *op = data.newOp(1,bb->getStart());
|
|
data.newVarnodeOut(ctx.loc.size,addr,op);
|
|
Varnode *vnin = data.newConstant(ctx.loc.size,ctx.val);
|
|
data.opSetOpcode(op,CPUI_COPY);
|
|
data.opSetInput(op,vnin,0);
|
|
data.opInsertBegin(op,bb);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// int4 ActionCse::apply(Funcdata &data)
|
|
|
|
// {
|
|
// vector< pair<uintm,PcodeOp *> > list;
|
|
// vector<Varnode *> vlist;
|
|
// PcodeOp *op;
|
|
// list<PcodeOp *>::const_iterator iter;
|
|
// uintm hash;
|
|
|
|
// for(iter=data.op_alive_begin();iter!=data.op_alive_end();++iter) {
|
|
// op = *iter;
|
|
// hash = op->getCseHash();
|
|
// if (hash == 0) continue;
|
|
// list.push_back(pair<uintm,PcodeOp *>(hash,op));
|
|
// }
|
|
// if (list.empty()) return 0;
|
|
|
|
// cseEliminateList(data,list,vlist);
|
|
// while(!vlist.empty()) {
|
|
// count += 1; // Indicate that changes have been made
|
|
// list.clear();
|
|
// cse_build_fromvarnode(list,vlist);
|
|
// vlist.clear();
|
|
// cseEliminateList(data,list,vlist);
|
|
// }
|
|
// return 0;
|
|
// }
|
|
|
|
/// We are substituting either -out1- for -out2- OR -out2- for -out1-
|
|
/// Return true if we prefer substituting -out2- for -out1-
|
|
/// \param out1 is one output
|
|
/// \param out2 is the other output
|
|
/// \return preference
|
|
bool ActionMultiCse::preferredOutput(Varnode *out1,Varnode *out2)
|
|
|
|
{
|
|
// Prefer the output that is used in a CPUI_RETURN
|
|
list<PcodeOp *>::const_iterator iter,enditer;
|
|
enditer = out1->endDescend();
|
|
for(iter=out1->beginDescend();iter!=enditer;++iter) {
|
|
PcodeOp *op = *iter;
|
|
if (op->code() == CPUI_RETURN)
|
|
return false;
|
|
}
|
|
enditer = out2->endDescend();
|
|
for(iter=out2->beginDescend();iter!=enditer;++iter) {
|
|
PcodeOp *op = *iter;
|
|
if (op->code() == CPUI_RETURN)
|
|
return true;
|
|
}
|
|
// Prefer addrtied over register over unique
|
|
if (!out1->isAddrTied()) {
|
|
if (out2->isAddrTied())
|
|
return true;
|
|
else {
|
|
if (out1->getSpace()->getType()==IPTR_INTERNAL) {
|
|
if (out2->getSpace()->getType()!=IPTR_INTERNAL)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Find any matching CPUI_MULTIEQUAL that occurs before \b target that has \b in as an input.
|
|
/// Then test to see if the \b target and the recovered op are functionally equivalent.
|
|
/// \param bl is the parent block
|
|
/// \param target is the given target CPUI_MULTIEQUAL
|
|
/// \param in is the specific input Varnode
|
|
PcodeOp *ActionMultiCse::findMatch(BlockBasic *bl,PcodeOp *target,Varnode *in)
|
|
|
|
{
|
|
list<PcodeOp *>::iterator iter = bl->beginOp();
|
|
|
|
for(;;) {
|
|
PcodeOp *op = *iter;
|
|
++iter;
|
|
if (op == target) // Caught up with target, nothing else before it
|
|
break;
|
|
int4 i,numinput;
|
|
numinput = op->numInput();
|
|
for(i=0;i<numinput;++i) {
|
|
Varnode *vn = op->getIn(i);
|
|
if (vn->isWritten() && (vn->getDef()->code() == CPUI_COPY))
|
|
vn = vn->getDef()->getIn(0); // Allow for differences in copy propagation
|
|
if (vn == in) break;
|
|
}
|
|
if (i < numinput) {
|
|
int4 j;
|
|
Varnode *buf1[2];
|
|
Varnode *buf2[2];
|
|
for(j=0;j<numinput;++j) {
|
|
Varnode *in1 = op->getIn(j);
|
|
if (in1->isWritten() && (in1->getDef()->code() == CPUI_COPY))
|
|
in1 = in1->getDef()->getIn(0); // Allow for differences in copy propagation
|
|
Varnode *in2 = target->getIn(j);
|
|
if (in2->isWritten() && (in2->getDef()->code() == CPUI_COPY))
|
|
in2 = in2->getDef()->getIn(0);
|
|
if (in1 == in2) continue;
|
|
if (0!=functionalEqualityLevel(in1,in2,buf1,buf2))
|
|
break;
|
|
}
|
|
if (j==numinput) // We have found a redundancy
|
|
return op;
|
|
}
|
|
}
|
|
return (PcodeOp *)0;
|
|
}
|
|
|
|
/// Search for pairs of CPUI_MULTIEQUAL ops in \b bl that share an input.
|
|
/// If the pairs found are functionally equivalent, delete one of the two.
|
|
/// \param data is the function owning the block
|
|
/// \param bl is the specific basic block
|
|
/// return \b true if a CPUI_MULTIEQUAL was (successfully) deleted
|
|
bool ActionMultiCse::processBlock(Funcdata &data,BlockBasic *bl)
|
|
|
|
{
|
|
vector<Varnode *> vnlist;
|
|
PcodeOp *targetop = (PcodeOp *)0;
|
|
PcodeOp *pairop;
|
|
list<PcodeOp *>::iterator iter = bl->beginOp();
|
|
list<PcodeOp *>::iterator enditer = bl->endOp();
|
|
while(iter != enditer) {
|
|
PcodeOp *op = *iter;
|
|
++iter;
|
|
OpCode opc = op->code();
|
|
if (opc == CPUI_COPY) continue;
|
|
if (opc != CPUI_MULTIEQUAL) break;
|
|
int4 vnpos = vnlist.size();
|
|
int4 i;
|
|
int4 numinput = op->numInput();
|
|
for(i=0;i<numinput;++i) {
|
|
Varnode *vn = op->getIn(i);
|
|
if (vn->isWritten() && (vn->getDef()->code() == CPUI_COPY)) // Some copies may not propagate into MULTIEQUAL
|
|
vn = vn->getDef()->getIn(0); // Allow for differences in copy propagation
|
|
vnlist.push_back(vn);
|
|
if (vn->isMark()) { // If we've seen this varnode before
|
|
pairop = findMatch(bl,op,vn);
|
|
if (pairop != (PcodeOp *)0)
|
|
break;
|
|
}
|
|
}
|
|
if (i<numinput) {
|
|
targetop = op;
|
|
break;
|
|
}
|
|
for(i=vnpos;i<vnlist.size();++i)
|
|
vnlist[i]->setMark(); // Mark that we have seen this varnode
|
|
}
|
|
|
|
// Clear out any of the marks we put down
|
|
for(int4 i=0;i<vnlist.size();++i)
|
|
vnlist[i]->clearMark();
|
|
|
|
if (targetop != (PcodeOp *)0) {
|
|
Varnode *out1 = pairop->getOut();
|
|
Varnode *out2 = targetop->getOut();
|
|
if (preferredOutput(out1,out2)) {
|
|
data.totalReplace(out1,out2); // Replace pairop and out1 in favor of targetop and out2
|
|
data.opDestroy(pairop);
|
|
}
|
|
else {
|
|
data.totalReplace(out2,out1);
|
|
data.opDestroy(targetop);
|
|
}
|
|
count += 1; // Indicate that a change has taken place
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int4 ActionMultiCse::apply(Funcdata &data)
|
|
|
|
{
|
|
const BlockGraph &bblocks( data.getBasicBlocks() );
|
|
int4 sz = bblocks.getSize();
|
|
for(int4 i=0;i<sz;++i) {
|
|
BlockBasic *bl = (BlockBasic *)bblocks.getBlock(i);
|
|
while(processBlock(data,bl)) {
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionShadowVar::apply(Funcdata &data)
|
|
|
|
{
|
|
const BlockGraph &bblocks(data.getBasicBlocks());
|
|
BlockBasic *bl;
|
|
PcodeOp *op;
|
|
Varnode *vn;
|
|
vector<Varnode *> vnlist;
|
|
list<PcodeOp *> oplist;
|
|
uintb startoffset;
|
|
for(int4 i=0;i<bblocks.getSize();++i) {
|
|
vnlist.clear();
|
|
bl = (BlockBasic *)bblocks.getBlock(i);
|
|
// Iterator over all MULTIEQUALs in the block
|
|
// We have to check all ops in the first address
|
|
// We cannot stop at first non-MULTIEQUAL because
|
|
// other ops creep in because of multi_collapse
|
|
startoffset = bl->getStart().getOffset();
|
|
list<PcodeOp *>::iterator iter = bl->beginOp();
|
|
while(iter != bl->endOp()) {
|
|
op = *iter++;
|
|
if (op->getAddr().getOffset() != startoffset) break;
|
|
if (op->code() != CPUI_MULTIEQUAL) continue;
|
|
vn = op->getIn(0);
|
|
if (vn->isMark())
|
|
oplist.push_back(op);
|
|
else {
|
|
vn->setMark();
|
|
vnlist.push_back(vn);
|
|
}
|
|
}
|
|
for(int4 j=0;j<vnlist.size();++j)
|
|
vnlist[j]->clearMark();
|
|
}
|
|
list<PcodeOp *>::iterator oiter;
|
|
for(oiter=oplist.begin();oiter!=oplist.end();++oiter) {
|
|
op = *oiter;
|
|
PcodeOp *op2;
|
|
for(op2=op->previousOp();op2!=(PcodeOp *)0;op2=op2->previousOp()) {
|
|
if (op2->code() != CPUI_MULTIEQUAL) continue;
|
|
int4 i;
|
|
for(i=0;i<op->numInput();++i) // Check for match in each branch
|
|
if (op->getIn(i) != op2->getIn(i)) break;
|
|
if (i != op->numInput()) continue; // All branches did not match
|
|
|
|
vector<Varnode *> plist;
|
|
plist.push_back(op2->getOut());
|
|
data.opSetOpcode(op,CPUI_COPY);
|
|
data.opSetAllInput(op,plist);
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// \brief Search for address space annotations in the path of a pointer constant.
|
|
///
|
|
/// From a constant, search forward in its data-flow either for a LOAD or STORE operation where we can
|
|
/// see the address space being accessed, or search for a pointer data-type with an address space attribute.
|
|
/// We make a limited traversal through the op reading the constant, through INT_ADD, INDIRECT, COPY,
|
|
/// and MULTIEQUAL until we hit a LOAD or STORE.
|
|
/// \param vn is the constant we are searching from
|
|
/// \param op is the PcodeOp reading the constant
|
|
/// \return the discovered AddrSpace or null
|
|
AddrSpace *ActionConstantPtr::searchForSpaceAttribute(Varnode *vn,PcodeOp *op)
|
|
|
|
{
|
|
for(int4 i=0;i<3;++i) {
|
|
Datatype *dt = vn->getType();
|
|
if (dt->getMetatype() == TYPE_PTR) {
|
|
AddrSpace *spc = ((TypePointer*)dt)->getSpace();
|
|
if (spc != (AddrSpace*)0 && spc->getAddrSize() == vn->getSize()) // If provided a pointer with space attribute
|
|
return spc; // use that
|
|
}
|
|
switch(op->code()) {
|
|
case CPUI_INT_ADD:
|
|
case CPUI_COPY:
|
|
case CPUI_INDIRECT:
|
|
case CPUI_MULTIEQUAL:
|
|
vn = op->getOut();
|
|
op = vn->loneDescend();
|
|
break;
|
|
case CPUI_LOAD:
|
|
return op->getIn(0)->getSpaceFromConst();
|
|
case CPUI_STORE:
|
|
if (op->getIn(1) == vn)
|
|
return op->getIn(0)->getSpaceFromConst();
|
|
return (AddrSpace *)0;
|
|
default:
|
|
return (AddrSpace *)0;
|
|
}
|
|
if (op == (PcodeOp *)0) break;
|
|
}
|
|
for(list<PcodeOp *>::const_iterator iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
|
|
op = *iter;
|
|
OpCode opc = op->code();
|
|
if (opc == CPUI_LOAD)
|
|
return op->getIn(0)->getSpaceFromConst();
|
|
else if (opc == CPUI_STORE && op->getIn(1) == vn)
|
|
return op->getIn(0)->getSpaceFromConst();
|
|
}
|
|
return (AddrSpace *)0;
|
|
}
|
|
|
|
/// \brief Select the AddrSpace in which we infer with the given constant is a pointer
|
|
///
|
|
/// The constant must match the AddrSpace address size. If there is more than one possible match,
|
|
/// search for more information in the syntax tree.
|
|
/// \param vn is the given constant Varnode
|
|
/// \param op is the PcodeOp which uses the constant
|
|
/// \param spaceList is the list of address spaces to select from
|
|
/// \return the selected address space or null
|
|
AddrSpace *ActionConstantPtr::selectInferSpace(Varnode *vn,PcodeOp *op,const vector<AddrSpace *> &spaceList)
|
|
|
|
{
|
|
AddrSpace *resSpace = (AddrSpace *)0;
|
|
if (vn->getType()->getMetatype() == TYPE_PTR) {
|
|
AddrSpace *spc = ((TypePointer *)vn->getType())->getSpace();
|
|
if (spc != (AddrSpace *)0 && spc->getAddrSize() == vn->getSize())
|
|
return spc;
|
|
}
|
|
for(int4 i=0;i<spaceList.size();++i) {
|
|
AddrSpace *spc = spaceList[i];
|
|
int4 minSize = spc->getMinimumPtrSize();
|
|
if (minSize == 0) {
|
|
if (vn->getSize() != spc->getAddrSize())
|
|
continue;
|
|
}
|
|
else if (vn->getSize() < minSize)
|
|
continue;
|
|
if (resSpace != (AddrSpace *)0) {
|
|
AddrSpace *searchSpc = searchForSpaceAttribute(vn,op);
|
|
if (searchSpc != (AddrSpace *)0)
|
|
resSpace = searchSpc;
|
|
break;
|
|
}
|
|
resSpace = spc;
|
|
}
|
|
return resSpace;
|
|
}
|
|
|
|
/// \brief Check if we need to try to infer a constant pointer from the input of the given COPY
|
|
///
|
|
/// In general, we do want try, but there is a special case where the COPY feeds into a RETURN and
|
|
/// the function does \e not return a pointer. We also consider the \b infer_pointers boolean.
|
|
/// \param op is the given COPY op
|
|
/// \param data is the function
|
|
/// \return \b true if we should try to infer
|
|
bool ActionConstantPtr::checkCopy(PcodeOp *op,Funcdata &data)
|
|
|
|
{
|
|
Varnode *vn = op->getOut();
|
|
PcodeOp *retOp = vn->loneDescend();
|
|
if (retOp != (PcodeOp *)0 && retOp->code() == CPUI_RETURN && data.getFuncProto().isOutputLocked()) {
|
|
type_metatype meta = data.getFuncProto().getOutput()->getType()->getMetatype();
|
|
if (meta != TYPE_PTR && meta != TYPE_UNKNOWN) {
|
|
return false; // Do NOT infer, we know the constant can't be pointer
|
|
}
|
|
return true; // Try to infer, regardless of infer_pointers config, because we KNOW it is a pointer
|
|
}
|
|
return data.getArch()->infer_pointers;
|
|
}
|
|
|
|
/// \brief Determine if given Varnode might be a pointer constant.
|
|
///
|
|
/// If it is a pointer, return the symbol it points to, or NULL otherwise. If it is determined
|
|
/// that the Varnode is a pointer to a specific symbol, the encoding of the full pointer is passed back.
|
|
/// Usually this is just the constant value of the Varnode, but in this case of partial pointers
|
|
/// (like \e near pointers) the full pointer may contain additional information.
|
|
/// \param spc is the address space being pointed to
|
|
/// \param vn is the given Varnode
|
|
/// \param op is the lone descendant of the Varnode
|
|
/// \param slot is the slot index of the Varnode
|
|
/// \param rampoint will hold the Address of the resolved symbol
|
|
/// \param fullEncoding will hold the full pointer encoding being passed back
|
|
/// \param data is the function being analyzed
|
|
/// \return the recovered symbol or NULL
|
|
SymbolEntry *ActionConstantPtr::isPointer(AddrSpace *spc,Varnode *vn,PcodeOp *op,int4 slot,
|
|
Address &rampoint,uintb &fullEncoding,Funcdata &data)
|
|
|
|
{
|
|
bool needexacthit;
|
|
Architecture *glb = data.getArch();
|
|
Varnode *outvn;
|
|
if (vn->getTypeReadFacing(op)->getMetatype() == TYPE_PTR) { // Are we explicitly marked as a pointer
|
|
rampoint = glb->resolveConstant(spc,vn->getOffset(),vn->getSize(),op->getAddr(),fullEncoding);
|
|
needexacthit = false;
|
|
}
|
|
else {
|
|
if (vn->isTypeLock()) return (SymbolEntry *)0; // Locked as NOT a pointer
|
|
needexacthit = true;
|
|
// Check if the constant is involved in a potential pointer expression
|
|
// as the base
|
|
switch(op->code()) {
|
|
case CPUI_CALL:
|
|
case CPUI_CALLIND:
|
|
{
|
|
if (slot==0)
|
|
return (SymbolEntry *)0;
|
|
// A constant parameter could be a pointer
|
|
FuncCallSpecs *fc = data.getCallSpecs(op);
|
|
if (fc != (FuncCallSpecs *)0 && fc->isInputLocked() && fc->numParams() > slot-1) {
|
|
type_metatype meta = fc->getParam(slot-1)->getType()->getMetatype();
|
|
if (meta != TYPE_PTR && meta != TYPE_UNKNOWN) {
|
|
return (SymbolEntry *)0; // Definitely not passing a pointer
|
|
}
|
|
}
|
|
else if (!glb->infer_pointers)
|
|
return (SymbolEntry *)0;
|
|
break;
|
|
}
|
|
case CPUI_COPY:
|
|
if (!checkCopy(op, data))
|
|
return (SymbolEntry *)0;
|
|
break;
|
|
case CPUI_PIECE:
|
|
// Pointers get concatenated in structures
|
|
case CPUI_INT_EQUAL:
|
|
case CPUI_INT_NOTEQUAL:
|
|
case CPUI_INT_LESS:
|
|
case CPUI_INT_LESSEQUAL:
|
|
// A comparison with a constant could be a pointer
|
|
if (!glb->infer_pointers)
|
|
return (SymbolEntry *)0;
|
|
break;
|
|
case CPUI_INT_ADD:
|
|
outvn = op->getOut();
|
|
if (outvn->getTypeDefFacing()->getMetatype()==TYPE_PTR) {
|
|
// Is there another pointer base in this expression
|
|
if (op->getIn(1-slot)->getTypeReadFacing(op)->getMetatype()==TYPE_PTR)
|
|
return (SymbolEntry *)0; // If so, we are not a pointer
|
|
// FIXME: need to fully explore additive tree
|
|
needexacthit = false;
|
|
}
|
|
else if (!glb->infer_pointers)
|
|
return (SymbolEntry *)0;
|
|
break;
|
|
case CPUI_STORE:
|
|
if (slot != 2)
|
|
return (SymbolEntry *)0;
|
|
break;
|
|
default:
|
|
return (SymbolEntry *)0;
|
|
}
|
|
// Make sure the constant is in the expected range for a pointer
|
|
if (spc->getPointerLowerBound() > vn->getOffset())
|
|
return (SymbolEntry *)0;
|
|
if (spc->getPointerUpperBound() < vn->getOffset())
|
|
return (SymbolEntry *)0;
|
|
// Check if the constant looks like a single bit or mask
|
|
if (bit_transitions(vn->getOffset(),vn->getSize()) < 3)
|
|
return (SymbolEntry *)0;
|
|
rampoint = glb->resolveConstant(spc,vn->getOffset(),vn->getSize(),op->getAddr(),fullEncoding);
|
|
}
|
|
|
|
if (rampoint.isInvalid()) return (SymbolEntry *)0;
|
|
// Since we are looking for a global address
|
|
// Assume it is address tied and use empty usepoint
|
|
SymbolEntry *entry = data.getScopeLocal()->getParent()->queryContainer(rampoint,1,Address());
|
|
if (entry != (SymbolEntry *)0) {
|
|
Datatype *ptrType = entry->getSymbol()->getType();
|
|
if (ptrType->getMetatype() == TYPE_ARRAY) {
|
|
Datatype *ct = ((TypeArray *)ptrType)->getBase();
|
|
// In the special case of strings (character arrays) we allow the constant pointer to
|
|
// refer to the middle of the string
|
|
if (ct->isCharPrint())
|
|
needexacthit = false;
|
|
}
|
|
if (needexacthit && entry->getAddr() != rampoint)
|
|
return (SymbolEntry *)0;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
int4 ActionConstantPtr::apply(Funcdata &data)
|
|
|
|
{
|
|
if (!data.hasTypeRecoveryStarted()) return 0;
|
|
|
|
if (localcount >= 4) // At most 4 passes (once type recovery starts)
|
|
return 0;
|
|
localcount += 1;
|
|
|
|
VarnodeLocSet::const_iterator begiter,enditer;
|
|
Architecture *glb = data.getArch();
|
|
AddrSpace *cspc = glb->getConstantSpace();
|
|
SymbolEntry *entry;
|
|
Varnode *vn;
|
|
|
|
begiter = data.beginLoc(cspc);
|
|
enditer = data.endLoc(cspc);
|
|
|
|
while(begiter!=enditer) {
|
|
vn = *begiter++;
|
|
if (!vn->isConstant()) break; // New varnodes may get inserted between begiter and enditer
|
|
if (vn->getOffset() == 0) continue; // Never make constant 0 into spacebase
|
|
if (vn->isPtrCheck()) continue; // Have we checked this variable before
|
|
if (vn->hasNoDescend()) continue;
|
|
if (vn->isSpacebase()) continue; // Don't use constant 0 which is already spacebase
|
|
// if (vn->getSize() != rspc->getAddrSize()) continue; // Must be size of pointer
|
|
|
|
PcodeOp *op = vn->loneDescend();
|
|
if (op == (PcodeOp *)0) continue;
|
|
AddrSpace *rspc = selectInferSpace(vn, op, glb->inferPtrSpaces);
|
|
if (rspc == (AddrSpace *)0) continue;
|
|
int4 slot = op->getSlot(vn);
|
|
OpCode opc = op->code();
|
|
if (opc == CPUI_INT_ADD) {
|
|
if (op->getIn(1-slot)->isSpacebase()) continue; // Make sure other side is not a spacebase already
|
|
}
|
|
else if ((opc == CPUI_PTRSUB)||(opc==CPUI_PTRADD))
|
|
continue;
|
|
Address rampoint;
|
|
uintb fullEncoding;
|
|
entry = isPointer(rspc,vn,op,slot,rampoint,fullEncoding,data);
|
|
vn->setPtrCheck(); // Set check flag AFTER searching for symbol
|
|
if (entry != (SymbolEntry *)0) {
|
|
data.spacebaseConstant(op,slot,entry,rampoint,fullEncoding,vn->getSize());
|
|
if ((opc == CPUI_INT_ADD)&&(slot==1))
|
|
data.opSwapInput(op,0,1);
|
|
count += 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionDeindirect::apply(Funcdata &data)
|
|
|
|
{
|
|
FuncCallSpecs *fc;
|
|
PcodeOp *op;
|
|
Varnode *vn;
|
|
|
|
for(int4 i=0;i<data.numCalls();++i) {
|
|
fc = data.getCallSpecs(i);
|
|
op = fc->getOp();
|
|
if (op->code() != CPUI_CALLIND) continue;
|
|
vn = op->getIn(0);
|
|
while(vn->isWritten()&&(vn->getDef()->code()==CPUI_COPY))
|
|
vn = vn->getDef()->getIn(0);
|
|
if (vn->isPersist() && vn->isExternalRef()) { // Check for possible external reference
|
|
Funcdata *newfd = data.getScopeLocal()->getParent()->queryExternalRefFunction(vn->getAddr());
|
|
if (newfd != (Funcdata *)0) {
|
|
fc->deindirect(data,newfd);
|
|
count += 1;
|
|
continue;
|
|
}
|
|
}
|
|
else if (vn->isConstant()) {
|
|
AddrSpace *sp = data.getAddress().getSpace(); // Assume function is in same space as calling function
|
|
// Convert constant to a byte address in this space
|
|
uintb offset = AddrSpace::addressToByte(vn->getOffset(),sp->getWordSize());
|
|
int4 align = data.getArch()->funcptr_align;
|
|
if (align != 0) { // If we know function pointer should be aligned
|
|
offset >>= align; // Remove any encoding bits before querying for the function
|
|
offset <<= align;
|
|
}
|
|
Address codeaddr(sp,offset);
|
|
Funcdata *newfd = data.getScopeLocal()->getParent()->queryFunction(codeaddr);
|
|
if (newfd != (Funcdata *)0) {
|
|
fc->deindirect(data,newfd);
|
|
count += 1;
|
|
continue;
|
|
}
|
|
}
|
|
if (data.hasTypeRecoveryStarted()) {
|
|
// Check for a function pointer that has an attached prototype
|
|
Datatype *ct = op->getIn(0)->getTypeReadFacing(op);
|
|
if ((ct->getMetatype()==TYPE_PTR)&&
|
|
(((TypePointer *)ct)->getPtrTo()->getMetatype()==TYPE_CODE)) {
|
|
TypeCode *tc = (TypeCode *)((TypePointer *)ct)->getPtrTo();
|
|
const FuncProto *fp = tc->getPrototype();
|
|
if (fp!=(const FuncProto *)0) {
|
|
if (!fc->isInputLocked()) {
|
|
// We use isInputLocked as a test of whether the
|
|
// function pointer prototype has been applied before
|
|
fc->forceSet(data,*fp);
|
|
count += 1;
|
|
}
|
|
}
|
|
// FIXME: If fc's input IS locked presumably this means
|
|
// that this prototype is already set, but it MIGHT mean
|
|
// we have conflicting locked prototypes
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionVarnodeProps::apply(Funcdata &data)
|
|
|
|
{
|
|
Architecture *glb = data.getArch();
|
|
bool cachereadonly = glb->readonlypropagate;
|
|
int4 pass = data.getHeritagePass();
|
|
VarnodeLocSet::const_iterator iter;
|
|
Varnode *vn;
|
|
|
|
iter = data.beginLoc();
|
|
while(iter != data.endLoc()) {
|
|
vn = *iter++; // Advance iterator in case vn is deleted
|
|
if (vn->isAnnotation()) continue;
|
|
int4 vnSize = vn->getSize();
|
|
if (vn->isAutoLiveHold()) {
|
|
if (pass > 0) {
|
|
if (vn->isWritten()) {
|
|
PcodeOp *loadOp = vn->getDef();
|
|
if (loadOp->code() == CPUI_LOAD) {
|
|
Varnode *ptr = loadOp->getIn(1);
|
|
if (ptr->isConstant() || ptr->isReadOnly())
|
|
continue;
|
|
if (ptr->isWritten()) {
|
|
PcodeOp *copyOp = ptr->getDef();
|
|
if (copyOp->code() == CPUI_COPY) {
|
|
ptr = copyOp->getIn(0);
|
|
if (ptr->isConstant() || ptr->isReadOnly())
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
vn->clearAutoLiveHold();
|
|
count += 1;
|
|
}
|
|
}
|
|
else if (vn->hasActionProperty()) {
|
|
if (cachereadonly&&vn->isReadOnly()) {
|
|
if (data.fillinReadOnly(vn)) // Try to replace vn with its lookup in LoadImage
|
|
count += 1;
|
|
}
|
|
else if (vn->isVolatile())
|
|
if (data.replaceVolatile(vn))
|
|
count += 1; // Try to replace vn with pcode op
|
|
}
|
|
else if (((vn->getNZMask() & vn->getConsume())==0)&&(vnSize<=sizeof(uintb))) {
|
|
// FIXME: uintb should be arbitrary precision
|
|
if (vn->isConstant()) continue; // Don't replace a constant
|
|
if (vn->isWritten()) {
|
|
if (vn->getDef()->code() == CPUI_COPY) {
|
|
if (vn->getDef()->getIn(0)->isConstant()) {
|
|
// Don't replace a COPY 0, with a zero, let
|
|
// constant propagation do that. This prevents
|
|
// an infinite recursion
|
|
if (vn->getDef()->getIn(0)->getOffset() == 0)
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (!vn->hasNoDescend()) {
|
|
data.totalReplaceConstant(vn,0);
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionDirectWrite::apply(Funcdata &data)
|
|
|
|
{
|
|
VarnodeLocSet::const_iterator iter;
|
|
list<PcodeOp *>::const_iterator oiter;
|
|
Varnode *vn,*dvn;
|
|
PcodeOp *op;
|
|
vector<Varnode *> worklist;
|
|
|
|
// Collect legal inputs and other auto direct writes
|
|
for(iter=data.beginLoc();iter!=data.endLoc();++iter) {
|
|
vn = *iter;
|
|
vn->clearDirectWrite();
|
|
if (vn->isInput()) {
|
|
if (vn->isPersist()||vn->isSpacebase()) {
|
|
vn->setDirectWrite();
|
|
worklist.push_back(vn);
|
|
}
|
|
else if (data.getFuncProto().possibleInputParam(vn->getAddr(),vn->getSize())) {
|
|
vn->setDirectWrite();
|
|
worklist.push_back(vn);
|
|
}
|
|
}
|
|
else if (vn->isWritten()) {
|
|
op = vn->getDef();
|
|
if (!op->isMarker()) {
|
|
if (vn->isPersist()) {
|
|
// Anything that writes to a global variable (in a real way) is considered a direct write
|
|
vn->setDirectWrite();
|
|
worklist.push_back(vn);
|
|
}
|
|
else if (op->code() == CPUI_COPY) { // For most COPYs, do not consider it a direct write
|
|
if (vn->isStackStore()) { // But, if the original operation was really a CPUI_STORE
|
|
Varnode *invn = op->getIn(0); // Trace COPY source
|
|
if (invn->isWritten()) { // Through possible multiple COPYs
|
|
PcodeOp *curop = invn->getDef();
|
|
if (curop->code() == CPUI_COPY)
|
|
invn = curop->getIn(0);
|
|
}
|
|
if (invn->isWritten() && invn->getDef()->isMarker()) { // if source is from an INDIRECT
|
|
vn->setDirectWrite(); // then treat this as a direct write
|
|
worklist.push_back(vn);
|
|
}
|
|
}
|
|
}
|
|
else if ((op->code()!=CPUI_PIECE)&&(op->code()!=CPUI_SUBPIECE)) {
|
|
// Anything that writes to a variable in a way that isn't some form of COPY is a direct write
|
|
vn->setDirectWrite();
|
|
worklist.push_back(vn);
|
|
}
|
|
}
|
|
else if (!propagateIndirect && op->code() == CPUI_INDIRECT) {
|
|
Varnode *outvn = op->getOut();
|
|
if (op->getIn(0)->getAddr() != outvn->getAddr()) // Check if storage address changes from input to output
|
|
vn->setDirectWrite(); // Indicates an active COPY, which is a direct write
|
|
else if (outvn->isPersist()) // Value must be present at global storage at point call is made
|
|
vn->setDirectWrite(); // so treat as direct write
|
|
// We do NOT add vn to worklist as INDIRECT otherwise does not propagate
|
|
}
|
|
}
|
|
else if (vn->isConstant()) {
|
|
if (!vn->isIndirectZero()) {
|
|
vn->setDirectWrite();
|
|
worklist.push_back(vn);
|
|
}
|
|
}
|
|
}
|
|
// Let legalness taint
|
|
while(!worklist.empty()) {
|
|
vn = worklist.back();
|
|
worklist.pop_back();
|
|
for(oiter=vn->beginDescend();oiter!=vn->endDescend();++oiter) {
|
|
op = *oiter;
|
|
if (!op->isAssignment()) continue;
|
|
dvn = op->getOut();
|
|
if (!dvn->isDirectWrite()) {
|
|
dvn->setDirectWrite();
|
|
// For call based INDIRECTs, output is marked, but does not propagate depending on setting
|
|
if (propagateIndirect || op->code() != CPUI_INDIRECT || op->isIndirectStore())
|
|
worklist.push_back(dvn);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionExtraPopSetup::apply(Funcdata &data)
|
|
|
|
{
|
|
FuncCallSpecs *fc;
|
|
PcodeOp *op;
|
|
|
|
if (stackspace == (AddrSpace *)0) return 0; // No stack to speak of
|
|
const VarnodeData &point(stackspace->getSpacebase(0));
|
|
Address sb_addr(point.space,point.offset);
|
|
int4 sb_size = point.size;
|
|
|
|
for(int4 i=0;i<data.numCalls();++i) {
|
|
fc = data.getCallSpecs(i);
|
|
if (fc->getExtraPop() == 0) continue; // Stack pointer is undisturbed
|
|
op = data.newOp(2,fc->getOp()->getAddr());
|
|
data.newVarnodeOut(sb_size,sb_addr,op);
|
|
data.opSetInput(op,data.newVarnode(sb_size,sb_addr),0);
|
|
if (fc->getExtraPop() != ProtoModel::extrapop_unknown) { // We know exactly how stack pointer is changed
|
|
fc->setEffectiveExtraPop(fc->getExtraPop());
|
|
data.opSetOpcode(op,CPUI_INT_ADD);
|
|
data.opSetInput(op,data.newConstant(sb_size,fc->getExtraPop()),1);
|
|
data.opInsertAfter(op,fc->getOp());
|
|
}
|
|
else { // We don't know exactly, so we create INDIRECT
|
|
data.opSetOpcode(op,CPUI_INDIRECT);
|
|
data.opSetInput(op,data.newVarnodeIop(fc->getOp()),1);
|
|
data.opInsertBefore(op,fc->getOp());
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// \brief Set up the parameter recovery process for a single sub-function call
|
|
///
|
|
/// If the prototype is known (locked), insert stub Varnodes
|
|
/// If the prototype is varargs (dotdotdot), set up recovery of variable Varnodes
|
|
/// \param fc is the given sub-function
|
|
/// \param data is the function being analyzed
|
|
void ActionFuncLink::funcLinkInput(FuncCallSpecs *fc,Funcdata &data)
|
|
|
|
{
|
|
bool inputlocked = fc->isInputLocked();
|
|
bool varargs = fc->isDotdotdot();
|
|
AddrSpace *spacebase = fc->getSpacebase(); // Non-zero spacebase indicates we need a stackplaceholder
|
|
ParamActive *active = fc->getActiveInput();
|
|
|
|
if ((!inputlocked)||varargs)
|
|
fc->initActiveInput();
|
|
if (inputlocked) {
|
|
PcodeOp *op = fc->getOp();
|
|
int4 numparam = fc->numParams();
|
|
bool setplaceholder = varargs;
|
|
for(int4 i=0;i<numparam;++i) {
|
|
ProtoParameter *param = fc->getParam(i);
|
|
active->registerTrial(param->getAddress(),param->getSize());
|
|
active->getTrial(i).markActive(); // Parameter is not optional
|
|
if (varargs){
|
|
active->getTrial(i).setFixedPosition(i);
|
|
}
|
|
AddrSpace *spc = param->getAddress().getSpace();
|
|
uintb off = param->getAddress().getOffset();
|
|
int4 sz = param->getSize();
|
|
if (spc->getType() == IPTR_SPACEBASE) { // Param is stack relative
|
|
Varnode *loadval = data.opStackLoad(spc,off,sz,op,(Varnode *)0,false);
|
|
data.opInsertInput(op,loadval,op->numInput());
|
|
if (!setplaceholder) {
|
|
setplaceholder = true;
|
|
loadval->setSpacebasePlaceholder();
|
|
spacebase = (AddrSpace *)0; // With a locked stack parameter, we don't need a stackplaceholder
|
|
}
|
|
continue;
|
|
}
|
|
if (spc->getType() == IPTR_JOIN) {
|
|
JoinRecord *join = data.getArch()->findJoin(off);
|
|
int4 index = -1;
|
|
if (join->getPiece(0).space->getType() == IPTR_SPACEBASE)
|
|
index = 0;
|
|
else if (join->getPiece(join->numPieces()-1).space->getType() == IPTR_SPACEBASE)
|
|
index = join->numPieces()-1;
|
|
if (index >= 0) {
|
|
const VarnodeData &stack(join->getPiece(index));
|
|
const VarnodeData &remain(data.getArch()->stripJoinPiece(join, index));
|
|
Varnode *loadval = data.opStackLoad(stack.space,stack.offset,stack.size,op,(Varnode *)0,false);
|
|
Varnode *remainval = data.newVarnode(remain.size, remain.space, remain.offset);
|
|
PcodeOp *concatOp = data.newOp(2, op->getAddr());
|
|
data.opSetOpcode(concatOp, CPUI_PIECE);
|
|
if (index == 0) {
|
|
data.opSetInput(concatOp,loadval,0);
|
|
data.opSetInput(concatOp,remainval,1);
|
|
}
|
|
else {
|
|
data.opSetInput(concatOp,remainval,0);
|
|
data.opSetInput(concatOp,loadval,1);
|
|
}
|
|
Varnode *outvn = data.newUniqueOut(sz, concatOp);
|
|
data.opInsertBefore(concatOp, op);
|
|
data.opInsertInput(op,outvn,op->numInput());
|
|
continue;
|
|
}
|
|
}
|
|
data.opInsertInput(op,data.newVarnode(param->getSize(),param->getAddress()),op->numInput());
|
|
}
|
|
}
|
|
if (spacebase != (AddrSpace *)0) // If we need it, create the stackplaceholder
|
|
fc->createPlaceholder(data, spacebase);
|
|
}
|
|
|
|
/// \brief Set up the return value recovery process for a single sub-function call
|
|
///
|
|
/// If the prototype is known(locked), insert an output Varnode on the call
|
|
/// If the prototype is unknown set-up the ParamActive, so that outputs will be "gathered"
|
|
/// \param fc is the given sub-function
|
|
/// \param data is the function being analyzed
|
|
void ActionFuncLink::funcLinkOutput(FuncCallSpecs *fc,Funcdata &data)
|
|
|
|
{
|
|
PcodeOp *callop = fc->getOp();
|
|
if (callop->getOut() != (Varnode *)0) {
|
|
// CALL ops are expected to have no output, but its possible an override has produced one
|
|
if (callop->getOut()->getSpace()->getType() == IPTR_INTERNAL) {
|
|
// Removing a varnode in the unique space will likely produce an input varnode in the unique space
|
|
ostringstream s;
|
|
s << "CALL op at ";
|
|
callop->getAddr().printRaw(s);
|
|
s << " has an unexpected output varnode";
|
|
throw LowlevelError(s.str());
|
|
}
|
|
// Otherwise just remove the Varnode and assume return recovery will reintroduce it if necessary
|
|
data.opUnsetOutput(callop);
|
|
}
|
|
if (fc->isOutputLocked()) {
|
|
ProtoParameter *outparam = fc->getOutput();
|
|
Datatype *outtype = outparam->getType();
|
|
if (outtype->getMetatype() != TYPE_VOID) {
|
|
int4 sz = outparam->getSize();
|
|
if (outtype->getMetatype() == TYPE_BOOL && data.isTypeRecoveryOn())
|
|
data.opMarkCalculatedBool(callop);
|
|
Address addr = outparam->getAddress();
|
|
if (addr.getSpace()->getType() == IPTR_SPACEBASE) {
|
|
// Delay creating output Varnode until heritage of the stack, when we know relative value of the stack pointer
|
|
fc->setStackOutputLock(true);
|
|
return;
|
|
}
|
|
data.newVarnodeOut(sz,addr,callop);
|
|
VarnodeData vdata;
|
|
OpCode res = fc->assumedOutputExtension(addr,sz,vdata);
|
|
if (res == CPUI_PIECE) { // Pick an extension based on type
|
|
if (outtype->getMetatype() == TYPE_INT)
|
|
res = CPUI_INT_SEXT;
|
|
else
|
|
res = CPUI_INT_ZEXT;
|
|
}
|
|
if (res != CPUI_COPY) { // We assume the (smallsize) output is extended to a full register
|
|
// Create the extension operation to eliminate artifact
|
|
PcodeOp *op = data.newOp(1,callop->getAddr());
|
|
data.newVarnodeOut(vdata.size,vdata.getAddr(),op);
|
|
Varnode *invn = data.newVarnode(sz,addr);
|
|
data.opSetInput(op,invn,0);
|
|
data.opSetOpcode(op,res);
|
|
data.opInsertAfter(op,callop); // Insert immediately after the call
|
|
}
|
|
}
|
|
}
|
|
else
|
|
fc->initActiveOutput();
|
|
}
|
|
|
|
int4 ActionFuncLink::apply(Funcdata &data)
|
|
|
|
{
|
|
int4 i,size;
|
|
|
|
size = data.numCalls();
|
|
for(i=0;i<size;++i) {
|
|
funcLinkInput(data.getCallSpecs(i),data);
|
|
funcLinkOutput(data.getCallSpecs(i),data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionFuncLinkOutOnly::apply(Funcdata &data)
|
|
|
|
{
|
|
int4 size = data.numCalls();
|
|
for(int4 i=0;i<size;++i)
|
|
ActionFuncLink::funcLinkOutput(data.getCallSpecs(i),data);
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionParamDouble::apply(Funcdata &data)
|
|
|
|
{
|
|
for(int4 i=0;i<data.numCalls();++i) {
|
|
FuncCallSpecs *fc = data.getCallSpecs(i);
|
|
PcodeOp *op = fc->getOp();
|
|
if (fc->isInputActive()) {
|
|
ParamActive *active = fc->getActiveInput();
|
|
for(int4 j=0;j<active->getNumTrials();++j) {
|
|
const ParamTrial ¶mtrial( active->getTrial(j) );
|
|
if (paramtrial.isChecked()) continue;
|
|
if (paramtrial.isUnref()) continue;
|
|
AddrSpace *spc = paramtrial.getAddress().getSpace();
|
|
if (spc->getType() != IPTR_SPACEBASE) continue;
|
|
int4 slot = paramtrial.getSlot();
|
|
Varnode *vn = op->getIn(slot);
|
|
if (!vn->isWritten()) continue;
|
|
PcodeOp *concatop = vn->getDef();
|
|
if (concatop->code() != CPUI_PIECE) continue;
|
|
if (!fc->hasModel()) continue;
|
|
Varnode *mostvn = concatop->getIn(0);
|
|
Varnode *leastvn = concatop->getIn(1);
|
|
int4 splitsize = spc->isBigEndian() ? mostvn->getSize() : leastvn->getSize();
|
|
if (fc->checkInputSplit(paramtrial.getAddress(),paramtrial.getSize(),splitsize)) {
|
|
active->splitTrial(j,splitsize);
|
|
if (spc->isBigEndian()) {
|
|
data.opInsertInput(op,mostvn,slot);
|
|
data.opSetInput(op,leastvn,slot+1);
|
|
}
|
|
else {
|
|
data.opInsertInput(op,leastvn,slot);
|
|
data.opSetInput(op,mostvn,slot+1);
|
|
}
|
|
count += 1; // Indicate that a change was made
|
|
|
|
j -= 1; // Note we decrement j here, so that we can check nested CONCATs
|
|
}
|
|
}
|
|
}
|
|
else if ((!fc->isInputLocked())&&(data.isDoublePrecisOn())) {
|
|
// Search for double precision objects that might become params
|
|
int4 max = op->numInput() - 1;
|
|
// Look for adjacent slots that form pieces of a double precision whole
|
|
for(int4 j=1;j<max;++j) {
|
|
Varnode *vn1 = op->getIn(j);
|
|
Varnode *vn2 = op->getIn(j+1);
|
|
SplitVarnode whole;
|
|
bool isslothi;
|
|
if (whole.inHandHi(vn1)) {
|
|
if (whole.getLo() != vn2) continue;
|
|
isslothi = true;
|
|
}
|
|
else if (whole.inHandLo(vn1)) {
|
|
if (whole.getHi() != vn2) continue;
|
|
isslothi = false;
|
|
}
|
|
else
|
|
continue;
|
|
if (fc->checkInputJoin(j,isslothi,vn1,vn2)) {
|
|
data.opSetInput(op,whole.getWhole(),j);
|
|
data.opRemoveInput(op,j+1);
|
|
fc->doInputJoin(j,isslothi);
|
|
max = op->numInput() - 1;
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
const FuncProto &fp( data.getFuncProto() );
|
|
if (fp.isInputLocked() && data.isDoublePrecisOn()) {
|
|
// Search for locked parameters that are being split into hi and lo components
|
|
vector<Varnode *> lovec;
|
|
vector<Varnode *> hivec;
|
|
int4 minDoubleSize = data.getArch()->getDefaultSize(); // Minimum size to consider
|
|
int4 numparams = fp.numParams();
|
|
for(int4 i=0;i<numparams;++i) {
|
|
ProtoParameter *param = fp.getParam(i);
|
|
Datatype *tp = param->getType();
|
|
if (!tp->isPrimitiveWhole())
|
|
continue; // Not double precision objects
|
|
Varnode *vn = data.findVarnodeInput(tp->getSize(),param->getAddress());
|
|
if (vn == (Varnode *)0) continue;
|
|
if (vn->getSize() < minDoubleSize) continue;
|
|
int4 halfSize = vn->getSize() / 2;
|
|
lovec.clear();
|
|
hivec.clear();
|
|
bool otherUse = false; // Have we seen use other than splitting into hi and lo
|
|
list<PcodeOp *>::const_iterator iter,enditer;
|
|
iter = vn->beginDescend();
|
|
enditer = vn->endDescend();
|
|
while(iter != enditer) {
|
|
PcodeOp *subop = *iter;
|
|
++iter;
|
|
if (subop->code() != CPUI_SUBPIECE) continue;
|
|
Varnode *outvn = subop->getOut();
|
|
if (outvn->getSize() != halfSize) continue;
|
|
if (subop->getIn(1)->getOffset() == 0) // Possible lo precision piece
|
|
lovec.push_back(outvn);
|
|
else if (subop->getIn(1)->getOffset() == halfSize) // Possible hi precision piece
|
|
hivec.push_back(outvn);
|
|
else {
|
|
otherUse = true;
|
|
break;
|
|
}
|
|
}
|
|
if ((!otherUse)&&(!lovec.empty())&&(!hivec.empty())) { // Seen (only) hi and lo uses
|
|
for(int4 j=0;j<lovec.size();++j) {
|
|
Varnode *piecevn = lovec[j];
|
|
if (!piecevn->isPrecisLo()) {
|
|
piecevn->setPrecisLo();
|
|
count += 1; // Indicate we made change
|
|
}
|
|
}
|
|
for(int4 j=0;j<hivec.size();++j) {
|
|
Varnode *piecevn = hivec[j];
|
|
if (!piecevn->isPrecisHi()) {
|
|
piecevn->setPrecisHi();
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionActiveParam::apply(Funcdata &data)
|
|
|
|
{
|
|
int4 i;
|
|
FuncCallSpecs *fc;
|
|
AliasChecker aliascheck;
|
|
aliascheck.gather(&data,data.getArch()->getStackSpace(),true);
|
|
|
|
for(i=0;i<data.numCalls();++i) {
|
|
fc = data.getCallSpecs(i);
|
|
// An indirect function is not trimmable until
|
|
// there has been at least one simplification pass
|
|
// there has been a change to deindirect
|
|
try {
|
|
if (fc->isInputActive()) {
|
|
ParamActive *activeinput = fc->getActiveInput();
|
|
bool trimmable = ((activeinput->getNumPasses()>0)||(fc->getOp()->code() != CPUI_CALLIND));
|
|
if (!activeinput->isFullyChecked())
|
|
fc->checkInputTrialUse(data,aliascheck);
|
|
activeinput->finishPass();
|
|
if (activeinput->getNumPasses() > activeinput->getMaxPass())
|
|
activeinput->markFullyChecked();
|
|
else
|
|
count += 1; // Count a change, to indicate we still have work to do
|
|
if (trimmable && activeinput->isFullyChecked()) {
|
|
if (activeinput->needsFinalCheck())
|
|
fc->finalInputCheck();
|
|
fc->resolveModel(activeinput);
|
|
fc->deriveInputMap(activeinput);
|
|
fc->buildInputFromTrials(data);
|
|
fc->clearActiveInput();
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
catch(LowlevelError &err) {
|
|
ostringstream s;
|
|
s << "Error processing " << fc->getName();
|
|
PcodeOp *op = fc->getOp();
|
|
if (op != (PcodeOp *)0)
|
|
s << " called at " << op->getSeqNum();
|
|
s << ": " << err.explain;
|
|
throw LowlevelError(s.str());
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionActiveReturn::apply(Funcdata &data)
|
|
|
|
{
|
|
int4 i;
|
|
FuncCallSpecs *fc;
|
|
|
|
for(i=0;i<data.numCalls();++i) {
|
|
fc = data.getCallSpecs(i);
|
|
if (fc->isOutputActive()) {
|
|
ParamActive *activeoutput = fc->getActiveOutput();
|
|
vector<Varnode *> trialvn;
|
|
fc->checkOutputTrialUse(data,trialvn);
|
|
fc->deriveOutputMap(activeoutput);
|
|
fc->buildOutputFromTrials(data,trialvn);
|
|
fc->clearActiveOutput();
|
|
count += 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// int4 ActionParamShiftStart::apply(Funcdata &data)
|
|
|
|
// {
|
|
// int4 i;
|
|
// FuncCallSpecs *fc;
|
|
|
|
// for(i=0;i<data.numCalls();++i) {
|
|
// fc = data.getCallSpecs(i);
|
|
// fc->paramshiftModifyStart();
|
|
// }
|
|
// return 0;
|
|
// }
|
|
|
|
// int4 ActionParamShiftStop::apply(Funcdata &data)
|
|
|
|
// {
|
|
// int4 i;
|
|
// FuncCallSpecs *fc;
|
|
|
|
// if (!paramshiftsleft) return 0;
|
|
// paramshiftsleft = false;
|
|
// for(i=0;i<data.numCalls();++i) {
|
|
// fc = data.getCallSpecs(i);
|
|
// if (fc->getParamshift() != 0) {
|
|
// if (!fc->isInputActive()) {
|
|
// if (fc->paramshiftModifyStop(data))
|
|
// count += 1;
|
|
// }
|
|
// else
|
|
// paramshiftsleft = true;
|
|
// }
|
|
// }
|
|
// return 0;
|
|
// }
|
|
|
|
/// \brief Rewrite a CPUI_RETURN op to reflect a recovered output parameter.
|
|
///
|
|
/// Add a second input Varnode to the given CPUI_RETURN PcodeOp holding the return value
|
|
/// for the function at that point. Construct concatentations if there are multiple pieces
|
|
/// \param active is the output parameter description
|
|
/// \param retop is the given CPUI_RETURN
|
|
/// \param data is the function being analyzed
|
|
void ActionReturnRecovery::buildReturnOutput(ParamActive *active,PcodeOp *retop,Funcdata &data)
|
|
|
|
{
|
|
vector<Varnode *> newparam;
|
|
|
|
newparam.push_back(retop->getIn(0)); // Keep the first param (the return indirect reference)
|
|
for(int4 i=0;i<active->getNumTrials();++i) { // Gather all the used varnodes to this return in proper order
|
|
ParamTrial &curtrial(active->getTrial(i));
|
|
if (!curtrial.isUsed()) break;
|
|
if (curtrial.getSlot() >= retop->numInput()) break;
|
|
newparam.push_back(retop->getIn(curtrial.getSlot()));
|
|
}
|
|
if (newparam.size()<=2) // Easy zero or one return varnode case
|
|
data.opSetAllInput(retop,newparam);
|
|
else if (newparam.size()==3) { // Two piece concatenation case
|
|
Varnode *lovn = newparam[1];
|
|
Varnode *hivn = newparam[2];
|
|
ParamTrial &triallo( active->getTrial(0) );
|
|
ParamTrial &trialhi( active->getTrial(1) );
|
|
Address joinaddr = data.getArch()->constructJoinAddress(data.getArch()->translate,
|
|
trialhi.getAddress(),trialhi.getSize(),
|
|
triallo.getAddress(),triallo.getSize());
|
|
PcodeOp *newop = data.newOp(2,retop->getAddr());
|
|
data.opSetOpcode(newop,CPUI_PIECE);
|
|
Varnode *newwhole = data.newVarnodeOut(trialhi.getSize()+triallo.getSize(),joinaddr,newop);
|
|
newwhole->setWriteMask(); // Don't let new Varnode cause additional heritage
|
|
data.opInsertBefore(newop,retop);
|
|
newparam.pop_back();
|
|
newparam.back() = newwhole;
|
|
data.opSetAllInput(retop,newparam);
|
|
data.opSetInput(newop,hivn,0);
|
|
data.opSetInput(newop,lovn,1);
|
|
}
|
|
else { // We may have several varnodes from a single container
|
|
// Concatenate them into a single result
|
|
newparam.clear();
|
|
newparam.push_back(retop->getIn(0));
|
|
int4 offmatch = 0;
|
|
Varnode *preexist = (Varnode *)0;
|
|
for(int4 i=0;i<active->getNumTrials();++i) {
|
|
ParamTrial &curtrial(active->getTrial(i));
|
|
if (!curtrial.isUsed()) break;
|
|
if (curtrial.getSlot() >= retop->numInput()) break;
|
|
if (preexist == (Varnode *)0) {
|
|
preexist = retop->getIn(curtrial.getSlot());
|
|
offmatch = curtrial.getOffset() + curtrial.getSize();
|
|
}
|
|
else if (offmatch == curtrial.getOffset()) {
|
|
offmatch += curtrial.getSize();
|
|
Varnode *vn = retop->getIn(curtrial.getSlot());
|
|
// Concatenate the preexisting pieces with this new piece
|
|
PcodeOp *newop = data.newOp(2,retop->getAddr());
|
|
data.opSetOpcode(newop,CPUI_PIECE);
|
|
Address addr = preexist->getAddr();
|
|
if (vn->getAddr() < addr)
|
|
addr = vn->getAddr();
|
|
Varnode *newout = data.newVarnodeOut(preexist->getSize()+vn->getSize(),addr,newop);
|
|
newout->setWriteMask(); // Don't let new Varnode cause additional heritage
|
|
data.opSetInput(newop,vn,0); // Most sig part
|
|
data.opSetInput(newop,preexist,1);
|
|
data.opInsertBefore(newop,retop);
|
|
preexist = newout;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
if (preexist != (Varnode *)0)
|
|
newparam.push_back(preexist);
|
|
data.opSetAllInput(retop,newparam);
|
|
}
|
|
}
|
|
|
|
int4 ActionReturnRecovery::apply(Funcdata &data)
|
|
|
|
{
|
|
ParamActive *active = data.getActiveOutput();
|
|
if (active != (ParamActive *)0) {
|
|
PcodeOp *op;
|
|
Varnode *vn;
|
|
list<PcodeOp *>::const_iterator iter,iterend;
|
|
int4 i;
|
|
|
|
int4 maxancestor = data.getArch()->trim_recurse_max;
|
|
iterend = data.endOp(CPUI_RETURN);
|
|
AncestorRealistic ancestorReal;
|
|
for(iter=data.beginOp(CPUI_RETURN);iter!=iterend;++iter) {
|
|
op = *iter;
|
|
if (op->isDead()) continue;
|
|
if (op->getHaltType() != 0) continue; // Don't evaluate special halts
|
|
for(i=0;i<active->getNumTrials();++i) {
|
|
ParamTrial &trial(active->getTrial(i));
|
|
if (trial.isChecked()) continue; // Already checked
|
|
int4 slot = trial.getSlot();
|
|
vn = op->getIn(slot);
|
|
if (ancestorReal.execute(op,slot,&trial,false))
|
|
if (data.ancestorOpUse(maxancestor,vn,op,trial,0,0))
|
|
trial.markActive(); // This varnode sees active use as a parameter
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
active->finishPass();
|
|
if (active->getNumPasses() > active->getMaxPass())
|
|
active->markFullyChecked();
|
|
|
|
if (active->isFullyChecked()) {
|
|
data.getFuncProto().deriveOutputMap(active);
|
|
iterend = data.endOp(CPUI_RETURN);
|
|
for(iter=data.beginOp(CPUI_RETURN);iter!=iterend;++iter) {
|
|
op = *iter;
|
|
if (op->isDead()) continue;
|
|
if (op->getHaltType() != 0) continue;
|
|
buildReturnOutput(active,op,data);
|
|
}
|
|
data.clearActiveOutput();
|
|
count += 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionRestrictLocal::apply(Funcdata &data)
|
|
|
|
{
|
|
FuncCallSpecs *fc;
|
|
list<PcodeOp *>::const_iterator iter;
|
|
PcodeOp *op;
|
|
Varnode *vn;
|
|
int4 i;
|
|
vector<EffectRecord>::const_iterator eiter,endeiter;
|
|
|
|
for(i=0;i<data.numCalls();++i) {
|
|
fc = data.getCallSpecs(i);
|
|
op = fc->getOp();
|
|
|
|
if (!fc->isInputLocked()) continue;
|
|
if (fc->getSpacebaseOffset() == FuncCallSpecs::offset_unknown) continue;
|
|
int4 numparam = fc->numParams();
|
|
for(int4 j=0;j<numparam;++j) {
|
|
ProtoParameter *param = fc->getParam(j);
|
|
Address addr = param->getAddress();
|
|
spacetype tp = addr.getSpace()->getType();
|
|
if (tp == IPTR_SPACEBASE) {
|
|
uintb off = addr.getSpace()->wrapOffset(fc->getSpacebaseOffset() + addr.getOffset());
|
|
data.getScopeLocal()->markNotMapped(addr.getSpace(),off,param->getSize(),true);
|
|
}
|
|
else if (tp == IPTR_JOIN) {
|
|
JoinRecord *joinRec = data.getArch()->findJoin(addr.getOffset());
|
|
for(int4 k=0;k<joinRec->numPieces();++k) {
|
|
const VarnodeData &vdata(joinRec->getPiece(k));
|
|
if (vdata.space->getType() == IPTR_SPACEBASE) {
|
|
uintb off = vdata.space->wrapOffset(fc->getSpacebaseOffset() + vdata.offset);
|
|
data.getScopeLocal()->markNotMapped(vdata.space,off,vdata.size,true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
eiter = data.getFuncProto().effectBegin();
|
|
endeiter = data.getFuncProto().effectEnd();
|
|
for(;eiter!=endeiter;++eiter) { // Iterate through saved registers
|
|
if ((*eiter).getType() == EffectRecord::killedbycall) continue; // Not saved
|
|
vn = data.findVarnodeInput((*eiter).getSize(),(*eiter).getAddress());
|
|
if ((vn != (Varnode *)0)&&(vn->isUnaffected())) {
|
|
// Mark storage locations for saved registers as not mapped
|
|
// This should pickup unaffected, reload, and return_address effecttypes
|
|
for(iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
|
|
op = *iter;
|
|
if (op->code() != CPUI_COPY) continue;
|
|
Varnode *outvn = op->getOut();
|
|
if (!data.getScopeLocal()->isUnaffectedStorage(outvn)) // Is this where unaffected values get saved
|
|
continue;
|
|
data.getScopeLocal()->markNotMapped(outvn->getSpace(),outvn->getOffset(),outvn->getSize(),false);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// Count the number of inputs to \b op which have their mark set
|
|
/// \param op is the PcodeOp to count
|
|
/// \return the number of marks set
|
|
uint4 ActionLikelyTrash::countMarks(PcodeOp *op)
|
|
|
|
{
|
|
uint4 res = 0;
|
|
for(int4 i=0;i<op->numInput();++i) {
|
|
Varnode *vn = op->getIn(i);
|
|
for(;;) {
|
|
if (vn->isMark()) {
|
|
res += 1;
|
|
break;
|
|
}
|
|
if (!vn->isWritten()) break;
|
|
PcodeOp *defOp = vn->getDef();
|
|
if (defOp == op) { // We have looped all the way around
|
|
res += 1;
|
|
break;
|
|
}
|
|
else if (defOp->code() != CPUI_INDIRECT) // Chain up through INDIRECTs
|
|
break;
|
|
vn = vn->getDef()->getIn(0);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/// \brief Decide if the given Varnode only ever flows into CPUI_INDIRECT
|
|
///
|
|
/// Return all the CPUI_INDIRECT ops that the Varnode hits in a list.
|
|
/// Trace forward down all paths from -vn-, if we hit
|
|
/// - CPUI_INDIRECT -> trim that path and store that op in the resulting -indlist-
|
|
/// - CPUI_SUBPIECE
|
|
/// - CPUI_MULTIEQUAL
|
|
/// - CPUI_PIECE
|
|
/// - CPUI_AND -> follow through to output
|
|
/// - anything else -> consider -vn- to NOT be trash
|
|
///
|
|
/// For any CPUI_MULTIEQUAL and CPUI_PIECE that are hit, all the other inputs must be hit as well
|
|
/// \param vn is the given Varnode
|
|
/// \param indlist is the list to populate with CPUI_INDIRECT ops
|
|
/// \return \b true if all flows look like trash
|
|
bool ActionLikelyTrash::traceTrash(Varnode *vn,vector<PcodeOp *> &indlist)
|
|
|
|
{
|
|
vector<PcodeOp *> allroutes; // Keep track of merging ops (with more than 1 input)
|
|
vector<Varnode *> markedlist; // All varnodes we have visited on paths from -vn-
|
|
list<PcodeOp *>::const_iterator iter,enditer;
|
|
Varnode *outvn;
|
|
uintb val;
|
|
uint4 traced = 0;
|
|
vn->setMark();
|
|
markedlist.push_back(vn);
|
|
bool istrash = true;
|
|
|
|
while(traced < markedlist.size()) {
|
|
Varnode *curvn = markedlist[traced++];
|
|
iter = curvn->beginDescend();
|
|
enditer = curvn->endDescend();
|
|
for(;iter!=enditer;++iter) {
|
|
PcodeOp *op = *iter;
|
|
outvn = op->getOut();
|
|
switch(op->code()) {
|
|
case CPUI_INDIRECT:
|
|
if (outvn->isPersist())
|
|
istrash = false;
|
|
else if (op->isIndirectStore()) {
|
|
if (!outvn->isMark()) {
|
|
outvn->setMark();
|
|
markedlist.push_back(outvn);
|
|
}
|
|
}
|
|
else
|
|
indlist.push_back(op);
|
|
break;
|
|
case CPUI_SUBPIECE:
|
|
if (outvn->isPersist())
|
|
istrash = false;
|
|
else {
|
|
if (!outvn->isMark()) {
|
|
outvn->setMark();
|
|
markedlist.push_back(outvn);
|
|
}
|
|
}
|
|
break;
|
|
case CPUI_MULTIEQUAL:
|
|
case CPUI_PIECE:
|
|
if (outvn->isPersist())
|
|
istrash = false;
|
|
else {
|
|
if (!op->isMark()) {
|
|
op->setMark();
|
|
allroutes.push_back(op);
|
|
}
|
|
uint4 nummark = countMarks(op);
|
|
if (nummark == op->numInput()) {
|
|
if (!outvn->isMark()) {
|
|
outvn->setMark();
|
|
markedlist.push_back(outvn);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CPUI_INT_AND:
|
|
// If the AND is using only the topmost significant bytes then it is likely trash
|
|
if (op->getIn(1)->isConstant()) {
|
|
val = op->getIn(1)->getOffset();
|
|
uintb mask = calc_mask(op->getIn(1)->getSize());
|
|
if ((val == ((mask<<8)&mask))||(val == ((mask<<16)&mask))||(val==((mask<<32)&mask))) {
|
|
indlist.push_back(op);
|
|
break;
|
|
}
|
|
}
|
|
istrash = false;
|
|
break;
|
|
default:
|
|
istrash = false;
|
|
break;
|
|
}
|
|
if (!istrash) break;
|
|
}
|
|
if (!istrash) break;
|
|
}
|
|
|
|
for(uint4 i=0;i<allroutes.size();++i) {
|
|
if (!allroutes[i]->getOut()->isMark())
|
|
istrash = false; // Didn't see all inputs
|
|
allroutes[i]->clearMark();
|
|
}
|
|
for(uint4 i=0;i<markedlist.size();++i)
|
|
markedlist[i]->clearMark();
|
|
|
|
return istrash;
|
|
}
|
|
|
|
int4 ActionLikelyTrash::apply(Funcdata &data)
|
|
|
|
{
|
|
vector<PcodeOp *> indlist;
|
|
|
|
vector<VarnodeData>::const_iterator iter,enditer;
|
|
iter = data.getFuncProto().trashBegin();
|
|
enditer = data.getFuncProto().trashEnd();
|
|
for(;iter!=enditer;++iter) {
|
|
const VarnodeData &vdata( *iter );
|
|
Varnode *vn = data.findCoveredInput(vdata.size,vdata.getAddr());
|
|
if (vn == (Varnode *)0) continue;
|
|
if (vn->isTypeLock()||vn->isNameLock()) continue;
|
|
indlist.clear();
|
|
if (!traceTrash(vn,indlist)) continue;
|
|
|
|
for(uint4 i=0;i<indlist.size();++i) {
|
|
PcodeOp *op = indlist[i];
|
|
if (op->code() == CPUI_INDIRECT) {
|
|
// Trucate data-flow through INDIRECT, turning it into indirect creation
|
|
data.opSetInput(op,data.newConstant(op->getOut()->getSize(), 0),0);
|
|
data.markIndirectCreation(op,false);
|
|
}
|
|
else if (op->code() == CPUI_INT_AND) {
|
|
data.opSetInput(op,data.newConstant(op->getIn(1)->getSize(),0),1);
|
|
}
|
|
count += 1; // Indicate we made a change
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// \param vn is the given Varnode
|
|
/// \return \b true if it is a constant or a COPY of a constant
|
|
bool ActionRestructureVarnode::isCopyConstant(Varnode *vn)
|
|
|
|
{
|
|
if (vn->isConstant()) return true;
|
|
if (!vn->isWritten()) return false;
|
|
if (vn->getDef()->code() != CPUI_COPY) return false;
|
|
return vn->getDef()->getIn(0)->isConstant();
|
|
}
|
|
|
|
/// Return \b true if either the Varnode is a constant or if it is the not yet simplified
|
|
/// COPY or INT_ADD of constants.
|
|
/// \param vn is the given Varnode to test
|
|
/// \return \b true if the Varnode will be a constant
|
|
bool ActionRestructureVarnode::isDelayedConstant(Varnode *vn)
|
|
|
|
{
|
|
if (vn->isConstant()) return true;
|
|
if (!vn->isWritten()) return false;
|
|
PcodeOp *op = vn->getDef();
|
|
OpCode opc = op->code();
|
|
if (opc == CPUI_COPY)
|
|
return op->getIn(0)->isConstant();
|
|
if (opc != CPUI_INT_ADD) return false;
|
|
if (!isCopyConstant(op->getIn(1))) return false;
|
|
if (!isCopyConstant(op->getIn(0))) return false;
|
|
return true;
|
|
}
|
|
|
|
/// Test if the path to the given BRANCHIND originates from a constant but passes through INDIRECT operations.
|
|
/// This indicates that the switch value is produced indirectly, so we mark the earliest INDIRECT
|
|
/// operation as \e not \e collapsible, to guarantee that the indirect value is not lost during analysis.
|
|
/// \param op is the given BRANCHIND op
|
|
void ActionRestructureVarnode::protectSwitchPathIndirects(PcodeOp *op)
|
|
|
|
{
|
|
PcodeOp *lastIndirect = (PcodeOp *)0;
|
|
Varnode *curVn = op->getIn(0);
|
|
while(curVn->isWritten()) {
|
|
PcodeOp *curOp = curVn->getDef();
|
|
uint4 evalType = curOp->getEvalType();
|
|
if ((evalType & (PcodeOp::binary | PcodeOp::ternary)) != 0) {
|
|
if (curOp->numInput() > 1) {
|
|
if (isDelayedConstant(curOp->getIn(1)))
|
|
curVn = curOp->getIn(0);
|
|
else if (isDelayedConstant(curOp->getIn(0)))
|
|
curVn = curOp->getIn(1);
|
|
else
|
|
return; // Multiple paths
|
|
}
|
|
else {
|
|
curVn = curOp->getIn(0);
|
|
}
|
|
}
|
|
else if ((evalType & PcodeOp::unary) != 0)
|
|
curVn = curOp->getIn(0);
|
|
else if (curOp->code() == CPUI_INDIRECT) {
|
|
lastIndirect = curOp;
|
|
curVn = curOp->getIn(0);
|
|
}
|
|
else if (curOp->code() == CPUI_LOAD) {
|
|
curVn = curOp->getIn(1);
|
|
}
|
|
else if (curOp->code() == CPUI_MULTIEQUAL) {
|
|
// Its possible there is a path from a constant that splits and rejoins.
|
|
// We test for INDIRECTs coming into the MULTIEQUAL. If there is at least one, we prevent it from collapsing,
|
|
// otherwise we assume the MULTIEQUAL itself is unlikely to collapse.
|
|
for(int4 i=0;i<curOp->numInput();++i) {
|
|
curVn = curOp->getIn(i);
|
|
if (!curVn->isWritten()) continue;
|
|
PcodeOp *inOp = curVn->getDef();
|
|
if (inOp->code() == CPUI_INDIRECT) {
|
|
inOp->setNoIndirectCollapse();
|
|
break;
|
|
}
|
|
}
|
|
return; // In any case, we don't try to backtrack further
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
if (!curVn->isConstant()) return;
|
|
// If we reach here, there is exactly one path, from a constant to a switch
|
|
if (lastIndirect != (PcodeOp *)0)
|
|
lastIndirect->setNoIndirectCollapse();
|
|
}
|
|
|
|
/// Run through BRANCHIND ops, treat them as switches and protect the data-flow path to the destination variable
|
|
/// \param data is the function to examine
|
|
void ActionRestructureVarnode::protectSwitchPaths(Funcdata &data)
|
|
|
|
{
|
|
const BlockGraph &bblocks(data.getBasicBlocks());
|
|
for(int4 i=0;i<bblocks.getSize();++i) {
|
|
PcodeOp *op = bblocks.getBlock(i)->lastOp();
|
|
if (op == (PcodeOp *)0) continue;
|
|
if (op->code() != CPUI_BRANCHIND) continue;
|
|
protectSwitchPathIndirects(op);
|
|
}
|
|
}
|
|
|
|
int4 ActionRestructureVarnode::apply(Funcdata &data)
|
|
|
|
{
|
|
ScopeLocal *l1 = data.getScopeLocal();
|
|
|
|
bool aliasyes = (numpass != 0); // Alias calculations are not reliable on the first pass
|
|
l1->restructureVarnode(aliasyes);
|
|
if (data.syncVarnodesWithSymbols(l1,false,aliasyes))
|
|
count += 1;
|
|
|
|
if (data.isJumptableRecoveryOn())
|
|
protectSwitchPaths(data);
|
|
|
|
numpass += 1;
|
|
#ifdef OPACTION_DEBUG
|
|
if ((flags&rule_debug)==0) return 0;
|
|
ostringstream s;
|
|
data.getScopeLocal()->printEntries(s);
|
|
data.getArch()->printDebug(s.str());
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionMappedLocalSync::apply(Funcdata &data)
|
|
|
|
{
|
|
ScopeLocal *l1 = data.getScopeLocal();
|
|
|
|
if (data.syncVarnodesWithSymbols(l1,true,true))
|
|
count += 1;
|
|
|
|
if (l1->hasOverlapProbems())
|
|
data.warningHeader("Could not reconcile some variable overlaps");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionDefaultParams::apply(Funcdata &data)
|
|
|
|
{
|
|
int4 i,size;
|
|
FuncCallSpecs *fc;
|
|
ProtoModel *evalfp = data.getArch()->evalfp_called; // Special model used when evaluating called funcs
|
|
if (evalfp == (ProtoModel *)0) // If no special evaluation
|
|
evalfp = data.getArch()->defaultfp; // Use the default model
|
|
|
|
size = data.numCalls();
|
|
for(i=0;i<size;++i) {
|
|
fc = data.getCallSpecs(i);
|
|
if (!fc->hasModel()) {
|
|
Funcdata *otherfunc = fc->getFuncdata();
|
|
|
|
if (otherfunc != (Funcdata *)0) {
|
|
fc->copy(otherfunc->getFuncProto());
|
|
if ((!fc->isModelLocked())&& !fc->hasMatchingModel(evalfp))
|
|
fc->setModel(evalfp);
|
|
}
|
|
else
|
|
fc->setInternal(evalfp,data.getArch()->types->getTypeVoid());
|
|
}
|
|
fc->insertPcode(data); // Insert any necessary pcode
|
|
}
|
|
return 0; // Indicate success
|
|
}
|
|
|
|
/// \brief Check if the data-type of the given value being used as a pointer makes sense
|
|
///
|
|
/// If the data-type is a pointer make sure:
|
|
/// - The pointed-to size matches the size of the value being loaded are stored
|
|
/// - Any address space attached to the pointer matches the address space of the LOAD/STORE
|
|
///
|
|
/// If any of the conditions are violated, a warning is added to the output.
|
|
/// \param op is the LOAD/STORE acting on a pointer
|
|
/// \param vn is the given value being used as a pointer
|
|
/// \param data is the function containing the PcodeOp
|
|
void ActionSetCasts::checkPointerIssues(PcodeOp *op,Varnode *vn,Funcdata &data)
|
|
|
|
{
|
|
if (op->doesSpecialPrinting()) return;
|
|
Datatype *ptrtype = op->getIn(1)->getHighTypeReadFacing(op);
|
|
int4 valsize = vn->getSize();
|
|
if ((ptrtype->getMetatype()!=TYPE_PTR)|| (((TypePointer *)ptrtype)->getPtrTo()->getSize() != valsize)) {
|
|
string name = op->getOpcode()->getName();
|
|
name[0] = toupper( name[0] );
|
|
data.warning(name + " size is inaccurate",op->getAddr());
|
|
}
|
|
if (ptrtype->getMetatype()==TYPE_PTR) {
|
|
AddrSpace *spc = ((TypePointer *)ptrtype)->getSpace();
|
|
if (spc != (AddrSpace *)0) {
|
|
AddrSpace *opSpc = op->getIn(0)->getSpaceFromConst();
|
|
if (opSpc != spc && spc->getContain() != opSpc) {
|
|
string name = op->getOpcode()->getName();
|
|
name[0] = toupper( name[0] );
|
|
ostringstream s;
|
|
s << name << " refers to '" << opSpc->getName() << "' but pointer attribute is '";
|
|
s << spc->getName() << '\'';
|
|
data.warning(s.str(),op->getAddr());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Test if the given cast conflict can be resolved by passing to the first structure/array field
|
|
///
|
|
/// Test if the current data-type is a pointer to a structure and if interpreting
|
|
/// the data-type as a pointer to the structure's first field will get it to match the
|
|
/// required data-type.
|
|
/// \param reqtype is the required data-type
|
|
/// \param curtype is the current data-type
|
|
/// \param castStrategy is used to determine if the data-types are compatible
|
|
/// \return \b true if a pointer to the first field makes sense
|
|
bool ActionSetCasts::testStructOffset0(Datatype *reqtype,Datatype *curtype,CastStrategy *castStrategy)
|
|
|
|
{
|
|
if (curtype->getMetatype() != TYPE_PTR) return false;
|
|
Datatype *highPtrTo = ((TypePointer *)curtype)->getPtrTo();
|
|
if (highPtrTo->getMetatype() == TYPE_STRUCT) {
|
|
TypeStruct *highStruct = (TypeStruct *)highPtrTo;
|
|
if (highStruct->numDepend() == 0) return false;
|
|
vector<TypeField>::const_iterator iter = highStruct->beginField();
|
|
if ((*iter).offset != 0) return false;
|
|
reqtype = ((TypePointer *)reqtype)->getPtrTo();
|
|
curtype = (*iter).type;
|
|
if (reqtype->getMetatype() == TYPE_ARRAY)
|
|
reqtype = ((TypeArray *)reqtype)->getBase();
|
|
if (curtype->getMetatype() == TYPE_ARRAY)
|
|
curtype = ((TypeArray *)curtype)->getBase();
|
|
}
|
|
else if (highPtrTo->getMetatype() == TYPE_ARRAY) {
|
|
TypeArray *highArray = (TypeArray *)highPtrTo;
|
|
reqtype = ((TypePointer *)reqtype)->getPtrTo();
|
|
curtype = highArray->getBase();
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
if (reqtype->getMetatype() == TYPE_VOID) {
|
|
return false; // Don't induce PTRSUB for "void *"
|
|
}
|
|
return (castStrategy->castStandard(reqtype, curtype, true, true) == (Datatype *)0);
|
|
}
|
|
|
|
/// \brief Try to adjust the input and output Varnodes to eliminate a CAST
|
|
///
|
|
/// If input/output data-types are different, it may be due to late merges. For
|
|
/// unions, the CAST can sometimes be eliminated by adjusting the data-type resolutions
|
|
/// of the Varnodes relative to the PcodeOp
|
|
/// \param op is the PcodeOp reading the input Varnode and writing the output Varnode
|
|
/// \param slot is the index of the input Varnode
|
|
/// \param data is the function
|
|
/// \return \b true if an adjustment is made so that a CAST is no longer needed
|
|
bool ActionSetCasts::tryResolutionAdjustment(PcodeOp *op,int4 slot,Funcdata &data)
|
|
|
|
{
|
|
Varnode *outvn = op->getOut();
|
|
if (outvn == (Varnode *)0)
|
|
return false;
|
|
Datatype *outType = outvn->getHigh()->getType();
|
|
Datatype *inType = op->getIn(slot)->getHigh()->getType();
|
|
if (!inType->needsResolution() && !outType->needsResolution()) return false;
|
|
int4 inResolve = -1;
|
|
int4 outResolve = -1;
|
|
if (inType->needsResolution()) {
|
|
inResolve = inType->findCompatibleResolve(outType);
|
|
if (inResolve < 0) return false;
|
|
}
|
|
if (outType->needsResolution()) {
|
|
if (inResolve >= 0)
|
|
outResolve = outType->findCompatibleResolve(inType->getDepend(inResolve));
|
|
else
|
|
outResolve = outType->findCompatibleResolve(inType);
|
|
if (outResolve < 0) return false;
|
|
}
|
|
|
|
TypeFactory *typegrp = data.getArch()->types;
|
|
if (inType->needsResolution()) {
|
|
ResolvedUnion resolve(inType,inResolve,*typegrp);
|
|
if (!data.setUnionField(inType, op, slot, resolve))
|
|
return false;
|
|
}
|
|
if (outType->needsResolution()) {
|
|
ResolvedUnion resolve(outType,outResolve,*typegrp);
|
|
if (!data.setUnionField(outType, op, -1, resolve))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// \brief Test if two data-types are operation identical
|
|
///
|
|
/// If, at a source code level, a variable with data-type \b ct1 can be
|
|
/// legally substituted for another variable with data-type \b ct2, return \b true.
|
|
/// The substitution must be allowed for all possible operations the variable
|
|
/// may be involved in.
|
|
/// \param ct1 is the first data-type
|
|
/// \param ct2 is the second data-type
|
|
bool ActionSetCasts::isOpIdentical(Datatype *ct1,Datatype *ct2)
|
|
|
|
{
|
|
while((ct1->getMetatype()==TYPE_PTR)&&(ct2->getMetatype()==TYPE_PTR)) {
|
|
ct1 = ((const TypePointer *)ct1)->getPtrTo();
|
|
ct2 = ((const TypePointer *)ct2)->getPtrTo();
|
|
}
|
|
while(ct1->getTypedef() != (Datatype *)0)
|
|
ct1 = ct1->getTypedef();
|
|
while(ct2->getTypedef() != (Datatype *)0)
|
|
ct2 = ct2->getTypedef();
|
|
return (ct1 == ct2);
|
|
}
|
|
|
|
/// \brief If the given op reads a pointer to a union, insert the CPUI_PTRSUB that resolves the union
|
|
///
|
|
/// \param op is the given PcodeOp
|
|
/// \param slot is index of the input slot being read
|
|
/// \param data is the containing function
|
|
/// \param castStrategy is used to determine if the cast is still necessary after resolution
|
|
/// \return 1 if a PTRSUB is inserted, 0 otherwise
|
|
int4 ActionSetCasts::resolveUnion(PcodeOp *op,int4 slot,Funcdata &data,CastStrategy *castStrategy)
|
|
|
|
{
|
|
Varnode *vn = op->getIn(slot);
|
|
if (vn->isAnnotation()) return 0;
|
|
Datatype *dt = vn->getHigh()->getType();
|
|
if (!dt->needsResolution())
|
|
return 0;
|
|
if (dt != vn->getType())
|
|
dt->resolveInFlow(op, slot); // Last chance to resolve data-type based on flow
|
|
const ResolvedUnion *resUnion = data.getUnionField(dt, op,slot);
|
|
if (resUnion != (ResolvedUnion*)0 && resUnion->getFieldNum() >= 0) {
|
|
if (dt->getMetatype() == TYPE_PTR) {
|
|
// Test if a cast is still needed even after resolution
|
|
Datatype *reqtype = vn->getTypeReadFacing(op);
|
|
if (castStrategy->castStandard(reqtype, resUnion->getDatatype(), true, true) != (Datatype *)0)
|
|
return 0; // If cast still needed, don't do the resolve
|
|
// Insert specific placeholder indicating which field is accessed
|
|
PcodeOp *ptrsub = insertPtrsubZero(op,slot,reqtype,data);
|
|
data.setUnionField(dt, ptrsub,-1,*resUnion); // Attach the resolution to the PTRSUB
|
|
}
|
|
else if (vn->isImplied()) {
|
|
if (vn->isWritten()) {
|
|
// If the writefacing and readfacing resolutions for vn (an implied variable) are the same,
|
|
// the resolutions are unnecessary and we treat the vn as if it had the field data-type
|
|
const ResolvedUnion *writeRes = data.getUnionField(dt, vn->getDef(), -1);
|
|
if (writeRes != (const ResolvedUnion *)0 && writeRes->getFieldNum() == resUnion->getFieldNum())
|
|
return 0; // Don't print implied fields for vn
|
|
}
|
|
vn->setImpliedField();
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// \brief Insert cast to output Varnode type after given PcodeOp if it is necessary
|
|
///
|
|
/// \param op is the given PcodeOp
|
|
/// \param data is the function being analyzed
|
|
/// \param castStrategy is used to determine if the cast is necessary
|
|
/// \return 1 if a cast inserted, 0 otherwise
|
|
int4 ActionSetCasts::castOutput(PcodeOp *op,Funcdata &data,CastStrategy *castStrategy)
|
|
|
|
{
|
|
Datatype *outct,*ct,*tokenct;
|
|
Varnode *vn,*outvn;
|
|
PcodeOp *newop;
|
|
Datatype *outHighType;
|
|
bool force=false;
|
|
|
|
tokenct = op->getOpcode()->getOutputToken(op,castStrategy);
|
|
outvn = op->getOut();
|
|
outHighType = outvn->getHigh()->getType();
|
|
if (tokenct == outHighType) {
|
|
if (tokenct->needsResolution()) {
|
|
// operation copies directly to outvn AS a union
|
|
ResolvedUnion resolve(tokenct); // Force the varnode to resolve to the parent data-type
|
|
data.setUnionField(tokenct, op, -1, resolve);
|
|
}
|
|
// Short circuit more sophisticated casting tests. If they are the same type, there is no cast
|
|
return 0;
|
|
}
|
|
Datatype *outHighResolve = outHighType;
|
|
if (outHighType->needsResolution()) {
|
|
if (outHighType != outvn->getType())
|
|
outHighType->resolveInFlow(op, -1); // Last chance to resolve data-type based on flow
|
|
outHighResolve = outHighType->findResolve(op, -1); // Finish fetching DefFacing data-type
|
|
}
|
|
if (outvn->isImplied()) {
|
|
// implied varnode must have parse type
|
|
if (outvn->isTypeLock()) {
|
|
PcodeOp *outOp = outvn->loneDescend();
|
|
// The Varnode input to a CPUI_RETURN is marked as implied but
|
|
// casting should act as if it were explicit
|
|
if (outOp == (PcodeOp *)0 || outOp->code() != CPUI_RETURN) {
|
|
force = !isOpIdentical(outHighResolve, tokenct);
|
|
}
|
|
}
|
|
else if (outHighResolve->getMetatype() != TYPE_PTR) { // If implied varnode has an atomic (non-pointer) type
|
|
outvn->updateType(tokenct); // Ignore it in favor of the token type
|
|
outHighResolve = outvn->getHighTypeDefFacing();
|
|
}
|
|
else if (tokenct->getMetatype() == TYPE_PTR) { // If the token is a pointer AND implied varnode is pointer
|
|
outct = ((TypePointer *)outHighResolve)->getPtrTo();
|
|
type_metatype meta = outct->getMetatype();
|
|
// Preserve implied pointer if it points to a composite
|
|
if ((meta!=TYPE_ARRAY)&&(meta!=TYPE_STRUCT)&&(meta!=TYPE_UNION)) {
|
|
outvn->updateType(tokenct); // Otherwise ignore it in favor of the token type
|
|
outHighResolve = outvn->getHighTypeDefFacing();
|
|
}
|
|
}
|
|
}
|
|
OpCode opc = CPUI_CAST;
|
|
if (!force) {
|
|
outct = outHighResolve; // Type of result
|
|
if (outct->getMetatype() == TYPE_PTR && testStructOffset0(outct, tokenct, castStrategy)) {
|
|
opc = CPUI_PTRSUB;
|
|
}
|
|
else {
|
|
ct = castStrategy->castStandard(outct,tokenct,false,true);
|
|
if (ct == (Datatype *)0) return 0;
|
|
}
|
|
}
|
|
// Generate the cast op
|
|
vn = data.newUnique(outvn->getSize());
|
|
vn->updateType(tokenct);
|
|
vn->setImplied();
|
|
newop = data.newOp((opc != CPUI_CAST) ? 2 : 1,op->getAddr());
|
|
#ifdef CPUI_STATISTICS
|
|
data.getArch()->stats->countCast();
|
|
#endif
|
|
data.opSetOpcode(newop,opc);
|
|
data.opSetOutput(newop,outvn);
|
|
data.opSetInput(newop,vn,0);
|
|
if (opc != CPUI_CAST) {
|
|
data.opSetInput(newop,data.newConstant(4, 0),1);
|
|
}
|
|
data.opSetOutput(op,vn);
|
|
data.opInsertAfter(newop,op); // Cast comes AFTER this operation
|
|
if (tokenct->needsResolution())
|
|
data.forceFacingType(tokenct, -1, newop, 0);
|
|
if (outHighType->needsResolution())
|
|
data.inheritResolution(outHighType, newop, -1, op, -1); // Inherit write resolution
|
|
|
|
return 1;
|
|
}
|
|
|
|
/// \brief Insert a PTRSUB with offset 0 that accesses a field of the given data-type
|
|
///
|
|
/// The data-type can be a structure, in which case the field at offset zero is being accessed.
|
|
/// The data-type can reference a union, in which case a specific field is being accessed
|
|
/// as indicated by Funcdata::getUnionField. The PTRSUB is inserted right before the given
|
|
/// PcodeOp. The indicated input Varnode becomes the PTRSUB input, and the PTRSUB output
|
|
/// replaces the Varnode in the PcodeOp.
|
|
/// \param op is the given PcodeOp where the PTRSUB is inserted
|
|
/// \param slot is the slot corresponding to the indicated Varnode
|
|
/// \param ct is the data-type produced by the PTRSUB
|
|
/// \param data is containing Function
|
|
/// \return the new PTRSUB op
|
|
PcodeOp *ActionSetCasts::insertPtrsubZero(PcodeOp *op,int4 slot,Datatype *ct,Funcdata &data)
|
|
|
|
{
|
|
Varnode *vn = op->getIn(slot);
|
|
PcodeOp *newop = data.newOp(2,op->getAddr());
|
|
Varnode *vnout = data.newUniqueOut(vn->getSize(), newop);
|
|
vnout->updateType(ct);
|
|
vnout->setImplied();
|
|
data.opSetOpcode(newop, CPUI_PTRSUB);
|
|
data.opSetInput(newop,vn,0);
|
|
data.opSetInput(newop,data.newConstant(4, 0),1);
|
|
data.opSetInput(op,vnout,slot);
|
|
data.opInsertBefore(newop,op);
|
|
return newop;
|
|
}
|
|
|
|
/// \brief Insert cast to produce the input Varnode to a given PcodeOp if necessary
|
|
///
|
|
/// This method can also mark a Varnode as an explicit integer constant.
|
|
/// Guard against chains of casts.
|
|
/// \param op is the given PcodeOp
|
|
/// \param slot is the slot of the input Varnode
|
|
/// \param data is the function being analyzed
|
|
/// \param castStrategy is used to determine if a cast is necessary
|
|
/// \return 1 if a change is made, 0 otherwise
|
|
int4 ActionSetCasts::castInput(PcodeOp *op,int4 slot,Funcdata &data,CastStrategy *castStrategy)
|
|
|
|
{
|
|
Datatype *ct;
|
|
Varnode *vn,*vnout,*vnin;
|
|
PcodeOp *newop;
|
|
|
|
ct = op->getOpcode()->getInputCast(op,slot,castStrategy); // Input type expected by this operation
|
|
if (ct == (Datatype *)0) {
|
|
bool resUnsigned = castStrategy->markExplicitUnsigned(op, slot);
|
|
bool resSized = castStrategy->markExplicitLongSize(op, slot);
|
|
if (resUnsigned || resSized)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
vnin = vn = op->getIn(slot);
|
|
// Check to make sure we don't have a double cast
|
|
if (vn->isWritten() && (vn->getDef()->code() == CPUI_CAST)) {
|
|
if (vn->isImplied()) {
|
|
if (vn->loneDescend() == op) {
|
|
vn->updateType(ct);
|
|
if (vn->getType()==ct)
|
|
return 1;
|
|
}
|
|
vnin = vn->getDef()->getIn(0); // Cast directly from input of previous cast
|
|
if (ct == vnin->getType()) { // If the earlier data-type is what the input expects
|
|
data.opSetInput(op, vnin, slot); // Just use the earlier Varnode
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
else if (vn->isConstant()) {
|
|
vn->updateType(ct);
|
|
if (vn->getType() == ct)
|
|
return 1;
|
|
}
|
|
else if (ct->getMetatype() == TYPE_PTR && testStructOffset0(ct, vn->getHighTypeReadFacing(op), castStrategy)) {
|
|
// Insert a PTRSUB(vn,#0) instead of a CAST
|
|
newop = insertPtrsubZero(op, slot, ct, data);
|
|
if (vn->getHigh()->getType()->needsResolution())
|
|
data.inheritResolution(vn->getHigh()->getType(),newop, 0, op, slot);
|
|
return 1;
|
|
}
|
|
else if (tryResolutionAdjustment(op, slot, data)) {
|
|
return 1;
|
|
}
|
|
newop = data.newOp(1,op->getAddr());
|
|
vnout = data.newUniqueOut(vnin->getSize(),newop);
|
|
vnout->updateType(ct);
|
|
vnout->setImplied();
|
|
#ifdef CPUI_STATISTICS
|
|
data.getArch()->stats->countCast();
|
|
#endif
|
|
data.opSetOpcode(newop,CPUI_CAST);
|
|
data.opSetInput(newop,vnin,0);
|
|
data.opSetInput(op,vnout,slot);
|
|
data.opInsertBefore(newop,op); // Cast comes AFTER operation
|
|
if (ct->needsResolution()) {
|
|
data.forceFacingType(ct, -1, newop, -1);
|
|
}
|
|
if (vn->getHigh()->getType()->needsResolution()) {
|
|
data.inheritResolution(vn->getHigh()->getType(),newop, 0, op, slot);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int4 ActionSetCasts::apply(Funcdata &data)
|
|
|
|
{
|
|
list<PcodeOp *>::const_iterator iter;
|
|
PcodeOp *op;
|
|
|
|
data.startCastPhase();
|
|
CastStrategy *castStrategy = data.getArch()->print->getCastStrategy();
|
|
// We follow data flow, doing basic blocks in dominance order
|
|
// Doing operations in basic block order
|
|
const BlockGraph &basicblocks( data.getBasicBlocks() );
|
|
for(int4 j=0;j<basicblocks.getSize();++j) {
|
|
BlockBasic *bb = (BlockBasic *)basicblocks.getBlock(j);
|
|
for(iter=bb->beginOp();iter!=bb->endOp();++iter) {
|
|
op = *iter;
|
|
if (op->notPrinted()) continue;
|
|
OpCode opc = op->code();
|
|
if (opc == CPUI_CAST) continue;
|
|
if (opc == CPUI_PTRADD) { // Check for PTRADD that no longer fits its pointer
|
|
int4 sz = (int4)op->getIn(2)->getOffset();
|
|
Datatype *ct = op->getIn(0)->getHighTypeReadFacing(op);
|
|
if (ct->getMetatype() != TYPE_PTR ||
|
|
((TypePointer *)ct)->getPtrTo()->getAlignSize() != AddrSpace::addressToByteInt(sz, ((TypePointer *)ct)->getWordSize()))
|
|
data.opUndoPtradd(op,true);
|
|
}
|
|
else if (opc == CPUI_PTRSUB) { // Check for PTRSUB that no longer fits pointer
|
|
if (!op->getIn(0)->getTypeReadFacing(op)->isPtrsubMatching(op->getIn(1)->getOffset(),0,0)) {
|
|
if (op->getIn(1)->getOffset() == 0) {
|
|
data.opRemoveInput(op, 1);
|
|
data.opSetOpcode(op, CPUI_COPY);
|
|
}
|
|
else
|
|
data.opSetOpcode(op, CPUI_INT_ADD);
|
|
}
|
|
}
|
|
// Do input casts first, as output may depend on input
|
|
for(int4 i=0;i<op->numInput();++i) {
|
|
count += resolveUnion(op, i, data, castStrategy);
|
|
count += castInput(op,i,data,castStrategy);
|
|
}
|
|
if (opc == CPUI_LOAD) {
|
|
checkPointerIssues(op, op->getOut(), data);
|
|
}
|
|
else if (opc == CPUI_STORE) {
|
|
checkPointerIssues(op, op->getIn(2), data);
|
|
}
|
|
Varnode *vn = op->getOut();
|
|
if (vn == (Varnode *)0) continue;
|
|
count += castOutput(op,data,castStrategy);
|
|
}
|
|
}
|
|
return 0; // Indicate full completion
|
|
}
|
|
|
|
/// Name the Varnode which seems to be the putative switch variable for an
|
|
/// unrecovered jump-table with a special name.
|
|
/// \param data is the function being analyzed
|
|
void ActionNameVars::lookForBadJumpTables(Funcdata &data)
|
|
|
|
{
|
|
int4 numfunc = data.numCalls();
|
|
ScopeLocal *localmap = data.getScopeLocal();
|
|
for(int4 i=0;i<numfunc;++i) {
|
|
FuncCallSpecs *fc = data.getCallSpecs(i);
|
|
if (fc->isBadJumpTable()) {
|
|
PcodeOp *op = fc->getOp();
|
|
Varnode *vn = op->getIn(0);
|
|
if (vn->isImplied()&&vn->isWritten()) { // Skip any cast into the function
|
|
PcodeOp *castop = vn->getDef();
|
|
if (castop->code() == CPUI_CAST)
|
|
vn = castop->getIn(0);
|
|
}
|
|
if (vn->isFree()) continue;
|
|
Symbol *sym = vn->getHigh()->getSymbol();
|
|
if (sym == (Symbol *)0) continue;
|
|
if (sym->isNameLocked()) continue; // Override any unlocked name
|
|
if (sym->getScope() != localmap) continue; // Only name this in the local scope
|
|
string newname = "UNRECOVERED_JUMPTABLE";
|
|
sym->getScope()->renameSymbol(sym,localmap->makeNameUnique(newname));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Add a recommendation to the database based on a particular sub-function parameter.
|
|
///
|
|
/// We know \b vn holds data-flow for parameter \b param, try to attach its name to \b vn's symbol.
|
|
/// We update map from \b vn to a name recommendation record.
|
|
/// If \b vn is input to multiple functions, the one whose parameter has the most specified type
|
|
/// will be preferred. If \b vn is passed to the function via a cast, this name will only be used
|
|
/// if there is no other function that takes \b vn as a parameter.
|
|
/// \param param is function prototype symbol
|
|
/// \param vn is the Varnode associated with the parameter
|
|
/// \param recmap is the recommendation map
|
|
void ActionNameVars::makeRec(ProtoParameter *param,Varnode *vn,map<HighVariable *,OpRecommend> &recmap)
|
|
|
|
{
|
|
if (!param->isNameLocked()) return;
|
|
if (param->isNameUndefined()) return;
|
|
if (vn->getSize() != param->getSize()) return;
|
|
Datatype *ct = param->getType();
|
|
if (vn->isImplied()&&vn->isWritten()) { // Skip any cast into the function
|
|
PcodeOp *castop = vn->getDef();
|
|
if (castop->code() == CPUI_CAST) {
|
|
vn = castop->getIn(0);
|
|
ct = (Datatype *)0; // Indicate that this is a less preferred name
|
|
}
|
|
}
|
|
HighVariable *high = vn->getHigh();
|
|
if (high->isAddrTied()) return; // Don't propagate parameter name to address tied variable
|
|
if (param->getName().compare(0,6,"param_")==0) return;
|
|
|
|
map<HighVariable *,OpRecommend>::iterator iter = recmap.find(high);
|
|
if (iter != recmap.end()) { // We have seen this varnode before
|
|
if (ct == (Datatype *)0) return; // Cannot override with null (casted) type
|
|
Datatype *oldtype = (*iter).second.ct;
|
|
if (oldtype != (Datatype *)0) {
|
|
if (oldtype->typeOrder(*ct) <= 0) return; // oldtype is more specified
|
|
}
|
|
(*iter).second.ct = ct;
|
|
(*iter).second.namerec = param->getName();
|
|
}
|
|
else {
|
|
OpRecommend oprec;
|
|
oprec.ct = ct;
|
|
oprec.namerec = param->getName();
|
|
recmap[high] = oprec;
|
|
}
|
|
}
|
|
|
|
/// \brief Collect potential variable names from sub-function parameters.
|
|
///
|
|
/// Run through all sub-functions with a known prototype and collect potential
|
|
/// names for current Varnodes used to pass the parameters. For these Varnodes,
|
|
/// select from among these names.
|
|
/// \param data is the function being analyzed
|
|
/// \param varlist is a list of Varnodes representing HighVariables that need names
|
|
void ActionNameVars::lookForFuncParamNames(Funcdata &data,const vector<Varnode *> &varlist)
|
|
|
|
{
|
|
int4 numfunc = data.numCalls();
|
|
if (numfunc == 0) return;
|
|
|
|
map<HighVariable *,OpRecommend> recmap;
|
|
|
|
ScopeLocal *localmap = data.getScopeLocal();
|
|
for(int4 i=0;i<numfunc;++i) { // Run through all calls to functions
|
|
FuncCallSpecs *fc = data.getCallSpecs(i);
|
|
if (!fc->isInputLocked()) continue;
|
|
PcodeOp *op = fc->getOp();
|
|
int4 numparam = fc->numParams();
|
|
if (numparam >= op->numInput())
|
|
numparam = op->numInput()-1;
|
|
for(int4 j=0;j<numparam;++j) {
|
|
ProtoParameter *param = fc->getParam(j); // Looking for a parameter
|
|
Varnode *vn = op->getIn(j+1);
|
|
makeRec(param,vn,recmap);
|
|
}
|
|
}
|
|
if (recmap.empty()) return;
|
|
|
|
map<HighVariable *,OpRecommend>::iterator iter;
|
|
for(uint4 i=0;i<varlist.size();++i) { // Do the actual naming in the original (address based) order
|
|
Varnode *vn = varlist[i];
|
|
if (vn->isFree()) continue;
|
|
if (vn->isInput()) continue; // Don't override unaffected or input naming strategy
|
|
HighVariable *high = vn->getHigh();
|
|
if (high->getNumMergeClasses() > 1) continue; // Don't inherit a name if speculatively merged
|
|
Symbol *sym = high->getSymbol();
|
|
if (sym == (Symbol *)0) continue;
|
|
if (!sym->isNameUndefined()) continue;
|
|
iter = recmap.find(high);
|
|
if (iter != recmap.end()) {
|
|
sym->getScope()->renameSymbol(sym,localmap->makeNameUnique((*iter).second.namerec));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Link symbols associated with a given \e spacebase Varnode
|
|
///
|
|
/// Look for PTRSUB ops which indicate a symbol reference within the address space
|
|
/// referred to by the \e spacebase Varnode. Decode any symbol reference and link it
|
|
/// to the appropriate HighVariable
|
|
/// \param vn is the given \e spacebase Varnode
|
|
/// \param data is the function containing the Varnode
|
|
/// \param namerec is used to store any recovered Symbol without a name
|
|
void ActionNameVars::linkSpacebaseSymbol(Varnode *vn,Funcdata &data,vector<Varnode *> &namerec)
|
|
|
|
{
|
|
if (!vn->isConstant() && !vn->isInput()) return;
|
|
list<PcodeOp *>::const_iterator iter;
|
|
for(iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
|
|
PcodeOp *op = *iter;
|
|
if (op->code() != CPUI_PTRSUB) continue;
|
|
Varnode *offVn = op->getIn(1);
|
|
Symbol *sym = data.linkSymbolReference(offVn);
|
|
if ((sym != (Symbol *)0) && sym->isNameUndefined())
|
|
namerec.push_back(offVn);
|
|
}
|
|
}
|
|
|
|
/// \brief Link formal Symbols to their HighVariable representative in the given Function
|
|
///
|
|
/// Run through all HighVariables for the given function and set up the explicit mapping with
|
|
/// existing Symbol objects. If there is no matching Symbol for a given HighVariable, a new
|
|
/// Symbol is created. Any Symbol that does not have a name is added to a list for further
|
|
/// name resolution.
|
|
/// \param data is the given function
|
|
/// \param namerec is the container for collecting Symbols with a name
|
|
void ActionNameVars::linkSymbols(Funcdata &data,vector<Varnode *> &namerec)
|
|
|
|
{
|
|
const AddrSpaceManager *manage = data.getArch();
|
|
VarnodeLocSet::const_iterator iter,enditer;
|
|
AddrSpace *spc;
|
|
AddrSpace *constSpace = manage->getConstantSpace();
|
|
enditer = data.endLoc(constSpace);
|
|
for(iter=data.beginLoc(constSpace);iter!=enditer;++iter) {
|
|
Varnode *curvn = *iter;
|
|
if (curvn->getSymbolEntry() != (SymbolEntry *)0)
|
|
data.linkSymbol(curvn); // Special equate symbol
|
|
else if (curvn->isSpacebase())
|
|
linkSpacebaseSymbol(curvn, data, namerec);
|
|
}
|
|
|
|
TypeFactory *typeFactory = data.getArch()->types;
|
|
for(int4 i=0;i<manage->numSpaces();++i) { // Build a list of nameable highs
|
|
spc = manage->getSpace(i);
|
|
if (spc == (AddrSpace *)0) continue;
|
|
if (spc == constSpace) continue;
|
|
enditer = data.endLoc(spc);
|
|
for(iter=data.beginLoc(spc);iter!=enditer;++iter) {
|
|
Varnode *curvn = *iter;
|
|
if (curvn->isFree()) {
|
|
continue;
|
|
}
|
|
if (curvn->isSpacebase())
|
|
linkSpacebaseSymbol(curvn, data, namerec);
|
|
Varnode *vn = curvn->getHigh()->getNameRepresentative();
|
|
if (vn != curvn) continue; // Hit each high only once
|
|
HighVariable *high = vn->getHigh();
|
|
if (!high->hasName()) continue;
|
|
Symbol *sym = data.linkSymbol(vn);
|
|
if (sym != (Symbol *)0) { // Can we associate high with a nameable symbol
|
|
if (sym->isNameUndefined() && high->getSymbolOffset() < 0)
|
|
namerec.push_back(vn); // Add if no name, and we have a high representing the whole
|
|
if (sym->isSizeTypeLocked()) {
|
|
if (vn->getSize() == sym->getType()->getSize())
|
|
sym->getScope()->overrideSizeLockType(sym,high->getType());
|
|
}
|
|
if (vn->isAddrTied() && !sym->getScope()->isGlobal())
|
|
high->finalizeDatatype(typeFactory);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int4 ActionNameVars::apply(Funcdata &data)
|
|
|
|
{
|
|
vector<Varnode *> namerec;
|
|
|
|
linkSymbols(data, namerec);
|
|
data.getScopeLocal()->recoverNameRecommendationsForSymbols(); // Make sure recommended names hit before subfunc
|
|
lookForBadJumpTables(data);
|
|
lookForFuncParamNames(data,namerec);
|
|
|
|
int4 base = 1;
|
|
for(uint4 i=0;i<namerec.size();++i) {
|
|
Varnode *vn = namerec[i];
|
|
Symbol *sym = vn->getHigh()->getSymbol();
|
|
if (sym->isNameUndefined()) {
|
|
Scope *scope = sym->getScope();
|
|
string newname = scope->buildDefaultName(sym, base, vn);
|
|
scope->renameSymbol(sym,newname);
|
|
}
|
|
}
|
|
data.getScopeLocal()->assignDefaultNames(base);
|
|
return 0;
|
|
}
|
|
|
|
/// If the given Varnode is defined by CPUI_NEW, return -2 indicating it should be explicit
|
|
/// and that it needs special printing.
|
|
/// \param vn is the given Varnode
|
|
/// \param maxref is the maximum number of references to consider before forcing explicitness
|
|
/// \return -1 or -2 if given Varnode should be marked explicit, the number of descendants otherwise
|
|
int4 ActionMarkExplicit::baseExplicit(Varnode *vn,int4 maxref)
|
|
|
|
{
|
|
list<PcodeOp *>::const_iterator iter;
|
|
|
|
PcodeOp *def = vn->getDef();
|
|
if (def == (PcodeOp *)0) return -1;
|
|
if (def->isMarker()) return -1;
|
|
if (def->isCall()) {
|
|
if ((def->code() == CPUI_NEW)&&(def->numInput() == 1))
|
|
return -2; // Explicit, but may need special printing
|
|
return -1;
|
|
}
|
|
HighVariable *high = vn->getHigh();
|
|
if ((high!=(HighVariable *)0)&&(high->numInstances()>1)) return -1; // Must not be merged at all
|
|
if (vn->isAddrTied()) { // We need to see addrtied as explicit because pointers may reference it
|
|
if (def->code() == CPUI_SUBPIECE) {
|
|
Varnode *vin = def->getIn(0);
|
|
if (vin->isAddrTied()) {
|
|
if (vn->overlapJoin(*vin) == def->getIn(1)->getOffset())
|
|
return -1; // Should be explicit, will be a copymarker and not printed
|
|
}
|
|
}
|
|
PcodeOp *useOp = vn->loneDescend();
|
|
if (useOp == (PcodeOp *)0) return -1;
|
|
if (useOp->code() == CPUI_INT_ZEXT) {
|
|
Varnode *vnout = useOp->getOut();
|
|
if ((!vnout->isAddrTied())||(0!=vnout->contains(*vn)))
|
|
return -1;
|
|
}
|
|
else if (useOp->code() == CPUI_PIECE) {
|
|
Varnode *rootVn = PieceNode::findRoot(vn);
|
|
if (vn == rootVn) return -1;
|
|
if (rootVn->getDef()->isPartialRoot()) {
|
|
// Varnode is getting PIECEd into a structure. All such PIECE operations should be explicit.
|
|
// Internal PIECE operations will be hidden.
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
}
|
|
else if (vn->isMapped()) {
|
|
// If NOT addrtied but is still mapped, there must be either a first use (register) mapping
|
|
// or a dynamic mapping causing the bit to be set. In either case, it should probably be explicit
|
|
return -1;
|
|
}
|
|
else if (vn->isProtoPartial()) {
|
|
// Varnode is getting PIECEd into a structure. All such PIECE operations should be explicit.
|
|
// Internal PIECE operations will be hidden.
|
|
return -1;
|
|
}
|
|
else if (def->code() == CPUI_PIECE && def->getIn(0)->isProtoPartial()) {
|
|
// The base of PIECE operations building a structure should be explicit.
|
|
return -1;
|
|
}
|
|
if (vn->hasNoDescend()) return -1; // Must have at least one descendant
|
|
if (def->code() == CPUI_INSERT) {
|
|
PcodeOp *storeOp = def->getOut()->loneDescend();
|
|
if (storeOp == (PcodeOp *)0 || storeOp->code() != CPUI_STORE)
|
|
return -1; // INSERT output is explicit unless it is immediately used by STORE
|
|
}
|
|
|
|
if (def->code() == CPUI_PTRSUB) { // A dereference
|
|
Varnode *basevn = def->getIn(0);
|
|
if (basevn->isSpacebase()) { // of a spacebase
|
|
if (basevn->isConstant() || basevn->isInput())
|
|
maxref = 1000000; // Should always be implicit, so remove limit on max references
|
|
}
|
|
}
|
|
int4 desccount = 0;
|
|
for(iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
|
|
PcodeOp *op = *iter;
|
|
if (op->isMarker()) return -1;
|
|
desccount += 1;
|
|
if (desccount > maxref) return -1; // Must not exceed max descendants
|
|
}
|
|
|
|
return desccount;
|
|
}
|
|
|
|
/// Look for certain situations where one Varnode with multiple descendants has one descendant who also has
|
|
/// multiple descendants. This routine is handed the list of Varnodes with multiple descendants;
|
|
/// These all must already have their mark set.
|
|
/// For the situations we can find with one flowing into another, mark the top Varnode
|
|
/// as \e explicit.
|
|
/// \param multlist is the list Varnodes with multiple descendants
|
|
/// \return the number Varnodes that were marked as explicit
|
|
int4 ActionMarkExplicit::multipleInteraction(vector<Varnode *> &multlist)
|
|
|
|
{
|
|
vector<Varnode *> purgelist;
|
|
|
|
for(int4 i=0;i<multlist.size();++i) {
|
|
Varnode *vn = multlist[i]; // All elements in this list should have a defining op
|
|
PcodeOp *op = vn->getDef();
|
|
OpCode opc = op->code();
|
|
if (op->isBoolOutput() || (opc == CPUI_INT_ZEXT) || (opc == CPUI_INT_SEXT) || (opc == CPUI_PTRADD)) {
|
|
int4 maxparam = 2;
|
|
if (op->numInput() < maxparam)
|
|
maxparam = op->numInput();
|
|
Varnode *topvn = (Varnode *)0;
|
|
for(int4 j=0;j<maxparam;++j) {
|
|
topvn = op->getIn(j);
|
|
if (topvn->isMark()) { // We have a "multiple" interaction between -topvn- and -vn-
|
|
OpCode topopc = CPUI_COPY;
|
|
if (topvn->isWritten()) {
|
|
if (topvn->getDef()->isBoolOutput())
|
|
continue; // Try not to make boolean outputs explicit
|
|
topopc = topvn->getDef()->code();
|
|
}
|
|
if (opc == CPUI_PTRADD) {
|
|
if (topopc == CPUI_PTRADD)
|
|
purgelist.push_back(topvn);
|
|
}
|
|
else
|
|
purgelist.push_back(topvn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int4 i=0;i<purgelist.size();++i) {
|
|
Varnode *vn = purgelist[i];
|
|
vn->setExplicit();
|
|
vn->clearImplied();
|
|
vn->clearMark();
|
|
}
|
|
return purgelist.size();
|
|
}
|
|
|
|
/// Record the Varnode just encountered and set-up the next (backward) edges to traverse.
|
|
/// \param v is the Varnode just encountered
|
|
ActionMarkExplicit::OpStackElement::OpStackElement(Varnode *v)
|
|
|
|
{
|
|
vn = v;
|
|
slot = 0;
|
|
slotback = 0;
|
|
if (v->isWritten()) {
|
|
OpCode opc = v->getDef()->code();
|
|
if (opc == CPUI_LOAD) {
|
|
slot = 1;
|
|
slotback = 2;
|
|
}
|
|
else if (opc == CPUI_PTRADD)
|
|
slotback = 1; // Don't traverse the multiplier slot
|
|
else if (opc == CPUI_SEGMENTOP) {
|
|
slot = 2;
|
|
slotback = 3;
|
|
}
|
|
else
|
|
slotback = v->getDef()->numInput();
|
|
}
|
|
}
|
|
|
|
/// Count the number of terms in the expression making up \b vn. If
|
|
/// there are more than \b max terms, mark \b vn as \e explicit.
|
|
/// The given Varnode is already assumed to have multiple descendants.
|
|
/// We do a depth first traversal along op inputs, to recursively
|
|
/// calculate the number of explicit terms in an expression.
|
|
/// \param vn is the given Varnode
|
|
/// \param max is the maximum number of terms to allow
|
|
void ActionMarkExplicit::processMultiplier(Varnode *vn,int4 max)
|
|
|
|
{
|
|
vector<OpStackElement> opstack;
|
|
Varnode *vncur;
|
|
int4 finalcount = 0;
|
|
|
|
opstack.push_back(vn);
|
|
do {
|
|
vncur = opstack.back().vn;
|
|
bool isaterm = vncur->isExplicit() || (!vncur->isWritten());
|
|
if (isaterm || (opstack.back().slotback<=opstack.back().slot)) { // Trimming condition
|
|
if (isaterm) {
|
|
if (!vncur->isSpacebase()) // Don't count space base
|
|
finalcount += 1;
|
|
}
|
|
if (finalcount > max) {
|
|
vn->setExplicit(); // Make this variable explicit
|
|
vn->clearImplied();
|
|
return;
|
|
}
|
|
opstack.pop_back();
|
|
}
|
|
else {
|
|
PcodeOp *op = vncur->getDef();
|
|
Varnode *newvn = op->getIn(opstack.back().slot++);
|
|
if (newvn->isMark()) { // If an ancestor is marked(also possible implied with multiple descendants)
|
|
vn->setExplicit(); // then automatically consider this to be explicit
|
|
vn->clearImplied();
|
|
}
|
|
opstack.push_back(newvn);
|
|
}
|
|
} while(!opstack.empty());
|
|
}
|
|
|
|
/// Assume \b vn is produced via a CPUI_NEW operation. If it is immediately fed to a constructor,
|
|
/// set special printing flags on the Varnode.
|
|
/// \param data is the function being analyzed
|
|
/// \param vn is the given Varnode
|
|
void ActionMarkExplicit::checkNewToConstructor(Funcdata &data,Varnode *vn)
|
|
|
|
{ PcodeOp *op = vn->getDef();
|
|
BlockBasic *bb = op->getParent();
|
|
PcodeOp *firstuse = (PcodeOp *)0;
|
|
list<PcodeOp *>::const_iterator iter;
|
|
for(iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
|
|
PcodeOp *curop = *iter;
|
|
if (curop->getParent() != bb) continue;
|
|
if (firstuse == (PcodeOp *)0)
|
|
firstuse = curop;
|
|
else if (curop->getSeqNum().getOrder() < firstuse->getSeqNum().getOrder())
|
|
firstuse = curop;
|
|
else if (curop->code() == CPUI_CALLIND) {
|
|
Varnode *ptr = curop->getIn(0);
|
|
if (ptr->isWritten()) {
|
|
if (ptr->getDef() == firstuse)
|
|
firstuse = curop;
|
|
}
|
|
}
|
|
}
|
|
if (firstuse == (PcodeOp *)0) return;
|
|
|
|
if (!firstuse->isCall()) return;
|
|
if (firstuse->getOut() != (Varnode *)0) return;
|
|
if (firstuse->numInput() < 2) return; // Must have at least 1 parameter (plus destination varnode)
|
|
if (firstuse->getIn(1) != vn) return; // First parameter must result of new
|
|
// if (!fc->isConstructor()) return; // Function must be a constructor
|
|
data.opMarkSpecialPrint(firstuse); // Mark call to print the new operator as well
|
|
data.opMarkNonPrinting(op); // Don't print the new operator as stand-alone operation
|
|
}
|
|
|
|
int4 ActionMarkExplicit::apply(Funcdata &data)
|
|
|
|
{
|
|
VarnodeDefSet::const_iterator viter,enditer;
|
|
vector<Varnode *> multlist; // implied varnodes with >1 descendants
|
|
int4 maxref;
|
|
|
|
maxref = data.getArch()->max_implied_ref;
|
|
enditer = data.beginDef(0); // Cut out free varnodes
|
|
for(viter=data.beginDef();viter!=enditer;++viter) {
|
|
Varnode *vn = *viter;
|
|
|
|
int4 desccount = baseExplicit(vn,maxref);
|
|
if (desccount < 0) {
|
|
vn->setExplicit();
|
|
count += 1;
|
|
if (desccount < -1)
|
|
checkNewToConstructor(data,vn);
|
|
}
|
|
else if (desccount > 1) { // Keep track of possible implieds with more than one descendant
|
|
vn->setMark();
|
|
multlist.push_back(vn);
|
|
}
|
|
}
|
|
|
|
count += multipleInteraction(multlist);
|
|
int4 maxdup = data.getArch()->max_term_duplication;
|
|
for(int4 i=0;i<multlist.size();++i) {
|
|
Varnode *vn = multlist[i];
|
|
if (vn->isMark()) // Mark may have been cleared by multipleInteraction
|
|
processMultiplier(vn,maxdup);
|
|
}
|
|
for(int4 i=0;i<multlist.size();++i)
|
|
multlist[i]->clearMark();
|
|
return 0;
|
|
}
|
|
|
|
/// Return false only if one Varnode is obtained by adding non-zero thing to another Varnode.
|
|
/// The order of the Varnodes is not important.
|
|
/// \param vn1 is the first Varnode
|
|
/// \param vn2 is the second Varnode
|
|
/// \return false if the additive relationship holds
|
|
bool ActionMarkImplied::isPossibleAliasStep(Varnode *vn1,Varnode *vn2)
|
|
|
|
{
|
|
Varnode *var[2];
|
|
var[0] = vn1;
|
|
var[1] = vn2;
|
|
for(int4 i=0;i<2;++i) {
|
|
Varnode *vncur = var[i];
|
|
if (!vncur->isWritten()) continue;
|
|
PcodeOp *op = vncur->getDef();
|
|
OpCode opc = op->code();
|
|
if ((opc!=CPUI_INT_ADD)&&(opc!=CPUI_PTRSUB)&&(opc!=CPUI_PTRADD)&&(opc!=CPUI_INT_XOR)) continue;
|
|
if (var[1-i] != op->getIn(0)) continue;
|
|
if (op->getIn(1)->isConstant()) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/// Return false \b only if we can guarantee two Varnodes have different values.
|
|
/// \param vn1 is the first Varnode
|
|
/// \param vn2 is the second Varnode
|
|
/// \param depth is the maximum level to recurse
|
|
/// \return true if its possible the Varnodes hold the same value
|
|
bool ActionMarkImplied::isPossibleAlias(Varnode *vn1,Varnode *vn2,int4 depth)
|
|
|
|
{
|
|
if (vn1 == vn2) return true; // Definite alias
|
|
if ((!vn1->isWritten())||(!vn2->isWritten())) {
|
|
if (vn1->isConstant() && vn2->isConstant())
|
|
return (vn1->getOffset()==vn2->getOffset()); // FIXME: these could be NEAR each other and still have an alias
|
|
return isPossibleAliasStep(vn1,vn2);
|
|
}
|
|
|
|
if (!isPossibleAliasStep(vn1,vn2))
|
|
return false;
|
|
Varnode *cvn1,*cvn2;
|
|
PcodeOp *op1 = vn1->getDef();
|
|
PcodeOp *op2 = vn2->getDef();
|
|
OpCode opc1 = op1->code();
|
|
OpCode opc2 = op2->code();
|
|
int4 mult1 = 1;
|
|
int4 mult2 = 1;
|
|
if (opc1 == CPUI_PTRSUB)
|
|
opc1 = CPUI_INT_ADD;
|
|
else if (opc1 == CPUI_PTRADD) {
|
|
opc1 = CPUI_INT_ADD;
|
|
mult1 = (int4) op1->getIn(2)->getOffset();
|
|
}
|
|
if (opc2 == CPUI_PTRSUB)
|
|
opc2 = CPUI_INT_ADD;
|
|
else if (opc2 == CPUI_PTRADD) {
|
|
opc2 = CPUI_INT_ADD;
|
|
mult2 = (int4) op2->getIn(2)->getOffset();
|
|
}
|
|
if (opc1 != opc2) return true;
|
|
if (depth == 0) return true; // Couldn't find absolute difference
|
|
depth -= 1;
|
|
switch(opc1) {
|
|
case CPUI_COPY:
|
|
case CPUI_INT_ZEXT:
|
|
case CPUI_INT_SEXT:
|
|
case CPUI_INT_2COMP:
|
|
case CPUI_INT_NEGATE:
|
|
return isPossibleAlias(op1->getIn(0),op2->getIn(0),depth);
|
|
case CPUI_INT_ADD:
|
|
cvn1 = op1->getIn(1);
|
|
cvn2 = op2->getIn(1);
|
|
if (cvn1->isConstant() && cvn2->isConstant()) {
|
|
uintb val1 = mult1 * cvn1->getOffset();
|
|
uintb val2 = mult2 * cvn2->getOffset();
|
|
if (val1 == val2)
|
|
return isPossibleAlias(op1->getIn(0),op2->getIn(0),depth);
|
|
return !functionalEquality(op1->getIn(0),op2->getIn(0));
|
|
}
|
|
if (mult1 != mult2) return true;
|
|
if (functionalEquality(op1->getIn(0),op2->getIn(0)))
|
|
return isPossibleAlias(op1->getIn(1),op2->getIn(1),depth);
|
|
if (functionalEquality(op1->getIn(1),op2->getIn(1)))
|
|
return isPossibleAlias(op1->getIn(0),op2->getIn(0),depth);
|
|
if (functionalEquality(op1->getIn(0),op2->getIn(1)))
|
|
return isPossibleAlias(op1->getIn(1),op2->getIn(0),depth);
|
|
if (functionalEquality(op1->getIn(1),op2->getIn(0)))
|
|
return isPossibleAlias(op1->getIn(0),op2->getIn(1),depth);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Marking a Varnode as \e implied causes the input Varnodes to its defining op to propagate farther
|
|
/// in the output. This may cause eventual variables to hold different values at the same
|
|
/// point in the code. Any input must test that its propagated Cover doesn't intersect its current Cover.
|
|
/// \param data is the function being analyzed
|
|
/// \param vn is the given Varnode
|
|
/// \return \b true if there is a Cover violation
|
|
bool ActionMarkImplied::checkImpliedCover(Funcdata &data,Varnode *vn)
|
|
|
|
{
|
|
PcodeOp *op,*storeop,*callop;
|
|
Varnode *defvn;
|
|
int4 i;
|
|
|
|
op = vn->getDef();
|
|
if (op->code() == CPUI_LOAD) { // Check for loads crossing stores
|
|
list<PcodeOp *>::const_iterator oiter,iterend;
|
|
iterend = data.endOp(CPUI_STORE);
|
|
for(oiter=data.beginOp(CPUI_STORE);oiter!=iterend;++oiter) {
|
|
storeop = *oiter;
|
|
if (storeop->isDead()) continue;
|
|
if (vn->getCover()->contain(storeop,2)) {
|
|
// The LOAD crosses a STORE. We are cavalier
|
|
// and let it through unless we can verify
|
|
// that the pointers are actually the same
|
|
if (storeop->getIn(0)->getOffset() == op->getIn(0)->getOffset()) {
|
|
// if (!functionalDifference(storeop->getIn(1),op->getIn(1),2)) return false;
|
|
if (isPossibleAlias(storeop->getIn(1),op->getIn(1),2)) return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (op->isCall() || (op->code() == CPUI_LOAD)) { // loads crossing calls
|
|
for(i=0;i<data.numCalls();++i) {
|
|
callop = data.getCallSpecs(i)->getOp();
|
|
if (vn->getCover()->contain(callop,2)) return false;
|
|
}
|
|
}
|
|
for(i=0;i<op->numInput();++i) {
|
|
defvn = op->getIn(i);
|
|
if (defvn->isConstant()) continue;
|
|
if (data.getMerge().inflateTest(defvn,vn->getHigh())) // Test for intersection
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int4 ActionMarkImplied::apply(Funcdata &data)
|
|
|
|
{
|
|
VarnodeLocSet::const_iterator viter;
|
|
list<PcodeOp *>::const_iterator oiter;
|
|
Varnode *vn,*vncur,*outvn;
|
|
vector<DescTreeElement> varstack; // Depth first varnode traversal stack
|
|
|
|
for(viter=data.beginLoc();viter!=data.endLoc();++viter) {
|
|
vn = *viter;
|
|
if (vn->isFree()) continue;
|
|
if (vn->isExplicit()) continue;
|
|
if (vn->isImplied()) continue;
|
|
varstack.push_back(vn);
|
|
do {
|
|
vncur = varstack.back().vn;
|
|
if (varstack.back().desciter == vncur->endDescend()) {
|
|
// All descendants are traced first, try to make vncur implied
|
|
count += 1; // Will be marked either explicit or implied
|
|
if (!checkImpliedCover(data,vncur)) // Can this variable be implied
|
|
vncur->setExplicit(); // if not, mark explicit
|
|
else {
|
|
Merge::markImplied(vncur);
|
|
// setting the implied type is now taken care of by ActionSetCasts
|
|
// vn->updatetype(op->outputtype_token(),false,false); // implied must have parsed type
|
|
}
|
|
varstack.pop_back();
|
|
}
|
|
else {
|
|
outvn = (*varstack.back().desciter++)->getOut();
|
|
if (outvn != (Varnode *)0) {
|
|
if ((!outvn->isExplicit())&&(!outvn->isImplied()))
|
|
varstack.push_back(outvn);
|
|
}
|
|
}
|
|
} while(!varstack.empty());
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionUnreachable::apply(Funcdata &data)
|
|
|
|
{ // Detect unreachable blocks and remove
|
|
if (data.removeUnreachableBlocks(true,false))
|
|
count += 1; // Deleting at least one block
|
|
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionDoNothing::apply(Funcdata &data)
|
|
|
|
{ // Remove blocks that do nothing
|
|
int4 i;
|
|
const BlockGraph &graph(data.getBasicBlocks());
|
|
BlockBasic *bb;
|
|
|
|
for(i=0;i<graph.getSize();++i) {
|
|
bb = (BlockBasic *) graph.getBlock(i);
|
|
if (bb->isDoNothing()) {
|
|
if ((bb->sizeOut()==1)&&(bb->getOut(0)==bb)) { // Infinite loop
|
|
if (!bb->isDonothingLoop()) {
|
|
bb->setDonothingLoop();
|
|
data.warning("Do nothing block with infinite loop",bb->getStart());
|
|
}
|
|
}
|
|
else if (bb->unblockedMulti(0)) {
|
|
data.removeDoNothingBlock(bb);
|
|
count += 1;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionRedundBranch::apply(Funcdata &data)
|
|
|
|
{
|
|
// Remove redundant branches, i.e. a CPUI_CBRANCH that falls thru and branches to the same place
|
|
int4 i,j;
|
|
const BlockGraph &graph(data.getBasicBlocks());
|
|
BlockBasic *bb;
|
|
FlowBlock *bl;
|
|
|
|
for(i=0;i<graph.getSize();++i) {
|
|
bb = (BlockBasic *) graph.getBlock(i);
|
|
if (bb->sizeOut() == 0) continue;
|
|
bl = bb->getOut(0);
|
|
if (bb->sizeOut() == 1) {
|
|
if ((bl->sizeIn() == 1)&&(!bl->isEntryPoint())&&(!bb->isSwitchOut())) {
|
|
// Do not splice block coming from single exit switch as this prevents possible second stage recovery
|
|
data.spliceBlockBasic(bb);
|
|
count += 1;
|
|
// This will remove one block, so reset i
|
|
i = -1;
|
|
}
|
|
continue;
|
|
}
|
|
for(j=1;j<bb->sizeOut();++j) // Are all exits to the same block? (bl)
|
|
if (bb->getOut(j) != bl) break;
|
|
if (j!=bb->sizeOut()) continue;
|
|
|
|
// ostringstream s;
|
|
// s << "Removing redundant branch out of block ";
|
|
// s << "code_" << bb->start.Target().getShortcut();
|
|
// bb->start.Target().printRaw(s);
|
|
// data.warningHeader(s.str());
|
|
data.removeBranch(bb,1); // Remove the branch instruction
|
|
count += 1;
|
|
}
|
|
return 0; // Indicate full rule was applied
|
|
}
|
|
|
|
int4 ActionDeterminedBranch::apply(Funcdata &data)
|
|
|
|
{
|
|
int4 i;
|
|
const BlockGraph &graph(data.getBasicBlocks());
|
|
BlockBasic *bb;
|
|
PcodeOp *cbranch;
|
|
|
|
for(i=0;i<graph.getSize();++i) {
|
|
bb = (BlockBasic *) graph.getBlock(i);
|
|
cbranch = bb->lastOp();
|
|
if ((cbranch == (PcodeOp *)0)||(cbranch->code() != CPUI_CBRANCH)) continue;
|
|
if (!cbranch->getIn(1)->isConstant()) continue;
|
|
uintb val = cbranch->getIn(1)->getOffset();
|
|
int4 num = ((val!=0)!=cbranch->isBooleanFlip()) ? 0 : 1;
|
|
data.removeBranch(bb,num);
|
|
count += 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// Given a new \e consume value to push to a Varnode, determine if this changes
|
|
/// the Varnodes consume value and whether to push the Varnode onto the work-list.
|
|
/// \param val is the new consume value
|
|
/// \param vn is the Varnode to push to
|
|
/// \param worklist is the current work-list
|
|
inline void ActionDeadCode::pushConsumed(uintb val,Varnode *vn,vector<Varnode *> &worklist)
|
|
|
|
{
|
|
uintb newval = (val | vn->getConsume())&calc_mask(vn->getSize());
|
|
if ((newval == vn->getConsume())&&vn->isConsumeVacuous()) return;
|
|
vn->setConsumeVacuous();
|
|
if (!vn->isConsumeList()) { // Check if already in list
|
|
vn->setConsumeList(); // Mark as in the list
|
|
if (vn->isWritten())
|
|
worklist.push_back(vn); // add to list
|
|
}
|
|
vn->setConsume(newval);
|
|
}
|
|
|
|
/// \brief Propagate the \e consumed value for one Varnode
|
|
///
|
|
/// The Varnode at the top of the stack is popped off, and its current
|
|
/// \e consumed value is propagated backward to the inputs of the op
|
|
/// that produced it.
|
|
/// \param worklist is the current stack of dirty Varnodes
|
|
void ActionDeadCode::propagateConsumed(vector<Varnode *> &worklist)
|
|
|
|
{
|
|
Varnode *vn = worklist.back();
|
|
worklist.pop_back();
|
|
uintb outc = vn->getConsume();
|
|
vn->clearConsumeList();
|
|
|
|
PcodeOp *op = vn->getDef(); // Assume vn is written
|
|
|
|
int4 sz;
|
|
uintb a,b;
|
|
|
|
switch(op->code()) {
|
|
case CPUI_INT_MULT:
|
|
b = coveringmask(outc);
|
|
if (op->getIn(1)->isConstant()) {
|
|
int4 leastSet = leastsigbit_set(op->getIn(1)->getOffset());
|
|
if (leastSet >= 0) {
|
|
a = calc_mask(vn->getSize()) >> leastSet;
|
|
a &= b;
|
|
}
|
|
else
|
|
a = 0;
|
|
}
|
|
else
|
|
a = b;
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
pushConsumed(b,op->getIn(1),worklist);
|
|
break;
|
|
case CPUI_INT_ADD:
|
|
case CPUI_INT_SUB:
|
|
a = coveringmask(outc); // Make sure value is filled out as a contiguous mask
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
pushConsumed(a,op->getIn(1),worklist);
|
|
break;
|
|
case CPUI_SUBPIECE:
|
|
sz = op->getIn(1)->getOffset();
|
|
if (sz >= sizeof(uintb)) // If we are truncating beyond the precision of the consume field
|
|
a = 0; // this tells us nothing about consuming bits within the field
|
|
else
|
|
a = outc << (sz*8);
|
|
if ((a==0)&&(outc!=0)&&(op->getIn(0)->getSize() > sizeof(uintb))) {
|
|
// If the consumed mask is zero because
|
|
// it isn't big enough to cover the whole varnode and
|
|
// there are still upper bits that are consumed
|
|
a = ~((uintb)0);
|
|
a = a ^ (a >> 1); // Set the highest bit possible in the mask to indicate some consumption
|
|
}
|
|
b = (outc == 0) ? 0 : ~((uintb)0);
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
pushConsumed(b,op->getIn(1),worklist);
|
|
break;
|
|
case CPUI_PIECE:
|
|
sz = op->getIn(1)->getSize();
|
|
if (vn->getSize() > sizeof(uintb)) { // If the concatenation goes beyond the consume precision
|
|
if (sz >= sizeof(uintb)) {
|
|
a = ~((uintb)0); // Assume the bits not in the consume field are consumed
|
|
b = outc;
|
|
}
|
|
else {
|
|
a = (outc >> (sz*8)) ^ ( (~((uintb)0)) << 8*(sizeof(uintb)-sz));
|
|
b = outc ^ (a << (sz*8));
|
|
}
|
|
}
|
|
else {
|
|
a = outc >> (sz*8);
|
|
b = outc ^ (a << (sz*8));
|
|
}
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
pushConsumed(b,op->getIn(1),worklist);
|
|
break;
|
|
case CPUI_INDIRECT:
|
|
pushConsumed(outc,op->getIn(0),worklist);
|
|
if (op->getIn(1)->getSpace()->getType()==IPTR_IOP) {
|
|
PcodeOp *indop = PcodeOp::getOpFromConst(op->getIn(1)->getAddr());
|
|
if (!indop->isDead()) {
|
|
if (indop->code() == CPUI_COPY) {
|
|
if (indop->getOut()->characterizeOverlap(*op->getOut())>0) {
|
|
pushConsumed(~((uintb)0),indop->getOut(),worklist); // Mark the copy as consumed
|
|
indop->setIndirectSource();
|
|
}
|
|
// If we reach here, there isn't a true block of INDIRECT (RuleIndirectCollapse will convert it to COPY)
|
|
}
|
|
else
|
|
indop->setIndirectSource();
|
|
}
|
|
}
|
|
break;
|
|
case CPUI_COPY:
|
|
case CPUI_INT_NEGATE:
|
|
pushConsumed(outc,op->getIn(0),worklist);
|
|
break;
|
|
case CPUI_INT_XOR:
|
|
case CPUI_INT_OR:
|
|
pushConsumed(outc,op->getIn(0),worklist);
|
|
pushConsumed(outc,op->getIn(1),worklist);
|
|
break;
|
|
case CPUI_INT_AND:
|
|
if (op->getIn(1)->isConstant()) {
|
|
uintb val = op->getIn(1)->getOffset();
|
|
pushConsumed(outc&val,op->getIn(0),worklist);
|
|
pushConsumed(outc,op->getIn(1),worklist);
|
|
}
|
|
else {
|
|
pushConsumed(outc,op->getIn(0),worklist);
|
|
pushConsumed(outc,op->getIn(1),worklist);
|
|
}
|
|
break;
|
|
case CPUI_MULTIEQUAL:
|
|
for(int4 i=0;i<op->numInput();++i)
|
|
pushConsumed(outc,op->getIn(i),worklist);
|
|
break;
|
|
case CPUI_INT_ZEXT:
|
|
pushConsumed(outc,op->getIn(0),worklist);
|
|
break;
|
|
case CPUI_INT_SEXT:
|
|
b = calc_mask(op->getIn(0)->getSize());
|
|
a = outc & b;
|
|
if (outc > b)
|
|
a |= (b ^ (b>>1)); // Make sure signbit is marked used
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
break;
|
|
case CPUI_INT_LEFT:
|
|
if (op->getIn(1)->isConstant()) {
|
|
sz = vn->getSize();
|
|
int4 sa = op->getIn(1)->getOffset();
|
|
if (sz > sizeof(uintb)) { // If there exists bits beyond the precision of the consume field
|
|
if (sa >= 8*sizeof(uintb))
|
|
a = ~((uintb)0); // Make sure we assume one bits where we shift in unrepresented bits
|
|
else if (sa == 0) {
|
|
a = outc;
|
|
}
|
|
else
|
|
a = (outc >> sa) ^ ( (~((uintb)0)) << (8*sizeof(uintb)-sa));
|
|
sz = 8*sz -sa;
|
|
if (sz < 8*sizeof(uintb)) {
|
|
uintb mask = ~((uintb)0);
|
|
mask <<= sz;
|
|
a = a & ~mask; // Make sure high bits that are left shifted out are not marked consumed
|
|
}
|
|
}
|
|
else
|
|
a = outc >> sa; // Most cases just do this
|
|
b = (outc == 0) ? 0 : ~((uintb)0);
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
pushConsumed(b,op->getIn(1),worklist);
|
|
}
|
|
else {
|
|
a = (outc==0) ? 0 : ~((uintb)0);
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
pushConsumed(a,op->getIn(1),worklist);
|
|
}
|
|
break;
|
|
case CPUI_INT_RIGHT:
|
|
if (op->getIn(1)->isConstant()) {
|
|
int4 sa = op->getIn(1)->getOffset();
|
|
if (sa >= 8*sizeof(uintb)) // If the shift is beyond the precision of the consume field
|
|
a = 0; // We know nothing about the low order consumption of the input bits
|
|
else
|
|
a = outc << sa; // Most cases just do this
|
|
b = (outc == 0) ? 0 : ~((uintb)0);
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
pushConsumed(b,op->getIn(1),worklist);
|
|
}
|
|
else {
|
|
a = (outc==0) ? 0 : ~((uintb)0);
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
pushConsumed(a,op->getIn(1),worklist);
|
|
}
|
|
break;
|
|
case CPUI_INT_LESS:
|
|
case CPUI_INT_LESSEQUAL:
|
|
case CPUI_INT_EQUAL:
|
|
case CPUI_INT_NOTEQUAL:
|
|
if (outc==0)
|
|
a = 0;
|
|
else // Anywhere we know is zero, is not getting "consumed"
|
|
a = op->getIn(0)->getNZMask() | op->getIn(1)->getNZMask();
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
pushConsumed(a,op->getIn(1),worklist);
|
|
break;
|
|
case CPUI_INSERT:
|
|
a = 1;
|
|
a <<= (int4)op->getIn(3)->getOffset();
|
|
a -= 1; // Insert mask
|
|
pushConsumed(a,op->getIn(1),worklist);
|
|
a <<= (int4)op->getIn(2)->getOffset();
|
|
pushConsumed(outc & ~a, op->getIn(0), worklist);
|
|
b = (outc == 0) ? 0 : ~((uintb)0);
|
|
pushConsumed(b,op->getIn(2), worklist);
|
|
pushConsumed(b,op->getIn(3), worklist);
|
|
break;
|
|
case CPUI_ZPULL:
|
|
case CPUI_SPULL:
|
|
a = 1;
|
|
a <<= (int4)op->getIn(2)->getOffset();
|
|
a -= 1; // Pull mask
|
|
a &= outc; // Consumed bits of mask
|
|
a <<= (int4)op->getIn(1)->getOffset();
|
|
pushConsumed(a,op->getIn(0),worklist);
|
|
b = (outc == 0) ? 0 : ~((uintb)0);
|
|
pushConsumed(b,op->getIn(1), worklist);
|
|
pushConsumed(b,op->getIn(2), worklist);
|
|
break;
|
|
case CPUI_POPCOUNT:
|
|
case CPUI_LZCOUNT:
|
|
a = 16 * op->getIn(0)->getSize() - 1; // Mask for possible bits that could be set
|
|
a &= outc; // Of the bits that could be set, which are consumed
|
|
b = (a == 0) ? 0 : ~((uintb)0); // if any consumed, treat all input bits as consumed
|
|
pushConsumed(b,op->getIn(0), worklist);
|
|
break;
|
|
case CPUI_CALL:
|
|
case CPUI_CALLIND:
|
|
break; // Call output doesn't indicate consumption of inputs
|
|
case CPUI_FLOAT_INT2FLOAT:
|
|
a = 0;
|
|
if (outc != 0)
|
|
a = coveringmask(op->getIn(0)->getNZMask());
|
|
pushConsumed(a,op->getIn(0), worklist);
|
|
break;
|
|
default:
|
|
a = (outc==0) ? 0 : ~((uintb)0); // all or nothing
|
|
for(int4 i=0;i<op->numInput();++i)
|
|
pushConsumed(a,op->getIn(i),worklist);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/// \brief Deal with unconsumed Varnodes
|
|
///
|
|
/// For a Varnode, none of whose bits are consumed, eliminate the PcodeOp defining it
|
|
/// and replace Varnode inputs to ops that officially read it with zero constants.
|
|
/// \param vn is the Varnode
|
|
/// \param data is the function being analyzed
|
|
/// \return true if the Varnode was eliminated
|
|
bool ActionDeadCode::neverConsumed(Varnode *vn,Funcdata &data)
|
|
|
|
{
|
|
if (vn->getSize() > sizeof(uintb)) return false; // Not enough precision to really tell
|
|
list<PcodeOp *>::const_iterator iter;
|
|
PcodeOp *op;
|
|
iter = vn->beginDescend();
|
|
while(iter != vn->endDescend()) {
|
|
op = *iter++; // Advance before ref is removed
|
|
int4 slot = op->getSlot(vn);
|
|
// Replace vn with 0 whereever it is read
|
|
// We don't worry about putting a constant in a marker
|
|
// because if vn is not consumed and is input to a marker
|
|
// then the output is also not consumed and the marker
|
|
// op is about to be deleted anyway
|
|
data.opSetInput(op,data.newConstant(vn->getSize(),0),slot);
|
|
}
|
|
op = vn->getDef();
|
|
if (op->isCall())
|
|
data.opUnsetOutput(op); // For calls just get rid of output
|
|
else
|
|
data.opDestroy(op); // Otherwise completely remove the op
|
|
return true;
|
|
}
|
|
|
|
/// \brief Determine how the given sub-function parameters are consumed
|
|
///
|
|
/// Set the consume property for each input Varnode of a CPUI_CALL or CPUI_CALLIND.
|
|
/// If the prototype is locked, assume parameters are entirely consumed.
|
|
/// \param fc is the call specification for the given sub-function
|
|
/// \param worklist will hold input Varnodes that can propagate their consume property
|
|
void ActionDeadCode::markConsumedParameters(FuncCallSpecs *fc,vector<Varnode *> &worklist)
|
|
|
|
{
|
|
PcodeOp *callOp = fc->getOp();
|
|
pushConsumed(~((uintb)0),callOp->getIn(0),worklist); // In all cases the first operand is fully consumed
|
|
if (fc->isInputLocked() || fc->isInputActive()) { // If the prototype is locked in, or in active recovery
|
|
for(int4 i=1;i<callOp->numInput();++i)
|
|
pushConsumed(~((uintb)0),callOp->getIn(i),worklist); // Treat all parameters as fully consumed
|
|
return;
|
|
}
|
|
for(int4 i=1;i<callOp->numInput();++i) {
|
|
Varnode *vn = callOp->getIn(i);
|
|
uintb consumeVal;
|
|
if (vn->isAutoLive())
|
|
consumeVal = ~((uintb)0);
|
|
else
|
|
consumeVal = minimalmask(vn->getNZMask());
|
|
int4 bytesConsumed = fc->getInputBytesConsumed(i);
|
|
if (bytesConsumed != 0)
|
|
consumeVal &= calc_mask(bytesConsumed);
|
|
pushConsumed(consumeVal,vn,worklist);
|
|
}
|
|
}
|
|
|
|
/// \brief Determine how the \e return \e values for the given function are consumed
|
|
///
|
|
/// Examine each CPUI_RETURN to see how the Varnode input is consumed.
|
|
/// If the function's prototype is locked, assume the Varnode is entirely consumed.
|
|
/// If there are no CPUI_RETURN ops, return 0
|
|
/// \param data is the given function
|
|
/// \return the bit mask of what is consumed
|
|
uintb ActionDeadCode::gatherConsumedReturn(Funcdata &data)
|
|
|
|
{
|
|
if (data.getFuncProto().isOutputLocked() || data.getActiveOutput() != (ParamActive *)0)
|
|
return ~((uintb)0);
|
|
list<PcodeOp *>::const_iterator iter,enditer;
|
|
enditer = data.endOp(CPUI_RETURN);
|
|
uintb consumeVal = 0;
|
|
for(iter=data.beginOp(CPUI_RETURN);iter!=enditer;++iter) {
|
|
PcodeOp *returnOp = *iter;
|
|
if (returnOp->isDead()) continue;
|
|
if (returnOp->numInput() > 1) {
|
|
Varnode *vn = returnOp->getIn(1);
|
|
consumeVal |= minimalmask(vn->getNZMask());
|
|
}
|
|
}
|
|
int4 val = data.getFuncProto().getReturnBytesConsumed();
|
|
if (val != 0) {
|
|
consumeVal &= calc_mask(val);
|
|
}
|
|
return consumeVal;
|
|
}
|
|
|
|
/// \brief Check if there are any unconsumed LOADs that may be from volatile addresses.
|
|
///
|
|
/// It may be too early to remove certain LOAD operations even though their result isn't
|
|
/// consumed because it may be of a volatile address with side effects. If a LOAD meets this
|
|
/// criteria, it is added to the worklist and \b true is returned.
|
|
/// \param data is the function being analyzed
|
|
/// \param worklist is the container of consumed Varnodes to further process
|
|
/// \return \b true if there was at least one LOAD added to the worklist
|
|
bool ActionDeadCode::lastChanceLoad(Funcdata &data,vector<Varnode *> &worklist)
|
|
|
|
{
|
|
if (data.getHeritagePass() > 1) return false;
|
|
if (data.isJumptableRecoveryOn()) return false;
|
|
list<PcodeOp *>::const_iterator iter = data.beginOp(CPUI_LOAD);
|
|
list<PcodeOp *>::const_iterator enditer = data.endOp(CPUI_LOAD);
|
|
bool res = false;
|
|
while(iter != enditer) {
|
|
PcodeOp *op = *iter;
|
|
++iter;
|
|
if (op->isDead()) continue;
|
|
Varnode *vn = op->getOut();
|
|
if (vn->isConsumeVacuous()) continue;
|
|
if (op->getIn(1)->isEventualConstant(3, 1)) {
|
|
pushConsumed(~(uintb)0, vn, worklist);
|
|
vn->setAutoLiveHold();
|
|
res = true;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int4 ActionDeadCode::apply(Funcdata &data)
|
|
|
|
{
|
|
int4 i;
|
|
list<PcodeOp *>::const_iterator iter;
|
|
PcodeOp *op;
|
|
Varnode *vn;
|
|
uintb returnConsume;
|
|
vector<Varnode *> worklist;
|
|
VarnodeLocSet::const_iterator viter,endviter;
|
|
const AddrSpaceManager *manage = data.getArch();
|
|
AddrSpace *spc;
|
|
|
|
// Clear consume flags
|
|
for(viter=data.beginLoc();viter!=data.endLoc();++viter) {
|
|
vn = *viter;
|
|
vn->clearConsumeList();
|
|
vn->clearConsumeVacuous();
|
|
vn->setConsume(0);
|
|
if (vn->isAddrForce()&&(!vn->isDirectWrite()))
|
|
vn->clearAddrForce();
|
|
}
|
|
|
|
// Set pre-live registers
|
|
for(i=0;i<manage->numSpaces();++i) {
|
|
spc = manage->getSpace(i);
|
|
if (spc == (AddrSpace *)0 || !spc->doesDeadcode()) continue;
|
|
if (data.deadRemovalAllowed(spc)) continue; // Mark consumed if we have NOT heritaged
|
|
viter = data.beginLoc(spc);
|
|
endviter = data.endLoc(spc);
|
|
while(viter != endviter) {
|
|
vn = *viter++;
|
|
pushConsumed(~((uintb)0),vn,worklist);
|
|
}
|
|
}
|
|
|
|
returnConsume = gatherConsumedReturn(data);
|
|
for(iter=data.beginOpAlive();iter!=data.endOpAlive();++iter) {
|
|
op = *iter;
|
|
|
|
op->clearIndirectSource();
|
|
if (op->isCall()) {
|
|
// Postpone setting consumption on CALL and CALLIND inputs
|
|
if (op->isCallWithoutSpec()) {
|
|
for(i=0;i<op->numInput();++i)
|
|
pushConsumed(~((uintb)0),op->getIn(i),worklist);
|
|
}
|
|
if (!op->isAssignment())
|
|
continue;
|
|
if (op->holdOutput())
|
|
pushConsumed(~((uintb)0),op->getOut(),worklist);
|
|
}
|
|
else if (!op->isAssignment()) {
|
|
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);
|
|
}
|
|
// Postpone setting consumption on RETURN input
|
|
continue;
|
|
}
|
|
else {
|
|
for(i=0;i<op->numInput();++i) {
|
|
vn = op->getIn(i);
|
|
if (vn->isAutoLive())
|
|
pushConsumed(~((uintb)0),vn,worklist);
|
|
}
|
|
}
|
|
vn = op->getOut();
|
|
if (vn->isAutoLive())
|
|
pushConsumed(~((uintb)0),vn,worklist);
|
|
}
|
|
|
|
// Mark consumption of call parameters
|
|
for(i=0;i<data.numCalls();++i)
|
|
markConsumedParameters(data.getCallSpecs(i),worklist);
|
|
|
|
// Propagate the consume flags
|
|
while(!worklist.empty())
|
|
propagateConsumed(worklist);
|
|
|
|
if (lastChanceLoad(data, worklist)) {
|
|
while(!worklist.empty())
|
|
propagateConsumed(worklist);
|
|
}
|
|
|
|
for(i=0;i<manage->numSpaces();++i) {
|
|
spc = manage->getSpace(i);
|
|
if (spc == (AddrSpace *)0 || !spc->doesDeadcode()) continue;
|
|
if (!data.deadRemovalAllowed(spc)) continue; // Don't eliminate if we haven't heritaged
|
|
viter = data.beginLoc(spc);
|
|
endviter = data.endLoc(spc);
|
|
int4 changecount = 0;
|
|
while(viter != endviter) {
|
|
vn = *viter++; // Advance iterator BEFORE (possibly) deleting varnode
|
|
if (!vn->isWritten()) continue;
|
|
bool vacflag = vn->isConsumeVacuous();
|
|
vn->clearConsumeList();
|
|
vn->clearConsumeVacuous();
|
|
if (!vacflag) { // Not even vacuously consumed
|
|
op = vn->getDef();
|
|
changecount += 1;
|
|
if (op->isCall())
|
|
data.opUnsetOutput(op); // For calls just get rid of output
|
|
else
|
|
data.opDestroy(op); // Otherwise completely remove the op
|
|
}
|
|
else {
|
|
// Check for values that are never used, but bang around
|
|
// for a while
|
|
if (vn->getConsume()==0) {
|
|
if (neverConsumed(vn,data))
|
|
changecount += 1;
|
|
}
|
|
}
|
|
}
|
|
if (changecount != 0)
|
|
data.seenDeadcode(spc); // Record that we have seen dead code for this space
|
|
}
|
|
#ifdef OPACTION_DEBUG
|
|
data.debugModPrint(getName()); // Print dead ops before freeing them
|
|
#endif
|
|
data.clearDeadVarnodes();
|
|
data.clearDeadOps();
|
|
return 0;
|
|
}
|
|
|
|
/// \brief Clear all marks on the given list of PcodeOps
|
|
///
|
|
/// \param opList is the given list
|
|
void ActionConditionalConst::clearMarks(const vector<PcodeOp *> &opList)
|
|
|
|
{
|
|
for(int4 i=0;i<opList.size();++i)
|
|
opList[i]->clearMark();
|
|
}
|
|
|
|
/// \brief Collect COPY, INDIRECT, and MULTIEQUAL ops reachable from the given Varnode, without going thru excised edges
|
|
///
|
|
/// If data-flow from the Varnode does not go through excised edges and reaches the op via other MULTIEQUALs,
|
|
/// INDIRECTs, and COPYs, the op is put in a list, and its mark is set
|
|
/// \param vn is the given Varnode
|
|
/// \param phiNodeEdges is the list of edges to excise
|
|
/// \param reachable will hold the list ops that have been reached
|
|
void ActionConditionalConst::collectReachable(Varnode *vn,vector<PcodeOpNode> &phiNodeEdges,vector<PcodeOp *> &reachable)
|
|
|
|
{
|
|
sort(phiNodeEdges.begin(),phiNodeEdges.end());
|
|
int4 count = 0;
|
|
if (vn->isWritten()) {
|
|
PcodeOp *op = vn->getDef();
|
|
if (op->code() == CPUI_MULTIEQUAL) {
|
|
// Consider defining MULTIEQUAL to be "reachable" This allows flowToAlternatePath to discover
|
|
// a loop back to vn from the constBlock, even if no other non-constant path survives
|
|
op->setMark();
|
|
reachable.push_back(op);
|
|
}
|
|
}
|
|
for(;;) {
|
|
list<PcodeOp *>::const_iterator iter;
|
|
for(iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
|
|
PcodeOp *op = *iter;
|
|
if (op->isMark()) continue;
|
|
OpCode opc = op->code();
|
|
if (opc == CPUI_MULTIEQUAL) {
|
|
PcodeOpNode tmpOp(op,0);
|
|
for(tmpOp.slot=0;tmpOp.slot<op->numInput();++tmpOp.slot) {
|
|
if (op->getIn(tmpOp.slot) != vn) continue; // Find incoming slot for current Varnode
|
|
// Don't count as flow if coming thru excised edge
|
|
if (!binary_search(phiNodeEdges.begin(),phiNodeEdges.end(),tmpOp)) break;
|
|
}
|
|
if (tmpOp.slot == op->numInput()) continue; // Was the MULTIEQUAL reached
|
|
}
|
|
else if (opc != CPUI_COPY && opc != CPUI_INDIRECT)
|
|
continue;
|
|
reachable.push_back(op);
|
|
op->setMark();
|
|
}
|
|
if (count >= reachable.size()) break;
|
|
vn = reachable[count]->getOut();
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
/// \brief Does the output of the given op reunite with the alternate flow
|
|
///
|
|
/// Assuming alternate flows have been marked, follow the flow of the given op forward through
|
|
/// MULTIEQUAL, INDIRECT, and COPY ops. If it hits the alternate flow, return \b true.
|
|
/// \param op is the given PcodeOp
|
|
/// \return \b true is there is an alternate path
|
|
bool ActionConditionalConst::flowToAlternatePath(PcodeOp *op)
|
|
|
|
{
|
|
if (op->isMark()) return true;
|
|
vector<Varnode *> markSet;
|
|
Varnode *vn = op->getOut();
|
|
markSet.push_back(vn);
|
|
vn->setMark();
|
|
int4 count = 0;
|
|
bool foundPath = false;
|
|
while(count < markSet.size()) {
|
|
vn = markSet[count];
|
|
count += 1;
|
|
list<PcodeOp *>::const_iterator iter;
|
|
for(iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
|
|
PcodeOp *nextOp = *iter;
|
|
OpCode opc = nextOp->code();
|
|
if (opc == CPUI_MULTIEQUAL) {
|
|
if (nextOp->isMark()) {
|
|
foundPath = true;
|
|
break;
|
|
}
|
|
}
|
|
else if (opc != CPUI_COPY && opc != CPUI_INDIRECT)
|
|
continue;
|
|
Varnode *outVn = nextOp->getOut();
|
|
if (outVn->isMark()) continue;
|
|
outVn->setMark();
|
|
markSet.push_back(outVn);
|
|
}
|
|
if (foundPath) break;
|
|
}
|
|
for(int4 i=0;i<markSet.size();++i)
|
|
markSet[i]->clearMark();
|
|
return foundPath;
|
|
}
|
|
|
|
/// \brief Test if flow from a specific edge is disjoint from other edges
|
|
///
|
|
/// All MULTIEQUAL and COPY ops reachable from the edge are marked. If any other edge
|
|
/// is in this marked set, mark both edges in the result set.
|
|
/// \param edges is the set of edges
|
|
/// \param i is the index of the specific edge to test
|
|
/// \param result is the array of marks to be returned
|
|
/// \return \b true if the selected edge flows together with any other edge
|
|
bool ActionConditionalConst::flowTogether(const vector<PcodeOpNode> &edges,int4 i,vector<int4> &result)
|
|
|
|
{
|
|
vector<PcodeOp *> reachable;
|
|
vector<PcodeOpNode> excise; // No edge excised
|
|
collectReachable(edges[i].op->getOut(),excise,reachable);
|
|
bool res = false;
|
|
for(int4 j=0;j<edges.size();++j) {
|
|
if (i == j) continue;
|
|
if (result[j] == 0) continue; // Check for disconnected path
|
|
if (edges[j].op->isMark()) {
|
|
result[i] = 2; // Disconnected paths, which flow together
|
|
result[j] = 2;
|
|
res = true;
|
|
}
|
|
}
|
|
clearMarks(reachable);
|
|
return res;
|
|
}
|
|
|
|
/// \brief Place a COPY of a constant at the end of a basic block
|
|
///
|
|
/// \param op is an alternate "last" op
|
|
/// \param bl is the basic block
|
|
/// \param constVn is the constant to be assigned
|
|
/// \param data is the function containing the block
|
|
/// \return the new output Varnode of the COPY
|
|
Varnode *ActionConditionalConst::placeCopy(PcodeOp *op,BlockBasic *bl,Varnode *constVn,Funcdata &data)
|
|
|
|
{
|
|
PcodeOp *lastOp = bl->lastOp();
|
|
list<PcodeOp *>::iterator iter;
|
|
Address addr;
|
|
if (lastOp == (PcodeOp *)0) {
|
|
iter = bl->endOp();
|
|
addr = op->getAddr();
|
|
}
|
|
else if (lastOp->isBranch()) {
|
|
iter = lastOp->getBasicIter(); // Insert before any branch
|
|
addr = lastOp->getAddr();
|
|
}
|
|
else {
|
|
iter = bl->endOp();
|
|
addr = lastOp->getAddr();
|
|
}
|
|
PcodeOp *copyOp = data.newOp(1,addr);
|
|
data.opSetOpcode(copyOp, CPUI_COPY);
|
|
Varnode *outVn = data.newUniqueOut(constVn->getSize(), copyOp);
|
|
data.opSetInput(copyOp,constVn,0);
|
|
data.opInsert(copyOp, bl, iter);
|
|
return outVn;
|
|
}
|
|
|
|
/// \brief Place a single COPY assignment shared by multiple MULTIEQUALs
|
|
///
|
|
/// Find the common ancestor block among all MULTIEQUALs marked as flowing together.
|
|
/// Place a COPY assigning a constant at the bottom of this block.
|
|
/// Replace all the input edge Varnodes on the MULTIEQUALs with the output of this COPY.
|
|
/// \param phiNodeEdges is the list of MULTIEQUALs and their incoming edges
|
|
/// \param marks are the marks applied to the MULTIEQUALs (2 == flowtogether)
|
|
/// \param constVn is the constant being assigned by the COPY
|
|
/// \param data is the function
|
|
void ActionConditionalConst::placeMultipleConstants(vector<PcodeOpNode> &phiNodeEdges,vector<int4> &marks,
|
|
Varnode *constVn,Funcdata &data)
|
|
{
|
|
vector<FlowBlock *> blocks;
|
|
PcodeOp *op = (PcodeOp *)0;
|
|
for(int4 i=0;i<phiNodeEdges.size();++i) {
|
|
if (marks[i] != 2) continue; // Check that the MULTIQUAL is marked as flowing together
|
|
op = phiNodeEdges[i].op;
|
|
FlowBlock *bl = op->getParent();
|
|
bl = bl->getIn(phiNodeEdges[i].slot);
|
|
blocks.push_back(bl);
|
|
}
|
|
BlockBasic *rootBlock = (BlockBasic *)FlowBlock::findCommonBlock(blocks);
|
|
Varnode *outVn = placeCopy(op, rootBlock, constVn, data);
|
|
for(int4 i=0;i<phiNodeEdges.size();++i) {
|
|
if (marks[i] != 2) continue;
|
|
data.opSetInput(phiNodeEdges[i].op, outVn, phiNodeEdges[i].slot);
|
|
}
|
|
}
|
|
|
|
/// \brief Try to push the constant at the front point through to the output of the given PcodeOp
|
|
///
|
|
/// If successful, create a ConstPoint to search for reads of this new constant.
|
|
/// \param points is the set of points with the current point at the front
|
|
/// \param op is p-code op to push the constant through
|
|
void ActionConditionalConst::pushConstant(list<ConstPoint> &points,PcodeOp *op)
|
|
|
|
{
|
|
if ((op->getEvalType() & PcodeOp::special) != 0) return;
|
|
if (op->getOpcode()->isFloatingPointOp()) return;
|
|
Varnode *outvn = op->getOut();
|
|
if (outvn->getSize() > sizeof(uintb)) return;
|
|
Varnode *vn = points.front().vn;
|
|
int4 slot = op->getSlot(vn);
|
|
uintb in[3];
|
|
for(int4 i=0;i<op->numInput();++i) {
|
|
if (i == slot)
|
|
in[i] = points.front().value;
|
|
else {
|
|
Varnode *inVn = op->getIn(i);
|
|
if (inVn->getSize() > sizeof(uintb)) return;
|
|
if (inVn->isConstant())
|
|
in[i] = op->getIn(i)->getOffset();
|
|
else
|
|
return; // Not all inputs are constant
|
|
}
|
|
}
|
|
bool evalError;
|
|
uintb outval = op->executeSimple(in,evalError);
|
|
if (evalError)
|
|
return;
|
|
points.emplace_back(outvn,outval,points.front().constBlock,points.front().inSlot,points.front().blockIsDom);
|
|
}
|
|
|
|
/// \brief Replace MULTIEQUAL edges with constant if there is no alternate flow
|
|
///
|
|
/// A given Varnode is known to be constant along a set of MULTIEQUAL edges. If these edges are excised from the
|
|
/// data-flow, and the output of a MULTIEQUAL does not rejoin with the Varnode along an alternate path, then that
|
|
/// edge is replaced with a constant.
|
|
/// \param varVn is the given Varnode
|
|
/// \param constVn is the constant to replace it with
|
|
/// \param phiNodeEdges is the set of edges the Varnode is known to be constant on
|
|
/// \param data is the function containing this data-flow
|
|
void ActionConditionalConst::handlePhiNodes(Varnode *varVn,Varnode *constVn,vector<PcodeOpNode> &phiNodeEdges,Funcdata &data)
|
|
|
|
{
|
|
vector<PcodeOp *> alternateFlow;
|
|
vector<int4> results(phiNodeEdges.size(),0);
|
|
collectReachable(varVn,phiNodeEdges,alternateFlow);
|
|
int4 alternate = 0;
|
|
for(int4 i=0;i<phiNodeEdges.size();++i) {
|
|
if (!flowToAlternatePath(phiNodeEdges[i].op)) {
|
|
results[i] = 1; // Mark as disconnecting
|
|
alternate += 1;
|
|
}
|
|
}
|
|
clearMarks(alternateFlow);
|
|
|
|
bool hasFlowTogether = false;
|
|
if (alternate > 1) {
|
|
// If we reach here, multiple MULTIEQUAL are disjoint from the non-constant flow
|
|
for(int4 i=0;i<results.size();++i) {
|
|
if (results[i] == 0) continue; // Is this a disconnected path
|
|
if (flowTogether(phiNodeEdges,i,results)) // Check if the disconnected paths flow together
|
|
hasFlowTogether = true;
|
|
}
|
|
}
|
|
// Add COPY assignment for each edge that has its own disconnected path going forward
|
|
for(int4 i=0;i<phiNodeEdges.size();++i) {
|
|
if (results[i] != 1) continue; // Check for disconnected path that does not flow into another path
|
|
PcodeOp *op = phiNodeEdges[i].op;
|
|
int4 slot = phiNodeEdges[i].slot;
|
|
BlockBasic *bl = (BlockBasic *)op->getParent()->getIn(slot);
|
|
Varnode *outVn = placeCopy(op, bl, constVn, data);
|
|
data.opSetInput(op,outVn,slot);
|
|
count += 1;
|
|
}
|
|
if (hasFlowTogether) {
|
|
placeMultipleConstants(phiNodeEdges, results, constVn, data); // Add COPY assignment for edges that flow together
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
/// \brief Test if we can reach the given Varnode via a path other than through the immediate edge
|
|
///
|
|
/// The given Varnode is an input to a MULTIEQUAL through a specific input slot. If we can reach the
|
|
/// same Varnode backtracking through one of the other slots, return \b true. We can backtrack
|
|
/// through MULTIEQUALs up to a given depth and possibly a final INT_ADD.
|
|
/// \param vn is the given Varnode
|
|
/// \param op is the MULTIEQUAL reading \b vn
|
|
/// \param slot is the input index of \b vn
|
|
/// \param depth is the maximum depth to backtrack
|
|
/// \return \b true if an alternate path to the Varnode is found
|
|
bool ActionConditionalConst::testAlternatePath(Varnode *vn,PcodeOp *op,int4 slot,int4 depth)
|
|
|
|
{
|
|
for(int4 i=0;i<op->numInput();++i) {
|
|
if (i == slot) continue;
|
|
Varnode *inVn = op->getIn(i);
|
|
if (inVn == vn) return true;
|
|
if (inVn->isWritten()) {
|
|
PcodeOp *curOp = inVn->getDef();
|
|
OpCode opc = curOp->code();
|
|
if (opc == CPUI_INT_ADD || opc == CPUI_PTRSUB || opc == CPUI_PTRADD) {
|
|
if (curOp->getIn(0) == vn || curOp->getIn(1) == vn)
|
|
return true;
|
|
}
|
|
else if (opc == CPUI_MULTIEQUAL) {
|
|
if (depth == 0) continue;
|
|
if (testAlternatePath(vn,curOp,-1,depth-1))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// \brief At each ConstPoint, replace reads of the Varnode down the constant path with a constant Varnode
|
|
///
|
|
/// Process ConstPoints from the front of the list.
|
|
/// For each read op of current point's Varnode, check if it is in the constant path.
|
|
/// If it is, replace the read with a new constant Varnode.
|
|
/// If not in the constant path, attempt to make the output Varnode into a new ConstPoint that may have
|
|
/// have reads in the constant path.
|
|
/// \param points is the list of ConstPoints
|
|
/// \param useMultiequal is \b true if conditional constants can be applied to MULTIEQUAL ops
|
|
/// \param data is the function being analyzed
|
|
void ActionConditionalConst::propagateConstant(list<ConstPoint> &points,bool useMultiequal,Funcdata &data)
|
|
|
|
{
|
|
vector<PcodeOpNode> phiNodeEdges;
|
|
while(!points.empty()) {
|
|
ConstPoint &point(points.front());
|
|
Varnode *varVn = point.vn;
|
|
Varnode *constVn = point.constVn;
|
|
FlowBlock *constBlock = point.constBlock;
|
|
list<PcodeOp *>::const_iterator iter = varVn->beginDescend();
|
|
list<PcodeOp *>::const_iterator enditer = varVn->endDescend();
|
|
while(iter != enditer) {
|
|
PcodeOp *op = *iter;
|
|
while(iter != enditer && *iter == op)
|
|
++iter; // Advance iterator off of current op, as this descendant may be erased
|
|
OpCode opc = op->code();
|
|
if (opc == CPUI_INDIRECT) // Don't propagate constant into these
|
|
continue;
|
|
else if (opc == CPUI_MULTIEQUAL) {
|
|
if (!useMultiequal)
|
|
continue;
|
|
if (varVn->isAddrTied() && varVn->getAddr() == op->getOut()->getAddr())
|
|
continue;
|
|
FlowBlock *bl = op->getParent();
|
|
if (bl == constBlock) { // The immediate edge from the conditional block, coming into a MULTIEQUAL
|
|
if (op->getIn(point.inSlot) == varVn) {
|
|
// Its possible the compiler still intends the constant value to be the same variable
|
|
// Test for conditions when this is likely so we don't unnecessarily create a new variable
|
|
if (point.value > 1) continue;
|
|
if (op->getOut()->isAddrTied()) continue;
|
|
if (testAlternatePath(varVn, op, point.inSlot, 2)) continue;
|
|
phiNodeEdges.emplace_back(op,point.inSlot);
|
|
}
|
|
}
|
|
else if (point.blockIsDom) {
|
|
for(int4 slot=0;slot<op->numInput();++slot) {
|
|
if (op->getIn(slot) == varVn) {
|
|
if (constBlock->dominates(bl->getIn(slot))) {
|
|
phiNodeEdges.emplace_back(op,slot);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
else if (opc == CPUI_COPY) { // Don't propagate into COPY unless...
|
|
PcodeOp *followOp = op->getOut()->loneDescend();
|
|
if (followOp == (PcodeOp *)0) continue;
|
|
if (followOp->isMarker()) continue;
|
|
if (followOp->code() == CPUI_COPY) continue;
|
|
// ...unless COPY is into something more interesting
|
|
}
|
|
if (!point.blockIsDom) continue;
|
|
if (constBlock->dominates(op->getParent())) {
|
|
if (constVn == (Varnode *)0)
|
|
constVn = data.newConstant(varVn->getSize(), point.value);
|
|
if (opc == CPUI_RETURN) {
|
|
// CPUI_RETURN ops can't directly take constants
|
|
// as inputs
|
|
PcodeOp *copyBeforeRet = data.newOp(1, op->getAddr());
|
|
data.opSetOpcode(copyBeforeRet,CPUI_COPY);
|
|
data.opSetInput(copyBeforeRet,constVn,0);
|
|
data.newVarnodeOut(varVn->getSize(),varVn->getAddr(),copyBeforeRet);
|
|
data.opSetInput(op,copyBeforeRet->getOut(),1);
|
|
data.opInsertBefore(copyBeforeRet,op);
|
|
}
|
|
else {
|
|
int4 slot = op->getSlot(varVn);
|
|
data.opSetInput(op,constVn,slot); // Replace ref with constant!
|
|
}
|
|
count += 1; // We made a change
|
|
}
|
|
else {
|
|
pushConstant(points, op);
|
|
}
|
|
}
|
|
if (!phiNodeEdges.empty()) {
|
|
if (constVn == (Varnode *)0)
|
|
constVn = data.newConstant(varVn->getSize(), point.value);
|
|
handlePhiNodes(varVn, constVn, phiNodeEdges, data);
|
|
phiNodeEdges.clear();
|
|
}
|
|
points.pop_front();
|
|
}
|
|
}
|
|
|
|
/// \brief Find a Varnode being compared to a constant creating the given CBRANCH boolean
|
|
///
|
|
/// If the boolean is created by comparing a Varnode to a constant, create a ConstPoint record
|
|
/// indicating the path down which the Varnode can be considered constant.
|
|
/// \param points will hold any new ConstPoint
|
|
/// \param boolVn is the given CBRANCH boolean
|
|
/// \param bl is the block constaining the CBRANCH
|
|
/// \param blockDom is an array of booleans indicating along which out edges a constant could be pushed
|
|
/// \param flipEdge is \b true if the meaning of the CBRANCH has been flipped
|
|
void ActionConditionalConst::findConstCompare(list<ConstPoint> &points,Varnode *boolVn,FlowBlock *bl,
|
|
bool *blockDom,bool flipEdge)
|
|
{
|
|
if (!boolVn->isWritten()) return;
|
|
PcodeOp *compOp = boolVn->getDef();
|
|
OpCode opc = compOp->code();
|
|
if (opc == CPUI_BOOL_NEGATE) {
|
|
flipEdge = !flipEdge;
|
|
boolVn = compOp->getIn(0);
|
|
if (!boolVn->isWritten()) return;
|
|
compOp = boolVn->getDef();
|
|
opc = compOp->code();
|
|
}
|
|
int4 constEdge; // Out edge where value is constant
|
|
if (opc == CPUI_INT_EQUAL)
|
|
constEdge = 1;
|
|
else if (opc == CPUI_INT_NOTEQUAL)
|
|
constEdge = 0;
|
|
else
|
|
return;
|
|
// Find the variable and verify that it is compared to a constant
|
|
Varnode *varVn = compOp->getIn(0);
|
|
Varnode *constVn = compOp->getIn(1);
|
|
if (!constVn->isConstant()) {
|
|
if (!varVn->isConstant())
|
|
return;
|
|
Varnode *tmp = constVn;
|
|
constVn = varVn;
|
|
varVn = tmp;
|
|
}
|
|
if (varVn->loneDescend() != (PcodeOp *)0) return;
|
|
if (flipEdge)
|
|
constEdge = 1 - constEdge;
|
|
points.emplace_back(varVn,constVn,bl->getOut(constEdge),bl->getOutRevIndex(constEdge),blockDom[constEdge]);
|
|
}
|
|
|
|
int4 ActionConditionalConst::apply(Funcdata &data)
|
|
|
|
{
|
|
bool useMultiequal = true;
|
|
AddrSpace *stackSpace = data.getArch()->getStackSpace();
|
|
if (stackSpace != (AddrSpace *)0) {
|
|
// Determining if conditional constants should apply to MULTIEQUAL operations may require
|
|
// flow calculations.
|
|
int4 numPasses = data.numHeritagePasses(stackSpace);
|
|
if (numPasses <= 0) // If the stack hasn't been heritaged yet
|
|
useMultiequal = false; // Don't propagate into MULTIEQUAL
|
|
}
|
|
const BlockGraph &blockGraph(data.getBasicBlocks());
|
|
bool blockDom[2];
|
|
list<ConstPoint> points;
|
|
for(int4 i=0;i<blockGraph.getSize();++i) {
|
|
FlowBlock *bl = blockGraph.getBlock(i);
|
|
PcodeOp *cBranch = bl->lastOp();
|
|
if (cBranch == (PcodeOp *)0 || cBranch->code() != CPUI_CBRANCH) continue;
|
|
Varnode *boolVn = cBranch->getIn(1);
|
|
blockDom[0] = bl->getOut(0)->restrictedByConditional(bl); // Make sure boolean constant holds down false branch
|
|
blockDom[1] = bl->getOut(1)->restrictedByConditional(bl);
|
|
bool flipEdge = cBranch->isBooleanFlip();
|
|
if (boolVn->loneDescend() == (PcodeOp *)0) { // If the boolean is read more than once
|
|
// Search for implied constants, bool=0 down false branch, bool=1 down true branch
|
|
points.emplace_back(boolVn, flipEdge ? 1 : 0, bl->getFalseOut(),bl->getOutRevIndex(0),blockDom[0]);
|
|
points.emplace_back(boolVn, flipEdge ? 0 : 1, bl->getTrueOut(),bl->getOutRevIndex(1),blockDom[1]);
|
|
}
|
|
findConstCompare(points, boolVn, bl, blockDom, flipEdge);
|
|
propagateConstant(points, useMultiequal, data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionSwitchNorm::apply(Funcdata &data)
|
|
|
|
{
|
|
for(int4 i=0;i<data.numJumpTables();++i) {
|
|
JumpTable *jt = data.getJumpTable(i);
|
|
if (!jt->isLabelled()) {
|
|
jt->matchModel(&data);
|
|
jt->recoverLabels(&data); // Recover case statement labels
|
|
jt->foldInNormalization(&data);
|
|
count += 1;
|
|
}
|
|
if (jt->foldInGuards(&data)) {
|
|
data.getStructure().clear(); // Make sure we redo structure
|
|
count += 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionNormalizeSetup::apply(Funcdata &data)
|
|
|
|
{
|
|
FuncProto &fp( data.getFuncProto() );
|
|
fp.clearInput();
|
|
fp.setModelLock(false); // This will cause the model to get reevaluated
|
|
fp.setOutputLock(false);
|
|
|
|
// FIXME: This should probably save and restore symbols, model, and state
|
|
// If we are calculating normalized trees in console mode, this currently eliminates locks
|
|
// that may be needed by other normalizing calculations
|
|
return 0;
|
|
}
|
|
|
|
/// \brief Extend Varnode inputs to match prototype model.
|
|
///
|
|
/// For prototype models that assume input variables are already extended in some way,
|
|
/// insert the appropriate extension operation to allow correct small-size input
|
|
/// Varnode to exist.
|
|
/// \param data is the function being analyzed
|
|
/// \param invn is the given (small) input Varnode
|
|
/// \param param is the matching symbol info for the Varnode
|
|
/// \param topbl is the entry block for the function
|
|
void ActionPrototypeTypes::extendInput(Funcdata &data,Varnode *invn,ProtoParameter *param,BlockBasic *topbl)
|
|
|
|
{
|
|
VarnodeData vdata;
|
|
OpCode res = data.getFuncProto().assumedInputExtension(invn->getAddr(),invn->getSize(),vdata);
|
|
if (res == CPUI_COPY) return; // no extension
|
|
if (res == CPUI_PIECE) { // Do an extension based on type of parameter
|
|
if (param->getType()->getMetatype() == TYPE_INT)
|
|
res = CPUI_INT_SEXT;
|
|
else
|
|
res = CPUI_INT_ZEXT;
|
|
}
|
|
PcodeOp *op = data.newOp(1,topbl->getStart());
|
|
data.newVarnodeOut(vdata.size,vdata.getAddr(),op);
|
|
data.opSetOpcode(op,res);
|
|
data.opSetInput(op,invn,0);
|
|
data.opInsertBegin(op,topbl);
|
|
}
|
|
|
|
int4 ActionPrototypeTypes::apply(Funcdata &data)
|
|
|
|
{
|
|
list<PcodeOp *>::const_iterator iter,iterend;
|
|
|
|
// Set the evaluation prototype if we are not already locked
|
|
ProtoModel *evalfp = data.getArch()->evalfp_current;
|
|
if (evalfp == (ProtoModel *)0)
|
|
evalfp = data.getArch()->defaultfp;
|
|
if ((!data.getFuncProto().isModelLocked()) && !data.getFuncProto().hasMatchingModel(evalfp))
|
|
data.getFuncProto().setModel(evalfp);
|
|
if (data.getFuncProto().hasThisPointer())
|
|
data.prepareThisPointer();
|
|
|
|
iterend = data.endOp(CPUI_RETURN);
|
|
|
|
// Strip the indirect register from all RETURN ops
|
|
// (Because we don't want to see this compiler
|
|
// mechanism in the high-level C output)
|
|
for(iter=data.beginOp(CPUI_RETURN);iter!=iterend;++iter) {
|
|
PcodeOp *op = *iter;
|
|
if (op->isDead()) continue;
|
|
if (!op->getIn(0)->isConstant()) {
|
|
Varnode *vn = data.newConstant(op->getIn(0)->getSize(),0);
|
|
data.opSetInput(op,vn,0);
|
|
}
|
|
}
|
|
|
|
if (data.getFuncProto().isOutputLocked()) {
|
|
ProtoParameter *outparam = data.getFuncProto().getOutput();
|
|
if (outparam->getType()->getMetatype() != TYPE_VOID) {
|
|
for(iter=data.beginOp(CPUI_RETURN);iter!=iterend;++iter) {
|
|
PcodeOp *op = *iter;
|
|
if (op->isDead()) continue;
|
|
if (op->getHaltType() != 0) continue;
|
|
Varnode *vn = data.newVarnode(outparam->getSize(),outparam->getAddress());
|
|
data.opInsertInput(op,vn,op->numInput());
|
|
vn->updateType(outparam->getType(),true,true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
data.initActiveOutput(); // Initiate gathering potential return values
|
|
|
|
AddrSpace *spc = data.getArch()->getDefaultCodeSpace();
|
|
if (spc->isTruncated()) {
|
|
// For truncated spaces we need a zext op, from the truncated stack pointer
|
|
// into the full stack pointer
|
|
AddrSpace *stackspc = data.getArch()->getStackSpace();
|
|
BlockBasic *topbl = (BlockBasic *)0;
|
|
if (data.getBasicBlocks().getSize() > 0)
|
|
topbl = (BlockBasic *)data.getBasicBlocks().getBlock(0);
|
|
if ((stackspc != (AddrSpace *)0)&&(topbl != (BlockBasic *)0)) {
|
|
for(int4 i=0;i<stackspc->numSpacebase();++i) {
|
|
const VarnodeData &fullReg( stackspc->getSpacebaseFull(i) );
|
|
const VarnodeData &truncReg( stackspc->getSpacebase(i) );
|
|
Varnode *invn = data.newVarnode( truncReg.size, truncReg.getAddr() );
|
|
invn = data.setInputVarnode(invn);
|
|
PcodeOp *extop = data.newOp(1,topbl->getStart());
|
|
data.newVarnodeOut(fullReg.size,fullReg.getAddr(),extop);
|
|
data.opSetOpcode(extop,CPUI_INT_ZEXT);
|
|
data.opSetInput(extop,invn,0);
|
|
data.opInsertBegin(extop,topbl);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Force locked inputs to exist as varnodes
|
|
|
|
// This is needed if we want to force a big input to exist
|
|
// but only part of it is getting used. This is allows
|
|
// a SUBPIECE instruction to get built with the big variable
|
|
// as input and the part getting used as output.
|
|
if (data.getFuncProto().isInputLocked()) {
|
|
|
|
int4 ptr_size = spc->isTruncated() ? spc->getAddrSize() : 0; // Check if we need to do pointer trimming
|
|
BlockBasic *topbl = (BlockBasic *)0;
|
|
if (data.getBasicBlocks().getSize() > 0)
|
|
topbl = (BlockBasic *)data.getBasicBlocks().getBlock(0);
|
|
|
|
int4 numparams = data.getFuncProto().numParams();
|
|
for(int4 i=0;i<numparams;++i) {
|
|
ProtoParameter *param = data.getFuncProto().getParam(i);
|
|
Varnode *vn = data.newVarnode( param->getSize(), param->getAddress());
|
|
vn = data.setInputVarnode(vn);
|
|
vn->setLockedInput();
|
|
if (topbl != (BlockBasic *)0)
|
|
extendInput(data,vn,param,topbl);
|
|
if (ptr_size > 0) {
|
|
Datatype *ct = param->getType();
|
|
if ((ct->getMetatype() == TYPE_PTR)&&(ct->getSize() == ptr_size))
|
|
vn->setPtrFlow();
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionInputPrototype::apply(Funcdata &data)
|
|
|
|
{
|
|
vector<Varnode *> triallist;
|
|
ParamActive active(false);
|
|
Varnode *vn;
|
|
|
|
data.getScopeLocal()->clearCategory(Symbol::fake_input);
|
|
data.getFuncProto().clearUnlockedInput();
|
|
if (!data.getFuncProto().isInputLocked()) {
|
|
VarnodeDefSet::const_iterator iter,enditer;
|
|
iter = data.beginDef(Varnode::input);
|
|
enditer = data.endDef(Varnode::input);
|
|
while(iter != enditer) {
|
|
vn = *iter;
|
|
++iter;
|
|
if (data.getFuncProto().possibleInputParam(vn->getAddr(),vn->getSize())) {
|
|
int4 slot = active.getNumTrials();
|
|
active.registerTrial(vn->getAddr(),vn->getSize());
|
|
if (!vn->hasNoDescend())
|
|
active.getTrial(slot).markActive(); // Mark as active if it has descendants
|
|
triallist.push_back(vn);
|
|
}
|
|
}
|
|
data.getFuncProto().resolveModel(&active);
|
|
data.getFuncProto().deriveInputMap(&active); // Derive the correct prototype from trials
|
|
// Create any unreferenced input varnodes
|
|
for(int4 i=0;i<active.getNumTrials();++i) {
|
|
ParamTrial ¶mtrial(active.getTrial(i));
|
|
if (paramtrial.isUnref() && paramtrial.isUsed()) {
|
|
if (data.hasInputIntersection(paramtrial.getSize(), paramtrial.getAddress())) {
|
|
// There is something in the way of the unreferenced parameter, don't create it
|
|
paramtrial.markNoUse();
|
|
}
|
|
else {
|
|
vn = data.newVarnode(paramtrial.getSize(),paramtrial.getAddress());
|
|
vn = data.setInputVarnode(vn);
|
|
int4 slot = triallist.size();
|
|
triallist.push_back(vn);
|
|
paramtrial.setSlot(slot + 1);
|
|
}
|
|
}
|
|
}
|
|
if (data.isHighOn())
|
|
data.getFuncProto().updateInputTypes(data,triallist,&active);
|
|
else
|
|
data.getFuncProto().updateInputNoTypes(data,triallist,&active);
|
|
}
|
|
data.clearDeadVarnodes();
|
|
#ifdef OPACTION_DEBUG
|
|
if ((flags&rule_debug)==0) return 0;
|
|
ostringstream s;
|
|
data.getScopeLocal()->printEntries(s);
|
|
data.getArch()->printDebug(s.str());
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionOutputPrototype::apply(Funcdata &data)
|
|
|
|
{
|
|
ProtoParameter *outparam = data.getFuncProto().getOutput();
|
|
if ((!outparam->isTypeLocked())||outparam->isSizeTypeLocked()) {
|
|
PcodeOp *op = data.getFirstReturnOp();
|
|
vector<Varnode *> vnlist;
|
|
if (op != (PcodeOp *)0) {
|
|
for(int4 i=1;i<op->numInput();++i)
|
|
vnlist.push_back(op->getIn(i));
|
|
}
|
|
if (data.isHighOn())
|
|
data.getFuncProto().updateOutputTypes(vnlist);
|
|
else
|
|
data.getFuncProto().updateOutputNoTypes(vnlist,data.getArch()->types);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionUnjustifiedParams::apply(Funcdata &data)
|
|
|
|
{
|
|
VarnodeDefSet::const_iterator iter,enditer;
|
|
FuncProto &proto( data.getFuncProto() );
|
|
|
|
iter = data.beginDef(Varnode::input);
|
|
enditer = data.endDef(Varnode::input);
|
|
|
|
while(iter != enditer) {
|
|
Varnode *vn = *iter++;
|
|
VarnodeData vdata;
|
|
if (!proto.unjustifiedInputParam(vn->getAddr(),vn->getSize(),vdata)) continue;
|
|
|
|
bool newcontainer;
|
|
do {
|
|
newcontainer = false;
|
|
VarnodeDefSet::const_iterator begiter,iter2;
|
|
begiter = data.beginDef(Varnode::input);
|
|
iter2 = iter;
|
|
bool overlaps = false;
|
|
while(iter2 != begiter) {
|
|
--iter2;
|
|
vn = *iter2;
|
|
if (vn->getSpace() != vdata.space) continue;
|
|
uintb offset = vn->getOffset() + vn->getSize()-1; // Last offset in varnode
|
|
if ((offset >= vdata.offset)&&(vn->getOffset()<vdata.offset)) { // If there is overlap that extends size
|
|
overlaps = true;
|
|
uintb endpoint = vdata.offset + vdata.size;
|
|
vdata.offset = vn->getOffset();
|
|
vdata.size = endpoint - vdata.offset;
|
|
}
|
|
}
|
|
if (!overlaps) break; // Found no additional overlaps, go with current justified container
|
|
// If there were overlaps, container may no longer be justified
|
|
newcontainer = proto.unjustifiedInputParam(vdata.getAddr(),vdata.size,vdata);
|
|
} while(newcontainer);
|
|
|
|
data.adjustInputVarnodes(vdata.getAddr(),vdata.size);
|
|
// Reset iterator because of additions and deletions
|
|
iter = data.beginDef(Varnode::input,vdata.getAddr());
|
|
enditer = data.endDef(Varnode::input);
|
|
count += 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionHideShadow::apply(Funcdata &data)
|
|
|
|
{
|
|
VarnodeDefSet::const_iterator iter,enditer;
|
|
HighVariable *high;
|
|
|
|
enditer = data.endDef(Varnode::written);
|
|
for(iter=data.beginDef();iter!=enditer;++iter) {
|
|
high = (*iter)->getHigh();
|
|
if (high->isMark()) continue;
|
|
if (data.getMerge().hideShadows(high))
|
|
count += 1;
|
|
high->setMark();
|
|
}
|
|
for(iter=data.beginDef();iter!=enditer;++iter) {
|
|
high = (*iter)->getHigh();
|
|
high->clearMark();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionDynamicMapping::apply(Funcdata &data)
|
|
|
|
{
|
|
ScopeLocal *localmap = data.getScopeLocal();
|
|
list<SymbolEntry>::iterator iter,enditer;
|
|
iter = localmap->beginDynamic();
|
|
enditer = localmap->endDynamic();
|
|
DynamicHash dhash;
|
|
while(iter != enditer) {
|
|
SymbolEntry *entry = &(*iter);
|
|
++iter;
|
|
if (data.attemptDynamicMapping(entry,dhash))
|
|
count += 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionDynamicSymbols::apply(Funcdata &data)
|
|
|
|
{
|
|
ScopeLocal *localmap = data.getScopeLocal();
|
|
list<SymbolEntry>::iterator iter,enditer;
|
|
iter = localmap->beginDynamic();
|
|
enditer = localmap->endDynamic();
|
|
DynamicHash dhash;
|
|
while(iter != enditer) {
|
|
SymbolEntry *entry = &(*iter);
|
|
++iter;
|
|
if (data.attemptDynamicMappingLate(entry, dhash))
|
|
count += 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionPrototypeWarnings::apply(Funcdata &data)
|
|
|
|
{
|
|
vector<string> overridemessages;
|
|
data.getOverride().generateOverrideMessages(overridemessages,data.getArch());
|
|
for(int4 i=0;i<overridemessages.size();++i)
|
|
data.warningHeader(overridemessages[i]);
|
|
|
|
FuncProto &ourproto( data.getFuncProto() );
|
|
if (ourproto.hasInputErrors()) {
|
|
data.warningHeader("Cannot assign parameter locations for this function: Prototype may be inaccurate");
|
|
}
|
|
if (ourproto.hasOutputErrors()) {
|
|
data.warningHeader("Cannot assign location of return value for this function: Return value may be inaccurate");
|
|
}
|
|
if (ourproto.isModelUnknown()) {
|
|
ostringstream s;
|
|
s << "Unknown calling convention";
|
|
if (ourproto.printModelInDecl())
|
|
s << ": " << ourproto.getModelName();
|
|
if (!ourproto.hasCustomStorage() && (ourproto.isInputLocked() || ourproto.isOutputLocked()))
|
|
s << " -- yet parameter storage is locked";
|
|
data.warningHeader(s.str());
|
|
}
|
|
int4 numcalls = data.numCalls();
|
|
for(int4 i=0;i<numcalls;++i) {
|
|
FuncCallSpecs *fc = data.getCallSpecs(i);
|
|
Funcdata *fd = fc->getFuncdata();
|
|
if (fc->hasInputErrors()) {
|
|
ostringstream s;
|
|
s << "Cannot assign parameter location for function ";
|
|
if (fd != (Funcdata *)0)
|
|
s << fd->getName();
|
|
else
|
|
s << "<indirect>";
|
|
s << ": Prototype may be inaccurate";
|
|
data.warning(s.str(),fc->getEntryAddress());
|
|
}
|
|
if (fc->hasOutputErrors()) {
|
|
ostringstream s;
|
|
s << "Cannot assign location of return value for function ";
|
|
if (fd != (Funcdata *)0)
|
|
s << fd->getName();
|
|
else
|
|
s << "<indirect>";
|
|
s << ": Return value may be inaccurate";
|
|
data.warning(s.str(),fc->getEntryAddress());
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int4 ActionInternalStorage::apply(Funcdata &data)
|
|
|
|
{
|
|
FuncProto &proto( data.getFuncProto() );
|
|
vector<VarnodeData>::const_iterator iter = proto.internalBegin();
|
|
vector<VarnodeData>::const_iterator enditer = proto.internalEnd();
|
|
while(iter != enditer) {
|
|
Address addr = (*iter).getAddr();
|
|
int4 sz = (*iter).size;
|
|
++iter;
|
|
|
|
VarnodeLocSet::const_iterator viter = data.beginLoc(sz, addr);
|
|
VarnodeLocSet::const_iterator endviter = data.endLoc(sz, addr);
|
|
while(viter != endviter) {
|
|
Varnode *vn = *viter;
|
|
++viter;
|
|
list<PcodeOp *>::const_iterator oiter = vn->beginDescend();
|
|
while(oiter != vn->endDescend()) {
|
|
PcodeOp *op = *oiter;
|
|
++oiter;
|
|
if (op->code() == CPUI_STORE) {
|
|
if (vn->isEventualConstant(3,0)) {
|
|
op->setStoreUnmapped();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef TYPEPROP_DEBUG
|
|
/// \brief Log a particular data-type propagation action.
|
|
///
|
|
/// Print the Varnode updated, the new data-type it contains, and
|
|
/// where the data-type propagated from.
|
|
/// \param glb is the Architecture holding the error console
|
|
/// \param vn is the target Varnode
|
|
/// \param newtype is the new data-type
|
|
/// \param op is the PcodeOp through which the data-type propagated
|
|
/// \param slot is the slot from which the data-type propagated
|
|
/// \param ptralias if not NULL holds the pointer that aliased the target Varnode
|
|
void ActionInferTypes::propagationDebug(Architecture *glb,Varnode *vn,const Datatype *newtype,PcodeOp *op,int4 slot,Varnode *ptralias)
|
|
|
|
{
|
|
if (!TypeFactory::propagatedbg_on) return;
|
|
ostringstream s;
|
|
|
|
vn->printRaw(s);
|
|
s << " : ";
|
|
newtype->printRaw(s);
|
|
if ((op == (PcodeOp *)0)&&(ptralias == (Varnode *)0)) {
|
|
s << " init";
|
|
}
|
|
else if (ptralias != (Varnode *)0) {
|
|
s << " alias ";
|
|
ptralias->printRaw(s);
|
|
}
|
|
else {
|
|
s << " from ";
|
|
op->printRaw(s);
|
|
s << " slot=" << dec << slot;
|
|
}
|
|
glb->printDebug(s.str());
|
|
}
|
|
#endif
|
|
|
|
/// Collect \e local data-type information on each Varnode inferred
|
|
/// from the PcodeOps that read and write to it.
|
|
/// \param data is the function being analyzed
|
|
void ActionInferTypes::buildLocaltypes(Funcdata &data)
|
|
|
|
{
|
|
Datatype *ct;
|
|
Varnode *vn;
|
|
VarnodeLocSet::const_iterator iter;
|
|
TypeFactory *typegrp = data.getArch()->types;
|
|
|
|
for(iter=data.beginLoc();iter!=data.endLoc();++iter) {
|
|
vn = *iter;
|
|
if (vn->isAnnotation()) continue;
|
|
if ((!vn->isWritten())&&(vn->hasNoDescend())) continue;
|
|
bool needsBlock = false;
|
|
SymbolEntry *entry = vn->getSymbolEntry();
|
|
if (entry != (SymbolEntry *)0 && !vn->isTypeLock() && entry->getSymbol()->isTypeLocked()) {
|
|
int4 curOff = (vn->getAddr().getOffset() - entry->getAddr().getOffset()) + entry->getOffset();
|
|
ct = typegrp->getExactPiece(entry->getSymbol()->getType(), curOff, vn->getSize());
|
|
if (ct == (Datatype *)0 || ct->getMetatype() == TYPE_UNKNOWN) // If we can't resolve, or resolve to UNKNOWN
|
|
ct = vn->getLocalType(needsBlock); // Let data-type float, even though parent symbol is type-locked
|
|
}
|
|
else
|
|
ct = vn->getLocalType(needsBlock);
|
|
if (needsBlock)
|
|
vn->setStopUpPropagation();
|
|
#ifdef TYPEPROP_DEBUG
|
|
propagationDebug(data.getArch(),vn,ct,(PcodeOp *)0,0,(Varnode *)0);
|
|
#endif
|
|
vn->setTempType(ct);
|
|
}
|
|
}
|
|
|
|
/// For each Varnode copy the temporary data-type to the permament
|
|
/// field, taking into account previous locks.
|
|
/// \param data is the function being analyzed
|
|
/// \return \b true if any Varnode's data-type changed from the last round of propagation
|
|
bool ActionInferTypes::writeBack(Funcdata &data)
|
|
|
|
{
|
|
bool change = false;
|
|
Datatype *ct;
|
|
Varnode *vn;
|
|
VarnodeLocSet::const_iterator iter;
|
|
|
|
for(iter=data.beginLoc();iter!=data.endLoc();++iter) {
|
|
vn = *iter;
|
|
if (vn->isAnnotation()) continue;
|
|
if ((!vn->isWritten())&&(vn->hasNoDescend())) continue;
|
|
ct = vn->getTempType();
|
|
if (vn->updateType(ct))
|
|
change = true;
|
|
}
|
|
return change;
|
|
}
|
|
|
|
/// \brief Attempt to propagate a data-type across a single PcodeOp edge
|
|
///
|
|
/// Given an \e input Varnode and an \e output Varnode defining a directed edge
|
|
/// through a PcodeOp, determine if and how the input data-type propagates to the
|
|
/// output. Update the output Varnode's (temporary) data-type. An input to the
|
|
/// edge may either an input or output to the PcodeOp. A \e slot value of -1
|
|
/// indicates the PcodeOp output, a non-negative value indicates a PcodeOp input index.
|
|
/// \param typegrp is the TypeFactory for building a possibly transformed data-type
|
|
/// \param op is the PcodeOp through which the propagation edge flows
|
|
/// \param inslot indicates the edge's input Varnode
|
|
/// \param outslot indicates the edge's output Varnode
|
|
/// \return \b true if the data-type propagates
|
|
bool ActionInferTypes::propagateTypeEdge(TypeFactory *typegrp,PcodeOp *op,int4 inslot,int4 outslot)
|
|
|
|
{
|
|
Varnode *invn,*outvn;
|
|
|
|
invn = (inslot==-1) ? op->getOut() : op->getIn(inslot);
|
|
Datatype *alttype = invn->getTempType();
|
|
if (alttype->needsResolution()) {
|
|
// Always give incoming data-type a chance to resolve, even if it would not otherwise propagate
|
|
alttype = alttype->resolveInFlow(op, inslot);
|
|
}
|
|
if (inslot == outslot) return false; // don't backtrack
|
|
if (outslot < 0)
|
|
outvn = op->getOut();
|
|
else {
|
|
outvn = op->getIn(outslot);
|
|
if (outvn->isAnnotation()) return false;
|
|
}
|
|
if (outvn->isTypeLock()) return false; // Can't propagate through typelock
|
|
if (outvn->stopsUpPropagation() && outslot >= 0) return false; // Propagation is blocked
|
|
|
|
if (alttype->getMetatype() == TYPE_BOOL) { // Only propagate boolean
|
|
if (outvn->getNZMask() > 1) // If we know output can only take boolean values
|
|
return false;
|
|
}
|
|
|
|
Datatype *newtype = op->getOpcode()->propagateType(alttype, op, invn, outvn, inslot, outslot);
|
|
if (newtype == (Datatype *)0)
|
|
return false;
|
|
|
|
if (0>newtype->typeOrder(*outvn->getTempType())) {
|
|
#ifdef TYPEPROP_DEBUG
|
|
propagationDebug(typegrp->getArch(),outvn,newtype,op,inslot,(Varnode *)0);
|
|
#endif
|
|
outvn->setTempType(newtype);
|
|
return !outvn->isMark();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// \param v is the root Varnode to iterate over
|
|
PropagationState::PropagationState(Varnode *v)
|
|
|
|
{
|
|
vn = v;
|
|
iter = vn->beginDescend();
|
|
if (iter != vn->endDescend()) {
|
|
op = *iter++;
|
|
if (op->getOut() != (Varnode *)0)
|
|
slot = -1;
|
|
else
|
|
slot = 0;
|
|
inslot = op->getSlot(vn);
|
|
}
|
|
else {
|
|
op = vn->getDef();
|
|
inslot = -1;
|
|
slot = 0;
|
|
}
|
|
}
|
|
|
|
/// At the high level, this iterates through all the descendant
|
|
/// PcodeOps of the root Varnode, then the defining PcodeOp.
|
|
/// At the low level, this iterates from the output Varnode
|
|
/// of the current PcodeOp then through all the input Varnodes
|
|
void PropagationState::step(void)
|
|
|
|
{
|
|
slot += 1;
|
|
if (slot < op->numInput())
|
|
return;
|
|
if (iter != vn->endDescend()) {
|
|
op = *iter++;
|
|
if (op->getOut() != (Varnode *)0)
|
|
slot = -1;
|
|
else
|
|
slot = 0;
|
|
inslot = op->getSlot(vn);
|
|
return;
|
|
}
|
|
if (inslot == -1)
|
|
op = (PcodeOp *)0;
|
|
else
|
|
op = vn->getDef();
|
|
inslot = -1;
|
|
slot = 0;
|
|
}
|
|
|
|
/// \brief Propagate a data-type starting from one Varnode across the function
|
|
///
|
|
/// Given a starting Varnode, propagate its Datatype as far as possible through
|
|
/// the data-flow graph, transforming the data-type through PcodeOps as necessary.
|
|
/// The data-type is push through all possible propagating edges, but each
|
|
/// Varnode is visited at most once. Propagation is trimmed along any particular
|
|
/// path if the pushed data-type isn't \e more \e specific than the current
|
|
/// data-type on a Varnode, under the data-type ordering.
|
|
/// \param typegrp is the TypeFactory for constructing transformed data-types
|
|
/// \param vn is the Varnode holding the root data-type to push
|
|
void ActionInferTypes::propagateOneType(TypeFactory *typegrp,Varnode *vn)
|
|
|
|
{
|
|
PropagationState *ptr;
|
|
vector<PropagationState> state;
|
|
|
|
state.emplace_back(vn);
|
|
vn->setMark();
|
|
|
|
while(!state.empty()) {
|
|
ptr = &state.back();
|
|
if (!ptr->valid()) { // If we are out of edges to traverse
|
|
ptr->vn->clearMark();
|
|
state.pop_back();
|
|
}
|
|
else {
|
|
if (propagateTypeEdge(typegrp,ptr->op,ptr->inslot,ptr->slot)) {
|
|
vn = (ptr->slot==-1) ? ptr->op->getOut() : ptr->op->getIn(ptr->slot);
|
|
ptr->step(); // Make sure to step before push_back
|
|
state.emplace_back(vn);
|
|
vn->setMark();
|
|
}
|
|
else
|
|
ptr->step();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Try to propagate a pointer data-type to known aliases.
|
|
///
|
|
/// Given a Varnode which is a likely pointer and an Address that
|
|
/// is a known alias of the pointer, attempt to propagate the Varnode's
|
|
/// data-type to Varnodes at that address.
|
|
/// \param data is the function being analyzed
|
|
/// \param vn is the given Varnode
|
|
/// \param addr is the aliased address
|
|
void ActionInferTypes::propagateRef(Funcdata &data,Varnode *vn,const Address &addr)
|
|
|
|
{
|
|
Datatype *ct = vn->getTempType();
|
|
if (ct->getMetatype() != TYPE_PTR) return;
|
|
ct = ((TypePointer *)ct)->getPtrTo();
|
|
if (ct->getMetatype() == TYPE_SPACEBASE) return;
|
|
if (ct->getMetatype() == TYPE_UNKNOWN) return; // Don't bother propagating this
|
|
VarnodeLocSet::const_iterator iter,enditer;
|
|
uintb off = addr.getOffset();
|
|
TypeFactory *typegrp = data.getArch()->types;
|
|
Address endaddr = addr + ct->getSize();
|
|
if (endaddr.getOffset() < off) // If the address wrapped
|
|
enditer = data.endLoc(addr.getSpace()); // Go to end of space
|
|
else
|
|
enditer = data.endLoc(endaddr);
|
|
iter = data.beginLoc(addr);
|
|
uintb lastoff = 0;
|
|
int4 lastsize = ct->getSize();
|
|
Datatype *lastct = ct;
|
|
while(iter != enditer) {
|
|
Varnode *curvn = *iter;
|
|
++iter;
|
|
if (curvn->isAnnotation()) continue;
|
|
if ((!curvn->isWritten())&&curvn->hasNoDescend()) continue;
|
|
if (curvn->isTypeLock()) continue;
|
|
if (curvn->getSymbolEntry() != (SymbolEntry *)0) continue;
|
|
uintb curoff = curvn->getOffset() - off;
|
|
int4 cursize = curvn->getSize();
|
|
if (curoff + cursize > ct->getSize()) continue;
|
|
if ((cursize!=lastsize)||(curoff!=lastoff)) {
|
|
lastoff = curoff;
|
|
lastsize = cursize;
|
|
lastct = typegrp->getExactPiece(ct, curoff, cursize);
|
|
}
|
|
if (lastct == (Datatype *)0) continue;
|
|
|
|
// Try to propagate the reference type into a varnode that is pointed to by that reference
|
|
if (0>lastct->typeOrder(*curvn->getTempType())) {
|
|
#ifdef TYPEPROP_DEBUG
|
|
propagationDebug(data.getArch(),curvn,lastct,(PcodeOp *)0,0,vn);
|
|
#endif
|
|
curvn->setTempType(lastct);
|
|
propagateOneType(typegrp,curvn); // Try to propagate the new type as far as possible
|
|
}
|
|
}
|
|
}
|
|
|
|
/// \brief Search for pointers and propagate its data-type to known aliases
|
|
///
|
|
/// This routine looks for ADD operations off of a specific
|
|
/// \e spacebase register that produce output Varnodes with a known
|
|
/// data-type. The offset of the ADD is calculated into the corresponding
|
|
/// address space, and an attempt is made to propagate the Varnodes data-type
|
|
/// to other Varnodes in the address space at that offset.
|
|
/// \param data is the function being analyzed
|
|
/// \param spcvn is the spacebase register
|
|
void ActionInferTypes::propagateSpacebaseRef(Funcdata &data,Varnode *spcvn)
|
|
|
|
{
|
|
Datatype *spctype = spcvn->getType(); // This is an absolute property of the varnode, so not temptype
|
|
if (spctype->getMetatype() != TYPE_PTR) return;
|
|
spctype = ((TypePointer *)spctype)->getPtrTo();
|
|
if (spctype->getMetatype() != TYPE_SPACEBASE) return;
|
|
TypeSpacebase *sbtype = (TypeSpacebase *)spctype;
|
|
list<PcodeOp *>::const_iterator iter;
|
|
Address addr;
|
|
|
|
for(iter=spcvn->beginDescend();iter!=spcvn->endDescend();++iter) {
|
|
PcodeOp *op = *iter;
|
|
Varnode *vn;
|
|
switch(op->code()) {
|
|
case CPUI_COPY:
|
|
vn = op->getIn(0);
|
|
addr = sbtype->getAddress(0,vn->getSize(),op->getAddr());
|
|
propagateRef(data,op->getOut(),addr);
|
|
break;
|
|
case CPUI_INT_ADD:
|
|
case CPUI_PTRSUB:
|
|
vn = op->getIn(1);
|
|
if (vn->isConstant()) {
|
|
addr = sbtype->getAddress(vn->getOffset(),vn->getSize(),op->getAddr());
|
|
propagateRef(data,op->getOut(),addr);
|
|
}
|
|
break;
|
|
case CPUI_PTRADD:
|
|
vn = op->getIn(1);
|
|
if (vn->isConstant()) {
|
|
uintb off = vn->getOffset() * op->getIn(2)->getOffset();
|
|
addr = sbtype->getAddress(off,vn->getSize(),op->getAddr());
|
|
propagateRef(data,op->getOut(),addr);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return the CPUI_RETURN op with the most specialized data-type, which is not
|
|
/// dead and is not a special halt.
|
|
/// \param data is the function
|
|
/// \return the representative CPUI_RETURN op or NULL
|
|
PcodeOp *ActionInferTypes::canonicalReturnOp(Funcdata &data)
|
|
|
|
{
|
|
PcodeOp *res = (PcodeOp *)0;
|
|
Datatype *bestdt = (Datatype *)0;
|
|
list<PcodeOp *>::const_iterator iter,iterend;
|
|
iterend = data.endOp(CPUI_RETURN);
|
|
for(iter=data.beginOp(CPUI_RETURN);iter!=iterend;++iter) {
|
|
PcodeOp *retop = *iter;
|
|
if (retop->isDead()) continue;
|
|
if (retop->getHaltType()!=0) continue;
|
|
if (retop->numInput() > 1) {
|
|
Varnode *vn = retop->getIn(1);
|
|
Datatype *ct = vn->getTempType();
|
|
if (bestdt == (Datatype *)0) {
|
|
res = retop;
|
|
bestdt = ct;
|
|
}
|
|
else if (ct->typeOrder(*bestdt) < 0) {
|
|
res = retop;
|
|
bestdt = ct;
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/// \brief Give data-types a chance to propagate between CPUI_RETURN operations.
|
|
///
|
|
/// Since a function is intended to return a single data-type, data-types effectively
|
|
/// propagate between the input Varnodes to CPUI_RETURN ops, if there are more than one.
|
|
void ActionInferTypes::propagateAcrossReturns(Funcdata &data)
|
|
|
|
{
|
|
if (data.getFuncProto().isOutputLocked()) return;
|
|
PcodeOp *op = canonicalReturnOp(data);
|
|
if (op == (PcodeOp *)0) return;
|
|
TypeFactory *typegrp = data.getArch()->types;
|
|
Varnode *baseVn = op->getIn(1);
|
|
Datatype *ct = baseVn->getTempType();
|
|
int4 baseSize = baseVn->getSize();
|
|
bool isBool = ct->getMetatype() == TYPE_BOOL;
|
|
list<PcodeOp *>::const_iterator iter,iterend;
|
|
iterend = data.endOp(CPUI_RETURN);
|
|
for(iter=data.beginOp(CPUI_RETURN);iter!=iterend;++iter) {
|
|
PcodeOp *retop = *iter;
|
|
if (retop == op) continue;
|
|
if (retop->isDead()) continue;
|
|
if (retop->getHaltType()!=0) continue;
|
|
if (retop->numInput() > 1) {
|
|
Varnode *vn = retop->getIn(1);
|
|
if (vn->getSize() != baseSize) continue;
|
|
if (isBool && vn->getNZMask() > 1) continue; // Don't propagate bool if value is not necessarily 0 or 1
|
|
if (vn->getTempType() == ct) continue; // Already propagated
|
|
vn->setTempType(ct);
|
|
#ifdef TYPEPROP_DEBUG
|
|
propagationDebug(typegrp->getArch(),vn,ct,op,1,(Varnode *)0);
|
|
#endif
|
|
propagateOneType(typegrp, vn);
|
|
}
|
|
}
|
|
}
|
|
|
|
int4 ActionInferTypes::apply(Funcdata &data)
|
|
|
|
{
|
|
// Make sure spacebase is accurate or bases could get typed and then ptrarithed
|
|
if (!data.hasTypeRecoveryStarted()) return 0;
|
|
TypeFactory *typegrp = data.getArch()->types;
|
|
Varnode *vn;
|
|
VarnodeLocSet::const_iterator iter;
|
|
|
|
#ifdef TYPEPROP_DEBUG
|
|
if (TypeFactory::propagatedbg_on) {
|
|
ostringstream s;
|
|
s << "Type propagation pass - " << dec << localcount;
|
|
data.getArch()->printDebug(s.str());
|
|
}
|
|
#endif
|
|
if (localcount >= 7) { // This constant arrived at empirically
|
|
if (localcount == 7) {
|
|
data.warningHeader("Type propagation algorithm not settling");
|
|
data.setTypeRecoveryExceeded();
|
|
localcount += 1;
|
|
}
|
|
return 0;
|
|
}
|
|
data.getScopeLocal()->applyTypeRecommendations();
|
|
buildLocaltypes(data); // Set up initial types (based on local info)
|
|
for(iter=data.beginLoc();iter!=data.endLoc();++iter) {
|
|
vn = *iter;
|
|
if (vn->isAnnotation()) continue;
|
|
if ((!vn->isWritten())&&(vn->hasNoDescend())) continue;
|
|
propagateOneType(typegrp,vn);
|
|
}
|
|
propagateAcrossReturns(data);
|
|
AddrSpace *spcid = data.getScopeLocal()->getSpaceId();
|
|
Varnode *spcvn = data.findSpacebaseInput(spcid);
|
|
if (spcvn != (Varnode *)0)
|
|
propagateSpacebaseRef(data,spcvn);
|
|
if (writeBack(data)) {
|
|
// count += 1; // Do not consider this a data-flow change
|
|
localcount += 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// (Re)build the default \e root Actions: decompile, jumptable, normalize, paramid, register, firstpass
|
|
void ActionDatabase::buildDefaultGroups(void)
|
|
|
|
{
|
|
if (isDefaultGroups) return;
|
|
groupmap.clear();
|
|
const char *members[] = { "base", "protorecovery", "protorecovery_a", "deindirect", "localrecovery",
|
|
"deadcode", "typerecovery", "stackptrflow",
|
|
"blockrecovery", "stackvars", "deadcontrolflow", "switchnorm",
|
|
"cleanup", "splitcopy", "splitpointer", "merge", "dynamic", "casts", "analysis",
|
|
"fixateglobals", "fixateproto", "constsequence", "bitfields",
|
|
"segment", "returnsplit", "nodejoin", "doubleload", "doubleprecis",
|
|
"unreachable", "subvar", "floatprecision",
|
|
"conditionalexe", "" };
|
|
setGroup("decompile",members);
|
|
|
|
const char *jumptab[] = { "base", "noproto", "localrecovery", "deadcode", "stackptrflow",
|
|
"stackvars", "analysis", "segment", "subvar", "normalizebranches", "conditionalexe", "" };
|
|
setGroup("jumptable",jumptab);
|
|
|
|
const char *normali[] = { "base", "protorecovery", "protorecovery_b", "deindirect", "localrecovery",
|
|
"deadcode", "stackptrflow", "normalanalysis",
|
|
"stackvars", "deadcontrolflow", "analysis", "fixateproto", "nodejoin",
|
|
"unreachable", "subvar", "floatprecision", "normalizebranches",
|
|
"conditionalexe", "" };
|
|
setGroup("normalize",normali);
|
|
|
|
const char *paramid[] = { "base", "protorecovery", "protorecovery_b", "deindirect", "localrecovery",
|
|
"deadcode", "typerecovery", "stackptrflow", "siganalysis",
|
|
"stackvars", "deadcontrolflow", "analysis", "fixateproto",
|
|
"unreachable", "subvar", "floatprecision",
|
|
"conditionalexe", "" };
|
|
setGroup("paramid",paramid);
|
|
|
|
const char *regmemb[] = { "base", "analysis", "subvar", "" };
|
|
setGroup("register",regmemb);
|
|
|
|
const char *firstmem[] = { "base", "" };
|
|
setGroup("firstpass",firstmem);
|
|
isDefaultGroups = true;
|
|
}
|
|
|
|
/// Construct the \b universal Action that contains all possible components
|
|
/// \param conf is the Architecture that will use the Action
|
|
void ActionDatabase::universalAction(Architecture *conf)
|
|
|
|
{
|
|
vector<Rule *>::iterator iter;
|
|
ActionGroup *act;
|
|
ActionGroup *actmainloop;
|
|
ActionGroup *actfullloop;
|
|
ActionPool *actprop,*actprop2;
|
|
ActionPool *actcleanup;
|
|
ActionGroup *actstackstall;
|
|
AddrSpace *stackspace = conf->getStackSpace();
|
|
|
|
act = new ActionRestartGroup(Action::rule_onceperfunc,"universal",1);
|
|
registerAction(universalname,act);
|
|
|
|
act->addAction( new ActionStart("base"));
|
|
act->addAction( new ActionConstbase("base"));
|
|
act->addAction( new ActionNormalizeSetup("normalanalysis"));
|
|
act->addAction( new ActionDefaultParams("base"));
|
|
// act->addAction( new ActionParamShiftStart("paramshift") );
|
|
act->addAction( new ActionExtraPopSetup("base",stackspace) );
|
|
act->addAction( new ActionPrototypeTypes("protorecovery"));
|
|
act->addAction( new ActionFuncLink("protorecovery") );
|
|
act->addAction( new ActionFuncLinkOutOnly("noproto") );
|
|
{
|
|
actfullloop = new ActionGroup(Action::rule_repeatapply,"fullloop");
|
|
{
|
|
actmainloop = new ActionGroup(Action::rule_repeatapply,"mainloop");
|
|
actmainloop->addAction( new ActionUnreachable("base") );
|
|
actmainloop->addAction( new ActionVarnodeProps("base") );
|
|
actmainloop->addAction( new ActionHeritage("base") );
|
|
actmainloop->addAction( new ActionParamDouble("protorecovery") );
|
|
actmainloop->addAction( new ActionSegmentize("base"));
|
|
actmainloop->addAction( new ActionInternalStorage("base") );
|
|
actmainloop->addAction( new ActionForceGoto("blockrecovery") );
|
|
actmainloop->addAction( new ActionDirectWrite("protorecovery_a", true) );
|
|
actmainloop->addAction( new ActionDirectWrite("protorecovery_b", false) );
|
|
actmainloop->addAction( new ActionActiveParam("protorecovery") );
|
|
actmainloop->addAction( new ActionReturnRecovery("protorecovery") );
|
|
// actmainloop->addAction( new ActionParamShiftStop("paramshift") );
|
|
actmainloop->addAction( new ActionRestrictLocal("localrecovery") ); // Do before dead code removed
|
|
actmainloop->addAction( new ActionDeadCode("deadcode") );
|
|
actmainloop->addAction( new ActionDynamicMapping("dynamic") ); // Must come before restructurevarnode and infertypes
|
|
actmainloop->addAction( new ActionRestructureVarnode("localrecovery") );
|
|
actmainloop->addAction( new ActionSpacebase("base") ); // Must come before infertypes and nonzeromask
|
|
actmainloop->addAction( new ActionNonzeroMask("analysis") );
|
|
actmainloop->addAction( new ActionInferTypes("typerecovery") );
|
|
actstackstall = new ActionGroup(Action::rule_repeatapply,"stackstall");
|
|
{
|
|
actprop = new ActionPool(Action::rule_repeatapply,"oppool1");
|
|
actprop->addRule( new RuleEarlyRemoval("deadcode"));
|
|
actprop->addRule( new RuleTermOrder("analysis"));
|
|
actprop->addRule( new RuleSelectCse("analysis"));
|
|
actprop->addRule( new RuleCollectTerms("analysis"));
|
|
actprop->addRule( new RulePullsubMulti("analysis"));
|
|
actprop->addRule( new RulePullsubIndirect("analysis"));
|
|
actprop->addRule( new RulePushMulti("nodejoin"));
|
|
actprop->addRule( new RuleSborrow("analysis") );
|
|
actprop->addRule( new RuleScarry("analysis") );
|
|
actprop->addRule( new RuleIntLessEqual("analysis") );
|
|
actprop->addRule( new RuleTrivialArith("analysis") );
|
|
actprop->addRule( new RuleTrivialBool("analysis") );
|
|
actprop->addRule( new RuleTrivialShift("analysis") );
|
|
actprop->addRule( new RuleSignShift("analysis") );
|
|
actprop->addRule( new RuleTestSign("analysis") );
|
|
actprop->addRule( new RuleIdentityEl("analysis") );
|
|
actprop->addRule( new RuleOrMask("analysis") );
|
|
actprop->addRule( new RuleAndMask("analysis") );
|
|
actprop->addRule( new RuleOrConsume("analysis") );
|
|
actprop->addRule( new RuleOrCollapse("analysis") );
|
|
actprop->addRule( new RuleAndOrLump("analysis") );
|
|
actprop->addRule( new RuleShiftBitops("analysis") );
|
|
actprop->addRule( new RuleRightShiftAnd("analysis") );
|
|
actprop->addRule( new RuleNotDistribute("analysis") );
|
|
actprop->addRule( new RuleHighOrderAnd("analysis") );
|
|
actprop->addRule( new RuleAndDistribute("analysis") );
|
|
actprop->addRule( new RuleAndCommute("analysis") );
|
|
actprop->addRule( new RuleAndPiece("analysis") );
|
|
actprop->addRule( new RuleAndZext("analysis") );
|
|
actprop->addRule( new RuleAndCompare("analysis") );
|
|
actprop->addRule( new RuleDoubleSub("analysis") );
|
|
actprop->addRule( new RuleDoubleShift("analysis") );
|
|
actprop->addRule( new RuleDoubleArithShift("analysis") );
|
|
actprop->addRule( new RuleConcatShift("analysis") );
|
|
actprop->addRule( new RuleLeftRight("analysis") );
|
|
actprop->addRule( new RuleShiftCompare("analysis") );
|
|
actprop->addRule( new RuleShift2Mult("analysis") );
|
|
actprop->addRule( new RuleShiftPiece("analysis") );
|
|
actprop->addRule( new RuleMultiCollapse("analysis") );
|
|
actprop->addRule( new RuleIndirectCollapse("analysis") );
|
|
actprop->addRule( new Rule2Comp2Mult("analysis") );
|
|
actprop->addRule( new RuleSub2Add("analysis") );
|
|
actprop->addRule( new RuleCarryElim("analysis") );
|
|
actprop->addRule( new RuleBxor2NotEqual("analysis") );
|
|
actprop->addRule( new RuleLess2Zero("analysis") );
|
|
actprop->addRule( new RuleLessEqual2Zero("analysis") );
|
|
actprop->addRule( new RuleSLess2Zero("analysis") );
|
|
actprop->addRule( new RuleEqual2Zero("analysis") );
|
|
actprop->addRule( new RuleEqual2Constant("analysis") );
|
|
actprop->addRule( new RuleThreeWayCompare("analysis") );
|
|
actprop->addRule( new RuleXorCollapse("analysis") );
|
|
actprop->addRule( new RuleAddMultCollapse("analysis") );
|
|
actprop->addRule( new RuleCollapseConstants("analysis") );
|
|
actprop->addRule( new RuleTransformCpool("analysis") );
|
|
actprop->addRule( new RulePropagateCopy("analysis") );
|
|
actprop->addRule( new RuleZextEliminate("analysis") );
|
|
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") );
|
|
actprop->addRule( new RuleSubExtComm("analysis") );
|
|
actprop->addRule( new RuleSubCommute("analysis") );
|
|
actprop->addRule( new RuleConcatCommute("analysis") );
|
|
actprop->addRule( new RuleConcatZext("analysis") );
|
|
actprop->addRule( new RuleZextCommute("analysis") );
|
|
actprop->addRule( new RuleZextShiftZext("analysis") );
|
|
actprop->addRule( new RuleShiftAnd("analysis") );
|
|
actprop->addRule( new RuleConcatZero("analysis") );
|
|
actprop->addRule( new RuleConcatLeftShift("analysis") );
|
|
actprop->addRule( new RuleSubZext("analysis") );
|
|
actprop->addRule( new RuleSubCancel("analysis") );
|
|
actprop->addRule( new RuleShiftSub("analysis") );
|
|
actprop->addRule( new RuleHumptyDumpty("analysis") );
|
|
actprop->addRule( new RuleDumptyHump("analysis") );
|
|
actprop->addRule( new RuleHumptyOr("analysis") );
|
|
actprop->addRule( new RuleNegateIdentity("analysis") );
|
|
actprop->addRule( new RuleSubNormal("analysis") );
|
|
actprop->addRule( new RulePositiveDiv("analysis") );
|
|
actprop->addRule( new RuleDivTermAdd("analysis") );
|
|
actprop->addRule( new RuleDivTermAdd2("analysis") );
|
|
actprop->addRule( new RuleDivOpt("analysis") );
|
|
actprop->addRule( new RuleSignForm("analysis") );
|
|
actprop->addRule( new RuleSignForm2("analysis") );
|
|
actprop->addRule( new RuleSignDiv2("analysis") );
|
|
actprop->addRule( new RuleDivChain("analysis") );
|
|
actprop->addRule( new RuleSignNearMult("analysis") );
|
|
actprop->addRule( new RuleModOpt("analysis") );
|
|
actprop->addRule( new RuleSignMod2nOpt("analysis") );
|
|
actprop->addRule( new RuleSignMod2nOpt2("analysis") );
|
|
actprop->addRule( new RuleSignMod2Opt("analysis") );
|
|
actprop->addRule( new RuleSwitchSingle("analysis") );
|
|
actprop->addRule( new RuleCondNegate("analysis") );
|
|
actprop->addRule( new RuleBoolNegate("analysis") );
|
|
actprop->addRule( new RuleLessEqual("analysis") );
|
|
actprop->addRule( new RuleLessNotEqual("analysis") );
|
|
actprop->addRule( new RuleLessOne("analysis") );
|
|
actprop->addRule( new RuleRangeMeld("analysis") );
|
|
actprop->addRule( new RuleFloatRange("analysis") );
|
|
actprop->addRule( new RulePiece2Zext("analysis") );
|
|
actprop->addRule( new RulePiece2Sext("analysis") );
|
|
actprop->addRule( new RulePopcountBoolXor("analysis") );
|
|
actprop->addRule( new RuleXorSwap("analysis") );
|
|
actprop->addRule( new RuleLzcountShiftBool("analysis") );
|
|
actprop->addRule( new RuleFloatSign("analysis") );
|
|
actprop->addRule( new RuleOrCompare("analysis") );
|
|
actprop->addRule( new RuleSubvarAnd("subvar") );
|
|
actprop->addRule( new RuleSubvarSubpiece("subvar") );
|
|
actprop->addRule( new RuleSplitFlow("subvar") );
|
|
actprop->addRule( new RulePtrFlow("subvar",conf) );
|
|
actprop->addRule( new RuleSubvarCompZero("subvar") );
|
|
actprop->addRule( new RuleSubvarShift("subvar") );
|
|
actprop->addRule( new RuleSubvarZext("subvar") );
|
|
actprop->addRule( new RuleSubvarSext("subvar") );
|
|
actprop->addRule( new RuleNegateNegate("analysis") );
|
|
actprop->addRule( new RuleConditionalMove("conditionalexe") );
|
|
actprop->addRule( new RuleOrPredicate("conditionalexe") );
|
|
actprop->addRule( new RuleFuncPtrEncoding("analysis") );
|
|
actprop->addRule( new RuleSubfloatConvert("floatprecision") );
|
|
actprop->addRule( new RuleFloatCast("floatprecision") );
|
|
actprop->addRule( new RuleIgnoreNan("floatprecision") );
|
|
actprop->addRule( new RuleUnsigned2Float("analysis") );
|
|
actprop->addRule( new RuleInt2FloatCollapse("analysis") );
|
|
actprop->addRule( new RulePtraddUndo("typerecovery") );
|
|
actprop->addRule( new RulePtrsubUndo("typerecovery") );
|
|
actprop->addRule( new RuleSegment("segment") );
|
|
actprop->addRule( new RulePiecePathology("protorecovery") );
|
|
|
|
actprop->addRule( new RuleDoubleLoad("doubleload") );
|
|
actprop->addRule( new RuleDoubleStore("doubleprecis") );
|
|
actprop->addRule( new RuleDoubleIn("doubleprecis") );
|
|
actprop->addRule( new RuleDoubleOut("doubleprecis") );
|
|
for(iter=conf->extra_pool_rules.begin();iter!=conf->extra_pool_rules.end();++iter)
|
|
actprop->addRule( *iter ); // Add CPU specific rules
|
|
conf->extra_pool_rules.clear(); // Rules are now absorbed into universal
|
|
}
|
|
actstackstall->addAction( actprop );
|
|
actstackstall->addAction( new ActionLaneDivide("base") );
|
|
actstackstall->addAction( new ActionMultiCse("analysis") );
|
|
actstackstall->addAction( new ActionShadowVar("analysis") );
|
|
actstackstall->addAction( new ActionDeindirect("deindirect") );
|
|
actstackstall->addAction( new ActionStackPtrFlow("stackptrflow",stackspace));
|
|
actmainloop->addAction( actstackstall );
|
|
actmainloop->addAction( new ActionRedundBranch("deadcontrolflow") ); // dead code removal
|
|
actmainloop->addAction( new ActionBlockStructure("blockrecovery"));
|
|
actmainloop->addAction( new ActionConstantPtr("typerecovery") );
|
|
{
|
|
actprop2 = new ActionPool(Action::rule_repeatapply,"oppool2");
|
|
|
|
actprop2->addRule( new RulePushPtr("typerecovery") );
|
|
actprop2->addRule( new RuleStructOffset0("typerecovery") );
|
|
actprop2->addRule( new RulePtrArith("typerecovery") );
|
|
// actprop2->addRule( new RuleIndirectConcat("analysis") );
|
|
actprop2->addRule( new RuleLoadVarnode("stackvars") );
|
|
actprop2->addRule( new RuleStoreVarnode("stackvars") );
|
|
}
|
|
actmainloop->addAction( actprop2 );
|
|
actmainloop->addAction( new ActionDeterminedBranch("unreachable") );
|
|
actmainloop->addAction( new ActionUnreachable("unreachable") );
|
|
actmainloop->addAction( new ActionNodeJoin("nodejoin") );
|
|
actmainloop->addAction( new ActionConditionalExe("conditionalexe") );
|
|
actmainloop->addAction( new ActionConditionalConst("analysis") );
|
|
}
|
|
actfullloop->addAction( actmainloop );
|
|
actfullloop->addAction( new ActionLikelyTrash("protorecovery") );
|
|
actfullloop->addAction( new ActionDirectWrite("protorecovery_a", true) );
|
|
actfullloop->addAction( new ActionDirectWrite("protorecovery_b", false) );
|
|
actfullloop->addAction( new ActionDeadCode("deadcode") );
|
|
actfullloop->addAction( new ActionDoNothing("deadcontrolflow") );
|
|
actfullloop->addAction( new ActionSwitchNorm("switchnorm") );
|
|
actfullloop->addAction( new ActionReturnSplit("returnsplit") );
|
|
actfullloop->addAction( new ActionUnjustifiedParams("protorecovery") );
|
|
actfullloop->addAction( new ActionStartTypes("typerecovery") );
|
|
actfullloop->addAction( new ActionActiveReturn("protorecovery") );
|
|
}
|
|
act->addAction( actfullloop );
|
|
act->addAction( new ActionMappedLocalSync("localrecovery") );
|
|
act->addAction( new ActionStartCleanUp("cleanup") );
|
|
{
|
|
actcleanup = new ActionPool(Action::rule_repeatapply,"cleanup");
|
|
|
|
actcleanup->addRule( new RuleMultNegOne("cleanup") );
|
|
actcleanup->addRule( new RuleAddUnsigned("cleanup") );
|
|
actcleanup->addRule( new Rule2Comp2Sub("cleanup") );
|
|
actcleanup->addRule( new RuleDumptyHumpLate("cleanup") );
|
|
actcleanup->addRule( new RuleSubRight("cleanup") );
|
|
actcleanup->addRule( new RuleFloatSignCleanup("cleanup") );
|
|
actcleanup->addRule( new RuleExpandLoad("cleanup") );
|
|
actcleanup->addRule( new RulePtrsubCharConstant("cleanup") );
|
|
actcleanup->addRule( new RuleExtensionPush("cleanup") );
|
|
actcleanup->addRule( new RulePieceStructure("cleanup") );
|
|
actcleanup->addRule( new RuleSplitCopy("splitcopy") );
|
|
actcleanup->addRule( new RuleSplitLoad("splitpointer") );
|
|
actcleanup->addRule( new RuleSplitStore("splitpointer") );
|
|
actcleanup->addRule( new RuleStringCopy("constsequence"));
|
|
actcleanup->addRule( new RuleStringStore("constsequence"));
|
|
actcleanup->addRule( new RuleBitFieldStore("bitfields"));
|
|
actcleanup->addRule( new RuleBitFieldOut("bitfields"));
|
|
actcleanup->addRule( new RuleBitFieldLoad("bitfields"));
|
|
actcleanup->addRule( new RuleBitFieldIn("bitfields"));
|
|
actcleanup->addRule( new RulePullAbsorb("bitfields"));
|
|
actcleanup->addRule( new RuleInsertAbsorb("bitfields"));
|
|
}
|
|
act->addAction( actcleanup );
|
|
|
|
act->addAction( new ActionPreferComplement("blockrecovery") );
|
|
act->addAction( new ActionStructureTransform("blockrecovery") );
|
|
act->addAction( new ActionNormalizeBranches("normalizebranches") );
|
|
act->addAction( new ActionAssignHigh("merge") );
|
|
act->addAction( new ActionMergeRequired("merge") );
|
|
act->addAction( new ActionMarkExplicit("merge") );
|
|
act->addAction( new ActionMarkImplied("merge") ); // This must come BEFORE general merging
|
|
act->addAction( new ActionMergeMultiEntry("merge") );
|
|
act->addAction( new ActionMergeCopy("merge") );
|
|
act->addAction( new ActionDominantCopy("merge") );
|
|
act->addAction( new ActionDynamicSymbols("dynamic") );
|
|
act->addAction( new ActionMarkIndirectOnly("merge") ); // Must come after required merges but before speculative
|
|
act->addAction( new ActionMergeAdjacent("merge") );
|
|
act->addAction( new ActionMergeType("merge") );
|
|
act->addAction( new ActionHideShadow("merge") );
|
|
act->addAction( new ActionCopyMarker("merge") );
|
|
act->addAction( new ActionOutputPrototype("localrecovery") );
|
|
act->addAction( new ActionInputPrototype("fixateproto") );
|
|
act->addAction( new ActionMapGlobals("fixateglobals") );
|
|
act->addAction( new ActionDynamicSymbols("dynamic") );
|
|
act->addAction( new ActionNameVars("merge") );
|
|
act->addAction( new ActionSetCasts("casts") );
|
|
act->addAction( new ActionFinalStructure("blockrecovery") );
|
|
act->addAction( new ActionPrototypeWarnings("protorecovery") );
|
|
act->addAction( new ActionStop("base") );
|
|
}
|
|
|
|
} // End namespace ghidra
|