/* ###
* 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.
*/
//DO NOT RUN. THIS IS NOT A SCRIPT! THIS IS A CLASS THAT IS USED BY SCRIPTS.
package classrecovery;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.app.cmd.function.ApplyFunctionSignatureCmd;
import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.label.SetLabelPrimaryCmd;
import ghidra.app.decompiler.DecompileOptions;
import ghidra.app.decompiler.component.DecompilerUtils;
import ghidra.app.decompiler.util.FillOutStructureHelper;
import ghidra.app.decompiler.util.FillOutStructureHelper.OffsetPcodeOpPair;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReference;
import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils;
import ghidra.app.util.NamespaceUtils;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.flatapi.FlatProgramAPI;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramMemoryUtil;
import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.bytesearch.*;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
public class RecoveredClassHelper {
public static final String DTM_CLASS_DATA_FOLDER_NAME = "ClassDataTypes";
public static final String DTM_CLASS_DATA_FOLDER_PATH = "/" + DTM_CLASS_DATA_FOLDER_NAME + "/";
private static final String CLASS_DATA_STRUCT_NAME = "_data";
private static final String DEFAULT_VFUNCTION_PREFIX = "vfunction";
private static final String VFUNCTION_COMMENT = "virtual function #";
private static final String CLASS_VFUNCTION_STRUCT_NAME = "_vftable";
private static final String CLASS_VTABLE_PTR_FIELD_EXT = "vftablePtr";
public static final String VFTABLE_LABEL = "vftable";
private static final String VBASE_DESTRUCTOR_LABEL = "vbase_destructor";
private static final String VBTABLE_LABEL = "vbtable";
private static final String VBTABLE_PTR = "vbtablePtr";
private static final String CLONE_LABEL = "clone";
private static final String DELETING_DESTRUCTOR_LABEL = "deleting_destructor";
private static final String BOOKMARK_CATEGORY = "RECOVERED CLASS";
private static final String INLINE_CONSTRUCTOR_BOOKMARK = "INLINED CONSTRUCTOR";
private static final String INLINE_DESTRUCTOR_BOOKMARK = "INLINED DESTRUCTOR";
private static final String INDETERMINATE_INLINE_BOOKMARK = "INDETERMINATE INLINE";
private static final int NONE = -1;
private static final boolean DEBUG = false;
private Map
vftableToClassMap = new HashMap();
// map from vftable references to the vftables they point to
private Map vftableRefToVftableMap = new HashMap();
// map from function to its vftable references
private Map> functionToVftableRefsMap =
new HashMap>();
// map from function to class(es) it is in
private Map> functionToClassesMap =
new HashMap>();
// map from class to list of possible parent classes
Map> possibleParentMap =
new HashMap>();
// map from namespace to class object
Map namespaceToClassMap = new HashMap();
Map> functionToStorePcodeOps =
new HashMap>();
Map> functionToLoadPcodeOps =
new HashMap>();
List allConstructors = new ArrayList();
List allDestructors = new ArrayList();
List allInlinedConstructors = new ArrayList();
List allInlinedDestructors = new ArrayList();
List nonClassInlines = new ArrayList();
Set badFIDNamespaces = new HashSet();
List badFIDStructures = new ArrayList();
List badFIDFunctions = new ArrayList();
List resolvedFIDFunctions = new ArrayList();
List fixedFIDFunctions = new ArrayList();
List operatorNews = new ArrayList();
List operatorDeletes = new ArrayList();
private static Function purecall = null;
private static Function atexit = null;
List atexitCalledFunctions = new ArrayList();
protected final GlobalNamespace globalNamespace;
protected final DataTypeManager dataTypeManager;
protected final int defaultPointerSize;
protected final SymbolTable symbolTable;
protected final ExtendedFlatProgramAPI extendedFlatAPI;
protected final DecompilerScriptUtils decompilerUtils;
protected final CategoryPath classDataTypesCategoryPath;
protected final TaskMonitor monitor;
protected final Program program;
protected final ServiceProvider serviceProvider;
protected final FlatProgramAPI api;
protected final boolean createBookmarks;
protected final boolean useShortTemplates;
protected final boolean nameVfunctions;
public RecoveredClassHelper(Program program, ServiceProvider serviceProvider,
FlatProgramAPI api, boolean createBookmarks, boolean useShortTemplates,
boolean nameVunctions, TaskMonitor monitor) throws Exception {
this.monitor = monitor;
this.program = program;
this.serviceProvider = serviceProvider;
this.api = api;
extendedFlatAPI = new ExtendedFlatProgramAPI(program, monitor);
CategoryPath path = new CategoryPath(CategoryPath.ROOT, DTM_CLASS_DATA_FOLDER_NAME);
this.classDataTypesCategoryPath = path;
this.createBookmarks = createBookmarks;
this.useShortTemplates = useShortTemplates;
this.nameVfunctions = nameVunctions;
globalNamespace = (GlobalNamespace) program.getGlobalNamespace();
decompilerUtils = new DecompilerScriptUtils(program, serviceProvider, monitor);
dataTypeManager = program.getDataTypeManager();
symbolTable = program.getSymbolTable();
defaultPointerSize = program.getDefaultPointerSize();
}
public void updateVftableToClassMap(Address vftableAddress, RecoveredClass recoveredClass) {
if (vftableToClassMap.get(vftableAddress) == null) {
vftableToClassMap.put(vftableAddress, recoveredClass);
}
}
public RecoveredClass getVftableClass(Address vftableAddress) {
return vftableToClassMap.get(vftableAddress);
}
/**
* Method to create a mappings between the list of vftable references to the vftables they reference
* @param referencesToVftable addresses that reference the given vftable address
* @param vftableAddress the given vftable address
* @throws CancelledException if cancelled
*/
public void addReferenceToVtableMapping(List referencesToVftable,
Address vftableAddress) throws CancelledException {
for (Address vtableReference : referencesToVftable) {
monitor.checkCancelled();
vftableRefToVftableMap.put(vtableReference, vftableAddress);
}
}
public Address getVftableAddress(Address vftableReference) {
return vftableRefToVftableMap.get(vftableReference);
}
/**
* Method to update the functionToVftableListMap with the given input
* @param vftableRefToFunctionMapping a mapping of a vftable reference to the function it is in
* @throws CancelledException if cancelled
*/
public void addFunctionToVftableReferencesMapping(
Map vftableRefToFunctionMapping) throws CancelledException {
Set keySet = vftableRefToFunctionMapping.keySet();
for (Address vtableReference : keySet) {
monitor.checkCancelled();
Function function = vftableRefToFunctionMapping.get(vtableReference);
if (functionToVftableRefsMap.containsKey(function)) {
List referenceList = functionToVftableRefsMap.get(function);
if (!referenceList.contains(vtableReference)) {
List newReferenceList = new ArrayList(referenceList);
newReferenceList.add(vtableReference);
functionToVftableRefsMap.replace(function, referenceList, newReferenceList);
}
}
else {
List referenceList = new ArrayList();
referenceList.add(vtableReference);
functionToVftableRefsMap.put(function, referenceList);
}
}
}
public List getVftableReferences(Function function) {
return functionToVftableRefsMap.get(function);
}
/**
* Method to add function to map of class it is contained in some functions are in
* multiple classes becuase they have references to multiple class vtables either
* because they have an inlined parent
* @param functions the given list of functions
* @param recoveredClass the given class
* @throws CancelledException if cancelled
*/
public void addFunctionsToClassMapping(List functions, RecoveredClass recoveredClass)
throws CancelledException {
for (Function function : functions) {
monitor.checkCancelled();
// if the map already contains a mapping for function and if
// the associated class list doesn't contain the new class, then
// add the new class and update the mapping
if (functionToClassesMap.containsKey(function)) {
List classList = functionToClassesMap.get(function);
if (!classList.contains(recoveredClass)) {
List newClassList = new ArrayList(classList);
newClassList.add(recoveredClass);
functionToClassesMap.replace(function, classList, newClassList);
}
}
// if the map doesn't contain a mapping for function, then add it
else {
List classList = new ArrayList();
classList.add(recoveredClass);
functionToClassesMap.put(function, classList);
}
}
}
public List getClasses(Function function) {
return functionToClassesMap.get(function);
}
/**
* Method to find all the vftables in the program
* @return list of all vftable symbols
* @throws CancelledException when cancelled
*/
public List getListOfVftableSymbols() throws CancelledException {
// Do with *'s to also get the PDB ones
SymbolIterator vftableSymbols =
program.getSymbolTable().getSymbolIterator("*vftable*", true);
List vftableSymbolList = new ArrayList();
while (vftableSymbols.hasNext()) {
monitor.checkCancelled();
Symbol vftableSymbol = vftableSymbols.next();
if (vftableSymbol.getName().equals("vftable")) {
vftableSymbolList.add(vftableSymbol);
}
// check for ones that are pdb that start with ' and may or may not end with '
// can't just get all that contain vftable because that would get some strings
else {
String name = vftableSymbol.getName();
name = name.substring(1, name.length());
if (name.startsWith("vftable")) {
vftableSymbolList.add(vftableSymbol);
}
}
}
return vftableSymbolList;
}
/**
* Method to return a symbol with the given name in the given namespace which is in the given
* parent namespace or null if one is not found
* @param parentNamespaceName name of parent namespace
* @param namespaceName name of symbol namespace
* @param symbolName name of symbol
* @return Symbol with given name, namespace and parent namespace or null if doesn't exist
* @throws CancelledException if cancelled
*/
public Symbol getSymbolInNamespaces(String parentNamespaceName, String namespaceName,
String symbolName) throws CancelledException {
SymbolIterator symbols = program.getSymbolTable().getSymbols(symbolName);
while (symbols.hasNext()) {
monitor.checkCancelled();
Symbol symbol = symbols.next();
if (symbol.getParentNamespace().getName().equals(namespaceName)) {
Namespace namespace = symbol.getParentNamespace();
Namespace parentNamespace = namespace.getParentNamespace();
// make this exact parent-namespace-name is global and is not in yet a further namespace
if (parentNamespace.getName().equals(parentNamespaceName) &&
parentNamespace.getParentNamespace().isGlobal()) {
return symbol;
}
}
}
return null;
}
/**
* Method to return a list symbol in the given namespace which is in the given
* parent namespace or null if one is not found
* @param parentNamespaceName name of parent namespace
* @param namespaceName name of symbol namespace
* @return Symbol with given name, namespace and parent namespace or null if doesn't exist
* @throws CancelledException if cancelled
*/
public List getSymbolsInNamespaces(String parentNamespaceName, String namespaceName)
throws CancelledException {
List symbolsInNamespace = new ArrayList();
SymbolIterator symbols = program.getSymbolTable().getAllSymbols(false);
while (symbols.hasNext()) {
monitor.checkCancelled();
Symbol symbol = symbols.next();
if (symbol.getParentNamespace().getName().equals(namespaceName)) {
Namespace namespace = symbol.getParentNamespace();
if (namespace.getParentNamespace().getName().equals(parentNamespaceName)) {
symbolsInNamespace.add(symbol);
}
}
}
return symbolsInNamespace;
}
public Address getSingleAddressOfSymbolContainingBothStrings(String string1, String string2)
throws CancelledException {
List symbolAddressList = new ArrayList();
SymbolIterator symbols =
program.getSymbolTable().getSymbolIterator("*" + string1 + "*", true);
while (symbols.hasNext()) {
monitor.checkCancelled();
Symbol symbol = symbols.next();
Address symbolAddress = symbol.getAddress();
if (symbol.getName().contains(string2)) {
if (!symbolAddressList.contains(symbolAddress)) {
symbolAddressList.add(symbolAddress);
}
}
}
if (symbolAddressList.size() == 1) {
return symbolAddressList.get(0);
}
return null;
}
public List getSymbolsContainingBothStrings(String string1, String string2)
throws CancelledException {
List symbolList = new ArrayList();
SymbolIterator symbols =
program.getSymbolTable().getSymbolIterator("*" + string1 + "*", true);
while (symbols.hasNext()) {
monitor.checkCancelled();
Symbol symbol = symbols.next();
if (symbol.getName().contains(string2)) {
symbolList.add(symbol);
}
}
return symbolList;
}
/**
* Method to create a map of calling address to called function for the given function.
* @param function the given function
* @param getThunkedFunction if true, use the thunked function in the map, if false use the
* directly called function from the calling function even if it is a thunk
* @return a map of the given functions calling addresses to the called functions
* @throws CancelledException if cancelled
*/
public Map getFunctionCallMap(Function function, boolean getThunkedFunction)
throws CancelledException {
Map functionCallMap = new HashMap();
InstructionIterator instructions =
function.getProgram().getListing().getInstructions(function.getBody(), true);
while (instructions.hasNext()) {
monitor.checkCancelled();
Instruction instruction = instructions.next();
if (instruction.getFlowType().isCall()) {
Function calledFunction = extendedFlatAPI
.getReferencedFunction(instruction.getMinAddress(), getThunkedFunction);
// include the null functions in map so things using map can get accurate count
// of number of CALL instructions even if the call reg type
functionCallMap.put(instruction.getMinAddress(), calledFunction);
}
}
return functionCallMap;
}
public void updateNamespaceToClassMap(Namespace namespace, RecoveredClass recoveredClass) {
namespaceToClassMap.put(namespace, recoveredClass);
}
public RecoveredClass getClass(Namespace namespace) {
return namespaceToClassMap.get(namespace);
}
public void updateFunctionToStorePcodeOpsMap(Function function,
List offsetPcodeOpPairs) {
if (functionToStorePcodeOps.get(function) == null) {
functionToStorePcodeOps.put(function, offsetPcodeOpPairs);
}
}
public List getStorePcodeOpPairs(Function function) {
return functionToStorePcodeOps.get(function);
}
public void updateFunctionToLoadPcodeOpsMap(Function function,
List offsetPcodeOpPairs) {
if (functionToLoadPcodeOps.get(function) == null) {
functionToLoadPcodeOps.put(function, offsetPcodeOpPairs);
}
}
public List getLoadPcodeOpPairs(Function function) {
return functionToLoadPcodeOps.get(function);
}
public Set getAllVfunctions(List vftableAddresses)
throws CancelledException {
Set allVfunctionsSet = new HashSet();
if (vftableAddresses.isEmpty()) {
return allVfunctionsSet;
}
for (Address vftableAddress : vftableAddresses) {
monitor.checkCancelled();
allVfunctionsSet.addAll(getVfunctions(vftableAddress));
}
return allVfunctionsSet;
}
public Set getAllClassFunctionsWithVtableRef(List vftables)
throws CancelledException {
Set allFunctionsThatReferenceVftables = new HashSet();
if (vftables.isEmpty()) {
return allFunctionsThatReferenceVftables;
}
for (Address vftableAddress : vftables) {
monitor.checkCancelled();
List functionsThatReferenceVftable =
geFunctionsThatReferenceAddress(vftableAddress);
if (functionsThatReferenceVftable.isEmpty()) {
continue;
}
for (Function function : functionsThatReferenceVftable) {
monitor.checkCancelled();
allFunctionsThatReferenceVftables.add(function);
}
}
return allFunctionsThatReferenceVftables;
}
public Set getAllPossibleConstructorDestructors(List vftables)
throws CancelledException {
Set possibleCDs = new HashSet();
if (vftables.isEmpty()) {
return possibleCDs;
}
for (Address vftableAddress : vftables) {
monitor.checkCancelled();
List possibleCDFunctions = geFunctionsThatReferenceAddress(vftableAddress);
if (possibleCDFunctions.isEmpty()) {
continue;
}
for (Function possibleCDFunction : possibleCDFunctions) {
monitor.checkCancelled();
// possible cd's that are also virtual functions cannot be cds
if (getAllVfunctions(vftables).contains(possibleCDFunction)) {
continue;
}
possibleCDs.add(possibleCDFunction);
}
}
return possibleCDs;
}
public void addToAllConstructors(Function function) {
allConstructors.add(function);
}
public void removeFromAllConstructors(Function function) {
allConstructors.remove(function);
}
public List getAllConstructors() {
return allConstructors;
}
public void addToAllDestructors(Function function) {
allDestructors.add(function);
}
public void removeFromAllDestructors(Function function) {
allDestructors.remove(function);
}
public List getAllDestructors() {
return allDestructors;
}
public void addToAllInlinedConstructors(Function function) {
allInlinedConstructors.add(function);
}
public void removeFromAllInlinedConstructors(Function function) {
allInlinedConstructors.remove(function);
}
public List getAllInlinedConstructors() {
return allInlinedConstructors;
}
public void addToAllInlinedDestructors(Function function) {
allInlinedDestructors.add(function);
}
public void removeFromAllInlinedDestructors(Function function) {
allInlinedDestructors.remove(function);
}
public List getAllInlinedDestructors() {
return allInlinedDestructors;
}
/**
* Method to determine if referenced vftables are from the same class
* @param vftableReferences list of vftable references
* @return true if all listed vftable references refer to vftables from the same class, false otherwise
* @throws CancelledException when cancelled
*/
public boolean areVftablesInSameClass(List vftableReferences)
throws CancelledException {
List classes = new ArrayList();
for (Address vftableReference : vftableReferences) {
monitor.checkCancelled();
Address vftableAddress = vftableRefToVftableMap.get(vftableReference);
RecoveredClass recoveredClass = vftableToClassMap.get(vftableAddress);
if (!classes.contains(recoveredClass)) {
classes.add(recoveredClass);
}
}
if (classes.size() > 1) {
return false;
}
return true;
}
/**
* Method to return the first reference to the class vftable in the given function
* @param recoveredClass the given class
* @param function the given function
* @return the reference to the class vftable in the given function or null if there isn't one
* @throws CancelledException if cancelled
*/
public Address getFirstClassVftableReference(RecoveredClass recoveredClass, Function function)
throws CancelledException {
List vftableReferenceList = functionToVftableRefsMap.get(function);
if (vftableReferenceList == null) {
return null;
}
Collections.sort(vftableReferenceList);
for (Address vftableRef : vftableReferenceList) {
monitor.checkCancelled();
Address vftableAddress = vftableRefToVftableMap.get(vftableRef);
if (vftableAddress == null) {
continue;
}
RecoveredClass referencedClass = vftableToClassMap.get(vftableAddress);
if (referencedClass == null) {
continue;
}
if (referencedClass.equals(recoveredClass)) {
return vftableRef;
}
}
return null;
}
/**
* Method to get a sorted list of both vftable and call refs to ancestor classes of the given
* class in the given function
* @param function the given function
* @param recoveredClass the given class
* @return a sorted list of both vftable and call refs to ancestor classes of the given
* class in the given function
* @throws CancelledException if cancelled
*/
public List getSortedListOfAncestorRefsInFunction(Function function,
RecoveredClass recoveredClass) throws CancelledException {
// get the map of all referenced vftables or constructor/desstructor calls in this function
Map referenceToClassMapForFunction =
getReferenceToClassMap(recoveredClass, function);
// get a list of all ancestor classes referenced in the map
List classHierarchy = recoveredClass.getClassHierarchy();
// make a list of all related class references
List listOfAncestorRefs = new ArrayList();
Set ancestorRefs = referenceToClassMapForFunction.keySet();
for (Address ancestorRef : ancestorRefs) {
monitor.checkCancelled();
RecoveredClass mappedClass = referenceToClassMapForFunction.get(ancestorRef);
if (classHierarchy.contains(mappedClass)) {
listOfAncestorRefs.add(ancestorRef);
}
}
Collections.sort(listOfAncestorRefs);
return listOfAncestorRefs;
}
/**
* Method to create a map of all references to classes in the given function. Classes are, for
* this purpose, referenced if they a vftable belonging to a class is referenced or if a
* constructor/destructor function from a class is called
* @param recoveredClass the given class
* @param function the given function
* @return Map of Address references to Class object for the given function
* @throws CancelledException when cancelled
*/
public Map getReferenceToClassMap(RecoveredClass recoveredClass,
Function function) throws CancelledException {
Map referenceToParentMap = new HashMap();
List vftableRefs = functionToVftableRefsMap.get(function);
if (vftableRefs == null) {
return referenceToParentMap;
}
// iterate through all vftable refs in the function and add it to ref/Parent map
for (Address vftableRef : vftableRefs) {
monitor.checkCancelled();
Address vftableAddress = extendedFlatAPI.getSingleReferencedAddress(vftableRef);
if (vftableAddress == null) {
continue;
}
RecoveredClass parentClass = vftableToClassMap.get(vftableAddress);
referenceToParentMap.put(vftableRef, parentClass);
}
// remove duplicate vftable refs (occasionally there are LEA then MOV of same vftable address
// a few intructions of each other. It confuses later processes to have both.
referenceToParentMap = dedupeMap(referenceToParentMap);
// loop through all the ref add/func pairs of called constructors/destructors in the function
// and get the class the constructor/destructor belongs to and add the ref/Parent pair to the
// same map as the vftable refs above
Map functionCallMap = getFunctionCallMap(function, true);
// if no calls then just return the ones found in the vftable section above
if (functionCallMap.isEmpty()) {
return referenceToParentMap;
}
for (Address address : functionCallMap.keySet()) {
monitor.checkCancelled();
Function calledFunction = functionCallMap.get(address);
// skip the call reg case
if (calledFunction == null) {
continue;
}
// skip if the called function doesn't have any references to a vftable (ie won't
// be a constructor or destructor in that case)
if (getVftableReferences(calledFunction) == null) {
continue;
}
RecoveredClass ancestorClass =
getAncestorClassWithGivenFunction(recoveredClass, calledFunction);
if (ancestorClass == null) {
continue;
}
referenceToParentMap.put(address, ancestorClass);
}
return referenceToParentMap;
}
/**
* Get the list of incorrect FID functions
* @return the list of incorrect FID functions
*/
public List getBadFIDFunctions() {
return badFIDFunctions;
}
/**
* Get the list of resolved functions that had multiple FID possibilities before but could
* be resolved to the correct name.
* @return the list of resolved FID functions
*/
public List getResolvedFIDFunctions() {
return badFIDFunctions;
}
/**
* Get the fixed functions that had incorrect data types due to incorrect FID (includes those
* incorrect due to decompiler propagation of bad types from bad FID)
* @return the fixed functions that had incorrect data types due to incorrect FID
*/
public List getFixedFIDFunctions() {
return badFIDFunctions;
}
private Map dedupeMap(Map map)
throws CancelledException {
// Sort the vftable refs in the order they appear in the function
Set vftableRefs = map.keySet();
List vftableRefList = new ArrayList(vftableRefs);
Collections.sort(vftableRefList);
RecoveredClass lastClass = null;
Address lastVftableRef = null;
for (Address vftableRef : vftableRefList) {
monitor.checkCancelled();
RecoveredClass currentClass = map.get(vftableRef);
if (lastClass != null && lastClass.equals(currentClass)) {
// if vftable refs are within a few instructions, dedupe map
if (numInstructionsApart(lastVftableRef, vftableRef) <= 3) {
map.remove(vftableRef);
}
}
lastClass = currentClass;
lastVftableRef = vftableRef;
}
return map;
}
private int numInstructionsApart(Address ref1, Address ref2) throws CancelledException {
Instruction instruction = program.getListing().getInstructionAfter(ref1);
int numApart = 0;
while (instruction != null && !instruction.contains(ref2)) {
monitor.checkCancelled();
numApart++;
instruction = program.getListing().getInstructionAfter(instruction.getMaxAddress());
}
return numApart;
}
/**
* Method to figure out which of the multiple parents of a class contain the given function in their class
* @param recoveredClass the given class with multiple parents
* @param function the given function that is in one of the parent classes
* @return the parent class that the given function belongs to
* @throws CancelledException if cancelled
*/
private RecoveredClass getAncestorClassWithGivenFunction(RecoveredClass recoveredClass,
Function function) throws CancelledException {
List classList = functionToClassesMap.get(function);
if (classList == null) {
return null;
}
if (classList.contains(recoveredClass)) {
classList.remove(recoveredClass);
}
if (classList.size() == 0) {
return null;
}
if (classList.size() == 1) {
return classList.get(0);
}
List parentClasses =
new ArrayList(recoveredClass.getClassHierarchyMap().keySet());
// try direct parents first
for (RecoveredClass parentClass : parentClasses) {
monitor.checkCancelled();
List constructorDestructorList =
new ArrayList(parentClass.getConstructorList());
constructorDestructorList.addAll(parentClass.getDestructorList());
if (constructorDestructorList.contains(function)) {
return parentClass;
}
}
// if not found in direct parents, try all ancestors
List ancestorClasses = recoveredClass.getClassHierarchy();
if (ancestorClasses.size() <= 1) {
return recoveredClass;
}
ListIterator ancestorIterator = ancestorClasses.listIterator(1);
while (ancestorIterator.hasNext()) {
monitor.checkCancelled();
RecoveredClass ancestorClass = ancestorIterator.next();
// already checked the parents
if (parentClasses.contains(ancestorClass)) {
continue;
}
List constructorDestructorList =
new ArrayList(ancestorClass.getConstructorList());
constructorDestructorList.addAll(ancestorClass.getDestructorList());
if (constructorDestructorList.contains(function)) {
return ancestorClass;
}
}
return null;
}
/**
* Method to get a list of addresses that are references from the given address
* @param address the given address
* @return a list of addresses that are references from the given address
*/
private List getReferenceFromAddresses(Address address) {
Reference[] referencesFrom = program.getReferenceManager().getReferencesFrom(address);
// get only the address references at the given address (ie no stack refs, ...)
List refFromAddresses = new ArrayList();
for (Reference referenceFrom : referencesFrom) {
if (referenceFrom.isMemoryReference()) {
refFromAddresses.add(referenceFrom.getToAddress());
}
}
return refFromAddresses;
}
/**
* Retrieve the first stored vftable from the pcodeOps in the list
* @param storedPcodeOps list of offset/PcodeOp pairs
* @return first referenced vftable address
* @throws CancelledException if cancelled
*/
public Address getStoredVftableAddress(List storedPcodeOps)
throws CancelledException {
if (storedPcodeOps.size() > 0) {
// figure out if vftable is referenced
for (OffsetPcodeOpPair offsetPcodeOpPair : storedPcodeOps) {
monitor.checkCancelled();
PcodeOp pcodeOp = offsetPcodeOpPair.getPcodeOp();
Varnode storedValue = pcodeOp.getInput(2);
Address vftableAddress = decompilerUtils.getAssignedAddressFromPcode(storedValue);
if (vftableAddress != null && vftableToClassMap.containsKey(vftableAddress)) {
return vftableAddress;
}
}
}
return null;
}
/**
* Method to get a list of addresses that reference the given vftable address
* @param vftableAddress the given vftable address
* @return list of addresses that reference the given vftable address
* @throws CancelledException if cancelled
*/
public List getReferencesToVftable(Address vftableAddress) throws CancelledException {
List referencesToVftable = new ArrayList<>();
ReferenceIterator iterator = program.getReferenceManager().getReferencesTo(vftableAddress);
while (iterator.hasNext()) {
monitor.checkCancelled();
Reference reference = iterator.next();
Address vtableReference = reference.getFromAddress();
referencesToVftable.add(vtableReference);
}
return referencesToVftable;
}
/**
* Method to determine if the given function is an inlined destructor or indeterminate in any class
* @param function the given function
* @return true if the given function is an inlined function in any class, false if it is not an
* inlined function in any class
* @throws CancelledException if cancelled
*/
public boolean isInlineDestructorOrIndeterminateInAnyClass(Function function)
throws CancelledException {
List functionClasses = getClasses(function);
if (functionClasses == null) {
if (DEBUG) {
Msg.debug(this, "no function to class map for " + function.getEntryPoint());
}
return true;
}
for (RecoveredClass recoveredClass : functionClasses) {
monitor.checkCancelled();
if (recoveredClass.getInlinedDestructorList().contains(function)) {
return true;
}
if (recoveredClass.getIndeterminateInlineList().contains(function)) {
return true;
}
}
return false;
}
/**
* Method to create mappings for the current class and use the decompiler
* to figure out class member data information
* @param recoveredClass class that function belongs to
* @param function current function to process
* @throws CancelledException if cancelled
* @throws DuplicateNameException if try to create same symbol name already in namespace
* @throws InvalidInputException if issues setting return type
* @throws CircularDependencyException if parent namespace is descendent of given namespace
*/
public void gatherClassMemberDataInfoForFunction(RecoveredClass recoveredClass,
Function function) throws CancelledException, DuplicateNameException,
InvalidInputException, CircularDependencyException {
// save off param and return information
Parameter[] originalParameters = function.getParameters();
DataType[] originalTypes = new DataType[originalParameters.length];
SourceType[] originalSources = new SourceType[originalParameters.length];
for (int i = 0; i < originalParameters.length; i++) {
monitor.checkCancelled();
originalTypes[i] = originalParameters[i].getDataType();
originalSources[i] = originalParameters[i].getSource();
}
DataType originalReturnType = function.getReturnType();
SourceType originalReturnSource = function.getReturn().getSource();
Namespace originalNamespace = function.getParentNamespace();
// temporarily remove class data type or other empty structures from return and params
// so they do not skew the structure contents from FillOutStructureCmd
temporarilyReplaceEmptyStructures(function, recoveredClass.getClassNamespace());
// create maps and updates class member data information using structure and pcode info returned
// from FillOutStructureCmd
updateMapsAndClassMemberDataInfo(function, recoveredClass);
// replace namespace if it was removed
if (!function.getParentNamespace().equals(originalNamespace)) {
function.setParentNamespace(originalNamespace);
}
// put back return and param data types that were temporarily removed
Parameter[] parameters = function.getParameters();
for (int i = 0; i < parameters.length; i++) {
monitor.checkCancelled();
if (parameters[i].getName().equals("this")) {
continue;
}
if (!parameters[i].getDataType().equals(originalTypes[i])) {
parameters[i].setDataType(originalTypes[i], originalSources[i]);
}
}
if (!function.getReturnType().equals(originalReturnType)) {
function.setReturnType(originalReturnType, originalReturnSource);
}
}
/**
* Method to update the given class's maps and class member data using the given function info
* @param function the given function
* @param recoveredClass the given class
* @throws CancelledException if cancelled
*/
private void updateMapsAndClassMemberDataInfo(Function function, RecoveredClass recoveredClass)
throws CancelledException {
List vftableReferenceList = getVftableReferences(function);
if (vftableReferenceList == null) {
if (DEBUG) {
Msg.debug(this, "In update maps: function to class map doesn't exist for " +
function.getEntryPoint().toString());
}
return;
}
Collections.sort(vftableReferenceList);
Address firstVftableReference = vftableReferenceList.get(0);
if (firstVftableReference == null) {
return;
}
// make sure first vftable ref is in the given class (if inlined it might not be)
Address vftableAddress = getVftableAddress(firstVftableReference);
RecoveredClass vftableClass = getVftableClass(vftableAddress);
if (!vftableClass.equals(recoveredClass)) {
if (DEBUG) {
Msg.debug(this,
"updating struct for " + recoveredClass.getName() +
" but first vftable in function " + function.getEntryPoint().toString() +
" is in class " + vftableClass.getName());
}
return;
}
getStructureFromDecompilerPcode(recoveredClass, function, firstVftableReference);
}
/**
* Method to retrieve a filled-in structure associated with the given function's high variable
* that stores the given first vftable reference address in the given function.
* @param recoveredClass the given class
* @param function the given function
* @param firstVftableReference the first vftable reference address in the given function
* @throws CancelledException if cancelled
*/
public void getStructureFromDecompilerPcode(RecoveredClass recoveredClass, Function function,
Address firstVftableReference) throws CancelledException {
// get the decompiler highFunction
HighFunction highFunction = decompilerUtils.getHighFunction(function);
if (highFunction == null) {
return;
}
List highVariables = new ArrayList();
// if there are params add the first or the "this" param to the list to be checked first
// It is the most likely to store the vftablePtr
if (highFunction.getFunctionPrototype().getNumParams() > 0) {
HighVariable thisParam =
highFunction.getFunctionPrototype().getParam(0).getHighVariable();
if (thisParam != null) {
highVariables.add(thisParam);
}
}
// add the other high variables that store vftable pointer
highVariables
.addAll(getVariableThatStoresVftablePointer(highFunction, firstVftableReference));
DecompileOptions decompileOptions =
DecompilerUtils.getDecompileOptions(serviceProvider, program);
FillOutStructureHelper fillStructHelper =
new FillOutStructureHelper(program, decompileOptions, monitor);
Address vftableAddress = null;
for (HighVariable highVariable : highVariables) {
monitor.checkCancelled();
Structure structure =
fillStructHelper.processStructure(highVariable, function, true, false);
NoisyStructureBuilder componentMap = fillStructHelper.getComponentMap();
List stores = fillStructHelper.getStorePcodeOps();
stores = removePcodeOpsNotInFunction(function, stores);
// this method checks the storedPcodeOps to see if one is the desired vftable address
vftableAddress = getStoredVftableAddress(stores);
if (vftableAddress != null &&
getVftableAddress(firstVftableReference).equals(vftableAddress)) {
if (structure != null) {
recoveredClass.updateClassMemberStructure(structure);
recoveredClass.updateClassMemberStructureUndefineds(componentMap);
}
List loads = fillStructHelper.getLoadPcodeOps();
loads = removePcodeOpsNotInFunction(function, loads);
//functionToStorePcodeOps.put(function, stores);
updateFunctionToStorePcodeOpsMap(function, stores);
updateFunctionToLoadPcodeOpsMap(function, loads);
return;
}
if (DEBUG) {
Msg.debug(this, "Could not find variable pointing to vftable in " +
function.getEntryPoint().toString());
}
}
}
/**
* Method to determine which variable in the decompiler stores the vftable address
* @param highFunction the decompiler high function
* @param vftableReference the address that points to a vftable
* @return the list of variables in the given function that store the vftable address
* @throws CancelledException if cancelled
*/
//TODO: Possibly refactor to use the same methodology as getAssignedAddressFrompcode or getStoredVftableAddress
private List getVariableThatStoresVftablePointer(HighFunction highFunction,
Address vftableReference) throws CancelledException {
List highVars = new ArrayList();
Iterator pcodeOps = highFunction.getPcodeOps();
while (pcodeOps.hasNext()) {
monitor.checkCancelled();
PcodeOp pcodeOp = pcodeOps.next();
if (pcodeOp.getOpcode() == PcodeOp.STORE) {
Address address = getTargetAddressFromPcodeOp(pcodeOp);
if (address.equals(vftableReference)) {
Varnode[] inputs = pcodeOp.getInputs();
for (Varnode input : inputs) {
monitor.checkCancelled();
if (input.getHigh() != null) {
highVars.add(input.getHigh());
}
}
}
}
}
return highVars;
}
/**
* temporarily change the function signature of the given constructor or destructor to replace
* any empty structure with same size undefined datatype and to also remove the functin from
* its namespace to remove the empty structure from the this param. This is so that the
* class member data calculations are made without bad info
* @param function the given function
* @param classNamespace the given class namespace
* @throws CancelledException if cancelled
* @throws DuplicateNameException if try to create same symbol name already in namespace
* @throws InvalidInputException if issue making function thiscall
* @throws CircularDependencyException if parent namespace is descendent of given namespace
*/
private void temporarilyReplaceEmptyStructures(Function function, Namespace classNamespace)
throws CancelledException, DuplicateNameException, InvalidInputException,
CircularDependencyException {
int parameterCount = function.getParameterCount();
for (int i = 0; i < parameterCount; i++) {
monitor.checkCancelled();
// if this call - temporarily put in global namespace to remove class structure
// in order to get unbiased pcode store information
if (function.getParameter(i).getName().equals("this")) {
function.setParentNamespace(globalNamespace);
continue;
}
DataType dataType = function.getParameter(i).getDataType();
if (!extendedFlatAPI.isPointerToEmptyStructure(dataType)) {
continue;
}
PointerDataType ptrUndefined =
extendedFlatAPI.createPointerToUndefinedDataType(dataType);
if (ptrUndefined != null) {
function.getParameter(i).setDataType(ptrUndefined, SourceType.ANALYSIS);
}
}
// Next check the return type to see if it is the empty structure
DataType returnType = function.getReturnType();
if (extendedFlatAPI.isPointerToEmptyStructure(returnType)) {
PointerDataType ptrUndefined =
extendedFlatAPI.createPointerToUndefinedDataType(returnType);
if (ptrUndefined != null) {
function.setReturnType(ptrUndefined, SourceType.ANALYSIS);
}
}
}
/**
* Method to determine if the given possible ancestor is an ancestor of any of the listed classes
* @param recoveredClasses List of classes
* @param possibleAncestor possible ancestor of one of the listed classes
* @return true if ancestor of one of the listed classes, false otherwise
* @throws Exception if one of the classes has empty class hierarchy
*/
public boolean isClassAnAncestorOfAnyOnList(List recoveredClasses,
RecoveredClass possibleAncestor) throws Exception {
for (RecoveredClass recoveredClass : recoveredClasses) {
monitor.checkCancelled();
if (isClassAnAncestor(recoveredClass, possibleAncestor)) {
return true;
}
}
return false;
}
/**
* Method to determine if a class is an ancestor of another class
* @param recoveredClass the class with possible ancestor
* @param possibleAncestorClass the class that might be ancestor of recoveredClass
* @return true if possibleAncestorClass is an ancestor of recoveredClass, false otherwise
* @throws Exception if class hierarchy is empty
*/
private boolean isClassAnAncestor(RecoveredClass recoveredClass,
RecoveredClass possibleAncestorClass) throws Exception {
List classHierarchy =
new ArrayList(recoveredClass.getClassHierarchy());
if (classHierarchy.isEmpty()) {
throw new Exception(
recoveredClass.getName() + " should not have an empty class hierarchy");
}
//remove self
classHierarchy.remove(recoveredClass);
if (classHierarchy.contains(possibleAncestorClass)) {
return true;
}
return false;
}
/**
* Method to return subList of offset/pcodeOp pairs from the given list that are contained in the current function
* @param function The given function
* @param pcodeOps The list of pcodeOps to filter
* @return List of pcodeOps from given list that are contained in given function
* @throws CancelledException when cancelled
*/
public List removePcodeOpsNotInFunction(Function function,
List pcodeOps) throws CancelledException {
Iterator pcodeOpsIterator = pcodeOps.iterator();
while (pcodeOpsIterator.hasNext()) {
monitor.checkCancelled();
OffsetPcodeOpPair offsetPcodeOpPair = pcodeOpsIterator.next();
PcodeOp pcodeOp = offsetPcodeOpPair.getPcodeOp();
Address pcodeOpAddress = pcodeOp.getSeqnum().getTarget();
if (!function.getBody().contains(pcodeOpAddress)) {
pcodeOpsIterator.remove();
}
}
return pcodeOps;
}
/**
* Method to get the listing address that the given PcodeOp is associated with
* @param pcodeOp the given PcodeOp
* @return the address the given PcodeOp is associated with
*/
public Address getTargetAddressFromPcodeOp(PcodeOp pcodeOp) {
return pcodeOp.getSeqnum().getTarget();
}
public Function getCalledFunctionFromCallPcodeOp(PcodeOp calledPcodeOp) {
Function calledFunction = api.getFunctionAt(calledPcodeOp.getInput(0).getAddress());
if (calledFunction == null) {
return null;
}
return calledFunction;
}
/**
* method to update the two given classes with their related class hierarchy
* @param parentClass parent of childClass
* @param childClass child of parentClass
*/
public void updateClassWithParent(RecoveredClass parentClass, RecoveredClass childClass) {
// return if they are already a known parent/child pair
if (parentClass.getChildClasses().contains(childClass) &&
childClass.getParentList().contains(parentClass)) {
return;
}
childClass.addParent(parentClass);
childClass.setHasParentClass(true);
parentClass.setHasChildClass(true);
parentClass.addChildClass(childClass);
return;
}
/**
* Method to retrieve only the classes that have vfunctions from the given list of classes
* @param recoveredClasses the given list of classes
* @return a list of classes that have vfunctions
* @throws CancelledException if cancelled
*/
public List getClassesWithVFunctions(List recoveredClasses)
throws CancelledException {
List classesWithVFunctions = new ArrayList();
for (RecoveredClass recoveredClass : recoveredClasses) {
monitor.checkCancelled();
if (recoveredClass.hasVftable()) {
classesWithVFunctions.add(recoveredClass);
}
}
return classesWithVFunctions;
}
/**
* Method that returns a list of all parents with virtual functions for the given class
* @param recoveredClass given class
* @return list of all parents with virtual functions for given class or empty list if none
* @throws CancelledException when cancelled
*/
public List getParentsWithVirtualFunctions(RecoveredClass recoveredClass)
throws CancelledException {
List parentsWithVFunctions = new ArrayList();
Map> classHierarchyMap =
recoveredClass.getClassHierarchyMap();
// no parents so return empty list
if (classHierarchyMap.isEmpty()) {
return parentsWithVFunctions;
}
List parentClassList =
new ArrayList(classHierarchyMap.keySet());
for (RecoveredClass parentClass : parentClassList) {
monitor.checkCancelled();
if (parentClass.hasVftable()) {
parentsWithVFunctions.add(parentClass);
}
}
return parentsWithVFunctions;
}
/**
* Method to get list of the given class's ancestors that have virtual functions or functions inherited virtual functions(ie a vftable)
* @param recoveredClass the given class
* @return list of the given class's ancestors that have virtual functions or functions inherited virtual functions(ie a vftable)
* @throws CancelledException if cancelled
*/
public List getAncestorsWithVirtualFunctions(RecoveredClass recoveredClass)
throws CancelledException {
List ancestorsWithVFunctions = new ArrayList();
// no parents so return empty list
if (!recoveredClass.hasParentClass()) {
return ancestorsWithVFunctions;
}
List classHierarchyList = recoveredClass.getClassHierarchy();
for (RecoveredClass parentClass : classHierarchyList) {
monitor.checkCancelled();
if (parentClass.hasVftable()) {
ancestorsWithVFunctions.add(parentClass);
}
}
return ancestorsWithVFunctions;
}
/**
* Method to get ancestors that do not have vfunctions
* @param recoveredClass the given class
* @return List of ancestors without vfunctions
* @throws CancelledException if cancelled
*/
public List getAncestorsWithoutVfunctions(RecoveredClass recoveredClass)
throws CancelledException {
List ancestorsWithoutVfunctions = new ArrayList();
List classHierarchy = recoveredClass.getClassHierarchy();
for (RecoveredClass ancestorClass : classHierarchy) {
monitor.checkCancelled();
// skip self
if (ancestorClass.equals(recoveredClass)) {
continue;
}
if (!ancestorClass.hasVftable()) {
ancestorsWithoutVfunctions.add(ancestorClass);
}
}
return ancestorsWithoutVfunctions;
}
/**
* Method to test whether class has ancestor without virtual functions or not
* @param recoveredClass given class object
* @return true if class has an ancestor class without virtual functions
* @throws CancelledException if cancelled
*/
public boolean hasNonVirtualFunctionAncestor(RecoveredClass recoveredClass)
throws CancelledException {
List classHierarchy = recoveredClass.getClassHierarchy();
for (RecoveredClass currentClass : classHierarchy) {
monitor.checkCancelled();
if (!currentClass.hasVftable()) {
return true;
}
}
return false;
}
/**
* Method to get all ancestor class constructors for the given class
* @param recoveredClass given class
* @return List of all functions that are constructors of an ancestor class of given class
* @throws CancelledException if script is cancelled
*/
public List getAllAncestorConstructors(RecoveredClass recoveredClass)
throws CancelledException {
List allAncestorConstructors = new ArrayList();
List classHierarchy = recoveredClass.getClassHierarchy();
if (classHierarchy == null) {
Msg.debug(this, recoveredClass.getName() + " has null class hierarchy list");
return allAncestorConstructors;
}
if (classHierarchy.isEmpty()) {
Msg.debug(this, recoveredClass.getName() + " has empty class hierarchy list");
return allAncestorConstructors;
}
ListIterator classHierarchyIterator = classHierarchy.listIterator(1);
while (classHierarchyIterator.hasNext()) {
monitor.checkCancelled();
RecoveredClass currentClass = classHierarchyIterator.next();
List constructorList =
new ArrayList(currentClass.getConstructorList());
constructorList.addAll(currentClass.getInlinedConstructorList());
for (Function constructor : constructorList) {
monitor.checkCancelled();
if (!allAncestorConstructors.contains(constructor)) {
allAncestorConstructors.add(constructor);
}
}
}
return allAncestorConstructors;
}
/**
* Method to retrieve a list of destructors for the ancestors of the given class
* @param recoveredClass the given class
* @return a list of destructors for the ancestors of the given class
* @throws CancelledException if cancelled
*/
public List getAncestorDestructors(RecoveredClass recoveredClass)
throws CancelledException {
List allAncestorDestructors = new ArrayList();
List classHierarchy = recoveredClass.getClassHierarchy();
if (classHierarchy == null) {
Msg.debug(this, recoveredClass.getName() + " has null class hierarchy list");
return allAncestorDestructors;
}
if (classHierarchy.isEmpty()) {
Msg.debug(this, recoveredClass.getName() + " has empty class hierarchy list");
return allAncestorDestructors;
}
ListIterator classHierarchyIterator = classHierarchy.listIterator(1);
while (classHierarchyIterator.hasNext()) {
monitor.checkCancelled();
RecoveredClass parentClass = classHierarchyIterator.next();
List destructorList =
new ArrayList(parentClass.getDestructorList());
destructorList.addAll(parentClass.getInlinedDestructorList());
for (Function destructor : destructorList) {
monitor.checkCancelled();
if (!allAncestorDestructors.contains(destructor)) {
allAncestorDestructors.add(destructor);
}
}
}
return allAncestorDestructors;
}
/**
* Method to retrieve all constructors of descendants of the given class
* @param recoveredClass the given class
* @return a list of all constructors of descendants of the given class
* @throws CancelledException if cancelled
*/
public List getAllDescendantConstructors(RecoveredClass recoveredClass)
throws CancelledException {
List allDescendantConstructors = new ArrayList();
List childClasses = recoveredClass.getChildClasses();
for (RecoveredClass childClass : childClasses) {
monitor.checkCancelled();
List constructorList =
new ArrayList(childClass.getConstructorList());
constructorList.addAll(childClass.getInlinedConstructorList());
for (Function constructor : constructorList) {
monitor.checkCancelled();
if (!allDescendantConstructors.contains(constructor)) {
allDescendantConstructors.add(constructor);
}
}
allDescendantConstructors.addAll(getAllDescendantConstructors(childClass));
}
return allDescendantConstructors;
}
/**
* Method to retrieve all destructors of descendants of the given class
* @param recoveredClass the given class
* @return a list of all destructors of descendants of the given class
* @throws CancelledException if cancelled
*/
public List getAllDescendantDestructors(RecoveredClass recoveredClass)
throws CancelledException {
List allDescendantDestructors = new ArrayList();
List childClasses = recoveredClass.getChildClasses();
for (RecoveredClass childClass : childClasses) {
monitor.checkCancelled();
List destructorList = new ArrayList(childClass.getDestructorList());
destructorList.addAll(childClass.getInlinedDestructorList());
for (Function destructor : destructorList) {
monitor.checkCancelled();
if (!allDescendantDestructors.contains(destructor)) {
allDescendantDestructors.add(destructor);
}
}
allDescendantDestructors.addAll(getAllDescendantDestructors(childClass));
}
return allDescendantDestructors;
}
/**
* Method to retrieve a list of possible parent class constructors to the given function
* @param function the given function
* @return a list of possible parent class constructors to the given function
* @throws CancelledException if cancelled
*/
public List getPossibleParentConstructors(Function function)
throws CancelledException {
List possibleParentConstructors = new ArrayList();
List vftableReferenceList = getVftableReferences(function);
if (vftableReferenceList == null) {
return possibleParentConstructors;
}
Collections.sort(vftableReferenceList);
Address minVftableReference = vftableReferenceList.get(0);
InstructionIterator instructions =
function.getProgram().getListing().getInstructions(function.getBody(), true);
while (instructions.hasNext()) {
monitor.checkCancelled();
Instruction instruction = instructions.next();
if (instruction.getMinAddress().compareTo(minVftableReference) >= 0) {
return possibleParentConstructors;
}
if (instruction.getFlowType().isCall()) {
Function calledFunction =
extendedFlatAPI.getReferencedFunction(instruction.getMinAddress(), true);
if (calledFunction == null) {
continue;
}
if (getVftableReferences(calledFunction) == null) {
continue;
}
possibleParentConstructors.add(calledFunction);
}
}
return possibleParentConstructors;
}
/**
* Method to retrieve a list of possible parent class destructors to the given function
* @param function the given function
* @return a list of possible parent class destructors to the given function
* @throws CancelledException if cancelled
*/
public List getPossibleParentDestructors(Function function)
throws CancelledException {
List possibleParentDestructors = new ArrayList();
List vftableReferenceList = getVftableReferences(function);
if (vftableReferenceList == null) {
return possibleParentDestructors;
}
Collections.sort(vftableReferenceList, Collections.reverseOrder());
Address maxVftableReference = vftableReferenceList.get(0);
InstructionIterator instructions =
function.getProgram().getListing().getInstructions(function.getBody(), true);
while (instructions.hasNext()) {
monitor.checkCancelled();
Instruction instruction = instructions.next();
if (instruction.getMinAddress().compareTo(maxVftableReference) <= 0) {
continue;
}
if (instruction.getFlowType().isCall()) {
Function calledFunction =
extendedFlatAPI.getReferencedFunction(instruction.getMinAddress(), true);
if (calledFunction == null) {
continue;
}
if (getVftableReferences(calledFunction) == null) {
continue;
}
possibleParentDestructors.add(calledFunction);
}
}
return possibleParentDestructors;
}
/**
* Method to retrieve a single common function on both lists
* @param list1 first list of functions
* @param list2 second list of functions
* @return single function if there is one function on both lists, null if there are none or
* more than one
*/
public Function getFunctionOnBothLists(List list1, List list2) {
List commonFunctions = getFunctionsOnBothLists(list1, list2);
if (commonFunctions.size() == 1) {
return commonFunctions.get(0);
}
// if none or more than one return null
return null;
}
public List getFunctionsOnBothLists(List list1, List list2) {
List commonFunctions =
list1.stream().distinct().filter(list2::contains).collect(Collectors.toList());
return commonFunctions;
}
/**
* Method to retrieve a set of functions contained in both of the given sets of functions
* @param set1 the first set of functions
* @param set2 the second set of functions
* @return a set of functions contained in both of the given sets of functions
*/
public Set getFunctionsContainedInBothSets(Set set1, Set set2) {
Set commonFunctions =
set1.stream().distinct().filter(set2::contains).collect(Collectors.toSet());
return commonFunctions;
}
/**
* Method to determine the constructors/destructors using known parent
* @param recoveredClass RecoveredClass object
* @param parentClass possible parent class of the given RecoveredClass
* @return true if processed successfully, else false
* @throws CancelledException if cancelled
* @throws InvalidInputException if issue setting return type
* @throws DuplicateNameException if try to create same symbol name already in namespace
* @throws CircularDependencyException if parent namespace is descendent of given namespace
*/
public boolean processConstructorsAndDestructorsUsingParent(RecoveredClass recoveredClass,
RecoveredClass parentClass) throws CancelledException, InvalidInputException,
DuplicateNameException, CircularDependencyException {
if (parentClass == null) {
return false;
}
Map childParentConstructorMap = new HashMap();
Map childParentDestructorMap = new HashMap();
List constDestFunctions =
new ArrayList(recoveredClass.getConstructorOrDestructorFunctions());
constDestFunctions.removeAll(recoveredClass.getIndeterminateInlineList());
List parentConstDestFunctions = parentClass.getConstructorOrDestructorFunctions();
List parentConstructors = getAllClassConstructors(parentClass);
List parentDestructors = getAllClassDestructors(parentClass);
List childConstructors = getAllClassConstructors(recoveredClass);
List childDestructors = getAllClassDestructors(recoveredClass);
for (Function constDestFunction : constDestFunctions) {
monitor.checkCancelled();
// based on call order get possible parent constructors for the given function
List possibleParentConstructors =
getPossibleParentConstructors(constDestFunction);
// remove any known destructors since they can't also be constructors - rarely these
// show up on possible const list
possibleParentConstructors.removeAll(parentDestructors);
Function parentConstructor =
getFunctionOnBothLists(possibleParentConstructors, parentConstDestFunctions);
// another sanity check - make sure child function isn't a known destructor
if (parentConstructor != null && !childDestructors.contains(constDestFunction)) {
childParentConstructorMap.put(constDestFunction, parentConstructor);
continue;
}
// based on call order get possible parent destructors for the given function
List possibleParentDestructors =
getPossibleParentDestructors(constDestFunction);
// remove any known constructors since they can't also be destructors - rarely these
// show up on possible dest list
possibleParentDestructors.removeAll(parentConstructors);
Function parentDestructor =
getFunctionOnBothLists(possibleParentDestructors, parentConstDestFunctions);
// another sanity check - make sure child function isn't a known constructor
if (parentDestructor != null && !childConstructors.contains(constDestFunction)) {
childParentDestructorMap.put(constDestFunction, parentDestructor);
continue;
}
}
// check to make sure there is no overlap in the poss c and poss d maps
Set constructorKeySet = childParentConstructorMap.keySet();
Set destructorKeySet = childParentDestructorMap.keySet();
Set functionsContainedInBothSets =
getFunctionsContainedInBothSets(constructorKeySet, destructorKeySet);
if (functionsContainedInBothSets.size() > 0) {
constructorKeySet.removeAll(functionsContainedInBothSets);
destructorKeySet.removeAll(functionsContainedInBothSets);
if (constructorKeySet.isEmpty() && destructorKeySet.isEmpty()) {
return false;
}
}
// once all checks pass, add both the child and parent constructors to their class
// constructor list and remove from the indeterminate lists
// the addConstructor method processes the offsets and types for the initialized class data
for (Function childConstructor : constructorKeySet) {
monitor.checkCancelled();
addConstructorToClass(recoveredClass, childConstructor);
recoveredClass.removeIndeterminateConstructorOrDestructor(childConstructor);
Function parentConstructor = childParentConstructorMap.get(childConstructor);
addConstructorToClass(parentClass, parentConstructor);
parentClass.removeIndeterminateConstructorOrDestructor(parentConstructor);
}
// Do the same for the child/parent destructors
for (Function childDestructor : destructorKeySet) {
monitor.checkCancelled();
addDestructorToClass(recoveredClass, childDestructor);
recoveredClass.removeIndeterminateConstructorOrDestructor(childDestructor);
Function parentDestructor = childParentDestructorMap.get(childDestructor);
addDestructorToClass(parentClass, parentDestructor);
parentClass.removeIndeterminateConstructorOrDestructor(parentDestructor);
}
return true;
}
/**
* Method to retrieve all types of class destructors including normal destructors, non-this
* destructors and inline destructors(does not include deleting destructors since they are
* really a vfunction)
* @param recoveredClass the given class
* @return the list of all destructors for the given class
*/
public List getAllClassDestructors(RecoveredClass recoveredClass) {
List allClassDestructors = new ArrayList();
allClassDestructors.addAll(recoveredClass.getDestructorList());
allClassDestructors.addAll(recoveredClass.getNonThisDestructors());
allClassDestructors.addAll(recoveredClass.getInlinedDestructorList());
return allClassDestructors;
}
/**
* Method to retrieve all types of class constructors including normal constructors and inline
* constructors
* @param recoveredClass the given class
* @return the list of all constructors for the given class
*/
public List getAllClassConstructors(RecoveredClass recoveredClass) {
List allClassConstructors = new ArrayList();
allClassConstructors.addAll(recoveredClass.getConstructorList());
allClassConstructors.addAll(recoveredClass.getInlinedConstructorList());
return allClassConstructors;
}
/**
* Method to add constructor function to the given class and collect member data information
* @param recoveredClass given class
* @param constructorFunction given function
* @throws CancelledException if cancelled
* @throws InvalidInputException if error setting return type
* @throws DuplicateNameException if try to create same symbol name already in namespace
* @throws CircularDependencyException if parent namespace is descendent of given namespace
*/
public void addConstructorToClass(RecoveredClass recoveredClass, Function constructorFunction)
throws CancelledException, InvalidInputException, DuplicateNameException,
CircularDependencyException {
// skip if already a constructor for this class
if (recoveredClass.getConstructorList().contains(constructorFunction)) {
return;
}
// create vftable mapping for any class that didn't have a constructor when the
// original mappings were created
if (recoveredClass.getOrderToVftableMap().size() == 0) {
createVftableOrderMapping(recoveredClass);
}
recoveredClass.addConstructor(constructorFunction);
addToAllConstructors(constructorFunction);
}
/**
* Method to add inlined constructor function to the given class
* @param recoveredClass given class
* @param inlinedConstructorFunction given function
* @throws InvalidInputException if issues setting return type
* @throws DuplicateNameException if try to create same symbol name already in namespace
*/
public void addInlinedConstructorToClass(RecoveredClass recoveredClass,
Function inlinedConstructorFunction)
throws InvalidInputException, DuplicateNameException {
recoveredClass.addInlinedConstructor(inlinedConstructorFunction);
addToAllInlinedConstructors(inlinedConstructorFunction);
}
/**
* Method to add destructor to the given class
* @param recoveredClass given class
* @param destructorFunction given destructor function
* @throws InvalidInputException if issues setting return type
* @throws DuplicateNameException if try to create same symbol name already in namespace
*/
public void addDestructorToClass(RecoveredClass recoveredClass, Function destructorFunction)
throws InvalidInputException, DuplicateNameException {
recoveredClass.addDestructor(destructorFunction);
addToAllDestructors(destructorFunction);
}
/**
* Method to add inlined destructor to the given class
* @param recoveredClass given class
* @param inlinedDestructorFunction given function
* @throws InvalidInputException if error setting return type
* @throws DuplicateNameException if try to create same symbol name already in namespace
*/
public void addInlinedDestructorToClass(RecoveredClass recoveredClass,
Function inlinedDestructorFunction)
throws InvalidInputException, DuplicateNameException {
recoveredClass.addInlinedDestructor(inlinedDestructorFunction);
addToAllInlinedDestructors(inlinedDestructorFunction);
}
/**
* Method to create a mapping between the order it appears and the vftable for the given class
* @param recoveredClass the given class
* @throws CancelledException if cancelled
*/
public void createVftableOrderMapping(RecoveredClass recoveredClass) throws CancelledException {
if (!recoveredClass.hasVftable()) {
return;
}
List vftableAddresses = recoveredClass.getVftableAddresses();
Map classOffsetToVftableMap = recoveredClass.getClassOffsetToVftableMap();
if (classOffsetToVftableMap.size() == 0) {
return;
}
if (vftableAddresses.size() != classOffsetToVftableMap.size()) {
if (DEBUG) {
Msg.debug(this, recoveredClass.getName() + " has " + vftableAddresses.size() +
" vftables but " + classOffsetToVftableMap.size() + " offset to vftable maps");
}
}
List offsetList = new ArrayList(classOffsetToVftableMap.keySet());
Collections.sort(offsetList);
int order = 0;
for (Integer offset : offsetList) {
monitor.checkCancelled();
Address vftableAddress = classOffsetToVftableMap.get(offset);
recoveredClass.addOrderToVftableMapping(order, vftableAddress);
order++;
}
}
/**
* Method to create a map for each class between the order a vftable is seen in a class and the vftable itself
* @param recoveredClasses list of classes to processes
* @throws CancelledException if cancelled
*/
public void createVftableOrderMap(List recoveredClasses)
throws CancelledException {
for (RecoveredClass recoveredClass : recoveredClasses) {
monitor.checkCancelled();
// create a mapping of the order of the vftable to the vftable address and save to class
createVftableOrderMapping(recoveredClass);
}
}
/**
*
* @param referenceToClassMap map of references to the class that contains the referenced function
* @param referencesToConstructors list of addresses referring to constructors
* @throws CancelledException if cancelled
* @throws InvalidInputException if error setting return type
* @throws DuplicateNameException if try to create same symbol name already in namespace
* @throws CircularDependencyException if parent namespace is descendent of given namespace
*/
public void createListedConstructorFunctions(Map referenceToClassMap,
List referencesToConstructors) throws CancelledException,
InvalidInputException, DuplicateNameException, CircularDependencyException {
for (Address constructorReference : referencesToConstructors) {
monitor.checkCancelled();
RecoveredClass recoveredClass = referenceToClassMap.get(constructorReference);
Function constructor =
extendedFlatAPI.getReferencedFunction(constructorReference, true);
if (recoveredClass.getIndeterminateList().contains(constructor)) {
addConstructorToClass(recoveredClass, constructor);
recoveredClass.removeIndeterminateConstructorOrDestructor(constructor);
continue;
}
if (recoveredClass.getIndeterminateInlineList().contains(constructor)) {
processInlineConstructor(recoveredClass, constructor, referenceToClassMap);
}
}
}
/**
* Method to process a found inlined constructor
* @param recoveredClass the class being processed
* @param inlinedConstructorFunction the function containing an inlined ancestor constructor
* @param referenceToClassMap the map of references to classes
* @throws CancelledException if cancelled
* @throws InvalidInputException if issue setting return type
* @throws DuplicateNameException if try to create same symbol name already in namespace
* @throws CircularDependencyException if parent namespace is descendent of given namespace
*/
public void processInlineConstructor(RecoveredClass recoveredClass,
Function inlinedConstructorFunction, Map referenceToClassMap)
throws CancelledException, InvalidInputException, DuplicateNameException,
CircularDependencyException {
if (referenceToClassMap.isEmpty()) {
return;
}
List referencesToVftables = new ArrayList();
List referenceAddresses = new ArrayList(referenceToClassMap.keySet());
for (Address reference : referenceAddresses) {
monitor.checkCancelled();
Address vftableAddress = getVftableAddress(reference);
if (vftableAddress != null) {
referencesToVftables.add(reference);
}
}
if (referencesToVftables.isEmpty()) {
return;
}
Collections.sort(referencesToVftables);
int numRefs = referencesToVftables.size();
Address lastRef = referencesToVftables.get(numRefs - 1);
for (Address refToVftable : referencesToVftables) {
monitor.checkCancelled();
RecoveredClass referencedClass = referenceToClassMap.get(refToVftable);
// last reference is the constructor
if (refToVftable.equals(lastRef)) {
addConstructorToClass(referencedClass, inlinedConstructorFunction);
}
// the rest are inlined constructors
else {
addInlinedConstructorToClass(referencedClass, inlinedConstructorFunction);
}
referencedClass.removeIndeterminateInline(inlinedConstructorFunction);
}
return;
}
/**
* Method to process an inlinedDestructor function
* @param recoveredClass the class the inlinedDestructor is in
* @param inlinedDestructorFunction the inlined function
* @param referenceToClassMap the map of references to classes
* @throws CancelledException if cancelled
* @throws InvalidInputException if issue setting return type
* @throws DuplicateNameException if try to create same symbol name already in namespace
*/
public void processInlineDestructor(RecoveredClass recoveredClass,
Function inlinedDestructorFunction, Map referenceToClassMap)
throws CancelledException, InvalidInputException, DuplicateNameException {
if (referenceToClassMap.isEmpty()) {
return;
}
List referencesToVftables = new ArrayList();
List referenceAddresses = new ArrayList(referenceToClassMap.keySet());
for (Address reference : referenceAddresses) {
monitor.checkCancelled();
Address vftableAddress = getVftableAddress(reference);
if (vftableAddress != null) {
referencesToVftables.add(reference);
}
}
if (referencesToVftables.isEmpty()) {
return;
}
// reverse sort
Collections.sort(referencesToVftables, Collections.reverseOrder());
int numRefs = referencesToVftables.size();
Address lastRef = referencesToVftables.get(numRefs - 1);
for (Address refToVftable : referencesToVftables) {
monitor.checkCancelled();
RecoveredClass referencedClass = referenceToClassMap.get(refToVftable);
// last reference is the constructor
if (refToVftable.equals(lastRef)) {
addDestructorToClass(referencedClass, inlinedDestructorFunction);
}
// the rest are inlined constructors
else {
addInlinedDestructorToClass(referencedClass, inlinedDestructorFunction);
}
referencedClass.removeIndeterminateInline(inlinedDestructorFunction);
}
return;
}
/**
* Method to get the address that references the first vftable in the given function
* @param function the given function
* @return the address in the given function that references the first referenced vftable or
* null if no vftable is referenced in the given function
*/
public Address getFirstVftableReferenceInFunction(Function function) {
List vftableReferenceList = getVftableReferences(function);
Collections.sort(vftableReferenceList);
return vftableReferenceList.get(0);
}
/**
* Method to make the given function a thiscall
* @param function the given function
* @throws InvalidInputException if issues setting return type
* @throws DuplicateNameException if try to create same symbol name already in namespace
*/
public void makeFunctionThiscall(Function function)
throws InvalidInputException, DuplicateNameException {
if (function.getCallingConventionName().equals(CompilerSpec.CALLING_CONVENTION_thiscall)) {
return;
}
ReturnParameterImpl returnType =
new ReturnParameterImpl(function.getSignature().getReturnType(), program);
function.updateFunction(CompilerSpec.CALLING_CONVENTION_thiscall, returnType,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, function.getSignatureSource(),
function.getParameters());
}
/**
* Method to determine if the given function calls a known constructor or inlined constructor
* @param callingFunction the given calling function
* @return true if calling function calls a known constructor or inlined constructor, false otherwise
* @throws CancelledException if cancelled
*/
public boolean callsKnownConstructor(Function callingFunction) throws CancelledException {
InstructionIterator instructions = callingFunction.getProgram()
.getListing()
.getInstructions(callingFunction.getBody(), true);
while (instructions.hasNext()) {
monitor.checkCancelled();
Instruction instruction = instructions.next();
if (instruction.getFlowType().isCall()) {
Function calledFunction =
extendedFlatAPI.getReferencedFunction(instruction.getMinAddress(), true);
if (calledFunction == null) {
continue;
}
if (getAllConstructors().contains(calledFunction) ||
getAllInlinedConstructors().contains(calledFunction)) {
return true;
}
}
}
return false;
}
/**
* Method to determine if the given function calls a known constructor or inlined constructor
* @param callingFunction the given calling function
* @return true if function calls a known constructor or inlined constructor, false otherwise
* @throws CancelledException if cancelled
*/
public boolean callsKnownDestructor(Function callingFunction) throws CancelledException {
InstructionIterator instructions = callingFunction.getProgram()
.getListing()
.getInstructions(callingFunction.getBody(), true);
while (instructions.hasNext()) {
monitor.checkCancelled();
Instruction instruction = instructions.next();
if (instruction.getFlowType().isCall()) {
Function calledFunction =
extendedFlatAPI.getReferencedFunction(instruction.getMinAddress(), true);
if (calledFunction == null) {
continue;
}
if (getAllDestructors().contains(calledFunction) ||
getAllInlinedDestructors().contains(calledFunction)) {
return true;
}
}
}
return false;
}
/**
* Method to get the total number of constructors in the given list of classes
* @param recoveredClasses list of classes to process
* @return number of constructor functions in all classes
* @throws CancelledException if cancelled
*/
public int getNumberOfConstructors(List recoveredClasses)
throws CancelledException {
int total = 0;
for (RecoveredClass recoveredClass : recoveredClasses) {
monitor.checkCancelled();
List