diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index 8e397e6d58..48191e20b2 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -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| diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc index a75a5e5929..2f2106dfcf 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.cc @@ -4410,12 +4410,8 @@ int4 ActionSwitchNorm::apply(Funcdata &data) for(int4 i=0;iisLabelled()) { - 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; } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc index e93fa719fb..00651c18e6 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc @@ -4,9 +4,9 @@ * 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. @@ -781,7 +781,6 @@ void FlowInfo::generateOps(void) if (hasInject()) injectPcode(); do { - bool collapsed_jumptable = false; while(!tablelist.empty()) { // For each jumptable found vector newTables; recoverJumpTables(newTables, notreached); @@ -793,16 +792,13 @@ void FlowInfo::generateOps(void) int4 num = jt->numEntries(); for(int4 i=0;igetIndirectOp(),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(); + checkMultistageJumptables(); while(notreachcnt < notreached.size()) { tablelist.push_back(notreached[notreachcnt]); notreachcnt += 1; @@ -1435,14 +1431,18 @@ void FlowInfo::recoverJumpTables(vector &newTables,vector 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); } } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc index c0924087ec..79791468b3 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc @@ -4,9 +4,9 @@ * 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. @@ -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 diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.cc index e41c7b3488..860a07a594 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.cc @@ -4,9 +4,9 @@ * 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. @@ -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) @@ -2252,12 +2279,12 @@ void JumpTable::sanityCheck(Funcdata *fd,vector *loadcounts) { if (jmodel->isOverride()) - return; // Don't perform sanity check on an override + return; // Don't perform sanity check on an override uint4 sz = addresstable.size(); if (!isReachable(indirect)) - throw JumptableNotReachableError("No legal flow"); - if (addresstable.size() == 1) { // One entry is likely some kind of thunk + 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; Address addr = addresstable[0]; @@ -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
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; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh index 917bb04ea1..eb6e1d7d11 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh @@ -4,9 +4,9 @@ * 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. @@ -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 *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 \ element diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/switchloop.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/switchloop.xml new file mode 100644 index 0000000000..60fc020e6a --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/switchloop.xml @@ -0,0 +1,44 @@ + + + + +f30f1efa4189f831c9488d35c0000000 +41b908000000bf0a00000041ba050000 +0041bb0300000083f90a0f849c000000 +8d41ff83f808770a486304864801f03e +ffe0418d4001b901000000eb77418d40 +02b902000000eb6c438d040083f80b41 +0f4dcbeb5f418d4064b904000000eb54 +4489c099f7ff4181f895000000410f4e +caeb41418d80e8030000b906000000eb +33418d8010270000b907000000eb2544 +89c04489c9348789c283e20129d1eb14 +418d4008b909000000eb09418d4010b9 +0a0000004189c0e95bffffff4489c0c3 +7dffffff88ffffff95ffffffa0ffffff +b3ffffffc1ffffffcfffffffe0ffffff +ebffffff + + + + +case .*: +startval = startval \+ 2; +startval = startval \* 2; +startval = startval \+ 100; +startval / 10; +startval = startval \+ 1000; +startval = startval \+ 10000; +startval = startval \^ 0x87; +startval = startval \+ 8; +startval = startval \+ 1; + diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/switchmulti.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/switchmulti.xml new file mode 100644 index 0000000000..21dfd75ca8 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/switchmulti.xml @@ -0,0 +1,43 @@ + + + + +f30f1efa4883ec2848897c24084889e7 +488974241048c704240000000048c744 +241800000000e8d50f00004c8b14244c +8b5c241031c9488d3583000000bf6500 +000041b9030000004d85d2745b4d8d04 +0b4883f90677574863048e4801f03eff +e0498d480aeb39498d48f6eb33496bc8 +07eb2d4c89c0489949f7f94889c1eb20 +4c89c0489948f7ff4889d1eb134c89c1 +4881f1ba0a0000eb074c89c14883c920 +4883f9637ea2eb0d4983c8ffeb0749c7 +c0feffffff4c89c04883c428c3 + + +a1ffffffa7ffffffadffffffb3ffffff +c0ffffffcdffffffd9ffffff + + + + +case .*: +uVar1 \+ 10; +uVar1 \- 10; +uVar1 \* 7; +uVar1 / 3; +uVar1 % 0x65; +uVar1 \^ 0xaba; +uVar1 \| 0x20; +return 0xfffffffffffffffe; +