mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-25 17:00:36 -09:00
1202 lines
30 KiB
Java
Executable File
1202 lines
30 KiB
Java
Executable File
/* ###
|
|
* 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.
|
|
*/
|
|
//Verify Hexagon test vectors with external .s file
|
|
//@category Languages
|
|
|
|
import java.io.*;
|
|
import java.math.BigInteger;
|
|
import java.util.*;
|
|
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.program.model.address.*;
|
|
import ghidra.program.model.lang.Language;
|
|
import ghidra.program.model.lang.Register;
|
|
import ghidra.program.model.listing.*;
|
|
import ghidra.program.model.mem.Memory;
|
|
import ghidra.program.model.symbol.Symbol;
|
|
import ghidra.program.model.symbol.SymbolType;
|
|
import ghidra.util.Msg;
|
|
import ghidra.util.StringUtilities;
|
|
import ghidra.util.exception.AssertException;
|
|
|
|
public class VerifyHexagonTestVectors extends GhidraScript {
|
|
|
|
Memory memory;
|
|
Listing listing;
|
|
BookmarkManager bookmarkMgr;
|
|
|
|
Map<String, Mismatch> mismatchMap = new HashMap<String, Mismatch>();
|
|
|
|
List<Symbol> labels;
|
|
|
|
@Override
|
|
protected void run() throws Exception {
|
|
|
|
if (currentProgram == null ||
|
|
!"Hexagon".equals(currentProgram.getLanguage().getProcessor().toString())) {
|
|
popup("Current program is not a Hexagon binary!");
|
|
return;
|
|
}
|
|
|
|
memory = currentProgram.getMemory();
|
|
listing = currentProgram.getListing();
|
|
bookmarkMgr = currentProgram.getBookmarkManager();
|
|
|
|
buildRegisterRenameMap();
|
|
|
|
buildLabelList(); // original label use should be replaced by address
|
|
|
|
File tvFile = askFile("Choose Hexagon Test Vector .s File", "Open");
|
|
if (tvFile == null) {
|
|
return;
|
|
}
|
|
|
|
OrigInstructionEnumeration origInstrEnumeration = new OrigInstructionEnumeration(tvFile);
|
|
GhidraInstructionEnumeration ghidraInstrEnumeration = new GhidraInstructionEnumeration();
|
|
|
|
OrigInstruction nextOrigInstr = null;
|
|
GhidraInstruction nextGhidraInstr = null;
|
|
while (true) {
|
|
|
|
if (nextOrigInstr == null && origInstrEnumeration.hasMoreElements()) {
|
|
nextOrigInstr = origInstrEnumeration.nextElement();
|
|
}
|
|
|
|
if (nextGhidraInstr == null && ghidraInstrEnumeration.hasMoreElements()) {
|
|
nextGhidraInstr = ghidraInstrEnumeration.nextElement();
|
|
}
|
|
|
|
if (nextOrigInstr == null) {
|
|
if (nextGhidraInstr == null) {
|
|
break; // done
|
|
}
|
|
markError(nextGhidraInstr.addr, "Sync Error", "Ran out of test vectors early");
|
|
Msg.showError(this, null, "Test Vector Sync Error",
|
|
"Ran out of test vectors early at " + nextGhidraInstr.addr);
|
|
break;
|
|
}
|
|
|
|
if (nextGhidraInstr == null) {
|
|
Msg.showError(this, null, "Test Vector Sync Error",
|
|
"Ran out of instruction memory early " + tvFile.getName() + ":" +
|
|
nextOrigInstr.lineNo);
|
|
break;
|
|
}
|
|
|
|
if (!"nop".equals(nextOrigInstr.instr)) {
|
|
// skip injected nop
|
|
// NOTE: this may cause endloop's to be lost
|
|
while ("nop".equals(nextGhidraInstr.instr)) {
|
|
if (ghidraInstrEnumeration.hasMoreElements()) {
|
|
nextGhidraInstr = ghidraInstrEnumeration.nextElement();
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nextOrigInstr.hasAssignment()) {
|
|
if (nextGhidraInstr.mnemonic != null &&
|
|
nextOrigInstr.instr.replace(" ", "").indexOf(nextGhidraInstr.mnemonic) >= 0) {
|
|
nextGhidraInstr.assumeAssignment(nextOrigInstr.isMemStore());
|
|
}
|
|
}
|
|
|
|
String origInstr = nextOrigInstr.toString().replace(" ", "").toUpperCase();
|
|
String ghidraInstr = nextGhidraInstr.toString(true).replace(" ", "").toUpperCase();
|
|
if (!origInstr.equals(ghidraInstr)) {
|
|
String key = origInstr + ghidraInstr;
|
|
Mismatch mismatch = mismatchMap.get(key);
|
|
if (mismatch != null) {
|
|
++mismatch.count;
|
|
}
|
|
else {
|
|
mismatch = new Mismatch(nextOrigInstr, nextGhidraInstr);
|
|
mismatchMap.put(key, mismatch);
|
|
}
|
|
}
|
|
|
|
nextOrigInstr = null;
|
|
nextGhidraInstr = null;
|
|
|
|
}
|
|
|
|
ArrayList<Mismatch> list = new ArrayList<Mismatch>();
|
|
list.addAll(mismatchMap.values());
|
|
Collections.sort(list, new Comparator<Mismatch>() {
|
|
|
|
@Override
|
|
public int compare(Mismatch arg0, Mismatch arg1) {
|
|
return arg0.origInstr.lineNo - arg1.origInstr.lineNo;
|
|
}
|
|
});
|
|
|
|
for (Mismatch mismatch : list) {
|
|
|
|
String lineNo =
|
|
StringUtilities.pad(Integer.toString(mismatch.origInstr.lineNo), ' ', 5);
|
|
String instr = StringUtilities.pad(mismatch.origInstr.toString(), ' ', 50);
|
|
|
|
String tail = "";
|
|
if (mismatch.count > 1) {
|
|
tail = " (" + mismatch.count + " occurances)";
|
|
}
|
|
|
|
System.out.println(
|
|
lineNo + ": " + instr + " >?> " + mismatch.ghidraInstr.addr.toString() + ": " +
|
|
mismatch.ghidraInstr.toString(true) + tail);
|
|
|
|
}
|
|
}
|
|
|
|
private void buildLabelList() {
|
|
labels = new ArrayList<Symbol>();
|
|
for (Symbol s : currentProgram.getSymbolTable().getAllSymbols(false)) {
|
|
if (s.getSymbolType() == SymbolType.LABEL) {
|
|
labels.add(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
private class Mismatch {
|
|
|
|
int count = 1;
|
|
OrigInstruction origInstr;
|
|
GhidraInstruction ghidraInstr;
|
|
|
|
Mismatch(OrigInstruction origInstr, GhidraInstruction ghidraInstr) {
|
|
this.origInstr = origInstr;
|
|
this.ghidraInstr = ghidraInstr;
|
|
}
|
|
}
|
|
|
|
private static final long R_REG_BASE_ADDR = 0; // 0-31
|
|
private static final long C_REG_BASE_ADDR = 0x200; // 0-13,24-29
|
|
private static final long G_REG_BASE_ADDR = 0x400; // 0-3, 24-29
|
|
private static final long S_REG_BASE_ADDR = 0x800; // 0-63
|
|
|
|
private Map<String, String> regRenameMap = new HashMap<String, String>();
|
|
|
|
private void buildRegisterRenameMap() {
|
|
addRegisterRange(R_REG_BASE_ADDR, 0, 31, "R");
|
|
addRegisterRange(C_REG_BASE_ADDR, 0, 13, "C");
|
|
addRegisterRange(C_REG_BASE_ADDR, 24, 29, "C");
|
|
addRegisterRange(G_REG_BASE_ADDR, 0, 3, "G");
|
|
addRegisterRange(G_REG_BASE_ADDR, 24, 29, "G");
|
|
addRegisterRange(S_REG_BASE_ADDR, 0, 63, "S");
|
|
}
|
|
|
|
private void addRegisterRange(long baseOffset, int startIndex, int endIndex, String regPrefix) {
|
|
AddressFactory addrFactory = currentProgram.getAddressFactory();
|
|
AddressSpace regSpace = addrFactory.getRegisterSpace();
|
|
Language lang = currentProgram.getLanguage();
|
|
for (int i = startIndex; i < (endIndex + 1); i++) {
|
|
Address regAddr = regSpace.getAddress(baseOffset + ((i - startIndex) * 4));
|
|
Register reg = lang.getRegister(regAddr, 4);
|
|
if (reg == null) {
|
|
throw new AssertException("Register expected at " + regAddr.toString(true));
|
|
}
|
|
String defaultName = regPrefix + i;
|
|
//System.out.println("Reg: " + defaultName);
|
|
if (defaultName.equals(reg.getName())) {
|
|
continue;
|
|
}
|
|
regRenameMap.put(defaultName, reg.getName());
|
|
}
|
|
}
|
|
|
|
private void markError(Address addr, String type, String msg) {
|
|
if (bookmarkMgr.getBookmarks(addr).length == 0) {
|
|
bookmarkMgr.setBookmark(addr, BookmarkType.ERROR, type, msg);
|
|
}
|
|
}
|
|
|
|
private class OrigInstructionEnumeration implements Enumeration<OrigInstruction> {
|
|
|
|
private List<TestVector> testVectors;
|
|
private int nextIndex = 0;
|
|
private OrigInstruction nextElement;
|
|
private OrigInstruction nextNextElement;
|
|
|
|
OrigInstructionEnumeration(File tvFile) throws IOException {
|
|
testVectors = readInstructions(tvFile);
|
|
}
|
|
|
|
@Override
|
|
public boolean hasMoreElements() {
|
|
if (nextElement == null && nextIndex < testVectors.size()) {
|
|
if (nextNextElement != null) {
|
|
nextElement = nextNextElement;
|
|
nextNextElement = null;
|
|
}
|
|
else {
|
|
getNextElement();
|
|
}
|
|
}
|
|
return nextElement != null;
|
|
}
|
|
|
|
private void getNextElement() {
|
|
|
|
TestVector testVector = testVectors.get(nextIndex++);
|
|
String origInstrStr = testVector.instr;
|
|
|
|
int index = origInstrStr.indexOf(';');
|
|
if (index > 0) {
|
|
String left = origInstrStr.substring(0, index).trim();
|
|
nextElement = new OrigInstruction(testVector.lineNo, left, true, false, false);
|
|
origInstrStr = origInstrStr.substring(index + 1).trim();
|
|
}
|
|
|
|
// check for endloop elements
|
|
boolean endloop0 = false;
|
|
boolean endloop1 = false;
|
|
try {
|
|
while (testVectors.get(nextIndex).instr.startsWith(":endloop")) {
|
|
String endloop = testVectors.get(nextIndex++).instr;
|
|
if (endloop.endsWith("0")) {
|
|
endloop0 = true;
|
|
}
|
|
else if (endloop.endsWith("1")) {
|
|
endloop1 = true;
|
|
}
|
|
}
|
|
}
|
|
catch (IndexOutOfBoundsException e) {
|
|
// ignore
|
|
}
|
|
|
|
if (nextElement != null) {
|
|
nextNextElement =
|
|
new OrigInstruction(testVector.lineNo, origInstrStr, false, endloop0, endloop1);
|
|
}
|
|
else {
|
|
nextElement =
|
|
new OrigInstruction(testVector.lineNo, origInstrStr, false, endloop0, endloop1);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public OrigInstruction nextElement() {
|
|
hasMoreElements();
|
|
OrigInstruction instr = nextElement;
|
|
nextElement = null;
|
|
return instr;
|
|
}
|
|
|
|
}
|
|
|
|
private class GhidraInstructionEnumeration implements Enumeration<GhidraInstruction> {
|
|
|
|
Address nextInstrAddr;
|
|
GhidraInstruction nextElement;
|
|
GhidraInstruction nextNextElement;
|
|
Register packetOffsetCtxReg;
|
|
|
|
GhidraInstructionEnumeration() {
|
|
nextInstrAddr = memory.getMinAddress();
|
|
packetOffsetCtxReg = currentProgram.getLanguage().getRegister("packetOffset");
|
|
}
|
|
|
|
@Override
|
|
public boolean hasMoreElements() {
|
|
if (nextElement == null) {
|
|
if (nextNextElement != null) {
|
|
nextElement = nextNextElement;
|
|
nextNextElement = null;
|
|
}
|
|
else {
|
|
getNextElement();
|
|
}
|
|
}
|
|
return nextElement != null;
|
|
}
|
|
|
|
private void getNextElement() {
|
|
if (nextInstrAddr == null) {
|
|
return;
|
|
}
|
|
if (!memory.contains(nextInstrAddr)) {
|
|
nextInstrAddr = null;
|
|
return;
|
|
}
|
|
Instruction instr = listing.getInstructionAt(nextInstrAddr);
|
|
if (instr == null) {
|
|
nextElement = new GhidraInstruction(nextInstrAddr, null);
|
|
}
|
|
else if (instr.toString().startsWith("immext")) {
|
|
nextInstrAddr = nextInstrAddr.add(4);
|
|
getNextElement();
|
|
return;
|
|
}
|
|
else {
|
|
String instrStr = instr.toString();
|
|
|
|
Address[] flows = instr.getFlows();
|
|
if (flows.length == 1) {
|
|
|
|
// Replace absolute flow address with relative offset
|
|
|
|
String flowOffsetStr = "0x" + flows[0].toString();
|
|
|
|
BigInteger value = instr.getValue(packetOffsetCtxReg, false);
|
|
int packetOffset = (int) (value != null ? value.longValue() : 0);
|
|
|
|
Address packetStartAddr = instr.getMinAddress().subtract(packetOffset * 4);
|
|
long relOffset = flows[0].subtract(packetStartAddr);
|
|
if ((relOffset & 0xc0000000L) == 0xc0000000L) {
|
|
relOffset = (relOffset << 32) >> 32; // sign fixup
|
|
}
|
|
|
|
boolean isNegative = relOffset < 0;
|
|
String relOffsetStr;
|
|
if (isNegative) {
|
|
relOffsetStr = "#-0x" + Long.toHexString(-relOffset);
|
|
}
|
|
else {
|
|
relOffsetStr = "#0x" + Long.toHexString(relOffset);
|
|
}
|
|
|
|
instrStr = instrStr.replace(flowOffsetStr, relOffsetStr);
|
|
|
|
}
|
|
|
|
int index = instrStr.indexOf(';');
|
|
if (index > 0) {
|
|
String left = instrStr.substring(0, index).trim();
|
|
nextElement = new GhidraInstruction(nextInstrAddr, left);
|
|
instrStr = instrStr.substring(index + 1).trim();
|
|
}
|
|
|
|
if (nextElement != null) {
|
|
nextNextElement = new GhidraInstruction(nextInstrAddr, instrStr);
|
|
}
|
|
else {
|
|
nextElement = new GhidraInstruction(nextInstrAddr, instrStr);
|
|
}
|
|
|
|
}
|
|
nextInstrAddr = nextInstrAddr.add(4);
|
|
}
|
|
|
|
@Override
|
|
public GhidraInstruction nextElement() {
|
|
hasMoreElements();
|
|
GhidraInstruction instr = nextElement;
|
|
nextElement = null;
|
|
return instr;
|
|
}
|
|
|
|
}
|
|
|
|
private class OrigInstruction {
|
|
|
|
private boolean isLeft;
|
|
private int lineNo;
|
|
private String instr;
|
|
private boolean endloop0;
|
|
private boolean endloop1;
|
|
|
|
OrigInstruction(int lineNo, String instr, boolean isLeft, boolean endloop0,
|
|
boolean endloop1) {
|
|
this.lineNo = lineNo;
|
|
this.instr = instr;
|
|
this.isLeft = isLeft;
|
|
this.endloop0 = endloop0;
|
|
this.endloop1 = endloop1;
|
|
parse();
|
|
}
|
|
|
|
boolean isLeftInstruction() {
|
|
return isLeft;
|
|
}
|
|
|
|
boolean hasAssignment() {
|
|
if (isMemStore()) {
|
|
return true;
|
|
}
|
|
int equalIndex = instr.indexOf('=');
|
|
int parenIndex = instr.indexOf('(');
|
|
if (parenIndex > 0 && instr.startsWith("if ")) {
|
|
parenIndex = instr.indexOf('(', parenIndex + 1);
|
|
}
|
|
return equalIndex > 0 && (parenIndex < 0 || parenIndex > equalIndex);
|
|
}
|
|
|
|
boolean isMemStore() {
|
|
int memOpIndex = instr.indexOf("mem");
|
|
int equalIndex = instr.indexOf('=');
|
|
|
|
return (memOpIndex >= 0 && memOpIndex < equalIndex);
|
|
}
|
|
|
|
private void parse() {
|
|
|
|
// Add missing conditional parens
|
|
addParens();
|
|
|
|
// Rename registers
|
|
for (String oldName : regRenameMap.keySet()) {
|
|
renameRegister(oldName, regRenameMap.get(oldName));
|
|
}
|
|
|
|
// ignore ## - treat as #
|
|
instr = instr.replace("##", "#");
|
|
|
|
// Convert shift expressions to single constant e.g., (#0x2 << 2)
|
|
fixShiftedConstantExpression();
|
|
|
|
// Convert decimal constants to hex
|
|
changeDecimalToHex();
|
|
|
|
// ignore << #0x0
|
|
instr = instr.replace("<< #0x0", "");
|
|
|
|
replaceLabels();
|
|
|
|
}
|
|
|
|
private void replaceLabels() {
|
|
for (Symbol s : labels) {
|
|
if (instr.indexOf(s.getName()) >= 0) {
|
|
String addrStr = "0x" + s.getAddress().toString();
|
|
instr = instr.replace(s.getName(), addrStr);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void addParens() {
|
|
if (!instr.startsWith("if ")) {
|
|
return;
|
|
}
|
|
// Assumes lower-case mnemonic and upper-case reg name
|
|
|
|
int condStartIndex = -1;
|
|
int condEndIndex = -1;
|
|
|
|
int index = 2;
|
|
int mode = 0;
|
|
while (index < instr.length() && condEndIndex < 0) {
|
|
char c = instr.charAt(index);
|
|
switch (mode) {
|
|
case 0: // looking for start
|
|
if (c == '(') {
|
|
return;
|
|
}
|
|
if (c == '!') {
|
|
condStartIndex = index;
|
|
}
|
|
else if (c != ' ') {
|
|
if (condStartIndex < 0) {
|
|
condStartIndex = index;
|
|
}
|
|
mode = 1;
|
|
}
|
|
break;
|
|
case 1: // on-reg
|
|
if (c == ' ') {
|
|
mode = 2;
|
|
}
|
|
break;
|
|
case 2: // after reg
|
|
if (c == '.') {
|
|
if (instr.substring(index).startsWith(".new")) {
|
|
index += 3;
|
|
}
|
|
else {
|
|
return; // unexpected
|
|
}
|
|
}
|
|
else if (c != ' ') {
|
|
condEndIndex = index;
|
|
}
|
|
break;
|
|
}
|
|
++index;
|
|
}
|
|
|
|
if (condEndIndex > 0) {
|
|
|
|
instr = instr.substring(0, condStartIndex) + "( " +
|
|
instr.substring(condStartIndex, condEndIndex) + ") " +
|
|
instr.substring(condEndIndex);
|
|
}
|
|
}
|
|
|
|
private void fixShiftedConstantExpression() {
|
|
try {
|
|
for (int index = instr.indexOf("#("); index >= 0 && index < instr.length(); index =
|
|
instr.indexOf("#(", index)) {
|
|
|
|
int expEndIndex = instr.indexOf(')', index);
|
|
if (expEndIndex <= 0) {
|
|
return; // unexpected
|
|
}
|
|
|
|
long expVal = getShiftedConstantValue(instr.substring(index + 2, expEndIndex));
|
|
|
|
boolean isNegative = expVal < 0;
|
|
String prefix = "#0x";
|
|
if (isNegative) {
|
|
prefix = "#-0x";
|
|
expVal = -expVal;
|
|
}
|
|
instr = instr.substring(0, index) + prefix + Long.toHexString(expVal) +
|
|
instr.substring(expEndIndex + 1);
|
|
|
|
++index;
|
|
}
|
|
}
|
|
catch (NumberFormatException e) {
|
|
Msg.error(this, e.getMessage());
|
|
}
|
|
}
|
|
|
|
private long getShiftedConstantValue(String constantShiftExp) throws NumberFormatException {
|
|
int shiftIndex = constantShiftExp.indexOf("<<");
|
|
|
|
String leftConstStr = (shiftIndex < 0) ? constantShiftExp
|
|
: constantShiftExp.substring(0, shiftIndex).trim();
|
|
|
|
boolean isNegative = leftConstStr.startsWith("-");
|
|
if (isNegative) {
|
|
leftConstStr = leftConstStr.substring(1);
|
|
}
|
|
long leftConst;
|
|
if (leftConstStr.startsWith("0x")) {
|
|
leftConstStr = leftConstStr.substring(2);
|
|
leftConst = Long.parseLong(leftConstStr, 16);
|
|
}
|
|
else {
|
|
leftConst = Long.parseLong(leftConstStr);
|
|
}
|
|
if (isNegative) {
|
|
leftConst = -leftConst;
|
|
}
|
|
|
|
if (shiftIndex < 0) {
|
|
return leftConst;
|
|
}
|
|
|
|
String rightConstStr = constantShiftExp.substring(shiftIndex + 2).trim();
|
|
int rightConst = Integer.parseInt(rightConstStr);
|
|
return leftConst << rightConst;
|
|
}
|
|
|
|
private void changeDecimalToHex() {
|
|
for (int index = instr.indexOf('#'); index >= 0 && index < (instr.length() - 1); index =
|
|
instr.indexOf('#', index)) {
|
|
if (instr.charAt(++index) == '-') {
|
|
++index;
|
|
}
|
|
if (instr.substring(index).startsWith("0x")) {
|
|
continue; // already hex
|
|
}
|
|
int valStartIndex = index;
|
|
StringBuilder buf = new StringBuilder();
|
|
while (index < instr.length()) {
|
|
char c = instr.charAt(index);
|
|
if (!Character.isDigit(c)) {
|
|
break;
|
|
}
|
|
buf.append(c);
|
|
++index;
|
|
}
|
|
if (buf.length() == 0) {
|
|
continue;
|
|
}
|
|
int val = Integer.parseInt(buf.toString());
|
|
String hexStr = "0x" + Integer.toHexString(val);
|
|
instr = instr.substring(0, valStartIndex) + hexStr +
|
|
instr.substring(valStartIndex + buf.length());
|
|
}
|
|
}
|
|
|
|
private void renameRegister(String oldName, String newName) {
|
|
// Assume only 32-bit regs
|
|
int nameLen = oldName.length();
|
|
int newNameLen = newName.length();
|
|
for (int index = instr.indexOf(oldName); index >= 0 && index < instr.length(); index =
|
|
instr.indexOf(oldName, index)) {
|
|
int indexAfterName = index + nameLen;
|
|
if ((index == 0 || instr.charAt(index - 1) != ':') &&
|
|
(indexAfterName == instr.length() || (instr.charAt(indexAfterName) != ':') &&
|
|
!Character.isDigit(instr.charAt(indexAfterName)))) {
|
|
instr = instr.substring(0, index) + newName + instr.substring(indexAfterName);
|
|
index += newNameLen;
|
|
}
|
|
else {
|
|
index = indexAfterName;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
StringBuilder buf = new StringBuilder(instr);
|
|
if (endloop0) {
|
|
buf.append(" :endloop0");
|
|
}
|
|
if (endloop1) {
|
|
buf.append(" :endloop1");
|
|
}
|
|
return buf.toString();
|
|
}
|
|
|
|
}
|
|
|
|
private class GhidraOperand {
|
|
|
|
String str;
|
|
|
|
GhidraOperand(String str) {
|
|
this.str = str.replace("##", "#"); // ignore ## - treat as #
|
|
this.str = this.str.replace("#0)", "#0x0)");
|
|
removeZeroPadding();
|
|
fixDoubleRegs();
|
|
}
|
|
|
|
private void removeZeroPadding() {
|
|
for (int index = str.indexOf("#0x0"); index >= 0 && index < str.length(); index =
|
|
str.indexOf("#0x0", index)) {
|
|
|
|
index += 3;
|
|
int startIndex = -1;
|
|
|
|
while (index < (str.length() - 1) && str.charAt(index) == '0') {
|
|
char nextChar = str.charAt(index + 1);
|
|
if (!Character.isDigit(nextChar)) {
|
|
break;
|
|
}
|
|
if (startIndex < 0) {
|
|
startIndex = index;
|
|
}
|
|
++index;
|
|
}
|
|
|
|
if (startIndex > 0) {
|
|
str = str.substring(0, startIndex) + str.substring(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void fixDoubleRegs() {
|
|
for (int index = 0; index <= str.length() - 4; ++index) {
|
|
char c = str.charAt(index);
|
|
if (c != 'R' && c != 'S' && c != 'C' && c != 'G') {
|
|
continue;
|
|
}
|
|
int numLen = getRegNumberLength(index + 1);
|
|
if (numLen < 0) {
|
|
continue;
|
|
}
|
|
index += numLen;
|
|
if (c == str.charAt(index + 1)) {
|
|
str = str.substring(0, index + 1) + ":" + str.substring(index + 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
private int getRegNumberLength(int startIndex) {
|
|
if (startIndex >= str.length() - 2) {
|
|
return -1;
|
|
}
|
|
if (!Character.isDigit(str.charAt(startIndex++))) {
|
|
return -1;
|
|
}
|
|
if (!Character.isDigit(str.charAt(startIndex))) {
|
|
return 1;
|
|
}
|
|
return 2;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return str;
|
|
}
|
|
|
|
}
|
|
|
|
private class GhidraInstruction {
|
|
|
|
private Address addr;
|
|
private String instr;
|
|
private boolean endloop0;
|
|
private boolean endloop1;
|
|
|
|
private GhidraOperand conditional;
|
|
private String mnemonic;
|
|
private String assignmentOperator; // = += -= etc.
|
|
private GhidraOperand outArg;
|
|
private List<String> modifiers; // :sat :<<1 etc.
|
|
private List<GhidraOperand> inArgs = new ArrayList<GhidraOperand>();
|
|
private boolean storeMemOp;
|
|
|
|
GhidraInstruction(Address addr, String instr) {
|
|
this.addr = addr;
|
|
this.instr = instr;
|
|
parse();
|
|
}
|
|
|
|
boolean isMissing() {
|
|
return instr == null;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return toString(false);
|
|
}
|
|
|
|
public String toString(boolean buildIt) {
|
|
if (instr == null) {
|
|
return "!MISSING!";
|
|
}
|
|
StringBuilder buf = new StringBuilder();
|
|
if (buildIt) {
|
|
if (conditional != null) {
|
|
buf.append("if (");
|
|
buf.append(conditional.toString());
|
|
buf.append(") ");
|
|
}
|
|
boolean isFlow = false;
|
|
if (outArg != null) {
|
|
boolean addParens = false;
|
|
if (mnemonic != null && inArgs.size() == 1 && storeMemOp) {
|
|
buf.append(mnemonic);
|
|
if (!outArg.str.startsWith("(")) {
|
|
buf.append('(');
|
|
addParens = true;
|
|
}
|
|
}
|
|
buf.append(outArg.toString());
|
|
if (addParens) {
|
|
buf.append(')');
|
|
}
|
|
buf.append(' ');
|
|
buf.append(assignmentOperator);
|
|
buf.append(' ');
|
|
}
|
|
if (mnemonic != null) {
|
|
if (!storeMemOp) {
|
|
buf.append(mnemonic);
|
|
}
|
|
}
|
|
else if (assignmentOperator == null) {
|
|
buf.append("!BAD-PARSE!");
|
|
}
|
|
if (inArgs.size() == 1 && assignmentOperator != null &&
|
|
(mnemonic == null || mnemonic.startsWith("mem"))) {
|
|
// omit parens
|
|
buf.append(' ');
|
|
buf.append(inArgs.get(0).toString());
|
|
}
|
|
else if (inArgs.size() == 1 && mnemonic != null &&
|
|
(mnemonic.startsWith("jump") || mnemonic.startsWith("call"))) {
|
|
|
|
if (modifiers != null) {
|
|
for (String modifier : modifiers) {
|
|
buf.append(" :");
|
|
buf.append(modifier);
|
|
}
|
|
}
|
|
|
|
// omit parens
|
|
isFlow = true;
|
|
buf.append(' ');
|
|
buf.append(inArgs.get(0).toString());
|
|
}
|
|
else if (inArgs.size() == 1 && inArgs.get(0).str.startsWith("(")) {
|
|
buf.append(inArgs.get(0).toString());
|
|
}
|
|
else if (inArgs.size() != 0) {
|
|
buf.append('(');
|
|
for (int i = 0; i < inArgs.size(); i++) {
|
|
if (i != 0) {
|
|
buf.append(", ");
|
|
}
|
|
buf.append(inArgs.get(i).toString());
|
|
}
|
|
buf.append(')');
|
|
}
|
|
|
|
if (!isFlow && modifiers != null) {
|
|
for (String modifier : modifiers) {
|
|
buf.append(" :");
|
|
buf.append(modifier);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
buf.append(instr);
|
|
}
|
|
|
|
if (endloop0) {
|
|
buf.append(" :endloop0");
|
|
}
|
|
if (endloop1) {
|
|
buf.append(" :endloop1");
|
|
}
|
|
return buf.toString();
|
|
}
|
|
|
|
boolean assumeAssignment(boolean storeMemOp) {
|
|
this.storeMemOp = storeMemOp;
|
|
if (assignmentOperator != null || outArg != null) {
|
|
return true; // already handled
|
|
}
|
|
if (inArgs.size() != 0) {
|
|
outArg = inArgs.remove(0);
|
|
assignmentOperator = "=";
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void parse() {
|
|
if (instr == null) {
|
|
return;
|
|
}
|
|
String instrStr = instr;
|
|
int index = instrStr.indexOf(":endloop0");
|
|
if (index > 0) {
|
|
instrStr = instrStr.replace(":endloop0", "").trim();
|
|
endloop0 = true;
|
|
}
|
|
index = instrStr.indexOf(":endloop1");
|
|
if (index > 0) {
|
|
instrStr = instrStr.replace(":endloop1", "").trim();
|
|
endloop0 = true;
|
|
}
|
|
|
|
instr = instrStr.trim();
|
|
|
|
index = instr.indexOf(' ');
|
|
if (index < 0) {
|
|
mnemonic = instr;
|
|
}
|
|
else {
|
|
mnemonic = instr.substring(0, index);
|
|
}
|
|
|
|
if (mnemonic.startsWith("mem")) {
|
|
// normalize low halfword memory load/store
|
|
instr = instr.replace(".L", "");
|
|
}
|
|
|
|
if (index > 0) {
|
|
parseOperands(instr.substring(index).trim());
|
|
}
|
|
|
|
// if (instr.startsWith("assign")) {
|
|
// if (inArgs.size() == 2) {
|
|
// outArg = inArgs.remove(0);
|
|
// assignmentOperator = "=";
|
|
// }
|
|
// mnemonic = null;
|
|
// return;
|
|
// }
|
|
|
|
index = indexOfSpecial(mnemonic);
|
|
if (index > 0) {
|
|
|
|
String addOns = mnemonic.substring(index);
|
|
mnemonic = mnemonic.substring(0, index);
|
|
|
|
// handle conditional
|
|
if (mnemonic != null && addOns.startsWith(".if")) {
|
|
if (addOns.charAt(3) == '(') {
|
|
index = addOns.indexOf(')');
|
|
if (index > 0) {
|
|
conditional = new GhidraOperand(addOns.substring(4, index));
|
|
addOns = addOns.substring(index + 1);
|
|
}
|
|
else {
|
|
mnemonic = null; // Bad mnemonic
|
|
}
|
|
}
|
|
else {
|
|
// assume conditional flow - use first argument
|
|
if (inArgs.size() >= 2) {
|
|
conditional = inArgs.remove(0);
|
|
addOns = addOns.substring(3);
|
|
}
|
|
else {
|
|
mnemonic = null; // Bad mnemonic
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle cmp modifiers .eq .lt etc.
|
|
if (mnemonic != null && addOns.startsWith(".")) {
|
|
mnemonic += ".";
|
|
index = 1;
|
|
while (index < addOns.length()) {
|
|
char c = addOns.charAt(index);
|
|
if (!Character.isLetter(c)) {
|
|
break;
|
|
}
|
|
mnemonic += c;
|
|
++index;
|
|
}
|
|
addOns = addOns.substring(index);
|
|
}
|
|
|
|
// explicit assignment operator - e.g., += -= ^= |= etc.
|
|
index = addOns.indexOf("=");
|
|
if (index >= 0) {
|
|
assignmentOperator = addOns.substring(0, index + 1);
|
|
addOns = addOns.substring(index + 1);
|
|
if (inArgs.size() != 0) {
|
|
outArg = inArgs.remove(0);
|
|
if (assignmentOperator == null) {
|
|
assignmentOperator = "=";
|
|
}
|
|
}
|
|
else {
|
|
mnemonic = null; // Bad mnemonic
|
|
}
|
|
}
|
|
|
|
if (mnemonic != null && addOns.length() != 0) {
|
|
char modifierChar = addOns.charAt(0);
|
|
if (modifierChar != '.' && modifierChar != ':') {
|
|
mnemonic = modifierChar + mnemonic;
|
|
addOns = addOns.substring(1);
|
|
}
|
|
}
|
|
|
|
// split-up modifiers
|
|
if (mnemonic != null && addOns.startsWith(":")) {
|
|
modifiers = new ArrayList<String>();
|
|
// TODO: Verify split
|
|
for (String modifier : addOns.substring(1).split(":")) {
|
|
if (!modifiers.add(modifier)) {
|
|
mnemonic = null; // duplicate
|
|
}
|
|
}
|
|
addOns = ""; // consumed everything
|
|
if (modifiers.isEmpty()) {
|
|
mnemonic = null;
|
|
}
|
|
}
|
|
|
|
if (addOns.length() != 0) {
|
|
mnemonic = null;
|
|
}
|
|
|
|
}
|
|
|
|
if (mnemonic == null) {
|
|
Msg.error(this, "Failed to morph: " + instr);
|
|
return;
|
|
}
|
|
|
|
// known assignments which are frequently switched at assembly time
|
|
if (instr.startsWith("assign")) {
|
|
assumeAssignment(false);
|
|
mnemonic = null;
|
|
}
|
|
else if (mnemonic.startsWith("cmp.")) {
|
|
assumeAssignment(false);
|
|
}
|
|
else if (mnemonic.startsWith("mem") && inArgs.size() == 2) {
|
|
assumeAssignment(inArgs.get(0).str.startsWith("("));
|
|
}
|
|
|
|
// if (assignmentOperator == null && inArgs.size() > 1) {
|
|
// checkPcodeForAssignment(); // TODO: won't work well for right-side packed instr
|
|
// }
|
|
|
|
}
|
|
|
|
private void parseOperands(String operands) {
|
|
|
|
operands = operands.trim();
|
|
if (operands.startsWith(",")) {
|
|
operands = operands.substring(1).trim();
|
|
}
|
|
else if (operands.length() == 0) {
|
|
return;
|
|
}
|
|
|
|
int index = 0;
|
|
int startIndex = 0;
|
|
int parenCnt = 0;
|
|
while (index < operands.length()) {
|
|
int leftParenIndex = operands.indexOf('(', index);
|
|
int rightParenIndex = operands.indexOf(')', index);
|
|
int commaIndex = operands.indexOf(',', index);
|
|
int endIndex = -1;
|
|
if (commaIndex < 0) {
|
|
endIndex = operands.length();
|
|
}
|
|
else if (parenCnt == 0 && (leftParenIndex < 0 || commaIndex < leftParenIndex)) {
|
|
endIndex = commaIndex;
|
|
index = commaIndex;
|
|
}
|
|
else if (parenCnt > 0 || leftParenIndex < commaIndex) {
|
|
// inside group or new group starts before comma
|
|
if (leftParenIndex >= 0 && leftParenIndex < rightParenIndex) {
|
|
// new group starts before next closure
|
|
++parenCnt;
|
|
index = leftParenIndex;
|
|
}
|
|
if (parenCnt > 0 && rightParenIndex >= 0) {
|
|
--parenCnt;
|
|
index = rightParenIndex;
|
|
}
|
|
}
|
|
|
|
if (endIndex >= 0) {
|
|
inArgs.add(new GhidraOperand(operands.substring(startIndex, endIndex)));
|
|
startIndex = endIndex + 1;
|
|
index = endIndex;
|
|
endIndex = -1;
|
|
}
|
|
|
|
++index;
|
|
}
|
|
|
|
if (startIndex < operands.length()) {
|
|
inArgs.add(new GhidraOperand(operands.substring(startIndex)));
|
|
}
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
// int parenIndex = operands.indexOf('(');
|
|
// int commaIndex = operands.indexOf(',');
|
|
//
|
|
//// if (parenIndex < 0) {
|
|
//// TODO: Verify split
|
|
// for (String opStr : operands.split(",")) {
|
|
// inArgs.add(new GhidraOperand(opStr));
|
|
// }
|
|
// return;
|
|
// }
|
|
//
|
|
// if (commaIndex > 0 && commaIndex < parenIndex) {
|
|
// inArgs.add(new GhidraOperand(operands.substring(0, commaIndex)));
|
|
// parseOperands(operands.substring(commaIndex + 1));
|
|
// return;
|
|
// }
|
|
//
|
|
// // assume no nested paren groups
|
|
//
|
|
// int rightParenIndex = operands.indexOf(')');
|
|
// if (rightParenIndex > 0) {
|
|
// inArgs.add(new GhidraOperand(operands.substring(0, rightParenIndex + 1)));
|
|
// parseOperands(operands.substring(rightParenIndex + 1));
|
|
// return;
|
|
// }
|
|
|
|
// no closing paren found
|
|
// TODO: generate error
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find index of first special character.
|
|
* @param str
|
|
* @return index or -1 if whitespace or end-of-string encountered before
|
|
* matching a non-alpha or numeric character
|
|
*/
|
|
private static int indexOfSpecial(String str) {
|
|
for (int i = 0; i < str.length(); i++) {
|
|
char c = str.charAt(i);
|
|
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
|
c == '_') {
|
|
continue;
|
|
}
|
|
if (c == ' ' || c == '\t') {
|
|
break;
|
|
}
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private class TestVector {
|
|
final int lineNo;
|
|
final String instr;
|
|
|
|
TestVector(int lineNo, String instr) {
|
|
this.lineNo = lineNo;
|
|
this.instr = instr;
|
|
}
|
|
}
|
|
|
|
private List<TestVector> readInstructions(File tvFile) throws IOException {
|
|
|
|
int row = 1;
|
|
ArrayList<TestVector> list = new ArrayList<TestVector>();
|
|
BufferedReader r = new BufferedReader(new FileReader(tvFile));
|
|
try {
|
|
String line;
|
|
while ((line = r.readLine()) != null) {
|
|
line = line.trim();
|
|
if (line.endsWith(":")) {
|
|
// skip labels
|
|
continue;
|
|
}
|
|
int index = line.indexOf("//");
|
|
if (index >= 0) {
|
|
line = line.substring(0, index).trim();
|
|
}
|
|
boolean addEndLoop0 = false;
|
|
boolean addEndLoop1 = false;
|
|
index = line.indexOf(":endloop0");
|
|
if (index >= 0) {
|
|
line = line.replace(":endloop0", "").trim();
|
|
addEndLoop0 = true;
|
|
}
|
|
index = line.indexOf(":endloop1");
|
|
if (index >= 0) {
|
|
line = line.replace(":endloop1", "").trim();
|
|
addEndLoop1 = true;
|
|
}
|
|
if (line.startsWith("{") || line.startsWith("}")) {
|
|
line = line.substring(1).trim();
|
|
}
|
|
if (line.endsWith("}")) {
|
|
line = line.substring(0, line.length() - 1).trim();
|
|
}
|
|
if (line.endsWith(";")) {
|
|
line = line.substring(0, line.length() - 1).trim();
|
|
}
|
|
if (line.length() != 0 && !line.startsWith(".")) {
|
|
list.add(new TestVector(row, line));
|
|
}
|
|
if (addEndLoop0) {
|
|
list.add(new TestVector(row, ":endloop0"));
|
|
}
|
|
if (addEndLoop1) {
|
|
list.add(new TestVector(row, ":endloop1"));
|
|
}
|
|
++row;
|
|
}
|
|
}
|
|
finally {
|
|
r.close();
|
|
}
|
|
return list;
|
|
}
|
|
|
|
}
|