From c674e1f2eccbb32b51742784955f613d7b712010 Mon Sep 17 00:00:00 2001 From: caheckman <48068198+caheckman@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:13:53 +0000 Subject: [PATCH] GP-4031 x86 System V ABI --- .../Decompiler/src/decompile/cpp/fspec.cc | 157 ++++- .../Decompiler/src/decompile/cpp/fspec.hh | 43 +- .../Decompiler/src/decompile/cpp/heritage.cc | 3 +- .../Decompiler/src/decompile/cpp/marshal.cc | 2 +- .../src/decompile/cpp/modelrules.cc | 615 ++++++++++++++++-- .../src/decompile/cpp/modelrules.hh | 103 ++- .../Decompiler/src/decompile/cpp/type.cc | 3 + .../src/decompile/unittests/testparamstore.cc | 58 +- .../data/languages/compiler_spec.rxg | 16 + .../model/lang/protorules/AssignAction.java | 3 + .../model/lang/protorules/DatatypeFilter.java | 76 +-- .../lang/protorules/HomogeneousAggregate.java | 12 +- .../lang/protorules/MultiMemberAssign.java | 7 +- .../lang/protorules/MultiSlotDualAssign.java | 317 +++++++++ .../lang/protorules/PrimitiveExtractor.java | 330 ++++++++++ .../ghidra/program/model/pcode/ElementId.java | 3 +- .../model/pcode/PcodeDataTypeManager.java | 21 +- .../x86/data/languages/x86-64-gcc.cspec | 25 +- .../model/lang/AbstractProtoModelTest.java | 2 + .../model/lang/X64PrototypeModelTest.java | 57 ++ 20 files changed, 1668 insertions(+), 185 deletions(-) create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/MultiSlotDualAssign.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/PrimitiveExtractor.java diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc index dc96f0c19c..623bdb7702 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc @@ -69,6 +69,24 @@ const ParamEntry *ParamEntry::findEntryByStorage(const list &entryLi return (const ParamEntry *)0; } +/// Check previous ParamEntry, if it exists, and compare storage class. +/// If it is different, this is the first, and its flag gets set. +/// \param curList is the list of previous ParamEntry +void ParamEntry::resolveFirst(list &curList) + +{ + list::const_iterator iter = curList.end(); + --iter; + if (iter == curList.begin()) { + flags |= first_storage; + return; + } + --iter; + if (type != (*iter).type) { + flags |= first_storage; + } +} + /// If the ParamEntry is initialized with a \e join address, cache the join record and /// adjust the group and groupsize based on the ParamEntrys being overlapped /// \param curList is the current list of ParamEntry @@ -536,6 +554,7 @@ void ParamEntry::decode(Decoder &decoder,bool normalstack,bool grouped,list::const_iterator ParamListStandard::getFirstIter(type_class type) const + +{ + list::const_iterator iter; + for(iter=entry.begin();iter!=entry.end();++iter) { + const ParamEntry &curEntry( *iter ); + if (curEntry.getType() == type && curEntry.getAllGroups().size() == 1) + return iter; + } + return iter; +} + +/// If the stack entry is not present, null is returned +/// \return the stack entry or null +const ParamEntry *ParamListStandard::getStackEntry(void) const + +{ + list::const_iterator iter = entry.end(); + if (iter != entry.begin()) { + --iter; // Stack entry necessarily must be the last entry + const ParamEntry &curEntry( *iter ); + if (!curEntry.isExclusion() && curEntry.getSpace()->getType() == IPTR_SPACEBASE) { + return &(*iter); + } + } + return (const ParamEntry *)0; +} + /// Find the (first) entry containing the given memory range /// \param loc is the starting address of the range /// \param size is the number of bytes in the range +/// \param just is \b true if the search enforces a justified match /// \return the pointer to the matching ParamEntry or null if no match exists -const ParamEntry *ParamListStandard::findEntry(const Address &loc,int4 size) const +const ParamEntry *ParamListStandard::findEntry(const Address &loc,int4 size,bool just) const { int4 index = loc.getSpace()->getIndex(); @@ -602,7 +654,7 @@ const ParamEntry *ParamListStandard::findEntry(const Address &loc,int4 size) con const ParamEntry *testEntry = (*res.first).getParamEntry(); ++res.first; if (testEntry->getMinSize() > size) continue; - if (testEntry->justifiedContain(loc,size)==0) // Make sure the range is properly justified in entry + if (!just || testEntry->justifiedContain(loc,size)==0) // Make sure the range is properly justified in entry return testEntry; } return (const ParamEntry *)0; @@ -779,7 +831,7 @@ void ParamListStandard::buildTrialMap(ParamActive *active) const for(int4 i=0;igetNumTrials();++i) { ParamTrial ¶mtrial(active->getTrial(i)); - const ParamEntry *entrySlot = findEntry(paramtrial.getAddress(),paramtrial.getSize()); + const ParamEntry *entrySlot = findEntry(paramtrial.getAddress(),paramtrial.getSize(),true); // Note: if a trial is "definitely not used" but there is a matching entry, // we still include it in the map if (entrySlot == (const ParamEntry *)0) @@ -1241,9 +1293,9 @@ void ParamListStandard::fillinMap(ParamActive *active) const bool ParamListStandard::checkJoin(const Address &hiaddr,int4 hisize,const Address &loaddr,int4 losize) const { - const ParamEntry *entryHi = findEntry(hiaddr,hisize); + const ParamEntry *entryHi = findEntry(hiaddr,hisize,true); if (entryHi == (const ParamEntry *)0) return false; - const ParamEntry *entryLo = findEntry(loaddr,losize); + const ParamEntry *entryLo = findEntry(loaddr,losize,true); if (entryLo == (const ParamEntry *)0) return false; if (entryHi->getGroup() == entryLo->getGroup()) { if (entryHi->isExclusion()||entryLo->isExclusion()) return false; @@ -1270,9 +1322,9 @@ bool ParamListStandard::checkSplit(const Address &loc,int4 size,int4 splitpoint) { Address loc2 = loc + splitpoint; int4 size2 = size - splitpoint; - const ParamEntry *entryNum = findEntry(loc,splitpoint); + const ParamEntry *entryNum = findEntry(loc,splitpoint,true); if (entryNum == (const ParamEntry *)0) return false; - entryNum = findEntry(loc2,size2); + entryNum = findEntry(loc2,size2,true); if (entryNum == (const ParamEntry *)0) return false; return true; } @@ -1280,13 +1332,13 @@ bool ParamListStandard::checkSplit(const Address &loc,int4 size,int4 splitpoint) bool ParamListStandard::possibleParam(const Address &loc,int4 size) const { - return ((const ParamEntry *)0 != findEntry(loc,size)); + return ((const ParamEntry *)0 != findEntry(loc,size,true)); } bool ParamListStandard::possibleParamWithSlot(const Address &loc,int4 size,int4 &slot,int4 &slotsize) const { - const ParamEntry *entryNum = findEntry(loc,size); + const ParamEntry *entryNum = findEntry(loc,size,true); if (entryNum == (const ParamEntry *)0) return false; slot = entryNum->getSlot(loc,0); if (entryNum->isExclusion()) { @@ -1473,7 +1525,7 @@ void ParamListRegister::fillinMap(ParamActive *active) const // Mark anything active as used for(int4 i=0;igetNumTrials();++i) { ParamTrial ¶mtrial(active->getTrial(i)); - const ParamEntry *entrySlot = findEntry(paramtrial.getAddress(),paramtrial.getSize()); + const ParamEntry *entrySlot = findEntry(paramtrial.getAddress(),paramtrial.getSize(),true); if (entrySlot == (const ParamEntry *)0) // There may be no matching entry (if the model was recovered late) paramtrial.markNoUse(); else { @@ -1535,10 +1587,22 @@ void ParamListStandardOut::assignMap(const PrototypePieces &proto,TypeFactory &t } } -void ParamListStandardOut::fillinMap(ParamActive *active) const +void ParamListStandardOut::initialize(void) + +{ + useFillinFallback = true; + list::const_iterator iter; + for(iter=modelRules.begin();iter!=modelRules.end();++iter) { + if ((*iter).canAffectFillinOutput()) { + useFillinFallback = false; + break; + } + } +} + +void ParamListStandardOut::fillinMapFallback(ParamActive *active,bool firstOnly) const { - if (active->getNumTrials() == 0) return; // No trials to check const ParamEntry *bestentry = (const ParamEntry *)0; int4 bestcover = 0; type_class bestclass = TYPECLASS_PTR; @@ -1547,6 +1611,9 @@ void ParamListStandardOut::fillinMap(ParamActive *active) const list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { const ParamEntry *curentry = &(*iter); + if (firstOnly && !curentry->isFirstInClass() && curentry->isExclusion() && curentry->getAllGroups().size() == 1) { + continue; // This is not the first entry in the storage class + } bool putativematch = false; for(int4 j=0;jgetNumTrials();++j) { // Evaluate all trials in terms of current ParamEntry ParamTrial ¶mtrial(active->getTrial(j)); @@ -1616,6 +1683,50 @@ void ParamListStandardOut::fillinMap(ParamActive *active) const } } +void ParamListStandardOut::fillinMap(ParamActive *active) const + +{ + if (active->getNumTrials() == 0) return; // No trials to check + if (useFillinFallback) { + fillinMapFallback(active,false); + return; + } + for(int4 i=0;igetNumTrials();++i) { + ParamTrial &trial(active->getTrial(i)); + trial.setEntry((const ParamEntry *)0, 0); + if (!trial.isActive()) continue; + const ParamEntry *entry = findEntry(trial.getAddress(),trial.getSize(),false); + if (entry == (const ParamEntry *)0) { + trial.markNoUse(); + continue; + } + int4 res = entry->justifiedContain(trial.getAddress(),trial.getSize()); + if ((trial.isRemFormed() || trial.isIndCreateFormed()) && !entry->isFirstInClass()) { + trial.markNoUse(); + continue; + } + trial.setEntry(entry,res); + } + active->sortTrials(); + list::const_iterator iter; + for(iter=modelRules.begin();iter!=modelRules.end();++iter) { + if ((*iter).fillinOutputMap(active)) { + for(int4 i=0;igetNumTrials();++i) { + ParamTrial &trial(active->getTrial(i)); + if (trial.isActive()) { + trial.markUsed(); + } + else { + trial.markNoUse(); + trial.setEntry((const ParamEntry *)0,0); + } + } + return; + } + } + fillinMapFallback(active, true); +} + bool ParamListStandardOut::possibleParam(const Address &loc,int4 size) const { @@ -1627,6 +1738,13 @@ bool ParamListStandardOut::possibleParam(const Address &loc,int4 size) const return false; } +void ParamListStandardOut::decode(Decoder &decoder,vector &effectlist,bool normalstack) + +{ + ParamListStandard::decode(decoder,effectlist,normalstack); + initialize(); +} + ParamList *ParamListStandardOut::clone(void) const { @@ -3615,6 +3733,8 @@ void FuncProto::setModel(ProtoModel *m) flags |= has_thisptr; if (m->isConstructor()) flags |= is_constructor; + if (m->isAutoKillByCall()) + flags |= auto_killbycall; model = m; } else { @@ -4389,6 +4509,19 @@ void FuncProto::printRaw(const string &funcname,ostream &s) const s << ") extrapop=" << dec << extrapop; } +/// This assumes the storage location has already been determined to be contained +/// in standard return value location. +/// \return \b true if the location should be considered killed by call +bool FuncProto::isAutoKillByCall(void) const + +{ + if ((flags & auto_killbycall)!=0) + return true; // The ProtoModel always does killbycall + if (isOutputLocked()) + return true; // A locked output location is killbycall by definition + return false; +} + /// \brief Encode \b this to a stream as a \ element. /// /// Save everything under the control of this prototype, which diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh index b1770c456b..d3e70953dc 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.hh @@ -88,12 +88,13 @@ public: smallsize_zext = 4, ///< Assume values that are below the max \b size are zero extended into this container smallsize_sext = 8, ///< Assume values that are below the max \b size are sign extended into this container // is_big_endian = 16, ///< Set if this value should be treated as big endian - smallsize_inttype = 32, ///< Assume values that are below the max \b size are sign OR zero extended based on integer type - smallsize_floatext = 64, ///< Assume values smaller than max \b size are floating-point extended to full size - extracheck_high = 128, ///< Perform extra checks during parameter recovery on most sig portion of the double - extracheck_low = 256, ///< Perform extra checks during parameter recovery on least sig portion of the double - is_grouped = 512, ///< This entry is grouped with other entries - overlapping = 0x100 ///< Overlaps an earlier entry (and doesn't consume additional resource slots) + smallsize_inttype = 0x20, ///< Assume values that are below the max \b size are sign OR zero extended based on integer type + smallsize_floatext = 0x40, ///< Assume values smaller than max \b size are floating-point extended to full size + extracheck_high = 0x80, ///< Perform extra checks during parameter recovery on most sig portion of the double + extracheck_low = 0x100, ///< Perform extra checks during parameter recovery on least sig portion of the double + is_grouped = 0x200, ///< This entry is grouped with other entries + overlapping = 0x400, ///< Overlaps an earlier entry (and doesn't consume additional resource slots) + first_storage = 0x800 ///< Entry is first in its storage class }; enum { no_containment, ///< Range neither contains nor is contained by a ParamEntry @@ -113,6 +114,7 @@ private: int4 numslots; ///< (Maximum) number of slots that can store separate parameters JoinRecord *joinrec; ///< Non-null if this is logical variable from joined pieces static const ParamEntry *findEntryByStorage(const list &entryList,const VarnodeData &vn); + void resolveFirst(list &curList); ///< Mark if \b this is the first ParamEntry in its storage class void resolveJoin(list &curList); ///< Make adjustments for a \e join ParamEntry void resolveOverlap(list &curList); ///< Make adjustments for ParamEntry that overlaps others @@ -132,6 +134,7 @@ public: bool isReverseStack(void) const { return ((flags & reverse_stack)!=0); } ///< Return \b true if parameters are allocated in reverse order bool isGrouped(void) const { return ((flags & is_grouped)!=0); } ///< Return \b true if \b this is grouped with other entries bool isOverlap(void) const { return ((flags & overlapping)!=0); } ///< Return \b true if \b this overlaps another entry + bool isFirstInClass(void) const { return ((flags & first_storage)!=0); } ///< Return \b true if \b this is the first entry in the storage class bool subsumesDefinition(const ParamEntry &op2) const; ///< Does \b this subsume the definition of the given ParamEntry bool containedBy(const Address &addr,int4 sz) const; ///< Is this entry contained by the given range bool intersects(const Address &addr,int4 sz) const; ///< Does \b this intersect the given range in some way @@ -554,6 +557,11 @@ public: /// \return the maximum number of passes across all parameters in \b this model virtual int4 getMaxDelay(void) const=0; + /// \brief Return \b true if ParamEntry locations should automatically be considered killed by call + /// + /// \return \b true if automatically assume killbycall + virtual bool isAutoKillByCall(void) const=0; + /// \brief Restore the model from an \ or \ element in the stream /// /// \param decoder is the stream decoder @@ -583,7 +591,7 @@ protected: vector resolverMap; ///< Map from space id to resolver list modelRules; ///< Rules to apply when assigning addresses AddrSpace *spacebase; ///< Address space containing relative offset parameters - const ParamEntry *findEntry(const Address &loc,int4 size) const; ///< Given storage location find matching ParamEntry + const ParamEntry *findEntry(const Address &loc,int4 size,bool just) const; ///< Given storage location find matching ParamEntry const ParamEntry *selectUnreferenceEntry(int4 grp,type_class prefType) const; ///< Select entry to fill an unreferenced param void buildTrialMap(ParamActive *active) const; ///< Build map from parameter trials to model ParamEntrys void separateSections(ParamActive *active,vector &trialStart) const; @@ -604,6 +612,8 @@ public: ParamListStandard(const ParamListStandard &op2); ///< Copy constructor virtual ~ParamListStandard(void); const list &getEntry(void) const { return entry; } ///< Get the list of parameter entries + list::const_iterator getFirstIter(type_class type) const; ///< Get iterator to first entry in a storage class + const ParamEntry *getStackEntry(void) const; ///< Get the stack entry uint4 assignAddressFallback(type_class resource,Datatype *tp,bool matchExact,vector &status, ParameterPieces ¶m) const; uint4 assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlst, @@ -623,6 +633,7 @@ public: virtual bool isThisBeforeRetPointer(void) const { return thisbeforeret; } virtual void getRangeList(AddrSpace *spc,RangeList &res) const; virtual int4 getMaxDelay(void) const { return maxdelay; } + virtual bool isAutoKillByCall(void) const { return false; } virtual void decode(Decoder &decoder,vector &effectlist,bool normalstack); virtual ParamList *clone(void) const; }; @@ -636,13 +647,19 @@ public: /// attempted again, and the return value is marked as a \e hidden return parameter /// to inform the input model. class ParamListStandardOut : public ParamListStandard { + bool useFillinFallback; ///< If \b true, use fillinMapFallback + void initialize(void); ///< Cache ModelRule information public: ParamListStandardOut(void) : ParamListStandard() {} ///< Constructor for use with decode() - ParamListStandardOut(const ParamListStandardOut &op2) : ParamListStandard(op2) {} ///< Copy constructor + ParamListStandardOut(const ParamListStandardOut &op2) : ParamListStandard(op2) { + useFillinFallback = op2.useFillinFallback; } ///< Copy constructor + void fillinMapFallback(ParamActive *active,bool firstOnly) const; virtual uint4 getType(void) const { return p_standard_out; } virtual void assignMap(const PrototypePieces &proto,TypeFactory &typefactory,vector &res) const; virtual void fillinMap(ParamActive *active) const; virtual bool possibleParam(const Address &loc,int4 size) const; + virtual bool isAutoKillByCall(void) const { return useFillinFallback; } + virtual void decode(Decoder &decoder,vector &effectlist,bool normalstack); virtual ParamList *clone(void) const; }; @@ -971,6 +988,11 @@ public: /// \return the maximum number of passes across all output parameters in \b this model int4 getMaxOutputDelay(void) const { return output->getMaxDelay(); } + /// \brief Does \b this model automatically consider potential output locations as killed by call + /// + /// \return \b true if output locations should be considered killed by call + bool isAutoKillByCall(void) const { return output->isAutoKillByCall(); } + /// \brief Is \b this a merged prototype model /// /// \return \b true if \b this is a merged form of multiple independent prototype models @@ -1323,7 +1345,8 @@ class FuncProto { is_constructor = 0x200, ///< Function is an (object-oriented) constructor is_destructor = 0x400, ///< Function is an (object-oriented) destructor has_thisptr= 0x800, ///< Function is a method with a 'this' pointer as an argument - is_override = 0x1000 ///< Set if \b this prototype is created to override a single call site + is_override = 0x1000, ///< Set if \b this prototype is created to override a single call site + auto_killbycall = 0x2000 ///< Potential output storage should always be considered \e killed \e by \e call }; ProtoModel *model; ///< Model of for \b this prototype ProtoStore *store; ///< Storage interface for parameters @@ -1583,6 +1606,8 @@ public: /// \return the active set of flags for \b this prototype uint4 getComparableFlags(void) const { return (flags & (dotdotdot | is_constructor | is_destructor | has_thisptr )); } + bool isAutoKillByCall(void) const; ///< Is a potential output automatically considered \e killed \e by \e call + void encode(Encoder &encoder) const; void decode(Decoder &decoder,Architecture *glb); }; diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/heritage.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/heritage.cc index d6ca0cb615..20df2465d6 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/heritage.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/heritage.cc @@ -1426,7 +1426,8 @@ void Heritage::guardCalls(uint4 fl,const Address &addr,int4 size,vectorgetActiveOutput(); int4 outputCharacter = fc->characterizeAsOutput(transAddr, size); if (outputCharacter != ParamEntry::no_containment) { - effecttype = EffectRecord::killedbycall; // A potential output is always killed by call + if (effecttype != EffectRecord::killedbycall && fc->isAutoKillByCall()) + effecttype = EffectRecord::killedbycall; if (outputCharacter == ParamEntry::contained_by) { if (tryOutputOverlapGuard(fc, addr, transAddr, size, write)) effecttype = EffectRecord::unaffected; // Range is handled, don't do additional guarding diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc index c1561dcdd2..22f216ce0b 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/marshal.cc @@ -1266,6 +1266,6 @@ ElementId ELEM_VAL = ElementId("val",8); ElementId ELEM_VALUE = ElementId("value",9); ElementId ELEM_VOID = ElementId("void",10); -ElementId ELEM_UNKNOWN = ElementId("XMLunknown",285); // Number serves as next open index +ElementId ELEM_UNKNOWN = ElementId("XMLunknown",286); // Number serves as next open index } // End namespace ghidra diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/modelrules.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/modelrules.cc index d49c106c1a..f92abbbb56 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/modelrules.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/modelrules.cc @@ -29,24 +29,150 @@ ElementId ELEM_POSITION = ElementId("position",280); ElementId ELEM_VARARGS = ElementId("varargs",281); ElementId ELEM_HIDDEN_RETURN = ElementId("hidden_return",282); ElementId ELEM_JOIN_PER_PRIMITIVE = ElementId("join_per_primitive",283); +ElementId ELEM_JOIN_DUAL_CLASS = ElementId("join_dual_class",285); -/// \brief Extract an ordered list of primitive data-types making up the given data-type +/// \brief Check that a big Primitive properly overlaps smaller Primitives /// -/// The primitive data-types are passed back in an array. If the given data-type is already -/// primitive, it is passed back as is. Otherwise if it is composite, its components are -/// recursively listed. If a maximum number of extracted primitives is exceeded, or if the -/// primitives are not properly aligned, or if a non-primitive non-composite data-type is -/// encountered, false is returned. +/// If the big Primitive does not properly overlap the smaller Primitives starting at the given \b point, +/// return -1. Otherwise, if the big Primitive is floating-point, add the overlapped primitives to the +/// common refinement list, or if not a floating-point, add the big Primitive to the list. +/// (Integer primitives are \e preferred over floating-point primitives in this way) Return the index of +/// the next primitive after the overlap. +/// \param res holds the common refinement list +/// \param small is the list of Primitives that are overlapped +/// \param point is the index of the first overlap +/// \param big is the big overlapping Primitive +/// \return the index of the next Primitive after the overlap or -1 if the overlap is invalid +int4 PrimitiveExtractor::checkOverlap(vector &res,vector &small,int4 point,Primitive &big) + +{ + int4 endOff = big.offset + big.dt->getAlignSize(); + // If big data-type is a float, let smaller primitives override it, otherwise we keep the big primitive + bool useSmall = big.dt->getMetatype() == TYPE_FLOAT; + while(point < small.size()) { + int4 curOff = small[point].offset; + if (curOff >= endOff) break; + curOff += small[point].dt->getAlignSize(); + if (curOff > endOff) + return -1; // Improper overlap of the end of big + if (useSmall) + res.push_back(small[point]); + point += 1; + } + if (!useSmall) // If big data-type was preferred + res.push_back(big); // use big Primitive in the refinement + return point; +} + +/// \brief Overwrite \b first list with common refinement of \b first and \b second +/// +/// Given two sets of overlapping Primitives, find a \e common \e refinement of the lists. +/// If there is any partial overlap of two Primitives, \b false is returned. +/// If the same primitive data-type occurs at the same offset, it is included in the refinement. +/// Otherwise an integer data-type is preferred over a floating-point data-type, or a bigger +/// primitive is preferred over smaller overlapping primitives. +/// The final refinement replaces the \b first list. +/// \param first is the first list of Primitives +/// \param second is the second list +/// \return \b true if a refinement was successfully constructed +bool PrimitiveExtractor::commonRefinement(vector &first,vector &second) + +{ + int4 firstPoint = 0; + int4 secondPoint = 0; + vector common; + while(firstPoint < first.size() && secondPoint < second.size()) { + Primitive &firstElement( first[firstPoint] ); + Primitive &secondElement( second[secondPoint] ); + if (firstElement.offset < secondElement.offset && + firstElement.offset + firstElement.dt->getAlignSize() <= secondElement.offset) { + common.push_back(firstElement); + firstPoint += 1; + continue; + } + if (secondElement.offset < firstElement.offset && + secondElement.offset + secondElement.dt->getAlignSize() <= firstElement.offset) { + common.push_back(secondElement); + secondPoint += 1; + continue; + } + if (firstElement.dt->getAlignSize() >= secondElement.dt->getAlignSize()) { + secondPoint = checkOverlap(common,second,secondPoint,firstElement); + if (secondPoint < 0) return false; + firstPoint += 1; + } + else { + firstPoint = checkOverlap(common,first,firstPoint,secondElement); + if (firstPoint < 0) return false; + secondPoint += 1; + } + } + // Add any tail primitives from either list + while(firstPoint < first.size()) { + common.push_back(first[firstPoint]); + firstPoint += 1; + } + while(secondPoint < second.size()) { + common.push_back(second[secondPoint]); + secondPoint += 1; + } + first.swap(common); // Replace first with the refinement + return true; +} + +/// Form a primitive list for each field of the union. Then, if possible, form a common refinement +/// of all the primitive lists and add to the end of \b this extractor's list. +/// \param dt is the union data-type +/// \param max is the maximum number primitives allowed for \b this extraction +/// \param offset is the starting offset of the union within the parent +/// \return \b true if a common refinement was found and appended +bool PrimitiveExtractor::handleUnion(TypeUnion *dt,int4 max,int4 offset) + +{ + if ((flags & union_invalid) != 0) + return false; + int4 num = dt->numDepend(); + if (num == 0) + return false; + const TypeField *curField = dt->getField(0); + PrimitiveExtractor common(curField->type,false,offset + curField->offset,max); + if (!common.isValid()) + return false; + for(int4 i=1;igetField(i); + PrimitiveExtractor next(curField->type,false,offset + curField->offset,max); + if (!next.isValid()) + return false; + if (!commonRefinement(common.primitives,next.primitives)) + return false; + } + if (primitives.size() + common.primitives.size() > max) + return false; + for(int4 i=0;i &res) +bool PrimitiveExtractor::extract(Datatype *dt,int4 max,int4 offset) { switch(dt->getMetatype()) { case TYPE_UNKNOWN: - return false; // Do not consider undefined data-types as primitive + flags |= unknown_element; ///< Mark that the data-type contains an unknown primitive + // fallthru case TYPE_INT: case TYPE_UINT: case TYPE_BOOL: @@ -54,46 +180,63 @@ bool DatatypeFilter::extractPrimitives(Datatype *dt,int4 max,vector case TYPE_FLOAT: case TYPE_PTR: case TYPE_PTRREL: - if (res.size() >= max) + if (primitives.size() >= max) return false; - res.push_back(dt); + primitives.emplace_back(dt,offset); return true; case TYPE_ARRAY: { int4 numEls = ((TypeArray *)dt)->numElements(); Datatype *base = ((TypeArray *)dt)->getBase(); for(int4 i=0;igetAlignSize(); } return true; } + case TYPE_UNION: + return handleUnion((TypeUnion *)dt,max,offset); case TYPE_STRUCT: break; default: return false; } TypeStruct *structPtr = (TypeStruct *)dt; - int4 curOff = 0; vector::const_iterator enditer = structPtr->endField(); + int4 expectedOff = offset; for(vector::const_iterator iter=structPtr->beginField();iter!=enditer;++iter) { Datatype *compDt = (*iter).type; - int4 nextOff = (*iter).offset; - int4 align = dt->getAlignment(); - int4 rem = curOff % align; + int4 curOff = (*iter).offset + offset; + int4 align = compDt->getAlignment(); + if (curOff % align != 0) + flags |= unaligned; + int4 rem = expectedOff % align; if (rem != 0) { - curOff += (align - rem); + expectedOff += (align - rem); } - if (curOff != nextOff) { - return false; + if (expectedOff != curOff) { + flags |= extra_space; } - curOff = nextOff + compDt->getAlignSize(); - if (!extractPrimitives(compDt,max,res)) + if (!extract(compDt,max,curOff)) return false; - } + expectedOff = curOff + compDt->getAlignSize(); + } return true; } +/// \param dt is data-type extract from +/// \param unionIllegal is \b true if unions encountered during extraction are considered illegal +/// \param offset is the starting offset to associate with the data-type +/// \param max is the maximum number of primitives to extract before giving up +PrimitiveExtractor::PrimitiveExtractor(Datatype *dt,bool unionIllegal,int offset,int4 max) + +{ + flags = unionIllegal ? union_invalid : 0; + if (!extract(dt,max,offset)) + flags |= invalid; +} + /// \param decoder is the given stream decoder /// \return the new data-type filter instance DatatypeFilter *DatatypeFilter::decodeFilter(Decoder &decoder) @@ -196,14 +339,15 @@ bool HomogeneousAggregate::filter(Datatype *dt) const type_metatype meta = dt->getMetatype(); if (meta != TYPE_ARRAY && meta != TYPE_STRUCT) return false; - vector res; - if (!extractPrimitives(dt, 4, res) || res.empty()) + PrimitiveExtractor primitives(dt,true,0,4); + if (!primitives.isValid() || primitives.size() == 0 || primitives.containsUnknown() + || !primitives.isAligned() || primitives.containsHoles()) return false; - Datatype *base = res[0]; + Datatype *base = primitives.get(0).dt; if (base->getMetatype() != metaType) return false; - for(int4 i=1;idecode(decoder); @@ -397,15 +550,7 @@ AssignAction *AssignAction::decodeSideeffect(Decoder &decoder,const ParamListSta void GotoStack::initializeEntry(void) { - const list &entries(resource->getEntry()); - list::const_iterator iter; - for(iter=entries.begin();iter!=entries.end();++iter) { - const ParamEntry &entry( *iter ); - if (!entry.isExclusion() && entry.getSpace()->getType() == IPTR_SPACEBASE) { - stackEntry = &entry; - break; - } - } + stackEntry = resource->getStackEntry(); if (stackEntry == (const ParamEntry *)0) throw LowlevelError("Cannot find matching for action: gotostack"); } @@ -416,12 +561,14 @@ GotoStack::GotoStack(const ParamListStandard *res,int4 val) : AssignAction(res) { stackEntry = (const ParamEntry *)0; + fillinOutputActive = true; } GotoStack::GotoStack(const ParamListStandard *res) : AssignAction(res) { stackEntry = (const ParamEntry *)0; + fillinOutputActive = true; initializeEntry(); } @@ -435,6 +582,23 @@ uint4 GotoStack::assignAddress(Datatype *dt,const PrototypePieces &proto,int4 po return success; } +bool GotoStack::fillinOutputMap(ParamActive *active) const + +{ + int4 count = 0; + for(int4 i=0;igetNumTrials();++i) { + ParamTrial &trial(active->getTrial(i)); + const ParamEntry *entry = trial.getEntry(); + if (entry == (const ParamEntry *)0) break; + if (entry != stackEntry) + return false; + count += 1; + if (count > 1) + return false; + } + return (count == 1); +} + void GotoStack::decode(Decoder &decoder) { @@ -477,20 +641,9 @@ void ConvertToPointer::decode(Decoder &decoder) void MultiSlotAssign::initializeEntries(void) { - const list &entries(resource->getEntry()); - firstIter = entries.end(); - list::const_iterator iter; - for(iter=entries.begin();iter!=entries.end();++iter) { - const ParamEntry &entry( *iter ); - if (firstIter == entries.end() && entry.isExclusion() && entry.getType() == resourceType && - entry.getAllGroups().size() == 1) - firstIter = iter; // First matching resource size - if (!entry.isExclusion() && entry.getSpace()->getType() == IPTR_SPACEBASE) { - stackEntry = &entry; - break; - } - } - if (firstIter == entries.end()) + firstIter = resource->getFirstIter(resourceType); + stackEntry = resource->getStackEntry(); + if (firstIter == resource->getEntry().end()) throw LowlevelError("Could not find matching resources for action: join"); if (consumeFromStack && stackEntry == (const ParamEntry *)0) throw LowlevelError("Cannot find matching for action: join"); @@ -502,6 +655,7 @@ MultiSlotAssign::MultiSlotAssign(const ParamListStandard *res) : AssignAction(res) { resourceType = TYPECLASS_GENERAL; // Join general purpose registers + fillinOutputActive = true; uint4 listType = res->getType(); // Consume from stack on input parameters by default consumeFromStack = (listType != ParamList::p_register_out && listType != ParamList::p_standard_out); @@ -520,6 +674,7 @@ MultiSlotAssign::MultiSlotAssign(type_class store,bool stack,bool mostSig,bool a : AssignAction(res) { resourceType = store; + fillinOutputActive = true; consumeFromStack = stack; consumeMostSig = mostSig; enforceAlignment = align; @@ -620,6 +775,55 @@ uint4 MultiSlotAssign::assignAddress(Datatype *dt,const PrototypePieces &proto,i return success; } +bool MultiSlotAssign::fillinOutputMap(ParamActive *active) const + +{ + int4 count = 0; + int4 curGroup = -1; + int4 partial = -1; + for(int4 i=0;igetNumTrials();++i) { + ParamTrial &trial(active->getTrial(i)); + const ParamEntry *entry = trial.getEntry(); + if (entry == (const ParamEntry *)0) break; + if (entry->getType() != resourceType) // Trials must come from action's type_class + return false; + if (count == 0) { + if (!entry->isFirstInClass()) + return false; // Trials must start on first entry of the type_class + } + else { + if (entry->getGroup() != curGroup + 1) // Trials must be consecutive + return false; + } + curGroup = entry->getGroup(); + if (trial.getSize() != entry->getSize()) { + if (partial != -1) + return false; // At most, one trial can be partial size + partial = i; + } + count += 1; + } + if (partial != -1) { + if (justifyRight) { + if (partial != 0) return false; + } + else { + if (partial != count - 1) return false; + } + ParamTrial &trial(active->getTrial(partial)); + if (justifyRight == consumeMostSig) { + if (trial.getOffset() != 0) + return false; // Partial entry must be least sig bytes + } + else { + if (trial.getOffset() + trial.getSize() != trial.getEntry()->getSize()) { + return false; // Partial entry must be most sig bytes + } + } + } + return (count > 0); +} + void MultiSlotAssign::decode(Decoder &decoder) { @@ -648,6 +852,7 @@ MultiMemberAssign::MultiMemberAssign(type_class store,bool stack,bool mostSig,co resourceType = store; consumeFromStack = stack; consumeMostSig = mostSig; + fillinOutputActive = true; } uint4 MultiMemberAssign::assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, @@ -655,12 +860,13 @@ uint4 MultiMemberAssign::assignAddress(Datatype *dt,const PrototypePieces &proto { vector tmpStatus = status; vector pieces; - vector primitives; - if (!DatatypeFilter::extractPrimitives(dt,16,primitives) || primitives.empty()) + PrimitiveExtractor primitives(dt,false,0,16); + if (!primitives.isValid() || primitives.size() == 0 || primitives.containsUnknown() + || !primitives.isAligned() || primitives.containsHoles()) return fail; ParameterPieces param; for(int4 i=0;iassignAddressFallback(resourceType, curType, !consumeFromStack, tmpStatus,param) == fail) return fail; pieces.push_back(VarnodeData()); @@ -687,6 +893,33 @@ uint4 MultiMemberAssign::assignAddress(Datatype *dt,const PrototypePieces &proto return success; } +bool MultiMemberAssign::fillinOutputMap(ParamActive *active) const + +{ + int4 count = 0; + int4 curGroup = -1; + for(int4 i=0;igetNumTrials();++i) { + ParamTrial &trial(active->getTrial(i)); + const ParamEntry *entry = trial.getEntry(); + if (entry == (const ParamEntry *)0) break; + if (entry->getType() != resourceType) // Trials must come from action's type_class + return false; + if (count == 0) { + if (!entry->isFirstInClass()) + return false; + } + else { + if (entry->getGroup() != curGroup + 1) // Trials must be consecutive + return false; + } + curGroup = entry->getGroup(); + if (trial.getOffset() != 0) + return false; // Entry must be justified + count += 1; + } + return (count > 0); +} + void MultiMemberAssign::decode(Decoder &decoder) { @@ -701,10 +934,252 @@ void MultiMemberAssign::decode(Decoder &decoder) decoder.closeElement(elemId); } +/// Find the first ParamEntry matching the \b baseType, and the first matching \b altType. +void MultiSlotDualAssign::initializeEntries(void) + +{ + baseIter = resource->getFirstIter(baseType); + altIter = resource->getFirstIter(altType); + list::const_iterator enditer = resource->getEntry().end(); + if (baseIter == enditer || altIter == enditer) + throw LowlevelError("Could not find matching resources for action: join_dual_class"); + tileSize = (*baseIter).getSize(); + if (tileSize != (*altIter).getSize()) + throw LowlevelError("Storage class register sizes do not match for action: join_dual_class"); +} + +/// \brief Get the first unused ParamEntry that matches the given storage class +/// +/// \param iter points to the starting entry to search +/// \param storage is the given storage class to match +/// \param status is the usage information for the entries +/// \return the iterator to the unused ParamEntry +list::const_iterator MultiSlotDualAssign::getFirstUnused(list::const_iterator iter, + type_class storage,vector &status) const +{ + list::const_iterator endIter = resource->getEntry().end(); + for(;iter != endIter; ++iter) { + const ParamEntry &entry( *iter ); + if (!entry.isExclusion()) + break; // Reached end of resource list + if (entry.getType() != storage || entry.getAllGroups().size() != 1) + continue; // Not a single register from desired resource + if (status[entry.getGroup()] != 0) + continue; // Already consumed + return iter; + } + return endIter; +} + +/// \brief Get the storage class to use for the specific section of the data-type +/// +/// For the section starting at \b off extending through \b tileSize bytes, if any primitive overlaps +/// the boundary of the section, return -1. Otherwise, if all the primitive data-types in the section +/// match the alternate storage class, return 1, or if one or more does not match, return 0. +/// The \b index of the first primitive after the start of the section is provided and is then updated +/// to be the first primitive after the end of the section. +/// \param primitives is the list of primitive data-types making up the data-type +/// \param off is the starting offset of the section +/// \param index is the index of the first primitive in the section +/// \return 0 for a base tile, 1 for an alternate tile, -1 for boundary overlaps +int4 MultiSlotDualAssign::getTileClass(const PrimitiveExtractor &primitives,int4 off,int4 &index) const + +{ + int4 res = 1; + int4 count = 0; + int4 endBoundary = off + tileSize; + while(index < primitives.size()) { + const PrimitiveExtractor::Primitive &element( primitives.get(index) ); + if (element.offset < off) return -1; + if (element.offset >= endBoundary) break; + if (element.offset + element.dt->getSize() > endBoundary) return -1; + count += 1; + index += 1; + type_class storage = metatype2typeclass(element.dt->getMetatype()); + if (storage != altType) + res = 0; + } + if (count == 0) return -1; // Must be at least one primitive in section + return res; +} + +/// Set default configuration +/// \param res is the new resource set to associate with \b this action +MultiSlotDualAssign::MultiSlotDualAssign(const ParamListStandard *res) + : AssignAction(res) +{ + fillinOutputActive = true; + baseType = TYPECLASS_GENERAL; // Tile from general purpose registers + altType = TYPECLASS_FLOAT; // Use specialized registers for floating-point components + consumeMostSig = false; + justifyRight = false; + AddrSpace *spc = res->getSpacebase(); + if (spc != (AddrSpace *)0 && spc->isBigEndian()) { + consumeMostSig = true; + justifyRight = true; + } + tileSize = 0; +} + +MultiSlotDualAssign::MultiSlotDualAssign(type_class baseStore,type_class altStore,bool mostSig,bool justRight, + const ParamListStandard *res) + : AssignAction(res) +{ + fillinOutputActive = true; + baseType = baseStore; + altType = altStore; + consumeMostSig = mostSig; + justifyRight = justRight; + initializeEntries(); +} + +uint4 MultiSlotDualAssign::assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, + vector &status,ParameterPieces &res) const +{ + PrimitiveExtractor primitives(dt,false,0,1024); + if (!primitives.isValid() || primitives.size() == 0 || primitives.containsHoles()) + return fail; + int4 primitiveIndex = 0; + vector tmpStatus = status; + vector pieces; + int4 typeSize = dt->getSize(); + int4 sizeLeft = typeSize; + list::const_iterator iterBase = baseIter; + list::const_iterator iterAlt = altIter; + list::const_iterator endIter = resource->getEntry().end(); + while(sizeLeft > 0) { + list::const_iterator iter; + int4 iterType = getTileClass(primitives, typeSize-sizeLeft, primitiveIndex); + if (iterType < 0) + return fail; + if (iterType == 0) { + iter = iterBase = getFirstUnused(iterBase, baseType, tmpStatus); + } + else { + iter = iterAlt = getFirstUnused(iterAlt, altType, tmpStatus); + } + if (iter == endIter) + return fail; // Out of the particular resource + const ParamEntry &entry( *iter ); + int4 trialSize = entry.getSize(); + Address addr = entry.getAddrBySlot(tmpStatus[entry.getGroup()], trialSize,1); + tmpStatus[entry.getGroup()] = -1; // Consume the register + pieces.push_back(VarnodeData()); + pieces.back().space = addr.getSpace(); + pieces.back().offset = addr.getOffset(); + pieces.back().size = trialSize; + sizeLeft -= trialSize; + } + if (sizeLeft < 0) { // Have odd data-type size + if (justifyRight) { + pieces.front().offset += -sizeLeft; // Initial bytes of first entry are padding + pieces.front().size += sizeLeft; + } + else { + pieces.back().size += sizeLeft; + } + } + status = tmpStatus; // Commit resource usage for all the pieces + res.flags = 0; + res.type = dt; + if (pieces.size() == 1) { + res.addr = pieces[0].getAddr(); + return success; + } + if (!consumeMostSig) { + vector reverse; + for(int4 i=pieces.size()-1;i>=0;--i) + reverse.push_back(pieces[i]); + pieces.swap(reverse); + } + JoinRecord *joinRecord = tlist.getArch()->findAddJoin(pieces, 0); + res.addr = joinRecord->getUnified().getAddr(); + return success; +} + +bool MultiSlotDualAssign::fillinOutputMap(ParamActive *active) const + +{ + int4 count = 0; + int4 curGroup = -1; + int4 partial = -1; + type_class resourceType = TYPECLASS_GENERAL; + for(int4 i=0;igetNumTrials();++i) { + ParamTrial &trial(active->getTrial(i)); + const ParamEntry *entry = trial.getEntry(); + if (entry == (const ParamEntry *)0) break; + if (count == 0) { + resourceType = entry->getType(); + if (resourceType != baseType && resourceType != altType) + return false; + } + else if (entry->getType() != resourceType) // Trials must come from action's type_class + return false; + if (count == 0) { + if (!entry->isFirstInClass()) + return false; // Trials must start on first entry of the type_class + } + else { + if (entry->getGroup() != curGroup + 1) // Trials must be consecutive + return false; + } + curGroup = entry->getGroup(); + if (trial.getSize() != entry->getSize()) { + if (partial != -1) + return false; // At most, one trial can be partial size + partial = i; + } + count += 1; + } + if (partial != -1) { + if (justifyRight) { + if (partial != 0) return false; + } + else { + if (partial != count - 1) return false; + } + ParamTrial &trial(active->getTrial(partial)); + if (justifyRight == consumeMostSig) { + if (trial.getOffset() != 0) + return false; // Partial entry must be least sig bytes + } + else { + if (trial.getOffset() + trial.getSize() != trial.getEntry()->getSize()) { + return false; // Partial entry must be most sig bytes + } + } + } + return (count > 0); + +} + +void MultiSlotDualAssign::decode(Decoder &decoder) + +{ + uint4 elemId = decoder.openElement(ELEM_JOIN_DUAL_CLASS); + for(;;) { + uint4 attribId = decoder.getNextAttributeId(); + if (attribId == 0) break; + if (attribId == ATTRIB_REVERSEJUSTIFY) { + if (decoder.readBool()) + justifyRight = !justifyRight; + } + else if (attribId == ATTRIB_STORAGE || attribId == ATTRIB_A) { + baseType = string2typeclass(decoder.readString()); + } + else if (attribId == ATTRIB_B) { + altType = string2typeclass(decoder.readString()); + } + } + decoder.closeElement(elemId); + initializeEntries(); // Need new firstIter +} + ConsumeAs::ConsumeAs(type_class store,const ParamListStandard *res) : AssignAction(res) { resourceType = store; + fillinOutputActive = true; } uint4 ConsumeAs::assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, @@ -713,6 +1188,27 @@ uint4 ConsumeAs::assignAddress(Datatype *dt,const PrototypePieces &proto,int4 po return resource->assignAddressFallback(resourceType, dt, true, status, res); } +bool ConsumeAs::fillinOutputMap(ParamActive *active) const + +{ + int4 count = 0; + for(int4 i=0;igetNumTrials();++i) { + ParamTrial &trial(active->getTrial(i)); + const ParamEntry *entry = trial.getEntry(); + if (entry == (const ParamEntry *)0) break; + if (entry->getType() != resourceType) // Trials must come from action's type_class + return false; + if (!entry->isFirstInClass()) + return false; + count += 1; + if (count > 1) + return false; + if (trial.getOffset() != 0) + return false; // Entry must be justified + } + return (count > 0); +} + void ConsumeAs::decode(Decoder &decoder) { @@ -761,17 +1257,8 @@ void HiddenReturnAssign::decode(Decoder &decoder) void ConsumeExtra::initializeEntries(void) { - const list &entries(resource->getEntry()); - firstIter = entries.end(); - list::const_iterator iter; - for(iter=entries.begin();iter!=entries.end();++iter) { - const ParamEntry &entry( *iter ); - if (entry.isExclusion() && entry.getType() == resourceType && entry.getAllGroups().size() == 1) { - firstIter = iter; // First matching resource size - break; - } - } - if (firstIter == entries.end()) + firstIter = resource->getFirstIter(resourceType); + if (firstIter == resource->getEntry().end()) throw LowlevelError("Could not find matching resources for action: consumeextra"); } diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/modelrules.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/modelrules.hh index eeca45d038..7daad20273 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/modelrules.hh +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/modelrules.hh @@ -26,6 +26,7 @@ namespace ghidra { class ParameterPieces; class ParamListStandard; class ParamEntry; +class ParamActive; extern ElementId ELEM_DATATYPE; ///< Marshaling element \ extern ElementId ELEM_CONSUME; ///< Marshaling element \ @@ -38,6 +39,43 @@ extern ElementId ELEM_POSITION; ///< Marshaling element \ extern ElementId ELEM_VARARGS; ///< Marshaling element \ extern ElementId ELEM_HIDDEN_RETURN; ///< Marshaling element \ extern ElementId ELEM_JOIN_PER_PRIMITIVE; ///< Marshaling element \ +extern ElementId ELEM_JOIN_DUAL_CLASS; ///< Marshaling element \ + +/// \brief Class for extracting primitive elements of a data-type +/// +/// This recursively collects the formal \e primitive data-types of a composite data-type, +/// laying them out with their offsets in an array. Other boolean properties are collected. +class PrimitiveExtractor { + enum { + unknown_element = 1, ///< Contains at least one TYPE_UNKNOWN primitive + unaligned = 2, ///< At least one primitive is not properly aligned + extra_space = 4, ///< Data-type contains empty space not attributable to alignment padding + invalid = 8, ///< Data-type exceeded maximum or contained illegal elements + union_invalid = 16 ///< Unions are treated as an illegal element + }; +public: + class Primitive { + public: + Datatype *dt; ///< Primitive data-type + int4 offset; ///< Offset within container + Primitive(Datatype *d,int4 off) { dt = d; offset = off; } ///< Constructor + }; +private: + vector primitives; ///< List of extracted primitives + uint4 flags; ///< Boolean properties of the data-type + int4 checkOverlap(vector &res,vector &small,int4 point,Primitive &big); + bool commonRefinement(vector &first,vector &second); + bool handleUnion(TypeUnion *dt,int4 max,int4 offset); ///< Add primitives representing a union data-type + bool extract(Datatype *dt,int4 max,int4 offset); ///< Extract list of primitives from given data-type +public: + PrimitiveExtractor(Datatype *dt,bool unionIllegal,int4 offset,int4 max); ///< Constructor + int4 size(void) const { return primitives.size(); } ///< Return the number of primitives extracted + const Primitive &get(int4 i) const { return primitives[i]; } ///< Get a particular primitive + bool isValid(void) const { return (flags & invalid) == 0; } + bool containsUnknown(void) const { return (flags & unknown_element)!=0; } ///< Are there \b unknown elements + bool isAligned(void) const { return (flags & unaligned)==0; } ///< Are all elements aligned + bool containsHoles(void) const { return (flags & extra_space)!=0; } ///< Is there empty space that is not padding +}; /// \brief A filter selecting a specific class of data-type /// @@ -63,7 +101,6 @@ public: /// \param decoder is the given stream decoder virtual void decode(Decoder &decoder)=0; - static bool extractPrimitives(Datatype *dt,int4 max,vector &res); static DatatypeFilter *decodeFilter(Decoder &decoder); ///< Instantiate a filter from the given stream }; @@ -208,8 +245,12 @@ public: }; protected: const ParamListStandard *resource; ///< Resources to which this action applies + bool fillinOutputActive; ///< If \b true, fillinOutputMap is active public: - AssignAction(const ParamListStandard *res) {resource = res; } ///< Constructor + AssignAction(const ParamListStandard *res) {resource = res; fillinOutputActive = false; } ///< Constructor + + bool canAffectFillinOutput(void) const { return fillinOutputActive; } ///< Return \b true if fillinOutputMap is active + virtual ~AssignAction(void) {} /// \brief Make a copy of \b this action @@ -237,6 +278,14 @@ public: virtual uint4 assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, vector &status,ParameterPieces &res) const=0; + /// \brief Test if \b this action could produce return value storage matching the given set of trials + /// + /// If there is a return value data-type that could be assigned storage matching the trials by \b this action, + /// return \b true. The trials have their matching ParamEntry and offset already set and are already sorted. + /// \param active is the given set of trials + /// \return \b true if the trials could form a valid return value + virtual bool fillinOutputMap(ParamActive *active) const; + /// \brief Configure any details of how \b this action should behave from the stream /// /// \param decoder is the given stream decoder @@ -255,6 +304,7 @@ public: virtual AssignAction *clone(const ParamListStandard *newResource) const { return new GotoStack(newResource); } virtual uint4 assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, vector &status,ParameterPieces &res) const; + virtual bool fillinOutputMap(ParamActive *active) const; virtual void decode(Decoder &decoder); }; @@ -292,6 +342,7 @@ public: return new MultiSlotAssign(resourceType,consumeFromStack,consumeMostSig,enforceAlignment,justifyRight,newResource); } virtual uint4 assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, vector &status,ParameterPieces &res) const; + virtual bool fillinOutputMap(ParamActive *active) const; virtual void decode(Decoder &decoder); }; @@ -310,6 +361,35 @@ public: return new MultiMemberAssign(resourceType,consumeFromStack,consumeMostSig,newResource); } virtual uint4 assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, vector &status,ParameterPieces &res) const; + virtual bool fillinOutputMap(ParamActive *active) const; + virtual void decode(Decoder &decoder); +}; + +/// \brief Consume multiple registers from different storage classes to pass a data-type +/// +/// This action is for calling conventions that can use both floating-point and general purpose registers +/// when assigning storage for a single composite data-type, such as the X86-64 System V ABI +class MultiSlotDualAssign : public AssignAction { + type_class baseType; ///< Resource list from which to consume general tiles + type_class altType; ///< Resource list from which to consume alternate tiles + bool consumeMostSig; ///< True if resources are consumed starting with most significant bytes + bool justifyRight; ///< True if initial bytes are padding for odd data-type sizes + int4 tileSize; ///< Number of bytes in a tile + list::const_iterator baseIter; ///< Iterator to first element in the base resource list + list::const_iterator altIter; ///< Iterator to first element in alternate resource list + void initializeEntries(void); ///< Cache specific ParamEntry needed by the action + list::const_iterator getFirstUnused(list::const_iterator iter,type_class storage, + vector &status) const; + int4 getTileClass(const PrimitiveExtractor &primitives,int4 off,int4 &index) const; +public: + MultiSlotDualAssign(const ParamListStandard *res); ///< Constructor for use with decode + MultiSlotDualAssign(type_class baseStore,type_class altStore,bool mostSig,bool justRight, + const ParamListStandard *res); ///< Constructor + virtual AssignAction *clone(const ParamListStandard *newResource) const { + return new MultiSlotDualAssign(baseType,altType,consumeMostSig,justifyRight,newResource); } + virtual uint4 assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, + vector &status,ParameterPieces &res) const; + virtual bool fillinOutputMap(ParamActive *active) const; virtual void decode(Decoder &decoder); }; @@ -325,6 +405,7 @@ public: return new ConsumeAs(resourceType,newResource); } virtual uint4 assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, vector &status,ParameterPieces &res) const; + virtual bool fillinOutputMap(ParamActive *active) const; virtual void decode(Decoder &decoder); }; @@ -392,8 +473,26 @@ public: ~ModelRule(void); ///< Destructor uint4 assignAddress(Datatype *dt,const PrototypePieces &proto,int4 pos,TypeFactory &tlist, vector &status,ParameterPieces &res) const; + bool fillinOutputMap(ParamActive *active) const; ///< Test and mark the trial(s) that can be valid return value + bool canAffectFillinOutput(void) const; ///< Return \b true if fillinOutputMap is active for \b this rule void decode(Decoder &decoder,const ParamListStandard *res); ///< Decode \b this rule from stream }; +/// If the assign action could produce the trials as return value storage, return \b true +/// \param active is the set of trials +/// \return \b true if the trials form a return value +inline bool ModelRule::fillinOutputMap(ParamActive *active) const + +{ + return assign->fillinOutputMap(active); +} + +/// \return \b true if the assign action can affect fillinOutputMap() +inline bool ModelRule::canAffectFillinOutput(void) const + +{ + return assign->canAffectFillinOutput(); +} + } // End namespace ghidra #endif diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc index e6292e138d..eeeb1231c0 100644 --- a/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc +++ b/Ghidra/Features/Decompiler/src/decompile/cpp/type.cc @@ -1507,6 +1507,9 @@ void TypeStruct::setFields(const vector &fd,int4 fixedSize,int4 fixed size = calcSize; alignment = (fixedAlign < 1) ? calcAlign : fixedAlign; calcAlignSize(); + if (fixedSize <= 0) { // Unless specifically overridden + size = alignSize; // pad out structure to with alignment bytes + } } /// Find the proper subfield given an offset. Return the index of that field diff --git a/Ghidra/Features/Decompiler/src/decompile/unittests/testparamstore.cc b/Ghidra/Features/Decompiler/src/decompile/unittests/testparamstore.cc index d48d97bb8e..2621e35ea7 100644 --- a/Ghidra/Features/Decompiler/src/decompile/unittests/testparamstore.cc +++ b/Ghidra/Features/Decompiler/src/decompile/unittests/testparamstore.cc @@ -199,7 +199,63 @@ bool ParamStoreEnvironment::comparePiece(const VarnodeData &vData,ParameterPiece TEST(paramstore_x64) { ProtoModel *model = theEnviron.getModel("x86:LE:64:default:gcc","__stdcall"); - ASSERT(theEnviron.test(model,"void func(int4 a,int4 b);","void,EDI,ESI")); + ASSERT(theEnviron.test(model,"void func(int4,int4);","void,EDI,ESI")); + ASSERT(theEnviron.test(model,"void func(float4,float4);","void,XMM0:4,XMM1:4")); + ASSERT(theEnviron.test(model, "void func(int2 a,int4 b,int1 c);", "void,DI,ESI,DX:1")); + ASSERT(theEnviron.test(model, "void func(int8,int8);", "void,RDI,RSI")); + ASSERT(theEnviron.test(model, "void func(float8,float8);", "void,XMM0:8,XMM1:8")); + ASSERT(theEnviron.test(model, "void func(int4,float4,int4,float4);", "void,EDI,XMM0:4,ESI,XMM1:4")); + ASSERT(theEnviron.test(model, "void func(float4,int4,float4,int4);", "void,XMM0:4,EDI,XMM1:4,ESI")); + ASSERT(theEnviron.test(model, "void func(int4,float8,float8,int4);", "void,EDI,XMM0:8,XMM1:8,ESI")); + ASSERT(theEnviron.test(model, "void func(float8,int8,int8,float8);", "void,XMM0:8,RDI,RSI,XMM1:8")); + ASSERT(theEnviron.test(model, "void func(float10);", "void,stack8:10")); + ASSERT(theEnviron.test(model, "void func(float4,float10,float4);", "void,XMM0:4,stack8:10,XMM1:4")); + theEnviron.parseType(model,"struct intfloatpair { int4 a; float4 b;};"); + ASSERT(theEnviron.test(model, "void func(intfloatpair);", "void,RDI")); + theEnviron.parseType(model,"struct longfloatpair { int8 a; float4 b;};"); + ASSERT(theEnviron.test(model, "void func(int4,longfloatpair);", "void,EDI,join XMM0:8 RSI")); + theEnviron.parseType(model,"struct longdoublepair { int8 a; float8 b;};"); + ASSERT(theEnviron.test(model, "void func(int4,longdoublepair);", "void,EDI,join XMM0:8 RSI")); + theEnviron.parseType(model,"struct intdoublepair { int4 a; float8 b;};"); + ASSERT(theEnviron.test(model, "void func(int4,intdoublepair);", "void,EDI,join XMM0:8 RSI")); + theEnviron.parseType(model,"struct floatintpair { float4 a; int4 b;};"); + ASSERT(theEnviron.test(model, "void func(int4,floatintpair);", "void,EDI,RSI")); + theEnviron.parseType(model,"struct doubleintpair { float8 a; int4 b;};"); + ASSERT(theEnviron.test(model, "void func(int4,doubleintpair);", "void,EDI,join RSI XMM0:8")); + theEnviron.parseType(model,"struct intintfloat { int4 a; int4 b; float4 c; };"); + ASSERT(theEnviron.test(model, "void func(int4,intintfloat);", "void,EDI,join XMM0:4 RSI")); + theEnviron.parseType(model,"struct intintfloatfloat { int4 a; int4 b; float4 c; float4 d;};"); + ASSERT(theEnviron.test(model, "void func(int4,intintfloatfloat);", "void,EDI,join XMM0:8 RSI")); + theEnviron.parseType(model,"struct intfloatfloatint { int4 a; float4 b; float4 c; int4 d;};"); + ASSERT(theEnviron.test(model, "void func(int4,intfloatfloatint);", "void,EDI,join RDX RSI")); + theEnviron.parseType(model,"struct intfloatfloat { int4 a; float4 b; float4 c; };"); + ASSERT(theEnviron.test(model, "void func(int4,intfloatfloat);", "void,EDI,join XMM0:4 RSI")); + theEnviron.parseType(model,"struct floatfloatpair { float4 a; float4 b; };"); + ASSERT(theEnviron.test(model, "void func(int4,floatfloatpair);", "void,EDI,XMM0:8")); + theEnviron.parseType(model,"struct doublefloatpair { float8 a; float4 b; };"); + ASSERT(theEnviron.test(model, "void func(int4,doublefloatpair);", "void,EDI,join XMM1:8 XMM0:8")); + theEnviron.parseType(model,"struct floatfloatfloat { float4 a; float4 b; float4 c; };"); + ASSERT(theEnviron.test(model, "void func(floatfloatfloat,int8);", "void,join XMM1:4 XMM0:8,RDI")); + theEnviron.parseType(model,"struct intintintint { int4 a; int4 b; int4 c; int4 d; };"); + ASSERT(theEnviron.test(model, "void func(intintintint);", "void,join RSI RDI")); + ASSERT(theEnviron.test(model, "void func(int4,intintintint);", "void,EDI,join RDX RSI")); + theEnviron.parseType(model,"struct intintintintint { int4 a; int4 b; int4 c; int4 d; int4 e;};"); + ASSERT(theEnviron.test(model, "void func(intintintintint);", "void,stack8:20")); + ASSERT(theEnviron.test(model, "void func(float4,float4,float4,float4,float4,float4,float4,float4,longfloatpair);", + "void,XMM0:4,XMM1:4,XMM2:4,XMM3:4,XMM4:4,XMM5:4,XMM6:4,XMM7:4,stack8:16")); + ASSERT(theEnviron.test(model, "void func(xunknown4,xunknown8);", "void,EDI,RSI")); + ASSERT(theEnviron.test(model, "intintintint func(void);", "join RDX RAX")); + ASSERT(theEnviron.test(model, "floatintpair func(void);", "RAX")); + ASSERT(theEnviron.test(model, "longfloatpair func(void);", "join XMM0:8 RAX")); + ASSERT(theEnviron.test(model, "longdoublepair func(void);", "join XMM0:8 RAX")); + ASSERT(theEnviron.test(model, "doubleintpair func(void);", "join RAX XMM0:8")); + ASSERT(theEnviron.test(model, "floatfloatfloat func(void);", "join XMM1:4 XMM0:8")); + theEnviron.parseType(model, "struct doubledoublepair { float8 a; float8 b; };"); + ASSERT(theEnviron.test(model, "doubledoublepair func(void);", "join XMM1:8 XMM0:8")); + ASSERT(theEnviron.test(model, "floatfloatpair func(void);", "XMM0:8")); + ASSERT(theEnviron.test(model, "intintintintint func(void);", "RAX,RDI")); + theEnviron.parseType(model, "struct doubleintintint { float8 a; int4 b; int4 c; int4 d; };"); + ASSERT(theEnviron.test(model, "doubleintintint func(void);", "RAX,RDI")); } TEST(paramstore_aarch64_cdecl) { diff --git a/Ghidra/Framework/SoftwareModeling/data/languages/compiler_spec.rxg b/Ghidra/Framework/SoftwareModeling/data/languages/compiler_spec.rxg index 7d15537052..f1234a4b7b 100644 --- a/Ghidra/Framework/SoftwareModeling/data/languages/compiler_spec.rxg +++ b/Ghidra/Framework/SoftwareModeling/data/languages/compiler_spec.rxg @@ -409,6 +409,22 @@ + + + + + + + + + + + + + + + + diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/AssignAction.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/AssignAction.java index 866c63356e..5c18c2454a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/AssignAction.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/AssignAction.java @@ -127,6 +127,9 @@ public abstract class AssignAction { boolean consumeMostSig = res.getEntry(0).isBigEndian(); action = new MultiMemberAssign(StorageClass.GENERAL, false, consumeMostSig, res); } + else if (nm.equals(ELEM_JOIN_DUAL_CLASS.name())) { + action = new MultiSlotDualAssign(res); + } else { throw new XmlParseException("Unknown model rule action: " + nm); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/DatatypeFilter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/DatatypeFilter.java index ecb29b93dc..e025023f54 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/DatatypeFilter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/DatatypeFilter.java @@ -18,9 +18,8 @@ package ghidra.program.model.lang.protorules; import static ghidra.program.model.pcode.AttributeId.*; import java.io.IOException; -import java.util.ArrayList; -import ghidra.program.model.data.*; +import ghidra.program.model.data.DataType; import ghidra.program.model.pcode.Encoder; import ghidra.program.model.pcode.PcodeDataTypeManager; import ghidra.xml.*; @@ -66,79 +65,6 @@ public interface DatatypeFilter { */ public void restoreXml(XmlPullParser parser) throws XmlParseException; - /** - * Extract an ordered list of primitive data-types making up the given data-type - * - * The primitive data-types are passed back in an ArrayList. If the given data-type is already - * primitive, it is passed back as is. Otherwise if it is composite, its components are - * recursively listed. If a maximum number of extracted primitives is exceeded, or if the - * primitives are not properly aligned, or if a non-primitive non-composite data-type is - * encountered, false is returned. - * @param dt is the given data-type to extract primitives from - * @param max is the maximum number of primitives to extract before giving up - * @param res will hold the list of primitives - * @return true if all primitives were extracted - */ - public static boolean extractPrimitives(DataType dt, int max, ArrayList res) { - if (dt instanceof TypeDef) { - dt = ((TypeDef) dt).getBaseDataType(); - } - int metaType = PcodeDataTypeManager.getMetatype(dt); - switch (metaType) { - case PcodeDataTypeManager.TYPE_UNKNOWN: - return false; // Do not consider undefined data-types as primitive - case PcodeDataTypeManager.TYPE_INT: - case PcodeDataTypeManager.TYPE_UINT: - case PcodeDataTypeManager.TYPE_BOOL: - case PcodeDataTypeManager.TYPE_CODE: - case PcodeDataTypeManager.TYPE_FLOAT: - case PcodeDataTypeManager.TYPE_PTR: - case PcodeDataTypeManager.TYPE_PTRREL: - if (res.size() >= max) { - return false; - } - res.add(dt); - return true; - case PcodeDataTypeManager.TYPE_ARRAY: { - int numEls = ((Array) dt).getNumElements(); - DataType base = ((Array) dt).getDataType(); - for (int i = 0; i < numEls; ++i) { - if (!extractPrimitives(base, max, res)) { - return false; - } - } - return true; - } - case PcodeDataTypeManager.TYPE_STRUCT: - break; - default: - return false; - } - Structure structPtr = (Structure) dt; - boolean isPacked = structPtr.isPackingEnabled(); - int curOff = 0; - DataTypeComponent[] components = structPtr.getDefinedComponents(); - for (DataTypeComponent component : components) { - DataType compDT = component.getDataType(); - if (!isPacked) { - int nextOff = component.getOffset(); - int align = dt.getAlignment(); - int rem = curOff % align; - if (rem != 0) { - curOff += (align - rem); - } - if (curOff != nextOff) { - return false; - } - curOff = nextOff + compDT.getAlignedLength(); - } - if (!extractPrimitives(compDT, max, res)) { - return false; - } - } - return true; - } - /** * Instantiate a filter from the given stream. * @param parser is the given stream decoder diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/HomogeneousAggregate.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/HomogeneousAggregate.java index d114fd5717..8299420fbb 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/HomogeneousAggregate.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/HomogeneousAggregate.java @@ -19,7 +19,6 @@ import static ghidra.program.model.pcode.AttributeId.*; import static ghidra.program.model.pcode.ElementId.*; import java.io.IOException; -import java.util.ArrayList; import ghidra.program.model.data.DataType; import ghidra.program.model.pcode.Encoder; @@ -67,17 +66,18 @@ public class HomogeneousAggregate extends SizeRestrictedFilter { if (meta != PcodeDataTypeManager.TYPE_ARRAY && meta != PcodeDataTypeManager.TYPE_STRUCT) { return false; } - ArrayList res = new ArrayList<>(); - if (!DatatypeFilter.extractPrimitives(dt, MAX_PRIMITIVES, res) || res.isEmpty()) { + PrimitiveExtractor primitives = new PrimitiveExtractor(dt, true, 0, MAX_PRIMITIVES); + if (!primitives.isValid() || primitives.size() == 0 || primitives.containsUnknown() || + !primitives.isAligned() || primitives.containsHoles()) { return false; } - DataType base = res.get(0); + DataType base = primitives.get(0).dt; int baseMeta = PcodeDataTypeManager.getMetatype(base); if (baseMeta != metaType) { return false; } - for (int i = 1; i < res.size(); ++i) { - if (res.get(i) != base) { + for (int i = 1; i < primitives.size(); ++i) { + if (primitives.get(i).dt != base) { return false; } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/MultiMemberAssign.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/MultiMemberAssign.java index 30bf32a66d..21bbd07530 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/MultiMemberAssign.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/MultiMemberAssign.java @@ -77,12 +77,13 @@ public class MultiMemberAssign extends AssignAction { int[] tmpStatus = status.clone(); ArrayList pieces = new ArrayList<>(); ParameterPieces param = new ParameterPieces(); - ArrayList primitives = new ArrayList<>(); - if (!DatatypeFilter.extractPrimitives(dt, 16, primitives) || primitives.isEmpty()) { + PrimitiveExtractor primitives = new PrimitiveExtractor(dt, false, 0, 16); + if (!primitives.isValid() || primitives.size() == 0 || primitives.containsUnknown() || + !primitives.isAligned() || primitives.containsHoles()) { return FAIL; } for (int i = 0; i < primitives.size(); ++i) { - DataType curType = primitives.get(i); + DataType curType = primitives.get(i).dt; if (resource.assignAddressFallback(resourceType, curType, !consumeFromStack, tmpStatus, param) == FAIL) { return FAIL; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/MultiSlotDualAssign.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/MultiSlotDualAssign.java new file mode 100644 index 0000000000..07cd01a368 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/MultiSlotDualAssign.java @@ -0,0 +1,317 @@ +/* ### + * 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. + */ +package ghidra.program.model.lang.protorules; + +import static ghidra.program.model.pcode.AttributeId.*; +import static ghidra.program.model.pcode.ElementId.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map.Entry; + +import ghidra.program.model.address.Address; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.lang.*; +import ghidra.program.model.lang.protorules.PrimitiveExtractor.Primitive; +import ghidra.program.model.pcode.Encoder; +import ghidra.program.model.pcode.Varnode; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; + +/** + * Consume multiple registers from different storage classes to pass a data-type + * + * This action is for calling conventions that can use both floating-point and general purpose registers + * when assigning storage for a single composite data-type, such as the X86-64 System V ABI + */ +public class MultiSlotDualAssign extends AssignAction { + private StorageClass baseType; // Resource list from which to consume general tiles + private StorageClass altType; // Resource list from which to consume alternate tiles + private boolean consumeMostSig; // True if resources are consumed starting with most significant bytes + private boolean justifyRight; // True if initial bytes are padding for odd data-type sizes + private int tileSize; // Number of bytes in a tile + private int baseIter; // Iterator to first element in the base resource list + private int altIter; // Iterator to first element in alternate resource list + + /** + * Find the first ParamEntry matching the baseType, and the first matching altType. + * @throws InvalidInputException if the required elements are not available in the resource list + */ + private void initializeEntries() throws InvalidInputException { + baseIter = -1; + altIter = -1; + for (int i = 0; i < resource.getNumParamEntry(); ++i) { + ParamEntry entry = resource.getEntry(i); + if (baseIter == -1 && entry.isExclusion() && entry.getType() == baseType && + entry.getAllGroups().length == 1) { + baseIter = i; // First matching base resource type + } + if (altIter == -1 && entry.isExclusion() && entry.getType() == altType && + entry.getAllGroups().length == 1) { + altIter = i; // First matching alt resource type + } + } + if (baseIter == -1 || altIter == -1) { + throw new InvalidInputException( + "Could not find matching resources for action: join_dual_class"); + } + tileSize = resource.getEntry(baseIter).getSize(); + if (tileSize != resource.getEntry(altIter).getSize()) { + throw new InvalidInputException( + "Storage class register sizes do not match for action: join_dual_class"); + } + } + + /** + * Get the first unused ParamEntry that matches the given storage class + * @param iter points to the starting entry to search + * @param storage is the given storage class to match + * @param status is the usage information for the entries + * @return the iterator to the unused ParamEntry + */ + private int getFirstUnused(int iter, StorageClass storage, int[] status) { + int endIter = resource.getNumParamEntry(); + for (; iter != endIter; ++iter) { + ParamEntry entry = resource.getEntry(iter); + if (!entry.isExclusion()) { + break; // Reached end of resource list + } + if (entry.getType() != storage || entry.getAllGroups().length != 1) { + continue; // Not a single register from desired resource + } + if (status[entry.getGroup()] != 0) { + continue; // Already consumed + } + return iter; + } + return endIter; + } + + /** + * Get the storage class to use for the specific section of the data-type + * + * For the section starting at -off- extending through -tileSize- bytes, if any primitive + * overlaps the boundary of the section, return -1. Otherwise, if all the primitive data-types + * in the section match the alternate storage class, return 1, or if one or more does not + * match, return 0. The -index- of the first primitive after the start of the section is + * provided and is then updated to be the first primitive after the end of the section. + * @param primitives is the list of primitive data-types making up the data-type + * @param off is the starting offset of the section + * @param index is the index of the first primitive in the section + * @return 0 for a base tile, 1 for an alternate tile, -1 for boundary overlaps + */ + private int getTileClass(PrimitiveExtractor primitives, int off, int[] index) { + int res = 1; + int count = 0; + int endBoundary = off + tileSize; + while (index[0] < primitives.size()) { + Primitive element = primitives.get(index[0]); + if (element.offset < off) { + return -1; + } + if (element.offset >= endBoundary) { + break; + } + if (element.offset + element.dt.getLength() > endBoundary) { + return -1; + } + count += 1; + index[0] += 1; + StorageClass storage = ParamEntry.getBasicTypeClass(element.dt); + if (storage != altType) { + res = 0; + } + } + if (count == 0) { + return -1; // Must be at least one primitive in section + } + return res; + } + + /** + * Constructor for use with decode. Set default configuration. + * @param res is the new resource set to associate with this action + */ + protected MultiSlotDualAssign(ParamListStandard res) { + super(res); + baseType = StorageClass.GENERAL; // Tile from general purpose registers + altType = StorageClass.FLOAT; // Use specialized registers for floating-point components + consumeMostSig = false; + justifyRight = false; + if (res.getEntry(0).isBigEndian()) { + consumeMostSig = true; + justifyRight = true; + } + tileSize = 0; + } + + public MultiSlotDualAssign(StorageClass baseStore, StorageClass altStore, boolean mostSig, + boolean justRight, ParamListStandard res) throws InvalidInputException { + super(res); + baseType = baseStore; + altType = altStore; + consumeMostSig = mostSig; + justifyRight = justRight; + initializeEntries(); + } + + @Override + public AssignAction clone(ParamListStandard newResource) throws InvalidInputException { + return new MultiSlotDualAssign(baseType, altType, consumeMostSig, justifyRight, + newResource); + } + + @Override + public boolean isEquivalent(AssignAction op) { + if (this.getClass() != op.getClass()) { + return false; + } + MultiSlotDualAssign otherAction = (MultiSlotDualAssign) op; + if (consumeMostSig != otherAction.consumeMostSig || + justifyRight != otherAction.justifyRight) { + return false; + } + if (baseIter != otherAction.baseIter || altIter != otherAction.altIter) { + return false; + } + if (baseType != otherAction.baseType || altType != otherAction.altType) { + return false; + } + return true; + } + + @Override + public int assignAddress(DataType dt, PrototypePieces proto, int pos, DataTypeManager dtManager, + int[] status, ParameterPieces res) { + PrimitiveExtractor primitives = new PrimitiveExtractor(dt, false, 0, 1024); + if (!primitives.isValid() || primitives.size() == 0 || primitives.containsHoles()) { + return FAIL; + } + ParameterPieces param = new ParameterPieces(); + int[] primitiveIndex = new int[1]; + primitiveIndex[0] = 0; + int[] tmpStatus = status.clone(); + ArrayList pieces = new ArrayList<>(); + int typeSize = dt.getLength(); + int sizeLeft = typeSize; + int iterBase = baseIter; + int iterAlt = altIter; + int endIter = resource.getNumParamEntry(); + while (sizeLeft > 0) { + int iter; + int iterType = getTileClass(primitives, typeSize - sizeLeft, primitiveIndex); + if (iterType < 0) { + return FAIL; + } + if (iterType == 0) { + iter = iterBase = getFirstUnused(iterBase, baseType, tmpStatus); + } + else { + iter = iterAlt = getFirstUnused(iterAlt, altType, tmpStatus); + } + if (iter == endIter) { + return FAIL; // Out of the particular resource + } + ParamEntry entry = resource.getEntry(iter); + int trialSize = entry.getSize(); + entry.getAddrBySlot(tmpStatus[entry.getGroup()], trialSize, 1, param); + tmpStatus[entry.getGroup()] = -1; // Consume the register + Varnode vn = new Varnode(param.address, trialSize); + pieces.add(vn); + sizeLeft -= trialSize; + } + if (sizeLeft < 0) { // Have odd data-type size + if (justifyRight) { + // Initial bytes of first entry are padding + Varnode vn = pieces.get(0); + Address addr = vn.getAddress().add(-sizeLeft); + int sz = vn.getSize() + sizeLeft; + vn = new Varnode(addr, sz); + pieces.set(0, vn); + } + else { + int end = pieces.size() - 1; + Varnode vn = pieces.get(end); + int sz = vn.getSize() + sizeLeft; + vn = new Varnode(vn.getAddress(), sz); + pieces.set(end, vn); + } + } + System.arraycopy(tmpStatus, 0, status, 0, tmpStatus.length); // Commit resource usage for all the pieces + res.type = dt; + if (pieces.size() == 1) { + res.address = pieces.get(0).getAddress(); + return SUCCESS; + } + res.joinPieces = new Varnode[pieces.size()]; + if (!consumeMostSig) { + for (int i = 0; i < res.joinPieces.length; ++i) { + res.joinPieces[i] = pieces.get(pieces.size() - 1 - i); + } + } + else { + for (int i = 0; i < pieces.size(); ++i) { + res.joinPieces[i] = pieces.get(i); + } + } + res.address = Address.NO_ADDRESS; + return SUCCESS; + } + + @Override + public void encode(Encoder encoder) throws IOException { + encoder.openElement(ELEM_JOIN_DUAL_CLASS); + if (resource.getEntry(0).isBigEndian() != justifyRight) { + encoder.writeBool(ATTRIB_REVERSEJUSTIFY, true); + } + if (baseType != StorageClass.GENERAL) { + encoder.writeString(ATTRIB_STORAGE, baseType.toString()); + } + if (altType != StorageClass.FLOAT) { + encoder.writeString(ATTRIB_B, altType.toString()); + } + encoder.closeElement(ELEM_JOIN); + } + + @Override + public void restoreXml(XmlPullParser parser) throws XmlParseException { + XmlElement elem = parser.start(ELEM_JOIN_DUAL_CLASS.name()); + for (Entry attrib : elem.getAttributes().entrySet()) { + String name = attrib.getKey(); + if (name.equals(ATTRIB_REVERSEJUSTIFY.name())) { + if (SpecXmlUtils.decodeBoolean(attrib.getValue())) { + justifyRight = !justifyRight; + } + } + else if (name.equals(ATTRIB_STORAGE.name()) || name.equals(ATTRIB_A.name())) { + baseType = StorageClass.getClass(attrib.getValue()); + } + else if (name.equals(ATTRIB_B.name())) { + altType = StorageClass.getClass(attrib.getValue()); + } + } + parser.end(elem); + try { + initializeEntries(); + } + catch (InvalidInputException e) { + throw new XmlParseException(e.getMessage()); + } // Need new firstIter + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/PrimitiveExtractor.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/PrimitiveExtractor.java new file mode 100644 index 0000000000..47d467da9a --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/protorules/PrimitiveExtractor.java @@ -0,0 +1,330 @@ +/* ### + * 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. + */ +package ghidra.program.model.lang.protorules; + +import java.util.ArrayList; + +import ghidra.program.model.data.*; +import ghidra.program.model.pcode.PcodeDataTypeManager; + +public class PrimitiveExtractor { + + public static class Primitive { + public DataType dt; // The primitive data-type + public int offset; // Offset within the container + + public Primitive(DataType d, int off) { + dt = d; + offset = off; + } + } + + private ArrayList primitives; // List of extracted primitives + private boolean valid; // Extraction was invalid + private boolean aligned; // True if all primitives are properly aligned + private boolean unknownElements; // True if at least one TYPE_UNKNOWN primitive + private boolean extraSpace; // True if extra space not attributable to padding + private boolean unionInvalid; // True if unions are treated as invalid primitive + + /** + * Check that a big Primitive properly overlaps smaller Primitives + * + * If the big Primitive does not properly overlap the smaller Primitives starting at the given + * point, return -1. Otherwise, if the big Primitive is floating-point, add the overlapped + * primitives to the common refinement list, or if not a floating-point, add the big Primitive + * to the list. (Integer primitives are \e preferred over floating-point primitives in this way) + * Return the index of the next primitive after the overlap. + * @param res holds the common refinement list + * @param small is the list of Primitives that are overlapped + * @param point is the index of the first overlap + * @param big is the big overlapping Primitive + * @return the index of the next Primitive after the overlap or -1 if the overlap is invalid + */ + private int checkOverlap(ArrayList res, ArrayList small, int point, + Primitive big) { + int endOff = big.offset + big.dt.getAlignedLength(); + // If big data-type is a float, let smaller primitives override it, otherwise we keep the big primitive + boolean useSmall = + PcodeDataTypeManager.getMetatype(big.dt) == PcodeDataTypeManager.TYPE_FLOAT; + while (point < small.size()) { + int curOff = small.get(point).offset; + if (curOff >= endOff) { + break; + } + curOff += small.get(point).dt.getAlignedLength(); + if (curOff > endOff) { + return -1; // Improper overlap of the end of big + } + if (useSmall) { + res.add(small.get(point)); + } + point += 1; + } + if (!useSmall) { // If big data-type was preferred + res.add(big); // use big Primitive in the refinement + } + return point; + } + + /** + * Overwrite first list with common refinement of first and second + * + * Given two sets of overlapping Primitives, find a \e common \e refinement of the lists. + * If there is any partial overlap of two Primitives, \b false is returned. + * If the same primitive data-type occurs at the same offset, it is included in the refinement. + * Otherwise an integer data-type is preferred over a floating-point data-type, or a bigger + * primitive is preferred over smaller overlapping primitives. + * The final refinement replaces the \b first list. + * @param first is the first list of Primitives + * @param second is the second list + * @return true if a refinement was successfully constructed + */ + private boolean commonRefinement(ArrayList first, ArrayList second) { + int firstPoint = 0; + int secondPoint = 0; + ArrayList common = new ArrayList<>(); + while (firstPoint < first.size() && secondPoint < second.size()) { + Primitive firstElement = first.get(firstPoint); + Primitive secondElement = second.get(secondPoint); + if (firstElement.offset < secondElement.offset && + firstElement.offset + firstElement.dt.getAlignedLength() <= secondElement.offset) { + common.add(firstElement); + firstPoint += 1; + continue; + } + if (secondElement.offset < firstElement.offset && + secondElement.offset + secondElement.dt.getAlignedLength() <= firstElement.offset) { + common.add(secondElement); + secondPoint += 1; + continue; + } + if (firstElement.dt.getAlignedLength() >= secondElement.dt.getAlignedLength()) { + secondPoint = checkOverlap(common, second, secondPoint, firstElement); + if (secondPoint < 0) { + return false; + } + firstPoint += 1; + } + else { + firstPoint = checkOverlap(common, first, firstPoint, secondElement); + if (firstPoint < 0) { + return false; + } + secondPoint += 1; + } + } + // Add any tail primitives from either list + while (firstPoint < first.size()) { + common.add(first.get(firstPoint)); + firstPoint += 1; + } + while (secondPoint < second.size()) { + common.add(second.get(secondPoint)); + secondPoint += 1; + } + first.clear(); + first.addAll(common); // Replace first with the refinement + return true; + } + + /** + * Form a primitive list for each field of the union. Then, if possible, form a common + * refinement of all the primitive lists and add to the end of this extractor's list. + * @param dt is the union data-type + * @param max is the maximum number primitives allowed for \b this extraction + * @param offset is the starting offset of the union within the parent + * @return true if a common refinement was found and appended + */ + private boolean handleUnion(UnionDataType dt, int max, int offset) { + if (unionInvalid) { + return false; + } + int num = dt.getNumComponents(); + if (num == 0) { + return false; + } + DataTypeComponent curField = dt.getComponent(0); + + PrimitiveExtractor common = new PrimitiveExtractor(curField.getDataType(), false, + offset + curField.getOffset(), max); + if (!common.isValid()) { + return false; + } + for (int i = 1; i < num; ++i) { + curField = dt.getComponent(i); + + PrimitiveExtractor next = new PrimitiveExtractor(curField.getDataType(), false, + offset + curField.getOffset(), max); + if (!next.isValid()) { + return false; + } + if (!commonRefinement(common.primitives, next.primitives)) { + return false; + } + } + if (primitives.size() + common.primitives.size() > max) { + return false; + } + for (int i = 0; i < common.primitives.size(); ++i) { + primitives.add(common.primitives.get(i)); + } + return true; + } + + /** + * An array of the primitive data-types, with their associated offsets, is constructed. + * If the given data-type is already primitive it is put in the array by itself. Otherwise + * if it is composite, its components are recursively added to the array. + * Boolean properties about the primitives encountered are recorded: + * - Are any of the primitives \b undefined + * - Are all the primitives properly aligned. + * + * If a maximum number of extracted primitives is exceeded, or if an illegal + * data-type is encountered (\b void or other internal data-type) false is returned. + * @param dt is the given data-type to extract primitives from + * @param max is the maximum number of primitives to extract before giving up + * @param offset is the starting offset to associate with the first primitive + * @return true if all primitives were extracted + */ + private boolean extract(DataType dt, int max, int offset) { + if (dt instanceof TypeDef) { + dt = ((TypeDef) dt).getBaseDataType(); + } + int metaType = PcodeDataTypeManager.getMetatype(dt); + switch (metaType) { + case PcodeDataTypeManager.TYPE_UNKNOWN: + unknownElements = true; + // fall-thru + case PcodeDataTypeManager.TYPE_INT: + case PcodeDataTypeManager.TYPE_UINT: + case PcodeDataTypeManager.TYPE_BOOL: + case PcodeDataTypeManager.TYPE_CODE: + case PcodeDataTypeManager.TYPE_FLOAT: + case PcodeDataTypeManager.TYPE_PTR: + case PcodeDataTypeManager.TYPE_PTRREL: + if (primitives.size() >= max) { + return false; + } + primitives.add(new Primitive(dt, offset)); + return true; + case PcodeDataTypeManager.TYPE_ARRAY: { + int numEls = ((Array) dt).getNumElements(); + DataType base = ((Array) dt).getDataType(); + for (int i = 0; i < numEls; ++i) { + if (!extract(base, max, offset)) { + return false; + } + offset += base.getAlignedLength(); + } + return true; + } + case PcodeDataTypeManager.TYPE_UNION: + return handleUnion((UnionDataType) dt, max, offset); + case PcodeDataTypeManager.TYPE_STRUCT: + break; + default: + return false; + } + Structure structPtr = (Structure) dt; + boolean isPacked = structPtr.isPackingEnabled(); + DataTypeComponent[] components = structPtr.getDefinedComponents(); + int expectedOff = offset; + for (DataTypeComponent component : components) { + DataType compDT = component.getDataType(); + int curOff = component.getOffset() + offset; + if (!isPacked) { + int align = compDT.getAlignment(); + if (curOff % align != 0) { + aligned = false; + } + int rem = expectedOff % align; + if (rem != 0) { + expectedOff += (align - rem); + } + if (expectedOff != curOff) { + extraSpace = true; + } + } + if (!extract(compDT, max, curOff)) { + return false; + } + expectedOff = curOff + compDT.getAlignedLength(); + } + return true; + } + + /** + * @param dt is data-type extract from + * @param unionIllegal is true if unions encountered during extraction are considered illegal + * @param offset is the starting offset to associate with the data-type + * @param max is the maximum number of primitives to extract before giving up + */ + public PrimitiveExtractor(DataType dt, boolean unionIllegal, int offset, int max) { + primitives = new ArrayList<>(); + valid = true; + aligned = true; + unknownElements = false; + extraSpace = false; + unionInvalid = unionIllegal; + if (!extract(dt, max, offset)) { + valid = false; + } + } + + /** + * @return true if all primitive elements were extracted + */ + public boolean isValid() { + return valid; + } + + /** + * @return true if any extracted element was unknown/undefined + */ + public boolean containsUnknown() { + return unknownElements; + } + + /** + * @return true if all extracted elements are aligned + */ + public boolean isAligned() { + return aligned; + } + + /** + * @return true if there is extra space in the data-type that is not alignment padding + */ + public boolean containsHoles() { + return extraSpace; + } + + /** + * @return the number of primitives extracted + */ + public int size() { + return primitives.size(); + } + + /** + * Get the i-th extracted primitive and its offset + * @param i is the index + * @return the primitive and offset + */ + public Primitive get(int i) { + return primitives.get(i); + } +} 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 6575d10078..430be5e85e 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 @@ -454,6 +454,7 @@ public record ElementId(String name, int id) { public static final ElementId ELEM_HIDDEN_RETURN = new ElementId("hidden_return", 282); public static final ElementId ELEM_JOIN_PER_PRIMITIVE = new ElementId("join_per_primitive", 283); + public static final ElementId ELEM_JOIN_DUAL_CLASS = new ElementId("join_dual_class", 285); - public static final ElementId ELEM_UNKNOWN = new ElementId("XMLunknown", 285); + public static final ElementId ELEM_UNKNOWN = new ElementId("XMLunknown", 286); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java index 736556d37b..9af94f1f9c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/PcodeDataTypeManager.java @@ -541,14 +541,8 @@ public class PcodeDataTypeManager { private void encodeEnum(Encoder encoder, Enum type, int size) throws IOException { encoder.openElement(ELEM_TYPE); encodeNameIdAttributes(encoder, type); + String metatype = type.isSigned() ? "int" : "uint"; long[] keys = type.getValues(); - String metatype = "uint"; - for (long key : keys) { - if (key < 0) { - metatype = "int"; - break; - } - } encoder.writeString(ATTRIB_METATYPE, metatype); encoder.writeSignedInteger(ATTRIB_SIZE, type.getLength()); encoder.writeBool(ATTRIB_ENUM, true); @@ -1292,6 +1286,19 @@ public class PcodeDataTypeManager { if (tp instanceof Array) { return TYPE_ARRAY; } + if (tp instanceof CharDataType) { + return ((CharDataType) tp).isSigned() ? TYPE_INT : TYPE_UINT; + } + if (tp instanceof WideCharDataType || tp instanceof WideChar16DataType || + tp instanceof WideChar32DataType) { + return TYPE_INT; + } + if (tp instanceof Enum) { + return ((Enum) tp).isSigned() ? TYPE_INT : TYPE_UINT; + } + if (tp instanceof FunctionDefinition) { + return TYPE_CODE; + } return TYPE_UNKNOWN; } diff --git a/Ghidra/Processors/x86/data/languages/x86-64-gcc.cspec b/Ghidra/Processors/x86/data/languages/x86-64-gcc.cspec index 5e7bcb462f..4ba8333c33 100644 --- a/Ghidra/Processors/x86/data/languages/x86-64-gcc.cspec +++ b/Ghidra/Processors/x86/data/languages/x86-64-gcc.cspec @@ -80,17 +80,36 @@ + + + + + + + + - + + + + - - + + + + + + + + + + diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/model/lang/AbstractProtoModelTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/model/lang/AbstractProtoModelTest.java index 4577e3d982..37b3398d4f 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/model/lang/AbstractProtoModelTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/model/lang/AbstractProtoModelTest.java @@ -90,6 +90,8 @@ public class AbstractProtoModelTest extends AbstractGenericTest { dtManager.addDataType(new FloatDataType(), null); dtManager.addDataType(new DoubleDataType(), null); dtManager.addDataType(new Float16DataType(), null); + dtManager.addDataType(new Undefined4DataType(), null); + dtManager.addDataType(new Undefined8DataType(), null); } finally { dtManager.endTransaction(txID, true); diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/model/lang/X64PrototypeModelTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/model/lang/X64PrototypeModelTest.java index 329c781da4..7facce4414 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/model/lang/X64PrototypeModelTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/model/lang/X64PrototypeModelTest.java @@ -30,6 +30,63 @@ public class X64PrototypeModelTest extends AbstractProtoModelTest { public void testStdCall() throws Exception { PrototypeModel model = cspec.getCallingConvention("__stdcall"); test(model, "void func(int a,int b)", "void,EDI,ESI"); + test(model, "void func(float,float)", "void,XMM0:4,XMM1:4"); + test(model, "void func(short a,int b,char c)", "void,DI,ESI,DX:1"); + test(model, "void func(long,long)", "void,RDI,RSI"); + test(model, "void func(double,double)", "void,XMM0:8,XMM1:8"); + test(model, "void func(int,float,int,float)", "void,EDI,XMM0:4,ESI,XMM1:4"); + test(model, "void func(float,int,float,int)", "void,XMM0:4,EDI,XMM1:4,ESI"); + test(model, "void func(int,double,double,int)", "void,EDI,XMM0:8,XMM1:8,ESI"); + test(model, "void func(double,long,long,double)", "void,XMM0:8,RDI,RSI,XMM1:8"); + test(model, "void func(long double)", "void,stack8:10"); + test(model, "void func(float,long double,float)", "void,XMM0:4,stack8:10,XMM1:4"); + parseStructure("intfloatpair", "int,float"); + test(model, "void func(intfloatpair)", "void,RDI"); + parseStructure("longfloatpair", "long,float"); + test(model, "void func(int,longfloatpair)", "void,EDI,join XMM0:8 RSI"); + parseStructure("longdoublepair", "long,double"); + test(model, "void func(int,longdoublepair)", "void,EDI,join XMM0:8 RSI"); + parseStructure("intdoublepair", "int,double"); + test(model, "void func(int,intdoublepair)", "void,EDI,join XMM0:8 RSI"); + parseStructure("floatintpair", "float,int"); + test(model, "void func(int,floatintpair)", "void,EDI,RSI"); + parseStructure("doubleintpair", "double,int"); + test(model, "void func(int,doubleintpair)", "void,EDI,join RSI XMM0:8"); + parseStructure("intintfloat", "int,int,float"); + test(model, "void func(int,intintfloat)", "void,EDI,join XMM0:4 RSI"); + parseStructure("intintfloatfloat", "int,int,float,float"); + test(model, "void func(int,intintfloatfloat)", "void,EDI,join XMM0:8 RSI"); + parseStructure("intfloatfloatint", "int,float,float,int"); + test(model, "void func(int,intfloatfloatint)", "void,EDI,join RDX RSI"); + parseStructure("intfloatfloat", "int,float,float"); + test(model, "void func(int,intfloatfloat)", "void,EDI,join XMM0:4 RSI"); + parseStructure("floatfloatpair", "float,float"); + test(model, "void func(int,floatfloatpair)", "void,EDI,XMM0:8"); + parseStructure("doublefloatpair", "double,float"); + test(model, "void func(int,doublefloatpair)", "void,EDI,join XMM1:8 XMM0:8"); + parseStructure("floatfloatfloat", "float,float,float"); + test(model, "void func(floatfloatfloat,long)", "void,join XMM1:4 XMM0:8,RDI"); + parseStructure("intintintint", "int,int,int,int"); + test(model, "void func(intintintint)", "void,join RSI RDI"); + test(model, "void func(int,intintintint)", "void,EDI,join RDX RSI"); + parseStructure("intintintintint", "int,int,int,int,int"); + test(model, "void func(intintintintint)", "void,stack8:20"); + test(model, "void func(float,float,float,float,float,float,float,float,longfloatpair)", + "void,XMM0:4,XMM1:4,XMM2:4,XMM3:4,XMM4:4,XMM5:4,XMM6:4,XMM7:4,stack8:16"); + test(model, "void func(undefined4,undefined8)", "void,EDI,RSI"); + + test(model, "intintintint func(void)", "join RDX RAX"); + test(model, "floatintpair func(void)", "RAX"); + test(model, "longfloatpair func(void)", "join XMM0:8 RAX"); + test(model, "longdoublepair func(void)", "join XMM0:8 RAX"); + test(model, "doubleintpair func(void)", "join RAX XMM0:8"); + test(model, "floatfloatfloat func(void)", "join XMM1:4 XMM0:8"); + parseStructure("doubledoublepair", "double,double"); + test(model, "doubledoublepair func(void)", "join XMM1:8 XMM0:8"); + test(model, "floatfloatpair func(void)", "XMM0:8"); + test(model, "intintintintint func(void)", "RAX,RDI"); + parseStructure("doubleintintint", "double,int,int,int"); + test(model, "doubleintintint func(void)", "RAX,RDI"); } @Test