diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/AutoVersionTrackingTask.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/AutoVersionTrackingTask.java index 41b5ac5341..3b0330f39d 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/AutoVersionTrackingTask.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/actions/AutoVersionTrackingTask.java @@ -17,9 +17,11 @@ package ghidra.feature.vt.gui.actions; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import javax.swing.SwingConstants; @@ -56,21 +58,17 @@ import ghidra.framework.options.ToolOptions; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.lang.OperandType; -import ghidra.program.model.listing.CodeUnit; -import ghidra.program.model.listing.CodeUnitIterator; +import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Function; -import ghidra.program.model.listing.FunctionManager; import ghidra.program.model.listing.Instruction; import ghidra.program.model.listing.InstructionIterator; import ghidra.program.model.listing.Program; -import ghidra.program.util.ListingDiff; +import ghidra.program.model.scalar.Scalar; import ghidra.util.Msg; -import ghidra.util.exception.AssertException; import ghidra.util.exception.CancelledException; import ghidra.util.task.Task; import ghidra.util.task.TaskMonitor; import ghidra.util.task.WrappingTaskMonitor; -import util.CollectionUtils; /** * This command runs all of the exact {@link VTProgramCorrelator}s that return @@ -328,7 +326,7 @@ public class AutoVersionTrackingTask extends Task { private void processImpliedMatches(TaskMonitor monitor) throws CancelledException { - List processedSrcDestPairs = new ArrayList<>(); + Set processedSrcDestPairs = new HashSet<>(); List matchSets = session.getMatchSets(); monitor.setMessage("Processing Implied Matches..."); @@ -443,7 +441,7 @@ public class AutoVersionTrackingTask extends Task { * correct matches based on matching operand values. Those matches are accepted and other * possible matches for those functions are blocked. Markup from accepted source functions * is applied to matching destination functions. - * + * * @param factory The correlator factory used to create and run the desired VT correlator. In * this case, the duplicate function instruction match correlator. * @param monitor Checks to see if user has cancelled. @@ -460,8 +458,8 @@ public class AutoVersionTrackingTask extends Task { VTMatchSet results = correlator.correlate(session, monitor); monitor.initialize(results.getMatchCount()); - boolean hasMarkupErrors = applyDuplicateFunctionMatches(results.getMatches(), monitor); + boolean hasMarkupErrors = applyDuplicateFunctionMatches(results, monitor); monitor.incrementProgress(1); return hasMarkupErrors; @@ -537,20 +535,27 @@ public class AutoVersionTrackingTask extends Task { } /** - * Accept matches and apply markup for duplicate function instruction matches with matching - * operands if they are a unique match within their associated set. - * @param matches A collection of version tracking matches from the duplicate instruction - * matcher + * Method to accept matches and apply markup for duplicate function instruction matches with + * matching operands if they are a unique match within their associated subset. To explain in + * more depth, the duplicate function instruction correlator returns a set of function matches + * such that there are subsets of matches where each function pair has the same exact function + * instructions but possibly different operands. Also, there must be more than one possible + * function pair association or it would have been identified as a unique match by the exact + * unique function instruction correltor. This method attempts to find unique matches from + * within the related subsets by comparing operand information. + * @param matches The set of matches from the duplicate function instruction correlator * @param monitor Allows user to cancel - * @return true if any markup errors, false if no markup errors + * @return true if there are any markup errors, false if no markup errors * @throws CancelledException if cancelled */ - private boolean applyDuplicateFunctionMatches(Collection matches, TaskMonitor monitor) + private boolean applyDuplicateFunctionMatches(VTMatchSet matchSet, TaskMonitor monitor) throws CancelledException { + Collection matches = matchSet.getMatches(); + // If this value gets set to true later it indicates markup errors upon applying markup. boolean someMatchesHaveMarkupErrors = false; - Set copyOfMatches = new HashSet<>(matches); + Set processedSrcDestPairs = new HashSet<>(); String message = "Processing match %d of %d..."; int n = matches.size(); @@ -561,61 +566,53 @@ public class AutoVersionTrackingTask extends Task { VTMatch match = it.next(); - // skip if match has already been removed (it was in a set that was already processed) - if (!copyOfMatches.contains(match)) { + VTAssociation association = match.getAssociation(); + + // skip if match has already been processed (ie matched or determined to be unable + // to match) + if (processedSrcDestPairs.contains(association)) { continue; } - // if already matched or blocked skip it - if (match.getAssociation().getStatus() != VTAssociationStatus.AVAILABLE) { + // if this association src/dest pair is already matched or blocked skip it + if (association.getStatus() != VTAssociationStatus.AVAILABLE) { + processedSrcDestPairs.add(association); continue; } - // Get a set of related matches from the set of all matches. These all have the same - // instructions as each other but not necessarily the same operands. - Set relatedMatches = getRelatedMatches(match, matches, monitor); + // get the entire set of functions with the same instructions as the given source and + // destination pair + Set allRelatedAssociations = getAllRelatedAssociations( + match.getSourceAddress(), match.getDestinationAddress(), monitor); - // remove related matches from the copy of set of matches which gets checked - // and skipped if not in the set - removeMatches(copyOfMatches, relatedMatches); + // Try to find all the unique matches in this set with the same operands as each other. + // The duplicate function instruction correlator already grouped them into sets of + // functions pairs with exactly the same instructions. This is trying to find the + // correct matches in this set. + Collection uniqueAssociations = + findUniqueAssociations(allRelatedAssociations, monitor); - if (relatedMatches.size() > 20) { - Msg.debug(this, "Too many related matches to process this match set " + - match.getSourceAddress()); + // Whether or not a unique association has been found, add these associations to the + // processed list so the check is not repeated for another src/dest pair in this + // set later. + processedSrcDestPairs.addAll(allRelatedAssociations); + if (uniqueAssociations == null) { continue; } - // remove any matches that have identical source functions - if more than one - // with exactly the same instructions and operands then cannot determine a unique match - Set
sourceAddresses = getSourceAddressesFromMatches(relatedMatches, monitor); - Set
uniqueSourceFunctionAddresses = - dedupMatchingFunctions(sourceProgram, sourceAddresses, monitor); - - // remove any matches that have identical destination functions - if more than one - // with exactly the same instructions and operands then cannot determine a unique match - Set
destAddresses = - getDestinationAddressesFromMatches(relatedMatches, monitor); - Set
uniqueDestFunctionAddresses = - dedupMatchingFunctions(destinationProgram, destAddresses, monitor); - - // Keep only matches containing unique source and destination functions from above - Set dedupedMatches = getMatches(relatedMatches, uniqueSourceFunctionAddresses, - uniqueDestFunctionAddresses, monitor); - - // loop through all the source functions - for (Address sourceAddress : uniqueSourceFunctionAddresses) { + // For each good match found, accept the match and apply markup + for (VTAssociation uniqueAssociation : uniqueAssociations) { monitor.checkCancelled(); - // find all destination functions with equivalent operands to source function - Set matchesWithEquivalentOperands = getMatchesWithEquivalentOperands( - dedupedMatches, sourceAddress, uniqueDestFunctionAddresses, monitor); - - // if there is just one equivalent match try to accept the match and apply markup - if (matchesWithEquivalentOperands.size() == 1) { - VTMatch theMatch = CollectionUtils.any(matchesWithEquivalentOperands); - someMatchesHaveMarkupErrors |= - tryToAcceptMatchAndApplyMarkup(theMatch, monitor); + VTMatch theMatch = + getAssociationMatchFromMatchSet(uniqueAssociation, matchSet, monitor); + if (theMatch == null) { + Msg.error(this, + uniqueAssociation.toString() + " Should be in the original match set used"); + continue; } + + someMatchesHaveMarkupErrors |= tryToAcceptMatchAndApplyMarkup(theMatch, monitor); } } @@ -623,6 +620,517 @@ public class AutoVersionTrackingTask extends Task { } + /** + * Get the entire set of related duplicate functions with the same instructions + * @param source the given source address + * @param destination the given destination address + * @param monitor the task monitor + * @return the entire set of related duplicate functions with the same instructions + * @throws CancelledException if cancelled + */ + private Set getAllRelatedAssociations(Address source, Address destination, + TaskMonitor monitor) throws CancelledException { + + // get all associations with the same source or the same destination address + VTAssociationManager vtAssocManager = session.getAssociationManager(); + Collection relatedAssociations = + vtAssocManager.getRelatedAssociationsBySourceAndDestinationAddress( + source, destination); + + Set allRelatedAssociations = new HashSet(relatedAssociations); + + // from the initial set of related associations get all the other ones that have related + // associations with all the source/destinations of the newly found associations + + for (VTAssociation association : relatedAssociations) { + monitor.checkCancelled(); + + allRelatedAssociations + .addAll(vtAssocManager.getRelatedAssociationsBySourceAndDestinationAddress( + association.getSourceAddress(), association.getDestinationAddress())); + } + + return allRelatedAssociations; + } + + /** + * Given an association, get the VTMatch from the given matchSet (ie set of matches from a + * particular correlator). There may be multiple correlators that have found the same match. + * This is making sure the match is from the desired correlator. + * @param association the given association + * @param matchSet the given correlator matchSet + * @param monitor the task monitor + * @return the match with same source and destination addresss as the given association from the + * given correlator's set of matches. + * @throws CancelledException if cancelled + */ + private VTMatch getAssociationMatchFromMatchSet(VTAssociation association, + VTMatchSet matchSet, TaskMonitor monitor) throws CancelledException { + + List assocMatchesInMatchSet = new ArrayList(); + + List assocationMatches = session.getMatches(association); + Collection matchSetMatches = matchSet.getMatches(); + + for (VTMatch match : assocationMatches) { + monitor.checkCancelled(); + + if (matchSetMatches.contains(match)) { + assocMatchesInMatchSet.add(match); + } + + } + + if (assocMatchesInMatchSet.size() == 1) { + return assocMatchesInMatchSet.get(0); + } + + Msg.error(this, + "Expected single match in matchset for association " + association.toString()); + + return null; + } + + + /** + * From the given related association, ie a group of src/dest pairs of functions with identical + * instructions, use operand information to find any unique matches in the set. + * @param relatedAssociations group of src/dest pairs of functions with identical instructions + * @param monitor the task monitor + * @return a list of src/destination associations that are uniquely matched based on matching + * operands + * @throws CancelledException if cancelled + */ + private List findUniqueAssociations( + Collection relatedAssociations, TaskMonitor monitor) + throws CancelledException { + + + // create function to operand map maps for each source and destination function + // in the given related associations (src/dst function pairs) + Map>> sourceFunctionsMap = + createFunctionsMap(relatedAssociations, true, monitor); + + Map>> destFunctionsMap = + createFunctionsMap(relatedAssociations, false, monitor); + + + // only functions with scalar or address operands are mapped so the lists could be + // empty if there are functions with no operand info to be mapped + if (sourceFunctionsMap.isEmpty() || destFunctionsMap.isEmpty()) { + return null; + } + + List uniqueAssociations = findUniqueAssociationsUsingMaps(sourceFunctionsMap, + destFunctionsMap, monitor); + + return uniqueAssociations; + } + + /** + * Method to use the given function to operand maps, for sets of source and destination functions + * with identical instructions, to identify any unique src/dst matches within the set. + * instructions + * @param sourceFunctionsMap the source functions map + * @param destFunctionsMap the destination functions map + * @param monitor the task monitor + * @return the list of unique associations (src/dest function pairs) if any + * @throws CancelledException if cancelled + */ + private List findUniqueAssociationsUsingMaps( + Map>> sourceFunctionsMap, + Map>> destFunctionsMap, + TaskMonitor monitor) + throws CancelledException { + + List uniqueAssociations = new ArrayList(); + + // for each source function, try to find a single matching destination function from + // the associated functions that have map info + VTAssociationManager vtAssocManager = session.getAssociationManager(); + Set sourceFunctions = sourceFunctionsMap.keySet(); + + Set matchedDestFunctions = new HashSet(); + + for (Function sourceFunction : sourceFunctions) { + monitor.checkCancelled(); + + Map> sourceFunctionMap = + sourceFunctionsMap.get(sourceFunction); + + Function destFunction = + getSingleMatch(sourceFunctionMap, destFunctionsMap, matchedDestFunctions, monitor); + + if (destFunction == null) { + continue; + } + + // track matched destination functions so they are not checked again later + matchedDestFunctions.add(destFunction); + + // add the association for the given src/dest pair to the list of good matches + VTAssociation association = vtAssocManager + .getAssociation(sourceFunction.getEntryPoint(), destFunction.getEntryPoint()); + if (association != null) { + uniqueAssociations.add(association); + } + } + return uniqueAssociations; + } + + /** + * Create an operand map for each source or destination function in the given associations + * @param associations The collection of associations (src/dest function pairs) + * @param source if true use the source function, if false use the destination function + * @param monitor the task monitor + * @return the map of functions to their operand maps + * @throws CancelledException if cancelled + */ + private Map>> createFunctionsMap( + Collection associations, boolean source, TaskMonitor monitor) + throws CancelledException { + + Map>> functionsMap = + new HashMap<>(); + + // to keep track of which functions are attempted so only mapped once since there are + // multiple pairs with same source function and multiple with the same dest function + Set functionsMapAttempted = new HashSet(); + + // make an operand map for each source and destination function in the given associations + for (VTAssociation association : associations) { + monitor.checkCancelled(); + + Function function = null; + if (source) { + function = getSourceFunction(association); + } + else { + function = getDestFunction(association); + } + if (function == null) { + continue; + } + + if (functionsMapAttempted.contains(function)) { + continue; + } + + functionsMapAttempted.add(function); + + // create offset/operand info map for the given source function + Map> map = + mapFunctionScalarAndAddressOperands(function, monitor); + + // only keep the ones with operand info to map + if (map != null) { + functionsMap.put(function, map); + } + } + return functionsMap; + } + + + /** + * Using the given source function's map and a list of destination function maps, and a list + * of destination functions to omit because they already have found matches, try to find a + * single match using matching operand info. + * + * @param sourceFunctionMap the operand map for the source function + * @param destFunctionsMap the maps for the destination functions + * @param destFunctionsToOmit the destination functions that already have been mapped + * @param monitor the task monitor + * @return a single matching destination function or null if none or more than one are found + * @throws CancelledException if cancelled + */ + private Function getSingleMatch(Map> sourceFunctionMap, + Map>> destFunctionsMap, + Set destFunctionsToOmit, + TaskMonitor monitor) throws CancelledException { + + Set destFunctions = destFunctionsMap.keySet(); + Set matchingFunctions = new HashSet<>(); + + // remove the omitted ones which were previously matched to something else + destFunctions.removeAll(destFunctionsToOmit); + + for (Function destFunction : destFunctions) { + monitor.checkCancelled(); + + Map> destFunctionMap = + destFunctionsMap.get(destFunction); + + // skip if the operand maps don't match + if (!equalsFunctionMap(sourceFunctionMap, destFunctionMap, monitor)) { + continue; + } + + // add to list of operand maps match + matchingFunctions.add(destFunction); + + } + if (matchingFunctions.size() == 1) { + List list = new ArrayList(matchingFunctions); + return list.get(0); + } + return null; + + } + + private boolean equalsFunctionMap(Map> map1, + Map> map2, TaskMonitor monitor) + throws CancelledException { + + if (!map1.keySet().equals(map2.keySet())) { + return false; + } + Set map1Longs = map1.keySet(); + + for (Long offset : map1Longs) { + + Map opMap1 = map1.get(offset); + Map opMap2 = map2.get(offset); + + if (!equivalentOperandMap(opMap1, opMap2, monitor)) { + return false; + } + } + return true; + + } + + private boolean equivalentOperandMap(Map map1, + Map map2, + TaskMonitor monitor) throws CancelledException { + + if (!map1.keySet().equals(map2.keySet())) { + return false; + } + + for (Integer i : map1.keySet()) { + + monitor.checkCancelled(); + + Object op1 = map1.get(i); + Object op2 = map2.get(i); + if (!equivalentOperands(op1, op2)) { + return false; + } + } + + return true; + + } + + private boolean equivalentOperands(Object op1, Object op2) { + + if ((op1 instanceof Scalar)) { + return op1.equals(op2); + } + + return isEquivalentAddressOperand(op1, op2); + + } + + /** + * Method to determine if the two given operands are equivalent Address operands + * @param op1 the first operand + * @param op2 the second operand + * @return true if the operands are equivalent Address operands, false otherwise + */ + private boolean isEquivalentAddressOperand(Object op1, Object op2) { + + if (!(op1 instanceof Address)) { + return false; + } + + if (!(op2 instanceof Address)) { + return false; + } + + Address addr1 = (Address) op1; + Address addr2 = (Address) op2; + + // if there a defined association then consider it an equivalent operand + VTAssociation association = session.getAssociationManager().getAssociation(addr1, addr2); + if (association != null) { + return true; + } + + // if either has existing association with something else then consider not equivalent + if (hasAnyAssociations(addr1, addr2)) { + return false; + } + + // if no association information then check to see if both are functions or if both are + // same data type + return isSameOperandType(addr1, addr2); + + } + + /** + * Method to check to see if both addresses have functions at them or the same data type at them + * @param addr1 the first Address + * @param addr2 the second Address + * @return + */ + private boolean isSameOperandType(Address addr1, Address addr2) { + + Function function1 = sourceProgram.getFunctionManager().getFunctionAt(addr1); + if (function1 != null) { + Function function2 = destinationProgram.getFunctionManager().getFunctionAt(addr2); + if (function2 != null) { + return true; + } + else { + return false; + } + } + + Data data1 = sourceProgram.getListing().getDataAt(addr1); + if (data1 != null && data1.isDefined()) { + Data data2 = destinationProgram.getListing().getDataAt(addr2); + if (data2 == null) { + return false; + } + if (!data2.isDefined()) { + return false; + } + + if (data1.getDataType().getName().equals(data2.getDataType().getName())) { + return true; + } + } + return false; + } + + /** + * Method to determine if the given src or dest addresses have existing association + * @param source the source address + * @param dest the destination address + * @return true if there are any existin associations, false if none + */ + private boolean hasAnyAssociations(Address source, Address dest) { + + + Collection sourceAssociations = session.getAssociationManager() + .getRelatedAssociationsBySourceAddress(source); + + if (!sourceAssociations.isEmpty()) { + return true; + } + + Collection destAssociations = session.getAssociationManager() + .getRelatedAssociationsByDestinationAddress(dest); + + if (!destAssociations.isEmpty()) { + return true; + } + + return false; + } + + private Function getSourceFunction(VTAssociation association) { + + Address address = association.getSourceAddress(); + return sourceProgram.getFunctionManager().getFunctionAt(address); + } + + private Function getDestFunction(VTAssociation association) { + + Address address = association.getDestinationAddress(); + return destinationProgram.getFunctionManager().getFunctionAt(address); + } + + /** + * Method to create a map of the given functions scalar operands and address reference operands. + * The map keys will be offsets from the top of the function to the instruction containing the + * operand(s). + * The map entries will be a map of the operands at the instruction indicated by the key's + * offset value. This map has keys for operand index and entries for the type of object at + * that operand index, either Scalar or Address + * @param function the given function + * @param monitor the task monitor + * @return the resulting map for the given function + * @throws CancelledException if cancelled + */ + private Map> mapFunctionScalarAndAddressOperands( + Function function, TaskMonitor monitor) + throws CancelledException { + + Map> offsetToOperandsMap = new HashMap<>(); + + + Program program = function.getProgram(); + + InstructionIterator func1InstIter = + program.getListing().getInstructions(function.getBody(), true); + + while (func1InstIter.hasNext()) { + monitor.checkCancelled(); + + Instruction inst = func1InstIter.next(); + + Map map = createOperandsMap(inst); + + if (map.keySet().isEmpty()) { + continue; + } + + // get offset from top of function to use in function to operandMap map + Long offset = + inst.getAddress().subtract(function.getEntryPoint().getOffset()).getOffset(); + offsetToOperandsMap.put(offset, map); + } + + if (offsetToOperandsMap.keySet().isEmpty()) { + return null; + } + + return offsetToOperandsMap; + } + + private Map createOperandsMap(Instruction inst) { + + Map map = new HashMap<>(); + int numOperands = inst.getNumOperands(); + + for (int opIndex = 0; opIndex < numOperands; opIndex++) { + + int opType = inst.getOperandType(opIndex); + + // save off operand if a scalar or a code or data reference + if (OperandType.isScalar(opType)) { + map.put(opIndex, inst.getScalar(opIndex)); + continue; + } + + // if operands are addresses check to see if both refer to data or both refer to code + if (OperandType.isAddress(opType)) { + if (OperandType.isDataReference(opType)) { + //Reference opRef = inst.getPrimaryReference(opIndex); + map.put(opIndex, inst.getAddress(opIndex)); + continue; + } + + if (OperandType.isCodeReference(opType)) { + map.put(opIndex, inst.getAddress(opIndex)); + continue; + } + + } + } + return map; + } + + + /** + * Method to create offset/operand mapping for each function in match set + * if more than one identical offset/operand mapping in src or dest piles then remove + * if no operands remove + * + */ + /** * Try to accept the given match and if can accept the match try to apply its markup * @param match The match to try and accept and apply markup to @@ -657,366 +1165,6 @@ public class AutoVersionTrackingTask extends Task { return false; } - /** - * Get a set of matches with equivalent operands. - * @param matches Set of version tracking matches with matching instructions - * @param sourceAddress Address of source function to compare with destination functions - * @param destAddresses Addresses of destination functions to compare with source function - * @param monitor Allows user to cancel - * @return Set of all matches with equivalent operands. - * @throws CancelledException if cancelled - */ - private Set getMatchesWithEquivalentOperands(Set matches, - Address sourceAddress, Set
destAddresses, TaskMonitor monitor) - throws CancelledException { - - Set matchesWithEquivalentOperands = new HashSet<>(); - for (Address destAddress : destAddresses) { - - if (haveEquivalentOperands(sourceProgram, sourceAddress, destinationProgram, - destAddress, monitor)) { - VTMatch goodMatch = getMatch(matches, sourceAddress, destAddress); - if (goodMatch != null) { - matchesWithEquivalentOperands.add(goodMatch); - } - } - } - return matchesWithEquivalentOperands; - } - - /** - * Determine which matches from a collection of matches are related to the given match, ie - * have the same source or destination address as the current match. - * @param match Current version tracking match - * @param matches Collection version tracking matches - * @param monitor Allows user to cancel - * @return Set of matches related to the given match - * @throws CancelledException if cancelled - */ - private Set getRelatedMatches(VTMatch match, Collection matches, - TaskMonitor monitor) throws CancelledException { - - VTAssociationManager vtAssocManager = session.getAssociationManager(); - Set relatedMatches = new HashSet<>(); - - Collection relatedAssociations = - vtAssocManager.getRelatedAssociationsBySourceAndDestinationAddress( - match.getSourceAddress(), match.getDestinationAddress()); - - //Add the current match and all related matches to a new set to process - relatedMatches.add(match); - // create set of related duplicate matches and remove all related matches from the - // copied set of all matches so they are not processed more than once - for (VTAssociation relatedAssociation : relatedAssociations) { - monitor.checkCancelled(); - VTMatch relMatch = getMatch(matches, relatedAssociation.getSourceAddress(), - relatedAssociation.getDestinationAddress()); - if (relMatch != null) { - relatedMatches.add(relMatch); - } - - } - return relatedMatches; - } - - /** - * Remove given matches from a set of matches. - * @param matchSet Set of matches. - * @param matchesToRemove Set of matches to remove from matches set. - */ - private void removeMatches(Set matchSet, Set matchesToRemove) { - - for (VTMatch matchToRemove : matchesToRemove) { - VTMatch match = getMatch(matchSet, matchToRemove.getSourceAddress(), - matchToRemove.getDestinationAddress()); - if (match != null) { - matchSet.remove(match); - } - } - } - - /** - * Get the source addresses from a set of version tracking matches. - * @param matches Set of version tracking matches - * @param monitor Allows user to cancel - * @return A set of source addresses from the given set of version tracking matches. - * @throws CancelledException if cancelled - */ - private Set
getSourceAddressesFromMatches(Set matches, TaskMonitor monitor) - throws CancelledException { - - Set
sourceAddresses = new HashSet<>(); - for (VTMatch match : matches) { - monitor.checkCancelled(); - sourceAddresses.add(match.getSourceAddress()); - } - return sourceAddresses; - } - - /** - * Get the destination addresses from a set of version tracking matches. - * @param matches Set of version tracking matches - * @param monitor Allows user to cancel - * @return A set of destination addresses from the given set of version tracking matches. - * @throws CancelledException if cancelled - */ - private Set
getDestinationAddressesFromMatches(Set matches, - TaskMonitor monitor) throws CancelledException { - - Set
destAddresses = new HashSet<>(); - for (VTMatch match : matches) { - monitor.checkCancelled(); - destAddresses.add(match.getDestinationAddress()); - } - return destAddresses; - } - - /** - * Given a set of version tracking matches, return the match with the given source/destination - * address pair. - * @param matches Set of version tracking matches. - * @param sourceAddress Address of the source program match item. - * @param destAddress Address of the destination program match item. - * @return The match with the given source/destination address pair or null if not found. - */ - private VTMatch getMatch(Collection matches, Address sourceAddress, - Address destAddress) { - for (VTMatch match : matches) { - if (match.getSourceAddress().equals(sourceAddress) && - match.getDestinationAddress().equals(destAddress)) { - return match; - } - } - return null; - } - - /** - * From a set of matches get the subset that contains the given source and destination addresses. - * @param matches Set of matches - * @param sourceAddresses Set of source addresses - * @param destAddresses Set of destination addresses - * @param monitor Allows user to cancel - * @return Set of matches containing given source and destination addresses. - * @throws CancelledException if cancelled - */ - private Set getMatches(Set matches, Set
sourceAddresses, - Set
destAddresses, TaskMonitor monitor) throws CancelledException { - - Set results = new HashSet<>(); - for (VTMatch match : matches) { - monitor.checkCancelled(); - if (sourceAddresses.contains(match.getSourceAddress()) && - destAddresses.contains(match.getDestinationAddress())) { - results.add(match); - } - } - return results; - } - - /** - * * This method is only called to compare functions with identical instruction - * bytes, identical operand lengths, but possibly different operand values. It returns true - * if the two functions in the match have potentially equivalent operands. It returns false if - * any of the operands do not match. - * Potentially equivalent means corresponding scalar operands match, corresponding other operands have - * the same type of operand (i.e., code, data,register) - * @param program1 Program containing function1 - * @param address1 Function to compare with function2 - * @param program2 Program containing function2 - * @param address2 Function to compare with function1 - * @param monitor Allows user to cancel - * @return true if all operands between the two functions match and false otherwise. - * @throws CancelledException if cancelled - */ - private boolean haveEquivalentOperands(Program program1, Address address1, Program program2, - Address address2, TaskMonitor monitor) throws CancelledException { - - Function function1 = program1.getFunctionManager().getFunctionAt(address1); - Function function2 = program2.getFunctionManager().getFunctionAt(address2); - if (function1 == null || function2 == null) { - return false; - } - - InstructionIterator func1InstIter = - program1.getListing().getInstructions(function1.getBody(), true); - InstructionIterator func2InstIter = - program2.getListing().getInstructions(function2.getBody(), true); - - // Setup the function comparer - ListingDiff listingDiff = new ListingDiff(); - listingDiff.setIgnoreByteDiffs(false); - listingDiff.setIgnoreConstants(false); - listingDiff.setIgnoreRegisters(false); - - while (func1InstIter.hasNext() && func2InstIter.hasNext()) { - monitor.checkCancelled(); - - Instruction inst1 = func1InstIter.next(); - Instruction inst2 = func2InstIter.next(); - - // Get the differing operands for this instruction - int[] operandsThatDiffer = listingDiff.getOperandsThatDiffer(inst1, inst2); - if (!haveEquivalentOperands(inst1, inst2, operandsThatDiffer)) { - return false; - } - } - - // This should never happen but if it does then throw an error because that means something - // weird is happening like the action updating the source and destination match lengths - // didn't do it correctly. - if (func1InstIter.hasNext() || func2InstIter.hasNext()) { - throw new AssertException( - "Expected Source and Destination function number of instructions to be equal but they differ."); - } - // True does not necessarily mean they are THE match. This has hopefully weeded out more - // bad matches but there are cases where we can't determine one unique match - return true; - } - - /** - * Determine if the given instructions which have at least one differing operand, have equivalent - * operand types. If operand type is a scalar, is it the same scalar. - * @param inst1 Instruction 1 - * @param inst2 Instruction 2 - * @param operandsThatDiffer Array of indexes of operands that differ - * @return true if all operands in the two instructions are equivalent types and scalars are equal, - * else return false - */ - private boolean haveEquivalentOperands(Instruction inst1, Instruction inst2, - int[] operandsThatDiffer) { - - for (int operand : operandsThatDiffer) { - - // First, check to see if the op type is the same. If not, return false. - int srcOpType = inst1.getOperandType(operand); - int destOpType = inst2.getOperandType(operand); - if (srcOpType != destOpType) { - return false; - } - - // If the matching op types are scalars, check to see if they are the same. If not - // return false. - if (OperandType.isScalar(srcOpType) && - !inst1.getScalar(operand).equals(inst2.getScalar(operand))) { - return false; - } - - // if operands are addresses check to see if both refer to data or both refer to code - if (OperandType.isAddress(srcOpType)) { - if (OperandType.isDataReference(srcOpType) && - OperandType.isDataReference(destOpType)) { - continue; - } - - if (OperandType.isCodeReference(srcOpType) && - OperandType.isCodeReference(destOpType)) { - continue; - } - return false; - } - } - return true; - } - - /** - * Method to determine if two functions with exactly the same instructions also have exactly the - * same operands. - * @param program1 Program that contains function1 - * @param function1 Function to compare with function2 - * @param program2 Program that contains function2 (can be same or different than program1) - * @param function2 Function to compare with function1 - * @param monitor the monitor - * @return true if two functions have no operand differences, else returns false - * @throws CancelledException if cancelled - */ - private boolean haveSameOperands(Program program1, Function function1, Program program2, - Function function2, TaskMonitor monitor) throws CancelledException { - - CodeUnitIterator sourceFuncCodeUnitIter = - program1.getListing().getCodeUnits(function1.getBody(), true); - CodeUnitIterator destFuncCodeUnitIter = - program2.getListing().getCodeUnits(function2.getBody(), true); - - ListingDiff listingDiff = new ListingDiff(); - listingDiff.setIgnoreByteDiffs(false); - listingDiff.setIgnoreConstants(false); - listingDiff.setIgnoreRegisters(false); - - while (sourceFuncCodeUnitIter.hasNext() && destFuncCodeUnitIter.hasNext()) { - monitor.checkCancelled(); - CodeUnit srcCodeUnit = sourceFuncCodeUnitIter.next(); - CodeUnit dstCodeUnit = destFuncCodeUnitIter.next(); - - int[] operandsThatDiffer = listingDiff.getOperandsThatDiffer(srcCodeUnit, dstCodeUnit); - - if (operandsThatDiffer.length > 0) { - return false; - } - } - // This should never happen but if it does then throw an error because that means something - // weird is happening like the action updating the source and destination match lengths - // didn't do it correctly. - if (sourceFuncCodeUnitIter.hasNext() || destFuncCodeUnitIter.hasNext()) { - throw new AssertException( - "Expected Source and Destination function number of instructions to be equal but they differ."); - } - return true; - } - - /** - * Remove addresses from a set of function starting addresses if any functions have all matching - * operands. - * @param program the program - * @param addresses Set of function start addresses - * @param monitor the monitor - * @return Set of addresses of deduped function bytes - * @throws CancelledException if cancelled - */ - private Set
dedupMatchingFunctions(Program program, Set
addresses, - TaskMonitor monitor) throws CancelledException { - - FunctionManager functionManager = program.getFunctionManager(); - - // Copy the list of addresses to a new array list - Set
uniqueFunctionAddresses = new HashSet<>(addresses); - - List
list = new ArrayList<>(addresses); - - // Compare 0 to 1, 0 to 2, ... 0 to j-1, 1 to 2, 1 to 3, ... 1- j-1, .... i-2 to j-1 - for (int i = 0; i < list.size(); i++) { - - Address address1 = list.get(i); - if (!uniqueFunctionAddresses.contains(address1)) { - continue; - } - - for (int j = i + 1; j < list.size(); j++) { - monitor.checkCancelled(); - - Address address2 = list.get(j); - - // If either of the two function addresses are not on the list, they have already - // been deemed a duplicate so no need to compare either of them again. - if (!uniqueFunctionAddresses.contains(address2)) { - continue; - } - - // Compare the functions at address1 and address2 and see if they have no matching - // operands. Since all functions in this list already have matching instructions, then - // if their operands all match, they are completely identical functions and we - // want to throw them out so remove them from the list. - if (haveSameOperands(program, functionManager.getFunctionAt(address1), program, - functionManager.getFunctionAt(address2), monitor)) { - - uniqueFunctionAddresses.remove(address1); - uniqueFunctionAddresses.remove(address2); - } - } - } - - return uniqueFunctionAddresses; - } - public String getStatusMsg() { return statusMsg; }