Commit 45ececd2 by Melvin Klimke Committed by Enkelmann

Parse Calling Convention Register from Ghidra (#87)

parent 945cbf90
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
......@@ -10,6 +12,7 @@ import org.apache.commons.lang3.EnumUtils;
import bil.*;
import term.*;
import internal.*;
import symbol.ExternSymbol;
import serializer.Serializer;
import ghidra.app.script.GhidraScript;
......@@ -20,6 +23,7 @@ import ghidra.program.model.block.CodeBlockIterator;
import ghidra.program.model.block.CodeBlockReferenceIterator;
import ghidra.program.model.block.SimpleBlockModel;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.PrototypeModel;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.FunctionIterator;
......@@ -28,6 +32,7 @@ import ghidra.program.model.listing.Instruction;
import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.listing.Parameter;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.Symbol;
......@@ -574,6 +579,14 @@ public class PcodeExtractor extends GhidraScript {
project.setProgram(program);
project.setStackPointerRegister(stackPointerVar);
project.setCpuArch(cpuArch);
try {
HashMap<String, RegisterConvention> conventions = new HashMap<String, RegisterConvention>();
ParseCspecContent.parseSpecs(ghidraProgram, conventions);
addParameterRegister(conventions);
project.setRegisterConvention(new ArrayList<RegisterConvention>(conventions.values()));
} catch (FileNotFoundException e) {
System.out.println(e);
}
return project;
}
......@@ -1066,4 +1079,20 @@ public class PcodeExtractor extends GhidraScript {
return "";
}
/**
* Adds parameter register to the RegisterCallingConvention object
*/
protected void addParameterRegister(HashMap<String, RegisterConvention> conventions) {
PrototypeModel[] models = ghidraProgram.getCompilerSpec().getCallingConventions();
for(PrototypeModel model : models) {
String cconv = model.getName();
if(conventions.get(cconv) != null) {
ArrayList<String> parameters = conventions.get(cconv).getParameter();
for(VariableStorage storage : model.getPotentialInputRegisterStorage(ghidraProgram)) {
parameters.add(storage.getRegister().getName());
}
}
}
}
}
package internal;
/**
* Most of the code below was inspired by Ghidra's source code at
* Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/BasicCompilerSpec.java,
* Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguageProvider.java
*
* It extracts unaffected, killed by call and return registers for the given CPU architecture and adds them to the json output.
*
* For the identification of the correct .cspec file, the processor name is extracted from Ghidra to find the processor's
* corresponding .ldefs file. In the .ldefs file the language id and and compilerSpec id is used to determine the correct .cspec file.
*
* If the correct .cpsec file was found, it iterates over the XML DOM to extract the above mentioned registers.
*
*/
import ghidra.xml.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import org.xml.sax.*;
import generic.jar.ResourceFile;
import ghidra.app.plugin.processors.sleigh.SleighException;
import ghidra.framework.Application;
import ghidra.program.model.lang.CompilerSpecID;
import ghidra.program.model.lang.CompilerSpecNotFoundException;
import ghidra.program.model.lang.LanguageID;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
public class ParseCspecContent {
private static Exception parseException;
private static LanguageID languageId;
private static CompilerSpecID compilerSpecId;
private static Program program;
/**
*
* @param ghidraProgram
* @param conventions
* @throws FileNotFoundException
*
* Set the important parameters and handle the file extraction and parsing
*/
public static void parseSpecs(Program ghidraProgram, HashMap<String, RegisterConvention> conventions) throws FileNotFoundException {
program = ghidraProgram;
languageId = program.getLanguageID();
compilerSpecId = program.getCompilerSpec().getCompilerSpecID();
ResourceFile ldefFile = getLdefFile();
if(ldefFile != null) {
XmlPullParser ldefParser = null;
try {
ldefParser = parseXmlFile(ldefFile);
}
catch (CompilerSpecNotFoundException e) {
System.out.println(e);
}
String cspecFileName = parseLdefFile(ldefParser);
ResourceFile cspecFile = getCspecFile(cspecFileName);
if(cspecFile != null) {
XmlPullParser cspecParser = null;
try {
cspecParser = parseXmlFile(cspecFile);
}
catch (CompilerSpecNotFoundException e) {
System.out.println(e);
}
parseCspecFile(cspecParser, conventions);
} else {
throw new FileNotFoundException("Could not find .cspec file.");
}
} else {
throw new FileNotFoundException("Could not find .ldef file.");
}
}
/**
*
* @param file: File in Ghidra directory
* @return: XML parser for input file
* @throws CompilerSpecNotFoundException
*
* Handles all of the XML error handling and creates the Parser from the XMLPullParserFactory
*/
public static XmlPullParser parseXmlFile(ResourceFile file) throws CompilerSpecNotFoundException {
ErrorHandler errHandler = new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
parseException = exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
parseException = exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
Msg.warn(this, "Warning parsing '" + file + "'", exception);
}
};
try {
XmlPullParser parser = XmlPullParserFactory.create(file, errHandler, false);
return parser;
}
catch (SleighException e) {
parseException = e;
Throwable cause = e.getCause();
if (cause != null) {
if (cause instanceof SAXException || cause instanceof IOException) {
parseException = (Exception) cause;
}
}
}
catch (FileNotFoundException e) {
parseException = e;
}
catch (IOException e) {
parseException = e;
}
catch (SAXException e) {
parseException = e;
}
if (parseException != null) {
throw new CompilerSpecNotFoundException(
languageId,
compilerSpecId,
file.getName(),
parseException
);
}
return null;
}
/**
*
* @return: resource file i.e. .ldef
*
* Searches the Ghidra's directories for .ldef extension and returns the corresponding
* file based on the processor name. In some cases the processor name has to be parsed
* for correct matching.
*/
public static ResourceFile getLdefFile() {
String processorDef = String.format("%s.ldefs", program.getLanguage().getLanguageDescription().getProcessor().toString());
if(processorDef.startsWith("MIPS")) {
processorDef = processorDef.toLowerCase();
}
if(processorDef.startsWith("PowerPC")) {
processorDef = "ppc.ldefs";
}
for(ResourceFile file : Application.findFilesByExtensionInApplication(".ldefs")) {
if(file.getName().equals(processorDef)) {
return file;
}
}
return null;
}
/**
*
* @param parser: parser for .ldef file
* @return: filename of .cspec file
*
* Parses the .cspec filename from the .ldef file by
* matching the language id. e.g. id = ARM:LE:32:v8
* to analyse the correct language.
*/
public static String parseLdefFile(XmlPullParser parser) {
String cspecName = null;
parser.start("language_definitions");
while(parser.peek().isStart()) {
XmlElement languageEnter = parser.peek();
if(languageEnter.getAttribute("id").equals(languageId.getIdAsString())) {
cspecName = getCompilerName(parser);
break;
} else {
discardSubTree(parser);
}
}
parser.end();
return cspecName;
}
/**
*
* @param parser
* @return
*
* Parses the compiler fields of the language and extracts the correct
* compiler using the compilerSpec Id e.g. id = default
*
* <compiler name="default" spec="ARM.cspec" id="default"/>
*/
public static String getCompilerName(XmlPullParser parser) {
String cspec = null;
parser.start("language");
while(parser.peek().isStart()) {
XmlElement langProperty = parser.peek();
if(langProperty.getName().equals("compiler")) {
if(langProperty.getAttribute("name").equals(compilerSpecId.getIdAsString())) {
parser.start();
cspec = langProperty.getAttribute("spec");
parser.end();
} else {
discardSubTree(parser);
}
} else {
discardSubTree(parser);
}
}
parser.end();
return cspec;
}
/**
*
* @param parser: parser for .cspec file
* @param conventions
*
* Searches the .cspec file for default_proto or prototype wrapper
*/
public static void parseCspecFile(XmlPullParser parser, HashMap<String, RegisterConvention> conventions) {
parser.start("compiler_spec");
while(parser.peek().isStart()) {
String field = parser.peek().getName();
if(field.equals("default_proto") || field.equals("prototype")) {
parsePrototype(parser, field, conventions);
} else {
discardSubTree(parser);
}
}
parser.end();
}
/**
*
* @param parser: parser for .cspec file
* @param name: name of the wrapper
* @param conventions
*
* Gets registers based on wrapper name. The default_proto wrapper
* is an additional wrapper around the default prototype. Therefore,
* the function has to go one level deeper.
*/
public static void parsePrototype(XmlPullParser parser, String name, HashMap<String, RegisterConvention> conventions) {
RegisterConvention convention = new RegisterConvention();
if(name.equals("default_proto")) {
parser.start();
getUnaffectedKilledByCallAndOutput(parser, convention);
parser.end();
} else if(name.equals("prototype")) {
getUnaffectedKilledByCallAndOutput(parser, convention);
}
// Using the hashmap this way will simplify the addition of parameter registers which are not parsed here
// as they are calling convention specific
conventions.put(convention.getCconv(), convention);
}
/**
*
* @param parser: parser for .cspec file
* @param convention: convention object for later serialization
*
* Sets the convention's unaffected, killed by call and return registers as well as the calling convention
*/
public static void getUnaffectedKilledByCallAndOutput(XmlPullParser parser, RegisterConvention convention) {
XmlElement protoElement = parser.start();
String cconv = protoElement.getAttribute("name");
convention.setCconv(cconv);
while(parser.peek().isStart()) {
XmlElement registers = parser.peek();
if(registers.getName().equals("unaffected")) {
convention.setUnaffected(getRegisters(parser));
} else if (registers.getName().equals("killedbycall")) {
convention.setKilledByCall(getRegisters(parser));
} else if (registers.getName().equals("output")) {
convention.setReturn(parseOutput(parser));
} else {
discardSubTree(parser);
}
}
parser.end(protoElement);
}
/**
*
* @param parser: parser for .cspec file
* @return: list of return registers
*
* Parses the output and pentry wrapper to access the return register fields
*/
public static ArrayList<String> parseOutput(XmlPullParser parser) {
ArrayList<String> registers = new ArrayList<String>();
parser.start("output");
while(parser.peek().isStart()) {
parser.start("pentry");
XmlElement entry = parser.peek();
if(entry.getName().equals("register")) {
parser.start("register");
registers.add(entry.getAttribute("name"));
parser.end();
} else {
discardSubTree(parser);
}
parser.end();
}
parser.end();
return registers;
}
/**
*
* @param parser: parser for .cspec file
* @return: either killed by call or unaffected registers
*
* Parses killed by call or unaffected registers and ingnores varnode types
*/
public static ArrayList<String> getRegisters(XmlPullParser parser) {
ArrayList<String> registers = new ArrayList<String>();
parser.start();
while(parser.peek().isStart()) {
XmlElement type = parser.peek();
if(type.getName().equals("register")) {
parser.start();
registers.add(type.getAttribute("name"));
parser.end();
} else if(type.getName().equals("varnode")) {
discardSubTree(parser);
}
}
parser.end();
return registers;
}
/**
*
* @param filename: .cspec filename
* @return: .cspec file
*
* Return the .cspec file for a given filename
*/
public static ResourceFile getCspecFile(String filename) {
for(ResourceFile file : Application.findFilesByExtensionInApplication(".cspec")) {
if(file.getName().equals(filename)) {
return file;
}
}
return null;
}
/**
*
* @param parser: xml parser
*
* discards XML subtrees if they do not contain necessary information.
* Simply used as a shortcut.
*/
public static void discardSubTree(XmlPullParser parser) {
XmlElement el = parser.start();
parser.discardSubTree(el);
}
}
package internal;
import java.util.ArrayList;
import ghidra.program.model.listing.Instruction;
......
package internal;
import java.util.ArrayList;
import com.google.gson.annotations.SerializedName;
public class RegisterConvention {
@SerializedName("calling_convention")
private String cconv;
@SerializedName("parameter_register")
private ArrayList<String> parameter;
@SerializedName("return_register")
private ArrayList<String> return_;
@SerializedName("unaffected_register")
private ArrayList<String> unaffected;
@SerializedName("killed_by_call_register")
private ArrayList<String> killedByCall;
public RegisterConvention() {
this.setParameter(new ArrayList<String>());
this.setReturn(new ArrayList<String>());
this.setUnaffected(new ArrayList<String>());
this.setKilledByCall(new ArrayList<String>());
}
public RegisterConvention(String cconv, ArrayList<String> parameter, ArrayList<String> return_, ArrayList<String> unaffected, ArrayList<String> killedByCall) {
this.setCconv(cconv);
this.setParameter(parameter);
this.setReturn(return_);
this.setUnaffected(unaffected);
this.setKilledByCall(killedByCall);
}
public String getCconv() {
return cconv;
}
public void setCconv(String cconv) {
this.cconv = cconv;
}
public ArrayList<String> getParameter() {
return parameter;
}
public void setParameter(ArrayList<String> parameter) {
this.parameter = parameter;
}
public ArrayList<String> getReturn() {
return return_;
}
public void setReturn(ArrayList<String> return_) {
this.return_ = return_;
}
public ArrayList<String> getUnaffected() {
return unaffected;
}
public void setUnaffected(ArrayList<String> unaffected) {
this.unaffected = unaffected;
}
public ArrayList<String> getKilledByCall() {
return killedByCall;
}
public void setKilledByCall(ArrayList<String> killedByCall) {
this.killedByCall = killedByCall;
}
}
package term;
import bil.Variable;
import internal.RegisterConvention;
import java.util.ArrayList;
import com.google.gson.annotations.SerializedName;
public class Project {
......@@ -10,14 +14,17 @@ public class Project {
private Variable stackPointerRegister;
@SerializedName("cpu_architecture")
private String cpuArch;
@SerializedName("register_calling_convention")
private ArrayList<RegisterConvention> conventions;
public Project() {
}
public Project(Term<Program> program, String cpuArch, Variable stackPointerRegister) {
public Project(Term<Program> program, String cpuArch, Variable stackPointerRegister, ArrayList<RegisterConvention> conventions) {
this.setProgram(program);
this.setCpuArch(cpuArch);
this.setStackPointerRegister(stackPointerRegister);
this.setRegisterConvention(conventions);
}
public Term<Program> getProgram() {
......@@ -43,4 +50,12 @@ public class Project {
public void setCpuArch(String cpuArch) {
this.cpuArch = cpuArch;
}
public ArrayList<RegisterConvention> getRegisterConvention() {
return conventions;
}
public void setRegisterConvention(ArrayList<RegisterConvention> conventions) {
this.conventions = conventions;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment