ghidra/Ghidra/Features/Decompiler/src/decompile/cpp/prefersplit.cc

631 lines
19 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 "prefersplit.hh"
#include "funcdata.hh"
namespace ghidra {
ElementId ELEM_PREFERSPLIT = ElementId("prefersplit",225);
bool PreferSplitRecord::operator<(const PreferSplitRecord &op2) const
{
if (storage.space != op2.storage.space)
return (storage.space->getIndex() < op2.storage.space->getIndex());
if (storage.size != op2.storage.size)
return (storage.size > op2.storage.size); // Bigger sizes come first
return storage.offset < op2.storage.offset;
}
void PreferSplitManager::fillinInstance(SplitInstance *inst,bool bigendian,bool sethi,bool setlo)
{ // Define the varnode pieces of -inst-
Varnode *vn = inst->vn;
int4 losize;
if (bigendian)
losize = vn->getSize() - inst->splitoffset;
else
losize = inst->splitoffset;
int4 hisize = vn->getSize() - losize;
if (vn->isConstant()) {
uintb origval = vn->getOffset();
uintb loval = origval & calc_mask( losize );// Split the constant into two pieces
uintb hival = (origval >> 8*losize) & calc_mask( hisize );
if (setlo && (inst->lo == (Varnode *)0))
inst->lo = data->newConstant(losize,loval);
if (sethi && (inst->hi == (Varnode *)0))
inst->hi = data->newConstant(hisize,hival);
}
else {
if (bigendian) {
if (setlo && (inst->lo == (Varnode *)0))
inst->lo = data->newVarnode(losize,vn->getAddr() + inst->splitoffset);
if (sethi && (inst->hi == (Varnode *)0))
inst->hi = data->newVarnode(hisize,vn->getAddr());
}
else {
if (setlo && (inst->lo == (Varnode *)0))
inst->lo = data->newVarnode(losize,vn->getAddr());
if (sethi && (inst->hi == (Varnode *)0))
inst->hi = data->newVarnode(hisize,vn->getAddr() + inst->splitoffset);
}
}
}
void PreferSplitManager::createCopyOps(SplitInstance *ininst,SplitInstance *outinst,PcodeOp *op,bool istemp)
{ // Create COPY ops based on input -ininst- and output -outinst- to replace -op-
PcodeOp *hiop = data->newOp(1,op->getAddr()); // Create two new COPYs
PcodeOp *loop = data->newOp(1,op->getAddr());
data->opSetOpcode(hiop,CPUI_COPY);
data->opSetOpcode(loop,CPUI_COPY);
data->opInsertAfter(loop,op); // Insert new COPYs at same position as original operation
data->opInsertAfter(hiop,op);
data->opUnsetInput(op,0); // Unset input so we can reassign free inputs to new ops
data->opSetOutput(hiop,outinst->hi); // Outputs
data->opSetOutput(loop,outinst->lo);
data->opSetInput(hiop,ininst->hi,0);
data->opSetInput(loop,ininst->lo,0);
tempsplits.push_back(hiop);
tempsplits.push_back(loop);
}
bool PreferSplitManager::testDefiningCopy(SplitInstance *inst,PcodeOp *def,bool &istemp)
{ // Check that -inst- defined by -def- is really splittable
Varnode *invn = def->getIn(0);
istemp = false;
if (!invn->isConstant()) {
if (invn->getSpace()->getType() != IPTR_INTERNAL) {
const PreferSplitRecord *inrec = findRecord(invn);
if (inrec == (const PreferSplitRecord *)0) return false;
if (inrec->splitoffset != inst->splitoffset) return false;
if (!invn->isFree()) return false;
}
else
istemp = true;
}
return true;
}
void PreferSplitManager::splitDefiningCopy(SplitInstance *inst,PcodeOp *def,bool istemp)
{ // Do split of prefered split varnode that is defined by a COPY
Varnode *invn = def->getIn(0);
SplitInstance ininst(invn,inst->splitoffset);
bool bigendian = inst->vn->getSpace()->isBigEndian();
fillinInstance(inst,bigendian,true,true);
fillinInstance(&ininst,bigendian,true,true);
createCopyOps(&ininst,inst,def,istemp);
}
bool PreferSplitManager::testReadingCopy(SplitInstance *inst,PcodeOp *readop,bool &istemp)
{ // Check that -inst- read by -readop- is really splittable
Varnode *outvn = readop->getOut();
istemp = false;
if (outvn->getSpace()->getType() != IPTR_INTERNAL) {
const PreferSplitRecord *outrec = findRecord(outvn);
if (outrec == (const PreferSplitRecord *)0) return false;
if (outrec->splitoffset != inst->splitoffset) return false;
}
else
istemp = true;
return true;
}
void PreferSplitManager::splitReadingCopy(SplitInstance *inst,PcodeOp *readop,bool istemp)
{ // Do split of varnode that is read by a COPY
Varnode *outvn = readop->getOut();
SplitInstance outinst(outvn,inst->splitoffset);
bool bigendian = inst->vn->getSpace()->isBigEndian();
fillinInstance(inst,bigendian,true,true);
fillinInstance(&outinst,bigendian,true,true);
createCopyOps(inst,&outinst,readop,istemp);
}
bool PreferSplitManager::testZext(SplitInstance *inst,PcodeOp *op)
{ // Check that -inst- defined by ZEXT is really splittable
Varnode *invn = op->getIn(0);
if (invn->isConstant())
return true;
bool bigendian = inst->vn->getSpace()->isBigEndian();
int4 losize;
if (bigendian)
losize = inst->vn->getSize() - inst->splitoffset;
else
losize = inst->splitoffset;
if (invn->getSize() != losize) return false;
return true;
}
void PreferSplitManager::splitZext(SplitInstance *inst,PcodeOp *op)
{
SplitInstance ininst(op->getIn(0),inst->splitoffset);
int4 losize,hisize;
bool bigendian = inst->vn->getSpace()->isBigEndian();
if (bigendian) {
hisize = inst->splitoffset;
losize = inst->vn->getSize() - inst->splitoffset;
}
else {
losize = inst->splitoffset;
hisize = inst->vn->getSize() - inst->splitoffset;
}
if (ininst.vn->isConstant()) {
uintb origval = ininst.vn->getOffset();
uintb loval = origval & calc_mask( losize );// Split the constant into two pieces
uintb hival = (origval >> 8*losize) & calc_mask( hisize );
ininst.lo = data->newConstant(losize,loval);
ininst.hi = data->newConstant(hisize,hival);
}
else {
ininst.lo = ininst.vn;
ininst.hi = data->newConstant(hisize,0);
}
fillinInstance(inst,bigendian,true,true);
createCopyOps(&ininst,inst,op,false);
}
bool PreferSplitManager::testPiece(SplitInstance *inst,PcodeOp *op)
{ // Check that -inst- defined by PIECE is really splittable
if (inst->vn->getSpace()->isBigEndian()) {
if (op->getIn(0)->getSize() != inst->splitoffset) return false;
}
else {
if (op->getIn(1)->getSize() != inst->splitoffset) return false;
}
return true;
}
void PreferSplitManager::splitPiece(SplitInstance *inst,PcodeOp *op)
{
Varnode *loin = op->getIn(1);
Varnode *hiin = op->getIn(0);
bool bigendian = inst->vn->getSpace()->isBigEndian();
fillinInstance(inst,bigendian,true,true);
PcodeOp *hiop = data->newOp(1,op->getAddr());
PcodeOp *loop = data->newOp(1,op->getAddr());
data->opSetOpcode(hiop,CPUI_COPY);
data->opSetOpcode(loop,CPUI_COPY);
data->opSetOutput(hiop,inst->hi); // Outputs are the pieces of the original
data->opSetOutput(loop,inst->lo);
data->opInsertAfter(loop,op);
data->opInsertAfter(hiop,op);
data->opUnsetInput(op,0);
data->opUnsetInput(op,1);
if (hiin->isConstant())
hiin = data->newConstant(hiin->getSize(),hiin->getOffset());
data->opSetInput(hiop,hiin,0); // Input for the COPY of the most significant part comes from high part of PIECE
if (loin->isConstant())
loin = data->newConstant(loin->getSize(),loin->getOffset());
data->opSetInput(loop,loin,0); // Input for the COPY of the least significant part comes from low part of PIECE
}
bool PreferSplitManager::testSubpiece(SplitInstance *inst,PcodeOp *op)
{ // Check that -inst- read by SUBPIECE is really splittable
Varnode *vn = inst->vn;
Varnode *outvn = op->getOut();
int4 suboff = (int4)op->getIn(1)->getOffset();
if (suboff == 0) {
if (vn->getSize() - inst->splitoffset != outvn->getSize())
return false;
}
else {
if (vn->getSize() - suboff != inst->splitoffset)
return false;
if (outvn->getSize() != inst->splitoffset)
return false;
}
return true;
}
void PreferSplitManager::splitSubpiece(SplitInstance *inst,PcodeOp *op)
{ // Knowing -op- is a CPUI_SUBPIECE that extracts a logical piece from -inst-, rewrite it to a copy
Varnode *vn = inst->vn;
int4 suboff = (int4)op->getIn(1)->getOffset();
bool grabbinglo = (suboff==0);
bool bigendian = vn->getSpace()->isBigEndian();
fillinInstance(inst,bigendian,!grabbinglo,grabbinglo);
data->opSetOpcode(op,CPUI_COPY); // Change SUBPIECE to a copy
data->opRemoveInput(op,1);
// Input is most/least significant piece, depending on which the SUBPIECE extracts
Varnode *invn = grabbinglo ? inst->lo : inst->hi;
data->opSetInput(op,invn,0);
}
bool PreferSplitManager::testLoad(SplitInstance *inst,PcodeOp *op)
{
return true;
}
void PreferSplitManager::splitLoad(SplitInstance *inst,PcodeOp *op)
{ // Knowing -op- is a CPUI_LOAD that defines the -inst- varnode, split it into two pieces
bool bigendian = inst->vn->getSpace()->isBigEndian();
fillinInstance(inst,bigendian,true,true);
PcodeOp *hiop = data->newOp(2,op->getAddr()); // Create two new LOAD ops
PcodeOp *loop = data->newOp(2,op->getAddr());
PcodeOp *addop = data->newOp(2,op->getAddr());
Varnode *ptrvn = op->getIn(1);
data->opSetOpcode(hiop,CPUI_LOAD);
data->opSetOpcode(loop,CPUI_LOAD);
data->opSetOpcode(addop,CPUI_INT_ADD); // Create a new ADD op to calculate and hold the second pointer
data->opInsertAfter(loop,op);
data->opInsertAfter(hiop,op);
data->opInsertAfter(addop,op);
data->opUnsetInput(op,1); // Free up ptrvn
Varnode *addvn = data->newUniqueOut(ptrvn->getSize(),addop);
data->opSetInput(addop,ptrvn,0);
data->opSetInput(addop,data->newConstant(ptrvn->getSize(),inst->splitoffset),1);
data->opSetOutput(hiop,inst->hi); // Outputs are the pieces of the original
data->opSetOutput(loop,inst->lo);
Varnode *spaceid = op->getIn(0);
AddrSpace *spc = spaceid->getSpaceFromConst();
spaceid = data->newConstant(spaceid->getSize(),spaceid->getOffset()); // Duplicate original spaceid into new LOADs
data->opSetInput(hiop,spaceid,0);
spaceid = data->newConstant(spaceid->getSize(),spaceid->getOffset());
data->opSetInput(loop,spaceid,0);
if (ptrvn->isFree()) // Don't read a free varnode twice
ptrvn = data->newVarnode(ptrvn->getSize(),ptrvn->getSpace(),ptrvn->getOffset());
if (spc->isBigEndian()) {
data->opSetInput(hiop,ptrvn,1);
data->opSetInput(loop,addvn,1);
}
else {
data->opSetInput(hiop,addvn,1);
data->opSetInput(loop,ptrvn,1);
}
}
bool PreferSplitManager::testStore(SplitInstance *inst,PcodeOp *op)
{
return true;
}
void PreferSplitManager::splitStore(SplitInstance *inst,PcodeOp *op)
{ // Knowing -op- stores the value -inst-, split it in two
fillinInstance(inst,inst->vn->getSpace()->isBigEndian(),true,true);
PcodeOp *hiop = data->newOp(3,op->getAddr()); // Create 2 new STOREs
PcodeOp *loop = data->newOp(3,op->getAddr());
PcodeOp *addop = data->newOp(2,op->getAddr());
Varnode *ptrvn = op->getIn(1);
data->opSetOpcode(hiop,CPUI_STORE);
data->opSetOpcode(loop,CPUI_STORE);
data->opSetOpcode(addop,CPUI_INT_ADD); // Create a new ADD op to calculate and hold the second pointer
data->opInsertAfter(loop,op);
data->opInsertAfter(hiop,op);
data->opInsertAfter(addop,op);
data->opUnsetInput(op,1); // Free up ptrvn
data->opUnsetInput(op,2); // Free up inst
Varnode *addvn = data->newUniqueOut(ptrvn->getSize(),addop);
data->opSetInput(addop,ptrvn,0);
data->opSetInput(addop,data->newConstant(ptrvn->getSize(),inst->splitoffset),1);
data->opSetInput(hiop,inst->hi,2); // Varnodes "being stored" are the pieces of the original
data->opSetInput(loop,inst->lo,2);
Varnode *spaceid = op->getIn(0);
AddrSpace *spc = spaceid->getSpaceFromConst();
spaceid = data->newConstant(spaceid->getSize(),spaceid->getOffset()); // Duplicate original spaceid into new STOREs
data->opSetInput(hiop,spaceid,0);
spaceid = data->newConstant(spaceid->getSize(),spaceid->getOffset());
data->opSetInput(loop,spaceid,0);
if (ptrvn->isFree()) // Don't read a free varnode twice
ptrvn = data->newVarnode(ptrvn->getSize(),ptrvn->getSpace(),ptrvn->getOffset());
if (spc->isBigEndian()) {
data->opSetInput(hiop,ptrvn,1);
data->opSetInput(loop,addvn,1);
}
else {
data->opSetInput(hiop,addvn,1);
data->opSetInput(loop,ptrvn,1);
}
}
bool PreferSplitManager::splitVarnode(SplitInstance *inst)
{ // Test if -vn- can be readily split, if so, do the split
Varnode *vn = inst->vn;
bool istemp;
if (vn->isWritten()) {
if (!vn->hasNoDescend()) return false; // Already linked in
PcodeOp *op = vn->getDef();
switch (op->code()) {
case CPUI_COPY:
if (!testDefiningCopy(inst,op,istemp))
return false;
splitDefiningCopy(inst,op,istemp);
break;
case CPUI_PIECE:
if (!testPiece(inst,op))
return false;
splitPiece(inst,op);
break;
case CPUI_LOAD:
if (!testLoad(inst,op))
return false;
splitLoad(inst,op);
break;
case CPUI_INT_ZEXT:
if (!testZext(inst,op))
return false;
splitZext(inst,op);
break;
default:
return false;
}
data->opDestroy(op);
}
else {
if (!vn->isFree()) return false; // Make sure vn is not already a marked input
PcodeOp *op = vn->loneDescend();
if (op == (PcodeOp *)0) // vn must be read exactly once
return false;
switch(op->code()) {
case CPUI_COPY:
if (!testReadingCopy(inst,op,istemp))
return false;
splitReadingCopy(inst,op,istemp);
break;
case CPUI_SUBPIECE:
if (!testSubpiece(inst,op))
return false;
splitSubpiece(inst,op);
return true; // Do not destroy op, it has been transformed
case CPUI_STORE:
if (!testStore(inst,op))
return false;
splitStore(inst,op);
break;
default:
return false;
}
data->opDestroy(op); // Original op is now dead
}
return true;
}
void PreferSplitManager::splitRecord(const PreferSplitRecord &rec)
{
Address addr = rec.storage.getAddr();
VarnodeLocSet::const_iterator iter,enditer;
SplitInstance inst((Varnode *)0,rec.splitoffset);
iter = data->beginLoc(rec.storage.size,addr);
enditer = data->endLoc(rec.storage.size,addr);
while(iter != enditer) {
inst.vn = *iter;
++iter;
inst.lo = (Varnode *)0;
inst.hi = (Varnode *)0;
if (splitVarnode(&inst)) { // If we found something, regenerate iterators, as they may be stale
iter = data->beginLoc(rec.storage.size,addr);
enditer = data->endLoc(rec.storage.size,addr);
}
}
}
bool PreferSplitManager::testTemporary(SplitInstance *inst)
{
PcodeOp *op = inst->vn->getDef();
switch(op->code()) {
case CPUI_PIECE:
if (!testPiece(inst,op))
return false;
break;
case CPUI_LOAD:
if (!testLoad(inst,op))
return false;
break;
case CPUI_INT_ZEXT:
if (!testZext(inst,op))
return false;
break;
default:
return false;
}
list<PcodeOp *>::const_iterator iter,enditer;
iter = inst->vn->beginDescend();
enditer = inst->vn->endDescend();
while(iter != enditer) {
PcodeOp *readop = *iter;
++iter;
switch(readop->code()) {
case CPUI_SUBPIECE:
if (!testSubpiece(inst,readop))
return false;
break;
case CPUI_STORE:
if (!testStore(inst,readop))
return false;
break;
default:
return false;
}
}
return true;
}
void PreferSplitManager::splitTemporary(SplitInstance *inst)
{
Varnode *vn = inst->vn;
PcodeOp *op = vn->getDef();
switch(op->code()) {
case CPUI_PIECE:
splitPiece(inst,op);
break;
case CPUI_LOAD:
splitLoad(inst,op);
break;
case CPUI_INT_ZEXT:
splitZext(inst,op);
break;
default:
break;
}
while(vn->beginDescend() != vn->endDescend()) {
PcodeOp *readop = *vn->beginDescend();
switch(readop->code()) {
case CPUI_SUBPIECE:
splitSubpiece(inst,readop);
break;
case CPUI_STORE:
splitStore(inst,readop);
data->opDestroy(readop);
break;
default:
break;
}
}
data->opDestroy(op);
}
void PreferSplitManager::init(Funcdata *fd,const vector<PreferSplitRecord> *rec)
{
data = fd;
records = rec;
}
const PreferSplitRecord *PreferSplitManager::findRecord(Varnode *vn) const
{ // Find the split record that applies to -vn-, otherwise return null
PreferSplitRecord templ;
templ.storage.space = vn->getSpace();
templ.storage.size = vn->getSize();
templ.storage.offset = vn->getOffset();
vector<PreferSplitRecord>::const_iterator iter;
iter = lower_bound(records->begin(),records->end(),templ);
if (iter == records->end())
return (PreferSplitRecord *)0;
if (templ < *iter)
return (PreferSplitRecord *)0;
return &(*iter);
}
void PreferSplitManager::initialize(vector<PreferSplitRecord> &records)
{
sort(records.begin(),records.end());
}
void PreferSplitManager::split(void)
{
for(int4 i=0;i<records->size();++i)
splitRecord((*records)[i]);
}
void PreferSplitManager::splitAdditional(void)
{
vector<PcodeOp *> defops;
for(int4 i=0;i<tempsplits.size();++i) {
PcodeOp *op = tempsplits[i]; // Look at everything connected to COPYs in -tempsplits-
if (op->isDead()) continue;
Varnode *vn = op->getIn(0);
if (vn->isWritten()) {
PcodeOp *defop = vn->getDef();
if (defop->code() == CPUI_SUBPIECE) { // SUBPIECEs flowing into the COPY
Varnode *invn = defop->getIn(0);
if (invn->getSpace()->getType() == IPTR_INTERNAL) // Might be from a temporary that needs further splitting
defops.push_back(defop);
}
}
list<PcodeOp *>::const_iterator iter,enditer;
iter = op->getOut()->beginDescend();
enditer = op->getOut()->endDescend();
while(iter != enditer) {
PcodeOp *defop = *iter;
++iter;
if (defop->code() == CPUI_PIECE) { // COPY flowing into PIECEs
Varnode *outvn = defop->getOut();
if (outvn->getSpace()->getType() == IPTR_INTERNAL) // Might be to a temporary that needs further splitting
defops.push_back(defop);
}
}
}
for(int4 i=0;i<defops.size();++i) {
PcodeOp *op = defops[i];
if (op->isDead()) continue;
if (op->code() == CPUI_PIECE) {
int4 splitoff;
Varnode *vn = op->getOut();
if (vn->getSpace()->isBigEndian())
splitoff = op->getIn(0)->getSize();
else
splitoff = op->getIn(1)->getSize();
SplitInstance inst(vn,splitoff);
if (testTemporary(&inst))
splitTemporary(&inst);
}
else if (op->code() == CPUI_SUBPIECE) {
int4 splitoff;
Varnode *vn = op->getIn(0);
uintb suboff = op->getIn(1)->getOffset();
if (vn->getSpace()->isBigEndian()) {
if (suboff == 0)
splitoff = vn->getSize() - op->getOut()->getSize();
else
splitoff = vn->getSize() - (int4)suboff;
}
else {
if (suboff == 0)
splitoff = op->getOut()->getSize();
else
splitoff = (int4)suboff;
}
SplitInstance inst(vn,splitoff);
if (testTemporary(&inst))
splitTemporary(&inst);
}
}
}
} // End namespace ghidra