GP-3947: Created an importer for the decompiler debug format which is

produced by the decompiler
This commit is contained in:
ghidravision
2024-11-18 16:00:35 +00:00
committed by Ryan Kurtz
parent da078607cb
commit 38541a7ac6
10 changed files with 3246 additions and 4 deletions

View File

@@ -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|

View File

@@ -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 <bytechunk> 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 <bytechunk> 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;
}
}

View File

@@ -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 <coretypes> and <typegrp> 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<DataTypeKey, DataType> 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<DataTypeKey, DataType>();
programDataManager = this.prog.getListing().getDataTypeManager();
}
/**
* Parse Data Type tag, handling types:
* <type>
* <typeref>
* <def>
* <void>
* @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 <type> 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 (<def> 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<DataTypeKey> {
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());
}
}
}

View File

@@ -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<Long, Namespace> 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.
* <p>
* 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.
* <p>
* @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:
* - <binaryimage>
* - <coretypes>
* - <typegrp>
* - <save_state>
* - <db>
* - <commentdb>
* - <stringmanage>
*
* NOTE: the following subtree tags are not yet supported:
* - <context_points>
* - <optionslist>
*
* @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<Long, Namespace>();
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 <db> subtree. Elements we currently handle include:
* - <scope>
*
* @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 <scope> 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 <symbollist> tag is being used around the collection of
* <mapsymp> 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 <mapsym> 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 <binaryimage> tag and subtree which includes the <bytechunk> 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 <labelsym> 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 <symbol> tag under the <mapsym> 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 <commentdb> tag and add to listing via setupComments method.
*
* Note: <commentdb> has two <addr> 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 <comment> 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 <encodeCommentsType> 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 <type> tags in the <coretypes> 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 <stringmanage> 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 <string> 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 <contextpointset> 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());
}
}
}

View File

@@ -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 <symbollist> 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<String, Namespace> used for getting parent namespace
* @param log XmlMessageLog
*/
public void parseFunctionSignature(XmlPullParser parser, Map<Long, Namespace> 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 <parent> subtag under <scope> and generate the function's Namespace.
*
* @param parser XmlPullParser
* @param scopeMap Map<String, Namespace> used for looking up parent Namespace based on ID from <parent> tag
* @param scopeName String from <scope> tag
*
* @return Namespace generated namespace
*/
private Namespace getParentNamespace(XmlPullParser parser, Map<Long, Namespace> 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 <scope> subtags, including:
* - <parent>
* - <rangelist>
* - <symbollist>
*
* NOTE: A populated <rangelist> 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 <mapsym> tags within the symbollist
*
* NOTE: populated <rangelist> 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<ParameterImpl> paramList = new ArrayList<ParameterImpl>();
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;
}
}

View File

