GP-4907 Multistage jumptable adjustment

This commit is contained in:
caheckman 2024-09-06 20:26:05 +00:00 committed by ghidra1
parent 86e77bd9ef
commit 691137abc1
8 changed files with 198 additions and 103 deletions

View File

@ -73,6 +73,8 @@ src/decompile/datatests/stackstring.xml||GHIDRA||||END|
src/decompile/datatests/statuscmp.xml||GHIDRA||||END|
src/decompile/datatests/switchhide.xml||GHIDRA||||END|
src/decompile/datatests/switchind.xml||GHIDRA||||END|
src/decompile/datatests/switchloop.xml||GHIDRA||||END|
src/decompile/datatests/switchmulti.xml||GHIDRA||||END|
src/decompile/datatests/switchreturn.xml||GHIDRA||||END|
src/decompile/datatests/threedim.xml||GHIDRA||||END|
src/decompile/datatests/twodim.xml||GHIDRA||||END|

View File

@ -4410,12 +4410,8 @@ int4 ActionSwitchNorm::apply(Funcdata &data)
for(int4 i=0;i<data.numJumpTables();++i) {
JumpTable *jt = data.getJumpTable(i);
if (!jt->isLabelled()) {
if (jt->recoverLabels(&data)) { // Recover case statement labels
// If this returns true, the jumptable was not fully recovered during flow analysis
// So we need to issue a restart
data.getOverride().insertMultistageJump(jt->getOpAddress());
data.setRestartPending(true);
}
jt->matchModel(&data);
jt->recoverLabels(&data); // Recover case statement labels
jt->foldInNormalization(&data);
count += 1;
}

View File

@ -781,7 +781,6 @@ void FlowInfo::generateOps(void)
if (hasInject())
injectPcode();
do {
bool collapsed_jumptable = false;
while(!tablelist.empty()) { // For each jumptable found
vector<JumpTable *> newTables;
recoverJumpTables(newTables, notreached);
@ -793,15 +792,12 @@ void FlowInfo::generateOps(void)
int4 num = jt->numEntries();
for(int4 i=0;i<num;++i)
newAddress(jt->getIndirectOp(),jt->getAddressByIndex(i));
if (jt->isPossibleMultistage())
collapsed_jumptable = true;
while(!addrlist.empty()) // Try to fill in as much more as possible
fallthru();
}
}
checkContainedCall(); // Check for PIC constructions
if (collapsed_jumptable)
checkMultistageJumptables();
while(notreachcnt < notreached.size()) {
tablelist.push_back(notreached[notreachcnt]);
@ -1435,14 +1431,18 @@ void FlowInfo::recoverJumpTables(vector<JumpTable *> &newTables,vector<PcodeOp *
JumpTable::RecoveryMode mode;
JumpTable *jt = data.recoverJumpTable(partial,op,this,mode); // Recover it
if (jt == (JumpTable *)0) { // Could not recover jumptable
if ((mode == JumpTable::fail_noflow) && (tablelist.size() > 1) && (!isInArray(notreached,op))) {
// If the indirect op was not reachable with current flow AND there is more flow to generate,
// AND we haven't tried to recover this table before
notreached.push_back(op); // Save this op so we can try to recovery table again later
}
else if (!isFlowForInline()) // Unless this flow is being inlined for something else
if (!isFlowForInline()) // Unless this flow is being inlined for something else
truncateIndirectJump(op,mode); // Treat the indirect jump as a call
}
else if (jt->isPartial()) {
if (tablelist.size() > 1 && !isInArray(notreached,op)) {
// If the recovery is incomplete with current flow AND there is more flow to generate,
// AND we haven't tried to recover this table before
notreached.push_back(op); // Save this op so we can try to recover the table again later
}
else
jt->markComplete(); // If we aren't revisiting, mark the table as complete
}
newTables.push_back(jt);
}
}

View File

@ -528,14 +528,11 @@ JumpTable::RecoveryMode Funcdata::stageJumpTable(Funcdata &partial,JumpTable *jt
try {
jt->setLoadCollect(flow->doesJumpRecord());
jt->setIndirectOp(partop);
if (jt->getStage()>0)
if (jt->isPartial())
jt->recoverMultistage(&partial);
else
jt->recoverAddresses(&partial); // Analyze partial to recover jumptable addresses
}
catch(JumptableNotReachableError &err) { // Thrown by recoverAddresses
return JumpTable::fail_noflow;
}
catch(JumptableThunkError &err) { // Thrown by recoverAddresses
return JumpTable::fail_thunk;
}
@ -645,7 +642,7 @@ JumpTable *Funcdata::recoverJumpTable(Funcdata &partial,PcodeOp *op,FlowInfo *fl
jt = linkJumpTable(op); // Search for pre-existing jumptable
if (jt != (JumpTable *)0) {
if (!jt->isOverride()) {
if (jt->getStage() != 1)
if (!jt->isPartial())
return jt; // Previously calculated jumptable (NOT an override and NOT incomplete)
}
mode = stageJumpTable(partial,jt,op,flow); // Recover based on override information

View File

@ -1049,7 +1049,7 @@ void JumpBasic::analyzeGuards(BlockBasic *bl,int4 pathout)
int4 i,j,indpath;
int4 maxbranch = 2; // Maximum number of CBRANCHs to consider
int4 maxpullback = 2;
bool usenzmask = (jumptable->getStage() == 0);
bool usenzmask = !jumptable->isPartial();
selectguards.clear();
BlockBasic *prevbl;
@ -2205,6 +2205,33 @@ JumpModel *JumpAssisted::clone(JumpTable *jt) const
return clone;
}
void JumpTable::saveModel(void)
{
if (origmodel != (JumpModel *)0)
delete origmodel;
origmodel = jmodel;
jmodel = (JumpModel *)0;
}
void JumpTable::restoreSavedModel(void)
{
if (jmodel != (JumpModel *)0)
delete jmodel;
jmodel = origmodel;
origmodel = (JumpModel *)0;
}
void JumpTable::clearSavedModel(void)
{
if (origmodel != (JumpModel *)0) {
delete origmodel;
origmodel = (JumpModel *)0;
}
}
/// Try to recover each model in turn, until we find one that matches the specific BRANCHIND.
/// \param fd is the function containing the switch
void JumpTable::recoverModel(Funcdata *fd)
@ -2256,7 +2283,7 @@ void JumpTable::sanityCheck(Funcdata *fd,vector<int4> *loadcounts)
uint4 sz = addresstable.size();
if (!isReachable(indirect))
throw JumptableNotReachableError("No legal flow");
partialTable = true; // If the jumptable is not reachable, mark as incomplete
if (addresstable.size() == 1) { // One entry is likely some kind of thunk
bool isthunk = false;
uintb diff;
@ -2346,7 +2373,7 @@ JumpTable::JumpTable(Architecture *g,Address ad)
maxaddsub = 1;
maxleftright = 1;
maxext = 1;
recoverystage = 0;
partialTable = false;
collectloads = false;
defaultIsFolded = false;
}
@ -2367,7 +2394,7 @@ JumpTable::JumpTable(const JumpTable *op2)
maxaddsub = op2->maxaddsub;
maxleftright = op2->maxleftright;
maxext = op2->maxext;
recoverystage = op2->recoverystage;
partialTable = op2->partialTable;
collectloads = op2->collectloads;
defaultIsFolded = false;
// We just clone the addresses themselves
@ -2587,8 +2614,8 @@ void JumpTable::recoverAddresses(Funcdata *fd)
}
if (jmodel->getTableSize() == 0) {
ostringstream err;
err << "Impossible to reach jumptable at " << opaddress;
throw JumptableNotReachableError(err.str());
err << "Jumptable with 0 entries at " << opaddress;
throw LowlevelError(err.str());
}
// if (sz < 2)
// fd->warning("Jumptable has only one branch",opaddress);
@ -2609,10 +2636,7 @@ void JumpTable::recoverAddresses(Funcdata *fd)
void JumpTable::recoverMultistage(Funcdata *fd)
{
if (origmodel != (JumpModel *)0)
delete origmodel;
origmodel = jmodel;
jmodel = (JumpModel *)0;
saveModel();
vector<Address> oldaddresstable = addresstable;
addresstable.clear();
@ -2621,36 +2645,25 @@ void JumpTable::recoverMultistage(Funcdata *fd)
recoverAddresses(fd);
}
catch(JumptableThunkError &err) {
if (jmodel != (JumpModel *)0)
delete jmodel;
jmodel = origmodel;
origmodel = (JumpModel *)0;
restoreSavedModel();
addresstable = oldaddresstable;
fd->warning("Second-stage recovery error",indirect->getAddr());
}
catch(LowlevelError &err) {
if (jmodel != (JumpModel *)0)
delete jmodel;
jmodel = origmodel;
origmodel = (JumpModel *)0;
restoreSavedModel();
addresstable = oldaddresstable;
fd->warning("Second-stage recovery error",indirect->getAddr());
}
recoverystage = 2;
if (origmodel != (JumpModel *)0) { // Keep the new model if it was created successfully
delete origmodel;
origmodel = (JumpModel *)0;
}
partialTable = false;
clearSavedModel(); // Keep the new model if it was created successfully
}
/// This is run assuming the address table has already been recovered, via recoverAddresses() in another
/// Funcdata instance. So recoverModel() needs to be rerun on the instance passed in here.
///
/// The unnormalized switch variable is recovered, and for each possible address table entry, the variable
/// value that produces it is calculated and stored as the formal \e case label for the associated code block.
/// \param fd is the (final instance of the) function containing the switch
/// \return \b true if it looks like a multi-stage restart is needed.
bool JumpTable::recoverLabels(Funcdata *fd)
/// This assumes the address table has already been recovered, via recoverAddresses() in another
/// Funcdata instance. We rerun recoverModel() to match with the current Funcdata. If the recovered model
/// does not match the original address table size, we may be missing control-flow. In this case,
/// if it looks like we have a \e multistage jumptable, we generate a multistage restart, otherwise
/// we generate a warning of the mismatch.
void JumpTable::matchModel(Funcdata *fd)
{
if (!isRecovered())
@ -2658,24 +2671,33 @@ bool JumpTable::recoverLabels(Funcdata *fd)
// Unless the model is an override, move model (created on a flow copy) so we can create a current instance
if (jmodel != (JumpModel *)0) {
if (origmodel != (JumpModel *)0)
delete origmodel;
if (!jmodel->isOverride()) {
origmodel = jmodel;
jmodel = (JumpModel *)0;
}
else
if (!jmodel->isOverride())
saveModel();
else {
clearSavedModel();
fd->warning("Switch is manually overridden",opaddress);
}
bool multistagerestart = false;
recoverModel(fd); // Create a current instance of the model
if (jmodel != (JumpModel *)0) {
if (jmodel->getTableSize() != addresstable.size()) {
fd->warning("Could not find normalized switch variable to match jumptable",opaddress);
if ((addresstable.size()==1)&&(jmodel->getTableSize() > 1))
multistagerestart = true;
}
recoverModel(fd); // Create a current instance of the model
if (jmodel != (JumpModel *)0 && jmodel->getTableSize() != addresstable.size()) {
if ((addresstable.size()==1)&&(jmodel->getTableSize() > 1)) {
// The jumptable was not fully recovered during flow analysis, try to issue a restart
fd->getOverride().insertMultistageJump(opaddress);
fd->setRestartPending(true);
return;
}
fd->warning("Could not find normalized switch variable to match jumptable",opaddress);
}
}
/// The unnormalized switch variable is recovered, and for each possible address table entry, the variable
/// value that produces it is calculated and stored as the formal \e case label for the associated code block.
/// \param fd is the (final instance of the) function containing the switch
void JumpTable::recoverLabels(Funcdata *fd)
{
if (jmodel != (JumpModel *)0) {
if ((origmodel == (JumpModel *)0)||(origmodel->getTableSize()==0)) {
jmodel->findUnnormalized(maxaddsub,maxleftright,maxext);
jmodel->buildLabels(fd,addresstable,label,jmodel);
@ -2692,11 +2714,7 @@ bool JumpTable::recoverLabels(Funcdata *fd)
trivialSwitchOver();
jmodel->buildLabels(fd,addresstable,label,origmodel);
}
if (origmodel != (JumpModel *)0) {
delete origmodel;
origmodel = (JumpModel *)0;
}
return multistagerestart;
clearSavedModel();
}
/// Clear out any data that is specific to a Funcdata instance.
@ -2704,10 +2722,7 @@ bool JumpTable::recoverLabels(Funcdata *fd)
void JumpTable::clear(void)
{
if (origmodel != (JumpModel *)0) {
delete origmodel;
origmodel = (JumpModel *)0;
}
clearSavedModel();
if (jmodel->isOverride())
jmodel->clear();
else {
@ -2722,7 +2737,7 @@ void JumpTable::clear(void)
indirect = (PcodeOp *)0;
switchVarConsume = ~((uintb)0);
defaultBlock = -1;
recoverystage = 0;
partialTable = false;
// -opaddress- -maxtablesize- -maxaddsub- -maxleftright- -maxext- -collectloads- are permanent
}
@ -2816,11 +2831,11 @@ bool JumpTable::checkForMultistage(Funcdata *fd)
{
if (addresstable.size()!=1) return false;
if (recoverystage != 0) return false;
if (partialTable) return false;
if (indirect == (PcodeOp *)0) return false;
if (fd->getOverride().queryMultistageJumptable(indirect->getAddr())) {
recoverystage = 1; // Mark that we need additional recovery
partialTable = true; // Mark that we need additional recovery
return true;
}
return false;

View File

@ -42,11 +42,6 @@ struct JumptableThunkError : public LowlevelError {
JumptableThunkError(const string &s) : LowlevelError(s) {} ///< Construct with an explanatory string
};
/// \brief Exception thrown is there are no legal flows to a switch
struct JumptableNotReachableError : public LowlevelError {
JumptableNotReachableError(const string &s) : LowlevelError(s) {} ///< Constructor
};
/// \brief A description where and how data was loaded from memory
///
/// This is a generic table description, giving the starting address
@ -547,9 +542,8 @@ public:
success = 0, ///< JumpTable is fully recovered
fail_normal = 1, ///< Normal failure to recover
fail_thunk = 2, ///< Likely \b thunk
fail_noflow = 3, ///< No legal flow to BRANCHIND
fail_return = 4, ///< Likely \b return operation
fail_callother = 5 ///< Address formed by CALLOTHER
fail_return = 3, ///< Likely \b return operation
fail_callother = 4 ///< Address formed by CALLOTHER
};
private:
/// \brief An address table index and its corresponding out-edge
@ -575,9 +569,12 @@ private:
uint4 maxaddsub; ///< Maximum ADDs or SUBs to normalize
uint4 maxleftright; ///< Maximum shifts to normalize
uint4 maxext; ///< Maximum extensions to normalize
int4 recoverystage; ///< 0=no stages recovered, 1=additional stage needed, 2=complete
bool partialTable; ///< Set to \b true if \b this table is incomplete and needs additional recovery steps
bool collectloads; ///< Set to \b true if information about in-memory model data is/should be collected
bool defaultIsFolded; ///< The \e default block is the target of a folded CBRANCH (and cannot have a label)
void saveModel(void); ///< Save off current model (if any) and prepare for instantiating a new model
void restoreSavedModel(void); ///< Restore any saved model as the current model
void clearSavedModel(void); ///< Clear any saved model
void recoverModel(Funcdata *fd); ///< Attempt recovery of the jump-table model
void trivialSwitchOver(void); ///< Switch \b this table over to a trivial model
void sanityCheck(Funcdata *fd,vector<int4> *loadpoints); ///< Perform sanity check on recovered address targets
@ -590,8 +587,8 @@ public:
bool isRecovered(void) const { return !addresstable.empty(); } ///< Return \b true if a model has been recovered
bool isLabelled(void) const { return !label.empty(); } ///< Return \b true if \e case labels are computed
bool isOverride(void) const; ///< Return \b true if \b this table was manually overridden
bool isPossibleMultistage(void) const { return (addresstable.size()==1); } ///< Return \b true if this could be multi-staged
int4 getStage(void) const { return recoverystage; } ///< Return what stage of recovery this jump-table is in.
bool isPartial(void) const { return partialTable; } ///< Return \b true if \b this is a partial table needing more recovery
void markComplete(void) { partialTable = false; } ///< Mark whatever is recovered so far as the complete table
int4 numEntries(void) const { return addresstable.size(); } ///< Return the size of the address table for \b this jump-table
uintb getSwitchVarConsume(void) const { return switchVarConsume; } ///< Get bits of switch variable consumed by \b this table
int4 getDefaultBlock(void) const { return defaultBlock; } ///< Get the out-edge corresponding to the \e default switch destination
@ -616,7 +613,8 @@ public:
bool foldInGuards(Funcdata *fd) { return jmodel->foldInGuards(fd,this); } ///< Hide any guard code for \b this switch
void recoverAddresses(Funcdata *fd); ///< Recover the raw jump-table addresses (the address table)
void recoverMultistage(Funcdata *fd); ///< Recover jump-table addresses keeping track of a possible previous stage
bool recoverLabels(Funcdata *fd); ///< Recover the case labels for \b this jump-table
void matchModel(Funcdata *fd); ///< Try to match JumpTable model to the existing function
void recoverLabels(Funcdata *fd); ///< Recover the case labels for \b this jump-table
bool checkForMultistage(Funcdata *fd); ///< Check if this jump-table requires an additional recovery stage
void clear(void); ///< Clear instance specific data for \b this jump-table
void encode(Encoder &encoder) const; ///< Encode \b this jump-table as a \<jumptable> element

View File

@ -0,0 +1,44 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Switch variable also a loop variable with increments in cases
-->
<bytechunk space="ram" offset="0x100000" readonly="true">
f30f1efa4189f831c9488d35c0000000
41b908000000bf0a00000041ba050000
0041bb0300000083f90a0f849c000000
8d41ff83f808770a486304864801f03e
ffe0418d4001b901000000eb77418d40
02b902000000eb6c438d040083f80b41
0f4dcbeb5f418d4064b904000000eb54
4489c099f7ff4181f895000000410f4e
caeb41418d80e8030000b906000000eb
33418d8010270000b907000000eb2544
89c04489c9348789c283e20129d1eb14
418d4008b909000000eb09418d4010b9
0a0000004189c0e95bffffff4489c0c3
7dffffff88ffffff95ffffffa0ffffff
b3ffffffc1ffffffcfffffffe0ffffff
ebffffff
</bytechunk>
<symbol space="ram" offset="0x100000" name="switchloop"/>
</binaryimage>
<script>
<com>option readonly on</com>
<com>parse line extern uint4 switchloop(uint4 startval);</com>
<com>lo fu switchloop</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Switch Loop #1" min="9" max="9">case .*:</stringmatch>
<stringmatch name="Switch Loop #2" min="1" max="1">startval = startval \+ 2;</stringmatch>
<stringmatch name="Switch Loop #3" min="1" max="1">startval = startval \* 2;</stringmatch>
<stringmatch name="Switch Loop #4" min="1" max="1">startval = startval \+ 100;</stringmatch>
<stringmatch name="Switch Loop #5" min="1" max="1">startval / 10;</stringmatch>
<stringmatch name="Switch Loop #6" min="1" max="1">startval = startval \+ 1000;</stringmatch>
<stringmatch name="Switch Loop #7" min="1" max="1">startval = startval \+ 10000;</stringmatch>
<stringmatch name="Switch Loop #8" min="1" max="1">startval = startval \^ 0x87;</stringmatch>
<stringmatch name="Switch Loop #9" min="1" max="1">startval = startval \+ 8;</stringmatch>
<stringmatch name="Switch Loop #10" min="1" max="1">startval = startval \+ 1;</stringmatch>
</decompilertest>

View File

@ -0,0 +1,43 @@
<decompilertest>
<binaryimage arch="x86:LE:64:default:gcc">
<!--
Switch in a loop and also depending on a variable set to a constant locally and
modified indirectly.
-->
<bytechunk space="ram" offset="0x100000" readonly="true">
f30f1efa4883ec2848897c24084889e7
488974241048c704240000000048c744
241800000000e8d50f00004c8b14244c
8b5c241031c9488d3583000000bf6500
000041b9030000004d85d2745b4d8d04
0b4883f90677574863048e4801f03eff
e0498d480aeb39498d48f6eb33496bc8
07eb2d4c89c0489949f7f94889c1eb20
4c89c0489948f7ff4889d1eb134c89c1
4881f1ba0a0000eb074c89c14883c920
4883f9637ea2eb0d4983c8ffeb0749c7
c0feffffff4c89c04883c428c3
</bytechunk>
<bytechunk space="ram" offset="0x1000c0" readonly="true">
a1ffffffa7ffffffadffffffb3ffffff
c0ffffffcdffffffd9ffffff
</bytechunk>
<symbol space="ram" offset="0x100000" name="switchmulti"/>
</binaryimage>
<script>
<com>option readonly on</com>
<com>lo fu switchmulti</com>
<com>decompile</com>
<com>print C</com>
<com>quit</com>
</script>
<stringmatch name="Switch Multi #1" min="7" max="7">case .*:</stringmatch>
<stringmatch name="Switch Multi #2" min="1" max="1">uVar1 \+ 10;</stringmatch>
<stringmatch name="Switch Multi #3" min="1" max="1">uVar1 \- 10;</stringmatch>
<stringmatch name="Switch Multi #4" min="1" max="1">uVar1 \* 7;</stringmatch>
<stringmatch name="Switch Multi #5" min="1" max="1">uVar1 / 3;</stringmatch>
<stringmatch name="Switch Multi #6" min="1" max="1">uVar1 % 0x65;</stringmatch>
<stringmatch name="Switch Multi #7" min="1" max="1">uVar1 \^ 0xaba;</stringmatch>
<stringmatch name="Switch Multi #8" min="1" max="1">uVar1 \| 0x20;</stringmatch>
<stringmatch name="Switch Multi #9" min="1" max="1">return 0xfffffffffffffffe;</stringmatch>
</decompilertest>