mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
Added extensions GnuDisassembler and SleighDevTools
This commit is contained in:
0
Ghidra/Extensions/SleighDevTools/Module.manifest
Normal file
0
Ghidra/Extensions/SleighDevTools/Module.manifest
Normal file
9
Ghidra/Extensions/SleighDevTools/build.gradle
Normal file
9
Ghidra/Extensions/SleighDevTools/build.gradle
Normal file
@@ -0,0 +1,9 @@
|
||||
apply from: "$rootProject.projectDir/gradle/distributableGhidraExtension.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
|
||||
apply plugin: 'eclipse'
|
||||
eclipse.project.name = 'Xtra SleighDevTools'
|
||||
|
||||
dependencies {
|
||||
compile project(':Base')
|
||||
}
|
||||
8
Ghidra/Extensions/SleighDevTools/certification.manifest
Normal file
8
Ghidra/Extensions/SleighDevTools/certification.manifest
Normal file
@@ -0,0 +1,8 @@
|
||||
##VERSION: 2.0
|
||||
.classpath||GHIDRA||||END|
|
||||
.project||GHIDRA||||END|
|
||||
Module.manifest||GHIDRA||||END|
|
||||
build.gradle||GHIDRA||||END|
|
||||
data/ExtensionPoint.manifest||GHIDRA||||END|
|
||||
data/LanguageMap.txt||GHIDRA||||END|
|
||||
extension.properties||GHIDRA||||END|
|
||||
@@ -0,0 +1 @@
|
||||
ExternalDisassembler
|
||||
13
Ghidra/Extensions/SleighDevTools/data/LanguageMap.txt
Normal file
13
Ghidra/Extensions/SleighDevTools/data/LanguageMap.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
// Format: LanguageID#CustomGDISExecutable
|
||||
//
|
||||
// Mapping of LanguageNameFromGhidra to external (gdis) architecture names is no longer done here.
|
||||
// This functionality has been moved to each language's ldefs file.
|
||||
// External names are mapped via 'external_name' tags in language definitions.
|
||||
// The CustomGDISExecutable is found via a call to Application.getOSFile(), which will search in
|
||||
// the platform-specific OS directory within all modules.
|
||||
//
|
||||
// Lines starting with "//" are not parsed.
|
||||
//
|
||||
// '*' can be used to wild-card parts of the languageID
|
||||
//
|
||||
|
||||
5
Ghidra/Extensions/SleighDevTools/extension.properties
Normal file
5
Ghidra/Extensions/SleighDevTools/extension.properties
Normal file
@@ -0,0 +1,5 @@
|
||||
name=SleighDevTools
|
||||
description=Sleigh language development tools including external disassembler capabilities. The GnuDisassembler extension may be also be required as a disassembly provider.
|
||||
author=Ghidra Team
|
||||
createdOn=6/18/2019
|
||||
version=@extversion@
|
||||
@@ -0,0 +1,320 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
// Compare Sliegh disassembly with external disassembly results
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.util.PseudoDisassembler;
|
||||
import ghidra.app.util.PseudoInstruction;
|
||||
import ghidra.app.util.disassemble.GNUExternalDisassembler;
|
||||
import ghidra.program.disassemble.Disassembler;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.UnknownInstructionException;
|
||||
import ghidra.program.model.listing.BookmarkType;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.model.scalar.Scalar;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class CompareSleighExternal extends GhidraScript {
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (currentProgram == null) {
|
||||
return;
|
||||
}
|
||||
AddressSetView set = currentSelection;
|
||||
if (set == null || set.isEmpty()) {
|
||||
set = currentProgram.getMemory().getLoadedAndInitializedAddressSet();
|
||||
}
|
||||
|
||||
putEquivalent("xzr", "x31"); // Think they messed up and allowed x31, there is no x31
|
||||
putEquivalent("wzr", "w31"); // Think they messed up and allowed w31, there is no w31
|
||||
putEquivalent("r12", "ip");
|
||||
|
||||
int completed = 0;
|
||||
monitor.initialize(set.getNumAddresses());
|
||||
|
||||
AddressIterator addresses = set.getAddresses(true);
|
||||
|
||||
PseudoDisassembler pseudoDisassembler = new PseudoDisassembler(currentProgram);
|
||||
|
||||
GNUExternalDisassembler dis = new GNUExternalDisassembler();
|
||||
|
||||
long align = currentProgram.getLanguage().getInstructionAlignment();
|
||||
while (addresses.hasNext()) {
|
||||
monitor.checkCanceled();
|
||||
Address addr = addresses.next();
|
||||
|
||||
completed++;
|
||||
|
||||
// only on valid boundaries
|
||||
if ((addr.getOffset() % align) != 0) {
|
||||
continue;
|
||||
}
|
||||
clearBad(addr);
|
||||
|
||||
monitor.setProgress(completed);
|
||||
|
||||
CodeUnit cu = currentProgram.getListing().getCodeUnitAt(addr);
|
||||
if (cu == null) {
|
||||
continue;
|
||||
}
|
||||
String str = dis.getDisassembly(cu);
|
||||
|
||||
str = str.toLowerCase();
|
||||
|
||||
PseudoInstruction pinst = null;
|
||||
try {
|
||||
pinst = pseudoDisassembler.disassemble(addr);
|
||||
} catch (UnknownInstructionException e) {
|
||||
// didn't get an instruction, did external not get one?
|
||||
if (str.startsWith(".inst") && str.endsWith("undefined")) {
|
||||
continue;
|
||||
}
|
||||
markErrorBad(addr,"Unimplemented Instruction", str);
|
||||
continue;
|
||||
}
|
||||
// didn't get an instruction, did external not get one?
|
||||
if (pinst == null && str.startsWith(".inst") && str.endsWith("undefined")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pinst == null) {
|
||||
markErrorBad(addr,"Unimplemented Instruction", str);
|
||||
continue;
|
||||
}
|
||||
|
||||
// collapse both instruction to strings, compare removing whitespace, and to-lower
|
||||
String pStr = pinst.toString().toLowerCase().replaceAll("\\s","");
|
||||
String eStr = str.toLowerCase().replaceAll("\\s", "");
|
||||
|
||||
// simple equivalence
|
||||
if (pStr.equals(eStr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String mnemonic = pinst.getMnemonicString().toLowerCase();
|
||||
if (!str.startsWith(mnemonic)) {
|
||||
markBad(addr,"Mnemonic Disagreement", str + " != " + mnemonic);
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = str.indexOf(" ");
|
||||
|
||||
for (int opIndex = 0; opIndex < pinst.getNumOperands(); opIndex++) {
|
||||
// try to parse the operand string from the instruction
|
||||
int sepEnd = str.indexOf(",", start);
|
||||
|
||||
String extOp = getExtOpStr(str, start, sepEnd);
|
||||
start = sepEnd + 1;
|
||||
|
||||
String valStr = null;
|
||||
|
||||
// TODO: could remove all characters, making sure none are left!
|
||||
int loc = 0;
|
||||
boolean subRegList = false;
|
||||
List<Object> opObjList = pinst.getDefaultOperandRepresentationList(opIndex);
|
||||
for (Object object : opObjList) {
|
||||
if (object instanceof Character) {
|
||||
Character ch = (Character) object;
|
||||
ch = Character.toLowerCase(ch);
|
||||
loc = extOp.indexOf(ch);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+1);
|
||||
continue;
|
||||
}
|
||||
if (ch.equals(',')) {
|
||||
if (subRegList) {
|
||||
continue;
|
||||
}
|
||||
// gotta move into next string, must be embedded comma
|
||||
sepEnd = str.indexOf(",", start);
|
||||
|
||||
extOp = getExtOpStr(str, start, sepEnd);
|
||||
start = sepEnd + 1;
|
||||
continue;
|
||||
}
|
||||
if (ch.equals(' ')) {
|
||||
continue;
|
||||
}
|
||||
markBad(addr,"Missing String Markup", ch.toString());
|
||||
break;
|
||||
}
|
||||
if (object instanceof Scalar) {
|
||||
// find the scalar, hex or decimal
|
||||
Scalar scalar = (Scalar) object;
|
||||
valStr = scalar.toString(16, false, false, "0x", "");
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
valStr = scalar.toString(16, true, false, "0x", "");
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
valStr = scalar.toString(10, false, true, "", "");
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
valStr = scalar.toString(10, false, false, "", "");
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
valStr = scalar.toString(16, false, false, "", "");
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
valStr = scalar.toString(16, true, false, "", "");
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
markBad(addr,"Missing Scalar", valStr);
|
||||
break;
|
||||
}
|
||||
if (object instanceof Register) {
|
||||
Register reg = (Register) object;
|
||||
loc = extOp.indexOf(reg.getName().toLowerCase());
|
||||
if (loc != -1) {
|
||||
// check for '-' first
|
||||
if (extOp.charAt(0) == '-') {
|
||||
extOp = extOp.substring(1);
|
||||
loc = 0;
|
||||
subRegList = false;
|
||||
}
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+reg.getName().length());
|
||||
if (extOp.length() > 0 && extOp.charAt(0) == '-') {
|
||||
subRegList = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for equivalent register
|
||||
String equivReg = regGetEquivalent(reg.getName());
|
||||
if (equivReg != null) {
|
||||
loc = extOp.indexOf(equivReg);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+equivReg.length());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
loc = extOp.indexOf('-'); // could be a register list, assume we will find beginning and end register
|
||||
if (loc != -1) {
|
||||
continue;
|
||||
}
|
||||
markBad(addr,"Missing Register", reg.toString());
|
||||
break;
|
||||
}
|
||||
if (object instanceof Address) {
|
||||
Address dest = (Address) object;
|
||||
valStr = dest.toString(false,true);
|
||||
valStr = "0x" + valStr;
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
valStr = dest.toString(false,false);
|
||||
valStr = "0x" + valStr;
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
valStr = dest.toString(false,true);
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
valStr = dest.toString(false,false);
|
||||
loc = extOp.indexOf(valStr);
|
||||
if (loc != -1) {
|
||||
extOp = extOp.substring(0,loc) + extOp.substring(loc+valStr.length());
|
||||
continue;
|
||||
}
|
||||
markBad(addr,"Missing Address", dest.toString());
|
||||
}
|
||||
}
|
||||
extOp = extOp.trim();
|
||||
if (extOp.length() > 0 && !extOp.startsWith(";") && !extOp.startsWith("//") && !extOp.equals("#") && !extOp.matches("[0x]+")) {
|
||||
markBad(addr,"Missing characters", extOp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HashMap<String, String> equivRegisters = new HashMap<String, String>();
|
||||
|
||||
private String regGetEquivalent(String name) {
|
||||
return equivRegisters.get(name);
|
||||
}
|
||||
|
||||
private void putEquivalent(String name, String equiv) {
|
||||
equivRegisters.put(name, equiv);
|
||||
}
|
||||
|
||||
private String getExtOpStr(String str, int start, int sepEnd) {
|
||||
String opS = null;
|
||||
if (start == -1) {
|
||||
return "";
|
||||
}
|
||||
if (sepEnd == -1) {
|
||||
opS = str.substring(start);
|
||||
} else {
|
||||
opS = str.substring(start, sepEnd);
|
||||
}
|
||||
String extOp = opS.trim();
|
||||
return extOp;
|
||||
}
|
||||
|
||||
private void markBad(Address addr, String type, String error) {
|
||||
currentProgram.getBookmarkManager().setBookmark(addr, BookmarkType.WARNING,
|
||||
type,
|
||||
error);
|
||||
}
|
||||
|
||||
private void markErrorBad(Address addr, String type, String error) {
|
||||
currentProgram.getBookmarkManager().setBookmark(addr, BookmarkType.ERROR,
|
||||
Disassembler.ERROR_BOOKMARK_CATEGORY,
|
||||
error);
|
||||
}
|
||||
|
||||
private void clearBad(Address addr) {
|
||||
AddressSet set = new AddressSet(addr);
|
||||
try {
|
||||
currentProgram.getBookmarkManager().removeBookmarks(set, BookmarkType.WARNING, monitor);
|
||||
currentProgram.getBookmarkManager().removeBookmarks(set, BookmarkType.ERROR, monitor);
|
||||
} catch (CancelledException e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/* ###
|
||||
* 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.
|
||||
*/
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.util.disassemble.GNUExternalDisassembler;
|
||||
import ghidra.app.util.disassemble.GnuDisassembledInstruction;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GNUDisassembleBlockScript extends GhidraScript {
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
|
||||
if (currentProgram == null || currentAddress == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GNUExternalDisassembler dis = new GNUExternalDisassembler();
|
||||
|
||||
Address addr = currentAddress.getNewAddress(currentAddress.getOffset() & -32); // block aligned address
|
||||
|
||||
List<GnuDisassembledInstruction> results = dis.getBlockDisassembly(currentProgram, addr, 5);
|
||||
|
||||
if (results == null) {
|
||||
println("Block Disassembly Failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
int maxByteLen = 0;
|
||||
for (GnuDisassembledInstruction result : results) {
|
||||
maxByteLen = Math.max(maxByteLen, result.getNumberOfBytesInInstruction());
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (GnuDisassembledInstruction result : results) {
|
||||
sb.append(addr.toString());
|
||||
sb.append(' ');
|
||||
int cnt = 0;
|
||||
byte[] bytes = new byte[result.getNumberOfBytesInInstruction()];
|
||||
currentProgram.getMemory().getBytes(addr, bytes);
|
||||
for (byte b : bytes) {
|
||||
if (b >= 0 && b < 0x10) {
|
||||
sb.append('0');
|
||||
}
|
||||
sb.append(Integer.toHexString(b & 0xff));
|
||||
sb.append(' ');
|
||||
++cnt;
|
||||
}
|
||||
if (cnt < maxByteLen) {
|
||||
int pad = (maxByteLen - cnt) * 3;
|
||||
for (int i = 0; i < pad; i++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
}
|
||||
sb.append(result.getInstruction());
|
||||
sb.append("\n");
|
||||
addr = addr.add(bytes.length);
|
||||
}
|
||||
if (sb.length() != 0) {
|
||||
println("Block Disassembly:\n" + sb.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* ###
|
||||
* 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.disassemble;
|
||||
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
|
||||
public interface ExternalDisassembler extends ExtensionPoint {
|
||||
|
||||
public String getDisassembly(CodeUnit cu) throws Exception;
|
||||
|
||||
public String getDisassemblyOfBytes(Language language, boolean isBigEndian, long address,
|
||||
byte[] byteString) throws Exception;
|
||||
|
||||
public boolean isSupportedLanguage(Language language);
|
||||
|
||||
public void destroy();
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/* ###
|
||||
* 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.disassemble;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
import docking.widgets.fieldpanel.field.*;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import ghidra.app.util.HighlightProvider;
|
||||
import ghidra.app.util.viewer.field.*;
|
||||
import ghidra.app.util.viewer.format.FieldFormatModel;
|
||||
import ghidra.app.util.viewer.proxy.ProxyObj;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
|
||||
public class ExternalDisassemblyFieldFactory extends FieldFactory {
|
||||
|
||||
private static List<ExternalDisassembler> availableDisassemblers;
|
||||
|
||||
private static synchronized List<ExternalDisassembler> getAvailableDisassemblers() {
|
||||
if (availableDisassemblers != null) {
|
||||
return availableDisassemblers;
|
||||
}
|
||||
availableDisassemblers = new ArrayList<>();
|
||||
|
||||
// find the available external disassemblers
|
||||
Set<ExternalDisassembler> extDisassemblers =
|
||||
ClassSearcher.getInstances(ExternalDisassembler.class);
|
||||
|
||||
for (ExternalDisassembler disassember : extDisassemblers) {
|
||||
availableDisassemblers.add(disassember);
|
||||
}
|
||||
return availableDisassemblers;
|
||||
}
|
||||
|
||||
public static final String FIELD_NAME = "External Disassembly";
|
||||
|
||||
public ExternalDisassemblyFieldFactory() {
|
||||
super(FIELD_NAME);
|
||||
}
|
||||
|
||||
private ExternalDisassemblyFieldFactory(FieldFormatModel model, HighlightProvider hlProvider,
|
||||
Options displayOptions, Options fieldOptions) {
|
||||
super(FIELD_NAME, model, hlProvider, displayOptions, fieldOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fieldOptionsChanged(Options options, String optionName, Object oldValue,
|
||||
Object newValue) {
|
||||
// have no options
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptsType(int category, Class<?> proxyObjectClass) {
|
||||
return (category == FieldFormatModel.INSTRUCTION_OR_DATA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum,
|
||||
ProgramLocation loc) {
|
||||
if (loc instanceof ExternalDisassemblyFieldLocation) {
|
||||
return new FieldLocation(index, fieldNum,
|
||||
((ExternalDisassemblyFieldLocation) loc).getRow(),
|
||||
((ExternalDisassemblyFieldLocation) loc).getCharOffset());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramLocation getProgramLocation(int row, int col, ListingField bf) {
|
||||
ProxyObj<?> proxy = bf.getProxy();
|
||||
Object obj = proxy.getObject();
|
||||
if (!(obj instanceof CodeUnit)) {
|
||||
return null;
|
||||
}
|
||||
CodeUnit cu = (CodeUnit) obj;
|
||||
|
||||
return new ExternalDisassemblyFieldLocation(cu.getProgram(), cu.getMinAddress(), row, col);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldFactory newInstance(FieldFormatModel formatModel,
|
||||
HighlightProvider highlightProvider, ToolOptions options, ToolOptions fieldOptions) {
|
||||
return new ExternalDisassemblyFieldFactory(formatModel, highlightProvider, options,
|
||||
fieldOptions);
|
||||
}
|
||||
|
||||
private ExternalDisassembler getDisassembler(Language language) {
|
||||
for (ExternalDisassembler disassembler : getAvailableDisassemblers()) {
|
||||
if (disassembler.isSupportedLanguage(language)) {
|
||||
return disassembler;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListingField getField(ProxyObj<?> proxy, int varWidth) {
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
Object obj = proxy.getObject();
|
||||
if (!(obj instanceof CodeUnit)) {
|
||||
return null;
|
||||
}
|
||||
CodeUnit cu = (CodeUnit) obj;
|
||||
|
||||
try {
|
||||
String disassembly = getDisassemblyText(cu);
|
||||
if (disassembly == null) {
|
||||
return null;
|
||||
}
|
||||
AttributedString text = new AttributedString(disassembly, Color.black, getMetrics());
|
||||
FieldElement fieldElement = new TextFieldElement(text, 0, 0);
|
||||
return ListingTextField.createSingleLineTextField(this, proxy, fieldElement,
|
||||
startX + varWidth, width, hlProvider);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return getErrorText(proxy, varWidth, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getDisassemblyText(CodeUnit cu) throws Exception {
|
||||
Language language = cu.getProgram().getLanguage();
|
||||
ExternalDisassembler disassembler = getDisassembler(language);
|
||||
if (disassembler == null) {
|
||||
return null;
|
||||
}
|
||||
String disassembly = disassembler.getDisassembly(cu);
|
||||
if (disassembly == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return disassembly;
|
||||
}
|
||||
|
||||
private ListingTextField getErrorText(ProxyObj<?> proxy, int varWidth, Exception e) {
|
||||
String message = e.getMessage();
|
||||
if (message == null) {
|
||||
message = e.toString();
|
||||
}
|
||||
AttributedString errorText = new AttributedString(message, Color.red, getMetrics());
|
||||
FieldElement fieldElement = new TextFieldElement(errorText, 0, 0);
|
||||
return ListingTextField.createSingleLineTextField(this, proxy, fieldElement,
|
||||
startX + varWidth, width, hlProvider);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* ###
|
||||
* 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.disassemble;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
||||
public class ExternalDisassemblyFieldLocation extends ProgramLocation {
|
||||
|
||||
public ExternalDisassemblyFieldLocation(Program program, Address addr, int row, int charOffset) {
|
||||
super(program, addr, row, 0, charOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the row within a group of pcode strings.
|
||||
*/
|
||||
public ExternalDisassemblyFieldLocation() {
|
||||
// for deserialization
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/* ###
|
||||
* 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.disassemble;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class ExternalStreamHandler extends Thread {
|
||||
private InputStream inStream;
|
||||
|
||||
ExternalStreamHandler(InputStream inStream) {
|
||||
this.inStream = inStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
InputStreamReader inStreamReader = new InputStreamReader(inStream);
|
||||
BufferedReader buffReader = new BufferedReader(inStreamReader);
|
||||
String line;
|
||||
while ((line = buffReader.readLine()) != null) {
|
||||
Msg.error(ExternalDisassemblyFieldFactory.class, "Error in Disassembler: " + line);
|
||||
}
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,646 @@
|
||||
/* ###
|
||||
* 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.disassemble;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.app.util.bin.MemoryByteProvider;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressOutOfBoundsException;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.LanguageID;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class GNUExternalDisassembler implements ExternalDisassembler {
|
||||
|
||||
private static final String UNSUPPORTED = "UNSUPPORTED";
|
||||
|
||||
// magic values for gdis that direct it to read bytes from stdin
|
||||
private static final String READ_FROM_STDIN_PARAMETER = "stdin";
|
||||
private static final String SEPARATOR_CHARACTER = "\n";
|
||||
private static final String ADDRESS_OUT_OF_BOUNDS = "is out of bounds.";
|
||||
private static final String ENDING_STRING = "EOF";
|
||||
private static final int NUM_BYTES = 32;
|
||||
|
||||
private static final String MAP_FILENAME = "LanguageMap.txt";
|
||||
private static final String GNU_DISASSEMBLER_MODULE_NAME = "GnuDisassembler";
|
||||
private static final String GDIS_EXE =
|
||||
Platform.CURRENT_PLATFORM.getOperatingSystem() == OperatingSystem.WINDOWS ? "gdis.exe"
|
||||
: "gdis";
|
||||
|
||||
private static HashMap<String, File> languageGdisMap;
|
||||
private static File defaultGdisExecFile;
|
||||
private static File gdisDataDirectory;
|
||||
|
||||
private static Map<LanguageID, GdisConfig> configCache = new HashMap<>();
|
||||
private static boolean missingExtensionReported;
|
||||
|
||||
private GdisConfig currentConfig;
|
||||
private boolean hadFailure;
|
||||
|
||||
private Process disassemblerProcess;
|
||||
private BufferedReader buffReader;
|
||||
private OutputStreamWriter outputWriter;
|
||||
|
||||
// private LanguageID lastLanguageWarnedAbout;
|
||||
|
||||
public GNUExternalDisassembler() throws Exception {
|
||||
initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (disassemblerProcess != null) {
|
||||
disassemblerProcess.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportedLanguage(Language language) {
|
||||
GdisConfig gdisConfig = checkLanguage(language);
|
||||
return gdisConfig != null && gdisConfig.architecture != UNSUPPORTED;
|
||||
}
|
||||
|
||||
private static void reportMultipleMappings(Language language) {
|
||||
List<String> externalNames = language.getLanguageDescription().getExternalNames("gnu");
|
||||
if (externalNames != null && externalNames.size() > 1) {
|
||||
LanguageID currentLanguageID = language.getLanguageID();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean prependSeparator = false;
|
||||
for (String name : externalNames) {
|
||||
if (prependSeparator)
|
||||
sb.append(", ");
|
||||
sb.append(name);
|
||||
prependSeparator = true;
|
||||
}
|
||||
Msg.warn(GNUExternalDisassembler.class,
|
||||
"Language " + currentLanguageID + " illegally maps to multiple (" +
|
||||
externalNames.size() + ") external gnu names: " + sb.toString() +
|
||||
". The first external name will be used.");
|
||||
}
|
||||
}
|
||||
|
||||
private static class GdisConfig {
|
||||
|
||||
String languageId;
|
||||
boolean isBigEndian;
|
||||
|
||||
String architecture;
|
||||
String machineId;
|
||||
File gdisExecFile;
|
||||
boolean usingDefault;
|
||||
|
||||
GdisConfig(Language language, boolean isBigEndian) {
|
||||
|
||||
this.languageId = language.getLanguageID().toString();
|
||||
this.isBigEndian = isBigEndian;
|
||||
|
||||
List<String> architectures = language.getLanguageDescription().getExternalNames("gnu");
|
||||
//get first non-null
|
||||
if (architectures != null && architectures.size() > 0) {
|
||||
architecture = architectures.get(0);
|
||||
if (architectures.size() > 1) {
|
||||
reportMultipleMappings(language);
|
||||
}
|
||||
}
|
||||
if (architecture == null) {
|
||||
architecture = UNSUPPORTED;
|
||||
return;
|
||||
}
|
||||
|
||||
machineId = "0x0";
|
||||
|
||||
// handle numeric entry which combines architecture and machineId
|
||||
if (architecture.startsWith("0x")) {
|
||||
String[] parts = architecture.split(":");
|
||||
architecture = parts[0];
|
||||
machineId = parts[1];
|
||||
}
|
||||
|
||||
gdisExecFile = languageGdisMap.get(languageId);
|
||||
if (gdisExecFile == null) {
|
||||
gdisExecFile = defaultGdisExecFile;
|
||||
usingDefault = true;
|
||||
}
|
||||
}
|
||||
|
||||
GdisConfig(Language lang) {
|
||||
this(lang, lang.isBigEndian());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof GdisConfig)) {
|
||||
return false;
|
||||
}
|
||||
// assume config will match for a given language
|
||||
return languageId.equals(((GdisConfig) obj).languageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return languageId.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized GdisConfig checkLanguage(Language lang) {
|
||||
LanguageID languageId = lang.getLanguageID();
|
||||
if (configCache.containsKey(languageId)) {
|
||||
return configCache.get(languageId);
|
||||
}
|
||||
GdisConfig config = new GdisConfig(lang);
|
||||
if (config.architecture == UNSUPPORTED) {
|
||||
Msg.warn(GNUExternalDisassembler.class,
|
||||
"Language not supported (ldefs 'gnu' map entry not found): " + languageId);
|
||||
}
|
||||
else if (gdisDataDirectory == null) {
|
||||
config = null;
|
||||
if (!missingExtensionReported) {
|
||||
missingExtensionReported = true;
|
||||
Msg.showError(GNUExternalDisassembler.class, null, "GNU Disassembler Not Found",
|
||||
"External Disassembler extension module not installed: " +
|
||||
GNU_DISASSEMBLER_MODULE_NAME);
|
||||
}
|
||||
}
|
||||
else if (config.gdisExecFile == null) {
|
||||
boolean usingDefault = config.usingDefault;
|
||||
config = null;
|
||||
if (usingDefault) {
|
||||
if (!missingExtensionReported) {
|
||||
missingExtensionReported = true;
|
||||
Msg.showError(GNUExternalDisassembler.class, null, "GNU Disassembler Not Found",
|
||||
"External GNU Disassembler not found (requires install and build of " +
|
||||
GNU_DISASSEMBLER_MODULE_NAME + " extension): " + GDIS_EXE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Msg.showError(GNUExternalDisassembler.class, null, "GNU Disassembler Not Found",
|
||||
"External GNU Disassembler not found for language (" + lang.getLanguageID() +
|
||||
", see LanguageMap.txt)");
|
||||
}
|
||||
}
|
||||
configCache.put(languageId, config);
|
||||
return config;
|
||||
}
|
||||
|
||||
private int pow2(int pow) {
|
||||
int r = 1;
|
||||
for (int i = 1; i <= pow; i++) {
|
||||
r *= 2;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed instruction list for a block of instructions.
|
||||
*
|
||||
* @param lang
|
||||
* processor language (corresponding LanguageID must be defined
|
||||
* within LanguageMap.txt)
|
||||
* @param blockAddr
|
||||
* start of block ( must be true: (offset & -(2^blockSizeFactor)
|
||||
* == offset)
|
||||
* @param blockSizeFactor
|
||||
* the block size factor where blockSize = 2^blockSizeFactor
|
||||
* (must be > 0)
|
||||
* @param byteProvider
|
||||
* provider for block of bytes to be disassembled starting at
|
||||
* offset 0
|
||||
* @return list of instructions or null if language not supported by GNU
|
||||
* Disassembler
|
||||
* @throws Exception
|
||||
*/
|
||||
public List<GnuDisassembledInstruction> getBlockDisassembly(Language lang, Address blockAddr,
|
||||
int blockSizeFactor, ByteProvider byteProvider) throws Exception {
|
||||
|
||||
GdisConfig gdisConfig = checkLanguage(lang);
|
||||
if (gdisConfig == null || gdisConfig.architecture == UNSUPPORTED) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (blockSizeFactor < 0 || blockSizeFactor > 8) {
|
||||
throw new IllegalArgumentException("blockSizeFactor must be > 0 and <= 8");
|
||||
}
|
||||
int blockSize = pow2(blockSizeFactor);
|
||||
|
||||
if ((blockAddr.getOffset() & -blockSize) != blockAddr.getOffset()) {
|
||||
throw new IllegalArgumentException("Address must be block aligned");
|
||||
}
|
||||
|
||||
long addressOffset = blockAddr.getAddressableWordOffset();
|
||||
String address = "0x" + Long.toHexString(addressOffset);
|
||||
|
||||
// for aligned languages, don't try on non-aligned block addr/size.
|
||||
int alignment = lang.getInstructionAlignment();
|
||||
if (blockAddr.getOffset() % alignment != 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Address does not satisfy instruction alignment constraint: " + alignment);
|
||||
}
|
||||
|
||||
String bytes = getBytes(byteProvider, blockSize);
|
||||
|
||||
return runDisassembler(gdisConfig, address, bytes);
|
||||
}
|
||||
|
||||
public List<GnuDisassembledInstruction> getBlockDisassembly(Program program, Address addr,
|
||||
int blockSizeFactor) throws Exception {
|
||||
|
||||
if (blockSizeFactor < 0 || blockSizeFactor > 8) {
|
||||
throw new IllegalArgumentException("blockSizeFactor must be > 0 and <= 8");
|
||||
}
|
||||
int blockSize = pow2(blockSizeFactor);
|
||||
|
||||
Address blockAddr = addr.getNewAddress(addr.getOffset() & -blockSize); // block
|
||||
// aligned
|
||||
// address
|
||||
|
||||
return getBlockDisassembly(program.getLanguage(), blockAddr, blockSizeFactor,
|
||||
new MemoryByteProvider(program.getMemory(), blockAddr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisassembly(CodeUnit cu) throws Exception {
|
||||
|
||||
GdisConfig gdisConfig = checkLanguage(cu.getProgram().getLanguage());
|
||||
if (gdisConfig == null || gdisConfig.architecture == UNSUPPORTED) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long addressOffset = cu.getAddress().getAddressableWordOffset();
|
||||
String address = "0x" + Long.toHexString(addressOffset);
|
||||
|
||||
// for aligned languages, don't try on non-aligned locations.
|
||||
if (cu.getMinAddress().getOffset() %
|
||||
cu.getProgram().getLanguage().getInstructionAlignment() != 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String bytes = getBytes(cu, NUM_BYTES);
|
||||
if (bytes == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
List<GnuDisassembledInstruction> disassembly = runDisassembler(gdisConfig, address, bytes);
|
||||
|
||||
if (disassembly == null || disassembly.size() == 0 || disassembly.get(0) == null) {
|
||||
return "(bad)";
|
||||
}
|
||||
return disassembly.get(0).toString();
|
||||
}
|
||||
|
||||
// disassembler without having to have a code unit
|
||||
@Override
|
||||
public String getDisassemblyOfBytes(Language language, boolean isBigEndian, long addressOffset,
|
||||
byte[] bytes) throws Exception {
|
||||
|
||||
GdisConfig gdisConfig = new GdisConfig(language, isBigEndian);
|
||||
if (gdisConfig.architecture == UNSUPPORTED || gdisConfig.gdisExecFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String bytesString = converBytesToString(bytes);
|
||||
|
||||
String address = "0x" + Long.toHexString(addressOffset);
|
||||
|
||||
List<GnuDisassembledInstruction> disassembly =
|
||||
runDisassembler(gdisConfig, address, bytesString);
|
||||
|
||||
if (disassembly == null || disassembly.isEmpty() || disassembly.get(0) == null) {
|
||||
return "(bad)";
|
||||
}
|
||||
return disassembly.get(0).toString();
|
||||
}
|
||||
|
||||
private String converBytesToString(byte[] bytes) {
|
||||
String byteString = null;
|
||||
for (byte thisByte : bytes) {
|
||||
String thisByteString = Integer.toHexString(thisByte);
|
||||
if (thisByteString.length() == 1)
|
||||
thisByteString = "0" + thisByteString; // pad single digits
|
||||
if (thisByteString.length() > 2)
|
||||
thisByteString = thisByteString.substring(thisByteString.length() - 2);
|
||||
// append this byte's hex string to the larger word length string
|
||||
byteString = byteString + thisByteString;
|
||||
}
|
||||
|
||||
return byteString;
|
||||
}
|
||||
|
||||
private boolean setupDisassembler(GdisConfig gdisConfig) {
|
||||
|
||||
if (disassemblerProcess != null) {
|
||||
disassemblerProcess.destroy();
|
||||
disassemblerProcess = null;
|
||||
outputWriter = null;
|
||||
buffReader = null;
|
||||
}
|
||||
|
||||
this.currentConfig = gdisConfig;
|
||||
hadFailure = false;
|
||||
|
||||
String endianString = gdisConfig.isBigEndian ? "0x00" : "0x01"; // 0x0 is big, 0x1 is little endian
|
||||
|
||||
// NOTE: valid target must be specified but has no effect on results
|
||||
String cmds[] = { gdisConfig.gdisExecFile.getAbsolutePath(), "pef", gdisConfig.architecture,
|
||||
gdisConfig.machineId, endianString, "0x0",
|
||||
gdisDataDirectory.getAbsolutePath() + File.separator,
|
||||
GNUExternalDisassembler.READ_FROM_STDIN_PARAMETER };
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (String str : cmds) {
|
||||
boolean addQuotes = str.indexOf(' ') >= 0;
|
||||
if (addQuotes) {
|
||||
buf.append('\"');
|
||||
}
|
||||
buf.append(str);
|
||||
if (addQuotes) {
|
||||
buf.append('\"');
|
||||
}
|
||||
buf.append(' ');
|
||||
}
|
||||
Msg.debug(this, "Starting gdis: " + buf.toString());
|
||||
|
||||
try {
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
disassemblerProcess = rt.exec(cmds, null, gdisConfig.gdisExecFile.getParentFile());
|
||||
}
|
||||
catch (IOException e) {
|
||||
buf = new StringBuilder();
|
||||
for (String arg : cmds) {
|
||||
buf.append("\"");
|
||||
buf.append(arg);
|
||||
buf.append("\" ");
|
||||
}
|
||||
Msg.debug(this, "GNU Disassembly setup failed, exec command: " + buf);
|
||||
Msg.showError(this, null, "GNU Disassembler Error",
|
||||
"Disassembler setup execution error: " + e.getMessage(), e);
|
||||
hadFailure = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<GnuDisassembledInstruction> runDisassembler(GdisConfig gdisConfig,
|
||||
String addrString, String bytes) throws IOException {
|
||||
|
||||
// if this is the first time running the disassembler process, or a
|
||||
// parameter has changed (notably, not the address--we pass that in
|
||||
// every time)
|
||||
boolean sameConfig = gdisConfig.equals(currentConfig);
|
||||
if (sameConfig && hadFailure) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (disassemblerProcess == null || !sameConfig) {
|
||||
|
||||
if (!setupDisassembler(gdisConfig))
|
||||
return null;
|
||||
|
||||
outputWriter = new OutputStreamWriter(disassemblerProcess.getOutputStream());
|
||||
|
||||
InputStreamReader inStreamReader =
|
||||
new InputStreamReader(disassemblerProcess.getInputStream());
|
||||
buffReader = new BufferedReader(inStreamReader);
|
||||
|
||||
ExternalStreamHandler errorHandler =
|
||||
new ExternalStreamHandler(disassemblerProcess.getErrorStream());
|
||||
errorHandler.start();
|
||||
}
|
||||
|
||||
if (!disassemblerProcess.isAlive()) {
|
||||
return null; // if process previously died return nothing - quickly
|
||||
}
|
||||
|
||||
String disassemblyRequest = addrString + SEPARATOR_CHARACTER + bytes + SEPARATOR_CHARACTER;
|
||||
try {
|
||||
outputWriter.write(disassemblyRequest);
|
||||
outputWriter.flush();
|
||||
return getDisassembledInstruction();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// force a restart of the disassembler on next call to this function
|
||||
// TODO: Should we not do this to avoid repeated failure and severe slowdown?
|
||||
// User must exit or switch configs/programs to retry after failure
|
||||
//disassemblerProcess.destroy();
|
||||
//disassemblerProcess = null; // assumes process exit
|
||||
Msg.error(this, "Last gdis request failed: " + disassemblyRequest);
|
||||
throw new IOException("gdis execution error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<GnuDisassembledInstruction> getDisassembledInstruction() throws IOException {
|
||||
|
||||
List<GnuDisassembledInstruction> results = new ArrayList<>();
|
||||
String instructionLine;
|
||||
|
||||
boolean error = false;
|
||||
do {
|
||||
instructionLine = buffReader.readLine();
|
||||
if (!error && instructionLine != null && !instructionLine.equals(ENDING_STRING) &&
|
||||
(instructionLine.indexOf(ADDRESS_OUT_OF_BOUNDS) < 0) &&
|
||||
!instructionLine.startsWith("Usage:") && !instructionLine.startsWith("Debug:")) {
|
||||
|
||||
String instructionMetadataLine = buffReader.readLine();
|
||||
if (!instructionMetadataLine.startsWith("Info: ")) {
|
||||
// TODO, throw an "ExternalDisassemblerInterfaceException"
|
||||
// or some such
|
||||
error = true; // still need to consume remainder of input
|
||||
continue;
|
||||
}
|
||||
String[] metadata = instructionMetadataLine.substring("Info: ".length()).split(",");
|
||||
results.add(new GnuDisassembledInstruction(instructionLine.replace('\t', ' '),
|
||||
Integer.parseInt(metadata[0]), "1".equals(metadata[1]),
|
||||
Integer.parseInt(metadata[2]), Integer.parseInt(metadata[3]),
|
||||
Integer.parseInt(metadata[4])));
|
||||
}
|
||||
}
|
||||
while (instructionLine != null && !instructionLine.equals(ENDING_STRING));
|
||||
|
||||
if (!disassemblerProcess.isAlive()) {
|
||||
throw new IOException("GNU disassembler process died unexpectedly.");
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private String getBytes(ByteProvider byteProvider, int size) throws IOException {
|
||||
StringBuffer byteString = new StringBuffer();
|
||||
for (int i = 0; i < size; i++) {
|
||||
byteString.append(formatHexString(byteProvider.readByte(i)));
|
||||
}
|
||||
return byteString.toString();
|
||||
}
|
||||
|
||||
private String getBytes(MemBuffer mem, int size) {
|
||||
StringBuffer byteString = new StringBuffer();
|
||||
for (int i = 0; i < size; i++) {
|
||||
try {
|
||||
byteString.append(formatHexString(mem.getByte(i)));
|
||||
}
|
||||
catch (AddressOutOfBoundsException e) {
|
||||
break;
|
||||
}
|
||||
catch (MemoryAccessException e) {
|
||||
if (i > 0) {
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return byteString.toString();
|
||||
}
|
||||
|
||||
private String formatHexString(byte byteToFix) {
|
||||
String byteString = "";
|
||||
String singleByte = "";
|
||||
if (byteToFix < 0) {
|
||||
singleByte = Integer.toHexString(byteToFix + 256);
|
||||
}
|
||||
else {
|
||||
singleByte = Integer.toHexString(byteToFix);
|
||||
}
|
||||
if (singleByte.length() == 1) {
|
||||
byteString += '0';
|
||||
}
|
||||
byteString += singleByte;
|
||||
return byteString;
|
||||
}
|
||||
|
||||
private static synchronized void initialize() throws Exception {
|
||||
if (languageGdisMap != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
languageGdisMap = new HashMap<>();
|
||||
|
||||
try {
|
||||
// sample elf files located in data directory
|
||||
ResourceFile dataDir =
|
||||
Application.getModuleSubDirectory(GNU_DISASSEMBLER_MODULE_NAME, "data");
|
||||
gdisDataDirectory = dataDir.getFile(false);
|
||||
defaultGdisExecFile = Application.getOSFile(GNU_DISASSEMBLER_MODULE_NAME, GDIS_EXE);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (gdisDataDirectory == null) {
|
||||
Msg.warn(GNUExternalDisassembler.class,
|
||||
"Use of External GNU Disassembler requires installation of extension: " +
|
||||
GNU_DISASSEMBLER_MODULE_NAME);
|
||||
}
|
||||
|
||||
initializeMaps();
|
||||
|
||||
if (defaultGdisExecFile == null || !defaultGdisExecFile.canExecute()) {
|
||||
Msg.warn(GNUExternalDisassembler.class,
|
||||
"External GNU Disassembler not found: " + GDIS_EXE);
|
||||
defaultGdisExecFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all language maps defined by any module. Any alternate external disassembler
|
||||
* executable will be looked for within the os directory of the contributing module or
|
||||
* within the gdis module
|
||||
* @throws Exception
|
||||
*/
|
||||
private static void initializeMaps() {
|
||||
for (ResourceFile file : Application.findFilesByExtensionInApplication(".txt")) {
|
||||
if (MAP_FILENAME.equals(file.getName())) {
|
||||
initializeMap(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeMap(ResourceFile mapFile) {
|
||||
|
||||
ResourceFile moduleForResourceFile = Application.getModuleContainingResourceFile(mapFile);
|
||||
if (moduleForResourceFile == null) {
|
||||
Msg.error(GNUExternalDisassembler.class,
|
||||
"Failed to identify module containing file: " + mapFile);
|
||||
return;
|
||||
}
|
||||
|
||||
Reader mapFileReader = null;
|
||||
try {
|
||||
mapFileReader = new InputStreamReader(mapFile.getInputStream());
|
||||
BufferedReader reader = new BufferedReader(mapFileReader);
|
||||
String line = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("//") || line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String[] parts = line.split("#");
|
||||
if (parts.length > 1) {
|
||||
|
||||
//System.out.println("found: " + parts[0] + " . " + parts[1]);
|
||||
|
||||
// TODO: should probably store exe module/name in map and defer search
|
||||
// until GdisConfig is created. This will allow us to complain about a
|
||||
// missing exe when it is needed/used.
|
||||
|
||||
String gdisExe = parts[1];
|
||||
if (Platform.CURRENT_PLATFORM.getOperatingSystem() == OperatingSystem.WINDOWS) {
|
||||
gdisExe = gdisExe + ".exe";
|
||||
}
|
||||
try {
|
||||
File customGdisExecFile;
|
||||
try {
|
||||
customGdisExecFile =
|
||||
Application.getOSFile(moduleForResourceFile.getName(), gdisExe);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
customGdisExecFile = Application.getOSFile(gdisExe);
|
||||
}
|
||||
languageGdisMap.put(parts[0], customGdisExecFile);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
Msg.error(GNUExternalDisassembler.class,
|
||||
"External disassembler not found (" + parts[0] + "): " + gdisExe);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(GNUExternalDisassembler.class,
|
||||
"Error reading from language mapping file: " + mapFile, e);
|
||||
}
|
||||
finally {
|
||||
if (mapFileReader != null) {
|
||||
try {
|
||||
mapFileReader.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// we tried
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/* ###
|
||||
* 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.disassemble;
|
||||
|
||||
/**
|
||||
* Holds the disassembled string of an instruction and the extra information
|
||||
* (type, number of bytes disassembled to produce instruction, etc.) of bytes
|
||||
* disassembled by the GNU disassembler.
|
||||
*/
|
||||
public class GnuDisassembledInstruction {
|
||||
|
||||
private final String instruction;
|
||||
private final int bytesInInstruction;
|
||||
|
||||
private final int branchDelayInstructions;
|
||||
private final int dataSize;
|
||||
private final DIS_INSN_TYPE instructionType;
|
||||
private final boolean isValid;
|
||||
|
||||
// from GNU binutils include/dis-asm.h
|
||||
enum DIS_INSN_TYPE {
|
||||
dis_noninsn, /* Not a valid instruction. */
|
||||
dis_nonbranch, /* Not a branch instruction. */
|
||||
dis_branch, /* Unconditional branch. */
|
||||
dis_condbranch, /* Conditional branch. */
|
||||
dis_jsr, /* Jump to subroutine. */
|
||||
dis_condjsr, /* Conditional jump to subroutine. */
|
||||
dis_dref, /* Data reference instruction. */
|
||||
dis_dref2 /* Two data references in instruction. */
|
||||
}
|
||||
|
||||
public GnuDisassembledInstruction(String instructionLine, int bytesInInstruction,
|
||||
boolean isValid, int branchDelayInstructions, int dataSize, int disInsnTypeOrdinal) {
|
||||
|
||||
this.instruction = instructionLine.trim();
|
||||
this.bytesInInstruction = bytesInInstruction;
|
||||
|
||||
this.isValid = isValid;
|
||||
this.branchDelayInstructions = branchDelayInstructions;
|
||||
this.dataSize = dataSize;
|
||||
this.instructionType = DIS_INSN_TYPE.values()[disInsnTypeOrdinal];
|
||||
}
|
||||
|
||||
public int getNumberOfBytesInInstruction() {
|
||||
return bytesInInstruction;
|
||||
}
|
||||
|
||||
public DIS_INSN_TYPE getInstructionType() {
|
||||
return isValid ? instructionType : null;
|
||||
}
|
||||
|
||||
public int getBranchDelayInstructions() {
|
||||
return isValid ? branchDelayInstructions : null;
|
||||
}
|
||||
|
||||
public int getDataSize() {
|
||||
return isValid ? dataSize : null;
|
||||
}
|
||||
|
||||
public String getInstruction() {
|
||||
return instruction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return instruction;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user