@@ -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<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
List<LoadSpec> 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<Loaded<Program>> loadProgram(ByteProvider provider, String programName,
Project project,
String programFolderPath, LoadSpec loadSpec, List<Option> options, MessageLog log,
Object consumer,
TaskMonitor monitor) throws IOException, CancelledException {
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec();
Language importerLanguage = getLanguageService().getLanguage(pair.languageID);
CompilerSpec importerCompilerSpec =
importerLanguage.getCompilerSpecByID(pair.compilerSpecID);
ParseGhidraDebugResult parsedResult = parse(provider);
if (parsedResult.lastInfo == null) {
return new ArrayList<Loaded<Program>>();
}
Address imageBase = null;
if (parsedResult.lastInfo.offset() != null) {
imageBase =
importerLanguage.getAddressFactory().getAddress(parsedResult.lastInfo.offset());
}
Program prog = createProgram(provider, programName, imageBase, getName(),
importerLanguage, importerCompilerSpec, consumer);
List<Loaded<Program>> loadedList =
List.of(new Loaded<>(prog, programName, project, programFolderPath, consumer));
int loadingId = prog.startTransaction("Loading debug XML file");
try {
doImport(parsedResult.debugXmlMgr, options, log, prog, monitor, false, programName);
}
catch (
IllegalArgumentException e) {
throw new LoadException("Failed to load");
}
finally {
prog.endTransaction(loadingId, true);
}
return loadedList;
}
/**
* Import the XML; first parse and load details into the generation of a new program based
* around the "central" function that the Decompile Debug XML file was generated from.
*
* @param debugXmlMgr Manages the parsing of the XML objects
* @param options if there are any settings for importing work - these will always be empty
* @param log for writing to
* @param prog main program
* @param monitor TaskMonitor
* @param isAddToProgram whether we are importing the program into an existing program - this
* will always be false.
* @param programName name of program
*
* @return boolean success
*/
private boolean doImport(DecompileDebugFormatManager debugXmlMgr, List<Option> options,
MessageLog log, Program prog, TaskMonitor monitor, boolean isAddToProgram,
String programName)
throws IOException {
// if we're adding to another program, an autoAnalysisManager will already exist
if (!AutoAnalysisManager.hasAutoAnalysisManager(prog)) {
// helps to manage the threads/task workers
int txId = prog.startTransaction("Decompiler Debug XML Import");
try {
return doImportWork(debugXmlMgr, options, log, prog, monitor, isAddToProgram,
programName);
}
finally {
prog.endTransaction(txId, true);
}
}
AutoAnalysisManager analysisMgr = AutoAnalysisManager.getAnalysisManager(prog);
try {
return analysisMgr.scheduleWorker(new AnalysisWorker() {
@Override
public String getWorkerName() {
return "Decompiler Debug XML Importer";
}
@Override
public boolean analysisWorkerCallback(Program program, Object workerContext,
TaskMonitor taskMonitor) throws Exception, CancelledException {
return doImportWork(debugXmlMgr, options, log, program, taskMonitor,
isAddToProgram, programName);
}
}, null, false, monitor);
}
catch (CancelledException e) {
return false;
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException) cause;
}
throw new RuntimeException(e);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* Handles calling the parsing function in the DecompileDebugFormat manager.
*
* @param debugXmlMgr DecompileDebugFormatManager object which handles the parsing of the XML
* @param options if there are any settings for importing work - these will always be empty
* @param log for writing to
* @param prog main program
* @param monitor TaskMonitor
* @param isAddToProgram whether we are importing the program into an existing program - this
* will always be false.
* @param programName name of program
*
* @return success
*/
private boolean doImportWork(DecompileDebugFormatManager debugXmlMgr, List<Option> options,
MessageLog log, Program prog, TaskMonitor monitor, boolean isAddToProgram,
String programName)
throws LoadException {
MessageLog mgrLog = null;
boolean success = false;
try {
mgrLog = debugXmlMgr.read(prog, monitor, programName);
log.copyFrom(mgrLog);
success = true;
}
catch (Exception e) {
String message = "";
if (mgrLog != null && !"".equals(mgrLog.toString())) {
message = mgrLog.toString();
}
if (log != null && !"".equals(log.toString())) {
message = log.toString();
}
Msg.warn(this, "XML import exception, log: " + message, e);
throw new LoadException(e.getMessage());
}
return success;
}
/**
* Class to organize the XML format handler and the program info as extracted from the
* uploaded decompiler debug XML file.
*/
private static class ParseGhidraDebugResult {
final DecompileDebugFormatManager debugXmlMgr;
final DecompileDebugProgramInfo lastInfo;
ParseGhidraDebugResult(DecompileDebugFormatManager debugXmlMgr,
DecompileDebugProgramInfo lastInfo) {
this.debugXmlMgr = debugXmlMgr;
this.lastInfo = lastInfo;
}
}
/**
* Parse the XML file for the needed tags in order to populate a full program to load
*
* @param provider ByteProvider from the XML
*
* @return result object populated with the program details from the XML file
*/
private ParseGhidraDebugResult parse(ByteProvider provider) {
try {
DecompileDebugFormatManager debugXmlMgr = new DecompileDebugFormatManager(provider);
DecompileDebugProgramInfo decompileProgInfo = debugXmlMgr.getProgramInfo();
return new ParseGhidraDebugResult(debugXmlMgr, decompileProgInfo);
}
catch (Throwable e) {
Msg.trace(this, "Unable to parse the Ghidra Decompiler XML for " + provider.getName(),
e);
return new ParseGhidraDebugResult(null, null);
}
}
@Override
protected void loadProgramInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
MessageLog messageLog, Program program, TaskMonitor monitor)
throws IOException, LoadException, CancelledException {
// since we will not ever be loading this debug program into an existing program, this
// should not be used.
}
/**
* Hold program details from the spec string for loading. Object is then passed forward in the
* .getProgramInfo() function implemented in @DecompileDebugFormatManager.java and populated by @DecompileDebugXmlLoader.java.
* @param offset String
* @param compilerString String
* @param specString String
*/
static record DecompileDebugProgramInfo(String offset, String compilerString,
String specString) {}
}

