diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index 66ddb236cb..74fa75a598 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -1086,4 +1086,5 @@ src/test.slow/resources/filterTestDirList.txt||GHIDRA||||END| src/test.slow/resources/ghidra/app/plugin/core/datamgr/TestDataType.txt||GHIDRA||||END| src/test.slow/resources/ghidra/app/script/GhidraScriptAsk.properties||GHIDRA||||END| src/test/resources/defaultTools/TestCodeBrowser.tool||GHIDRA||||END| +src/test/resources/ghidra/app/util/opinion/decompile_debug_test.xml||GHIDRA||||END| src/test/resources/ghidra/app/util/opinion/test.ord||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugByteManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugByteManager.java new file mode 100644 index 0000000000..ea0279f57c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugByteManager.java @@ -0,0 +1,114 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + */ +package ghidra.app.util.opinion; + +import static ghidra.program.model.pcode.AttributeId.*; + +import java.util.HexFormat; + +import ghidra.framework.store.LockException; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressOverflowException; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; +import ghidra.xml.*; + +/** + * Manager to hold byte information from the tags inside the Decompiler Debug's XML. + */ +public class DecompileDebugByteManager { + + TaskMonitor monitor; + Program prog; + String programName; + + /** + * @param monitor TaskMonitor + * @param prog main program + * @param programName name of program + */ + public DecompileDebugByteManager(TaskMonitor monitor, + Program prog, String programName) { + this.monitor = monitor; + this.prog = prog; + this.programName = programName; + } + + /** + * Parse the tag - has the memory offset and the raw bytes + * + * @param parser XmlPullParser + * @param log Xml + */ + public void parse(XmlPullParser parser, XmlMessageLog log) { + + while (parser.peek().getName().equals("bytechunk") && !monitor.isCancelled()) { + processByteChunk(parser, log); + } + } + + /** + * Handle parsing and creating bytechunks & pulling out the byte string as a byte array. + * + * @param parser XmlPullParser + * @param log XmlMessageLog + */ + private void processByteChunk(XmlPullParser parser, XmlMessageLog log) { + + XmlElement byteChunkElement = parser.start("bytechunk"); + Address address = + prog.getAddressFactory() + .getAddress(byteChunkElement.getAttribute(ATTRIB_OFFSET.name())); + + // end element contains the byte content + byteChunkElement = parser.end(byteChunkElement); + String hexString = byteChunkElement.getText().trim().replaceAll("\n", ""); + byte[] rawBytes = HexFormat.of().parseHex(hexString); + + if (!generateMemoryChunk(rawBytes, address, log)) { + log.appendMsg("Error attempting to load memory chunk"); + } + } + + /** + * Create memory blocks with the raw bytes from the central function and any other blocks + * such as for pointers or other data types that the Decompile.xml file was generated from. + * + * @param rawBytes raw program bytes + * @param address memory offset + * @param log XmlMessageLog + */ + private boolean generateMemoryChunk(byte[] rawBytes, Address address, XmlMessageLog log) { + Memory memory = prog.getMemory(); + try { + memory.createInitializedBlock(programName, address, rawBytes.length, (byte) 0, + monitor, false); + memory.setBytes(address, rawBytes); + } + catch (LockException | IllegalArgumentException | MemoryConflictException + | AddressOverflowException | CancelledException | MemoryAccessException e) { + log.appendException(e); + return false; + } + return true; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugDataTypeManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugDataTypeManager.java new file mode 100644 index 0000000000..bf7d506138 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugDataTypeManager.java @@ -0,0 +1,470 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + */ +package ghidra.app.util.opinion; + +import static ghidra.program.model.pcode.AttributeId.*; + +import java.math.BigInteger; +import java.util.Map; +import java.util.TreeMap; + +import ghidra.program.model.data.*; +import ghidra.program.model.data.Enum; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.AttributeId; +import ghidra.util.task.TaskMonitor; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; + +/** + * Manager for parsing and storing data type objects from the XML - identified by + * the and tags. + * + * NOTE: In the typegrp subtree, ID is often on a different line from the element's name and + * metatype, so we need a way to reference this for use in the map -- String idHolder var + * helps with this. + */ +public class DecompileDebugDataTypeManager { + + TaskMonitor monitor; + Program prog; + Map dataTypeMap; + DataTypeManager programDataManager; + BuiltInDataTypeManager builtInMngr = BuiltInDataTypeManager.getDataTypeManager(); + private String idHolder; + + /** + * @param monitor TaskMonitor + * @param prog main program info + */ + public DecompileDebugDataTypeManager(TaskMonitor monitor, Program prog) { + this.monitor = monitor; + this.prog = prog; + this.dataTypeMap = new TreeMap(); + programDataManager = this.prog.getListing().getDataTypeManager(); + } + + /** + * Parse Data Type tag, handling types: + * + * + * + * + * @param parser XmlPullParser + * @param log XmlMessageLog + * + * @return retrieved DataType + */ + public DataType parseDataTypeTag(XmlPullParser parser, XmlMessageLog log) { + + String tagName = parser.peek().getName(); + DataType retrieved = null; + switch (tagName) { + case "type": + retrieved = parseType(parser, log); + break; + case "typeref": + retrieved = parseRefType(parser, log); + break; + case "def": + retrieved = parseDef(parser, log); + break; + case "void": + XmlElement voidElement = parser.start("void"); + parser.end(voidElement); + return new VoidDataType(); + default: + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + tagName); + parser.discardSubTree(); + } + return retrieved; + } + + /** + * Parse the tag + * + * @param parser XmlPullParser + * @param log XmlMessageLog + */ + private DataType parseType(XmlPullParser parser, XmlMessageLog log) { + + DataType retrieved = null; + String metatype = parser.peek().getAttribute("metatype"); + if (metatype == null) { // in the typegrp subtree, metatype and name/id are not on the same line + retrieved = retreiveBaseType(parser, log); + return retrieved; + } + switch (metatype) { + case "ptr": + retrieved = parsePointer(parser, log); + break; + case "ptrrel": + retrieved = parsePointerRelative(parser, log); + break; + case "array": + retrieved = parseArray(parser, log); + break; + case "struct": + retrieved = parseStruct(parser, log); + break; + case "union": + retrieved = parseUnion(parser, log); + break; + case "enum_int": + case "enum_uint": + retrieved = parseEnum(parser, log); + break; + default: + retrieved = retreiveBaseType(parser, log); + } + return retrieved; + } + + /** + * TypeDefs ( tags) are new definitions of types - basically, a re-naming. + * + * @param parser XmlPullParser + * @param log XmlMessageLog + * + * @return retrieved DataType + */ + private DataType parseDef(XmlPullParser parser, XmlMessageLog log) { + XmlElement defElement = parser.start("def"); + DataTypeKey key = new DataTypeKey(defElement); + + if (!dataTypeMap.containsKey(key)) { + DataType typeDefedType = parseDataTypeTag(parser, log); + TypedefDataType generatedTypeDef = new TypedefDataType( + new CategoryPath(CategoryPath.ROOT + key.name()), key.name(), typeDefedType, + programDataManager); + DataType resolvedDT = resolveAndMapDataType(key, generatedTypeDef); + parser.end(defElement); + return resolvedDT; + } + return dataTypeMap.get(key); + } + + /** + * Parse and handle enum types - signed/unsigned + * @param parser XmlPullParser + * @param log XmlMessageLog + * + * @return resolved DataType + */ + private DataType parseEnum(XmlPullParser parser, XmlMessageLog log) { + XmlElement enumElement = parser.start("type"); + DataTypeKey key = new DataTypeKey(enumElement); + int length = SpecXmlUtils.decodeInt(enumElement.getAttribute(ATTRIB_SIZE.name())); + Enum enumDT = null; + + if (dataTypeMap.containsKey(key) == false) { + enumDT = new EnumDataType(new CategoryPath(CategoryPath.ROOT + key.name()), key.name(), + length, + programDataManager); + enumDT = + (Enum) resolveAndMapDataType(key, enumDT); + } + else { + enumDT = (Enum) dataTypeMap.get(key); + } + + while (parser.peek().getName().equals(ATTRIB_VAL.name())) { + XmlElement valElement = parser.start(ATTRIB_VAL.name()); + + enumDT.add(valElement.getAttribute(ATTRIB_NAME.name()), + SpecXmlUtils.decodeInt(valElement.getAttribute(ATTRIB_VALUE.name())), ""); + parser.end(valElement); + } + parser.end(enumElement); + return enumDT; + } + + /** + * Parse & create union types + * @param parser XmlPullParser + * @param log XmlMessageLog + * + * @return resolved DataType + */ + private DataType parseUnion(XmlPullParser parser, XmlMessageLog log) { + XmlElement unionElement = parser.start("type"); + DataTypeKey key = new DataTypeKey(unionElement); + int size = SpecXmlUtils.decodeInt(unionElement.getAttribute(ATTRIB_SIZE.name())); + Union unionDT = null; + + if (dataTypeMap.containsKey(key) == false) { + unionDT = + new UnionDataType(new CategoryPath(CategoryPath.ROOT + key.name()), key.name(), + programDataManager); + unionDT = (Union) resolveAndMapDataType(key, unionDT); + } + else { + unionDT = (Union) dataTypeMap.get(key); + } + + if (unionElement.hasAttribute(ATTRIB_INCOMPLETE.name()) || size == 0) { + parser.end(unionElement); + return unionDT; + } + + while (parser.peek().getName().equals("field")) { + XmlElement fieldElement = parser.start("field"); + DataType fieldDT = parseDataTypeTag(parser, log); + unionDT.add(fieldDT, fieldDT.getLength(), key.name(), ""); + parser.end(fieldElement); + } + parser.end(unionElement); + return unionDT; + } + + /** + * Parse and process Array Data Type + * @param parser XmlPullParser + * @param log XmlMessageLog + * + * @return DataType + */ + private DataType parseArray(XmlPullParser parser, XmlMessageLog log) { + XmlElement arrayElement = parser.start("type"); + int arraySize = + SpecXmlUtils.decodeInt(arrayElement.getAttribute(AttributeId.ATTRIB_ARRAYSIZE.name())); + + DataType baseType = parseDataTypeTag(parser, log); + ArrayDataType arrayDT = + new ArrayDataType(baseType, arraySize, baseType.getLength(), programDataManager); + DataType resolved = resolveAndMapDataType( + new DataTypeKey(baseType.getName() + "array", idHolder), arrayDT); + parser.end(arrayElement); + return resolved; + } + + /** + * Handle parsing and creating a pointer data type. Add to the programDataManager and the + * DataTypeMap for use later. + * @param parser XmlPullParser + * @param log XmlMessageLog + * @return generated pointer data type + */ + private DataType parsePointer(XmlPullParser parser, XmlMessageLog log) { + XmlElement pointerElement = parser.start("type"); + int size = SpecXmlUtils.decodeInt(pointerElement.getAttribute(ATTRIB_SIZE.name())); + + DataType baseType = parseDataTypeTag(parser, log); + PointerDataType pointerDT = new PointerDataType(baseType, size, programDataManager); + DataType resolved = + resolveAndMapDataType(new DataTypeKey(baseType.getName() + "ptr", idHolder), + pointerDT); + + parser.end(pointerElement); + return resolved; + } + + /** + * Parse and handle 'pointer with offset' data types: PointerTypeDef {@PointerTypedef.java line #91}. + * These are useful for labeling a variable that points into the interior of a structure, + * but where the compiler still knows it can access the whole structure. + * + * @param parser XmlPullParser + * @param log XmlMessageLog + * @return DataType - PointerTypeDef + */ + private DataType parsePointerRelative(XmlPullParser parser, XmlMessageLog log) { + XmlElement pointerRelElement = parser.start("type"); + int size = SpecXmlUtils.decodeInt(pointerRelElement.getAttribute(ATTRIB_SIZE.name())); + Long offset = SpecXmlUtils.decodeLong(pointerRelElement.getAttribute(ATTRIB_OFF.name())); + + DataType baseType = parseDataTypeTag(parser, log); + PointerTypedef relPointerDT = + new PointerTypedef(baseType.getName(), baseType, size, programDataManager, offset); + + DataType resolved = resolveAndMapDataType(new DataTypeKey(baseType.getName()+"relptr", idHolder), relPointerDT); + parser.end(pointerRelElement); + + return resolved; + } + + /** + * Handle parsing and generating a struct type; populating it with fields. + * + * @param parser XmlPullParser + * @param log XmlMessageLog + + * @return Structure data type - either an empty one or completed one with fields + */ + private DataType parseStruct(XmlPullParser parser, XmlMessageLog log) { + XmlElement structElement = parser.start("type"); + DataTypeKey key = new DataTypeKey(structElement); + int size = SpecXmlUtils.decodeInt(structElement.getAttribute(ATTRIB_SIZE.name())); + Structure createdStruct = null; + + if (dataTypeMap.containsKey(key) == false) { + StructureDataType newStruct = + new StructureDataType(key.name(), size, programDataManager); + createdStruct = (Structure) resolveAndMapDataType(key, newStruct); + } + else { + createdStruct = + (Structure) dataTypeMap.get(key); + } + + if (structElement.hasAttribute(ATTRIB_INCOMPLETE.name()) || size == 0) { + parser.end(structElement); + return createdStruct; + } + + while (parser.peek().getName().equals("field")) { + XmlElement fieldElement = parser.start("field"); + int fieldOffset = + SpecXmlUtils.decodeInt(fieldElement.getAttribute(ATTRIB_OFFSET.name())); + + DataType fieldDT = parseDataTypeTag(parser, log); + createdStruct.replaceAtOffset(fieldOffset, fieldDT, fieldDT.getLength(), + fieldElement.getAttribute(ATTRIB_NAME.name()), ""); + parser.end(fieldElement); + } + parser.end(structElement); + + return createdStruct; + + } + + /** + * The type should already exist in the map and in the Program's data type manager. + * If it's not there, this is an error. + * + * @param parser XmlPullParser + * @param log XmlMessageLog + * + * @return retrieved DataType or null if it's not actually in the map (this should never happen) + */ + private DataType parseRefType(XmlPullParser parser, XmlMessageLog log) { + XmlElement typeRefElement = parser.start("typeref"); + DataTypeKey key = new DataTypeKey(typeRefElement); + DataType dt = dataTypeMap.get(key); + + if (dt == null) { + log.appendMsg("Data Type referenced without first being created."); + } + parser.end(typeRefElement); + return dt; + } + + /** + * Pull the core type from the data type map + * @param parser XmlPullParser + * @param log XmlMessageLog + * + * @return Retrieved DataType + */ + private DataType retreiveBaseType(XmlPullParser parser, XmlMessageLog log) { + XmlElement typeElement = parser.start("type"); + DataType retrieved = null; + DataTypeKey key = new DataTypeKey(typeElement); + // We need to be able to reference the current ID when building composite DTs + idHolder = key.id(); + + if (dataTypeMap.containsKey(key)) { + retrieved = dataTypeMap.get(key); + } + else { + retrieved = builtInMngr.getDataType(CategoryPath.ROOT, key.name().toLowerCase()); // there are some cases where the DT name is defined in lowercase and then later referred inconsistently + if (retrieved != null) { + retrieved = resolveAndMapDataType(key, retrieved); + } + else { + log.appendMsg("Type tag " + key.name() + " didn't resolve"); + } + } + parser.end(typeElement); + return retrieved; + } + + /** + * Resolve the provided DataType against the Program Manager then add it to the DataTypeMap + * for referencing later. + * @param key DataTypeKey consisting of a name and ID for referencing + * @param generatedTypeDef DataType identified Data Type for resolving and mapping + * + * @return resolved DataType + */ + private DataType resolveAndMapDataType(DataTypeKey key, DataType generatedTypeDef) { + + DataType resolvedDT = programDataManager.resolve(generatedTypeDef, dtConflictHandler); + dataTypeMap.put(key, resolvedDT); + return resolvedDT; + } + + public static DataTypeConflictHandler dtConflictHandler = new DataTypeConflictHandler() { + + @Override + public ConflictResult resolveConflict(DataType addedDataType, DataType existingDataType) { + return ConflictResult.RENAME_AND_ADD; + } + + @Override + public boolean shouldUpdate(DataType sourceDataType, DataType localDataType) { + return true; + } + + @Override + public DataTypeConflictHandler getSubsequentHandler() { + return DEFAULT_HANDLER; + } + + }; + + /** + * Data Types are organized by their name and ID; utilize a combo key structure for this organization + * Name, ID -> DataType + * @param name String data type name + * @param id String data type id + * @param val BigInteger to account for ID's that aren't written in Hex. + */ + static record DataTypeKey(String name, String id, BigInteger val) + implements Comparable { + + public DataTypeKey(XmlElement typeRefElement) { + this(typeRefElement.getAttribute(ATTRIB_NAME.name()), + typeRefElement.getAttribute(ATTRIB_ID.name())); + } + + public DataTypeKey(String name, String id) { + this(name, id, + (id.startsWith("0x") ? new BigInteger(id.substring(2), 16) + : new BigInteger(id, 10))); + } + + public DataTypeKey(String name, String id, BigInteger val) { + this.name = name; + this.id = id; + this.val = val; + } + + @Override + public int compareTo(DataTypeKey other) { + int nameCompare = this.name.compareTo(other.name); + if (nameCompare != 0) { + return nameCompare; + } + return (int) (this.val.longValue() - other.val.longValue()); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugFormatManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugFormatManager.java new file mode 100644 index 0000000000..6aab6903a4 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugFormatManager.java @@ -0,0 +1,722 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * + */ +package ghidra.app.util.opinion; + +import static ghidra.program.model.pcode.AttributeId.*; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.util.*; + +import org.xml.sax.*; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.opinion.DecompileDebugXmlLoader.DecompileDebugProgramInfo; +import ghidra.framework.store.LockException; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressOverflowException; +import ghidra.program.model.data.DataType; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.*; +import ghidra.program.model.pcode.AddressXML; +import ghidra.program.model.symbol.*; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.util.exception.*; +import ghidra.util.task.TaskMonitor; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; + +/** + * Main manager for handling the coordination of the parsing of the XML and loading of the program + * details into Ghidra. + */ +public class DecompileDebugFormatManager { + + private File file; + private DecompileDebugProgramInfo progInfo; + private Map scopeMap; + + /** + * Constructs a new program Decompiler Debug XML manager using the provided file. + * The file should be an XML file generated by the Ghidra Decompiler as a debug file. + * + * @param file generated XML Decompile Debug file + */ + public DecompileDebugFormatManager(File file) { + this.file = file; + } + + /* + * Constructs a new program Decompiler Debug XML manager using the provided ByteProvider. + *

+ * If {@link ByteProvider} has a {@link FSRL} and it is a simple local filepath, + * convert that to a normal local java.io.File instance instead of using the + * {@link ByteProvider}'s File property which is probably located in the + * {@link FileSystemService} filecache directory, which will break the ability + * to find the *.bytes file associated with this .xml file. + *

+ * @param provider + */ + public DecompileDebugFormatManager(ByteProvider provider) { + this.file = (provider.getFSRL() != null && provider.getFSRL().getNestingDepth() == 1) + ? new File(provider.getFSRL().getPath()) + : provider.getFile(); + } + + /* + * Initial parsing of the XML file to obtain the binary image info with load specs. + * + * @return DecompileDebugProgramInfo binary image / load spec details + */ + public DecompileDebugProgramInfo getProgramInfo() throws SAXException, IOException { + XmlPullParser parser = + XmlPullParserFactory.create(file, new MyErrorHandler(new MessageLog()), false); + + String loadSpecString = ""; + String offset = ""; + + while (parser.hasNext()) { + XmlElement element = parser.next(); + if (element.isStart("binaryimage")) { + loadSpecString = element.getAttribute("arch"); + } + else if (element.isStart("bytechunk")) { + offset = element.getAttribute(ATTRIB_OFFSET.name()); + break; + } + + } + String compilerString = + loadSpecString.substring(loadSpecString.lastIndexOf(':') + 1, loadSpecString.length()); + loadSpecString = loadSpecString.substring(0, loadSpecString.lastIndexOf(':')); + + progInfo = new DecompileDebugProgramInfo(offset, compilerString, loadSpecString); + parser.dispose(); + return progInfo; + } + + /** + * Perform the parsing from the underlying decompile debug XML file and populates the program fields. + * See @DecompileDebug.java for reference on the generation of the XML file. + * Tags currently supported/expected: + * - + * - + * - + * - + * - + * - + * - + * + * NOTE: the following subtree tags are not yet supported: + * - + * - + * + * @param prog created program + * @param monitor task monitor + * @param programName name of program + *@return MessageLog + * @throws LoadException If there is a parsing issue with the XML file. + */ + public MessageLog read(Program prog, TaskMonitor monitor, String programName) + throws LoadException { + XmlMessageLog log = new XmlMessageLog(); + MyErrorHandler errorHandler = new MyErrorHandler(log); + scopeMap = new TreeMap(); + scopeMap.put((long) 0, prog.getGlobalNamespace()); + int transactionId = prog.startTransaction("Loading"); + XmlPullParser parser = null; + try { + parser = XmlPullParserFactory.create(file, errorHandler, false); + log.setParser(parser); + monitor.setMessage("Beginning Load"); + log.appendMsg("Beginning Load"); + XmlElement startSavefileElement = parser.start("xml_savefile"); + XmlElement saveStateElement = null; + DecompileDebugDataTypeManager dataTypeManager = + new DecompileDebugDataTypeManager(monitor, prog); + while (parser.peek().isStart() && !monitor.isCancelled()) { + XmlElement element = null; + String name = parser.peek().getName(); + switch (name) { + + case "binaryimage": + element = parser.start("binaryimage"); + handleBinaryImageElements(parser, monitor, prog, programName, log); + parser.end(element); + break; + + case "coretypes": + monitor.setMessage("Processing Core Data Types"); + element = parser.start("coretypes"); + parseDataTypes(parser, monitor, prog, dataTypeManager, log); + parser.end(element); + break; + + case "typegrp": + monitor.setMessage("Processing Composite Data Types"); + element = parser.start("typegrp"); + parseDataTypes(parser, monitor, prog, dataTypeManager, log); + parser.end(element); + break; + + case "save_state": + saveStateElement = parser.start("save_state"); // wrapper tag holds all the program details aside from the binaryimage/memory + break; + + case "db": + element = parser.start("db"); + handleDBElements(parser, monitor, prog, dataTypeManager, programName, log); + parser.end(element); + break; + + case "commentdb": + element = parser.start("commentdb"); + parseComments(parser, monitor, prog, log); + parser.end(element); + break; + + case "stringmanage": + element = parser.start("stringmanage"); + parseStrings(parser, monitor, prog, log); + parser.end(element); + break; + + case "context_points": + element = parser.start("context_points"); + parseContextPoints(parser, monitor, prog, log); + parser.end(element); + break; + + default: + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + name); + parser.discardSubTree(name); + break; + } + } + + parser.end(saveStateElement); + parser.end(startSavefileElement); + } + + catch (SAXException | IOException e) { + log.appendException(e); + throw new LoadException("File read error."); + } + + finally { + monitor.setMessage("Finished import"); + log.appendMsg("Finished import"); + prog.endTransaction(transactionId, true); + parser.dispose(); + } + + return log; + } + + /** + * Parse elements in the subtree. Elements we currently handle include: + * - + * + * @param parser XmlPullParser + * @param monitor TaskMonitor + * @param prog Program + */ + private void handleDBElements(XmlPullParser parser, TaskMonitor monitor, Program prog, + DecompileDebugDataTypeManager dataTypeManager, String programName, + XmlMessageLog log) { + + while (parser.peek().isStart() && !monitor.isCancelled()) { + if (parser.peek().getName().equals("scope")) { + XmlElement scopeElement = parser.start("scope"); + Long scopeId = + SpecXmlUtils.decodeLong(scopeElement.getAttribute(ATTRIB_ID.name())); + String namespaceName = scopeElement.getAttribute(ATTRIB_NAME.name()); + + long parentId = 0; + if (parser.peek().getName().equals("parent")) { + XmlElement parentElement = parser.start("parent"); + parentId = + SpecXmlUtils.decodeLong(parentElement.getAttribute(ATTRIB_ID.name())); + parser.end(parentElement); + } + + // scopeMap is initialized with the global namespace, the first tag will be the global one if it doesn't have a parent tag + Namespace namespace = scopeMap.get(scopeId); + + if (namespace == null) { + Namespace parentNamespace = scopeMap.get(parentId); + try { + namespace = prog.getSymbolTable() + .createNameSpace(parentNamespace, namespaceName, + SourceType.IMPORTED); + scopeMap.put(scopeId, namespace); + } + catch (DuplicateNameException | InvalidInputException e) { + log.appendException(e); + } + } + + handleScopeSubtree(namespace, parser, monitor, prog, dataTypeManager, programName, + log); + parser.end(scopeElement); + + } + else { + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + parser.peek().getName()); + parser.discardSubTree(); + } + } + + } + + /** + * Parse element subtrees within the scope tag. + * + * NOTE: The scope tag must be parsed prior to a call to this message with the value + * of the namespace object sent as the first parameter. + * + * NOTE: it is expected that the wrapper tag is being used around the collection of + * tags. + * + * @param namespace Namespace + * @param parser XmlPullParser + * @param monitor TaskMonitor + * @param prog Program + * @param log XmlMessageLog + */ + private void handleScopeSubtree(Namespace namespace, XmlPullParser parser, TaskMonitor monitor, + Program prog, DecompileDebugDataTypeManager dataTypeManager, String programName, + XmlMessageLog log) { + XmlElement symbollistElement = null; + DecompileDebugFunctionManager functionManager = + new DecompileDebugFunctionManager(prog, monitor, dataTypeManager); + + while (parser.peek().isStart() && !monitor.isCancelled()) { + String name = parser.peek().getName(); + switch (name) { + case "symbollist": // this is a wrapper tag for tags + symbollistElement = parser.start("symbollist"); + break; + case "mapsym": + XmlElement mapsymTypeElement = parser.start("mapsym"); + monitor.setMessage("Processing Symbols"); + String symbolType = parser.peek().getName(); + if (symbolType.equals("function")) { + functionManager.parseFunctionSignature(parser, scopeMap, log); + while (parser.peek().isStart()) { + log.appendMsg(parser.getLineNumber(), + "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + name); + parser.discardSubTree(); // any extra tags after the function tag before mapsym can be thrown away for now + } + } + else if (symbolType.equals("labelsym")) { + parseLabelSymbol(prog, parser, log); + } + else if (symbolType.equals("symbol")) { + try { + parseSymbol(prog, parser, dataTypeManager, namespace, programName, + monitor, log); + } + catch (LoadException e) { + log.appendException(e); + } + } + parser.end(mapsymTypeElement); + break; + default: + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + name); + parser.discardSubTree(); + } + } + parser.end(symbollistElement); + } + + /** + * Handle the tag and subtree which includes the tag(s). + * + * @param parser XmlPullparser + * @param monitor TaskMonitor + * @param prog Program + * @param programName String + * @param log MessageLog + */ + private void handleBinaryImageElements(XmlPullParser parser, TaskMonitor monitor, Program prog, + String programName, XmlMessageLog log) { + monitor.setMessage("Processing binary image"); + while (parser.peek().isStart() && !monitor.isCancelled()) { + if (parser.peek().getName().equals("bytechunk")) { + monitor.setMessage("Processing Byte Chunk(s)"); + DecompileDebugByteManager byteMngr = + new DecompileDebugByteManager(monitor, prog, programName); + byteMngr.parse(parser, log); + } + else { + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + parser.peek().getName()); + parser.discardSubTree(); + } + } + } + + /** + * Handle generation of labels from the tag + * @param prog program + * @param parser XmlPullParser + * @param log XmlMessageLog + */ + private void parseLabelSymbol(Program prog, XmlPullParser parser, XmlMessageLog log) { + XmlElement symbolElement = parser.start("labelsym"); + String symbolName = symbolElement.getAttribute(ATTRIB_NAME.name()); + parser.end(symbolElement); + XmlElement addrElement = parser.start("addr"); + Address symbolAddr; + try { + symbolAddr = + AddressXML.restoreXml(addrElement, prog.getCompilerSpec()).getFirstAddress(); + SymbolTable st = prog.getSymbolTable(); + Symbol createdSymbol = + st.createLabel(symbolAddr, symbolName, SourceType.IMPORTED); + createdSymbol.setPrimary(); + + parser.end(addrElement); + while (parser.peek().isStart()) { + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + parser.peek().getName()); + parser.discardSubTree(); + } + } + catch (XmlParseException | InvalidInputException e) { + log.appendException(e); + } + + } + + /** + * Parse tag under the tag -- meaning, they are outside of a function, + * most likely these are data references. + * + * NOTE: We are currently not pulling the bytes for referenced functions or data, as a result + * we need to generate an initialized memory block for data references to avoid errors + * in the Listing pane. + * + * @param prog program + * @param parser XmlPullParser + * @param dataTypeManager DecompileDebugDataTypeManager + * @param namespace Namespace + * @param log XmlMessageLog + * + * @throws LoadException Memory allocation will block program load. + */ + private void parseSymbol(Program prog, XmlPullParser parser, + DecompileDebugDataTypeManager dataTypeManager, Namespace namespace, String programName, + TaskMonitor monitor, XmlMessageLog log) throws LoadException { + XmlElement symbolElement = parser.start("symbol"); + String symbolName = symbolElement.getAttribute(ATTRIB_NAME.name()); + DataType dt = dataTypeManager.parseDataTypeTag(parser, log); + Boolean readOnly = symbolElement.hasAttribute(ATTRIB_READONLY.name()); // readOnly is only present if it's true + parser.end(symbolElement); + XmlElement addrElement = parser.start("addr"); + Address symbolAddr = null; // make available for display in the error dialog below + + try { + AddressXML xmlAddr = AddressXML.restoreXml(addrElement, prog.getCompilerSpec()); + symbolAddr = xmlAddr.getFirstAddress(); + Symbol createdSymbol = + SymbolUtilities.createPreferredLabelOrFunctionSymbol(prog, + symbolAddr, namespace, symbolName, SourceType.IMPORTED); + createdSymbol.setPrimary(); + + Memory memory = prog.getMemory(); + Address end = symbolAddr.addNoWrap(xmlAddr.getSize() - 1); + // check to see if the data element would overlap existing blocks + if (!memory.contains(symbolAddr, end)) { + MemoryBlock generatedBlock = memory.createInitializedBlock(programName, symbolAddr, + xmlAddr.getSize(), (byte) 0, + monitor, false); + generatedBlock.setWrite(!readOnly); // if readOnly is true, write should be false + prog.getListing().createData(symbolAddr, dt, (int) xmlAddr.getSize()); + } + + else { // just generate the symbol + prog.getListing().createData(symbolAddr, dt, (int) xmlAddr.getSize()); + } + } + + catch (MemoryConflictException mce) { + log.appendMsg("Attempted to allocate overlapping memory block for data: " + + dt.getDisplayName() + " at address: " + symbolAddr); + log.appendException(mce); + } + + catch (LockException | IllegalArgumentException + | AddressOverflowException | CancelledException | InvalidInputException + | XmlParseException | CodeUnitInsertionException e) { + log.appendException(e); + } + + parser.end(addrElement); + while (parser.peek().isStart()) { + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + parser.peek().getName()); + parser.discardSubTree(); // skip rangelist tag and any others not yet handled + } + } + + /** + * Handle parsing and loading of comments. + * @param parser XmlPullParser + * @param monitor TaskMonitor + * @param prog Ghidra Program + * @param log XmlMessageLog + */ + private void parseComments(XmlPullParser parser, TaskMonitor monitor, Program prog, + XmlMessageLog log) { + while (parser.peek().isStart("comment") && !monitor.isCancelled()) { + parseAndAddComment(parser, prog, log); + } + } + + /** + * Parse comments from the tag and add to listing via setupComments method. + * + * Note: has two tags. The first is the function address, and the second + * is the (CodeUnit) address where the comment should be placed. Discard the first address. + * + * @param parser XmlPullParser + * @param prog Program + * @param log XmlMessageLog + */ + private void parseAndAddComment(XmlPullParser parser, Program prog, XmlMessageLog log) { + XmlElement commentElement = parser.start("comment"); + String commentType = commentElement.getAttribute(ATTRIB_TYPE.name()); + CommentType decodedType = decodeCommentType(commentType); + parser.discardSubTree("addr"); // this is the address of the function + XmlElement addrElement = parser.start("addr"); // this is the CodeUnit address where the comment goes + + try { + Address commentAddr = + AddressXML.restoreXml(addrElement, prog.getCompilerSpec()).getFirstAddress(); + parser.end(addrElement); + + parser.start("text"); + String commentText = parser.end().getText(); + setupComments(decodedType, commentAddr, commentText, prog); + parser.end(commentElement); + } + + catch (XmlParseException e) { + log.appendException(e); + } + } + + /** + * Setup comments from the tag. Follows the comment type constants found in + * @CodeUnit.java. + * + * @param decodedType int + * @param commentAddr Address + * @param commentText String + * @param prog Program + */ + private void setupComments(CommentType decodedType, Address commentAddr, String commentText, + Program prog) { + + CodeUnit cu = prog.getListing().getCodeUnitAt(commentAddr); + cu.setComment(decodedType, commentText); + } + + /** + * See @DecompileCallback.java for the encoding of the comments by @DecompileDebug.java. + * The method encodes the comment types found in @CodeUnit.java + * in 4 alternative labels: + * CodeUnit.EOL_COMMENT = "user1" + * CodeUnit.PRE_COMMENT = "user2" + * CodeUnit.POST_COMMENT = "user3" + * CodeUnit.PLATE_COMMENT = "header" + * + * In order to generate comments using @CodeUnit.java, we will need to re-encode the user<1-3> + * and header type labels from the DecompileDebug XML back into the CodeUnit constant values of: + * 0-3 (respectively). + * + * @param typeName String label from DecompileDebug.java and DecompileCallback.java + * + * @return CodeUnit comment type (0-3) + */ + private CommentType decodeCommentType(String typeName) { + CommentType commentType; + switch (typeName) { + case "user1": + commentType = CommentType.EOL; + break; + case "user2": + commentType = CommentType.PRE; + break; + case "user3": + commentType = CommentType.POST; + break; + case "header": + commentType = CommentType.PLATE; + break; + default: + commentType = CommentType.valueOf(""); + break; + } + return commentType; + } + + /** + * Loop through the tags in the subtree + * + * @param parser XmlPullParser + * @param prog built program + * @param monitor TaskMonitor + * @param dataTypeManager Program's DataTypeManager for parsing and managing the DataTypeManager Map + * @param log XmlMessageLog + */ + private void parseDataTypes(XmlPullParser parser, TaskMonitor monitor, Program prog, + DecompileDebugDataTypeManager dataTypeManager, XmlMessageLog log) { + + while (parser.peek().isStart() && !monitor.isCancelled()) { + dataTypeManager.parseDataTypeTag(parser, log); + } + } + + /** + * Parse the subtree + * + * @param parser XmlPullParser + * @param monitor TaskMonitor + * @param prog Program + * @param log XmlMessageLog + */ + private void parseStrings(XmlPullParser parser, TaskMonitor monitor, Program prog, + XmlMessageLog log) { + while (parser.peek().isStart("string") && !monitor.isCancelled()) { + parseAndAddStrings(parser, monitor, prog, log); + } + } + + /** + * Parse the tag and insert into the program + * + * @param parser XmlPullParser + * @param monitor TaskMonitor + * @param prog Program + * @param log XmlMessageLog + */ + private void parseAndAddStrings(XmlPullParser parser, TaskMonitor monitor, Program prog, + XmlMessageLog log) { + XmlElement stringElement = parser.start("string"); + XmlElement addrElement = parser.start("addr"); + try { + Address stringAddr = + AddressXML.restoreXml(addrElement, prog.getCompilerSpec()).getFirstAddress(); + parser.end(addrElement); + + parser.start("bytes"); + String hexString = parser.end().getText().trim().replaceAll("\n", ""); + hexString = hexString.replaceAll(" ", ""); + byte[] rawBytes = HexFormat.of().parseHex(hexString); + Memory memory = prog.getMemory(); + memory.setBytes(stringAddr, rawBytes); + } + catch (XmlParseException | IllegalArgumentException | MemoryAccessException e) { + log.appendException(e); + } + parser.end(stringElement); + } + + /** + * Handle the parsing of the context pointset inside of the subtree + * + * @param parser XmlPullParser + * @param monitor TaskMonitor + * @param prog Program + * @param log XmlMessageLog + */ + private void parseContextPoints(XmlPullParser parser, TaskMonitor monitor, Program prog, + XmlMessageLog log) { + while (parser.peek().isStart("context_pointset")) { + XmlElement contextElement = parser.start("context_pointset"); + Address addr; + + try { + addr = + AddressXML.restoreXml(contextElement, prog.getCompilerSpec()).getFirstAddress(); + + ProgramContext pc = prog.getProgramContext(); + while (parser.peek().isStart("set") && !monitor.isCancelled()) { + XmlElement setElement = parser.start("set"); + String regName = setElement.getAttribute(ATTRIB_NAME.name()); + BigInteger regVal = new BigInteger(setElement.getAttribute(ATTRIB_VAL.name())); + Register reg = pc.getRegister(regName); + pc.setValue(reg, addr, addr, regVal); + parser.end(setElement); + } + } + catch (XmlParseException | ContextChangeException e) { + log.appendException(e); + } + parser.end(contextElement); + } + + while (parser.peek().isStart("tracked_pointset")) { + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + parser.peek().getName()); + parser.discardSubTree(); + } + + } + + /** + * Simple handling of error messages + */ + class MyErrorHandler implements ErrorHandler { + private MessageLog log; + + MyErrorHandler(MessageLog log) { + this.log = log; + } + + @Override + public void warning(SAXParseException exception) throws SAXException { + log.appendMsg(exception.getMessage()); + + } + + @Override + public void error(SAXParseException exception) throws SAXException { + log.appendMsg(exception.getMessage()); + + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + log.appendMsg(exception.getMessage()); + + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugFunctionManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugFunctionManager.java new file mode 100644 index 0000000000..301d211378 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugFunctionManager.java @@ -0,0 +1,347 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.opinion; + +import static ghidra.program.model.pcode.AttributeId.*; + +import java.util.ArrayList; +import java.util.Map; + +import ghidra.framework.store.LockException; +import ghidra.program.database.function.OverlappingFunctionException; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function.FunctionUpdateType; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryConflictException; +import ghidra.program.model.pcode.AddressXML; +import ghidra.program.model.symbol.Namespace; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.exception.*; +import ghidra.util.task.TaskMonitor; +import ghidra.util.xml.SpecXmlUtils; +import ghidra.xml.*; + +/** + * Manage the parsing and population of function objects. All functions are loaded the same way, + * even if it's only referenced by the "central function" (the function that was in the decompiler + * pane when the Decompile Debug (@DecompileDebug.java) feature was used). + */ +public class DecompileDebugFunctionManager { + + Program prog; + TaskMonitor monitor; + DecompileDebugDataTypeManager dataTypeManager; + + /** + * Each function requires a program, task monitor and the program's Data Type Manager in order + * to be generated. + * + * @param prog Program + * @param monitor TaskMonitor + * @param dataTypeManager Program's DataTypeManager + */ + public DecompileDebugFunctionManager(Program prog, TaskMonitor monitor, + DecompileDebugDataTypeManager dataTypeManager) { + this.prog = prog; + this.monitor = monitor; + this.dataTypeManager = dataTypeManager; + } + + /** + * Setup functions from within the tag. + * Functions referenced by the central function are loaded the same except we do not + * (currently) have the memory/program context for them. Thus, they will show up in the Listing + * with a red "X". + * NOTE: This is the expected functionality. + * + * @param parser XmlPullParser + * @param scopeMap Map used for getting parent namespace + * @param log XmlMessageLog + */ + public void parseFunctionSignature(XmlPullParser parser, Map scopeMap, + XmlMessageLog log) { + XmlElement functionElement = parser.start("function"); + String functionName = functionElement.getAttribute(ATTRIB_NAME.name()); + boolean noReturn = functionElement.hasAttribute(ATTRIB_NORETURN.name()); + + Function createdFunction = null; + XmlElement localdb = null; + Address functionAddr = null; + while (parser.peek().isStart()) { + String tagName = parser.peek().getName(); + switch (tagName) { + case "addr": + try { + XmlElement addressTag = parser.start("addr"); + functionAddr = + AddressXML.restoreXml(addressTag, prog.getCompilerSpec()) + .getFirstAddress(); + parser.end(addressTag); + break; + } + catch (XmlParseException e) { + log.appendException(e); + } + case "scope": + + XmlElement scopeElement = parser.start("scope"); + String scopeName = scopeElement.getAttribute(ATTRIB_NAME.name()); + + Namespace functionNamespace = + getParentNamespace(parser, scopeMap, scopeName); + + createdFunction = + setFunctionNamespaceAndStorage(functionName, functionAddr, + functionNamespace, + log); + createdFunction.setNoReturn(noReturn); + handleScopeSubtags(createdFunction, parser, log); + + parser.end(scopeElement); + parser.end(localdb); + + break; + case "prototype": + XmlElement prototypeElement = parser.start("prototype"); + try { + createdFunction + .setCallingConvention(prototypeElement.getAttribute("model")); + createdFunction.setReturnType(retrieveReturnType(parser, log), + SourceType.IMPORTED); + + if (parser.peek().isStart("inject")) { + parser.start("inject"); + String callFixup = parser.end().getText(); + createdFunction.setCallFixup(callFixup); + log.appendMsg("Found an inject tag on the function: " + + createdFunction.getName()); + } + } + catch (InvalidInputException e) { + log.appendException(e); + } + + while (!parser.peek().isEnd()) { + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + parser.peek().getName()); + parser.discardSubTree(); + } + parser.end(prototypeElement); + break; + case "localdb": + localdb = parser.start("localdb"); // this is a wrapper, it ends after the scope tag + break; + default: + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + parser.peek().getName()); + parser.discardSubTree(); + } + } + parser.end(functionElement); + } + + /** + * Parse the subtag under and generate the function's Namespace. + * + * @param parser XmlPullParser + * @param scopeMap Map used for looking up parent Namespace based on ID from tag + * @param scopeName String from tag + * + * @return Namespace generated namespace + */ + private Namespace getParentNamespace(XmlPullParser parser, Map scopeMap, + String scopeName) { + XmlElement parentTag = parser.start("parent"); + Long parentId = SpecXmlUtils.decodeLong(parentTag.getAttribute(ATTRIB_ID.name())); + Namespace parentNamespace = scopeMap.get(parentId); + parser.end(parentTag); + return parentNamespace; + } + + /** + * Step through the subtags, including: + * - + * - + * - + * + * NOTE: A populated is not currently supported. + * + * @param createdFunction Function + * @param parser XmlPullParser + * @param log XmlMessageLog + */ + private void handleScopeSubtags(Function createdFunction, + XmlPullParser parser, XmlMessageLog log) { + + while (parser.peek().isStart()) { + String tagName = parser.peek().getName(); + switch (tagName) { + case "rangelist": + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + tagName); + parser.discardSubTree(); // we currently do not support a populated rangelist + break; + case "symbollist": + XmlElement symbollistElement = parser.start("symbollist"); + findFunctionVariables(parser, createdFunction, log); + parser.end(symbollistElement); + break; + default: + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + tagName); + parser.discardSubTree(); + } + } + } + + /** + * Parse and retrieve the return type from the program's data type manager. + * + * @param parser XmlPullParser + * @param log XmlMessageLog + * + * @return DataType return type + */ + private DataType retrieveReturnType(XmlPullParser parser, XmlMessageLog log) { + XmlElement returnElement = parser.start("returnsym"); + XmlElement addrElement = parser.start("addr"); + //Address address = AddressXML.restoreXml(addrElement, prog.getCompilerSpec()).getFirstAddress(); + parser.end(addrElement); + + DataType returnType = dataTypeManager.parseDataTypeTag(parser, log); + parser.end(returnElement); + return returnType; + } + + /** + * Parse and load parameter and local variables for the given function by + * cycling through the tags within the symbollist + * + * NOTE: populated tags within a mapsym tag are not currently supported. + * + * NOTE: Keep a list of parameters and modify the function only once + * (ie. 1 call to updateFunction()) after all variables have been parsed. + * + * @param parser XmlPullParser + * @param createdFunction Function + * @param log XmlMessageLog + */ + private void findFunctionVariables(XmlPullParser parser, Function createdFunction, + XmlMessageLog log) { + + ArrayList paramList = new ArrayList(); + try { + while (parser.peek().isStart()) { + XmlElement mapsymElement = parser.start("mapsym"); + XmlElement symbolElement = parser.start("symbol"); + boolean isParam = SpecXmlUtils.decodeInt(symbolElement.getAttribute("cat")) == 0; + String varName = symbolElement.getAttribute(ATTRIB_NAME.name()); + DataType varType = dataTypeManager.parseDataTypeTag(parser, log); + parser.end(symbolElement); + XmlElement addrElement = parser.start("addr"); + + if (isParam) { + Address address = AddressXML.restoreXml(addrElement, prog.getCompilerSpec()) + .getFirstAddress(); + ParameterImpl param = new ParameterImpl(varName, varType, + address, prog); + paramList.add(param); + } + else { + int offset = (int) AddressXML.restoreXml(addrElement, prog.getCompilerSpec()) + .getOffset(); + LocalVariableImpl lvar = + new LocalVariableImpl(varName, varType, offset, prog, SourceType.IMPORTED); + createdFunction.addLocalVariable(lvar, SourceType.IMPORTED); + } + parser.end(addrElement); + while (parser.peek().isStart()) { + log.appendMsg(parser.getLineNumber(), "Level " + parser.getCurrentLevel() + + " tag not currently supported: " + parser.peek().getName()); + parser.discardSubTree(); + } + parser.end(mapsymElement); + createdFunction.updateFunction(null, null, paramList, + FunctionUpdateType.CUSTOM_STORAGE, + true, SourceType.IMPORTED); // calling convention & return variable are updated later + + } + } + catch (NumberFormatException | InvalidInputException + | DuplicateNameException | XmlParseException e) { + log.appendException(e); + } + } + + /** + * Set the Name and Namespace / custom variable storage on the function symbol after the + * initial function signature has been created. + * + * @param functionName name + * @param functionAddr address offset + * @param namespace Namespace parent namespace + * @param log XmlMessageLog + * + * @return generated Function + */ + private Function setFunctionNamespaceAndStorage(String functionName, Address functionAddr, + Namespace namespace, XmlMessageLog log) { + + Function createdFunction = createFunction(functionName, functionAddr, namespace, log); + try { + createdFunction.setCustomVariableStorage(true); + Memory memory = prog.getMemory(); + if (!memory.contains(functionAddr)) { // main function block has already been generated + memory.createInitializedBlock(functionName, functionAddr, 1, (byte) 0, monitor, + false); + } + } + catch (LockException | IllegalArgumentException + | MemoryConflictException | AddressOverflowException | CancelledException e) { + log.appendException(e); + } + return createdFunction; + } + + /** + * Create Function object for populating later using the program's function manager. + * The central function is the one processed first. + * + * @param functionName String + * @param functionAddr Address + * @param namespace Namespace parent namespace + * @param log XmlMessageLog + * @return generated function + */ + private Function createFunction(String functionName, Address functionAddr, + Namespace namespace, XmlMessageLog log) { + + FunctionManager funcM = prog.getFunctionManager(); + Function generatedFunction = null; + try { + generatedFunction = funcM.createFunction(functionName, namespace, functionAddr, + new AddressSet(functionAddr, functionAddr), SourceType.IMPORTED); + } + catch (InvalidInputException | OverlappingFunctionException e) { + log.appendException(e); + } + + return generatedFunction; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugXmlLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugXmlLoader.java new file mode 100644 index 0000000000..7138537687 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/DecompileDebugXmlLoader.java @@ -0,0 +1,324 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.opinion; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.*; + +import ghidra.app.plugin.core.analysis.AnalysisWorker; +import ghidra.app.plugin.core.analysis.AutoAnalysisManager; +import ghidra.app.util.Option; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.model.Project; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Loader for handling XML files generated by the Decompiler on the central function that was in the + * Decompiler pane when the Debug Function Decompilation feature in the Decompiler is used. + * + * Reference @DecompileDebug.java for the generation of the XML file. + */ +public class DecompileDebugXmlLoader extends AbstractProgramLoader { + + public static final String DECOMPILER_DEBUG_SRC_NAME = "Decompiler Debug XML"; + + @Override + public String getName() { + return DECOMPILER_DEBUG_SRC_NAME; + } + + @Override + public LoaderTier getTier() { + return LoaderTier.SPECIALIZED_TARGET_LOADER; + } + + @Override + public int getTierPriority() { + return 50; + } + + /* + * This is an override method that identifies the load specs for the program to populate + * the initial upload dialog. + * + * @param provider + * @return the list of possible load specs (though really only one should work) + */ + @Override + public Collection findSupportedLoadSpecs(ByteProvider provider) throws IOException { + + List loadSpecs = new ArrayList<>(); + + // the language service sets up the log, language map, and service + getLanguageService(); + + ParseGhidraDebugResult result = parse(provider); + DecompileDebugProgramInfo info = result.lastInfo; + + if (info == null) { + // indicates that a different loader should be used + return loadSpecs; + } + LanguageID languageID = new LanguageID(info.specString()); + + LanguageDescription languageDescription = + getLanguageService().getLanguageDescription(languageID); + + for (CompilerSpecDescription csd : languageDescription + .getCompatibleCompilerSpecDescriptions()) { + LanguageCompilerSpecPair pair = + new LanguageCompilerSpecPair(languageDescription.getLanguageID(), + csd.getCompilerSpecID()); + + if (info.compilerString().equals(csd.getCompilerSpecID().getIdAsString())) { + loadSpecs.add(new LoadSpec(this, 0, pair, true)); + } + } + + return loadSpecs; + } + + /** + * After initial parsing of the XML file, load the details into a new program for + * loading/viewing in Ghidra. + * + * @param provider ByteProvider + * @param programName Program name from the XML + * @param project active project + * @param programFolderPath folder path to XML + * @param loadSpec String parsed out of the XML for details regarding language/compiler spec + * @param options program options - will always be empty + * @param log MessageLog + * @param consumer Object + * @param monitor TaskMonitor + * + * @return List of loaded programs - should always be 1 + */ + @Override + protected List> loadProgram(ByteProvider provider, String programName, + Project project, + String programFolderPath, LoadSpec loadSpec, List