mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-22 19:35:42 +08:00
Merge branch 'GP-3947_ghidravision_DecompilerDebugXmlLoader_final'
This commit is contained in:
@@ -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|
|
||||
|
||||
+114
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+470
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+722
File diff suppressed because it is too large
Load Diff
+347
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+324
@@ -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) {}
|
||||
|
||||
}
|
||||
+239
@@ -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());
|
||||
}
|
||||
}
|
||||
+1011
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user