View File

@@ -0,0 +1,239 @@
/* ###
* 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 org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.AccessMode;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import org.xml.sax.SAXException;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.FileByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.DecompileDebugXmlLoader.DecompileDebugProgramInfo;
import ghidra.framework.model.DomainObject;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.database.function.FunctionManagerDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.data.Structure;
import ghidra.program.model.listing.Function;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
import resources.ResourceManager;
/**
*
*/
public class DecompileDebugXmlLoaderTest extends AbstractGhidraHeadedIntegrationTest {
File decompileDebugTestFile;
private DecompileDebugXmlLoader loader;
private ProgramDB program;
@Before
public void setUp() {
String DECOMPILE_DEBUG_TEST_FILE = "ghidra/app/util/opinion/decompile_debug_test.xml";
decompileDebugTestFile = ResourceManager.getResourceFile(DECOMPILE_DEBUG_TEST_FILE);
loader = new DecompileDebugXmlLoader();
}
/**
* Test method for {@link ghidra.app.util.opinion.DecompileDebugFormatManager#getProgramInfo()}.
*/
@Test
public void testDecompileDebugXmlLoad() {
Collection<LoadSpec> loadSpecs;
MessageLog log = new MessageLog();
try {
ByteProvider byteProvider =
new FileByteProvider(decompileDebugTestFile, null, AccessMode.READ);
loadSpecs = loader.findSupportedLoadSpecs(byteProvider);
assertTrue("Expected single loader opinion", loadSpecs.size() == 1);
LoadSpec loadSpec = loadSpecs.iterator().next();
List<Option> options = loader.getDefaultOptions(byteProvider, loadSpec, null, false);
LoadResults<? extends DomainObject> loadResults =
loader.load(byteProvider, byteProvider.getName(), null, null, loadSpec, options,
log, this, TaskMonitor.DUMMY);
loadResults.getNonPrimary();
program = (ProgramDB) loadResults.getPrimaryDomainObject(this);
assertTrue("expected single loaded program", loadResults.size() == 1);
}
catch (IOException | CancelledException | VersionException e) {
log.appendException(e);
}
}
@Test
public void testVerifyProgramInfo() {
DecompileDebugFormatManager mngr = new DecompileDebugFormatManager(decompileDebugTestFile);
DecompileDebugProgramInfo progInfo;
MessageLog log = new MessageLog();
try {
progInfo = mngr.getProgramInfo();
assertEquals("Spec string should be parsed correctly", "x86:LE:64:default",
progInfo.specString());
assertEquals("Compiler string should be extracted separately", "gcc",
progInfo.compilerString());
assertEquals("Memory offset should be parsed from the offset tag", "0x140022cb8",
progInfo.offset());
}
catch (SAXException | IOException e) {
log.appendException(e);
}
}
@Test
public void testVerifyLoadedProgramBytes() {
Collection<LoadSpec> loadSpecs;
MessageLog log = new MessageLog();
try {
ByteProvider byteProvider =
new FileByteProvider(decompileDebugTestFile, null, AccessMode.READ);
loadSpecs = loader.findSupportedLoadSpecs(byteProvider);
LoadSpec loadSpec = loadSpecs.iterator().next();
List<Option> options = loader.getDefaultOptions(byteProvider, loadSpec, null, false);
LoadResults<? extends DomainObject> loadResults =
loader.load(byteProvider, byteProvider.getName(), null, null, loadSpec, options,
new MessageLog(), this, TaskMonitor.DUMMY);
loadResults.getNonPrimary();
program = (ProgramDB) loadResults.getPrimaryDomainObject(this);
assertEquals("gcc", program.getCompilerSpec().getCompilerSpecID().toString());
Memory memory = program.getMemory();
MemoryBlock[] blocks = memory.getBlocks();
assertEquals(26, blocks.length);
// Verify memory blocks
verifyBlock(blocks[0], "decompile_debug_test.xml", true,
getAddr(new BigInteger("140000000", 16)), 128);
verifyBlock(blocks[1], "__scrt_acquire_startup_lock", true,
getAddr(new BigInteger("1400227f8", 16)), 1);
verifyBlock(blocks[2], "FUN_140022834", true,
getAddr(new BigInteger("140022834", 16)), 1);
verifyBlock(blocks[3], "FUN_1400228fc", true,
getAddr(new BigInteger("1400228fc", 16)), 1);
verifyBlock(blocks[4], "__scrt_release_startup_lock", true,
getAddr(new BigInteger("140022994", 16)), 1);
verifyBlock(blocks[5], "__scrt_uninitialize_crt", true,
getAddr(new BigInteger("1400229b8", 16)), 1);
verifyBlock(blocks[6], "decompile_debug_test.xml", true,
getAddr(new BigInteger("140022cb8", 16)), 296);
verifyBlock(blocks[7], "decompile_debug_test.xml", true,
getAddr(new BigInteger("140022df9", 16)), 39);
}
catch (IOException | CancelledException | VersionException e) {
log.appendException(e);
}
}
@Test
public void testVerifyLoadedProgramDataTypes() {
Collection<LoadSpec> loadSpecs;
MessageLog log = new MessageLog();
try {
ByteProvider byteProvider =
new FileByteProvider(decompileDebugTestFile, null, AccessMode.READ);
loadSpecs = loader.findSupportedLoadSpecs(byteProvider);
LoadSpec loadSpec = loadSpecs.iterator().next();
List<Option> options = loader.getDefaultOptions(byteProvider, loadSpec, null, false);
LoadResults<? extends DomainObject> loadResults =
loader.load(byteProvider, byteProvider.getName(), null, null, loadSpec, options,
new MessageLog(), this, TaskMonitor.DUMMY);
loadResults.getNonPrimary();
program = (ProgramDB) loadResults.getPrimaryDomainObject(this);
// Verify data type generation
ProgramDataTypeManager dtm = program.getDataTypeManager();
assertEquals("Category count didn't match. ", 2, dtm.getCategoryCount());
Iterator<Structure> structures = dtm.getAllStructures();
Structure struct = structures.next(); // there is only 1 struct in the example XML dump
assertEquals("Component count didn't match.", 20, struct.getNumComponents());
assertEquals("Struct name is incorrect", "IMAGE_DOS_HEADER", struct.getName());
DataTypeComponent array = struct.getComponentAt(0); // the first component is an array
assertEquals("Array component name doesn't match", "e_magic", array.getFieldName());
assertEquals("Array wasn't sized right", 2, array.getLength());
}
catch (IOException | CancelledException | VersionException e) {
log.appendException(e);
}
}
@Test
public void verifyLoadedProgramFunctions() {
Collection<LoadSpec> loadSpecs;
MessageLog log = new MessageLog();
try {
ByteProvider byteProvider =
new FileByteProvider(decompileDebugTestFile, null, AccessMode.READ);
loadSpecs = loader.findSupportedLoadSpecs(byteProvider);
LoadSpec loadSpec = loadSpecs.iterator().next();
List<Option> options = loader.getDefaultOptions(byteProvider, loadSpec, null, false);
LoadResults<? extends DomainObject> loadResults =
loader.load(byteProvider, byteProvider.getName(), null, null, loadSpec, options,
new MessageLog(), this, TaskMonitor.DUMMY);
loadResults.getNonPrimary();
program = (ProgramDB) loadResults.getPrimaryDomainObject(this);
// Verify function collection
FunctionManagerDB funcMngr = program.getFunctionManager();
assertEquals("Function count doesn't match", 19, funcMngr.getFunctionCount());
Iterator<Function> functions = funcMngr.getFunctions(true);
Function function = functions.next(); // this is the function the dump was made for; the others are references
assertEquals("Function name needs to match.", "__scrt_acquire_startup_lock",
function.getName());
}
catch (IOException | CancelledException | VersionException e) {
log.appendException(e);
}
}
private void verifyBlock(MemoryBlock block, String name, boolean initialized, Address min,
long length) {
assertEquals("Name should match", name, block.getName());
assertEquals("Initalization didn't match", initialized, block.isInitialized());
assertEquals("Start address didn't match", min, block.getStart());
assertEquals("Block length didn't match", length, block.getSize());
}
private Address getAddr(BigInteger offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset.longValue());
}
}

View File

@@ -4,9 +4,9 @@
* 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.
@@ -44,4 +44,6 @@ public interface XmlElement {
public int getLineNumber();
public void setAttribute(String key, String value);
public boolean isStart(String string);
}

View File

@@ -4,9 +4,9 @@
* 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.
@@ -189,4 +189,16 @@ public class XmlElementImpl implements XmlElement {
}
return result;
}
@Override
/**
* Checks that the element is a starting element and also that its name matches the passed
* string.
*
* @param string element name to check for a match.
* @return boolean
*/
public boolean isStart(String string) {
return this.isStart && this.name.equals(string);
}
}