diff --git a/Ghidra/Features/Decompiler/certification.manifest b/Ghidra/Features/Decompiler/certification.manifest index eecf7c60c5..67ce855515 100644 --- a/Ghidra/Features/Decompiler/certification.manifest +++ b/Ghidra/Features/Decompiler/certification.manifest @@ -4,7 +4,6 @@ ##MODULE IP: Modified Nuvola Icons - LGPL 2.1 ##MODULE IP: Oxygen Icons - LGPL 3.0 ##MODULE IP: Tango Icons - Public Domain -.gitignore||GHIDRA||||END| Module.manifest||GHIDRA||||END| data/decompiler.theme.properties||GHIDRA||||END| src/decompile/.cproject||GHIDRA||||END| @@ -62,6 +61,7 @@ src/decompile/datatests/skipnext2.xml||GHIDRA||||END| src/decompile/datatests/stackreturn.xml||GHIDRA||||END| src/decompile/datatests/statuscmp.xml||GHIDRA||||END| src/decompile/datatests/switchind.xml||GHIDRA||||END| +src/decompile/datatests/switchreturn.xml||GHIDRA||||END| src/decompile/datatests/threedim.xml||GHIDRA||||END| src/decompile/datatests/twodim.xml||GHIDRA||||END| src/decompile/datatests/union_datatype.xml||GHIDRA||||END| diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc index 3b1cb47412..38c0d5c825 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc @@ -711,20 +711,26 @@ bool FlowInfo::setupCallindSpecs(PcodeOp *op,FuncCallSpecs *fc) } /// \param op is the BRANCHIND operation to convert -/// \param failuremode is a code indicating the type of failure when trying to recover the jump table -void FlowInfo::truncateIndirectJump(PcodeOp *op,int4 failuremode) +/// \param mode indicates the type of failure when trying to recover the jump table +void FlowInfo::truncateIndirectJump(PcodeOp *op,JumpTable::RecoveryMode mode) { - data.opSetOpcode(op,CPUI_CALLIND); // Turn jump into call - setupCallindSpecs(op,(FuncCallSpecs *)0); - if (failuremode != 2) // Unless the switch was a thunk mechanism - data.getCallSpecs(op)->setBadJumpTable(true); // Consider using special name for switch variable + if (mode == JumpTable::fail_return) { + data.opSetOpcode(op,CPUI_RETURN); // Turn jump into return + data.warning("Treating indirect jump as return",op->getAddr()); + } + else { + data.opSetOpcode(op,CPUI_CALLIND); // Turn jump into call + setupCallindSpecs(op,(FuncCallSpecs *)0); + if (mode != JumpTable::fail_thunk) // Unless the switch was a thunk mechanism + data.getCallSpecs(op)->setBadJumpTable(true); // Consider using special name for switch variable - // Create an artificial return - PcodeOp *truncop = artificialHalt(op->getAddr(),0); - data.opDeadInsertAfter(truncop,op); + // Create an artificial return + PcodeOp *truncop = artificialHalt(op->getAddr(),0); + data.opDeadInsertAfter(truncop,op); - data.warning("Treating indirect jump as call",op->getAddr()); + data.warning("Treating indirect jump as call",op->getAddr()); + } } /// \brief Test if the given p-code op is a member of an array @@ -1404,16 +1410,16 @@ void FlowInfo::recoverJumpTables(vector &newTables,vector 1) && (!isInArray(notreached,op))) { + 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 - truncateIndirectJump(op,failuremode); // Treat the indirect jump as a call + truncateIndirectJump(op,mode); // Treat the indirect jump as a call } newTables.push_back(jt); } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.hh index b596bf074f..4bd5743b0c 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.hh @@ -137,7 +137,7 @@ private: void checkMultistageJumptables(void); void recoverJumpTables(vector &newTables,vector ¬reached); void deleteCallSpec(FuncCallSpecs *fc); ///< Remove the given call site from the list for \b this function - void truncateIndirectJump(PcodeOp *op,int4 failuremode); ///< Treat indirect jump as indirect call that never returns + void truncateIndirectJump(PcodeOp *op,JumpTable::RecoveryMode mode); ///< Treat indirect jump as CALLIND/RETURN static bool isInArray(vector &array,PcodeOp *op); public: FlowInfo(Funcdata &d,PcodeOpBank &o,BlockGraph &b,vector &q); ///< Constructor diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh index 7ea31a4cc8..84a46b4240 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata.hh @@ -118,7 +118,7 @@ class Funcdata { void pushMultiequals(BlockBasic *bb); ///< Push MULTIEQUAL Varnodes of the given block into the output block void clearBlocks(void); ///< Clear all basic blocks void structureReset(void); ///< Calculate initial basic block structures (after a control-flow change) - int4 stageJumpTable(Funcdata &partial,JumpTable *jt,PcodeOp *op,FlowInfo *flow); + JumpTable::RecoveryMode stageJumpTable(Funcdata &partial,JumpTable *jt,PcodeOp *op,FlowInfo *flow); void switchOverJumpTables(const FlowInfo &flow); ///< Convert jump-table addresses to basic block indices void clearJumpTables(void); ///< Clear any jump-table information @@ -423,6 +423,7 @@ public: Varnode *findLinkedVarnode(SymbolEntry *entry) const; ///< Find a Varnode matching the given Symbol mapping void findLinkedVarnodes(SymbolEntry *entry,vector &res) const; ///< Find Varnodes that map to the given SymbolEntry void buildDynamicSymbol(Varnode *vn); ///< Build a \e dynamic Symbol associated with the given Varnode + bool testForReturnAddress(Varnode *vn); ///< Test if the given Varnode is (derived from) the return address bool attemptDynamicMapping(SymbolEntry *entry,DynamicHash &dhash); bool attemptDynamicMappingLate(SymbolEntry *entry,DynamicHash &dhash); Merge &getMerge(void) { return covermerge; } ///< Get the Merge object for \b this function @@ -519,7 +520,7 @@ public: JumpTable *linkJumpTable(PcodeOp *op); ///< Link jump-table with a given BRANCHIND JumpTable *findJumpTable(const PcodeOp *op) const; ///< Find a jump-table associated with a given BRANCHIND JumpTable *installJumpTable(const Address &addr); ///< Install a new jump-table for the given Address - JumpTable *recoverJumpTable(Funcdata &partial,PcodeOp *op,FlowInfo *flow,int4 &failuremode); + JumpTable *recoverJumpTable(Funcdata &partial,PcodeOp *op,FlowInfo *flow,JumpTable::RecoveryMode &mode); bool earlyJumpTableFail(PcodeOp *op); ///< Try to determine, early, if jump-table analysis will fail int4 numJumpTables(void) const { return jumpvec.size(); } ///< Get the number of jump-tables for \b this function JumpTable *getJumpTable(int4 i) { return jumpvec[i]; } ///< Get the i-th jump-table diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc index 5b90c1d97e..80e40074ef 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_block.cc @@ -477,18 +477,15 @@ JumpTable *Funcdata::installJumpTable(const Address &addr) /// A partial function (copy) is built using the flow info. Simplification is performed on the /// partial function (using the "jumptable" strategy), then destination addresses of the /// branch are recovered by examining the simplified data-flow. The jump-table object -/// is populated with the recovered addresses. An integer value is returned: -/// - 0 = success -/// - 1 = normal could-not-recover failure -/// - 2 = \b likely \b thunk failure -/// - 3 = no legal flows to the BRANCHIND failure +/// is populated with the recovered addresses. A code indicating success or the type of +/// failure is returned. /// /// \param partial is a function object for caching analysis /// \param jt is the jump-table object to populate /// \param op is the BRANCHIND p-code op to analyze /// \param flow is the existing flow information /// \return the success/failure code -int4 Funcdata::stageJumpTable(Funcdata &partial,JumpTable *jt,PcodeOp *op,FlowInfo *flow) +JumpTable::RecoveryMode Funcdata::stageJumpTable(Funcdata &partial,JumpTable *jt,PcodeOp *op,FlowInfo *flow) { if (!partial.isJumptableRecoveryOn()) { @@ -514,7 +511,7 @@ int4 Funcdata::stageJumpTable(Funcdata &partial,JumpTable *jt,PcodeOp *op,FlowIn catch(LowlevelError &err) { glb->allacts.setCurrent(oldactname); warning(err.explain,op->getAddr()); - return 1; + return JumpTable::fail_normal; } } PcodeOp *partop = partial.findOp(op->getSeqNum()); @@ -522,7 +519,11 @@ int4 Funcdata::stageJumpTable(Funcdata &partial,JumpTable *jt,PcodeOp *op,FlowIn if (partop==(PcodeOp *)0 || partop->code() != CPUI_BRANCHIND || partop->getAddr() != op->getAddr()) throw LowlevelError("Error recovering jumptable: Bad partial clone"); if (partop->isDead()) // Indirectop we were trying to recover was eliminated as dead code (unreachable) - return 0; // Return jumptable as + return JumpTable::success; // Return jumptable as + + // Test if the branch target is copied from the return address. + if (testForReturnAddress(partop->getIn(0))) + return JumpTable::fail_return; // Return special failure code. Switch would not recover anyway. try { jt->setLoadCollect(flow->doesJumpRecord()); @@ -533,16 +534,16 @@ int4 Funcdata::stageJumpTable(Funcdata &partial,JumpTable *jt,PcodeOp *op,FlowIn jt->recoverAddresses(&partial); // Analyze partial to recover jumptable addresses } catch(JumptableNotReachableError &err) { // Thrown by recoverAddresses - return 3; + return JumpTable::fail_noflow; } catch(JumptableThunkError &err) { // Thrown by recoverAddresses - return 2; + return JumpTable::fail_thunk; } catch(LowlevelError &err) { warning(err.explain,op->getAddr()); - return 1; + return JumpTable::fail_normal; } - return 0; + return JumpTable::success; } /// Backtrack from the BRANCHIND, looking for ops that might affect the destination. @@ -633,22 +634,22 @@ bool Funcdata::earlyJumpTableFail(PcodeOp *op) /// \param partial is the Funcdata copy to perform analysis on if necessary /// \param op is the given BRANCHIND PcodeOp /// \param flow is current flow information for \b this function -/// \param failuremode will hold the final success/failure code (0=success) +/// \param mode will hold the final success/failure code /// \return the recovered JumpTable or NULL if there was no success -JumpTable *Funcdata::recoverJumpTable(Funcdata &partial,PcodeOp *op,FlowInfo *flow,int4 &failuremode) +JumpTable *Funcdata::recoverJumpTable(Funcdata &partial,PcodeOp *op,FlowInfo *flow,JumpTable::RecoveryMode &mode) { JumpTable *jt; - failuremode = 0; + mode = JumpTable::success; jt = linkJumpTable(op); // Search for pre-existing jumptable if (jt != (JumpTable *)0) { if (!jt->isOverride()) { if (jt->getStage() != 1) return jt; // Previously calculated jumptable (NOT an override and NOT incomplete) } - failuremode = stageJumpTable(partial,jt,op,flow); // Recover based on override information - if (failuremode != 0) + mode = stageJumpTable(partial,jt,op,flow); // Recover based on override information + if (mode != JumpTable::success) return (JumpTable *)0; jt->setIndirectOp(op); // Relink table back to original op return jt; @@ -659,8 +660,8 @@ JumpTable *Funcdata::recoverJumpTable(Funcdata &partial,PcodeOp *op,FlowInfo *fl if (earlyJumpTableFail(op)) return (JumpTable *)0; JumpTable trialjt(glb); - failuremode = stageJumpTable(partial,&trialjt,op,flow); - if (failuremode != 0) + mode = stageJumpTable(partial,&trialjt,op,flow); + if (mode != JumpTable::success) return (JumpTable *)0; // if (trialjt.is_twostage()) // warning("Jumptable maybe incomplete. Second-stage recovery not implemented",trialjt.Opaddress()); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc index 37500c5104..fc094ee969 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_varnode.cc @@ -1282,6 +1282,40 @@ bool Funcdata::attemptDynamicMappingLate(SymbolEntry *entry,DynamicHash &dhash) return true; } +/// Follow the Varnode back to see if it comes from the return address for \b this function. +/// If so, return \b true. The return address can flow through COPY, INDIRECT, and AND operations. +/// If there are any other operations in the flow path, or if a standard storage location for the +/// return address was not defined, return \b false. +/// \param vn is the given Varnode to trace +/// \return \b true if flow is from the return address +bool Funcdata::testForReturnAddress(Varnode *vn) + +{ + VarnodeData &retaddr(glb->defaultReturnAddr); + if (retaddr.space == (AddrSpace *)0) + return false; // No standard storage location to compare to + while(vn->isWritten()) { + PcodeOp *op = vn->getDef(); + OpCode opc = op->code(); + if (opc == CPUI_INDIRECT || opc == CPUI_COPY) { + vn = op->getIn(0); + } + else if (opc == CPUI_INT_AND) { + // We only want to allow "alignment" style masking + if (!op->getIn(1)->isConstant()) + return false; + vn = op->getIn(0); + } + else + return false; + } + if (vn->getSpace() != retaddr.space || vn->getOffset() != retaddr.offset || vn->getSize() != retaddr.size) + return false; + if (!vn->isInput()) + return false; + return true; +} + /// \brief Replace all read references to the first Varnode with a second Varnode /// /// \param vn is the first Varnode (being replaced) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh index fbe018c848..f550d24325 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/jumptable.hh @@ -525,6 +525,16 @@ public: /// It knows how to map from specific switch variable values to the destination /// \e case block and how to label the value. class JumpTable { +public: + /// \brief Recovery status for a specific JumpTable + enum RecoveryMode { + 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 + }; +private: /// \brief An address table index and its corresponding out-edge struct IndexPair { int4 blockPosition; ///< Out-edge index for the basic-block diff --git a/Ghidra/Features/Decompiler/src/decompile/datatests/switchreturn.xml b/Ghidra/Features/Decompiler/src/decompile/datatests/switchreturn.xml new file mode 100644 index 0000000000..f81f406cab --- /dev/null +++ b/Ghidra/Features/Decompiler/src/decompile/datatests/switchreturn.xml @@ -0,0 +1,26 @@ + + + + +fa630b620018084000650b9700ef0065 + + + + + +Treating indirect jump as return +Could not recover +\(\*UNRECOVERED_JUMPTABLE\) +