diff --git a/Ghidra/Features/Base/ghidra_scripts/GraphClassesScript.java b/Ghidra/Features/Base/ghidra_scripts/GraphClassesScript.java index f0bec607e1..cf88d951f9 100644 --- a/Ghidra/Features/Base/ghidra_scripts/GraphClassesScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/GraphClassesScript.java @@ -16,7 +16,8 @@ //Script to graph class hierarchies given metadata found in class structure description that // was applied using the RecoverClassesFromRTTIScript. //@category C++ -import java.util.*; +import java.util.ArrayList; +import java.util.List; import ghidra.app.script.GhidraScript; import ghidra.app.services.GraphDisplayBroker; @@ -46,7 +47,7 @@ public class GraphClassesScript extends GhidraScript { Category category = dataTypeManager.getCategory(dataTypePath); if (category == null) { println( - "/ClassDataTypes folder does not exist so there is no class data to process. Please run the ExtractClassInfoFromRTTIScript to generate the necessary information needed to run this script."); + "/ClassDataTypes folder does not exist so there is no class data to process. Please run the RecoverClassesFromRTTIScript to generate the necessary information needed to run this script."); return; } @@ -54,6 +55,11 @@ public class GraphClassesScript extends GhidraScript { getClassStructures(subCategories); + if (classStructures.isEmpty()) { + println("There were no class structures to process."); + return; + } + AttributedGraph graph = createGraph(); if (graph.getVertexCount() == 0) { println( @@ -93,43 +99,47 @@ public class GraphClassesScript extends GhidraScript { } } - private AttributedGraph createGraph() throws CancelledException { + /** + * Method to create a graph using preconfigured information found in class structure descriptions. + * The structure descriptions are created using + * {@link RecoveredClassUtils#createParentStringBuffer(RecoveredClass)} + * @return the newly created graph + */ + private AttributedGraph createGraph() throws Exception { AttributedGraph g = new AttributedGraph(); - Iterator classStructuresIterator = classStructures.iterator(); - while (classStructuresIterator.hasNext()) { + for (Structure classStructure : classStructures) { monitor.checkCanceled(); - Structure classStructure = classStructuresIterator.next(); - String description = classStructure.getDescription(); - String mainClassName = getClassName(description); - if (mainClassName == null) { + // parse description for class hierarchy + if (!description.startsWith("class")) { continue; } - AttributedVertex classVertex = g.addVertex(mainClassName); + // skip "class " to get overall class + description = description.substring(6); + String mainClassName = getClassName(description); + + if (mainClassName == null || mainClassName.isBlank()) { + continue; + } + + AttributedVertex classVertex = + g.addVertex(classStructure.getCategoryPath().getPath(), mainClassName); + classVertex.setDescription(classStructure.getCategoryPath().getPath()); int numParents = 0; - while (description.contains(":")) { + description = removeClassSubstring(description, mainClassName); + + while (description != null) { numParents++; - int indexOfColon = description.indexOf(":", 0); - - description = description.substring(indexOfColon + 1); - - int endOfBlock = description.indexOf(":", 0); - if (endOfBlock == -1) { - endOfBlock = description.length(); - } - - String parentName = description.substring(0, endOfBlock); - - description = description.substring(endOfBlock); + String parentName = getClassName(description); boolean isVirtualParent = false; if (parentName.contains("virtual")) { @@ -139,14 +149,38 @@ public class GraphClassesScript extends GhidraScript { parentName = parentName.replace("virtual", ""); parentName = parentName.replace(" ", ""); + // first try to get parent structure from inside child structure + Structure parentStructure = + getParentStructureFromChildStructure(classStructure, parentName); - AttributedVertex parentVertex = g.addVertex(parentName); + // if parent structure isn't in child structure then try to get it by name + // from the list of class structures - only returns one if unique + if (parentStructure == null) { + parentStructure = getParentStructureFromClassStructures(parentName); + } + + AttributedVertex parentVertex; + if (parentStructure == null) { + parentVertex = g.addVertex(parentName); + parentVertex.setDescription("Couldn't get parent structure " + parentName + + " from structure " + classStructure.getName() + + " or uniquely from all class structures"); + println("Couldn't get parent structure " + parentName + " from structure " + + classStructure.getName() + " or uniquely from all class structures"); + } + else { + parentVertex = + g.addVertex(parentStructure.getCategoryPath().getPath(), parentName); + parentVertex.setDescription(parentStructure.getCategoryPath().getPath()); + } AttributedEdge edge = g.addEdge(parentVertex, classVertex); if (isVirtualParent) { edge.setAttribute("Color", "Orange"); } // else leave it default lime green + + description = removeClassSubstring(description, parentName); } // no parent = blue vertex @@ -166,6 +200,94 @@ public class GraphClassesScript extends GhidraScript { return g; } + private String removeClassSubstring(String string, String substring) { + + int indexofSubstring = string.indexOf(substring); + if (indexofSubstring == -1) { + return null; + } + + if (indexofSubstring + substring.length() >= string.length()) { + return null; + } + + String newString = string.substring(indexofSubstring + substring.length()); + if (newString.isBlank() || newString.isEmpty()) { + return null; + } + + // should be a space : space and another class name next if gets to here + if (newString.length() < 4) { + return null; + } + + if (newString.indexOf(" : ") == 0) { + return newString.substring(3); + } + + return null; + + } + + private int getIndexOfFirstSingleColon(String string) { + + // replace all :: with something else so can isolate :'s + String testString = new String(string); + testString = testString.replace("::", "xx"); + + return testString.indexOf(":", 0); + + } + + /** + * Attempts to get the parent structure from within the child structure given the parent name + * @param childStructure the child structure + * @param parentName the name of the parent structure + * @return the parent structure or null if the parent structure is not contained in the child structure + * @throws CancelledException if cancelled + */ + private Structure getParentStructureFromChildStructure(Structure childStructure, + String parentName) + throws CancelledException { + + DataTypeComponent[] components = childStructure.getComponents(); + for (DataTypeComponent component : components) { + + monitor.checkCanceled(); + DataType componentDataType = component.getDataType(); + if (componentDataType instanceof Structure && + componentDataType.getName().equals(parentName)) { + return (Structure) componentDataType; + } + } + return null; + } + + /** + * Attempts to get the parent structure from the list of class structures + * @param parentName the name of the parent + * @return the parent structure if there is only one with the given name, else returns null + * @throws CancelledException if cancelled + */ + private Structure getParentStructureFromClassStructures(String parentName) + throws CancelledException { + + List parentStructures = new ArrayList(); + for (Structure classStructure : classStructures) { + monitor.checkCanceled(); + + if (classStructure.getName().equals(parentName)) { + parentStructures.add(classStructure); + } + + } + if (parentStructures.size() == 1) { + return parentStructures.get(0); + } + return null; + + } + private void showGraph(AttributedGraph graph) throws Exception { GraphDisplay display; @@ -176,26 +298,20 @@ public class GraphClassesScript extends GhidraScript { display.setGraph(graph, "test graph", false, TaskMonitor.DUMMY); } + private String getClassName(String description) { - // parse description for class hierarchy - if (!description.startsWith("class")) { - return null; - } - - // skip "class " to get overall class - description = description.substring(6); - int indexOfColon = description.indexOf(":", 0); - String mainClassName; + int indexOfColon = getIndexOfFirstSingleColon(description); + String firstClassName; if (indexOfColon == -1) { - mainClassName = description; + firstClassName = description; } else { - mainClassName = description.substring(0, indexOfColon - 1); + firstClassName = description.substring(0, indexOfColon - 1); } - mainClassName = mainClassName.replace(" ", ""); + firstClassName = firstClassName.replace(" ", ""); - return mainClassName; + return firstClassName; } } diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/RecoverClassesFromRTTIScript.java b/Ghidra/Features/Decompiler/ghidra_scripts/RecoverClassesFromRTTIScript.java index 7eb439c967..47bbe0c120 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/RecoverClassesFromRTTIScript.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/RecoverClassesFromRTTIScript.java @@ -114,7 +114,7 @@ public class RecoverClassesFromRTTIScript extends GhidraScript { // multiple parents = red vertex // edge between child and parent is orange if child inherits the parent virtually // edge between child and parent is lime green if child inherits the parent non-virtually - private static final boolean GRAPH_CLASS_HIERARCHIES = false; + private static final boolean GRAPH_CLASS_HIERARCHIES = true; // show shortened class template names in class structure field names private static final boolean USE_SHORT_TEMPLATE_NAMES_IN_STRUCTURE_FIELDS = true; @@ -348,7 +348,8 @@ public class RecoverClassesFromRTTIScript extends GhidraScript { RecoveredClass recoveredClass = recoveredClassIterator.next(); - AttributedVertex classVertex = g.addVertex(recoveredClass.getName()); + AttributedVertex classVertex = + g.addVertex(recoveredClass.getClassPath().getPath(), recoveredClass.getName()); Map> classHierarchyMap = recoveredClass.getClassHierarchyMap(); @@ -356,6 +357,7 @@ public class RecoverClassesFromRTTIScript extends GhidraScript { // no parent = blue vertex if (classHierarchyMap.isEmpty()) { classVertex.setAttribute("Color", "Blue"); + classVertex.setDescription(recoveredClass.getClassPath().getPath()); continue; } @@ -370,6 +372,8 @@ public class RecoverClassesFromRTTIScript extends GhidraScript { classVertex.setAttribute("Color", "Red"); } + classVertex.setDescription(recoveredClass.getClassPath().getPath()); + Map parentToBaseTypeMap = recoveredClass.getParentToBaseTypeMap(); @@ -378,7 +382,10 @@ public class RecoverClassesFromRTTIScript extends GhidraScript { monitor.checkCanceled(); RecoveredClass parent = parentIterator.next(); - AttributedVertex parentVertex = g.addVertex(parent.getName()); + AttributedVertex parentVertex = + g.addVertex(parent.getClassPath().getPath(), parent.getName()); + + parentVertex.setDescription(parent.getClassPath().getPath()); AttributedEdge edge = g.addEdge(parentVertex, classVertex); diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassUtils.java b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassUtils.java index bef192897a..7331621216 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassUtils.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/classrecovery/RecoveredClassUtils.java @@ -3196,9 +3196,15 @@ public class RecoveredClassUtils { } /** - * Method to create a string buffer containing class parents in the corrector order + * Method to create a string buffer containing class parents in the correct order. The format + * of the parent string is of the format "class : : ... + * where parentN_spec = "virtual (only if inherited virtually) " + * Examples: + * The class Pet with no parents would be "class Pet" + * The class Cat with non-virtual parent Pet would be "class Cat : Pet" + * The class A with virtual parent B and non-virtual parent C would be "class A : virtual B : C" * @param recoveredClass the given class - * @return StringBuffer containing class parents + * @return StringBuffer containing class parent description * @throws CancelledException if cancelled */ public StringBuffer createParentStringBuffer(RecoveredClass recoveredClass) @@ -4442,6 +4448,7 @@ public class RecoveredClassUtils { recoveredClass.getName(), defaultPointerSize, dataTypeManager); } + // create a description indicating class parentage classStruct.setDescription(createParentStringBuffer(recoveredClass).toString()); classStruct = (Structure) dataTypeManager.addDataType(classStruct,