From d424b6ff9bc06d9d954ac49cf83098fff04d9092 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Tue, 31 Mar 2026 22:24:22 +0000 Subject: [PATCH] GP-6945 Count bad data exceptions --- .../src/decompile/cpp/architecture.cc | 1 + .../src/decompile/cpp/architecture.hh | 1 + .../Decompiler/src/decompile/cpp/flow.cc | 18 +++++++++- .../Decompiler/src/decompile/cpp/flow.hh | 13 +++++--- .../src/decompile/cpp/funcdata_op.cc | 2 +- .../Decompiler/src/decompile/cpp/marshal.cc | 2 +- .../Decompiler/src/decompile/cpp/options.cc | 33 +++++++++++++++++-- .../Decompiler/src/decompile/cpp/options.hh | 11 +++++-- .../app/decompiler/DecompileOptions.java | 22 +++++++++++++ .../ghidra/program/model/pcode/ElementId.java | 3 +- 10 files changed, 93 insertions(+), 13 deletions(-) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc index 97c33cd9f8..192807a66a 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.cc @@ -1421,6 +1421,7 @@ void Architecture::resetDefaultsInternal(void) max_basetype_size = 10; // Needs to be 8 or bigger flowoptions = FlowInfo::error_toomanyinstructions; max_instructions = 100000; + max_baddata = 4; infer_pointers = true; analyze_for_loops = true; readonlypropagate = false; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh index ebd0e84343..6d20382482 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/architecture.hh @@ -183,6 +183,7 @@ public: int4 funcptr_align; ///< How many bits of alignment a function ptr has uint4 flowoptions; ///< options passed to flow following engine uint4 max_instructions; ///< Maximum instructions that can be processed in one function + uint4 max_baddata; ///< Maximum number of bad instructions that one function can encounter int4 alias_block_level; ///< Aliases blocked by 0=none, 1=struct, 2=array, 3=all uint4 split_datatype_config; ///< Toggle for data-types splitting: Bit 0=structs, 1=arrays, 2=pointers vector extra_pool_rules; ///< Extra rules that go in the main pool (cpu specific, experimental) diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc index 0add3cc173..37ee96b763 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.cc @@ -38,6 +38,7 @@ FlowInfo::FlowInfo(Funcdata &d,PcodeOpBank &o,BlockGraph &b,vector *)0; insn_count = 0; insn_max = ~((uint4)0); + baddata_count = 0; flowoverride_present = data.getOverride().hasFlowOverride(); } @@ -72,6 +73,7 @@ FlowInfo::FlowInfo(Funcdata &d,PcodeOpBank &o,BlockGraph &b,vector *)0; insn_count = op2->insn_count; insn_max = op2->insn_max; + baddata_count = op2->baddata_count; flowoverride_present = data.getOverride().hasFlowOverride(); } @@ -80,6 +82,7 @@ void FlowInfo::clearProperties(void) { flags &= ~((uint4)(unimplemented_present|baddata_present|outofbounds_present)); insn_count = 0; + baddata_count = 0; } /// For efficiency, this method assumes the given op can actually fall-thru. @@ -442,9 +445,10 @@ bool FlowInfo::processInstruction(const Address &curaddr,bool &startbasic) } } catch(BadDataError &err) { - if ((flags & error_unimplemented)!=0) + if ((flags & error_baddata)!=0) throw err; // rethrow else { + countBadData(err.explain); // Add infinite loop instruction step = 1; // Pretend size 1 artificialHalt(curaddr,PcodeOp::badinstruction); @@ -539,6 +543,17 @@ void FlowInfo::handleOutOfBounds(const Address &fromaddr,const Address &toaddr) } } +/// If the count exceeds the maximum allowable, an exception is thrown. +/// \param errMsg is the error message associated with the current bad data +void FlowInfo::countBadData(const string &errMsg) + +{ + if (baddata_count >= glb->max_baddata) { + throw BadDataError("Bad instruction count exceeded:\n ...\n "+ errMsg); + } + baddata_count += 1; +} + /// The address at the top stack that still needs processing is popped. /// P-code is generated for instructions starting at this address until /// one no longer has fall-thru flow (or some other error occurs). @@ -622,6 +637,7 @@ void FlowInfo::reinterpreted(const Address &addr) if ((flags & error_reinterpreted)!=0) throw LowlevelError(s.str()); + countBadData(s.str()); if ((flags & reinterpreted_present)==0) { flags |= reinterpreted_present; data.warningHeader(s.str()); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.hh index 360a864587..572abd8c57 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/flow.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/flow.hh @@ -63,11 +63,12 @@ public: error_unimplemented = 8, ///< Throw an exception for flow into unimplemented instructions error_reinterpreted = 0x10, ///< Throw an exception for flow into previously encountered data at a difference \e cut error_toomanyinstructions = 0x20, ///< Throw an exception if too many instructions are encountered - unimplemented_present = 0x40, ///< Indicate we have encountered unimplemented instructions - baddata_present = 0x80, ///< Indicate we have encountered flow into unaccessible data - outofbounds_present = 0x100, ///< Indicate we have encountered flow out of the specified range - reinterpreted_present = 0x200, ///< Indicate we have encountered reinterpreted data - toomanyinstructions_present = 0x400, ///< Indicate the maximum instruction threshold was reached + error_baddata = 0x40, ///< Throw an exception if an instruction cannot be decoded + unimplemented_present = 0x80, ///< Indicate we have encountered unimplemented instructions + baddata_present = 0x100, ///< Indicate we have encountered flow into unaccessible data + outofbounds_present = 0x200, ///< Indicate we have encountered flow out of the specified range + reinterpreted_present = 0x400, ///< Indicate we have encountered reinterpreted data + toomanyinstructions_present = 0x800, ///< Indicate the maximum instruction threshold was reached possible_unreachable = 0x1000, ///< Indicate a CALL was converted to a BRANCH and some code may be unreachable flow_forinline = 0x2000, ///< Indicate flow is being generated to in-line (a function) record_jumploads = 0x4000 ///< Indicate that any jump table recovery should record the table structure @@ -99,6 +100,7 @@ private: Address maxaddr; ///< End of actual function range bool flowoverride_present; ///< Does the function have registered flow override instructions uint4 flags; ///< Boolean options for flow following + uint4 baddata_count; ///< Number of instructions that could not be disassembled Funcdata *inline_head; ///< First function in the in-lining chain set
*inline_recursion; ///< Active list of addresses for function that are in-lined set
inline_base; ///< Storage for addresses of functions that are in-lined @@ -122,6 +124,7 @@ private: void connectBasic(void); ///< Generate edges between basic blocks bool setFallthruBound(Address &bound); ///< Find end of the next unprocessed region void handleOutOfBounds(const Address &fromaddr,const Address &toaddr); + void countBadData(const string &errMsg); ///< Increment bad data counter PcodeOp *artificialHalt(const Address &addr,uint4 flag); ///< Create an artificial halt p-code op void reinterpreted(const Address &addr); ///< Generate warning message or exception for a \e reinterpreted address bool checkForFlowModification(FuncCallSpecs &fspecs); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_op.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_op.cc index 1939dc5aab..b8bb92d492 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_op.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/funcdata_op.cc @@ -862,7 +862,7 @@ int4 Funcdata::inlineFlow(Funcdata *inlinefd,FlowInfo &flow,PcodeOp *callop) Address eaddr(baseaddr.getSpace(),~((uintb)0)); inlineflow.setRange(baddr,eaddr); inlineflow.setFlags(FlowInfo::error_outofbounds|FlowInfo::error_unimplemented| - FlowInfo::error_reinterpreted|FlowInfo::flow_forinline); + FlowInfo::error_baddata|FlowInfo::error_reinterpreted|FlowInfo::flow_forinline); inlineflow.forwardRecursion(flow); inlineflow.generateOps(); diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc index 66b1562328..73fb5701a3 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc @@ -1272,6 +1272,6 @@ ElementId ELEM_VAL = ElementId("val",8); ElementId ELEM_VALUE = ElementId("value",9); ElementId ELEM_VOID = ElementId("void",10); -ElementId ELEM_UNKNOWN = ElementId("XMLunknown",290); // Number serves as next open index +ElementId ELEM_UNKNOWN = ElementId("XMLunknown",291); // Number serves as next open index } // End namespace ghidra diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc index e0778433e1..ea9f8f5c96 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/options.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/options.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. @@ -33,6 +33,7 @@ ElementId ELEM_DEFAULTPROTOTYPE = ElementId("defaultprototype",183); ElementId ELEM_ERRORREINTERPRETED = ElementId("errorreinterpreted",184); ElementId ELEM_ERRORTOOMANYINSTRUCTIONS = ElementId("errortoomanyinstructions",185); ElementId ELEM_ERRORUNIMPLEMENTED = ElementId("errorunimplemented",186); +ElementId ELEM_BADDATACOUNT = ElementId("baddatacount",290); ElementId ELEM_EXTRAPOP = ElementId("extrapop",187); ElementId ELEM_IGNOREUNIMPLEMENTED = ElementId("ignoreunimplemented",188); ElementId ELEM_INDENTINCREMENT = ElementId("indentincrement",189); @@ -100,6 +101,7 @@ OptionDatabase::OptionDatabase(Architecture *g) registerOption(new OptionErrorUnimplemented()); registerOption(new OptionErrorReinterpreted()); registerOption(new OptionErrorTooManyInstructions()); + registerOption(new OptionBadDataCount()); registerOption(new OptionDefaultPrototype()); registerOption(new OptionInferConstPtr()); registerOption(new OptionForLoops()); @@ -782,6 +784,33 @@ string OptionErrorTooManyInstructions::apply(Architecture *glb,const string &p1, return res; } +/// \class OptionErrorBadData +/// \brief Toggle whether disassembling bytes that can't be decoded or at a non-existent address is considered a fatal error. +/// +/// If the first parameter is "on" then any BadDataError encountered while following flow will cause a fatal error. +/// Otherwise, artificial halts are generated at addresses causing the BadDataError, allowing analysis to continue. +string OptionBadDataCount::apply(Architecture *glb,const string &p1,const string &p2,const string &p3) const + +{ + uint4 newMax; + string res; + if (p1.size() == 0) { + newMax = 0xffffffff; + res = "No limit on instructions that cannot be disassembled"; + } + else { + newMax = 0xdeadbeef; + istringstream s1(p1); + s1.unsetf(ios::dec | ios::hex | ios::oct); // Let user specify base + s1 >> newMax; + if (newMax == 0xdeadbeef) + throw ParseError("Bad baddatacount parameter"); + res = "Maximum instructions that cannot be disassembled set to " + p1; + } + glb->max_baddata = newMax; + return res; +} + /// \class OptionProtoEval /// \brief Set the prototype model to use when evaluating the parameters of the \e current function /// diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/options.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/options.hh index 7a7f713b84..91fa7719cb 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/options.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/options.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. @@ -39,6 +39,7 @@ extern ElementId ELEM_DEFAULTPROTOTYPE; ///< Marshaling element \ extern ElementId ELEM_ERRORTOOMANYINSTRUCTIONS; ///< Marshaling element \ extern ElementId ELEM_ERRORUNIMPLEMENTED; ///< Marshaling element \ +extern ElementId ELEM_BADDATACOUNT; ///< Marshaling element \ extern ElementId ELEM_EXTRAPOP; ///< Marshaling element \ extern ElementId ELEM_IGNOREUNIMPLEMENTED; ///< Marshaling element \ extern ElementId ELEM_INDENTINCREMENT; ///< Marshaling element \ @@ -283,6 +284,12 @@ public: virtual string apply(Architecture *glb,const string &p1,const string &p2,const string &p3) const; }; +class OptionBadDataCount : public ArchOption { +public: + OptionBadDataCount(void) { name = "baddatacount"; } ///< Constructor + virtual string apply(Architecture *glb,const string &p1,const string &p2,const string &p3) const; +}; + class OptionProtoEval : public ArchOption { public: OptionProtoEval(void) { name = "protoeval"; } ///< Constructor diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java index 120fe0781f..acba13f92a 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java @@ -477,6 +477,7 @@ public class DecompileOptions { private final static String MAX_INSTRUCTIONS = "Max Instructions per Function"; private final static String MAX_JUMPTABLE_ENTRIES = "Max Entries per Jumptable"; private final static Boolean LINE_NUMBER_DEF = Boolean.TRUE; + private final static int SUGGESTED_MAX_BADDATA = 4; // Must match Architecture::resetDefaultsInternal private boolean displayLineNumbers; private int decompileTimeoutSeconds; @@ -485,6 +486,8 @@ public class DecompileOptions { private int maxJumpTableEntries; private int cachedResultsSize; + private int maxBadData; // Maximum number of bad data exceptions caught before aborting + private DecompilerLanguage displayLanguage; // Output language displayed by the decompiler private NameTransformer nameTransformer; // Transformer applied to data-type/function names @@ -533,6 +536,7 @@ public class DecompileOptions { maxIntructionsPer = SUGGESTED_MAX_INSTRUCTIONS; maxJumpTableEntries = SUGGESTED_MAX_JUMPTABLE_ENTRIES; cachedResultsSize = SUGGESTED_CACHED_RESULTS_SIZE; + maxBadData = SUGGESTED_MAX_BADDATA; nameTransformer = null; } @@ -981,6 +985,9 @@ public class DecompileOptions { if (maxJumpTableEntries != SUGGESTED_MAX_JUMPTABLE_ENTRIES) { appendOption(encoder, ELEM_JUMPTABLEMAX, Integer.toString(maxJumpTableEntries), "", ""); } + if (maxBadData != SUGGESTED_MAX_BADDATA) { + appendOption(encoder, ELEM_BADDATACOUNT, Integer.toString(maxBadData), "", ""); + } appendOption(encoder, ELEM_PROTOEVAL, protoEvalModel, "", ""); encoder.closeElement(ELEM_OPTIONSLIST); } @@ -1325,6 +1332,13 @@ public class DecompileOptions { return displayLanguage; } + /** + * @return the maximum number of times the decompiler will catch a "bad data" exception + */ + public int getMaxBadData() { + return maxBadData; + } + /** * Retrieve the transformer being applied to data-type, function, and namespace names. * If no transform is being applied, a pass-through object is returned. @@ -1377,6 +1391,14 @@ public class DecompileOptions { this.noCastPrint = noCastPrint; } + /** + * Set the maximum number of times the decompiler will catch a "bad data" exception + * @param val is the maximum value + */ + public void setMaxBadData(int val) { + maxBadData = val; + } + /** * Set the source programming language that decompiler output should be rendered in. * @param val is the source language diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/ElementId.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/ElementId.java index 287e533640..b308889907 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/ElementId.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/ElementId.java @@ -290,6 +290,7 @@ public record ElementId(String name, int id) { new ElementId("errortoomanyinstructions", 185); public static final ElementId ELEM_ERRORUNIMPLEMENTED = new ElementId("errorunimplemented", 186); + public static final ElementId ELEM_BADDATACOUNT = new ElementId("baddatacount", 290); public static final ElementId ELEM_EXTRAPOP = new ElementId("extrapop", 187); public static final ElementId ELEM_IGNOREUNIMPLEMENTED = new ElementId("ignoreunimplemented", 188); @@ -460,5 +461,5 @@ public record ElementId(String name, int id) { public static final ElementId ELEM_EXTRA_STACK = new ElementId("extra_stack", 287); public static final ElementId ELEM_CONSUME_REMAINING = new ElementId("consume_remaining", 288); - public static final ElementId ELEM_UNKNOWN = new ElementId("XMLunknown", 290); + public static final ElementId ELEM_UNKNOWN = new ElementId("XMLunknown", 291); }