/* ### * 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. */ #include "fspec.hh" #include "funcdata.hh" /// \brief Find a ParamEntry matching the given storage Varnode /// /// Search through the list backward. /// \param entryList is the list of ParamEntry to search through /// \param vn is the storage to search for /// \return the matching ParamEntry or null const ParamEntry *ParamEntry::findEntryByStorage(const list &entryList,const VarnodeData &vn) { list::const_reverse_iterator iter = entryList.rbegin(); for(;iter!=entryList.rend();++iter) { const ParamEntry &entry(*iter); if (entry.spaceid == vn.space && entry.addressbase == vn.offset && entry.size == vn.size) { return &entry; } } return (const ParamEntry *)0; } /// 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 void ParamEntry::resolveJoin(list &curList) { if (spaceid->getType() != IPTR_JOIN) { joinrec = (JoinRecord *)0; return; } joinrec = spaceid->getManager()->findJoin(addressbase); int4 mingrp = 1000; int4 maxgrp = -1; for(int4 i=0;inumPieces();++i) { const ParamEntry *entry = findEntryByStorage(curList, joinrec->getPiece(i)); if (entry != (const ParamEntry *)0) { if (entry->group < mingrp) mingrp = entry->group; int4 max = entry->group + entry->groupsize; if (max > maxgrp) maxgrp = max; } } if (maxgrp < 0 || mingrp >= 1000) throw LowlevelError(" join must overlap at least one previous entry"); group = mingrp; groupsize = (maxgrp - mingrp); if (groupsize > joinrec->numPieces()) throw LowlevelError(" join must overlap sequential entries"); } /// A ParamEntry with \e join storage must either overlap a single other ParamEntry or /// all pieces must overlap. /// \return \b true if \b this is a join whose pieces do not all overlap bool ParamEntry::isNonOverlappingJoin(void) const { if (joinrec == (JoinRecord *)0) return false; return (joinrec->numPieces() != groupsize); } /// This entry must properly contain the other memory range, and /// the entry properties must be compatible. /// \param op2 is the other entry to compare with \b this /// \return \b true if the other entry is contained bool ParamEntry::contains(const ParamEntry &op2) const { if ((type!=TYPE_UNKNOWN)&&(op2.type != type)) return false; if (spaceid != op2.spaceid) return false; if (op2.addressbase < addressbase) return false; if ((op2.addressbase+op2.size-1) > (addressbase+size-1)) return false; if (alignment != op2.alignment) return false; return true; } /// \param addr is the starting address of the potential containing range /// \param sz is the number of bytes in the range /// \return \b true if the entire ParamEntry fits inside the range bool ParamEntry::containedBy(const Address &addr,int4 sz) const { if (spaceid != addr.getSpace()) return false; if (addressbase < addr.getOffset()) return false; uintb entryoff = addressbase + size-1; uintb rangeoff = addr.getOffset() + sz-1; return (entryoff <= rangeoff); } /// Check if the given memory range is contained in \b this. /// If it is contained, return the endian aware offset of the containment. /// I.e. if the least significant byte of the given range falls on the least significant /// byte of the \b this, return 0. If it intersects the second least significant, return 1, etc. /// \param addr is the starting address of the given memory range /// \param sz is the size of the given memory range in bytes /// \return the endian aware alignment or -1 if the given range isn't contained int4 ParamEntry::justifiedContain(const Address &addr,int4 sz) const { if (joinrec != (JoinRecord *)0) { int4 res = 0; for(int4 i=joinrec->numPieces()-1;i>=0;--i) { // Move from least significant to most const VarnodeData &vdata(joinrec->getPiece(i)); int4 cur = vdata.getAddr().justifiedContain(vdata.size,addr,sz,false); if (cur<0) res += vdata.size; // We skipped this many less significant bytes else { return res + cur; } } return -1; // Not contained at all } if (alignment==0) { // Ordinary endian containment Address entry(spaceid,addressbase); return entry.justifiedContain(size,addr,sz,((flags&force_left_justify)!=0)); } if (spaceid != addr.getSpace()) return -1; uintb startaddr = addr.getOffset(); if (startaddr < addressbase) return -1; uintb endaddr = startaddr + sz - 1; if (endaddr < startaddr) return -1; // Don't allow wrap around if (endaddr > (addressbase+size-1)) return -1; startaddr -= addressbase; endaddr -= addressbase; if (!isLeftJustified()) { // For right justified (big endian), endaddr must be aligned int4 res = (int4)((endaddr+1) % alignment); if (res==0) return 0; return (alignment-res); } return (int4)(startaddr % alignment); } /// \brief Calculate the containing memory range /// /// Pass back the VarnodeData (space,offset,size) of the parameter that would contain /// the given memory range. If \b this contains the range and is \e exclusive, just /// pass back \b this memory range. Otherwise the passed back range will depend on /// alignment. /// \param addr is the starting address of the given range /// \param sz is the size of the given range in bytes /// \param res is the reference to VarnodeData that will be passed back /// \return \b true if the given range is contained at all bool ParamEntry::getContainer(const Address &addr,int4 sz,VarnodeData &res) const { Address endaddr = addr + (sz-1); if (joinrec != (JoinRecord *)0) { for(int4 i=joinrec->numPieces()-1;i>=0;--i) { // Move from least significant to most const VarnodeData &vdata(joinrec->getPiece(i)); if ((addr.overlap(0,vdata.getAddr(),vdata.size) >=0)&& (endaddr.overlap(0,vdata.getAddr(),vdata.size)>=0)) { res = vdata; return true; } } return false; // Not contained at all } Address entry(spaceid,addressbase); if (addr.overlap(0,entry,size)<0) return false; if (endaddr.overlap(0,entry,size)<0) return false; if (alignment==0) { // Ordinary endian containment res.space = spaceid; res.offset = addressbase; res.size = size; return true; } uintb al = (addr.getOffset() - addressbase) % alignment; res.space = spaceid; res.offset = addr.getOffset() - al; res.size = (int4)(endaddr.getOffset()-res.offset) + 1; int4 al2 = res.size % alignment; if (al2 != 0) res.size += (alignment - al2); // Bump up size to nearest alignment return true; } /// \brief Calculate the type of \e extension to expect for the given logical value /// /// Return: /// - CPUI_COPY if no extensions are assumed for small values in this container /// - CPUI_INT_SEXT indicates a sign extension /// - CPUI_INT_ZEXT indicates a zero extension /// - CPUI_PIECE indicates an integer extension based on type of parameter /// /// (A CPUI_FLOAT2FLOAT=float extension is handled by heritage and JoinRecord) /// If returning an extension operator, pass back the container being extended. /// \param addr is the starting address of the logical value /// \param sz is the size of the logical value in bytes /// \param res will hold the passed back containing range /// \return the type of extension OpCode ParamEntry::assumedExtension(const Address &addr,int4 sz,VarnodeData &res) const { if ((flags & (smallsize_zext|smallsize_sext|smallsize_inttype))==0) return CPUI_COPY; if (alignment != 0) { if (sz >= alignment) return CPUI_COPY; } else if (sz >= size) return CPUI_COPY; if (joinrec != (JoinRecord *)0) return CPUI_COPY; if (justifiedContain(addr,sz)!=0) return CPUI_COPY; // (addr,sz) is not justified properly to allow an extension if (alignment == 0) { // If exclusion, take up the whole entry res.space = spaceid; res.offset = addressbase; res.size = size; } else { // Otherwise take up whole alignment res.space = spaceid; int4 alignAdjust = (addr.getOffset() - addressbase) % alignment; res.offset = addr.getOffset() - alignAdjust; res.size = alignment; } if ((flags & smallsize_zext)!=0) return CPUI_INT_ZEXT; if ((flags & smallsize_inttype)!=0) return CPUI_PIECE; return CPUI_INT_SEXT; } /// \brief Calculate the \e slot occupied by a specific address /// /// For \e non-exclusive entries, the memory range can be divided up into /// \b slots, which are chunks that take up a full alignment. I.e. for an entry with /// alignment 4, slot 0 is bytes 0-3 of the range, slot 1 is bytes 4-7, etc. /// Assuming the given address is contained in \b this entry, and we \b skip ahead a number of bytes, /// return the \e slot associated with that byte. /// NOTE: its important that the given address has already been checked for containment. /// \param addr is the given address /// \param skip is the number of bytes to skip ahead /// \return the slot index int4 ParamEntry::getSlot(const Address &addr,int4 skip) const { int4 res = group; if (alignment != 0) { uintb diff = addr.getOffset() + skip - addressbase; int4 baseslot = (int4)diff / alignment; if (isReverseStack()) res += (numslots -1) - baseslot; else res += baseslot; } else if (skip != 0) { res += (groupsize-1); } return res; } /// \brief Calculate the storage address assigned when allocating a parameter of a given size /// /// Assume \b slotnum slots have already been assigned and increment \b slotnum /// by the number of slots used. /// Return an invalid address if the size is too small or if there are not enough slots left. /// \param slotnum is a reference to used slots (which will be updated) /// \param sz is the size of the parameter to allocated /// \return the address of the new parameter (or an invalid address) Address ParamEntry::getAddrBySlot(int4 &slotnum,int4 sz) const { Address res; // Start with an invalid result int4 spaceused; if (sz < minsize) return res; if (alignment == 0) { // If not an aligned entry (allowing multiple slots) if (slotnum != 0) return res; // Can only allocate slot 0 if (sz > size) return res; // Check on maximum size res = Address(spaceid,addressbase); // Get base address of the slot spaceused = size; if (((flags & smallsize_floatext)!=0)&&(sz != size)) { // Do we have an implied floating-point extension AddrSpaceManager *manager = spaceid->getManager(); res = manager->constructFloatExtensionAddress(res,size,sz); return res; } } else { int4 slotsused = sz / alignment; // How many slots does a -sz- byte object need if ( (sz % alignment) != 0) slotsused += 1; if (slotnum + slotsused > numslots) // Check if there are enough slots left return res; spaceused = slotsused * alignment; int4 index; if (isReverseStack()) { index = numslots; index -= slotnum; index -= slotsused; } else index = slotnum; res = Address(spaceid, addressbase + index * alignment); slotnum += slotsused; // Inform caller of number of slots used } if (!isLeftJustified()) // Adjust for right justified (big endian) res = res + (spaceused - sz); return res; } /// \brief Restore the entry from an XML stream /// /// \param el is the root \ element /// \param manage is a manager to resolve address space references /// \param normalstack is \b true if the parameters should be allocated from the front of the range /// \param grouped is \b true if \b this will be grouped with other entries /// \param curList is the list of ParamEntry defined up to this point void ParamEntry::restoreXml(const Element *el,const AddrSpaceManager *manage,bool normalstack,bool grouped,list &curList) { flags = 0; type = TYPE_UNKNOWN; size = minsize = -1; // Must be filled in alignment = 0; // default numslots = 1; groupsize = 1; // default int4 num = el->getNumAttributes(); for(int4 i=0;igetAttributeName(i) ); if (attrname=="minsize") { istringstream i1(el->getAttributeValue(i)); i1.unsetf(ios::dec | ios::hex | ios::oct); i1 >> minsize; } else if (attrname == "size") { // old style istringstream i2(el->getAttributeValue(i)); i2.unsetf(ios::dec | ios::hex | ios::oct); i2 >> alignment; } else if (attrname == "align") { // new style istringstream i4(el->getAttributeValue(i)); i4.unsetf(ios::dec | ios::hex | ios::oct); i4 >> alignment; } else if (attrname == "maxsize") { istringstream i3(el->getAttributeValue(i)); i3.unsetf(ios::dec | ios::hex | ios::oct); i3 >> size; } else if (attrname == "metatype") type = string2metatype(el->getAttributeValue(i)); else if (attrname == "extension") { flags &= ~((uint4)(smallsize_zext | smallsize_sext | smallsize_inttype)); if (el->getAttributeValue(i) == "sign") flags |= smallsize_sext; else if (el->getAttributeValue(i) == "zero") flags |= smallsize_zext; else if (el->getAttributeValue(i) == "inttype") flags |= smallsize_inttype; else if (el->getAttributeValue(i) == "float") flags |= smallsize_floatext; else if (el->getAttributeValue(i) != "none") throw LowlevelError("Bad extension attribute"); } else throw LowlevelError("Unknown ParamEntry attribute: "+attrname); } if ((size==-1)||(minsize==-1)) throw LowlevelError("ParamEntry not fully specified"); if (alignment == size) alignment = 0; Address addr; addr = Address::restoreXml( *el->getChildren().begin(),manage); spaceid = addr.getSpace(); addressbase = addr.getOffset(); if (alignment != 0) { // if ((addressbase % alignment) != 0) // throw LowlevelError("Stack address must match alignment"); numslots = size / alignment; } if (spaceid->isReverseJustified()) { if (spaceid->isBigEndian()) flags |= force_left_justify; else throw LowlevelError("No support for right justification in little endian encoding"); } if (!normalstack) { flags |= reverse_stack; if (alignment != 0) { if ((size % alignment) != 0) throw LowlevelError("For positive stack growth, size must match alignment"); } } if (grouped) flags |= is_grouped; resolveJoin(curList); } /// \brief Check if \b this entry represents a \e joined parameter and requires extra scrutiny /// /// Return value parameter lists allow overlapping entries if one of the overlapping entries /// is a \e joined parameter. In this case the return value recovery logic needs to know /// what portion(s) of the joined parameter are overlapped. This method sets flags on \b this /// to indicate the overlap. /// \param entry is the full parameter list to check for overlaps with \b this void ParamEntry::extraChecks(list &entry) { if (joinrec == (JoinRecord *)0) return; // Nothing to do if not multiprecision if (joinrec->numPieces() != 2) return; const VarnodeData &highPiece(joinrec->getPiece(0)); bool seenOnce = false; list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { // Search for high piece, used as whole/low in another entry AddrSpace *spc = (*iter).getSpace(); uintb off = (*iter).getBase(); int4 sz = (*iter).getSize(); if ((highPiece.offset == off)&&(highPiece.space == spc)&&(highPiece.size == sz)) { if (seenOnce) throw LowlevelError("Extra check hits twice"); seenOnce = true; flags |= extracheck_low; // If found, we must do extra checks on the low } } if (!seenOnce) flags |= extracheck_high; // The default is to do extra checks on the high } /// If the storage is in the \e join space, we count the number of join pieces that overlap something /// in the given list of entries. /// \param curList is the list of entries to check /// \return the number of overlapping pieces int4 ParamEntry::countJoinOverlap(const list &curList) const { if (joinrec == (JoinRecord *)0) return 0; int count = 0; for (int4 i=0;inumPieces();++i) { const ParamEntry *match = findEntryByStorage(curList, joinrec->getPiece(i)); if (match != (const ParamEntry *)0) count += 1; } return count; } /// Entries within a group must be distinguishable by size or by type. /// Throw an exception if the entries aren't distinguishable /// \param entry1 is the first ParamEntry to compare /// \param entry2 is the second ParamEntry to compare void ParamEntry::orderWithinGroup(const ParamEntry &entry1,const ParamEntry &entry2) { if (entry2.minsize > entry1.size || entry1.minsize > entry2.size) return; if (entry1.type != entry2.type) { if (entry1.type == TYPE_UNKNOWN) { throw LowlevelError(" tags with a specific type must come before the general type"); } return; } throw LowlevelError(" tags within a group must be distinguished by size or type"); } ParamListStandard::ParamListStandard(const ParamListStandard &op2) { numgroup = op2.numgroup; entry = op2.entry; spacebase = op2.spacebase; maxdelay = op2.maxdelay; pointermax = op2.pointermax; thisbeforeret = op2.thisbeforeret; resourceTwoStart = op2.resourceTwoStart; populateResolver(); } ParamListStandard::~ParamListStandard(void) { for(int4 i=0;igetIndex(); if (index >= resolverMap.size()) return (const ParamEntry *)0; ParamEntryResolver *resolver = resolverMap[index]; if (resolver == (ParamEntryResolver *)0) return (const ParamEntry *)0; pair res; res = resolver->find(loc.getOffset()); while(res.first != res.second) { 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 return testEntry; } return (const ParamEntry *)0; } int4 ParamListStandard::characterizeAsParam(const Address &loc,int4 size) const { int4 index = loc.getSpace()->getIndex(); if (index >= resolverMap.size()) return 0; ParamEntryResolver *resolver = resolverMap[index]; if (resolver == (ParamEntryResolver *)0) return 0; pair iterpair; iterpair = resolver->find(loc.getOffset()); int4 res = 0; while(iterpair.first != iterpair.second) { const ParamEntry *testEntry = (*iterpair.first).getParamEntry(); if (testEntry->getMinSize() <= size && testEntry->justifiedContain(loc, size)==0) return 1; if (testEntry->isExclusion() && testEntry->containedBy(loc, size)) res = 2; ++iterpair.first; } if (res != 2 && iterpair.first != resolver->end()) { iterpair.second = resolver->find_end(loc.getOffset() + (size-1)); while(iterpair.first != iterpair.second) { const ParamEntry *testEntry = (*iterpair.first).getParamEntry(); if (testEntry->isExclusion() && testEntry->containedBy(loc, size)) { res = 2; break; } ++iterpair.first; } } return res; } /// Given the next data-type and the status of previously allocated slots, /// select the storage location for the parameter. The status array is /// indexed by \e group: a positive value indicates how many \e slots have been allocated /// from that group, and a -1 indicates the group/resource is fully consumed. /// \param tp is the data-type of the next parameter /// \param status is an array marking how many \e slots have already been consumed in a group /// \return the newly assigned address for the parameter Address ParamListStandard::assignAddress(const Datatype *tp,vector &status) const { list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { const ParamEntry &curEntry( *iter ); int4 grp = curEntry.getGroup(); if (status[grp]<0) continue; if ((curEntry.getType() != TYPE_UNKNOWN)&& tp->getMetatype() != curEntry.getType()) continue; // Wrong type Address res = curEntry.getAddrBySlot(status[grp],tp->getSize()); if (res.isInvalid()) continue; // If -tp- doesn't fit an invalid address is returned if (curEntry.isExclusion()) { int4 maxgrp = grp + curEntry.getGroupSize(); for(int4 j=grp;j &proto,TypeFactory &typefactory,vector &res) const { vector status(numgroup,0); if (res.size() == 2) { // Check for hidden parameters defined by the output list res.back().addr = assignAddress(res.back().type,status); // Reserve first param for hidden ret value res.back().flags |= ParameterPieces::hiddenretparm; if (res.back().addr.isInvalid()) throw ParamUnassignedError("Cannot assign parameter address for " + res.back().type->getName()); } for(int4 i=1;igetSize() > pointermax)) { // Datatype is too big // Assume datatype is stored elsewhere and only the pointer is passed AddrSpace *spc = spacebase; if (spc == (AddrSpace*)0) spc = typefactory.getArch()->getDefaultDataSpace(); int4 pointersize = spc->getAddrSize(); int4 wordsize = spc->getWordSize(); Datatype *pointertp = typefactory.getTypePointer(pointersize,proto[i],wordsize); res.back().addr = assignAddress(pointertp,status); res.back().type = pointertp; res.back().flags = ParameterPieces::indirectstorage; } else res.back().addr = assignAddress(proto[i],status); if (res.back().addr.isInvalid()) throw ParamUnassignedError("Cannot assign parameter address for " + proto[i]->getName()); res.back().type = proto[i]; res.back().flags = 0; } } /// Given a set of \b trials (putative Varnode parameters) as ParamTrial objects, /// associate each trial with a model ParamEntry within \b this list. Trials for /// for which there are no matching entries are marked as unused. Any holes /// in the resource list are filled with \e unreferenced trials. The trial list is sorted. /// \param active is the set of \b trials to map and organize void ParamListStandard::buildTrialMap(ParamActive *active) const { vector hitlist; // List of groups for which we have a representative bool seenfloattrial = false; bool seeninttrial = false; for(int4 i=0;igetNumTrials();++i) { ParamTrial ¶mtrial(active->getTrial(i)); const ParamEntry *entrySlot = findEntry(paramtrial.getAddress(),paramtrial.getSize()); // 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) paramtrial.markNoUse(); else { paramtrial.setEntry( entrySlot, 0 ); // Keep track of entry recovered for this trial if (entrySlot->getType() == TYPE_FLOAT) seenfloattrial = true; else seeninttrial = true; // Make sure we list that the entries group is marked int4 grp = entrySlot->getGroup(); while(hitlist.size() <= grp) hitlist.push_back((const ParamEntry *)0); const ParamEntry *lastentry = hitlist[grp]; if (lastentry == (const ParamEntry *)0) hitlist[grp] = entrySlot; // This is the first hit for this group } } // Created unreferenced (unref) ParamTrial for any group that we don't have a representive for // if that group occurs before one where we do have a representative for(int4 i=0;i::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { curentry = &(*iter); if (curentry->getGroup() == i) break; // Find first entry of the missing group } if ((!seenfloattrial)&&(curentry->getType()==TYPE_FLOAT)) continue; // Don't fill in unreferenced floats if we haven't seen any floats if ((!seeninttrial)&&(curentry->getType()!=TYPE_FLOAT)) continue; // Don't fill in unreferenced int if all we have seen is floats int4 sz = curentry->isExclusion() ? curentry->getSize() : curentry->getAlign(); int4 nextslot = 0; Address addr = curentry->getAddrBySlot(nextslot,sz); int4 trialpos = active->getNumTrials(); active->registerTrial(addr,sz); ParamTrial ¶mtrial(active->getTrial(trialpos)); paramtrial.markUnref(); paramtrial.setEntry(curentry,0); } else if (!curentry->isExclusion()) { // For non-exclusion groups, we need to create a secondary hitlist to find holes within the group vector slotlist; for(int4 j=0;jgetNumTrials();++j) { ParamTrial ¶mtrial(active->getTrial(j)); if (paramtrial.getEntry() != curentry) continue; int4 slot = curentry->getSlot(paramtrial.getAddress(),0) - curentry->getGroup(); int4 endslot = curentry->getSlot(paramtrial.getAddress(),paramtrial.getSize()-1) - curentry->getGroup(); if (endslot < slot) { // With reverse stacks, the ending address may be in an earlier slot int4 tmp = slot; slot = endslot; endslot = tmp; } while(slotlist.size() <= endslot) slotlist.push_back(0); while(slot<=endslot) { slotlist[slot] = 1; slot += 1; } } for(int4 j=0;jgetAddrBySlot(nextslot,curentry->getAlign()); int4 trialpos = active->getNumTrials(); active->registerTrial(addr,curentry->getAlign()); ParamTrial ¶mtrial(active->getTrial(trialpos)); paramtrial.markUnref(); paramtrial.setEntry(curentry,0); } } } } active->sortTrials(); } /// \brief Calculate the range of trials in each of the two resource sections /// /// The trials must already be mapped, which should put them in group order. The sections /// split at the group given by \b resourceTwoStart. We pass back the range of trial indices /// for each section. If \b resourceTwoStart is 0, then there is really only one section, and /// the empty range [0,0] is passed back for the second section. /// \param active is the given set of parameter trials /// \param oneStart will pass back the index of the first trial in the first section /// \param oneStop will pass back the index (+1) of the last trial in the first section /// \param twoStart will pass back the index of the first trial in the second section /// \param twoStop will pass back the index (+1) of the last trial in the second section void ParamListStandard::separateSections(ParamActive *active,int4 &oneStart,int4 &oneStop,int4 &twoStart,int4 &twoStop) const { int4 numtrials = active->getNumTrials(); if (resourceTwoStart == 0) { // Only one section oneStart = 0; oneStop = numtrials; twoStart = 0; twoStop = 0; return; } int4 i=0; for(;igetTrial(i)); if (curtrial.getEntry()==(const ParamEntry *)0) continue; if (curtrial.getEntry()->getGroup() >= resourceTwoStart) break; } oneStart = 0; oneStop = i; twoStart = i; twoStop = numtrials; } /// \brief Enforce exclusion rules for the given set of parameter trials /// /// If there are more than one active trials in a single group, /// and if that group is an exclusion group, mark all but the first trial to \e inactive. /// \param active is the set of trials void ParamListStandard::forceExclusionGroup(ParamActive *active) const { int4 curupper = -1; bool exclusion = false; int4 numtrials = active->getNumTrials(); for(int4 i=0;igetTrial(i)); if (curtrial.isActive()) { int4 grp = curtrial.getEntry()->getGroup(); exclusion = curtrial.getEntry()->isExclusion(); if (grp <= curupper) { // If curtrial's group falls below highest group where we have seen an active if (exclusion) curtrial.markInactive(); // mark inactive if it is an exclusion group } else curupper = grp + curtrial.getEntry()->getGroupSize() - 1; // This entry covers some number of groups } } } /// \brief Mark every trial above the first "definitely not used" as \e inactive. /// /// Inspection and marking only occurs within an indicated range of trials, /// allowing floating-point and general purpose resources to be treated separately. /// \param active is the set of trials, which must already be ordered /// \param start is the index of the first trial in the range to consider /// \param stop is the index (+1) of the last trial in the range to consider void ParamListStandard::forceNoUse(ParamActive *active, int4 start, int4 stop) const { bool seendefnouse = false; int4 curgroup = -1; bool exclusion = false; bool alldefnouse = false; for (int4 i = start; i < stop; ++i) { ParamTrial &curtrial(active->getTrial(i)); if (curtrial.getEntry() == (const ParamEntry *) 0) continue; // Already marked as not used int4 grp = curtrial.getEntry()->getGroup(); exclusion = curtrial.getEntry()->isExclusion(); if ((grp <= curgroup) && exclusion) {// If in the same exclusion group if (!curtrial.isDefinitelyNotUsed()) // A single element that might be used alldefnouse = false; // means that the whole group might be used } else { // First trial in a new group (or next element in same non-exclusion group) if (alldefnouse) // If all in the last group were defnotused seendefnouse = true;// then force everything afterword to be defnotused alldefnouse = curtrial.isDefinitelyNotUsed(); curgroup = grp + curtrial.getEntry()->getGroupSize() - 1; } if (seendefnouse) curtrial.markInactive(); } } /// \brief Enforce rules about chains of inactive slots. /// /// If there is a chain of slots whose length is greater than \b maxchain, /// where all trials are \e inactive, mark trials in any later slot as \e inactive. /// Mark any \e inactive trials before this (that aren't in a maximal chain) as active. /// The parameter entries in the model may be split up into different resource sections, /// as in floating-point vs general purpose. This method must be called on a single /// section at a time. The \b start and \b stop indices describe the range of trials /// in the particular section. /// \param active is the set of trials, which must be sorted /// \param maxchain is the maximum number of \e inactive trials to allow in a chain /// \param start is the first index in the range of trials to consider /// \param stop is the last index (+1) in the range of trials to consider /// \param groupstart is the smallest group id in the particular section void ParamListStandard::forceInactiveChain(ParamActive *active,int4 maxchain,int4 start,int4 stop,int4 groupstart) const { bool seenchain = false; int4 chainlength = 0; int4 max = -1; for(int4 i=start;igetTrial(i)); if (trial.getEntry() == (const ParamEntry *)0) continue; // Already know not used if (!trial.isActive()) { if (trial.isUnref()&&active->isRecoverSubcall()) { // If there is no reference to the trial within the function, the only real possibility // is that a register is an input to the calling function and it is being reused (immediately) // to pass the input into the called function. This really can't happen on the stack because // the stack relative caller offset and callee offset are different if (trial.getAddress().getSpace()->getType() == IPTR_SPACEBASE) // So if the parameter is on the stack seenchain = true; // Mark that we have already seen an inactive chain } if (i==start) { chainlength += (trial.slotGroup() - groupstart + 1); } else chainlength += trial.slotGroup() - active->getTrial(i-1).slotGroup(); if (chainlength > maxchain) seenchain = true; } else { chainlength = 0; if (!seenchain) max = i; } if (seenchain) trial.markInactive(); } for(int4 i=start;i<=max;++i) { // Across the range of active trials, fill in "holes" of inactive trials ParamTrial &trial(active->getTrial(i)); if (trial.isDefinitelyNotUsed()) continue; if (!trial.isActive()) trial.markActive(); } } void ParamListStandard::calcDelay(void) { maxdelay = 0; list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { int4 delay = (*iter).getSpace()->getDelay(); if (delay > maxdelay) maxdelay = delay; } } /// Enter all the ParamEntry objects into an interval map (based on address space) void ParamListStandard::populateResolver(void) { int4 maxid = -1; list::iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { int4 id = (*iter).getSpace()->getIndex(); if (id > maxid) maxid = id; } resolverMap.resize(maxid+1, (ParamEntryResolver *)0); int4 position = 0; for(iter=entry.begin();iter!=entry.end();++iter) { ParamEntry *paramEntry = &(*iter); int4 spaceId = paramEntry->getSpace()->getIndex(); ParamEntryResolver *resolver = resolverMap[spaceId]; if (resolver == (ParamEntryResolver *)0) { resolver = new ParamEntryResolver(); resolverMap[spaceId] = resolver; } uintb first = paramEntry->getBase(); uintb last = first + (paramEntry->getSize() - 1); ParamEntryResolver::inittype initData(position,paramEntry); position += 1; resolver->insert(initData,first,last); } } /// \brief Read a \ tag and add it to \b this list /// /// \param el is the \ &effectlist, int4 groupid,bool normalstack,bool autokill,bool splitFloat,bool grouped) { entry.emplace_back(groupid); entry.back().restoreXml(el,manage,normalstack,grouped,entry); if (splitFloat) { if (entry.back().getType() == TYPE_FLOAT) { if (resourceTwoStart >= 0) throw LowlevelError("parameter list floating-point entries must come first"); } else if (resourceTwoStart < 0) resourceTwoStart = groupid; // First time we have seen an integer slot } AddrSpace *spc = entry.back().getSpace(); if (spc->getType() == IPTR_SPACEBASE) spacebase = spc; else if (autokill) // If a register parameter AND we automatically generate killedbycall effectlist.push_back(EffectRecord(entry.back(),EffectRecord::killedbycall)); int4 maxgroup = entry.back().getGroup() + entry.back().getGroupSize(); if (maxgroup > numgroup) numgroup = maxgroup; } /// \brief Read a group of \ tags that are allocated as a group /// /// All ParamEntry objects will share the same \b group id. /// \param el is the \ &effectlist, int4 groupid,bool normalstack,bool autokill,bool splitFloat) { const List &flist(el->getChildren()); List::const_iterator iter = flist.begin(); int4 basegroup = numgroup; ParamEntry *previous1 = (ParamEntry *)0; ParamEntry *previous2 = (ParamEntry *)0; for(;iter!=flist.end();++iter) { const Element *subel = *iter; if (subel->getName() != "pentry") throw LowlevelError("Expected child of : " + subel->getName()); parsePentry(subel, manage, effectlist, basegroup, normalstack, autokill, splitFloat, true); ParamEntry &pentry( entry.back() ); if (pentry.getSpace()->getType() == IPTR_JOIN) throw LowlevelError(" in the join space not allowed in tag"); if (previous1 != (ParamEntry *)0) { ParamEntry::orderWithinGroup(*previous1,pentry); if (previous2 != (ParamEntry *)0) ParamEntry::orderWithinGroup(*previous2,pentry); } previous2 = previous1; previous1 = &pentry; } } void ParamListStandard::fillinMap(ParamActive *active) const { if (active->getNumTrials() == 0) return; // No trials to check buildTrialMap(active); // Associate varnodes with sorted list of parameter locations forceExclusionGroup(active); int4 oneStart,oneStop,twoStart,twoStop; separateSections(active,oneStart,oneStop,twoStart,twoStop); forceNoUse(active,oneStart,oneStop); forceNoUse(active,twoStart,twoStop); // Definitely not used -- overrides active forceInactiveChain(active,2,oneStart,oneStop,0); // Chains of inactivity override later actives forceInactiveChain(active,2,twoStart,twoStop,resourceTwoStart); // Mark every active trial as used for(int4 i=0;igetNumTrials();++i) { ParamTrial ¶mtrial(active->getTrial(i)); if (paramtrial.isActive()) paramtrial.markUsed(); } } bool ParamListStandard::checkJoin(const Address &hiaddr,int4 hisize,const Address &loaddr,int4 losize) const { const ParamEntry *entryHi = findEntry(hiaddr,hisize); if (entryHi == (const ParamEntry *)0) return false; const ParamEntry *entryLo = findEntry(loaddr,losize); if (entryLo == (const ParamEntry *)0) return false; if (entryHi->getGroup() == entryLo->getGroup()) { if (entryHi->isExclusion()||entryLo->isExclusion()) return false; if (!hiaddr.isContiguous(hisize,loaddr,losize)) return false; if (((hiaddr.getOffset() - entryHi->getBase()) % entryHi->getAlign()) != 0) return false; if (((loaddr.getOffset() - entryLo->getBase()) % entryLo->getAlign()) != 0) return false; return true; } else { int4 sizesum = hisize + losize; list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { if ((*iter).getSize() < sizesum) continue; if ((*iter).justifiedContain(loaddr,losize)!=0) continue; if ((*iter).justifiedContain(hiaddr,hisize)!=losize) continue; return true; } } return false; } bool ParamListStandard::checkSplit(const Address &loc,int4 size,int4 splitpoint) const { Address loc2 = loc + splitpoint; int4 size2 = size - splitpoint; const ParamEntry *entryNum = findEntry(loc,splitpoint); if (entryNum == (const ParamEntry *)0) return false; entryNum = findEntry(loc2,size2); if (entryNum == (const ParamEntry *)0) return false; return true; } bool ParamListStandard::possibleParam(const Address &loc,int4 size) const { return ((const ParamEntry *)0 != findEntry(loc,size)); } bool ParamListStandard::possibleParamWithSlot(const Address &loc,int4 size,int4 &slot,int4 &slotsize) const { const ParamEntry *entryNum = findEntry(loc,size); if (entryNum == (const ParamEntry *)0) return false; slot = entryNum->getSlot(loc,0); if (entryNum->isExclusion()) { slotsize = entryNum->getGroupSize(); } else { slotsize = ((size-1) / entryNum->getAlign()) + 1; } return true; } bool ParamListStandard::getBiggestContainedParam(const Address &loc,int4 size,VarnodeData &res) const { int4 index = loc.getSpace()->getIndex(); if (index >= resolverMap.size()) return false; ParamEntryResolver *resolver = resolverMap[index]; if (resolver == (ParamEntryResolver *)0) return false; Address endLoc = loc + (size-1); if (endLoc.getOffset() < loc.getOffset()) return false; // Assume there is no parameter if we see wrapping const ParamEntry *maxEntry = (const ParamEntry *)0; ParamEntryResolver::const_iterator iter = resolver->find_begin(loc.getOffset()); ParamEntryResolver::const_iterator enditer = resolver->find_end(endLoc.getOffset()); while(iter != enditer) { const ParamEntry *testEntry = (*iter).getParamEntry(); ++iter; if (testEntry->containedBy(loc, size)) { if (maxEntry == (const ParamEntry *)0) maxEntry = testEntry; else if (testEntry->getSize() > maxEntry->getSize()) maxEntry = testEntry; } } if (maxEntry != (const ParamEntry *)0) { if (!maxEntry->isExclusion()) return false; res.space = maxEntry->getSpace(); res.offset = maxEntry->getBase(); res.size = maxEntry->getSize(); return true; } return false; } bool ParamListStandard::unjustifiedContainer(const Address &loc,int4 size,VarnodeData &res) const { list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { if ((*iter).getMinSize() > size) continue; int4 just = (*iter).justifiedContain(loc,size); if (just < 0) continue; if (just == 0) return false; (*iter).getContainer(loc,size,res); return true; } return false; } OpCode ParamListStandard::assumedExtension(const Address &addr,int4 size,VarnodeData &res) const { list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { if ((*iter).getMinSize() > size) continue; OpCode ext = (*iter).assumedExtension(addr,size,res); if (ext != CPUI_COPY) return ext; } return CPUI_COPY; } void ParamListStandard::getRangeList(AddrSpace *spc,RangeList &res) const { list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { if ((*iter).getSpace() != spc) continue; uintb baseoff = (*iter).getBase(); uintb endoff = baseoff + (*iter).getSize() - 1; res.insertRange(spc,baseoff,endoff); } } void ParamListStandard::restoreXml(const Element *el,const AddrSpaceManager *manage, vector &effectlist,bool normalstack) { numgroup = 0; spacebase = (AddrSpace *)0; pointermax = 0; thisbeforeret = false; bool splitFloat = true; // True if we should split FLOAT entries into their own resource section bool autokilledbycall = false; for(int4 i=0;igetNumAttributes();++i) { const string &attrname( el->getAttributeName(i) ); if (attrname == "pointermax") { istringstream i1(el->getAttributeValue(i)); i1.unsetf(ios::dec | ios::hex | ios::oct); i1 >> pointermax; } else if (attrname == "thisbeforeretpointer") { thisbeforeret = xml_readbool( el->getAttributeValue(i) ); } else if (attrname == "killedbycall") { autokilledbycall = xml_readbool( el->getAttributeValue(i) ); } else if (attrname == "separatefloat") { splitFloat = xml_readbool( el->getAttributeValue(i) ); } } resourceTwoStart = splitFloat ? -1 : 0; const List &flist(el->getChildren()); List::const_iterator fiter; for(fiter=flist.begin();fiter!=flist.end();++fiter) { const Element *subel = *fiter; if (subel->getName() == "pentry") { parsePentry(subel, manage, effectlist, numgroup, normalstack, autokilledbycall, splitFloat, false); } else if (subel->getName() == "group") { parseGroup(subel, manage, effectlist, numgroup, normalstack, autokilledbycall, splitFloat); } } // Check that any pentry tags with join storage don't overlap following tags for (list::const_iterator eiter=entry.begin();eiter!=entry.end();++eiter) { const ParamEntry &curEntry( *eiter ); if (curEntry.isNonOverlappingJoin()) { if (curEntry.countJoinOverlap(entry) != 1) { throw LowlevelError("pentry tag must be listed after all its overlaps"); } } } calcDelay(); populateResolver(); } ParamList *ParamListStandard::clone(void) const { ParamList *res = new ParamListStandard(*this); return res; } void ParamListRegisterOut::assignMap(const vector &proto,TypeFactory &typefactory,vector &res) const { vector status(numgroup,0); res.emplace_back(); if (proto[0]->getMetatype() != TYPE_VOID) { res.back().addr = assignAddress(proto[0],status); if (res.back().addr.isInvalid()) throw ParamUnassignedError("Cannot assign parameter address for " + proto[0]->getName()); } res.back().type = proto[0]; res.back().flags = 0; } void ParamListRegisterOut::fillinMap(ParamActive *active) const { if (active->getNumTrials() == 0) return; // No trials to check const ParamEntry *bestentry = (const ParamEntry *)0; int4 bestcover = 0; type_metatype bestmetatype = TYPE_PTR; // Find entry which is best covered by the active trials list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { const ParamEntry *curentry = &(*iter); bool putativematch = false; for(int4 j=0;jgetNumTrials();++j) { // Evaluate all trials in terms of current ParamEntry ParamTrial ¶mtrial(active->getTrial(j)); if (paramtrial.isActive()) { int4 res = curentry->justifiedContain(paramtrial.getAddress(),paramtrial.getSize()); if (res >= 0) { paramtrial.setEntry(curentry,res); putativematch = true; } else paramtrial.setEntry((const ParamEntry *)0,0); } else paramtrial.setEntry((const ParamEntry *)0,0); } if (!putativematch) continue; active->sortTrials(); // Calculate number of least justified, contiguous, bytes for this entry int4 offmatch = 0; int4 k; for(k=0;kgetNumTrials();++k) { ParamTrial ¶mtrial(active->getTrial(k)); if (paramtrial.getEntry() == (const ParamEntry *)0) continue; if (offmatch != paramtrial.getOffset()) break; if (((offmatch == 0)&&curentry->isParamCheckLow()) || ((offmatch != 0)&&curentry->isParamCheckHigh())) { // If this is multi-precision // Do extra checks that this portion isn't created normally if (paramtrial.isRemFormed()) break; // Formed as a remainder of dual div/rem operation if (paramtrial.isIndCreateFormed()) break; // Formed indirectly by call } offmatch += paramtrial.getSize(); } if (offmatch < curentry->getMinSize()) // If we didn't match enough to cover minimum size k = 0; // Don't use this entry // Prefer a more generic type restriction if we have it // prefer the larger coverage if ((k==active->getNumTrials())&&((curentry->getType() > bestmetatype)||(offmatch > bestcover))) { bestentry = curentry; bestcover = offmatch; bestmetatype = curentry->getType(); } } if (bestentry==(const ParamEntry *)0) { for(int4 i=0;igetNumTrials();++i) active->getTrial(i).markNoUse(); } else { for(int4 i=0;igetNumTrials();++i) { ParamTrial ¶mtrial(active->getTrial(i)); if (paramtrial.isActive()) { int4 res = bestentry->justifiedContain(paramtrial.getAddress(),paramtrial.getSize()); if (res >= 0) { paramtrial.markUsed(); // Only actives are ever marked used paramtrial.setEntry(bestentry,res); } else { paramtrial.markNoUse(); paramtrial.setEntry((const ParamEntry *)0,0); } } else { paramtrial.markNoUse(); paramtrial.setEntry((const ParamEntry *)0,0); } } active->sortTrials(); } } bool ParamListRegisterOut::possibleParam(const Address &loc,int4 size) const { list::const_iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { if ((*iter).justifiedContain(loc,size)>=0) return true; } return false; } void ParamListRegisterOut::restoreXml(const Element *el,const AddrSpaceManager *manage, vector &effectlist,bool normalstack) { ParamListStandard::restoreXml(el,manage,effectlist,normalstack); list::iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { ParamEntry &curEntry(*iter); curEntry.extraChecks(entry); } } ParamList *ParamListRegisterOut::clone(void) const { ParamList *res = new ParamListRegisterOut(*this); return res; } void ParamListRegister::fillinMap(ParamActive *active) const { if (active->getNumTrials() == 0) return; // No trials to check // Mark anything active as used for(int4 i=0;igetNumTrials();++i) { ParamTrial ¶mtrial(active->getTrial(i)); const ParamEntry *entrySlot = findEntry(paramtrial.getAddress(),paramtrial.getSize()); if (entrySlot == (const ParamEntry *)0) // There may be no matching entry (if the model was recovered late) paramtrial.markNoUse(); else { paramtrial.setEntry( entrySlot,0 ); // Keep track of entry recovered for this trial if (paramtrial.isActive()) paramtrial.markUsed(); } } active->sortTrials(); } ParamList *ParamListRegister::clone(void) const { ParamList *res = new ParamListRegister( *this ); return res; } void ParamListStandardOut::assignMap(const vector &proto,TypeFactory &typefactory,vector &res) const { vector status(numgroup,0); res.emplace_back(); res.back().type = proto[0]; res.back().flags = 0; if (proto[0]->getMetatype() == TYPE_VOID) { return; // Leave the address as invalid } res.back().addr = assignAddress(proto[0],status); if (res.back().addr.isInvalid()) { // Could not assign an address (too big) AddrSpace *spc = spacebase; if (spc == (AddrSpace *)0) spc = typefactory.getArch()->getDefaultDataSpace(); int4 pointersize = spc->getAddrSize(); int4 wordsize = spc->getWordSize(); Datatype *pointertp = typefactory.getTypePointer(pointersize, proto[0], wordsize); res.back().addr = assignAddress(pointertp,status); if (res.back().addr.isInvalid()) throw ParamUnassignedError("Cannot assign return value as a pointer"); res.back().type = pointertp; res.back().flags = ParameterPieces::indirectstorage; res.emplace_back(); // Add extra storage location in the input params res.back().type = pointertp; // that holds a pointer to where the return value should be stored // leave its address invalid, to be filled in by the input list assignMap res.back().flags = ParameterPieces::hiddenretparm; // Mark it as special } } void ParamListStandardOut::restoreXml(const Element *el,const AddrSpaceManager *manage,vector &effectlist,bool normalstack) { ParamListRegisterOut::restoreXml(el,manage,effectlist,normalstack); // Check for double precision entries list::iterator iter; ParamEntry *previous1 = (ParamEntry *)0; ParamEntry *previous2 = (ParamEntry *)0; for(iter=entry.begin();iter!=entry.end();++iter) { ParamEntry &curEntry(*iter); if (previous1 != (ParamEntry *)0) { ParamEntry::orderWithinGroup(*previous1, curEntry); if (previous2 != (ParamEntry *)0) ParamEntry::orderWithinGroup(*previous2, curEntry); } previous2 = previous1; previous1 = &curEntry; } } ParamList *ParamListStandardOut::clone(void) const { ParamList *res = new ParamListStandardOut( *this ); return res; } /// The given set of parameter entries are folded into \b this set. /// Duplicate entries are eliminated. Containing entries subsume what /// they contain. /// \param op2 is the list model to fold into \b this void ParamListMerged::foldIn(const ParamListStandard &op2) { if (entry.empty()) { spacebase = op2.getSpacebase(); entry = op2.getEntry(); return; } if ((spacebase != op2.getSpacebase())&&(op2.getSpacebase() != (AddrSpace *)0)) throw LowlevelError("Cannot merge prototype models with different stacks"); list::const_iterator iter2; for(iter2=op2.getEntry().begin();iter2!=op2.getEntry().end();++iter2) { const ParamEntry &opentry( *iter2 ); int4 typeint = 0; list::iterator iter; for(iter=entry.begin();iter!=entry.end();++iter) { if ((*iter).contains(opentry)) { typeint = 2; break; } if (opentry.contains( *iter )) { typeint = 1; break; } } if (typeint==2) { if ((*iter).getMinSize() != opentry.getMinSize()) typeint = 0; } else if (typeint == 1) { if ((*iter).getMinSize() != opentry.getMinSize()) typeint = 0; else *iter = opentry; // Replace with the containing entry } if (typeint == 0) entry.push_back(opentry); } } ParamList *ParamListMerged::clone(void) const { ParamList *res = new ParamListMerged(*this); return res; } /// Create a new ParamTrial based on the first bytes of the memory range. /// \param sz is the number of bytes to include in the new trial /// \return the new trial ParamTrial ParamTrial::splitHi(int4 sz) const { ParamTrial res(addr,sz,slot); res.flags = flags; return res; } /// Create a new ParamTrial based on the last bytes of the memory range. /// \param sz is the number of bytes to include in the new trial /// \return the new trial ParamTrial ParamTrial::splitLo(int4 sz) const { Address newaddr = addr + (size-sz); ParamTrial res(newaddr,sz,slot+1); res.flags = flags; return res; } /// A new address and size for the memory range is given, which /// must respect the endianness of the putative parameter and /// any existing match with the PrototypeModel /// \param newaddr is the new address /// \param sz is the new size /// \return true if the trial can be shrunk to the new range bool ParamTrial::testShrink(const Address &newaddr,int4 sz) const { Address testaddr; if (addr.isBigEndian()) testaddr = addr + (size - sz); else testaddr = addr; if (testaddr != newaddr) return false; if (entry != (const ParamEntry *)0) return false; // if (entry != (const ParamEntry *)0) { // int4 res = entry->justifiedContain(newaddr,sz); // if (res < 0) return false; // } return true; } /// Trials are sorted primarily by the \e group index assigned by the PrototypeModel. /// Trials within the same group are sorted in address order (or its reverse) /// \param b is the other trial to compare with \b this /// \return \b true if \b this should be ordered before the other trial bool ParamTrial::operator<(const ParamTrial &b) const { if (entry == (const ParamEntry *)0) return false; if (b.entry == (const ParamEntry *)0) return true; int4 grpa = entry->getGroup(); int4 grpb = b.entry->getGroup(); if (grpa != grpb) return (grpa < grpb); if (entry != b.entry) // Compare entry pointers directly return (entry < b.entry); if (entry->isExclusion()) { return (offset < b.offset); } if (addr != b.addr) { if (entry->isReverseStack()) return (b.addr < addr); else return (addr < b.addr); } return (size < b.size); } /// \param recoversub selects whether a sub-function or the active function is being tested ParamActive::ParamActive(bool recoversub) { slotbase = 1; stackplaceholder = -1; numpasses = 0; maxpass = 0; isfullychecked = false; needsfinalcheck = false; recoversubcall = recoversub; } void ParamActive::clear(void) { trial.clear(); slotbase = 1; stackplaceholder = -1; numpasses = 0; isfullychecked = false; } /// A ParamTrial object is created and a slot is assigned. /// \param addr is the starting address of the memory range /// \param sz is the number of bytes in the range void ParamActive::registerTrial(const Address &addr,int4 sz) { trial.push_back(ParamTrial(addr,sz,slotbase)); // It would require too much work to calculate whether a specific data location is changed // by a subfunction, but a fairly strong assumption is that (unless it is explicitly saved) a // register may change and is thus unlikely to be used as a location for passing parameters. // However stack locations saving a parameter across a function call is a common construction // Since this all a heuristic for recovering parameters, we assume this rule is always true // to get an efficient test if (addr.getSpace()->getType() != IPTR_SPACEBASE) trial.back().markKilledByCall(); slotbase += 1; } /// The (index of) the first overlapping trial is returned. /// \param addr is the starting address of the given range /// \param sz is the number of bytes in the range /// \return the index of the overlapping trial, or -1 if no trial overlaps int4 ParamActive::whichTrial(const Address &addr,int4 sz) const { for(int4 i=0;i=0) return i; if (sz<=1) return -1; Address endaddr = addr + (sz-1); if (endaddr.overlap(0,trial[i].getAddress(),trial[i].getSize())>=0) return i; } return -1; } /// Free up the stack placeholder slot, which may cause trial slots to get adjusted void ParamActive::freePlaceholderSlot(void) { for(int4 i=0;i stackplaceholder) trial[i].setSlot(trial[i].getSlot() - 1); } stackplaceholder = -2; slotbase -= 1; // If we've found the placeholder, then the -next- time we // analyze parameters, we will have given all locations the // chance to show up, so we prevent any analysis after -next- maxpass = 0; } /// Delete any trial for which isUsed() returns \b false. /// This is used in conjunction with setting the active Varnodes on a call, so the slot number is /// reordered too. void ParamActive::deleteUnusedTrials(void) { vector newtrials; int4 slot = 1; for(int4 i=0;i= 0) throw LowlevelError("Cannot split parameter when the placeholder has not been recovered"); vector newtrials; int4 slot = trial[i].getSlot(); for(int4 j=0;j slot) newtrials.back().setSlot(oldslot+1); } newtrials.push_back(trial[i].splitHi(sz)); newtrials.push_back(trial[i].splitLo(trial[i].getSize()-sz)); for(int4 j=i+1;j slot) newtrials.back().setSlot(oldslot+1); } slotbase += 1; trial = newtrials; } /// Join the trial at the given slot with the trial in the next slot /// \param slot is the given slot /// \param addr is the address of the new joined memory range /// \param sz is the size of the new memory range void ParamActive::joinTrial(int4 slot,const Address &addr,int4 sz) { if (stackplaceholder >= 0) throw LowlevelError("Cannot join parameters when the placeholder has not been removed"); vector newtrials; int4 sizecheck = 0; for(int4 i=0;igetEntryAddress().isInvalid()) s << " space=\"fspec\""; else { AddrSpace *id = fc->getEntryAddress().getSpace(); a_v(s,"space",id->getName()); // Just append the proper attributes s << ' ' << "offset=\""; printOffset(s,fc->getEntryAddress().getOffset()); s << "\""; } } void FspecSpace::saveXmlAttributes(ostream &s,uintb offset,int4 size) const { FuncCallSpecs *fc = (FuncCallSpecs *)(uintp)offset; if (fc->getEntryAddress().isInvalid()) s << " space=\"fspec\""; else { AddrSpace *id = fc->getEntryAddress().getSpace(); a_v(s,"space",id->getName()); // Just append the proper attributes s << ' ' << "offset=\""; printOffset(s,fc->getEntryAddress().getOffset()); s << "\""; a_v_i(s,"size",size); } } void FspecSpace::printRaw(ostream &s,uintb offset) const { FuncCallSpecs *fc = (FuncCallSpecs *)(uintp)offset; if (fc->getName().size() != 0) s << fc->getName(); else { s << "func_"; fc->getEntryAddress().printRaw(s); } } void FspecSpace::saveXml(ostream &s) const { throw LowlevelError("Should never save fspec space to XML"); } void FspecSpace::restoreXml(const Element *el) { throw LowlevelError("Should never restore fspec space from XML"); } /// The type is set to \e unknown_effect /// \param addr is the start of the memory range /// \param size is the number of bytes in the memory range EffectRecord::EffectRecord(const Address &addr,int4 size) { range.space = addr.getSpace(); range.offset = addr.getOffset(); range.size = size; type = unknown_effect; } /// \param entry is a model of the parameter storage /// \param t is the effect type EffectRecord::EffectRecord(const ParamEntry &entry,uint4 t) { range.space = entry.getSpace(); range.offset = entry.getBase(); range.size = entry.getSize(); type = t; } /// \param data is the memory range affected /// \param t is the effect type EffectRecord::EffectRecord(const VarnodeData &data,uint4 t) { range = data; type = t; } /// Writes just an \ tag. The effect type is indicated by the parent tag. /// \param s is the output stream void EffectRecord::saveXml(ostream &s) const { Address addr(range.space,range.offset); if ((type == unaffected)||(type == killedbycall)||(type == return_address)) addr.saveXml(s,range.size); else throw LowlevelError("Bad EffectRecord type"); } /// Reads an \ tag to get the memory range. The effect type is inherited from the parent. /// \param grouptype is the effect inherited from the parent /// \param el is address element /// \param manage is a manager to resolve address space references void EffectRecord::restoreXml(uint4 grouptype,const Element *el,const AddrSpaceManager *manage) { type = grouptype; range.restoreXml(el,manage); } void ProtoModel::defaultLocalRange(void) { AddrSpace *spc = glb->getStackSpace(); uintb first,last; if (stackgrowsnegative) { // This the normal stack convention // Default locals are negative offsets off the stack last = spc->getHighest(); if (spc->getAddrSize()>=4) first = last - 999999; else if (spc->getAddrSize()>=2) first = last - 9999; else first = last - 99; localrange.insertRange(spc,first,last); } else { // This is the flipped stack convention first = 0; if (spc->getAddrSize()>=4) last = 999999; else if (spc->getAddrSize()>=2) last = 9999; else last = 99; localrange.insertRange(spc,first,last); } } void ProtoModel::defaultParamRange(void) { AddrSpace *spc = glb->getStackSpace(); uintb first,last; if (stackgrowsnegative) { // This the normal stack convention // Default parameters are positive offsets off the stack first = 0; if (spc->getAddrSize()>=4) last = 511; else if (spc->getAddrSize()>=2) last = 255; else last = 15; paramrange.insertRange(spc,first,last); } else { // This is the flipped stack convention last = spc->getHighest(); if (spc->getAddrSize()>=4) first = last - 511; else if (spc->getAddrSize()>=2) first = last - 255; else first = last - 15; paramrange.insertRange(spc,first,last); // Parameters are negative offsets } } /// Generate derived ParamList objects based on a given strategy /// \param strategy is the resource \e strategy: currently "standard" or "register" void ProtoModel::buildParamList(const string &strategy) { if ((strategy == "")||(strategy == "standard")) { input = new ParamListStandard(); output = new ParamListStandardOut(); } else if (strategy == "register") { input = new ParamListRegister(); output = new ParamListRegisterOut(); } else throw LowlevelError("Unknown strategy type: "+strategy); } /// \param g is the Architecture that will own the new prototype model ProtoModel::ProtoModel(Architecture *g) { glb = g; input = (ParamList *)0; output = (ParamList *)0; compatModel = (const ProtoModel *)0; extrapop=0; injectUponEntry = -1; injectUponReturn = -1; stackgrowsnegative = true; // Normal stack parameter ordering hasThis = false; isConstruct = false; defaultLocalRange(); defaultParamRange(); } /// Everything is copied from the given prototype model except the name /// \param nm is the new name for \b this copy /// \param op2 is the prototype model to copy ProtoModel::ProtoModel(const string &nm,const ProtoModel &op2) { glb = op2.glb; name = nm; extrapop = op2.extrapop; if (op2.input != (ParamList *)0) input = op2.input->clone(); else input = (ParamList *)0; if (op2.output != (ParamList *)0) output = op2.output->clone(); else output = (ParamList *)0; effectlist = op2.effectlist; likelytrash = op2.likelytrash; injectUponEntry = op2.injectUponEntry; injectUponReturn = op2.injectUponReturn; localrange = op2.localrange; paramrange = op2.paramrange; stackgrowsnegative = op2.stackgrowsnegative; hasThis = op2.hasThis; isConstruct = op2.isConstruct; if (name == "__thiscall") hasThis = true; compatModel = &op2; } ProtoModel::~ProtoModel(void) { if (input != (ParamList *)0) delete input; if (output != (ParamList *)0) delete output; } /// Test whether one ProtoModel can substituted for another during FuncCallSpecs::deindirect /// Currently this can only happen if one model is a copy of the other except for the /// hasThis boolean property. /// \param op2 is the other ProtoModel to compare with \b this /// \return \b true if the two models are compatible bool ProtoModel::isCompatible(const ProtoModel *op2) const { if (this == op2 || compatModel == op2 || op2->compatModel == this) return true; return false; } /// \brief Calculate input and output storage locations given a function prototype /// /// The data-types of the function prototype are passed in as an ordered list, with the /// first data-type corresponding to the \e return \e value and all remaining /// data-types corresponding to the input parameters. Based on \b this model, a storage location /// is selected for each (input and output) parameter and passed back to the caller. /// The passed back storage locations are ordered similarly, with the output storage /// as the first entry. The model has the option of inserting a \e hidden return value /// pointer in the input storage locations. /// /// A \b void return type is indicated by the formal TYPE_VOID in the (either) list. /// If the model can't map the specific output prototype, the caller has the option of whether /// an exception (ParamUnassignedError) is thrown. If they choose not to throw, /// the unmapped return value is assumed to be \e void. /// \param typelist is the list of data-types from the function prototype /// \param res will hold the storage locations for each parameter /// \param ignoreOutputError is \b true if problems assigning the output parameter are ignored void ProtoModel::assignParameterStorage(const vector &typelist,vector &res,bool ignoreOutputError) { if (ignoreOutputError) { try { output->assignMap(typelist,*glb->types,res); } catch(ParamUnassignedError &err) { res.clear(); res.emplace_back(); // leave address undefined res.back().flags = 0; res.back().type = glb->types->getTypeVoid(); } } else { output->assignMap(typelist,*glb->types,res); } input->assignMap(typelist,*glb->types,res); } /// \brief Look up an effect from the given EffectRecord list /// /// If a given memory range matches an EffectRecord, return the effect type. /// Otherwise return EffectRecord::unknown_effect /// \param efflist is the list of EffectRecords which must be sorted /// \param addr is the starting address of the given memory range /// \param size is the number of bytes in the memory range /// \return the EffectRecord type uint4 ProtoModel::lookupEffect(const vector &efflist,const Address &addr,int4 size) { // Unique is always local to function if (addr.getSpace()->getType()==IPTR_INTERNAL) return EffectRecord::unaffected; EffectRecord cur(addr,size); vector::const_iterator iter; iter = upper_bound(efflist.begin(),efflist.end(),cur,EffectRecord::compareByAddress); // First element greater than cur (address must be greater) // go back one more, and we get first el less or equal to cur if (iter==efflist.begin()) return EffectRecord::unknown_effect; // Can't go back one --iter; Address hit = (*iter).getAddress(); int4 sz = (*iter).getSize(); if (sz == 0 && (hit.getSpace() == addr.getSpace())) // A size of zero indicates the whole space is unaffected return EffectRecord::unaffected; int4 where = addr.overlap(0,hit,sz); if ((where>=0)&&(where+size<=sz)) return (*iter).getType(); return EffectRecord::unknown_effect; } /// \brief Look up a particular EffectRecord from a given list by its Address and size /// /// The index of the matching EffectRecord from the given list is returned. Only the first /// \e listSize elements are examined, which much be sorted by Address. /// If no matching range exists, a negative number is returned. /// - -1 if the Address and size don't overlap any other EffectRecord /// - -2 if there is overlap with another EffectRecord /// /// \param efflist is the given list /// \param listSize is the number of records in the list to search through /// \param addr is the starting Address of the record to find /// \param size is the size of the record to find /// \return the index of the matching record or a negative number int4 ProtoModel::lookupRecord(const vector &efflist,int4 listSize, const Address &addr,int4 size) { if (listSize == 0) return -1; EffectRecord cur(addr,size); vector::const_iterator begiter = efflist.begin(); vector::const_iterator enditer = begiter + listSize; vector::const_iterator iter; iter = upper_bound(begiter,enditer,cur,EffectRecord::compareByAddress); // First element greater than cur (address must be greater) // go back one more, and we get first el less or equal to cur if (iter==efflist.begin()) { Address closeAddr = (*iter).getAddress(); return (closeAddr.overlap(0,addr,size) < 0) ? -1 : -2; } --iter; Address closeAddr =(*iter).getAddress(); int4 sz = (*iter).getSize(); if (addr == closeAddr && size == sz) return iter - begiter; return (addr.overlap(0,closeAddr,sz) < 0) ? -1 : -2; } /// The model is searched for an EffectRecord matching the given range /// and the effect type is returned. If there is no EffectRecord or the /// effect generally isn't known, EffectRecord::unknown_effect is returned. /// \param addr is the starting address of the given memory range /// \param size is the number of bytes in the given range /// \return the EffectRecord type uint4 ProtoModel::hasEffect(const Address &addr,int4 size) const { return lookupEffect(effectlist,addr,size); } /// Read in details about \b this model from a \ tag /// \param el is the \ element void ProtoModel::restoreXml(const Element *el) { int4 numattr = el->getNumAttributes(); bool sawlocalrange = false; bool sawparamrange = false; bool sawretaddr = false; stackgrowsnegative = true; // Default growth direction AddrSpace *stackspc = glb->getStackSpace(); if (stackspc != (AddrSpace *)0) stackgrowsnegative = stackspc->stackGrowsNegative(); // Get growth boolean from stack space itself string strategystring; localrange.clear(); paramrange.clear(); extrapop = -300; hasThis = false; isConstruct = false; effectlist.clear(); injectUponEntry = -1; injectUponReturn = -1; likelytrash.clear(); for(int4 i=0;igetAttributeName(i) == "name") name = el->getAttributeValue(i); else if (el->getAttributeName(i) == "extrapop") { if (el->getAttributeValue(i) == "unknown") extrapop = extrapop_unknown; else { istringstream s(el->getAttributeValue(i)); s.unsetf(ios::dec | ios::hex | ios::oct); s >> extrapop; } } else if (el->getAttributeName(i) == "stackshift") { // Allow this attribute for backward compatibility } else if (el->getAttributeName(i) == "strategy") { strategystring = el->getAttributeValue(i); } else if (el->getAttributeName(i) == "hasthis") { hasThis = xml_readbool(el->getAttributeValue(i)); } else if (el->getAttributeName(i) == "constructor") { isConstruct = xml_readbool(el->getAttributeValue(i)); } else throw LowlevelError("Unknown prototype attribute: "+el->getAttributeName(i)); } if (name == "__thiscall") hasThis = true; if (extrapop == -300) throw LowlevelError("Missing prototype attributes"); buildParamList(strategystring); // Allocate input and output ParamLists const List &list(el->getChildren()); List::const_iterator iter; for(iter=list.begin();iter!=list.end();++iter) { const Element *subnode = *iter; if (subnode->getName() == "input") { input->restoreXml(subnode,glb,effectlist,stackgrowsnegative); if (stackspc != (AddrSpace *)0) { input->getRangeList(stackspc,paramrange); if (!paramrange.empty()) sawparamrange = true; } } else if (subnode->getName() == "output") { output->restoreXml(subnode,glb,effectlist,stackgrowsnegative); } else if (subnode->getName() == "unaffected") { const List &flist(subnode->getChildren()); List::const_iterator fiter; for(fiter=flist.begin();fiter!=flist.end();++fiter) { effectlist.emplace_back(); effectlist.back().restoreXml(EffectRecord::unaffected,*fiter,glb); } } else if (subnode->getName() == "killedbycall") { const List &flist(subnode->getChildren()); List::const_iterator fiter; for(fiter=flist.begin();fiter!=flist.end();++fiter) { effectlist.emplace_back(); effectlist.back().restoreXml(EffectRecord::killedbycall,*fiter,glb); } } else if (subnode->getName() == "returnaddress") { const List &flist(subnode->getChildren()); List::const_iterator fiter; for(fiter=flist.begin();fiter!=flist.end();++fiter) { effectlist.emplace_back(); effectlist.back().restoreXml(EffectRecord::return_address,*fiter,glb); } sawretaddr = true; } else if (subnode->getName() == "localrange") { sawlocalrange = true; const List &sublist(subnode->getChildren()); List::const_iterator subiter; for(subiter=sublist.begin();subiter!=sublist.end();++subiter) { Range range; range.restoreXml(*subiter,glb); localrange.insertRange(range.getSpace(),range.getFirst(),range.getLast()); } } else if (subnode->getName() == "paramrange") { sawparamrange = true; const List &sublist(subnode->getChildren()); List::const_iterator subiter; for(subiter=sublist.begin();subiter!=sublist.end();++subiter) { Range range; range.restoreXml(*subiter,glb); paramrange.insertRange(range.getSpace(),range.getFirst(),range.getLast()); } } else if (subnode->getName() == "likelytrash") { const List &flist(subnode->getChildren()); List::const_iterator fiter; for(fiter=flist.begin();fiter!=flist.end();++fiter) { likelytrash.emplace_back(); likelytrash.back().restoreXml(*fiter,glb); } } else if (subnode->getName() == "pcode") { if (subnode->getAttributeValue("inject") == "uponentry") { injectUponEntry = glb->pcodeinjectlib->restoreXmlInject("Protomodel : "+name, name+"@@inject_uponentry", InjectPayload::CALLMECHANISM_TYPE,subnode); } else { injectUponReturn = glb->pcodeinjectlib->restoreXmlInject("Protomodel : "+name, name+"@@inject_uponreturn", InjectPayload::CALLMECHANISM_TYPE,subnode); } } else if (subnode->getName() == "description") { } else throw LowlevelError("Unknown element in prototype: "+subnode->getName()); } if ((!sawretaddr)&&(glb->defaultReturnAddr.space != (AddrSpace *)0)) { // Provide the default return address, if there isn't a specific one for the model effectlist.push_back(EffectRecord(glb->defaultReturnAddr,EffectRecord::return_address)); } sort(effectlist.begin(),effectlist.end(),EffectRecord::compareByAddress); sort(likelytrash.begin(),likelytrash.end()); if (!sawlocalrange) defaultLocalRange(); if (!sawparamrange) defaultParamRange(); } /// \param isinput is set to \b true to compute scores against the input part of the model /// \param mod is the prototype model to score against /// \param numparam is the presumed number of trials that will constitute the score ScoreProtoModel::ScoreProtoModel(bool isinput,const ProtoModel *mod,int4 numparam) { isinputscore = isinput; model = mod; entry.reserve(numparam); finalscore = -1; mismatch = 0; } /// \param addr is the starting address of the trial /// \param sz is the number of bytes in the trial void ScoreProtoModel::addParameter(const Address &addr,int4 sz) { int4 orig = entry.size(); int4 slot,slotsize; bool isparam; if (isinputscore) isparam = model->possibleInputParamWithSlot(addr,sz,slot,slotsize); else isparam = model->possibleOutputParamWithSlot(addr,sz,slot,slotsize); if (isparam) { entry.emplace_back(); entry.back().origIndex = orig; entry.back().slot = slot; entry.back().size = slotsize; } else { mismatch += 1; } } void ScoreProtoModel::doScore(void) { sort(entry.begin(),entry.end()); // Sort our entries via slot int4 nextfree = 0; // Next slot we expect to see int4 basescore = 0; int4 penalty[4]; penalty[0] = 16; penalty[1] = 10; penalty[2] = 7; penalty[3] = 5; int4 penaltyfinal = 3; int4 mismatchpenalty = 20; for(int4 i=0;i nextfree) { // We have some kind of hole in our slot coverage while(nextfree < p.slot) { if (nextfree < 4) basescore += penalty[nextfree]; else basescore += penaltyfinal; nextfree += 1; } nextfree += p.size; } else if (nextfree > p.slot) { // Some kind of slot duplication basescore += mismatchpenalty; if (p.slot + p.size > nextfree) nextfree = p.slot + p.size; } else { nextfree = p.slot + p.size; } } finalscore = basescore + mismatchpenalty * mismatch; } /// The EffectRecord lists are intersected. Anything in \b this that is not also in the /// given EffectRecord list is removed. /// \param efflist is the given EffectRecord list void ProtoModelMerged::intersectEffects(const vector &efflist) { vector newlist; int4 i = 0; int4 j = 0; while((i &trashlist) { vector newlist; int4 i=0; int4 j=0; while((iglb != glb) throw LowlevelError("Mismatched architecture"); if ((model->input->getType() != ParamList::p_standard)&& (model->input->getType() != ParamList::p_register)) throw LowlevelError("Can only resolve between standard prototype models"); if (input == (ParamList *)0) { // First fold in input = new ParamListMerged(); output = new ParamListStandardOut(*(ParamListStandardOut *)model->output); ((ParamListMerged *)input)->foldIn(*(ParamListStandard *)model->input); // Fold in the parameter lists extrapop = model->extrapop; effectlist = model->effectlist; injectUponEntry = model->injectUponEntry; injectUponReturn = model->injectUponReturn; likelytrash = model->likelytrash; localrange = model->localrange; paramrange = model->paramrange; } else { ((ParamListMerged *)input)->foldIn(*(ParamListStandard *)model->input); // We assume here that the output models are the same, but we don't check if (extrapop != model->extrapop) extrapop = ProtoModel::extrapop_unknown; if ((injectUponEntry != model->injectUponEntry)||(injectUponReturn != model->injectUponReturn)) throw LowlevelError("Cannot merge prototype models with different inject ids"); intersectEffects(model->effectlist); intersectLikelyTrash(model->likelytrash); // Take the union of the localrange and paramrange set::const_iterator iter; for(iter=model->localrange.begin();iter!=model->localrange.end();++iter) localrange.insertRange((*iter).getSpace(),(*iter).getFirst(),(*iter).getLast()); for(iter=model->paramrange.begin();iter!=model->paramrange.end();++iter) paramrange.insertRange((*iter).getSpace(),(*iter).getFirst(),(*iter).getLast()); } } /// The model that best matches the given set of input parameter trials is /// returned. This method currently uses the ScoreProtoModel object to /// score the different prototype models. /// \param active is the set of parameter trials /// \return the prototype model that scores the best ProtoModel *ProtoModelMerged::selectModel(ParamActive *active) const { int4 bestscore = 500; int4 bestindex = -1; for(int4 i=0;igetNumTrials(); ScoreProtoModel scoremodel(true,modellist[i],numtrials); for(int4 j=0;jgetTrial(j) ); if (trial.isActive()) scoremodel.addParameter(trial.getAddress(),trial.getSize()); } scoremodel.doScore(); int4 score = scoremodel.getScore(); if (score < bestscore) { bestscore = score; bestindex = i; if (bestscore == 0) break; // Can't get any lower } } if (bestindex >= 0) return modellist[bestindex]; throw LowlevelError("No model matches : missing default"); } void ProtoModelMerged::restoreXml(const Element *el) { name = el->getAttributeValue("name"); const List &list(el->getChildren()); List::const_iterator iter; for(iter=list.begin();iter!=list.end();++iter) { // A tag for each merged prototype const Element *subel = *iter; ProtoModel *mymodel = glb->getModel( subel->getAttributeValue("name")); if (mymodel == (ProtoModel *)0) throw LowlevelError("Missing prototype model: "+subel->getAttributeValue("name")); foldIn(mymodel); modellist.push_back(mymodel); } ((ParamListMerged *)input)->finalize(); ((ParamListMerged *)output)->finalize(); } void ParameterBasic::setTypeLock(bool val) { if (val) { flags |= ParameterPieces::typelock; if (type->getMetatype() == TYPE_UNKNOWN) // Check if we are locking TYPE_UNKNOWN flags |= ParameterPieces::sizelock; } else flags &= ~((uint4)(ParameterPieces::typelock|ParameterPieces::sizelock)); } void ParameterBasic::setNameLock(bool val) { if (val) flags |= ParameterPieces::namelock; else flags &= ~((uint4)ParameterPieces::namelock); } void ParameterBasic::setThisPointer(bool val) { if (val) flags |= ParameterPieces::isthis; else flags &= ~((uint4)ParameterPieces::isthis); } void ParameterBasic::overrideSizeLockType(Datatype *ct) { if (type->getSize() == ct->getSize()) { if (!isSizeTypeLocked()) throw LowlevelError("Overriding parameter that is not size locked"); type = ct; return; } throw LowlevelError("Overriding parameter with different type size"); } void ParameterBasic::resetSizeLockType(TypeFactory *factory) { if (type->getMetatype() == TYPE_UNKNOWN) return; // Nothing to do int4 size = type->getSize(); type = factory->getBase(size,TYPE_UNKNOWN); } ProtoParameter *ParameterBasic::clone(void) const { ParameterBasic *res = new ParameterBasic(name,addr,type,flags); return res; } const string &ParameterSymbol::getName(void) const { return sym->getName(); } Datatype *ParameterSymbol::getType(void) const { return sym->getType(); } Address ParameterSymbol::getAddress(void) const { return sym->getFirstWholeMap()->getAddr(); } int4 ParameterSymbol::getSize(void) const { return sym->getFirstWholeMap()->getSize(); } bool ParameterSymbol::isTypeLocked(void) const { return sym->isTypeLocked(); } bool ParameterSymbol::isNameLocked(void) const { return sym->isNameLocked(); } bool ParameterSymbol::isSizeTypeLocked(void) const { return sym->isSizeTypeLocked(); } bool ParameterSymbol::isThisPointer(void) const { return sym->isThisPointer(); } bool ParameterSymbol::isIndirectStorage(void) const { return sym->isIndirectStorage(); } bool ParameterSymbol::isHiddenReturn(void) const { return sym->isHiddenReturn(); } bool ParameterSymbol::isNameUndefined(void) const { return sym->isNameUndefined(); } void ParameterSymbol::setTypeLock(bool val) { Scope *scope = sym->getScope(); uint4 attrs = Varnode::typelock; if (!sym->isNameUndefined()) attrs |= Varnode::namelock; if (val) scope->setAttribute(sym,attrs); else scope->clearAttribute(sym,attrs); } void ParameterSymbol::setNameLock(bool val) { Scope *scope = sym->getScope(); if (val) scope->setAttribute(sym,Varnode::namelock); else scope->clearAttribute(sym,Varnode::namelock); } void ParameterSymbol::setThisPointer(bool val) { Scope *scope = sym->getScope(); scope->setThisPointer(sym, val); } void ParameterSymbol::overrideSizeLockType(Datatype *ct) { sym->getScope()->overrideSizeLockType(sym,ct); } void ParameterSymbol::resetSizeLockType(TypeFactory *factory) { sym->getScope()->resetSizeLockType(sym); } ProtoParameter *ParameterSymbol::clone(void) const { throw LowlevelError("Should not be cloning ParameterSymbol"); } Symbol *ParameterSymbol::getSymbol(void) const { return sym; } /// \param sc is the function Scope that will back \b this store /// \param usepoint is the starting address of the function (-1) ProtoStoreSymbol::ProtoStoreSymbol(Scope *sc,const Address &usepoint) { scope = sc; restricted_usepoint = usepoint; outparam = (ProtoParameter *)0; ParameterPieces pieces; pieces.type = scope->getArch()->types->getTypeVoid(); pieces.flags = 0; ProtoStoreSymbol::setOutput(pieces); } ProtoStoreSymbol::~ProtoStoreSymbol(void) { for(int4 i=0;i(inparam[i]); if (res != (ParameterSymbol *)0) return res; if (inparam[i] != (ProtoParameter *)0) delete inparam[i]; res = new ParameterSymbol(); inparam[i] = res; return res; } ProtoParameter *ProtoStoreSymbol::setInput(int4 i, const string &nm,const ParameterPieces &pieces) { ParameterSymbol *res = getSymbolBacked(i); res->sym = scope->getCategorySymbol(0,i); SymbolEntry *entry; Address usepoint; bool isindirect = (pieces.flags & ParameterPieces::indirectstorage) != 0; bool ishidden = (pieces.flags & ParameterPieces::hiddenretparm) != 0; if (res->sym != (Symbol *)0) { entry = res->sym->getFirstWholeMap(); if ((entry->getAddr() != pieces.addr)||(entry->getSize() != pieces.type->getSize())) { scope->removeSymbol(res->sym); res->sym = (Symbol *)0; } } if (res->sym == (Symbol *)0) { if (scope->discoverScope(pieces.addr,pieces.type->getSize(),usepoint) == (Scope *)0) usepoint = restricted_usepoint; res->sym = scope->addSymbol(nm,pieces.type,pieces.addr,usepoint)->getSymbol(); scope->setCategory(res->sym,0,i); if (isindirect || ishidden) { uint4 mirror = 0; if (isindirect) mirror |= Varnode::indirectstorage; if (ishidden) mirror |= Varnode::hiddenretparm; scope->setAttribute(res->sym,mirror); } return res; } if (res->sym->isIndirectStorage() != isindirect) { if (isindirect) scope->setAttribute(res->sym,Varnode::indirectstorage); else scope->clearAttribute(res->sym,Varnode::indirectstorage); } if (res->sym->isHiddenReturn() != ishidden) { if (ishidden) scope->setAttribute(res->sym,Varnode::hiddenretparm); else scope->clearAttribute(res->sym,Varnode::hiddenretparm); } if ((nm.size()!=0)&&(nm!=res->sym->getName())) scope->renameSymbol(res->sym,nm); if (pieces.type != res->sym->getType()) scope->retypeSymbol(res->sym,pieces.type); return res; } void ProtoStoreSymbol::clearInput(int4 i) { Symbol *sym = scope->getCategorySymbol(0,i); if (sym != (Symbol *)0) { scope->setCategory(sym,-1,0); // Remove it from category list scope->removeSymbol(sym); // Remove it altogether } // Renumber any category 0 symbol with index greater than i int4 sz = scope->getCategorySize(0); for(int4 j=i+1;jgetCategorySymbol(0,j); if (sym != (Symbol *)0) scope->setCategory(sym,0,j-1); } } void ProtoStoreSymbol::clearAllInputs(void) { scope->clearCategory(0); } int4 ProtoStoreSymbol::getNumInputs(void) const { return scope->getCategorySize(0); } ProtoParameter *ProtoStoreSymbol::getInput(int4 i) { Symbol *sym = scope->getCategorySymbol(0,i); if (sym == (Symbol *)0) return (ProtoParameter *)0; ParameterSymbol *res = getSymbolBacked(i); res->sym = sym; return res; } ProtoParameter *ProtoStoreSymbol::setOutput(const ParameterPieces &piece) { if (outparam != (ProtoParameter *)0) delete outparam; outparam = new ParameterBasic("",piece.addr,piece.type,piece.flags); return outparam; } void ProtoStoreSymbol::clearOutput(void) { ParameterPieces pieces; pieces.type = scope->getArch()->types->getTypeVoid(); pieces.flags = 0; setOutput(pieces); } ProtoParameter *ProtoStoreSymbol::getOutput(void) { return outparam; } ProtoStore *ProtoStoreSymbol::clone(void) const { ProtoStoreSymbol *res; res = new ProtoStoreSymbol(scope,restricted_usepoint); delete res->outparam; if (outparam != (ProtoParameter *)0) res->outparam = outparam->clone(); else res->outparam = (ProtoParameter *)0; return res; } void ProtoStoreSymbol::saveXml(ostream &s) const { // Do not store anything explicitly for a symboltable backed store // as the symboltable will be stored separately } void ProtoStoreSymbol::restoreXml(const Element *el,ProtoModel *model) { throw LowlevelError("Do not restore symbol-backed prototype through this interface"); } /// \param vt is the \b void data-type used for an unspecified return value ProtoStoreInternal::ProtoStoreInternal(Datatype *vt) { voidtype = vt; outparam = (ProtoParameter *)0; ParameterPieces pieces; pieces.type = voidtype; pieces.flags = 0; ProtoStoreInternal::setOutput(pieces); } ProtoStoreInternal::~ProtoStoreInternal(void) { if (outparam != (ProtoParameter *)0) delete outparam; for(int4 i=0;i=sz) return; if (inparam[i] != (ProtoParameter *)0) delete inparam[i]; inparam[i] = (ProtoParameter *)0; for(int4 j=i+1;j i inparam[j-1] = inparam[j]; inparam[j] = (ProtoParameter *)0; } while(inparam.back() == (ProtoParameter *)0) inparam.pop_back(); } void ProtoStoreInternal::clearAllInputs(void) { for(int4 i=0;i=inparam.size()) return (ProtoParameter *)0; return inparam[i]; } ProtoParameter *ProtoStoreInternal::setOutput(const ParameterPieces &piece) { if (outparam != (ProtoParameter *)0) delete outparam; outparam = new ParameterBasic("",piece.addr,piece.type,piece.flags); return outparam; } void ProtoStoreInternal::clearOutput(void) { if (outparam != (ProtoParameter *)0) delete outparam; outparam = new ParameterBasic(voidtype); } ProtoParameter *ProtoStoreInternal::getOutput(void) { return outparam; } ProtoStore *ProtoStoreInternal::clone(void) const { ProtoStoreInternal *res = new ProtoStoreInternal(voidtype); delete res->outparam; if (outparam != (ProtoParameter *)0) res->outparam = outparam->clone(); else res->outparam = (ProtoParameter *)0; for(int4 i=0;iclone(); res->inparam.push_back(param); } return res; } void ProtoStoreInternal::saveXml(ostream &s) const { s << "\n"; if (outparam != (ProtoParameter *)0) { s << "isTypeLocked()) a_v_b(s,"typelock",true); s << ">\n"; outparam->getAddress().saveXml(s); outparam->getType()->saveXml(s); s << "\n"; } else { s << "\n \n \n\n"; } for(int4 i=0;igetName().size()!=0) a_v(s,"name",param->getName()); if (param->isTypeLocked()) a_v_b(s,"typelock",true); if (param->isNameLocked()) a_v_b(s,"namelock",true); if (param->isThisPointer()) a_v_b(s,"thisptr",true); if (param->isIndirectStorage()) a_v_b(s,"indirectstorage",true); if (param->isHiddenReturn()) a_v_b(s,"hiddenretparm",true); s << ">\n"; param->getAddress().saveXml(s); param->getType()->saveXml(s); s << "\n"; } s << "\n"; } void ProtoStoreInternal::restoreXml(const Element *el,ProtoModel *model) { if (el->getName() != "internallist") throw LowlevelError("Mismatched ProtoStore tag: ProtoStoreInternal did not get "); Architecture *glb = model->getArch(); const List &list(el->getChildren()); List::const_iterator iter; vector pieces; vector namelist; bool addressesdetermined = true; pieces.push_back( ParameterPieces() ); // Push on placeholder for output pieces namelist.push_back("ret"); pieces.back().type = outparam->getType(); pieces.back().flags = 0; if (outparam->isTypeLocked()) pieces.back().flags |= ParameterPieces::typelock; if (outparam->isIndirectStorage()) pieces.back().flags |= ParameterPieces::indirectstorage; if (outparam->getAddress().isInvalid()) addressesdetermined = false; for(iter=list.begin();iter!=list.end();++iter) { // This is only the input params const Element *subel = *iter; string name; uint4 flags = 0; for(int4 i=0;igetNumAttributes();++i) { const string &attr( subel->getAttributeName(i) ); if (attr == "name") name = subel->getAttributeValue(i); else if (attr == "typelock") { if (xml_readbool(subel->getAttributeValue(i))) flags |= ParameterPieces::typelock; } else if (attr == "namelock") { if (xml_readbool(subel->getAttributeValue(i))) flags |= ParameterPieces::namelock; } else if (attr == "thisptr") { if (xml_readbool(subel->getAttributeValue(i))) flags |= ParameterPieces::isthis; } else if (attr == "indirectstorage") { if (xml_readbool(subel->getAttributeValue(i))) flags |= ParameterPieces::indirectstorage; } else if (attr == "hiddenretparm") { if (xml_readbool(subel->getAttributeValue(i))) flags |= ParameterPieces::hiddenretparm; } } if ((flags & ParameterPieces::hiddenretparm) == 0) namelist.push_back(name); pieces.emplace_back(); ParameterPieces &curparam( pieces.back() ); const List &sublist(subel->getChildren()); List::const_iterator subiter; subiter = sublist.begin(); curparam.addr = Address::restoreXml(*subiter,glb); ++subiter; curparam.type = glb->types->restoreXmlType(*subiter); curparam.flags = flags; if (curparam.addr.isInvalid()) addressesdetermined = false; } ProtoParameter *curparam; if (!addressesdetermined) { // If addresses for parameters are not provided, use // the model to derive them from type info vector typelist; for(int4 i=0;i addrPieces; model->assignParameterStorage(typelist,addrPieces,true); addrPieces.swap(pieces); uint4 k = 0; for(uint4 i=0;isetTypeLock((pieces[0].flags & ParameterPieces::typelock)!=0); } uint4 j=1; for(uint4 i=1;isetTypeLock((pieces[0].flags & ParameterPieces::typelock)!=0); // Has output's typelock continue; // increment i but not j } curparam = setInput(i-1,namelist[j],pieces[i]); curparam->setTypeLock((pieces[i].flags & ParameterPieces::typelock)!=0); curparam->setNameLock((pieces[i].flags & ParameterPieces::namelock)!=0); j = j + 1; } } /// This is called after a new prototype is established (via restoreXml or updateAllTypes) /// It makes sure that if the ProtoModel calls for a "this" parameter, then the appropriate parameter /// is explicitly marked as the "this". void FuncProto::updateThisPointer(void) { if (!model->hasThisPointer()) return; int4 numInputs = store->getNumInputs(); if (numInputs == 0) return; ProtoParameter *param = store->getInput(0); if (param->isHiddenReturn()) { if (numInputs < 2) return; param = store->getInput(1); } param->setThisPointer(true); } /// If the \e effectlist for \b this is non-empty, it contains the complete set of /// EffectRecords. Save just those that override the underlying list from ProtoModel /// \param s is the stream to write to void FuncProto::saveEffectXml(ostream &s) const { if (effectlist.empty()) return; vector unaffectedList; vector killedByCallList; const EffectRecord *retAddr = (const EffectRecord *)0; for(vector::const_iterator iter=effectlist.begin();iter!=effectlist.end();++iter) { const EffectRecord &curRecord( *iter ); uint4 type = model->hasEffect(curRecord.getAddress(), curRecord.getSize()); if (type == curRecord.getType()) continue; if (curRecord.getType() == EffectRecord::unaffected) unaffectedList.push_back(&curRecord); else if (curRecord.getType() == EffectRecord::killedbycall) killedByCallList.push_back(&curRecord); else if (curRecord.getType() == EffectRecord::return_address) retAddr = &curRecord; } if (!unaffectedList.empty()) { s << " \n"; for(int4 i=0;isaveXml(s); s << '\n'; } s << " \n"; } if (!killedByCallList.empty()) { s << " \n"; for(int4 i=0;isaveXml(s); s << '\n'; } s << " \n"; } if (retAddr != (const EffectRecord *)0) { s << " \n "; retAddr->saveXml(s); s << "\n \n"; } } /// If the -likelytrash- list is not empty it overrides the underlying ProtoModel's list. /// Write any VarnodeData that does not appear in the ProtoModel to the XML stream. /// \param s is the stream to write to void FuncProto::saveLikelyTrashXml(ostream &s) const { if (likelytrash.empty()) return; vector::const_iterator iter1,iter2; iter1 = model->trashBegin(); iter2 = model->trashEnd(); s << " \n"; for(vector::const_iterator iter=likelytrash.begin();iter!=likelytrash.end();++iter) { const VarnodeData &cur(*iter); if (binary_search(iter1,iter2,cur)) continue; // Already exists in ProtoModel s << " saveXmlAttributes(s,cur.offset,cur.size); s << "/>\n"; } s << " \n"; } /// EffectRecords read into \e effectlist by restoreXml() override the list from ProtoModel. /// If this list is not empty, set up \e effectlist as a complete override containing /// all EffectRecords from ProtoModel plus all the overrides. void FuncProto::restoreEffectXml(void) { if (effectlist.empty()) return; vector tmpList; tmpList.swap(effectlist); for(vector::const_iterator iter=model->effectBegin();iter!=model->effectEnd();++iter) { effectlist.push_back(*iter); } bool hasNew = false; int4 listSize = effectlist.size(); for(vector::const_iterator iter=tmpList.begin();iter!=tmpList.end();++iter) { const EffectRecord &curRecord( *iter ); int4 off = ProtoModel::lookupRecord(effectlist, listSize, curRecord.getAddress(), curRecord.getSize()); if (off == -2) throw LowlevelError("Partial overlap of prototype override with existing effects"); else if (off >= 0) { // Found matching record, change its type effectlist[off] = curRecord; } else { effectlist.push_back(curRecord); hasNew = true; } } if (hasNew) sort(effectlist.begin(),effectlist.end(),EffectRecord::compareByAddress); } /// VarnodeData read into \e likelytrash by restoreXml() are additional registers over /// what is already in ProtoModel. Make \e likelytrash in \b this a complete list by /// merging in everything from ProtoModel. void FuncProto::restoreLikelyTrashXml(void) { if (likelytrash.empty()) return; vector tmpList; tmpList.swap(likelytrash); vector::const_iterator iter1,iter2; iter1 = model->trashBegin(); iter2 = model->trashEnd(); for(vector::const_iterator iter=iter1;iter!=iter2;++iter) likelytrash.push_back(*iter); for(vector::const_iterator iter=tmpList.begin();iter!=tmpList.end();++iter) { if (!binary_search(iter1,iter2,*iter)) likelytrash.push_back(*iter); // Add in the new register } sort(likelytrash.begin(),likelytrash.end()); } /// Prepend the indicated number of input parameters to \b this. /// The new parameters have a data-type of xunknown4. If they were /// originally locked, the existing parameters are preserved. /// \param paramshift is the number of parameters to add (must be >0) void FuncProto::paramShift(int4 paramshift) { if ((model == (ProtoModel *)0)||(store == (ProtoStore *)0)) throw LowlevelError("Cannot parameter shift without a model"); vector nmlist; vector typelist; bool isdotdotdot = false; TypeFactory *typefactory = model->getArch()->types; if (isOutputLocked()) typelist.push_back( getOutputType() ); else typelist.push_back( typefactory->getTypeVoid() ); nmlist.push_back(""); Datatype *extra = typefactory->getBase(4,TYPE_UNKNOWN); // The extra parameters have this type for(int4 i=0;igetName()); typelist.push_back( param->getType() ); } } else isdotdotdot = true; // Reassign the storage locations for this new parameter list vector pieces; model->assignParameterStorage(typelist,pieces,false); delete store; // This routine always converts -this- to have a ProtoStoreInternal store = new ProtoStoreInternal(typefactory->getTypeVoid()); store->setOutput(pieces[0]); uint4 j=1; for(uint4 i=1;isetInput(i-1,"rethidden",pieces[i]); continue; // increment i but not j } store->setInput(j,nmlist[j],pieces[i]); j = j + 1; } setInputLock(true); setDotdotdot(isdotdotdot); } /// \brief If \b this has a \e merged model, pick the most likely model (from the merged set) /// /// The given parameter trials are used to pick from among the merged ProtoModels and /// \b this prototype is changed (specialized) to the pick /// \param active is the set of parameter trials to evaluate with void FuncProto::resolveModel(ParamActive *active) { if (model == (ProtoModel *)0) return; if (!model->isMerged()) return; // Already been resolved ProtoModelMerged *mergemodel = (ProtoModelMerged *)model; ProtoModel *newmodel = mergemodel->selectModel(active); setModel(newmodel); // we don't need to remark the trials, as this is accomplished by the ParamList::fillinMap method } FuncProto::FuncProto(void) { model = (ProtoModel *)0; store = (ProtoStore *)0; flags = 0; injectid = -1; returnBytesConsumed = 0; } /// \param op2 is the other function prototype to copy into \b this void FuncProto::copy(const FuncProto &op2) { model = op2.model; extrapop = op2.extrapop; flags = op2.flags; if (store != (ProtoStore *)0) delete store; if (op2.store != (ProtoStore *)0) store = op2.store->clone(); else store = (ProtoStore *)0; effectlist = op2.effectlist; likelytrash = op2.likelytrash; injectid = op2.injectid; } void FuncProto::copyFlowEffects(const FuncProto &op2) { flags &= ~((uint4)(is_inline|no_return)); flags |= op2.flags & (is_inline|no_return); injectid = op2.injectid; } /// Establish a specific prototype model for \b this function prototype. /// Some basic properties are inherited from the model, otherwise parameters /// are unchanged. /// \param m is the new prototype model to set void FuncProto::setModel(ProtoModel *m) { if (m != (ProtoModel *)0) { int4 expop = m->getExtraPop(); // If a model previously existed don't overwrite extrapop with unknown if ((model == (ProtoModel *)0)||(expop != ProtoModel::extrapop_unknown)) extrapop = expop; if (m->hasThisPointer()) flags |= has_thisptr; if (m->isConstructor()) flags |= is_constructor; model = m; } else { model = m; extrapop = ProtoModel::extrapop_unknown; } flags &= ~((uint4)unknown_model); // Model is not "unknown" (even if null pointer is passed in) } /// The full function prototype is (re)set from a model, names, and data-types /// The new input and output parameters are both assumed to be locked. /// \param pieces is the raw collection of names and data-types void FuncProto::setPieces(const PrototypePieces &pieces) { if (pieces.model != (ProtoModel *)0) setModel(pieces.model); vector typelist; vector nmlist; typelist.push_back(pieces.outtype); nmlist.push_back(""); for(int4 i=0;igetOutput()->getType(); int4 num = store->getNumInputs(); for(int4 i=0;igetInput(i); pieces.intypes.push_back(param->getType()); pieces.innames.push_back(param->getName()); } pieces.dotdotdot = isDotdotdot(); } /// Input parameters are set based on an existing function Scope /// and if there is no prototype model the default model is set. /// Parameters that are added to \b this during analysis will automatically /// be reflected in the symbol table. /// This should only be called during initialization of \b this prototype. /// \param s is the Scope to set /// \param startpoint is a usepoint to associate with the parameters void FuncProto::setScope(Scope *s,const Address &startpoint) { store = new ProtoStoreSymbol(s,startpoint); if (model == (ProtoModel *)0) setModel(s->getArch()->defaultfp); } /// A prototype model is set, and any parameters added to \b this during analysis /// will be backed internally. /// \param m is the prototype model to set /// \param vt is the default \e void data-type to use if the return-value remains unassigned void FuncProto::setInternal(ProtoModel *m,Datatype *vt) { store = new ProtoStoreInternal(vt); if (model == (ProtoModel *)0) setModel(m); } FuncProto::~FuncProto(void) { if (store != (ProtoStore *)0) delete store; } bool FuncProto::isInputLocked(void) const { if ((flags&voidinputlock)!=0) return true; if (numParams()==0) return false; ProtoParameter *param = getParam(0); if (param->isTypeLocked()) return true; return false; } /// The lock on the data-type of input parameters is set as specified. /// A \b true value indicates that future analysis will not change the /// number of input parameters or their data-type. Zero parameters /// or \e void can be locked. /// \param val is \b true to indicate a lock, \b false for unlocked void FuncProto::setInputLock(bool val) { if (val) flags |= modellock; // Locking input locks the model int4 num = numParams(); if (num == 0) { flags = val ? (flags|voidinputlock) : (flags& ~((uint4)voidinputlock)); return; } for(int4 i=0;isetTypeLock(val); } } /// The lock of the data-type of the return value is set as specified. /// A \b true value indicates that future analysis will not change the /// presence of or the data-type of the return value. A \e void return /// value can be locked. /// \param val is \b true to indicate a lock, \b false for unlocked void FuncProto::setOutputLock(bool val) { if (val) flags |= modellock; // Locking output locks the model store->getOutput()->setTypeLock(val); } /// Provide a hint as to how many bytes of the return value are important. /// The smallest hint is used to inform the dead-code removal algorithm. /// \param val is the hint (number of bytes or 0 for all bytes) /// \return \b true if the smallest hint has changed bool FuncProto::setReturnBytesConsumed(int4 val) { if (val == 0) return false; if (returnBytesConsumed == 0 || val < returnBytesConsumed) { returnBytesConsumed = val; return true; } return false; } /// \brief Assuming \b this prototype is locked, calculate the \e extrapop /// /// If \e extrapop is unknown and \b this prototype is locked, try to directly /// calculate what the \e extrapop should be. This is really only designed to work with /// 32-bit x86 binaries. void FuncProto::resolveExtraPop(void) { if (!isInputLocked()) return; int4 numparams = numParams(); if (isDotdotdot()) { if (numparams != 0) // If this is a "standard" varargs, with fixed initial parameters setExtraPop(4); // then this must be __cdecl return; // otherwise we can't resolve the extrapop, as in the FARPROC prototype } int4 expop = 4; // Extrapop is at least 4 for the return address for(int4 i=0;igetAddress() ); if (addr.getSpace()->getType() != IPTR_SPACEBASE) continue; int4 cur = (int4)addr.getOffset() + param->getSize(); cur = (cur+3)&0xffffffc; // Must be 4-byte aligned if (cur > expop) expop = cur; } setExtraPop(expop); } void FuncProto::clearUnlockedInput(void) { if (isInputLocked()) return; store->clearAllInputs(); } void FuncProto::clearUnlockedOutput(void) { ProtoParameter *outparam = getOutput(); if (outparam->isTypeLocked()) { if (outparam->isSizeTypeLocked()) { if (model != (ProtoModel *)0) outparam->resetSizeLockType(getArch()->types); } } else store->clearOutput(); returnBytesConsumed = 0; } void FuncProto::clearInput(void) { store->clearAllInputs(); flags &= ~((uint4)voidinputlock); // If a void was locked in clear it } void FuncProto::cancelInjectId(void) { injectid = -1; flags &= ~((uint4)is_inline); } /// \brief Update input parameters based on Varnode trials /// /// If the input parameters are locked, don't do anything. Otherwise, /// given a list of Varnodes and their associated trial information, /// create an input parameter for each trial in order, grabbing data-type /// information from the Varnode. Any old input parameters are cleared. /// \param data is the function containing the trial Varnodes /// \param triallist is the list of Varnodes /// \param activeinput is the trial container void FuncProto::updateInputTypes(Funcdata &data,const vector &triallist,ParamActive *activeinput) { if (isInputLocked()) return; // Input is locked, do no updating store->clearAllInputs(); int4 count = 0; int4 numtrials = activeinput->getNumTrials(); for(int4 i=0;igetTrial(i)); if (trial.isUsed()) { Varnode *vn = triallist[trial.getSlot()-1]; if (vn->isMark()) continue; ParameterPieces pieces; if (vn->isPersist()) { int4 sz; pieces.addr = data.findDisjointCover(vn, sz); if (sz == vn->getSize()) pieces.type = vn->getHigh()->getType(); else pieces.type = data.getArch()->types->getBase(sz, TYPE_UNKNOWN); pieces.flags = 0; } else { pieces.addr = trial.getAddress(); pieces.type = vn->getHigh()->getType(); pieces.flags = 0; } store->setInput(count,"",pieces); count += 1; vn->setMark(); } } for(int4 i=0;iclearMark(); updateThisPointer(); } /// \brief Update input parameters based on Varnode trials, but do not store the data-type /// /// This is accomplished in the same way as if there were data-types but instead of /// pulling a data-type from the Varnode, only the size is used. /// Undefined data-types are pulled from the given TypeFactory /// \param data is the function containing the trial Varnodes /// \param triallist is the list of Varnodes /// \param activeinput is the trial container void FuncProto::updateInputNoTypes(Funcdata &data,const vector &triallist,ParamActive *activeinput) { if (isInputLocked()) return; // Input is locked, do no updating store->clearAllInputs(); int4 count = 0; int4 numtrials = activeinput->getNumTrials(); TypeFactory *factory = data.getArch()->types; for(int4 i=0;igetTrial(i)); if (trial.isUsed()) { Varnode *vn = triallist[trial.getSlot()-1]; if (vn->isMark()) continue; ParameterPieces pieces; if (vn->isPersist()) { int4 sz; pieces.addr = data.findDisjointCover(vn, sz); pieces.type = factory->getBase(sz, TYPE_UNKNOWN); pieces.flags = 0; } else { pieces.addr = trial.getAddress(); pieces.type = factory->getBase(vn->getSize(),TYPE_UNKNOWN); pieces.flags = 0; } store->setInput(count,"",pieces); count += 1; vn->setMark(); // Make sure vn is used only once } } for(int4 i=0;iclearMark(); } /// \brief Update the return value based on Varnode trials /// /// If the output parameter is locked, don't do anything. Otherwise, /// given a list of (at most 1) Varnode, create a return value, grabbing /// data-type information from the Varnode. Any old return value is removed. /// \param triallist is the list of Varnodes void FuncProto::updateOutputTypes(const vector &triallist) { ProtoParameter *outparm = getOutput(); if (!outparm->isTypeLocked()) { if (triallist.empty()) { store->clearOutput(); return; } } else if (outparm->isSizeTypeLocked()) { if (triallist.empty()) return; if ((triallist[0]->getAddr() == outparm->getAddress())&&(triallist[0]->getSize() == outparm->getSize())) outparm->overrideSizeLockType(triallist[0]->getHigh()->getType()); return; } else return; // Locked if (triallist.empty()) return; // If we reach here, output is not locked, not sizelocked, and there is a valid trial ParameterPieces pieces; pieces.addr = triallist[0]->getAddr(); pieces.type = triallist[0]->getHigh()->getType(); pieces.flags = 0; store->setOutput(pieces); } /// \brief Update the return value based on Varnode trials, but don't store the data-type /// /// If the output parameter is locked, don't do anything. Otherwise, /// given a list of (at most 1) Varnode, create a return value, grabbing /// size information from the Varnode. An undefined data-type is created from the /// given TypeFactory. Any old return value is removed. /// \param triallist is the list of Varnodes /// \param factory is the given TypeFactory void FuncProto::updateOutputNoTypes(const vector &triallist,TypeFactory *factory) { if (isOutputLocked()) return; if (triallist.empty()) { store->clearOutput(); return; } ParameterPieces pieces; pieces.type = factory->getBase(triallist[0]->getSize(),TYPE_UNKNOWN); pieces.addr = triallist[0]->getAddr(); pieces.flags = 0; store->setOutput(pieces); } /// \brief Set \b this entire function prototype based on a list of names and data-types. /// /// Prototype information is provided as separate lists of names and data-types, where /// the first entry corresponds to the output parameter (return value) and the remaining /// entries correspond to input parameters. Storage locations and hidden return parameters are /// calculated, creating a complete function protototype. Existing locks are overridden. /// \param namelist is the list of parameter names /// \param typelist is the list of data-types /// \param dtdtdt is \b true if the new prototype accepts variable argument lists void FuncProto::updateAllTypes(const vector &namelist,const vector &typelist, bool dtdtdt) { setModel(model); // This resets extrapop store->clearAllInputs(); store->clearOutput(); flags &= ~((uint4)voidinputlock); setDotdotdot(dtdtdt); vector pieces; // Calculate what memory locations hold each type try { model->assignParameterStorage(typelist,pieces,false); store->setOutput(pieces[0]); uint4 j=1; for(uint4 i=1;isetInput(i-1,"rethidden",pieces[i]); continue; // increment i but not j } store->setInput(i-1,namelist[j],pieces[i]); j = j + 1; } } catch(ParamUnassignedError &err) { flags |= error_inputparam; } updateThisPointer(); } /// \brief Calculate the effect \b this has an a given storage location /// /// For a storage location that is active before and after a call to a function /// with \b this prototype, we determine the type of side-effect the function /// will have on the storage. /// \param addr is the starting address of the storage location /// \param size is the number of bytes in the storage /// \return the type of side-effect: EffectRecord::unaffected, EffectRecord::killedbycall, etc. uint4 FuncProto::hasEffect(const Address &addr,int4 size) const { if (effectlist.empty()) return model->hasEffect(addr,size); return ProtoModel::lookupEffect(effectlist,addr,size); } vector::const_iterator FuncProto::effectBegin(void) const { if (effectlist.empty()) return model->effectBegin(); return effectlist.begin(); } vector::const_iterator FuncProto::effectEnd(void) const { if (effectlist.empty()) return model->effectEnd(); return effectlist.end(); } /// \return the iterator to the start of the list vector::const_iterator FuncProto::trashBegin(void) const { if (likelytrash.empty()) return model->trashBegin(); return likelytrash.begin(); } /// \return the iterator to the end of the list vector::const_iterator FuncProto::trashEnd(void) const { if (likelytrash.empty()) return model->trashEnd(); return likelytrash.end(); } /// \brief Decide whether a given storage location could be, or could hold, an input parameter /// /// If the input is locked, check if the location overlaps one of the current parameters. /// Otherwise, check if the location overlaps an entry in the prototype model. /// Return: /// - 0 if the location neither contains or is contained by a parameter storage location /// - 1 if the location is contained by a parameter storage location /// - 2 if the location contains a parameter storage location /// \param addr is the starting address of the given storage location /// \param size is the number of bytes in the storage /// \return the characterization code int4 FuncProto::characterizeAsInputParam(const Address &addr,int4 size) const { if (!isDotdotdot()) { // If the proto is varargs, go straight to the model if ((flags&voidinputlock)!=0) return 0; int4 num = numParams(); if (num > 0) { bool locktest = false; // Have tested against locked symbol int4 characterCode = 0; for(int4 i=0;iisTypeLocked()) continue; locktest = true; Address iaddr = param->getAddress(); // If the parameter already exists, the varnode must be justified in the parameter relative // to the endianness of the space, irregardless of the forceleft flag if (iaddr.justifiedContain(param->getSize(),addr,size,false)==0) return 1; if (iaddr.containedBy(param->getSize(), addr, size)) characterCode = 2; } if (locktest) return characterCode; } } return model->characterizeAsInputParam(addr, size); } /// \brief Decide whether a given storage location could be an input parameter /// /// If the input is locked, check if the location matches one of the current parameters. /// Otherwise, check if the location \e could be a parameter based on the /// prototype model. /// \param addr is the starting address of the given storage location /// \param size is the number of bytes in the storage /// \return \b false if the location is definitely not an input parameter bool FuncProto::possibleInputParam(const Address &addr,int4 size) const { if (!isDotdotdot()) { // If the proto is varargs, go straight to the model if ((flags&voidinputlock)!=0) return false; int4 num = numParams(); if (num > 0) { bool locktest = false; // Have tested against locked symbol for(int4 i=0;iisTypeLocked()) continue; locktest = true; Address iaddr = param->getAddress(); // If the parameter already exists, the varnode must be justified in the parameter relative // to the endianness of the space, irregardless of the forceleft flag if (iaddr.justifiedContain(param->getSize(),addr,size,false)==0) return true; } if (locktest) return false; } } return model->possibleInputParam(addr,size); } /// \brief Decide whether a given storage location could be a return value /// /// If the output is locked, check if the location matches the current return value. /// Otherwise, check if the location \e could be a return value based on the /// prototype model. /// \param addr is the starting address of the given storage location /// \param size is the number of bytes in the storage /// \return \b false if the location is definitely not the return value bool FuncProto::possibleOutputParam(const Address &addr,int4 size) const { if (isOutputLocked()) { ProtoParameter *outparam = getOutput(); if (outparam->getType()->getMetatype() == TYPE_VOID) return false; Address iaddr = outparam->getAddress(); // If the output is locked, the varnode must be justified in the location relative // to the endianness of the space, irregardless of the forceleft flag if (iaddr.justifiedContain(outparam->getSize(),addr,size,false)==0) return true; return false; } return model->possibleOutputParam(addr,size); } /// \brief Check if the given storage location looks like an \e unjustified input parameter /// /// The storage for a value may be contained in a normal parameter location but be /// unjustified within that container, i.e. the least significant bytes are not being used. /// If this is the case, pass back the full parameter location and return \b true. /// If the input is locked, checking is againt the set parameters, otherwise the /// check is against the prototype model. /// \param addr is the starting address of the given storage /// \param size is the number of bytes in the given storage /// \param res is the full parameter storage to pass back /// \return \b true if the given storage is unjustified within its parameter container bool FuncProto::unjustifiedInputParam(const Address &addr,int4 size,VarnodeData &res) const { if (!isDotdotdot()) { // If the proto is varargs, go straight to the model if ((flags&voidinputlock)!=0) return false; int4 num = numParams(); if (num > 0) { bool locktest = false; // Have tested against locked symbol for(int4 i=0;iisTypeLocked()) continue; locktest = true; Address iaddr = param->getAddress(); // If the parameter already exists, test if -addr- -size- is improperly contained in param int4 just = iaddr.justifiedContain(param->getSize(),addr,size,false); if (just ==0) return false; // Contained but not improperly if (just > 0) { res.space = iaddr.getSpace(); res.offset = iaddr.getOffset(); res.size = param->getSize(); return true; } } if (locktest) return false; } } return model->unjustifiedInputParam(addr,size,res); } /// \brief Pass-back the biggest input parameter contained within the given range /// /// \param loc is the starting address of the given range /// \param size is the number of bytes in the range /// \param res will hold the parameter storage description being passed back /// \return \b true if there is at least one parameter contained in the range bool FuncProto::getBiggestContainedInputParam(const Address &loc,int4 size,VarnodeData &res) const { if (!isDotdotdot()) { // If the proto is varargs, go straight to the model if ((flags&voidinputlock)!=0) return false; int4 num = numParams(); if (num > 0) { bool locktest = false; // Have tested against locked symbol res.size = 0; for(int4 i=0;iisTypeLocked()) continue; locktest = true; Address iaddr = param->getAddress(); if (iaddr.containedBy(param->getSize(), loc, size)) { if (param->getSize() > res.size) { res.space = iaddr.getSpace(); res.offset = iaddr.getOffset(); res.size = param->getSize(); } } } if (locktest) return (res.size == 0); } } return model->getBiggestContainedInputParam(loc,size,res); } /// \brief Decide if \b this can be safely restricted to match another prototype /// /// Do \b this and another given function prototype share enough of /// their model, that if we restrict \b this to the other prototype, we know /// we won't miss data-flow. /// \param op2 is the other restricting prototype /// \return \b true if the two prototypes are compatible enough to restrict bool FuncProto::isCompatible(const FuncProto &op2) const { if (!model->isCompatible(op2.model)) return false; if (op2.isOutputLocked()) { if (isOutputLocked()) { ProtoParameter *out1 = store->getOutput(); ProtoParameter *out2 = op2.store->getOutput(); if (*out1 != *out2) return false; } } if ((extrapop != ProtoModel::extrapop_unknown)&& (extrapop != op2.extrapop)) return false; if (isDotdotdot() != op2.isDotdotdot()) { // Mismatch in varargs if (op2.isDotdotdot()) { // If -this- is a generic prototype, then the trials // are still setup to recover varargs even though // the prototype hasn't been marked as varargs if (isInputLocked()) return false; } else return false; } if (injectid != op2.injectid) return false; if ((flags&(is_inline|no_return)) != (op2.flags&(is_inline|no_return))) return false; if (effectlist.size() != op2.effectlist.size()) return false; for(int4 i=0;igetName() << ' '; else s << "(no model) "; getOutputType()->printRaw(s); s << ' ' << funcname << '('; int4 num = numParams(); for(int4 i=0;igetType()->printRaw(s); } if (isDotdotdot()) { if (num!=0) s << ','; s << "..."; } s << ") extrapop=" << dec << extrapop; } /// \brief Save \b this to an XML stream as a \ tag. /// /// Save everything under the control of this prototype, which /// may \e not include input parameters, as these are typically /// controlled by the function's symbol table scope. /// \param s is the output stream void FuncProto::saveXml(ostream &s) const { s << " getName()); if (extrapop == ProtoModel::extrapop_unknown) a_v(s,"extrapop","unknown"); else a_v_i(s,"extrapop",extrapop); if (isDotdotdot()) a_v_b(s,"dotdotdot",true); if (isModelLocked()) a_v_b(s,"modellock",true); if ((flags&voidinputlock)!=0) a_v_b(s,"voidlock",true); if (isInline()) a_v_b(s,"inline",true); if (isNoReturn()) a_v_b(s,"noreturn",true); if (hasCustomStorage()) a_v_b(s,"custom",true); if (isConstructor()) a_v_b(s,"constructor",true); if (isDestructor()) a_v_b(s,"destructor",true); s << ">\n"; ProtoParameter *outparam = store->getOutput(); s << " isTypeLocked()) a_v_b(s,"typelock",true); s << ">\n "; outparam->getAddress().saveXml(s,outparam->getSize()); outparam->getType()->saveXml(s); s << " \n"; saveEffectXml(s); saveLikelyTrashXml(s); if (injectid >= 0) { Architecture *glb = model->getArch(); s << " " << glb->pcodeinjectlib->getCallFixupName(injectid) << "\n"; } store->saveXml(s); // Store any internally backed prototyped symbols s << " \n"; } /// \brief Restore \b this from an XML stream /// /// The backing store for the parameters must already be established using either /// setStore() or setInternal(). /// \param el is the \ XML element /// \param glb is the Architecture owning the prototype void FuncProto::restoreXml(const Element *el,Architecture *glb) { // Model must be set first if (store == (ProtoStore *)0) throw LowlevelError("Prototype storage must be set before restoring FuncProto"); ProtoModel *mod = (ProtoModel *)0; bool seenextrapop = false; bool seenunknownmod = false; int4 readextrapop; int4 num = el->getNumAttributes(); flags = 0; injectid = -1; for(int4 i=0;igetAttributeName(i) ); if (attrname == "model") { const string &modelname( el->getAttributeValue(i) ); if ((modelname == "default")||(modelname.size()==0)) mod = glb->defaultfp; // Get default model else if (modelname == "unknown") { mod = glb->defaultfp; // Use the default seenunknownmod = true; } else mod = glb->getModel(modelname); } else if (attrname == "extrapop") { seenextrapop = true; const string &expopval( el->getAttributeValue(i) ); if (expopval == "unknown") readextrapop = ProtoModel::extrapop_unknown; else { istringstream i1(expopval); i1.unsetf(ios::dec | ios::hex | ios::oct); i1 >> readextrapop; } } else if (attrname == "modellock") { if (xml_readbool(el->getAttributeValue(i))) flags |= modellock; } else if (attrname == "dotdotdot") { if (xml_readbool(el->getAttributeValue(i))) flags |= dotdotdot; } else if (attrname == "voidlock") { if (xml_readbool(el->getAttributeValue(i))) flags |= voidinputlock; } else if (attrname == "inline") { if (xml_readbool(el->getAttributeValue(i))) flags |= is_inline; } else if (attrname == "noreturn") { if (xml_readbool(el->getAttributeValue(i))) flags |= no_return; } else if (attrname == "custom") { if (xml_readbool(el->getAttributeValue(i))) flags |= custom_storage; } else if (attrname == "constructor") { if (xml_readbool(el->getAttributeValue(i))) flags |= is_constructor; } else if (attrname == "destructor") { if (xml_readbool(el->getAttributeValue(i))) flags |= is_destructor; } } if (mod != (ProtoModel *)0) // If a model was specified setModel(mod); // This sets extrapop to model default if (seenextrapop) // If explicitly set extrapop = readextrapop; if (seenunknownmod) flags |= unknown_model; const List &list(el->getChildren()); List::const_iterator iter = list.begin(); const Element *subel = (const Element *)0; if (iter != list.end()) { subel = *iter; ++iter; } if (subel != (const Element *)0) { ParameterPieces outpieces; bool outputlock = false; if (subel->getName() == "returnsym") { num = subel->getNumAttributes(); for(int4 i=0;igetAttributeName(i) ); if (attrname == "typelock") outputlock = xml_readbool(subel->getAttributeValue(i)); } const List &list2(subel->getChildren()); List::const_iterator riter = list2.begin(); int4 tmpsize; outpieces.addr = Address::restoreXml(*riter,glb,tmpsize); ++riter; outpieces.type = glb->types->restoreXmlType(*riter); outpieces.flags = 0; } else if (subel->getName() == "addr") { // Old-style specification of return (supported partially for backward compat) int4 tmpsize; outpieces.addr = Address::restoreXml(subel,glb,tmpsize); outpieces.type = glb->types->restoreXmlType(*iter); outpieces.flags = 0; ++iter; } else throw LowlevelError("Missing tag"); store->setOutput(outpieces); // output may be missing storage at this point but ProtoStore should fillin store->getOutput()->setTypeLock(outputlock); } else throw LowlevelError("Missing tag"); if (((flags&voidinputlock)!=0)||(isOutputLocked())) flags |= modellock; for(;iter!=list.end();++iter) { if ((*iter)->getName() == "unaffected") { const List &list2((*iter)->getChildren()); List::const_iterator iter2 = list2.begin(); while(iter2 != list2.end()) { effectlist.emplace_back(); effectlist.back().restoreXml(EffectRecord::unaffected,*iter2,glb); ++iter2; } } else if ((*iter)->getName() == "killedbycall") { const List &list2((*iter)->getChildren()); List::const_iterator iter2 = list2.begin(); while(iter2 != list2.end()) { effectlist.emplace_back(); effectlist.back().restoreXml(EffectRecord::killedbycall,*iter2,glb); ++iter2; } } else if ((*iter)->getName() == "returnaddress") { const List &list2((*iter)->getChildren()); List::const_iterator iter2 = list2.begin(); while(iter2 != list2.end()) { effectlist.emplace_back(); effectlist.back().restoreXml(EffectRecord::return_address,*iter2,glb); ++iter2; } } else if ((*iter)->getName() == "likelytrash") { const List &list2((*iter)->getChildren()); List::const_iterator iter2 = list2.begin(); while(iter2 != list2.end()) { likelytrash.emplace_back(); likelytrash.back().restoreXml(*iter2,glb); ++iter2; } } else if ((*iter)->getName() == "inject") { injectid = glb->pcodeinjectlib->getPayloadId(InjectPayload::CALLFIXUP_TYPE,(*iter)->getContent()); flags |= is_inline; } else if ((*iter)->getName() == "internallist") { store->restoreXml(*iter,model); } } restoreEffectXml(); restoreLikelyTrashXml(); if (!isModelLocked()) { if (isInputLocked()) flags |= modellock; } if (extrapop == ProtoModel::extrapop_unknown) resolveExtraPop(); ProtoParameter *outparam = store->getOutput(); if ((outparam->getType()->getMetatype()!=TYPE_VOID)&&outparam->getAddress().isInvalid()) { throw LowlevelError(" tag must include a valid storage address"); } updateThisPointer(); } /// \brief Calculate the stack offset of \b this call site /// /// The given Varnode must be the input to the CALL in the \e placeholder slot /// and must be defined by a COPY from a Varnode in the stack space. /// Calculate the offset of the stack-pointer at the point of \b this CALL, /// relative to the incoming stack-pointer value. This can be obtained /// either be looking at a stack parameter, or if there is no stack parameter, /// the stack-pointer \e placeholder can be used. /// If the \e placeholder has no other purpose, remove it. /// \param data is the calling function /// \param phvn is the Varnode in the \e placeholder slot for \b this CALL void FuncCallSpecs::resolveSpacebaseRelative(Funcdata &data,Varnode *phvn) { Varnode *refvn = phvn->getDef()->getIn(0); AddrSpace *spacebase = refvn->getSpace(); if (spacebase->getType() != IPTR_SPACEBASE) { data.warningHeader("This function may have set the stack pointer"); } stackoffset = refvn->getOffset(); if (stackPlaceholderSlot >= 0) { if (op->getIn(stackPlaceholderSlot) == phvn) { data.opRemoveInput(op,stackPlaceholderSlot); clearStackPlaceholderSlot(); return; } } if (isInputLocked()) { // The prototype is locked and had stack parameters, we grab the relative offset from this // rather than from a placeholder int4 slot = op->getSlot(phvn)-1; if (slot >= numParams()) throw LowlevelError("Stack placeholder does not line up with locked parameter"); ProtoParameter *param = getParam(slot); Address addr = param->getAddress(); if (addr.getSpace() != spacebase) { if (spacebase->getType() == IPTR_SPACEBASE) throw LowlevelError("Stack placeholder does not match locked space"); } stackoffset -= addr.getOffset(); stackoffset = spacebase->wrapOffset(stackoffset); return; } throw LowlevelError("Unresolved stack placeholder"); } /// \brief Abort the attempt to recover the relative stack offset for \b this function /// /// Any stack-pointer \e placeholder is removed. /// \param data is the calling function void FuncCallSpecs::abortSpacebaseRelative(Funcdata &data) { if (stackPlaceholderSlot >= 0) { data.opRemoveInput(op,stackPlaceholderSlot); clearStackPlaceholderSlot(); } } /// \param call_op is the representative call site within the data-flow FuncCallSpecs::FuncCallSpecs(PcodeOp *call_op) : FuncProto(), activeinput(true), activeoutput(true) { effective_extrapop = ProtoModel::extrapop_unknown; stackoffset = offset_unknown; stackPlaceholderSlot = -1; paramshift = 0; op = call_op; fd = (Funcdata *)0; if (call_op->code() == CPUI_CALL) { entryaddress = call_op->getIn(0)->getAddr(); if (entryaddress.getSpace()->getType() == IPTR_FSPEC) { // op->getIn(0) was already converted to fspec pointer // This can happen if we are cloning an op for inlining FuncCallSpecs *otherfc = FuncCallSpecs::getFspecFromConst(entryaddress); entryaddress = otherfc->entryaddress; } } // If call is indirect, we leave address as invalid isinputactive = false; isoutputactive = false; isbadjumptable = false; } void FuncCallSpecs::setFuncdata(Funcdata *f) { if (fd != (Funcdata *)0) throw LowlevelError("Setting call spec function multiple times"); fd = f; if (fd != (Funcdata *)0) { entryaddress = fd->getAddress(); if (fd->getName().size() != 0) name = fd->getName(); } } /// \param newop replaces the CALL or CALLIND op in the clone /// \return the cloned FuncCallSpecs FuncCallSpecs *FuncCallSpecs::clone(PcodeOp *newop) const { FuncCallSpecs *res = new FuncCallSpecs(newop); res->setFuncdata(fd); // This sets op, name, address, fd res->effective_extrapop = effective_extrapop; res->stackoffset = stackoffset; res->paramshift = paramshift; // We are skipping activeinput, activeoutput res->isbadjumptable = isbadjumptable; res->copy(*this); // Copy the FuncProto portion return res; } /// Find an instance of the stack-pointer (spacebase register) that is active at the /// point of \b this CALL, by examining the \e stack-pointer \e placeholder slot. /// \return the stack-pointer Varnode Varnode *FuncCallSpecs::getSpacebaseRelative(void) const { if (stackPlaceholderSlot<0) return (Varnode *)0; Varnode *tmpvn = op->getIn(stackPlaceholderSlot); if (!tmpvn->isSpacebasePlaceholder()) return (Varnode *)0; if (!tmpvn->isWritten()) return (Varnode *)0; PcodeOp *loadop = tmpvn->getDef(); if (loadop->code() != CPUI_LOAD) return (Varnode *)0; return loadop->getIn(1); // The load input (ptr) is the reference we want } /// \brief Build a Varnode representing a specific parameter /// /// If the Varnode holding the parameter directly as input to the CALL is available, /// it must be provided to this method. If it is not available, this assumes an /// (indirect) stack Varnode is needed and builds one. If the holding Varnode is the /// correct size it is returned, otherwise a truncated Varnode is constructed. /// \param data is the calling function /// \param vn is the Varnode holding the parameter (or NULL for a stack parameter) /// \param param is the actual parameter description /// \param stackref is the stack-pointer placeholder for \b this function /// \return the Varnode that exactly matches the parameter Varnode *FuncCallSpecs::buildParam(Funcdata &data,Varnode *vn,ProtoParameter *param,Varnode *stackref) { if (vn == (Varnode *)0) { // Need to build a spacebase relative varnode AddrSpace *spc = param->getAddress().getSpace(); uintb off = param->getAddress().getOffset(); int4 sz = param->getSize(); vn = data.opStackLoad(spc,off,sz,op,stackref,false); return vn; } if (vn->getSize() == param->getSize()) return vn; PcodeOp *newop = data.newOp(2,op->getAddr()); data.opSetOpcode(newop,CPUI_SUBPIECE); Varnode *newout = data.newUniqueOut(param->getSize(),newop); // Its possible vn is free, in which case the SetInput would give it multiple descendants // See we construct a new version if (vn->isFree() && !vn->isConstant() && !vn->hasNoDescend()) vn = data.newVarnode(vn->getSize(),vn->getAddr()); data.opSetInput(newop,vn,0); data.opSetInput(newop,data.newConstant(4,0),1); data.opInsertBefore(newop,op); return newout; } /// \brief Get the index of the CALL input Varnode that matches the given parameter /// /// This method facilitates the building of a Varnode matching the given parameter /// from existing data-flow. Return either: /// - 0 if the Varnode can't be built /// - slot# for the input Varnode to reuse /// - -1 if the parameter needs to be built from the stack /// \param param is the given parameter to match /// \return the encoded slot int4 FuncCallSpecs::transferLockedInputParam(ProtoParameter *param) { int4 numtrials = activeinput.getNumTrials(); Address startaddr = param->getAddress(); int4 sz = param->getSize(); Address lastaddr = startaddr + (sz-1); for(int4 i=0;igetType() == IPTR_SPACEBASE) return -1; return 0; } /// Return the p-code op whose output Varnode corresponds to the given parameter (return value) /// /// The Varnode may be attached to the base CALL or CALLIND, but it also may be /// attached to an INDIRECT preceding the CALL. The output Varnode may not exactly match /// the dimensions of the given parameter. We return non-null if either: /// - The parameter contains the Varnode (the easier case) OR if /// - The Varnode properly contains the parameter /// \param param is the given paramter (return value) /// \return the matching PcodeOp or NULL PcodeOp *FuncCallSpecs::transferLockedOutputParam(ProtoParameter *param) { Varnode *vn = op->getOut(); if (vn != (Varnode *)0) { if (param->getAddress().justifiedContain(param->getSize(),vn->getAddr(),vn->getSize(),false)==0) return op; if (vn->getAddr().justifiedContain(vn->getSize(),param->getAddress(),param->getSize(),false)==0) return op; return (PcodeOp *)0; } PcodeOp *indop = op->previousOp(); while((indop!=(PcodeOp *)0)&&(indop->code()==CPUI_INDIRECT)) { if (indop->isIndirectCreation()) { vn = indop->getOut(); if (param->getAddress().justifiedContain(param->getSize(),vn->getAddr(),vn->getSize(),false)==0) return indop; if (vn->getAddr().justifiedContain(vn->getSize(),param->getAddress(),param->getSize(),false)==0) return indop; } indop = indop->previousOp(); } return (PcodeOp *)0; } /// \brief List and/or create a Varnode for each input parameter of \b this prototype /// /// Varnodes will be passed back in order that match current input parameters. /// A NULL Varnode indicates a stack parameter. Varnode dimensions may not match /// parameter dimensions exactly. /// \param newinput will hold the resulting list of Varnodes /// \return \b false only if the list needs to indicate stack variables and there is no stack-pointer placeholder bool FuncCallSpecs::transferLockedInput(vector &newinput) { newinput.push_back(op->getIn(0)); // Always keep the call destination address int4 numparams = numParams(); Varnode *stackref = (Varnode *)0; for(int4 i=0;i 0) newinput.push_back(op->getIn(reuse)); else { if (stackref == (Varnode *)0) stackref = getSpacebaseRelative(); if (stackref == (Varnode *)0) return false; newinput.push_back((Varnode *)0); } } return true; } /// \brief Pass back the Varnode needed to match the output parameter (return value) /// /// Search for the Varnode matching the current output parameter and pass /// it back. The dimensions of the Varnode may not exactly match the return value. /// If the return value is e void, a NULL is passed back. /// \param newoutput will hold the passed back Varnode /// \return \b true if the passed back value is accurate bool FuncCallSpecs::transferLockedOutput(Varnode *&newoutput) { ProtoParameter *param = getOutput(); if (param->getType()->getMetatype() == TYPE_VOID) { newoutput = (Varnode *)0; return true; } PcodeOp *outop = transferLockedOutputParam(param); if (outop == (PcodeOp *)0) newoutput = (Varnode *)0; else newoutput = outop->getOut(); return true; } /// \brief Update input Varnodes to \b this CALL to reflect the formal input parameters /// /// The current input parameters must be locked and are presumably out of date /// with the current state of the CALL Varnodes. These existing input Varnodes must /// already be gathered in a list. Each Varnode is updated to reflect the parameters, /// which may involve truncating or extending. Any active trials and stack-pointer /// placeholder is updated, and the new Varnodes are set as the CALL input. /// \param data is the calling function /// \param newinput holds old input Varnodes and will hold new input Varnodes void FuncCallSpecs::commitNewInputs(Funcdata &data,vector &newinput) { if (!isInputLocked()) return; Varnode *stackref = getSpacebaseRelative(); Varnode *placeholder = (Varnode *)0; if (stackPlaceholderSlot>=0) placeholder = op->getIn(stackPlaceholderSlot); bool noplacehold = true; // Clear activeinput and old placeholder stackPlaceholderSlot = -1; int4 numPasses = activeinput.getNumPasses(); activeinput.clear(); int4 numparams = numParams(); for(int4 i=0;igetAddress(),param->getSize()); activeinput.getTrial(i).markActive(); // Parameter is not optional if (noplacehold&&(param->getAddress().getSpace()->getType() == IPTR_SPACEBASE)) { // We have a locked stack parameter, use it to recover the stack offset vn->setSpacebasePlaceholder(); noplacehold = false; // Only set this on the first parameter placeholder = (Varnode *)0; // With a locked stack param, we don't need a placeholder } } if (placeholder != (Varnode *)0) { // If we still need a placeholder newinput.push_back(placeholder); // Add it at end of parameters setStackPlaceholderSlot(newinput.size()-1); } data.opSetAllInput(op,newinput); if (!isDotdotdot()) // Unless we are looking for varargs clearActiveInput(); // turn off parameter recovery (we are locked and have all our varnodes) else { if (numPasses > 0) activeinput.finishPass(); // Don't totally reset the pass counter } } /// \brief Update output Varnode to \b this CALL to reflect the formal return value /// /// The current return value must be locked and is presumably out of date /// with the current CALL output. Unless the return value is \e void, the /// output Varnode must exist and must be provided. /// The Varnode is updated to reflect the return value, /// which may involve truncating or extending. Any active trials are updated, /// and the new Varnode is set as the CALL output. /// \param data is the calling function /// \param newout is the provided old output Varnode (or NULL) void FuncCallSpecs::commitNewOutputs(Funcdata &data,Varnode *newout) { if (!isOutputLocked()) return; activeoutput.clear(); if (newout != (Varnode *)0) { ProtoParameter *param = getOutput(); // We could conceivably truncate the output to the correct size to match the parameter activeoutput.registerTrial(param->getAddress(),param->getSize()); PcodeOp *indop = newout->getDef(); if (newout->getSize() == param->getSize()) { if (indop != op) { data.opUnsetOutput(indop); data.opUnlink(indop); // We know this is an indirect creation which is no longer used // If we reach here, we know -op- must have no output data.opSetOutput(op,newout); } } else if (newout->getSize() < param->getSize()) { // We know newout is properly justified within param if (indop != op) { data.opUninsert(indop); data.opSetOpcode(indop,CPUI_SUBPIECE); } else { indop = data.newOp(2,op->getAddr()); data.opSetOpcode(indop,CPUI_SUBPIECE); data.opSetOutput(indop,newout); // Move -newout- from -op- to -indop- } Varnode *realout = data.newVarnodeOut(param->getSize(),param->getAddress(),op); data.opSetInput(indop,realout,0); data.opSetInput(indop,data.newConstant(4,0),1); data.opInsertAfter(indop,op); } else { // param->getSize() < newout->getSize() // We know param is justified contained in newout VarnodeData vardata; // Test whether the new prototype naturally extends its output OpCode opc = assumedOutputExtension(param->getAddress(),param->getSize(),vardata); Address hiaddr = newout->getAddr(); if (opc != CPUI_COPY) { // If -newout- looks like a natural extension of the true output type, create the extension op if (opc == CPUI_PIECE) { // Extend based on the datatype if (param->getType()->getMetatype() == TYPE_INT) opc = CPUI_INT_SEXT; else opc = CPUI_INT_ZEXT; } if (indop != op) { data.opUninsert(indop); data.opRemoveInput(indop,1); data.opSetOpcode(indop,opc); Varnode *outvn = data.newVarnodeOut(param->getSize(),param->getAddress(),op); data.opSetInput(indop,outvn,0); data.opInsertAfter(indop,op); } else { PcodeOp *extop = data.newOp(1,op->getAddr()); data.opSetOpcode(extop,opc); data.opSetOutput(extop,newout); // Move newout from -op- to -extop- Varnode *outvn = data.newVarnodeOut(param->getSize(),param->getAddress(),op); data.opSetInput(extop,outvn,0); data.opInsertAfter(extop,op); } } else { // If all else fails, concatenate in extra byte from something "indirectly created" by -op- int4 hisz = newout->getSize() - param->getSize(); if (!newout->getAddr().getSpace()->isBigEndian()) hiaddr = hiaddr + param->getSize(); PcodeOp *newindop = data.newIndirectCreation(op,hiaddr,hisz,true); if (indop != op) { data.opUninsert(indop); data.opSetOpcode(indop,CPUI_PIECE); Varnode *outvn = data.newVarnodeOut(param->getSize(),param->getAddress(),op); data.opSetInput(indop,newindop->getOut(),0); data.opSetInput(indop,outvn,1); data.opInsertAfter(indop,op); } else { PcodeOp *concatop = data.newOp(2,op->getAddr()); data.opSetOpcode(concatop,CPUI_PIECE); data.opSetOutput(concatop,newout); // Move newout from -op- to -concatop- Varnode *outvn = data.newVarnodeOut(param->getSize(),param->getAddress(),op); data.opSetInput(concatop,newindop->getOut(),0); data.opSetInput(concatop,outvn,1); data.opInsertAfter(concatop,op); } } } } clearActiveOutput(); } void FuncCallSpecs::initActiveInput(void) { isinputactive = true; int4 maxdelay = getMaxInputDelay(); if (maxdelay > 0) maxdelay = 3; activeinput.setMaxPass(maxdelay); } /// \brief Check if adjacent parameter trials can be combined into a single logical parameter /// /// A slot must be provided indicating the trial and the only following it. /// \param slot1 is the first trial slot /// \param ishislot is \b true if the first slot will be the most significant piece /// \param vn1 is the Varnode corresponding to the first trial /// \param vn2 is the Varnode corresponding to the second trial /// \return \b true if the trials can be combined bool FuncCallSpecs::checkInputJoin(int4 slot1,bool ishislot,Varnode *vn1,Varnode *vn2) const { if (isInputActive()) return false; if (slot1 >= activeinput.getNumTrials()) return false; // Not enough params const ParamTrial *hislot,*loslot; if (ishislot) { // slot1 looks like the high slot hislot = &activeinput.getTrialForInputVarnode(slot1); loslot = &activeinput.getTrialForInputVarnode(slot1+1); if (hislot->getSize() != vn1->getSize()) return false; if (loslot->getSize() != vn2->getSize()) return false; } else { loslot = &activeinput.getTrialForInputVarnode(slot1); hislot = &activeinput.getTrialForInputVarnode(slot1+1); if (loslot->getSize() != vn1->getSize()) return false; if (hislot->getSize() != vn2->getSize()) return false; } return FuncProto::checkInputJoin(hislot->getAddress(),hislot->getSize(),loslot->getAddress(),loslot->getSize()); } /// \brief Join two parameter trials /// /// We assume checkInputJoin() has returned \b true. Perform the join, replacing /// the given adjacent trials with a single merged parameter. /// \param slot1 is the trial slot of the first trial /// \param ishislot is \b true if the first slot will be the most significant piece void FuncCallSpecs::doInputJoin(int4 slot1,bool ishislot) { if (isInputLocked()) throw LowlevelError("Trying to join parameters on locked function prototype"); const ParamTrial &trial1( activeinput.getTrialForInputVarnode(slot1) ); const ParamTrial &trial2( activeinput.getTrialForInputVarnode(slot1+1) ); const Address &addr1( trial1.getAddress() ); const Address &addr2( trial2.getAddress() ); Architecture *glb = getArch(); Address joinaddr; if (ishislot) joinaddr = glb->constructJoinAddress(glb->translate,addr1,trial1.getSize(),addr2,trial2.getSize()); else joinaddr = glb->constructJoinAddress(glb->translate,addr2,trial2.getSize(),addr1,trial1.getSize()); activeinput.joinTrial(slot1,joinaddr,trial1.getSize()+trial2.getSize()); } /// \brief Update \b this prototype to match a given (more specialized) prototype /// /// This method assumes that \b this prototype is in some intermediate state during the /// parameter recovery process and that a new definitive (locked) prototype is discovered /// for \b this call site. This method checks to see if \b this can be updated to match the /// new prototype without missing any data-flow. If so, \b this is updated, and new input /// and output Varnodes for the CALL are passed back. /// \param restrictedProto is the new definitive function prototype /// \param newinput will hold the new list of input Varnodes for the CALL /// \param newoutput will hold the new output Varnode or NULL /// \return \b true if \b this can be fully converted bool FuncCallSpecs::lateRestriction(const FuncProto &restrictedProto,vector &newinput,Varnode *&newoutput) { if (!hasModel()) { copy(restrictedProto); return true; } if (!isCompatible(restrictedProto)) return false; copy(restrictedProto); // Convert ourselves to restrictedProto // if (!isInputLocked()) return false; if (isDotdotdot() && (!isinputactive)) return false; // Redo all the varnode inputs (if possible) if (isInputLocked()) if (!transferLockedInput(newinput)) return false; // Redo all the varnode outputs (if possible) if (isOutputLocked()) if (!transferLockedOutput(newoutput)) return false; return true; } /// \brief Convert \b this call site from an indirect to a direct function call /// /// This call site must be a CALLIND, and the function that it is actually calling /// must be provided. The method makes a determination if the current /// state of data-flow allows converting to the prototype of the new function without /// dropping information due to inaccurate dead-code elimination. If conversion is /// safe, it is performed immediately. Otherwise a \e restart directive issued to /// force decompilation to restart from scratch (now with the direct function in hand) /// \param data is the calling function /// \param newfd is the Funcdata object that we know is the destination of \b this CALLIND void FuncCallSpecs::deindirect(Funcdata &data,Funcdata *newfd) { entryaddress = newfd->getAddress(); name = newfd->getName(); fd = newfd; Varnode *vn = data.newVarnodeCallSpecs(this); data.opSetInput(op,vn,0); data.opSetOpcode(op,CPUI_CALL); if (isOverride()) // If we are overridden at the call-site return; // Don't use the discovered function prototype data.getOverride().insertIndirectOverride(op->getAddr(),entryaddress); // Try our best to merge existing prototype // with the one we have just been handed vector newinput; Varnode *newoutput; FuncProto &newproto( newfd->getFuncProto() ); if ((!newproto.isNoReturn())&&(!newproto.isInline())&& lateRestriction(newproto,newinput,newoutput)) { commitNewInputs(data,newinput); commitNewOutputs(data,newoutput); } else { data.setRestartPending(true); } } /// \brief Force a more restrictive prototype on \b this call site /// /// A new prototype must be given, typically recovered from a function pointer /// data-type that has been propagated to \b this call site. /// The method makes a determination if the current /// state of data-flow allows converting to the new prototype without /// dropping information due to inaccurate dead-code elimination. If conversion is /// safe, it is performed immediately. Otherwise a \e restart directive issued to /// force decompilation to restart from scratch (now with the new prototype in hand) /// \param data is the calling function /// \param fp is the new (more restrictive) function prototype void FuncCallSpecs::forceSet(Funcdata &data,const FuncProto &fp) { vector newinput; Varnode *newoutput; // Copy the recovered prototype into the override manager so that // future restarts don't have to rediscover it FuncProto *newproto = new FuncProto(); newproto->copy(fp); data.getOverride().insertProtoOverride(op->getAddr(),newproto); if (lateRestriction(fp,newinput,newoutput)) { commitNewInputs(data,newinput); commitNewOutputs(data,newoutput); } else { // Too late to make restrictions to correct prototype // Force a restart data.setRestartPending(true); } // Regardless of what happened, lock the prototype so it doesn't happen again setInputLock(true); setInputErrors(fp.hasInputErrors()); setOutputErrors(fp.hasOutputErrors()); } /// \brief Inject any \e upon-return p-code at \b this call site /// /// This function prototype may trigger injection of p-code immediately after /// the CALL or CALLIND to mimic a portion of the callee that decompilation /// of the caller otherwise wouldn't see. /// \param data is the calling function void FuncCallSpecs::insertPcode(Funcdata &data) { int4 id = getInjectUponReturn(); if (id < 0) return; // Nothing to inject InjectPayload *payload = data.getArch()->pcodeinjectlib->getPayload(id); // do the insertion right after the callpoint list::iterator iter = op->getBasicIter(); ++iter; data.doLiveInject(payload,op->getAddr(),op->getParent(),iter); } /// Collect Varnode objects associated with each output trial /// /// Varnodes can be attached to the CALL or CALLIND or one of the /// preceding INDIRECTs. They are passed back in a list matching the /// order of the trials. /// \param trialvn holds the resulting list of Varnodes void FuncCallSpecs::collectOutputTrialVarnodes(vector &trialvn) { if (op->getOut() != (Varnode *)0) throw LowlevelError("Output of call was determined prematurely"); while(trialvn.size() < activeoutput.getNumTrials()) // Size of array should match number of trials trialvn.push_back((Varnode *)0); PcodeOp *indop = op->previousOp(); while(indop != (PcodeOp *)0) { if (indop->code() != CPUI_INDIRECT) break; if (indop->isIndirectCreation()) { Varnode *vn = indop->getOut(); int4 index = activeoutput.whichTrial(vn->getAddr(),vn->getSize()); if (index >= 0) { trialvn[index] = vn; // the exact varnode may have changed, so we reset the trial activeoutput.getTrial(index).setAddress(vn->getAddr(),vn->getSize()); } } indop = indop->previousOp(); } } /// \brief Make final activity check on trials that might have been affected by conditional execution /// /// The activity level a trial may change once conditional execution has been analyzed. /// This routine (re)checks trials that might be affected by this, which may then /// be converted to \e not \e used. void FuncCallSpecs::finalInputCheck(void) { AncestorRealistic ancestorReal; for(int4 i=0;iisDead()) throw LowlevelError("Function call in dead code"); int4 maxancestor = data.getArch()->trim_recurse_max; bool callee_pop = false; int4 expop = 0; if (hasModel()) { callee_pop = (getModelExtraPop() == ProtoModel::extrapop_unknown); if (callee_pop) { expop = getExtraPop(); // Tried to use getEffectiveExtraPop at one point, but it is too unreliable if ((expop==ProtoModel::extrapop_unknown)||(expop <=4)) callee_pop = false; // If the subfunctions do their own parameter popping and // if the extrapop is successfully recovered this is hard evidence // about which trials are active // If the extrapop is 4, this might be a _cdecl convention, and doesn't necessarily mean // that there are no parameters } } AncestorRealistic ancestorReal; for(int4 i=0;igetIn(slot); if (vn->getSpace()->getType() == IPTR_SPACEBASE) { if (aliascheck.hasLocalAlias(vn)) trial.markNoUse(); else if (!data.getFuncProto().getLocalRange().inRange(vn->getAddr(),1)) trial.markNoUse(); else if (callee_pop) { if ((int4)(trial.getAddress().getOffset() + (trial.getSize()-1)) < expop) trial.markActive(); else trial.markNoUse(); } else if (ancestorReal.execute(op,slot,&trial,false)) { if (data.ancestorOpUse(maxancestor,vn,op,trial,0)) trial.markActive(); else trial.markInactive(); } else trial.markNoUse(); // Stackvar for unrealistic ancestor is definitely not a parameter } else { if (ancestorReal.execute(op,slot,&trial,true)) { if (data.ancestorOpUse(maxancestor,vn,op,trial,0)) { trial.markActive(); if (trial.hasCondExeEffect()) activeinput.markNeedsFinalCheck(); } else trial.markInactive(); } else if (vn->isInput()) // Not likely a parameter but maybe trial.markInactive(); else trial.markNoUse(); // An ancestor is unaffected, an unusual input, or killed by a call } if (trial.isDefinitelyNotUsed()) // If definitely not used, free up the dataflow data.opSetInput(op,data.newConstant(vn->getSize(),0),slot); } } /// \brief Mark if output trials are being actively used /// /// Run through each output trial and try to make a determination if the trial is \e active or not, /// meaning basically that the first occurrence of a trial after the call is a read. /// \param data is the calling function /// \param trialvn will hold Varnodes corresponding to the trials void FuncCallSpecs::checkOutputTrialUse(Funcdata &data,vector &trialvn) { collectOutputTrialVarnodes(trialvn); // The location is either used or not. If it is used it can either be the official output // or a killedbycall, so whether the trial is present as a varnode (as determined by dataflow // and deadcode analysis) determines whether we consider the trial active or not for(int4 i=0;i newparam; newparam.push_back(op->getIn(0)); // Preserve the fspec parameter for(int4 i=0;igetType() == IPTR_SPACEBASE) { isspacebase = true; off = spc->wrapOffset(stackoffset + off); // Translate the parameter address relative to caller's spacebase } if (paramtrial.isUnref()) { // recovered unreferenced address as part of prototype vn = data.newVarnode(sz,Address(spc,off)); // We need to create the varnode } else { vn = op->getIn(paramtrial.getSlot()); // Where parameter is currently if (vn->getSize() > sz) { // Varnode is bigger than type Varnode *outvn; // Create truncate op PcodeOp *newop = data.newOp(2,op->getAddr()); if (data.getArch()->translate->isBigEndian()) outvn = data.newVarnodeOut(sz,vn->getAddr()+(vn->getSize()-sz),newop); else outvn = data.newVarnodeOut(sz,vn->getAddr(),newop); data.opSetOpcode(newop,CPUI_SUBPIECE); data.opSetInput(newop,vn,0); data.opSetInput(newop,data.newConstant(1,0),1); data.opInsertBefore(newop,op); vn = outvn; } } newparam.push_back(vn); // Mark the stack range used to pass this parameter as unmapped if (isspacebase) data.getScopeLocal()->markNotMapped(spc,off,sz,true); } data.opSetAllInput(op,newparam); // Set final parameter list activeinput.deleteUnusedTrials(); } /// \brief Check if given two Varnodes are merged into a whole /// /// If the Varnodes are merged immediately into a common whole /// and aren't used for anything else, return the whole Varnode. /// \param vn1 is the first given Varnode /// \param vn2 is the second given Varnode /// \return the combined Varnode or NULL Varnode *FuncCallSpecs::findPreexistingWhole(Varnode *vn1,Varnode *vn2) { PcodeOp *op1 = vn1->loneDescend(); if (op1 == (PcodeOp *)0) return (Varnode *)0; PcodeOp *op2 = vn2->loneDescend(); if (op2 == (PcodeOp *)0) return (Varnode *)0; if (op1 != op2) return (Varnode *)0; if (op1->code() != CPUI_PIECE) return (Varnode *)0; return op1->getOut(); } /// \brief Set the final output Varnode of \b this CALL based on ParamActive analysis of trials /// /// If it exists, the active output trial is moved to be the output Varnode of \b this CALL. /// If there are two active trials, they are merged as a single output of the CALL. /// Any INDIRECT ops that were holding the active trials are removed. /// This prototype itself is unchanged. /// \param data is the calling function /// \param trialvn is the list of Varnodes associated with trials void FuncCallSpecs::buildOutputFromTrials(Funcdata &data,vector &trialvn) { Varnode *finaloutvn; vector finalvn; for(int4 i=0;i deletedops; if (activeoutput.getNumTrials()==1) { // We have a single, properly justified output finaloutvn = finalvn[0]; PcodeOp *indop = finaloutvn->getDef(); // ParamTrial &curtrial(activeoutput.getTrial(0)); // if (finaloutvn->getSize() != curtrial.getSize()) { // If the varnode does not exactly match the original trial // int4 res = curtrial.getEntry()->justifiedContain(finaloutvn->getAddress(),finaloutvn->getSize()); // if (res > 0) { // data.opUninsert(indop); // data.opSetOpcode(indop,CPUI_SUBPIECE); // Insert a subpiece // Varnode *wholevn = data.newVarnodeOut(curtrial.getSize(),curtrial.getAddress(),op); // data.opSetInput(indop,wholevn,0); // data.opSetInput(indop,data.newConstant(4,res),1); // data.opInsertAfter(indop,op); // return; // } // } deletedops.push_back(indop); data.opSetOutput(op,finaloutvn); // Move varnode to its new position as output of call } else if (activeoutput.getNumTrials()==2) { Varnode *hivn = finalvn[1]; // orderOutputPieces puts hi last Varnode *lovn = finalvn[0]; if (data.isDoublePrecisOn()) { lovn->setPrecisLo(); // Mark that these varnodes are part of a larger precision whole hivn->setPrecisHi(); } deletedops.push_back(hivn->getDef()); deletedops.push_back(lovn->getDef()); finaloutvn = findPreexistingWhole(hivn,lovn); if (finaloutvn == (Varnode *)0) { Address joinaddr = data.getArch()->constructJoinAddress(data.getArch()->translate, hivn->getAddr(),hivn->getSize(), lovn->getAddr(),lovn->getSize()); finaloutvn = data.newVarnode(hivn->getSize()+lovn->getSize(),joinaddr); data.opSetOutput(op,finaloutvn); PcodeOp *sublo = data.newOp(2,op->getAddr()); data.opSetOpcode(sublo,CPUI_SUBPIECE); data.opSetInput(sublo,finaloutvn,0); data.opSetInput(sublo,data.newConstant(4,0),1); data.opSetOutput(sublo,lovn); data.opInsertAfter(sublo,op); PcodeOp *subhi = data.newOp(2,op->getAddr()); data.opSetOpcode(subhi,CPUI_SUBPIECE); data.opSetInput(subhi,finaloutvn,0); data.opSetInput(subhi,data.newConstant(4,lovn->getSize()),1); data.opSetOutput(subhi,hivn); data.opInsertAfter(subhi,op); } else { // Preexisting whole deletedops.push_back(finaloutvn->getDef()); // Its inputs are used only in this op data.opSetOutput(op,finaloutvn); } } else return; for(int4 i=0;igetIn(0); Varnode *in1 = dop->getIn(1); data.opDestroy(dop); if (in0 != (Varnode *)0) data.deleteVarnode(in0); if (in1 != (Varnode *)0) data.deleteVarnode(in1); } } /// \brief Get the estimated number of bytes within the given parameter that are consumed /// /// As a function is decompiled, there may hints about how many of the bytes, within the /// storage location used to pass the parameter, are used by \b this sub-function. A non-zero /// value means that that many least significant bytes of the storage location are used. A value /// of zero means all bytes are presumed used. /// \param slot is the slot of the given input parameter /// \return the number of bytes used (or 0) int4 FuncCallSpecs::getInputBytesConsumed(int4 slot) const { if (slot >= inputConsume.size()) return 0; return inputConsume[slot]; } /// \brief Set the estimated number of bytes within the given parameter that are consumed /// /// This provides a hint to the dead code \e consume algorithm, while examining the calling /// function, about how the given parameter within the subfunction is used. /// A non-zero value means that that many least significant bytes of the storage location /// are used. A value of zero means all bytes are presumed used. /// \param slot is the slot of the given input parameter /// \param val is the number of bytes consumed (or 0) /// \return \b true if there was a change in the estimate bool FuncCallSpecs::setInputBytesConsumed(int4 slot,int4 val) const { while(inputConsume.size() <= slot) inputConsume.push_back(0); int4 oldVal = inputConsume[slot]; if (oldVal == 0 || val < oldVal) inputConsume[slot] = val; return (oldVal != val); } /// \brief Prepend any extra parameters if a paramshift is required void FuncCallSpecs::paramshiftModifyStart(void) { if (paramshift==0) return; paramShift(paramshift); } /// \brief Throw out any paramshift parameters /// \param data is the calling function /// \return \b true if a change was made bool FuncCallSpecs::paramshiftModifyStop(Funcdata &data) { if (paramshift == 0) return false; if (isParamshiftApplied()) return false; setParamshiftApplied(true); if (op->numInput() < paramshift + 1) throw LowlevelError("Paramshift mechanism is confused"); for(int4 i=0;igetType() != IPTR_SPACEBASE) return hasEffect(addr,size); if (stackoffset == offset_unknown) return EffectRecord::unknown_effect; uintb newoff = spc->wrapOffset(addr.getOffset()-stackoffset); // Translate to callee's spacebase point of view return hasEffect(Address(spc,newoff),size); } /// \brief Calculate the number of times an individual sub-function is called. /// /// Provided a list of all call sites for a calling function, tally the number of calls /// to the same sub-function. Update the \b matchCallCount field of each FuncCallSpecs /// \param qlst is the list of call sites (FuncCallSpecs) for the calling function void FuncCallSpecs::countMatchingCalls(const vector &qlst) { vector copyList(qlst); sort(copyList.begin(),copyList.end(),compareByEntryAddress); int4 i; for(i=0;ientryaddress.isInvalid()) break; copyList[i]->matchCallCount = 1; // Mark all invalid addresses as a singleton } if (i == copyList.size()) return; Address lastAddr = copyList[i]->entryaddress; int4 lastChange = i++; int4 num; for(;ientryaddress == lastAddr) continue; num = i - lastChange; for(;lastChangematchCallCount = num; lastAddr = copyList[i]->entryaddress; } num = i - lastChange; for(;lastChangematchCallCount = num; }