344 lines
8.7 KiB
Java
344 lines
8.7 KiB
Java
/* ###
|
|
* 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 java.io.File;
|
|
import java.io.PrintWriter;
|
|
import java.math.BigInteger;
|
|
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.program.disassemble.Disassembler;
|
|
import ghidra.program.model.address.*;
|
|
import ghidra.program.model.lang.*;
|
|
import ghidra.program.model.listing.*;
|
|
import ghidra.program.model.mem.*;
|
|
import ghidra.util.Msg;
|
|
import ghidra.util.StringUtilities;
|
|
|
|
public class RangeDisassemblerScript extends GhidraScript {
|
|
|
|
/**
|
|
* Required properties:
|
|
* min - minimum disassembly address or block name
|
|
* max - maximum disassembly address (not used if block name used for min)
|
|
* out - disassembly output file path
|
|
*
|
|
* Optional:
|
|
* set.<context-reg-name> - optional starting context value
|
|
* set.range.<context-reg-name> - optional context value over range
|
|
*
|
|
*/
|
|
|
|
private AddressSetView region;
|
|
private File outFile;
|
|
|
|
@Override
|
|
protected void run() throws Exception {
|
|
|
|
if (!isRunningHeadless()) {
|
|
printerr("Script intended for Headless use only");
|
|
return;
|
|
}
|
|
|
|
if (currentProgram == null) {
|
|
printerr("Requires open program");
|
|
return;
|
|
}
|
|
|
|
currentProgram.setTemporary(true);
|
|
|
|
if (!processParameters()) {
|
|
return;
|
|
}
|
|
|
|
Msg.info(this, "Disassmbly Output File: " + outFile.getAbsolutePath());
|
|
|
|
PrintWriter out = new PrintWriter(outFile);
|
|
try {
|
|
disassembleRegion(out);
|
|
}
|
|
finally {
|
|
out.close();
|
|
}
|
|
|
|
}
|
|
|
|
private void disassembleRegion(PrintWriter out) {
|
|
|
|
int alignment = currentProgram.getLanguage().getInstructionAlignment();
|
|
|
|
Disassembler disassembler =
|
|
Disassembler.getDisassembler(currentProgram, false, false, false, monitor, null);
|
|
|
|
DumbMemBufferImpl memBuffer =
|
|
new DumbMemBufferImpl(currentProgram.getMemory(), region.getMinAddress());
|
|
|
|
ParallelInstructionLanguageHelper helper =
|
|
currentProgram.getLanguage().getParallelInstructionHelper();
|
|
|
|
int cnt = 0;
|
|
|
|
for (AddressRange range : region.getAddressRanges(true)) {
|
|
|
|
Address nextAddr = range.getMinAddress();
|
|
|
|
InstructionBlock lastPseudoInstructionBlock = null;
|
|
|
|
while (nextAddr != null && nextAddr.compareTo(range.getMaxAddress()) <= 0) {
|
|
|
|
if ((nextAddr.getOffset() % alignment) != 0) {
|
|
nextAddr = nextAddr.next();
|
|
continue;
|
|
}
|
|
|
|
Instruction pseudoInstruction = null;
|
|
InstructionError error = null;
|
|
|
|
if (lastPseudoInstructionBlock != null) {
|
|
pseudoInstruction = lastPseudoInstructionBlock.getInstructionAt(nextAddr);
|
|
if (pseudoInstruction == null) {
|
|
error = lastPseudoInstructionBlock.getInstructionConflict();
|
|
if (error != null && !nextAddr.equals(error.getInstructionAddress())) {
|
|
error = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pseudoInstruction == null && error == null) {
|
|
memBuffer.setPosition(nextAddr);
|
|
lastPseudoInstructionBlock =
|
|
disassembler.pseudoDisassembleBlock(memBuffer, null, 1);
|
|
if (lastPseudoInstructionBlock != null) {
|
|
pseudoInstruction = lastPseudoInstructionBlock.getInstructionAt(nextAddr);
|
|
if (pseudoInstruction == null) {
|
|
error = lastPseudoInstructionBlock.getInstructionConflict();
|
|
if (error != null && !nextAddr.equals(error.getInstructionAddress())) {
|
|
error = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
if (pseudoInstruction != null) {
|
|
out.print(nextAddr.toString());
|
|
out.print(" ");
|
|
out.print(formatBytes(pseudoInstruction.getBytes()));
|
|
out.print(" ");
|
|
|
|
String prefix = null;
|
|
if (helper != null) {
|
|
prefix = helper.getMnemonicPrefix(pseudoInstruction);
|
|
}
|
|
if (prefix == null) {
|
|
prefix = " ";
|
|
}
|
|
else {
|
|
prefix = StringUtilities.pad(prefix, ' ', -4);
|
|
}
|
|
out.println(prefix);
|
|
|
|
out.println(pseudoInstruction.toString());
|
|
|
|
nextAddr = pseudoInstruction.getMaxAddress().next();
|
|
}
|
|
else {
|
|
out.print(nextAddr.toString());
|
|
out.print(" ");
|
|
out.print(formatBytes(new byte[] { memBuffer.getByte(0) }));
|
|
out.print(" ERROR: ");
|
|
out.println(error.getConflictMessage());
|
|
|
|
nextAddr = nextAddr.add(alignment);
|
|
}
|
|
|
|
if ((++cnt % 20000) == 0) {
|
|
Msg.info(this, "Disassembled: " + cnt);
|
|
}
|
|
}
|
|
catch (AddressOutOfBoundsException e) {
|
|
nextAddr = null; // next range
|
|
}
|
|
catch (MemoryAccessException e) {
|
|
out.print(nextAddr.toString());
|
|
out.println(" ERROR: " + e.getMessage());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Msg.info(this, "Disassembled: " + cnt + " instructions to " + outFile);
|
|
}
|
|
|
|
private static final int MAX_BYTES = 4;
|
|
|
|
private String formatBytes(byte[] bytes) {
|
|
|
|
int totalWidth = (3 * 4) + 2;
|
|
StringBuilder buf = new StringBuilder();
|
|
|
|
for (int i = 0; i < bytes.length && i < MAX_BYTES; i++) {
|
|
if (i != 0) {
|
|
buf.append(' ');
|
|
}
|
|
int v = bytes[i] & 0xff;
|
|
if (v < 0x10) {
|
|
buf.append('0');
|
|
}
|
|
buf.append(Integer.toHexString(v));
|
|
}
|
|
if (bytes.length > MAX_BYTES) {
|
|
buf.append('.');
|
|
}
|
|
|
|
return StringUtilities.pad(buf.toString(), ' ', -totalWidth);
|
|
}
|
|
|
|
private boolean processParameters() {
|
|
|
|
Address minAddr = null;
|
|
Address maxAddr = null;
|
|
|
|
AddressFactory addrFactory = currentProgram.getAddressFactory();
|
|
|
|
boolean missingParam = false;
|
|
|
|
String minAddrStr = propertiesFileParams.getValue("min");
|
|
if (minAddrStr == null) {
|
|
printerr("Missing required minimum address property: min");
|
|
missingParam = true;
|
|
}
|
|
else {
|
|
minAddr = addrFactory.getAddress(minAddrStr);
|
|
if (minAddr == null) {
|
|
|
|
// Try as block name
|
|
MemoryBlock block = currentProgram.getMemory().getBlock(minAddrStr);
|
|
if (block == null) {
|
|
printerr("Failed to parse min address/block: " + minAddrStr);
|
|
missingParam = true;
|
|
}
|
|
else {
|
|
minAddr = block.getStart();
|
|
maxAddr = block.getEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (maxAddr == null) {
|
|
String maxAddrStr = propertiesFileParams.getValue("max");
|
|
if (minAddrStr == null) {
|
|
printerr("Missing required maximum address property: max");
|
|
missingParam = true;
|
|
}
|
|
else {
|
|
maxAddr = addrFactory.getAddress(maxAddrStr);
|
|
if (maxAddr == null) {
|
|
printerr("Failed to parse max address: " + maxAddrStr);
|
|
missingParam = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
String filepath = propertiesFileParams.getValue("out");
|
|
if (minAddrStr == null) {
|
|
printerr("Missing required output file path property: out");
|
|
missingParam = true;
|
|
}
|
|
else {
|
|
outFile = new File(filepath);
|
|
File parentDir = outFile.getParentFile();
|
|
if (!parentDir.exists()) {
|
|
printerr("Output directory not found: " + parentDir.getAbsolutePath());
|
|
missingParam = true;
|
|
}
|
|
}
|
|
|
|
if (missingParam) {
|
|
return false;
|
|
}
|
|
|
|
if (!minAddr.hasSameAddressSpace(maxAddr) || minAddr.compareTo(maxAddr) > 0) {
|
|
printerr("Invalid address range: " + minAddr + " - " + maxAddr);
|
|
return false;
|
|
}
|
|
|
|
region =
|
|
currentProgram.getMemory().getLoadedAndInitializedAddressSet().intersectRange(minAddr, maxAddr);
|
|
|
|
if (region.isEmpty()) {
|
|
printerr("Address range does not intersect initiailized memory: " + minAddr + " - " +
|
|
maxAddr);
|
|
return false;
|
|
}
|
|
|
|
ProgramContext programContext = currentProgram.getProgramContext();
|
|
|
|
boolean badReg;
|
|
try {
|
|
badReg = false;
|
|
for (String key : propertiesFileParams.keySet()) {
|
|
boolean setRange = false;
|
|
int index;
|
|
if (key.startsWith("set.range.")) {
|
|
setRange = true;
|
|
index = 10;
|
|
}
|
|
else if (key.startsWith("set.")) {
|
|
index = 4;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
String regName = key.substring(index);
|
|
Register reg = currentProgram.getRegister(regName);
|
|
if (reg == null || !reg.isProcessorContext()) {
|
|
printerr("Processor context register field not found: " + regName);
|
|
badReg = true;
|
|
continue;
|
|
}
|
|
|
|
BigInteger value;
|
|
String valueStr = propertiesFileParams.getValue(key);
|
|
try {
|
|
value = BigInteger.valueOf(Long.parseLong(valueStr));
|
|
}
|
|
catch (NumberFormatException e) {
|
|
printerr("Invalid register set value: " + valueStr);
|
|
badReg = true;
|
|
continue;
|
|
}
|
|
|
|
if (setRange) {
|
|
programContext.setValue(reg, minAddr, maxAddr, value);
|
|
}
|
|
else {
|
|
programContext.setValue(reg, minAddr, minAddr, value);
|
|
}
|
|
|
|
}
|
|
}
|
|
catch (ContextChangeException e) {
|
|
printerr("Program must be clear of all code units!");
|
|
printerr(e.getMessage());
|
|
return false;
|
|
}
|
|
|
|
if (badReg) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|