mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Emulator - added simplified program emulation API via EmulatorHelper
This commit is contained in:
parent
8843a70947
commit
7c5523362c
23 changed files with 3112 additions and 27 deletions
|
@ -0,0 +1,207 @@
|
||||||
|
/* ###
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// An example script demonstrating the ability to emulate a specific portion of code within
|
||||||
|
// a disassembled program to extract return values of interest (deobfuscated data in this case)
|
||||||
|
// and generate program listing comments.
|
||||||
|
// This script emulates the "main" function within the deobExample program
|
||||||
|
// (see docs/GhidraClass/ExerciseFiles/Emulation/Source) built with gcc for x86-64.
|
||||||
|
// The program's "data" array contains simple obfuscated data and has a function "deobfuscate"
|
||||||
|
// which is called for each piece of obfuscated data. The "main" function loops through all
|
||||||
|
// the data and deobfuscates each one invoking the "use_string" function for each deobfuscated
|
||||||
|
// data. Breakpoints are placed on the call (and just after the call)
|
||||||
|
// to the function "deobfuscate" so that the various return values can be recorded with a comment
|
||||||
|
// placed just after the call.
|
||||||
|
//@category Examples.Emulation
|
||||||
|
import ghidra.app.emulator.EmulatorHelper;
|
||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.app.util.opinion.ElfLoader;
|
||||||
|
import ghidra.pcode.emulate.EmulateExecutionState;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.listing.Instruction;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.symbol.*;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.exception.NotFoundException;
|
||||||
|
|
||||||
|
public class EmuX86DeobfuscateExampleScript extends GhidraScript {
|
||||||
|
|
||||||
|
private static String PROGRAM_NAME = "deobExample";
|
||||||
|
|
||||||
|
private EmulatorHelper emuHelper;
|
||||||
|
|
||||||
|
// Important breakpoint locations
|
||||||
|
private Address deobfuscateCall;
|
||||||
|
private Address deobfuscateReturn;
|
||||||
|
|
||||||
|
// Function locations
|
||||||
|
private Address mainFunctionEntry; // start of emulation address
|
||||||
|
|
||||||
|
// Address used as final return location
|
||||||
|
// A breakpoint will be set here so we can determine when function execution
|
||||||
|
// has completed.
|
||||||
|
private static final long CONTROLLED_RETURN_OFFSET = 0;
|
||||||
|
private Address controlledReturnAddr; // end of emulation address
|
||||||
|
|
||||||
|
// First argument passed to deobfuscate function on last call (used for comment generation)
|
||||||
|
private long lastDeobfuscateArg0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void run() throws Exception {
|
||||||
|
|
||||||
|
String format =
|
||||||
|
currentProgram.getOptions(Program.PROGRAM_INFO).getString("Executable Format", null);
|
||||||
|
|
||||||
|
if (currentProgram == null || !currentProgram.getName().startsWith(PROGRAM_NAME) ||
|
||||||
|
!"x86:LE:64:default".equals(currentProgram.getLanguageID().toString()) ||
|
||||||
|
!ElfLoader.ELF_NAME.equals(format)) {
|
||||||
|
|
||||||
|
printerr(
|
||||||
|
"This emulation example script is specifically intended to be executed against the\n" +
|
||||||
|
PROGRAM_NAME +
|
||||||
|
" program whose source is contained within the GhidraClass exercise files\n" +
|
||||||
|
"(see docs/GhidraClass/ExerciseFiles/Emulation/" + PROGRAM_NAME + ".c).\n" +
|
||||||
|
"This program should be compiled using gcc for x86 64-bit, imported into your project, \n" +
|
||||||
|
"analyzed and open as the active program before running ths script.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify function to be emulated
|
||||||
|
mainFunctionEntry = getSymbolAddress("main");
|
||||||
|
|
||||||
|
// Obtain entry instruction in order to establish initial processor context
|
||||||
|
Instruction entryInstr = getInstructionAt(mainFunctionEntry);
|
||||||
|
if (entryInstr == null) {
|
||||||
|
printerr("Instruction not found at main entry point: " + mainFunctionEntry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify important symbol addresses
|
||||||
|
// NOTE: If the sample is recompiled the following addresses may need to be adjusted
|
||||||
|
Instruction callSite = getCalledFromInstruction("deobfuscate");
|
||||||
|
if (callSite == null) {
|
||||||
|
printerr("Instruction not found at call site for: deobfuscate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deobfuscateCall = callSite.getAddress();
|
||||||
|
deobfuscateReturn = callSite.getFallThrough(); // instruction address immediately after deobfuscate call
|
||||||
|
|
||||||
|
// Remove prior pre-comment
|
||||||
|
setPreComment(deobfuscateReturn, null);
|
||||||
|
|
||||||
|
// Establish emulation helper
|
||||||
|
emuHelper = new EmulatorHelper(currentProgram);
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Initialize stack pointer (not used by this example)
|
||||||
|
long stackOffset =
|
||||||
|
(entryInstr.getAddress().getAddressSpace().getMaxAddress().getOffset() >>> 1) -
|
||||||
|
0x7fff;
|
||||||
|
emuHelper.writeRegister(emuHelper.getStackPointerRegister(), stackOffset);
|
||||||
|
|
||||||
|
// Setup breakpoints
|
||||||
|
emuHelper.setBreakpoint(deobfuscateCall);
|
||||||
|
emuHelper.setBreakpoint(deobfuscateReturn);
|
||||||
|
|
||||||
|
// Set controlled return location so we can identify return from emulated function
|
||||||
|
controlledReturnAddr = getAddress(CONTROLLED_RETURN_OFFSET);
|
||||||
|
emuHelper.writeStackValue(0, 8, CONTROLLED_RETURN_OFFSET);
|
||||||
|
emuHelper.setBreakpoint(controlledReturnAddr);
|
||||||
|
|
||||||
|
Msg.debug(this, "EMU starting at " + mainFunctionEntry);
|
||||||
|
|
||||||
|
// Execution loop until return from function or error occurs
|
||||||
|
while (!monitor.isCancelled()) {
|
||||||
|
boolean success =
|
||||||
|
(emuHelper.getEmulateExecutionState() == EmulateExecutionState.BREAKPOINT)
|
||||||
|
? emuHelper.run(monitor)
|
||||||
|
: emuHelper.run(mainFunctionEntry, entryInstr, monitor);
|
||||||
|
Address executionAddress = emuHelper.getExecutionAddress();
|
||||||
|
if (monitor.isCancelled()) {
|
||||||
|
println("Emulation cancelled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (executionAddress.equals(controlledReturnAddr)) {
|
||||||
|
println("Returned from function");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
String lastError = emuHelper.getLastError();
|
||||||
|
printerr("Emulation Error: " + lastError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processBreakpoint(executionAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// cleanup resources and release hold on currentProgram
|
||||||
|
emuHelper.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Address getAddress(long offset) {
|
||||||
|
return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform processing for the various breakpoints.
|
||||||
|
* @param addr current execute address where emulation has been suspended
|
||||||
|
* @throws Exception if an error occurs
|
||||||
|
*/
|
||||||
|
private void processBreakpoint(Address addr) throws Exception {
|
||||||
|
|
||||||
|
if (addr.equals(deobfuscateCall)) {
|
||||||
|
lastDeobfuscateArg0 = emuHelper.readRegister("RDI").longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (addr.equals(deobfuscateReturn)) {
|
||||||
|
long deobfuscateReturnValue = emuHelper.readRegister("RAX").longValue();
|
||||||
|
String str = "deobfuscate(src=0x" + Long.toHexString(lastDeobfuscateArg0) + ") -> \"" +
|
||||||
|
emuHelper.readNullTerminatedString(getAddress(deobfuscateReturnValue), 32) + "\"";
|
||||||
|
String comment = getPreComment(deobfuscateReturn);
|
||||||
|
if (comment == null) {
|
||||||
|
comment = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
comment += "\n";
|
||||||
|
}
|
||||||
|
comment += str;
|
||||||
|
println("Updated pre-comment at " + deobfuscateReturn);
|
||||||
|
setPreComment(deobfuscateReturn, comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Instruction getCalledFromInstruction(String functionName) {
|
||||||
|
Symbol s = SymbolUtilities.getExpectedLabelOrFunctionSymbol(currentProgram, functionName,
|
||||||
|
m -> printerr(m));
|
||||||
|
for (Reference ref : s.getReferences(monitor)) {
|
||||||
|
if (ref.getReferenceType().isCall()) {
|
||||||
|
return currentProgram.getListing().getInstructionAt(ref.getFromAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Address getSymbolAddress(String symbolName) throws NotFoundException {
|
||||||
|
Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(currentProgram, symbolName,
|
||||||
|
err -> Msg.error(this, err));
|
||||||
|
if (symbol != null) {
|
||||||
|
return symbol.getAddress();
|
||||||
|
}
|
||||||
|
throw new NotFoundException("Failed to locate label: " + symbolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,288 @@
|
||||||
|
/* ###
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// An example script demonstrating the ability to emulate a specific portion of code within
|
||||||
|
// a disassembled program to dump data of interest (deobfuscated data in this case).
|
||||||
|
// This script emulates the "main" function within the deobHookExampleX86 program
|
||||||
|
// (see docs/GhidraClass/ExerciseFiles/Emulation/Source) built with gcc for x86-64.
|
||||||
|
// The program's "data" array contains simple obfuscated data and has a function "deobfuscate"
|
||||||
|
// which is called for each piece of ofuscated data. The "main" function loops through all
|
||||||
|
// the data and deobfuscates each one invoking the "use_string" function for each deobfuscated
|
||||||
|
// data. This script hooks the functions "malloc", "free" and "use_string" where the later
|
||||||
|
// simply prints the deobfuscated string passed as an argument.
|
||||||
|
//@category Examples.Emulation
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ghidra.app.emulator.EmulatorHelper;
|
||||||
|
import ghidra.app.script.GhidraScript;
|
||||||
|
import ghidra.app.util.opinion.ElfLoader;
|
||||||
|
import ghidra.pcode.emulate.EmulateExecutionState;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.lang.InsufficientBytesException;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.symbol.*;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.exception.NotFoundException;
|
||||||
|
|
||||||
|
public class EmuX86GccDeobfuscateHookExampleScript extends GhidraScript {
|
||||||
|
|
||||||
|
private static String PROGRAM_NAME = "deobHookExample";
|
||||||
|
|
||||||
|
// Heap allocation area
|
||||||
|
private static final int MALLOC_REGION_SIZE = 0x1000;
|
||||||
|
|
||||||
|
// Address used as final return location
|
||||||
|
private static final long CONTROLLED_RETURN_OFFSET = 0;
|
||||||
|
|
||||||
|
private EmulatorHelper emuHelper;
|
||||||
|
private SimpleMallocMgr mallocMgr;
|
||||||
|
|
||||||
|
// Important breakpoint locations for hooking behavior not contained with binary (e.g., dynamic library)
|
||||||
|
private Address mallocEntry;
|
||||||
|
private Address freeEntry;
|
||||||
|
private Address strlenEntry;
|
||||||
|
private Address useStringEntry;
|
||||||
|
|
||||||
|
// Function locations
|
||||||
|
private Address mainFunctionEntry; // start of emulation
|
||||||
|
private Address controlledReturnAddr; // end of emulation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void run() throws Exception {
|
||||||
|
|
||||||
|
String format =
|
||||||
|
currentProgram.getOptions(Program.PROGRAM_INFO).getString("Executable Format", null);
|
||||||
|
|
||||||
|
if (currentProgram == null || !currentProgram.getName().startsWith(PROGRAM_NAME) ||
|
||||||
|
!"x86:LE:64:default".equals(currentProgram.getLanguageID().toString()) ||
|
||||||
|
!ElfLoader.ELF_NAME.equals(format)) {
|
||||||
|
|
||||||
|
printerr(
|
||||||
|
"This emulation example script is specifically intended to be executed against the\n" +
|
||||||
|
PROGRAM_NAME +
|
||||||
|
" program whose source is contained within the GhidraClass exercise files\n" +
|
||||||
|
"(see docs/GhidraClass/ExerciseFiles/Emulation/" + PROGRAM_NAME + ".c).\n" +
|
||||||
|
"This program should be compiled using gcc for x86 64-bit, imported into your project, \n" +
|
||||||
|
"analyzed and open as the active program before running ths script.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify function be emulated
|
||||||
|
mainFunctionEntry = getSymbolAddress("main");
|
||||||
|
useStringEntry = getSymbolAddress("use_string");
|
||||||
|
|
||||||
|
// Identify important symbol addresses
|
||||||
|
mallocEntry = getExternalThunkAddress("malloc");
|
||||||
|
freeEntry = getExternalThunkAddress("free");
|
||||||
|
strlenEntry = getExternalThunkAddress("strlen");
|
||||||
|
|
||||||
|
// Establish emulation helper
|
||||||
|
emuHelper = new EmulatorHelper(currentProgram);
|
||||||
|
try {
|
||||||
|
// Initialize stack pointer (not used by this example)
|
||||||
|
long stackOffset =
|
||||||
|
(mainFunctionEntry.getAddressSpace().getMaxAddress().getOffset() >>> 1) - 0x7fff;
|
||||||
|
emuHelper.writeRegister(emuHelper.getStackPointerRegister(), stackOffset);
|
||||||
|
|
||||||
|
// Establish simple malloc memory manager with memory region spaced relative to stack pointer
|
||||||
|
mallocMgr = new SimpleMallocMgr(getAddress(stackOffset - 0x10000), MALLOC_REGION_SIZE);
|
||||||
|
|
||||||
|
// Setup hook breakpoints
|
||||||
|
emuHelper.setBreakpoint(mallocEntry);
|
||||||
|
emuHelper.setBreakpoint(freeEntry);
|
||||||
|
emuHelper.setBreakpoint(strlenEntry);
|
||||||
|
emuHelper.setBreakpoint(useStringEntry);
|
||||||
|
|
||||||
|
// Set controlled return location so we can identify return from emulated function
|
||||||
|
controlledReturnAddr = getAddress(CONTROLLED_RETURN_OFFSET);
|
||||||
|
emuHelper.writeStackValue(0, 8, CONTROLLED_RETURN_OFFSET);
|
||||||
|
emuHelper.setBreakpoint(controlledReturnAddr);
|
||||||
|
|
||||||
|
// This example directly manipulates the PC register to facilitate hooking
|
||||||
|
// which must alter the PC during a breakpoint, and optional stepping which does not
|
||||||
|
// permit an initial address to be specified.
|
||||||
|
emuHelper.writeRegister(emuHelper.getPCRegister(), mainFunctionEntry.getOffset());
|
||||||
|
Msg.debug(this, "EMU starting at " + emuHelper.getExecutionAddress());
|
||||||
|
|
||||||
|
// Execution loop until return from function or error occurs
|
||||||
|
while (!monitor.isCancelled()) {
|
||||||
|
// Use stepping if needed for troubleshooting - although it runs much slower
|
||||||
|
//boolean success = emuHelper.step();
|
||||||
|
boolean success = emuHelper.run(monitor);
|
||||||
|
Address executionAddress = emuHelper.getExecutionAddress();
|
||||||
|
if (monitor.isCancelled()) {
|
||||||
|
println("Emulation cancelled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (executionAddress.equals(controlledReturnAddr)) {
|
||||||
|
println("Returned from function");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
String lastError = emuHelper.getLastError();
|
||||||
|
printerr("Emulation Error: " + lastError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processBreakpoint(executionAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
// cleanup resources and release hold on currentProgram
|
||||||
|
emuHelper.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Address getAddress(long offset) {
|
||||||
|
return currentProgram.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform processing for the various hook points where breakpoints have been set.
|
||||||
|
* @param addr current execute address where emulation has been suspended
|
||||||
|
* @throws Exception if an error occurs
|
||||||
|
*/
|
||||||
|
private void processBreakpoint(Address addr) throws Exception {
|
||||||
|
|
||||||
|
// malloc hook
|
||||||
|
if (addr.equals(mallocEntry)) {
|
||||||
|
int size = emuHelper.readRegister("RDI").intValue();
|
||||||
|
Address memAddr = mallocMgr.malloc(size);
|
||||||
|
emuHelper.writeRegister("RAX", memAddr.getOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
// free hook
|
||||||
|
else if (addr.equals(freeEntry)) {
|
||||||
|
Address freeAddr = getAddress(emuHelper.readRegister("RDI").longValue());
|
||||||
|
mallocMgr.free(freeAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// strlen hook
|
||||||
|
else if (addr.equals(strlenEntry)) {
|
||||||
|
Address ptr = getAddress(emuHelper.readRegister("RDI").longValue());
|
||||||
|
int len = 0;
|
||||||
|
while (emuHelper.readMemoryByte(ptr) != 0) {
|
||||||
|
++len;
|
||||||
|
ptr = ptr.next();
|
||||||
|
}
|
||||||
|
emuHelper.writeRegister("RAX", len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use_string hook - print string
|
||||||
|
else if (addr.equals(useStringEntry)) {
|
||||||
|
Address stringAddr = getAddress(emuHelper.readRegister("RDI").longValue());
|
||||||
|
String str = emuHelper.readNullTerminatedString(stringAddr, 32);
|
||||||
|
println("use_string: " + str); // output string argument to consoles
|
||||||
|
}
|
||||||
|
|
||||||
|
// unexpected
|
||||||
|
else {
|
||||||
|
if (emuHelper.getEmulateExecutionState() != EmulateExecutionState.BREAKPOINT) {
|
||||||
|
// assume we are stepping and simply return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new NotFoundException("Unhandled breakpoint at " + addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// force early return
|
||||||
|
long returnOffset = emuHelper.readStackValue(0, 8, false).longValue();
|
||||||
|
|
||||||
|
emuHelper.writeRegister(emuHelper.getPCRegister(), returnOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the thunk function corresponding to an external function. Such thunks
|
||||||
|
* should reside within the EXTERNAL block. (Note: this is specific to the ELF import)
|
||||||
|
* @param symbolName external function name
|
||||||
|
* @return address of thunk function which corresponds to an external function
|
||||||
|
* @throws NotFoundException if thunk not found
|
||||||
|
*/
|
||||||
|
private Address getExternalThunkAddress(String symbolName) throws NotFoundException {
|
||||||
|
Symbol externalSymbol = currentProgram.getSymbolTable().getExternalSymbol(symbolName);
|
||||||
|
if (externalSymbol != null && externalSymbol.getSymbolType() == SymbolType.FUNCTION) {
|
||||||
|
Function f = (Function) externalSymbol.getObject();
|
||||||
|
Address[] thunkAddrs = f.getFunctionThunkAddresses();
|
||||||
|
if (thunkAddrs.length == 1) {
|
||||||
|
return thunkAddrs[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NotFoundException("Failed to locate label: " + symbolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the global namespace symbol address which corresponds to the specified name.
|
||||||
|
* @param symbolName global symbol name
|
||||||
|
* @return symbol address
|
||||||
|
* @throws NotFoundException if symbol not found
|
||||||
|
*/
|
||||||
|
private Address getSymbolAddress(String symbolName) throws NotFoundException {
|
||||||
|
Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(currentProgram, symbolName,
|
||||||
|
err -> Msg.error(this, err));
|
||||||
|
if (symbol != null) {
|
||||||
|
return symbol.getAddress();
|
||||||
|
}
|
||||||
|
throw new NotFoundException("Failed to locate label: " + symbolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>SimpleMallocMgr</code> provides a simple malloc memory manager to be used by the
|
||||||
|
* malloc/free hooked implementations.
|
||||||
|
*/
|
||||||
|
private class SimpleMallocMgr {
|
||||||
|
|
||||||
|
private AddressSet allocSet;
|
||||||
|
private Map<Address, AddressRange> mallocMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>SimpleMallocMgr</code> constructor.
|
||||||
|
* @param rangeStart start of the free malloc region (i.e., Heap) which has been
|
||||||
|
* deemed a safe
|
||||||
|
* @param byteSize
|
||||||
|
* @throws AddressOverflowException
|
||||||
|
*/
|
||||||
|
SimpleMallocMgr(Address rangeStart, int byteSize) throws AddressOverflowException {
|
||||||
|
allocSet = new AddressSet(
|
||||||
|
new AddressRangeImpl(rangeStart, rangeStart.addNoWrap(byteSize - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized Address malloc(int byteLength) throws InsufficientBytesException {
|
||||||
|
if (byteLength <= 0) {
|
||||||
|
throw new IllegalArgumentException("malloc request for " + byteLength);
|
||||||
|
}
|
||||||
|
for (AddressRange range : allocSet.getAddressRanges()) {
|
||||||
|
if (range.getLength() >= byteLength) {
|
||||||
|
AddressRange mallocRange = new AddressRangeImpl(range.getMinAddress(),
|
||||||
|
range.getMinAddress().add(byteLength - 1));
|
||||||
|
mallocMap.put(mallocRange.getMinAddress(), mallocRange);
|
||||||
|
allocSet.delete(mallocRange);
|
||||||
|
return mallocRange.getMinAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new InsufficientBytesException(
|
||||||
|
"SimpleMallocMgr failed to allocate " + byteLength + " bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void free(Address mallocRangeAddr) {
|
||||||
|
AddressRange range = mallocMap.remove(mallocRangeAddr);
|
||||||
|
if (range == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"free request for unallocated block at " + mallocRangeAddr);
|
||||||
|
}
|
||||||
|
allocSet.add(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,453 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import ghidra.app.emulator.memory.*;
|
||||||
|
import ghidra.app.emulator.state.*;
|
||||||
|
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
|
import ghidra.pcode.emulate.*;
|
||||||
|
import ghidra.pcode.error.LowlevelError;
|
||||||
|
import ghidra.pcode.memstate.*;
|
||||||
|
import ghidra.program.disassemble.Disassembler;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.lang.*;
|
||||||
|
import ghidra.program.model.listing.Instruction;
|
||||||
|
import ghidra.util.*;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
import ghidra.util.task.TaskMonitorAdapter;
|
||||||
|
|
||||||
|
public class Emulator {
|
||||||
|
|
||||||
|
private final MemoryFaultHandler faultHandler;
|
||||||
|
|
||||||
|
private SleighLanguage language;
|
||||||
|
private AddressFactory addrFactory;
|
||||||
|
|
||||||
|
private CompositeLoadImage loadImage = new CompositeLoadImage();
|
||||||
|
|
||||||
|
private RegisterState mstate;
|
||||||
|
private MemoryPageBank registerState;
|
||||||
|
private FilteredMemoryState memState;
|
||||||
|
private ghidra.pcode.emulate.BreakTableCallBack breakTable;
|
||||||
|
private Emulate emulator;
|
||||||
|
|
||||||
|
private boolean emuHalt = true;
|
||||||
|
private boolean isExecuting = false;
|
||||||
|
|
||||||
|
private boolean writeBack = false;
|
||||||
|
private int pageSize;
|
||||||
|
|
||||||
|
private String pcName;
|
||||||
|
private long initialPC;
|
||||||
|
private int instExecuted = 0;
|
||||||
|
|
||||||
|
public Emulator(EmulatorConfiguration cfg) {
|
||||||
|
|
||||||
|
this.faultHandler = cfg.getMemoryFaultHandler();
|
||||||
|
|
||||||
|
pcName = cfg.getProgramCounterName();
|
||||||
|
writeBack = cfg.isWriteBackEnabled();
|
||||||
|
pageSize = cfg.getPreferredMemoryPageSize();
|
||||||
|
|
||||||
|
Language lang = cfg.getLanguage();
|
||||||
|
if (!(lang instanceof SleighLanguage)) {
|
||||||
|
throw new IllegalArgumentException("Invalid configuartion language [" +
|
||||||
|
lang.getLanguageID() + "]: only Sleigh languages are supported by emulator");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: The way this is currently done, we are unable to emulate within overlay spaces
|
||||||
|
// The addrFactory should be obtained memState which is a reversal
|
||||||
|
// When a program load image is used the addrFactory should come from the program and
|
||||||
|
// not the language. Things may also get complex in terms of handling loads/stores and
|
||||||
|
// flow associated with overlays.
|
||||||
|
|
||||||
|
language = (SleighLanguage) lang;
|
||||||
|
addrFactory = lang.getAddressFactory();
|
||||||
|
|
||||||
|
EmulatorLoadData load = cfg.getLoadData();
|
||||||
|
loadImage.addProvider(load.getMemoryLoadImage(), load.getView());
|
||||||
|
mstate = load.getInitialRegisterState();
|
||||||
|
|
||||||
|
initMemState(mstate);
|
||||||
|
|
||||||
|
breakTable = new BreakTableCallBack(language);
|
||||||
|
emulator = new Emulate(language, memState, breakTable);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setExecuteAddress(initialPC);
|
||||||
|
}
|
||||||
|
catch (LowlevelError lle) {
|
||||||
|
Msg.warn(this, "pc is unmappable -- no execution possible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getValidPageSize(AddressSpace space) {
|
||||||
|
int ps = pageSize;
|
||||||
|
long maxOffset = space.getMaxAddress().getOffset();
|
||||||
|
if (ps > maxOffset && maxOffset > 0) {
|
||||||
|
ps = (int) maxOffset;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ps -= (ps % space.getAddressableUnitSize());
|
||||||
|
}
|
||||||
|
if (pageSize != ps) {
|
||||||
|
Msg.warn(this, "Emulator using adjusted page size of " + ps + " bytes for " +
|
||||||
|
space.getName() + " address space");
|
||||||
|
}
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initMemState(RegisterState rstate) {
|
||||||
|
|
||||||
|
memState = new FilteredMemoryState(language);
|
||||||
|
|
||||||
|
for (AddressSpace space : addrFactory.getPhysicalSpaces()) {
|
||||||
|
if (!space.isLoadedMemorySpace()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FilteredMemoryPageOverlay ramBank = getMemoryBank(space, getValidPageSize(space));
|
||||||
|
memState.setMemoryBank(ramBank);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddressSpace registerSpace = addrFactory.getRegisterSpace();
|
||||||
|
registerState = new FilteredRegisterBank(registerSpace, pageSize, rstate, language,
|
||||||
|
writeBack, faultHandler);
|
||||||
|
|
||||||
|
memState.setMemoryBank(registerState);
|
||||||
|
|
||||||
|
initRegisters(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryState cloneMemory() {
|
||||||
|
MemoryState newMemState = new FilteredMemoryState(language);
|
||||||
|
|
||||||
|
for (AddressSpace space : addrFactory.getPhysicalSpaces()) {
|
||||||
|
if (!space.isLoadedMemorySpace()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FilteredMemoryPageOverlay ramBank = getMemoryBank(space, getValidPageSize(space));
|
||||||
|
newMemState.setMemoryBank(ramBank);
|
||||||
|
}
|
||||||
|
return newMemState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilteredMemoryPageOverlay getMemoryBank(AddressSpace space, int ps) {
|
||||||
|
MemoryImage image =
|
||||||
|
new MemoryImage(space, language.isBigEndian(), ps, loadImage, faultHandler);
|
||||||
|
return new FilteredMemoryPageOverlay(space, image, writeBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize memory state using the initial register state. If restore is true,
|
||||||
|
* only those registers within the register space which have been modified will
|
||||||
|
* be reported and restored to their initial state.
|
||||||
|
* @param restore if true restore modified registers within the register space only
|
||||||
|
*/
|
||||||
|
private void initRegisters(boolean restore) {
|
||||||
|
DataConverter conv =
|
||||||
|
language.isBigEndian() ? new BigEndianDataConverter() : new LittleEndianDataConverter();
|
||||||
|
Set<String> keys = mstate.getKeys();
|
||||||
|
for (String key : keys) {
|
||||||
|
List<byte[]> vals = mstate.getVals(key);
|
||||||
|
List<Boolean> initiailizedVals = mstate.isInitialized(key);
|
||||||
|
for (int i = 0; i < vals.size(); i++) {
|
||||||
|
String useKey = "";
|
||||||
|
if (key.equals("GDTR") || key.equals("IDTR") || key.equals("LDTR")) {
|
||||||
|
if (i == 0)
|
||||||
|
useKey = key + "_Limit";
|
||||||
|
if (i == 1)
|
||||||
|
useKey = key + "_Address";
|
||||||
|
}
|
||||||
|
else if (key.equals("S.base")) {
|
||||||
|
Integer lval = conv.getInt(vals.get(i));
|
||||||
|
if (lval != 0 && i < vals.size() - 1) {
|
||||||
|
useKey = "FS_OFFSET"; // Colossal hack
|
||||||
|
memState.setValue("FS", (i + 2) * 0x8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
useKey = (vals.size() > 1) ? key + i : key;
|
||||||
|
}
|
||||||
|
Register register = language.getRegister(useKey);
|
||||||
|
if (register == null) {
|
||||||
|
useKey = useKey.toUpperCase();
|
||||||
|
register = language.getRegister(useKey);
|
||||||
|
}
|
||||||
|
if (register != null) {
|
||||||
|
if (restore && !register.getAddress().isRegisterAddress()) {
|
||||||
|
continue; // only restore registers within register space
|
||||||
|
}
|
||||||
|
byte[] valBytes = vals.get(i);
|
||||||
|
boolean initializedValue = initiailizedVals.get(i);
|
||||||
|
|
||||||
|
Address regAddr = register.getAddress();
|
||||||
|
|
||||||
|
if (restore) {
|
||||||
|
byte[] curVal = new byte[valBytes.length];
|
||||||
|
memState.getChunk(curVal, regAddr.getAddressSpace(), regAddr.getOffset(),
|
||||||
|
register.getMinimumByteSize(), false);
|
||||||
|
if (Arrays.equals(curVal, valBytes)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
System.out.println(
|
||||||
|
"resetRegisters : " + useKey + "=" + dumpBytesAsSingleValue(valBytes) +
|
||||||
|
"->" + dumpBytesAsSingleValue(curVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
memState.setChunk(valBytes, regAddr.getAddressSpace(), regAddr.getOffset(),
|
||||||
|
register.getMinimumByteSize());
|
||||||
|
|
||||||
|
if (!initializedValue) {
|
||||||
|
memState.setInitialized(false, regAddr.getAddressSpace(),
|
||||||
|
regAddr.getOffset(), register.getMinimumByteSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (register.isProgramCounter() ||
|
||||||
|
register.getName().equalsIgnoreCase(pcName)) {
|
||||||
|
initialPC = conv.getValue(valBytes, valBytes.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String dumpBytesAsSingleValue(byte[] bytes) {
|
||||||
|
StringBuffer buf = new StringBuffer("0x");
|
||||||
|
if (language.isBigEndian()) {
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
String byteStr = Integer.toHexString(bytes[i] & 0xff);
|
||||||
|
if (byteStr.length() == 1) {
|
||||||
|
buf.append('0');
|
||||||
|
}
|
||||||
|
buf.append(byteStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int i = bytes.length - 1; i >= 0; i--) {
|
||||||
|
String byteStr = Integer.toHexString(bytes[i] & 0xff);
|
||||||
|
if (byteStr.length() == 1) {
|
||||||
|
buf.append('0');
|
||||||
|
}
|
||||||
|
buf.append(byteStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
emuHalt = true;
|
||||||
|
emulator.dispose();
|
||||||
|
if (writeBack) {
|
||||||
|
initRegisters(true);
|
||||||
|
mstate.dispose();
|
||||||
|
}
|
||||||
|
loadImage.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address genAddress(String addr) {
|
||||||
|
return addrFactory.getDefaultAddressSpace().getAddress(NumericUtilities.parseHexLong(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPC() {
|
||||||
|
return memState.getValue(pcName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPCRegisterName() {
|
||||||
|
return pcName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryState getMemState() {
|
||||||
|
return memState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilteredMemoryState getFilteredMemState() {
|
||||||
|
return memState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMemoryAccessFilter(MemoryAccessFilter filter) {
|
||||||
|
filter.addFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BreakTableCallBack getBreakTable() {
|
||||||
|
return breakTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExecuteAddress(long addressableWordOffset) {
|
||||||
|
AddressSpace space = addrFactory.getDefaultAddressSpace();
|
||||||
|
Address address = space.getTruncatedAddress(addressableWordOffset, true);
|
||||||
|
emulator.setExecuteAddress(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address getExecuteAddress() {
|
||||||
|
return emulator.getExecuteAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address getLastExecuteAddress() {
|
||||||
|
return emulator.getLastExecuteAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getDefaultContext() {
|
||||||
|
return mstate.getKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHalt(boolean halt) {
|
||||||
|
emuHalt = halt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getHalt() {
|
||||||
|
return emuHalt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
|
||||||
|
throws CancelledException, LowlevelError, InstructionDecodeException {
|
||||||
|
isExecuting = true;
|
||||||
|
try {
|
||||||
|
emulator.executeInstruction(stopAtBreakpoint, monitor);
|
||||||
|
instExecuted++;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isExecuting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if halted at a breakpoint
|
||||||
|
*/
|
||||||
|
public boolean isAtBreakpoint() {
|
||||||
|
return getHalt() && emulator.getExecutionState() == EmulateExecutionState.BREAKPOINT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return emulator execution state. This can be useful within a memory fault handler to
|
||||||
|
* determine if a memory read was associated with instruction parsing (i.e., PCODE_EMIT) or
|
||||||
|
* normal an actual emulated read (i.e., EXECUTE).
|
||||||
|
*/
|
||||||
|
public EmulateExecutionState getEmulateExecutionState() {
|
||||||
|
return emulator.getExecutionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if emulator is busy executing an instruction
|
||||||
|
*/
|
||||||
|
public boolean isExecuting() {
|
||||||
|
return isExecuting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SleighLanguage getLanguage() {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disassemble from the current execute address
|
||||||
|
* @param count number of contiguous instructions to disassemble
|
||||||
|
* @return list of instructions
|
||||||
|
*/
|
||||||
|
public List<String> disassemble(Integer count) {
|
||||||
|
if (!emuHalt || isExecuting) {
|
||||||
|
throw new IllegalStateException("disassembly not allowed while emulator is executing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This can provide bad disassembly if reliant on future context state (e.g., end of loop)
|
||||||
|
|
||||||
|
List<String> disassembly = new ArrayList<>();
|
||||||
|
|
||||||
|
EmulateDisassemblerContext disassemblerContext = emulator.getNewDisassemblerContext();
|
||||||
|
Address addr = getExecuteAddress();
|
||||||
|
EmulateMemoryStateBuffer memBuffer = new EmulateMemoryStateBuffer(memState, addr);
|
||||||
|
|
||||||
|
Disassembler disassembler = Disassembler.getDisassembler(language, addrFactory,
|
||||||
|
TaskMonitorAdapter.DUMMY_MONITOR, null);
|
||||||
|
|
||||||
|
boolean stopOnError = false;
|
||||||
|
|
||||||
|
while (count > 0 && !stopOnError) {
|
||||||
|
memBuffer.setAddress(addr);
|
||||||
|
disassemblerContext.setCurrentAddress(addr);
|
||||||
|
|
||||||
|
InstructionBlock block = disassembler.pseudoDisassembleBlock(memBuffer,
|
||||||
|
disassemblerContext.getCurrentContextRegisterValue(), count);
|
||||||
|
|
||||||
|
if (block.hasInstructionError() && count > block.getInstructionCount()) {
|
||||||
|
InstructionError instructionError = block.getInstructionConflict();
|
||||||
|
Msg.error(this,
|
||||||
|
"Target disassembler error at " + instructionError.getConflictAddress() + ": " +
|
||||||
|
instructionError.getConflictMessage());
|
||||||
|
stopOnError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction lastInstr = null;
|
||||||
|
Iterator<Instruction> iterator = block.iterator();
|
||||||
|
while (iterator.hasNext() && count != 0) {
|
||||||
|
Instruction instr = iterator.next();
|
||||||
|
disassembly.add(instr.getAddressString(false, true) + " " + instr.toString());
|
||||||
|
lastInstr = instr;
|
||||||
|
--count;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
addr = lastInstr.getAddress().addNoWrap(lastInstr.getLength());
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return disassembly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTickCount() {
|
||||||
|
return instExecuted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current context register value. The context value returned reflects
|
||||||
|
* its state when the previously executed instruction was
|
||||||
|
* parsed/executed. The context value returned will feed into the next
|
||||||
|
* instruction to be parsed with its non-flowing bits cleared and
|
||||||
|
* any future context state merged in.
|
||||||
|
*/
|
||||||
|
public RegisterValue getContextRegisterValue() {
|
||||||
|
return emulator.getContextRegisterValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the context register value at the current execute address.
|
||||||
|
* The Emulator should not be running when this method is invoked.
|
||||||
|
* Only flowing context bits should be set, as non-flowing bits
|
||||||
|
* will be cleared prior to parsing on instruction. In addition,
|
||||||
|
* any future context state set by the pcode emitter will
|
||||||
|
* take precedence over context set using this method. This method
|
||||||
|
* is primarily intended to be used to establish the initial
|
||||||
|
* context state.
|
||||||
|
* @param regValue
|
||||||
|
*/
|
||||||
|
public void setContextRegisterValue(RegisterValue regValue) {
|
||||||
|
emulator.setContextRegisterValue(regValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add memory load image provider
|
||||||
|
* @param provider memory load image provider
|
||||||
|
* @param view memory region which corresponds to provider
|
||||||
|
*/
|
||||||
|
public void addProvider(MemoryLoadImage provider, AddressSetView view) {
|
||||||
|
loadImage.addProvider(provider, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator;
|
||||||
|
|
||||||
|
import ghidra.app.emulator.memory.EmulatorLoadData;
|
||||||
|
import ghidra.pcode.memstate.MemoryFaultHandler;
|
||||||
|
import ghidra.program.model.lang.Language;
|
||||||
|
import ghidra.program.model.lang.Register;
|
||||||
|
|
||||||
|
public interface EmulatorConfiguration {
|
||||||
|
|
||||||
|
Language getLanguage();
|
||||||
|
|
||||||
|
EmulatorLoadData getLoadData();
|
||||||
|
|
||||||
|
MemoryFaultHandler getMemoryFaultHandler();
|
||||||
|
|
||||||
|
default boolean isWriteBackEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default int getPreferredMemoryPageSize() {
|
||||||
|
return 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
default String getProgramCounterName() {
|
||||||
|
Language lang = getLanguage();
|
||||||
|
Register pcReg = lang.getProgramCounter();
|
||||||
|
if (pcReg == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Language has not defined Program Counter Register: " + lang.getLanguageID());
|
||||||
|
}
|
||||||
|
return pcReg.getName();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,725 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import ghidra.app.emulator.memory.*;
|
||||||
|
import ghidra.app.emulator.state.DumpMiscState;
|
||||||
|
import ghidra.app.emulator.state.RegisterState;
|
||||||
|
import ghidra.framework.store.LockException;
|
||||||
|
import ghidra.pcode.emulate.BreakCallBack;
|
||||||
|
import ghidra.pcode.emulate.EmulateExecutionState;
|
||||||
|
import ghidra.pcode.memstate.MemoryFaultHandler;
|
||||||
|
import ghidra.pcode.memstate.MemoryState;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.lang.*;
|
||||||
|
import ghidra.program.model.listing.*;
|
||||||
|
import ghidra.program.model.mem.MemoryBlock;
|
||||||
|
import ghidra.program.model.mem.MemoryConflictException;
|
||||||
|
import ghidra.util.*;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration {
|
||||||
|
|
||||||
|
private final Program program;
|
||||||
|
private final Emulator emulator;
|
||||||
|
|
||||||
|
private Register stackPtrReg;
|
||||||
|
private AddressSpace stackMemorySpace;
|
||||||
|
|
||||||
|
private String lastError;
|
||||||
|
private MemoryWriteTracker memoryWriteTracker;
|
||||||
|
|
||||||
|
private MemoryFaultHandler faultHandler;
|
||||||
|
|
||||||
|
private BreakCallBack addressBreak = new BreakCallBack() {
|
||||||
|
@Override
|
||||||
|
public boolean addressCallback(Address addr) {
|
||||||
|
emulator.setHalt(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public EmulatorHelper(Program program) {
|
||||||
|
|
||||||
|
this.program = program;
|
||||||
|
|
||||||
|
stackPtrReg = program.getCompilerSpec().getStackPointer();
|
||||||
|
stackMemorySpace = program.getCompilerSpec().getStackBaseSpace();
|
||||||
|
|
||||||
|
emulator = new Emulator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
emulator.dispose();
|
||||||
|
if (memoryWriteTracker != null) {
|
||||||
|
memoryWriteTracker.dispose();
|
||||||
|
memoryWriteTracker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MemoryFaultHandler getMemoryFaultHandler() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmulatorLoadData getLoadData() {
|
||||||
|
|
||||||
|
return new EmulatorLoadData() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MemoryLoadImage getMemoryLoadImage() {
|
||||||
|
return new ProgramMappedLoadImage(
|
||||||
|
new ProgramMappedMemory(program, EmulatorHelper.this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RegisterState getInitialRegisterState() {
|
||||||
|
return new DumpMiscState(getLanguage());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Language getLanguage() {
|
||||||
|
return program.getLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Program getProgram() {
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Program Counter (PC) register defined by applicable processor specification
|
||||||
|
* @return Program Counter register
|
||||||
|
*/
|
||||||
|
public Register getPCRegister() {
|
||||||
|
return program.getLanguage().getProgramCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Stack Pointer register defined by applicable compiler specification
|
||||||
|
* @return Stack Pointer register
|
||||||
|
*/
|
||||||
|
public Register getStackPointerRegister() {
|
||||||
|
return stackPtrReg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides ability to install a low-level memory fault handler.
|
||||||
|
* The handler methods should generally return 'false' to allow
|
||||||
|
* the default handler to generate the appropriate target error.
|
||||||
|
* Within the fault handler, the EmulateExecutionState can be used
|
||||||
|
* to distinguish the pcode-emit state and the actual execution state
|
||||||
|
* since an attempt to execute an instruction at an uninitialized
|
||||||
|
* memory location will cause an uninitializedRead during the PCODE_EMIT
|
||||||
|
* state.
|
||||||
|
* @param handler memory fault handler.
|
||||||
|
*/
|
||||||
|
public void setMemoryFaultHandler(MemoryFaultHandler handler) {
|
||||||
|
faultHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the low-level emulator execution state
|
||||||
|
*/
|
||||||
|
public EmulateExecutionState getEmulateExecutionState() {
|
||||||
|
return emulator.getEmulateExecutionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Register getRegister(String regName) throws IllegalArgumentException {
|
||||||
|
Register reg = program.getRegister(regName);
|
||||||
|
if (reg == null) {
|
||||||
|
throw new IllegalArgumentException("Undefined register: " + regName);
|
||||||
|
}
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger readRegister(Register reg) {
|
||||||
|
if (reg.isProcessorContext()) {
|
||||||
|
RegisterValue contextRegisterValue = emulator.getContextRegisterValue();
|
||||||
|
if (!reg.equals(contextRegisterValue.getRegister())) {
|
||||||
|
contextRegisterValue = contextRegisterValue.getRegisterValue(reg);
|
||||||
|
}
|
||||||
|
return contextRegisterValue.getSignedValueIgnoreMask();
|
||||||
|
}
|
||||||
|
if (reg.getName().equals(emulator.getPCRegisterName())) {
|
||||||
|
return BigInteger.valueOf(emulator.getPC());
|
||||||
|
}
|
||||||
|
return emulator.getMemState().getBigInteger(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger readRegister(String regName) {
|
||||||
|
Register reg = getRegister(regName);
|
||||||
|
if (reg == null) {
|
||||||
|
throw new IllegalArgumentException("Undefined register: " + regName);
|
||||||
|
}
|
||||||
|
return readRegister(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRegister(Register reg, long value) {
|
||||||
|
writeRegister(reg, BigInteger.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRegister(String regName, long value) {
|
||||||
|
writeRegister(regName, BigInteger.valueOf(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRegister(Register reg, BigInteger value) {
|
||||||
|
if (reg.isProcessorContext()) {
|
||||||
|
RegisterValue contextRegisterValue = new RegisterValue(reg, value);
|
||||||
|
RegisterValue existingRegisterValue = emulator.getContextRegisterValue();
|
||||||
|
if (!reg.equals(existingRegisterValue.getRegister())) {
|
||||||
|
contextRegisterValue = existingRegisterValue.combineValues(contextRegisterValue);
|
||||||
|
}
|
||||||
|
emulator.setContextRegisterValue(contextRegisterValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emulator.getMemState().setValue(reg, value);
|
||||||
|
if (reg.getName().equals(emulator.getPCRegisterName())) {
|
||||||
|
emulator.setExecuteAddress(value.longValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRegister(String regName, BigInteger value) {
|
||||||
|
Register reg = getRegister(regName);
|
||||||
|
if (reg == null) {
|
||||||
|
throw new IllegalArgumentException("Undefined register: " + regName);
|
||||||
|
}
|
||||||
|
writeRegister(reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read string from memory state.
|
||||||
|
* @param addr memory address
|
||||||
|
* @param maxLength limit string read to this length. If return string is
|
||||||
|
* truncated, "..." will be appended.
|
||||||
|
* @return string read from memory state
|
||||||
|
*/
|
||||||
|
public String readNullTerminatedString(Address addr, int maxLength) {
|
||||||
|
int len = 0;
|
||||||
|
byte[] bytes = new byte[maxLength];
|
||||||
|
byte b = 0;
|
||||||
|
while (len < maxLength && (b = readMemoryByte(addr)) != 0) {
|
||||||
|
bytes[len++] = b;
|
||||||
|
addr = addr.next();
|
||||||
|
}
|
||||||
|
String str = new String(bytes, 0, len);
|
||||||
|
if (b != 0) {
|
||||||
|
str += "..."; // indicate string truncation
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte readMemoryByte(Address addr) {
|
||||||
|
byte[] value = readMemory(addr, 1);
|
||||||
|
return value[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] readMemory(Address addr, int length) {
|
||||||
|
byte[] res = new byte[length];
|
||||||
|
int len = emulator.getMemState().getChunk(res, addr.getAddressSpace(), addr.getOffset(),
|
||||||
|
length, false);
|
||||||
|
if (len == 0) {
|
||||||
|
Msg.error(this, "Failed to read memory from Emulator at: " + addr);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if (len < length) {
|
||||||
|
Msg.error(this,
|
||||||
|
"Only " + len + " of " + length + " bytes read memory from Emulator at: " + addr);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeMemory(Address addr, byte[] bytes) {
|
||||||
|
emulator.getMemState().setChunk(bytes, addr.getAddressSpace(), addr.getOffset(),
|
||||||
|
bytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeMemoryValue(Address addr, int size, long value) {
|
||||||
|
emulator.getMemState().setValue(addr.getAddressSpace(), addr.getOffset(), size, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a stack value from the memory state.
|
||||||
|
* @param relativeOffset offset relative to current stack pointer
|
||||||
|
* @param size data size in bytes
|
||||||
|
* @param signed true if value read is signed, false if unsigned
|
||||||
|
* @return value
|
||||||
|
* @throws Exception error occurs reading stack pointer
|
||||||
|
*/
|
||||||
|
public BigInteger readStackValue(int relativeOffset, int size, boolean signed)
|
||||||
|
throws Exception {
|
||||||
|
long offset = readRegister(stackPtrReg).longValue() + relativeOffset;
|
||||||
|
byte[] bytes = readMemory(stackMemorySpace.getAddress(offset), size);
|
||||||
|
if (program.getMemory().isBigEndian()) {
|
||||||
|
return BigEndianDataConverter.INSTANCE.getBigInteger(bytes, size, signed);
|
||||||
|
}
|
||||||
|
return LittleEndianDataConverter.INSTANCE.getBigInteger(bytes, size, signed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a value onto the stack
|
||||||
|
* @param relativeOffset offset relative to current stack pointer
|
||||||
|
* @param size data size in bytes
|
||||||
|
* @param value
|
||||||
|
* @throws Exception error occurs reading stack pointer
|
||||||
|
*/
|
||||||
|
public void writeStackValue(int relativeOffset, int size, long value) throws Exception {
|
||||||
|
long offset = readRegister(stackPtrReg).longValue() + relativeOffset;
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
if (program.getMemory().isBigEndian()) {
|
||||||
|
BigEndianDataConverter.INSTANCE.getBytes(value, bytes);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LittleEndianDataConverter.INSTANCE.getBytes(value, bytes);
|
||||||
|
}
|
||||||
|
writeMemory(stackMemorySpace.getAddress(offset), bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a value onto the stack
|
||||||
|
* @param relativeOffset offset relative to current stack pointer
|
||||||
|
* @param size data size in bytes
|
||||||
|
* @param value
|
||||||
|
* @throws Exception error occurs reading stack pointer
|
||||||
|
*/
|
||||||
|
public void writeStackValue(int relativeOffset, int size, BigInteger value) throws Exception {
|
||||||
|
// TODO: verify that sign byte is not added to size of bytes
|
||||||
|
long offset = readRegister(stackPtrReg).longValue() + relativeOffset;
|
||||||
|
byte[] bytes;
|
||||||
|
if (program.getMemory().isBigEndian()) {
|
||||||
|
bytes = BigEndianDataConverter.INSTANCE.getBytes(value, size);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bytes = LittleEndianDataConverter.INSTANCE.getBytes(value, size);
|
||||||
|
}
|
||||||
|
writeMemory(stackMemorySpace.getAddress(offset), bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establish breakpoint
|
||||||
|
* @param address memory address for new breakpoint
|
||||||
|
*/
|
||||||
|
public void setBreakpoint(Address addr) {
|
||||||
|
emulator.getBreakTable().registerAddressCallback(addr, addressBreak);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear breakpoint
|
||||||
|
* @param address memory address for breakpoint to be cleared
|
||||||
|
*/
|
||||||
|
public void clearBreakpoint(Address addr) {
|
||||||
|
emulator.getBreakTable().unregisterAddressCallback(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set current context register value.
|
||||||
|
* Keep in mind that any non-flowing context values will be stripped.
|
||||||
|
* @param ctxRegValue
|
||||||
|
*/
|
||||||
|
public void setContextRegister(RegisterValue ctxRegValue) {
|
||||||
|
emulator.setContextRegisterValue(ctxRegValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set current context register value.
|
||||||
|
* Keep in mind that any non-flowing context values will be stripped.
|
||||||
|
* @param ctxReg context register
|
||||||
|
* @param value context value
|
||||||
|
*/
|
||||||
|
public void setContextRegister(Register ctxReg, BigInteger value) {
|
||||||
|
emulator.setContextRegisterValue(new RegisterValue(ctxReg, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current context register value
|
||||||
|
* @return context register value or null if not set or unknown
|
||||||
|
*/
|
||||||
|
public RegisterValue getContextRegister() {
|
||||||
|
return emulator.getContextRegisterValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register callback for language defined pcodeop (call other).
|
||||||
|
* WARNING! Using this method may circumvent the default CALLOTHER emulation support
|
||||||
|
* when supplied by the Processor module.
|
||||||
|
* @param pcodeOpName
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public void registerCallOtherCallback(String pcodeOpName, BreakCallBack callback) {
|
||||||
|
emulator.getBreakTable().registerPcodeCallback(pcodeOpName, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register default callback for language defined pcodeops (call other).
|
||||||
|
* WARNING! Using this method may circumvent the default CALLOTHER emulation support
|
||||||
|
* when supplied by the Processor module.
|
||||||
|
* @param pcodeOpName
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public void registerDefaultCallOtherCallback(BreakCallBack callback) {
|
||||||
|
emulator.getBreakTable().registerPcodeCallback("*", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister callback for language defined pcodeop (call other).
|
||||||
|
* @param pcodeOpName
|
||||||
|
*/
|
||||||
|
public void unregisterCallOtherCallback(String pcodeOpName) {
|
||||||
|
emulator.getBreakTable().unregisterPcodeCallback(pcodeOpName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister default callback for language defined pcodeops (call other).
|
||||||
|
* WARNING! Using this method may circumvent the default CALLOTHER emulation support
|
||||||
|
* when supplied by the Processor module.
|
||||||
|
*/
|
||||||
|
public void unregisterDefaultCallOtherCallback() {
|
||||||
|
emulator.getBreakTable().unregisterPcodeCallback("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current execution address
|
||||||
|
* @return current execution address
|
||||||
|
*/
|
||||||
|
public Address getExecutionAddress() {
|
||||||
|
return emulator.getExecuteAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start execution at the specified address using the initial context specified.
|
||||||
|
* Method will block until execution stops. This method will initialize context
|
||||||
|
* register based upon the program stored context if not already done. In addition,
|
||||||
|
* both general register value and the context register may be further modified
|
||||||
|
* via the context parameter if specified.
|
||||||
|
* @param addr initial program address
|
||||||
|
* @param context optional context settings which override current program context
|
||||||
|
* @param monitor
|
||||||
|
* @return true if execution completes without error (i.e., is at breakpoint)
|
||||||
|
* @throws CancelledException if execution cancelled via monitor
|
||||||
|
*/
|
||||||
|
public boolean run(Address addr, ProcessorContext context, TaskMonitor monitor)
|
||||||
|
throws CancelledException {
|
||||||
|
|
||||||
|
if (emulator.isExecuting()) {
|
||||||
|
throw new IllegalStateException("Emulator is already running");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize context
|
||||||
|
ProgramContext programContext = program.getProgramContext();
|
||||||
|
Register baseContextRegister = programContext.getBaseContextRegister();
|
||||||
|
RegisterValue contextRegValue = null;
|
||||||
|
boolean mustSetContextReg = false;
|
||||||
|
|
||||||
|
if (baseContextRegister != null) {
|
||||||
|
contextRegValue = getContextRegister();
|
||||||
|
if (contextRegValue == null) {
|
||||||
|
contextRegValue = programContext.getRegisterValue(baseContextRegister, addr);
|
||||||
|
mustSetContextReg = (contextRegValue != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
for (Register reg : context.getRegisters()) {
|
||||||
|
// skip non-base registers
|
||||||
|
if (reg.isBaseRegister() && context.hasValue(reg)) {
|
||||||
|
RegisterValue registerValue = context.getRegisterValue(reg);
|
||||||
|
if (reg.isProcessorContext()) {
|
||||||
|
if (contextRegValue != null) {
|
||||||
|
contextRegValue = contextRegValue.combineValues(registerValue);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
contextRegValue = registerValue;
|
||||||
|
}
|
||||||
|
mustSetContextReg = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
BigInteger value = registerValue.getUnsignedValueIgnoreMask();
|
||||||
|
writeRegister(reg, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long pcValue = addr.getAddressableWordOffset();
|
||||||
|
emulator.setExecuteAddress(pcValue);
|
||||||
|
|
||||||
|
if (mustSetContextReg) {
|
||||||
|
setContextRegister(contextRegValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
continueExecution(monitor);
|
||||||
|
return emulator.isAtBreakpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continue execution from the current execution address.
|
||||||
|
* No adjustment will be made to the context beyond the normal
|
||||||
|
* context flow behavior defined by the language.
|
||||||
|
* Method will block until execution stops.
|
||||||
|
* @param monitor
|
||||||
|
* @return true if execution completes without error (i.e., is at breakpoint)
|
||||||
|
* @throws CancelledException if execution cancelled via monitor
|
||||||
|
*/
|
||||||
|
public synchronized boolean run(TaskMonitor monitor) throws CancelledException {
|
||||||
|
|
||||||
|
if (emulator.isExecuting()) {
|
||||||
|
throw new IllegalStateException("Emulator is already running");
|
||||||
|
}
|
||||||
|
continueExecution(monitor);
|
||||||
|
return emulator.isAtBreakpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continue execution and block until either a breakpoint hits or error occurs.
|
||||||
|
* @throws CancelledException if execution was cancelled
|
||||||
|
*/
|
||||||
|
private void continueExecution(TaskMonitor monitor) throws CancelledException {
|
||||||
|
emulator.setHalt(false);
|
||||||
|
do {
|
||||||
|
executeInstruction(true, monitor);
|
||||||
|
}
|
||||||
|
while (!emulator.getHalt());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute instruction at current address
|
||||||
|
* @param stopAtBreakpoint if true and breakpoint hits at current execution address
|
||||||
|
* execution will halt without executing instruction.
|
||||||
|
* @throws CancelledException if execution was cancelled
|
||||||
|
*/
|
||||||
|
private void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
|
||||||
|
throws CancelledException {
|
||||||
|
|
||||||
|
lastError = null;
|
||||||
|
try {
|
||||||
|
if (emulator.getLastExecuteAddress() == null) {
|
||||||
|
setProcessorContext();
|
||||||
|
}
|
||||||
|
emulator.executeInstruction(stopAtBreakpoint, monitor);
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
// TODO: need to enumerate errors better !!
|
||||||
|
lastError = t.getMessage();
|
||||||
|
emulator.setHalt(true); // force execution to stop
|
||||||
|
if (t instanceof CancelledException) {
|
||||||
|
throw (CancelledException) t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when the emulator has had the execution address changed to
|
||||||
|
* make sure it has a context consistent with the program context
|
||||||
|
* if there is one.
|
||||||
|
*/
|
||||||
|
private void setProcessorContext() {
|
||||||
|
// this assumes you have set the emulation address
|
||||||
|
// the emu will have cleared the context for the new address
|
||||||
|
RegisterValue contextRegisterValue = emulator.getContextRegisterValue();
|
||||||
|
if (contextRegisterValue != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Address executeAddress = emulator.getExecuteAddress();
|
||||||
|
Instruction instructionAt = program.getListing().getInstructionAt(executeAddress);
|
||||||
|
if (instructionAt != null) {
|
||||||
|
RegisterValue disassemblyContext =
|
||||||
|
instructionAt.getRegisterValue(instructionAt.getBaseContextRegister());
|
||||||
|
emulator.setContextRegisterValue(disassemblyContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return last error message associated with execution failure
|
||||||
|
*/
|
||||||
|
public String getLastError() {
|
||||||
|
return lastError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step execution one instruction which may consist of multiple
|
||||||
|
* pcode operations. No adjustment will be made to the context beyond the normal
|
||||||
|
* context flow behavior defined by the language.
|
||||||
|
* Method will block until execution stops.
|
||||||
|
* @return true if execution completes without error
|
||||||
|
* @throws CancelledException if execution cancelled via monitor
|
||||||
|
*/
|
||||||
|
public synchronized boolean step(TaskMonitor monitor) throws CancelledException {
|
||||||
|
executeInstruction(true, monitor);
|
||||||
|
return lastError == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new initialized memory block using the current emulator memory state
|
||||||
|
* @param name block name
|
||||||
|
* @param start start address of the block
|
||||||
|
* @param length the size of the block
|
||||||
|
* @param overlay if true, the block will be created as an OVERLAY which means that a new
|
||||||
|
* overlay address space will be created and the block will have a starting address at the same
|
||||||
|
* offset as the given start address parameter, but in the new address space.
|
||||||
|
* @param monitor
|
||||||
|
* @return new memory block
|
||||||
|
* @throws LockException if exclusive lock not in place (see haveLock())
|
||||||
|
* @throws MemoryConflictException if the new block overlaps with a
|
||||||
|
* previous block
|
||||||
|
* @throws AddressOverflowException if the start is beyond the
|
||||||
|
* address space
|
||||||
|
* @throws CancelledException user cancelled operation
|
||||||
|
* @throws DuplicateNameException
|
||||||
|
*/
|
||||||
|
public MemoryBlock createMemoryBlockFromMemoryState(String name, final Address start,
|
||||||
|
final int length, boolean overlay, TaskMonitor monitor) throws MemoryConflictException,
|
||||||
|
AddressOverflowException, CancelledException, LockException, DuplicateNameException {
|
||||||
|
|
||||||
|
if (emulator.isExecuting()) {
|
||||||
|
throw new IllegalStateException("Emulator must be paused to access memory state");
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream memStateStream = new InputStream() {
|
||||||
|
|
||||||
|
private MemoryState memState = emulator.getMemState();
|
||||||
|
|
||||||
|
private byte[] buffer = new byte[1024];
|
||||||
|
private long nextBufferOffset = start.getOffset();
|
||||||
|
private int bytesRemaining = length;
|
||||||
|
private int bufferPos = buffer.length;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
|
||||||
|
if (bytesRemaining <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferPos == buffer.length) {
|
||||||
|
int size = Math.min(buffer.length, bytesRemaining);
|
||||||
|
memState.getChunk(buffer, start.getAddressSpace(), nextBufferOffset, size,
|
||||||
|
false);
|
||||||
|
nextBufferOffset += buffer.length;
|
||||||
|
bufferPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte b = buffer[bufferPos++];
|
||||||
|
--bytesRemaining;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MemoryBlock block;
|
||||||
|
boolean success = false;
|
||||||
|
int txId = program.startTransaction("Create Memory Block");
|
||||||
|
try {
|
||||||
|
block = program.getMemory().createInitializedBlock(name, start, memStateStream, length,
|
||||||
|
monitor, overlay);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
program.endTransaction(txId, success);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable/Disable tracking of memory writes in the form of an
|
||||||
|
* address set.
|
||||||
|
* @param enable
|
||||||
|
*/
|
||||||
|
public void enableMemoryWriteTracking(boolean enable) {
|
||||||
|
|
||||||
|
if (!enable) {
|
||||||
|
if (memoryWriteTracker != null) {
|
||||||
|
memoryWriteTracker.dispose();
|
||||||
|
memoryWriteTracker = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryWriteTracker = new MemoryWriteTracker();
|
||||||
|
emulator.addMemoryAccessFilter(memoryWriteTracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return address set of memory locations written by the emulator
|
||||||
|
* if memory write tracking is enabled, otherwise null is returned.
|
||||||
|
* The address set returned will continue to be updated unless
|
||||||
|
* memory write tracking becomes disabled.
|
||||||
|
*/
|
||||||
|
public AddressSetView getTrackedMemoryWriteSet() {
|
||||||
|
if (memoryWriteTracker != null) {
|
||||||
|
return memoryWriteTracker.writeSet;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MemoryWriteTracker extends MemoryAccessFilter {
|
||||||
|
|
||||||
|
AddressSet writeSet = new AddressSet();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processRead(AddressSpace spc, long off, int size, byte[] values) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processWrite(AddressSpace spc, long off, int size, byte[] values) {
|
||||||
|
AddressRange range =
|
||||||
|
new AddressRangeImpl(spc.getAddress(off), spc.getAddress(off + size - 1));
|
||||||
|
writeSet.add(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unknownAddress(Address address, boolean write) {
|
||||||
|
if (faultHandler != null) {
|
||||||
|
return faultHandler.unknownAddress(address, write);
|
||||||
|
}
|
||||||
|
Address pc = emulator.getExecuteAddress();
|
||||||
|
String access = write ? "written" : "read";
|
||||||
|
Msg.warn(this, "Unknown address " + access + " at " + pc + ": " + address);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean uninitializedRead(Address address, int size, byte[] buf, int bufOffset) {
|
||||||
|
if (faultHandler != null) {
|
||||||
|
return faultHandler.uninitializedRead(address, size, buf, bufOffset);
|
||||||
|
}
|
||||||
|
if (emulator.getEmulateExecutionState() == EmulateExecutionState.INSTRUCTION_DECODE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Address pc = emulator.getExecuteAddress();
|
||||||
|
Register reg = program.getRegister(address, size);
|
||||||
|
if (reg != null) {
|
||||||
|
Msg.warn(this, "Uninitialized register read at " + pc + ": " + reg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Msg.warn(this,
|
||||||
|
"Uninitialized memory read at " + pc + ": " + address.toString(true) + ":" + size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Emulator getEmulator() {
|
||||||
|
return emulator;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator;
|
||||||
|
|
||||||
|
import ghidra.pcode.memstate.MemoryState;
|
||||||
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
import ghidra.program.model.lang.Language;
|
||||||
|
|
||||||
|
class FilteredMemoryState extends MemoryState {
|
||||||
|
|
||||||
|
private MemoryAccessFilter filter;
|
||||||
|
private boolean filterEnabled = true; // used to prevent filtering filter queries
|
||||||
|
|
||||||
|
FilteredMemoryState(Language lang) {
|
||||||
|
super(lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getChunk(byte[] res, AddressSpace spc, long off, int size,
|
||||||
|
boolean stopOnUnintialized) {
|
||||||
|
int readLen = super.getChunk(res, spc, off, size, stopOnUnintialized);
|
||||||
|
if (filterEnabled && filter != null) {
|
||||||
|
filterEnabled = false;
|
||||||
|
try {
|
||||||
|
filter.filterRead(spc, off, readLen, res);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
filterEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return readLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChunk(byte[] res, AddressSpace spc, long off, int size) {
|
||||||
|
super.setChunk(res, spc, off, size);
|
||||||
|
if (filterEnabled && filter != null) {
|
||||||
|
filterEnabled = false;
|
||||||
|
try {
|
||||||
|
filter.filterWrite(spc, off, size, res);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
filterEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryAccessFilter setFilter(MemoryAccessFilter filter) {
|
||||||
|
MemoryAccessFilter oldFilter = this.filter;
|
||||||
|
this.filter = filter;
|
||||||
|
return oldFilter;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator;
|
||||||
|
|
||||||
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
|
||||||
|
public abstract class MemoryAccessFilter {
|
||||||
|
|
||||||
|
private MemoryAccessFilter prevFilter;
|
||||||
|
private MemoryAccessFilter nextFilter;
|
||||||
|
|
||||||
|
protected Emulator emu;
|
||||||
|
|
||||||
|
private boolean filterOnExecutionOnly = true;
|
||||||
|
|
||||||
|
final void filterRead(AddressSpace spc, long off, int size, byte [] values) {
|
||||||
|
if (filterOnExecutionOnly() && !emu.isExecuting()) return; // do not filter idle queries
|
||||||
|
processRead(spc, off, size, values);
|
||||||
|
if (nextFilter != null) {
|
||||||
|
nextFilter.filterRead(spc, off, size, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void processRead(AddressSpace spc, long off, int size, byte[] values);
|
||||||
|
|
||||||
|
final void filterWrite(AddressSpace spc, long off, int size, byte [] values) {
|
||||||
|
if (filterOnExecutionOnly() && !emu.isExecuting()) return; // do not filter idle queries
|
||||||
|
processWrite(spc, off, size, values);
|
||||||
|
if (nextFilter != null) {
|
||||||
|
nextFilter.filterWrite(spc, off, size, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void processWrite(AddressSpace spc, long off, int size, byte[] values);
|
||||||
|
|
||||||
|
final void addFilter(Emulator emu) {
|
||||||
|
this.emu = emu;
|
||||||
|
nextFilter = emu.getFilteredMemState().setFilter(this);
|
||||||
|
if (nextFilter != null) {
|
||||||
|
nextFilter.prevFilter = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose this filter which will cause it to be removed from the memory state.
|
||||||
|
* If overriden, be sure to invoke super.dispose().
|
||||||
|
*/
|
||||||
|
public void dispose() {
|
||||||
|
if (nextFilter != null) {
|
||||||
|
nextFilter.prevFilter = prevFilter;
|
||||||
|
}
|
||||||
|
if (prevFilter != null) {
|
||||||
|
prevFilter.nextFilter = nextFilter;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
emu.getFilteredMemState().setFilter(nextFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean filterOnExecutionOnly() {
|
||||||
|
return filterOnExecutionOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilterOnExecutionOnly(boolean filterOnExecutionOnly) {
|
||||||
|
this.filterOnExecutionOnly = filterOnExecutionOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void compare(String id);
|
||||||
|
// public void clear();
|
||||||
|
// public void updateFlags(String id);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.memory;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import ghidra.pcode.memstate.MemoryPage;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressSetView;
|
||||||
|
|
||||||
|
public class CompositeLoadImage implements MemoryLoadImage {
|
||||||
|
|
||||||
|
private List<MemoryLoadImage> providers = new ArrayList<MemoryLoadImage>();
|
||||||
|
private HashMap<MemoryLoadImage, AddressSetView> addrSets =
|
||||||
|
new HashMap<MemoryLoadImage, AddressSetView>();
|
||||||
|
|
||||||
|
public void addProvider(MemoryLoadImage provider, AddressSetView view) {
|
||||||
|
if (view == null) {
|
||||||
|
providers.add(providers.size(), provider);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
providers.add(0, provider);
|
||||||
|
}
|
||||||
|
addrSets.put(provider, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] loadFill(byte[] buf, int size, Address addr, int bufOffset,
|
||||||
|
boolean generateInitializedMask) {
|
||||||
|
// Warning: this implementation assumes that the memory page (specified by addr and size)
|
||||||
|
// will only correspond to a single program image.
|
||||||
|
Address endAddr = addr.add(size - 1);
|
||||||
|
for (MemoryLoadImage provider : providers) {
|
||||||
|
AddressSetView view = addrSets.get(provider);
|
||||||
|
if (view == null || view.intersects(addr, endAddr)) {
|
||||||
|
return provider.loadFill(buf, size, addr, bufOffset, generateInitializedMask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return generateInitializedMask ? MemoryPage.getInitializedMask(size, false) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeBack(byte[] bytes, int size, Address addr, int offset) {
|
||||||
|
// Warning: this implementation assumes that the memory page (specified by addr and size)
|
||||||
|
// will only correspond to a single program image.
|
||||||
|
Address endAddr = addr.add(size - 1);
|
||||||
|
for (MemoryLoadImage provider : providers) {
|
||||||
|
AddressSetView view = addrSets.get(provider);
|
||||||
|
if (view == null || view.intersects(addr, endAddr)) {
|
||||||
|
provider.writeBack(bytes, size, addr, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
for (MemoryLoadImage provider : providers) {
|
||||||
|
provider.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.memory;
|
||||||
|
|
||||||
|
import ghidra.app.emulator.state.RegisterState;
|
||||||
|
import ghidra.program.model.address.AddressSetView;
|
||||||
|
|
||||||
|
public interface EmulatorLoadData {
|
||||||
|
|
||||||
|
public MemoryLoadImage getMemoryLoadImage();
|
||||||
|
|
||||||
|
public RegisterState getInitialRegisterState();
|
||||||
|
|
||||||
|
public default AddressSetView getView() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.memory;
|
||||||
|
|
||||||
|
import ghidra.pcode.memstate.*;
|
||||||
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
|
||||||
|
/// A kind of MemoryBank which retrieves its data from an underlying LoadImage
|
||||||
|
///
|
||||||
|
/// Any bytes requested on the bank which lie in the LoadImage are retrieved from
|
||||||
|
/// the LoadImage. Other addresses in the space are filled in with zero.
|
||||||
|
/// This bank cannot be written to.
|
||||||
|
public class MemoryImage extends MemoryBank {
|
||||||
|
|
||||||
|
private MemoryLoadImage loader; // The underlying LoadImage
|
||||||
|
|
||||||
|
/// A MemoryImage needs everything a basic memory bank needs and is needs to know
|
||||||
|
/// the underlying LoadImage object to forward read requests to.
|
||||||
|
/// \param spc is the address space associated with the memory bank
|
||||||
|
/// \param ws is the number of bytes in the preferred wordsize (must be power of 2)
|
||||||
|
/// \param ps is the number of bytes in a page (must be power of 2)
|
||||||
|
/// \param ld is the underlying LoadImage
|
||||||
|
public MemoryImage(AddressSpace spc, boolean isBigEndian, int ps, MemoryLoadImage ld,
|
||||||
|
MemoryFaultHandler faultHandler) {
|
||||||
|
super(spc, isBigEndian, ps, faultHandler);
|
||||||
|
loader = ld;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve an aligned page from the bank. First an attempt is made to retrieve the
|
||||||
|
/// page from the LoadImage, which may do its own zero filling. If the attempt fails, the
|
||||||
|
/// page is entirely filled in with zeros.
|
||||||
|
@Override
|
||||||
|
public MemoryPage getPage(long addr) {
|
||||||
|
MemoryPage page = new MemoryPage(getPageSize());
|
||||||
|
// Assume that -addr- is page aligned
|
||||||
|
AddressSpace spc = getSpace();
|
||||||
|
byte[] maskUpdate =
|
||||||
|
loader.loadFill(page.data, getPageSize(), spc.getAddress(addr), 0, true);
|
||||||
|
page.setInitialized(0, getPageSize(), maskUpdate);
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setPage(long addr, byte[] val, int skip, int size, int bufOffset) {
|
||||||
|
AddressSpace spc = getSpace();
|
||||||
|
loader.writeBack(val, size, spc.getAddress(addr), bufOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setPageInitialized(long addr, boolean initialized, int skip, int size,
|
||||||
|
int bufOffset) {
|
||||||
|
// unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.memory;
|
||||||
|
|
||||||
|
import ghidra.pcode.loadimage.LoadImage;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
|
||||||
|
public interface MemoryLoadImage extends LoadImage {
|
||||||
|
|
||||||
|
public void writeBack(byte[] bytes, int size, Address addr, int offset);
|
||||||
|
|
||||||
|
public void dispose();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,263 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.memory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import ghidra.pcode.error.LowlevelError;
|
||||||
|
import ghidra.pcode.memstate.MemoryFaultHandler;
|
||||||
|
import ghidra.pcode.memstate.MemoryPage;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.mem.*;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
// Derived from ProgramMappedMemory
|
||||||
|
public class ProgramLoadImage {
|
||||||
|
|
||||||
|
private Program program;
|
||||||
|
private AddressSetView initializedAddressSet;
|
||||||
|
private MemoryFaultHandler faultHandler;
|
||||||
|
|
||||||
|
public ProgramLoadImage(Program program, MemoryFaultHandler faultHandler) {
|
||||||
|
this.program = program;
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
initializedAddressSet = memory.getLoadedAndInitializedAddressSet();
|
||||||
|
for (MemoryBlock block : memory.getBlocks()) {
|
||||||
|
if (!block.isInitialized() && (block instanceof MappedMemoryBlock)) {
|
||||||
|
initializedAddressSet = addMappedInitializedMemory((MappedMemoryBlock) block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.faultHandler = faultHandler;
|
||||||
|
// TODO: consider adding program consumer (would require proper dispose)
|
||||||
|
}
|
||||||
|
|
||||||
|
private AddressSetView addMappedInitializedMemory(MappedMemoryBlock mappedBlock) {
|
||||||
|
long size = mappedBlock.getSize();
|
||||||
|
if (size <= 0) {
|
||||||
|
// TODO: can't handle massive mapped blocks
|
||||||
|
return initializedAddressSet;
|
||||||
|
}
|
||||||
|
AddressSet modifiedSet = new AddressSet(initializedAddressSet);
|
||||||
|
Address mapStart = mappedBlock.getOverlayedMinAddress();
|
||||||
|
Address mapEnd = mapStart.add(size - 1);
|
||||||
|
AddressSet mappedAreas = initializedAddressSet.intersectRange(mapStart, mapEnd);
|
||||||
|
for (AddressRange range : mappedAreas) {
|
||||||
|
Address start = mappedBlock.getStart().add(range.getMinAddress().subtract(mapStart));
|
||||||
|
Address end = mappedBlock.getStart().add(range.getMaxAddress().subtract(mapStart));
|
||||||
|
modifiedSet.add(start, end);
|
||||||
|
}
|
||||||
|
return modifiedSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Need to investigate program write-back transaction issues -
|
||||||
|
// it could also be very expensive writing memory without some form of write-back cache
|
||||||
|
public void write(byte[] bytes, int size, Address addr, int offset) {
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
int currentOffset = offset;
|
||||||
|
int remaining = size;
|
||||||
|
Address nextAddr = addr;
|
||||||
|
Address endAddr;
|
||||||
|
try {
|
||||||
|
endAddr = addr.addNoWrap(size - 1);
|
||||||
|
}
|
||||||
|
catch (AddressOverflowException e) {
|
||||||
|
throw new LowlevelError(
|
||||||
|
"Illegal memory write request: " + addr + ", length=" + size + " bytes");
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
int chunkSize = remaining;
|
||||||
|
AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true);
|
||||||
|
AddressRange range = it.hasNext() ? it.next() : null;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Begin change for addressSet changes - wcb
|
||||||
|
///
|
||||||
|
|
||||||
|
if (range == null) {
|
||||||
|
// nextAddr not in memory and is bigger that any initialized memory
|
||||||
|
handleWriteFault(bytes, currentOffset, remaining, nextAddr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (range.contains(nextAddr)) {
|
||||||
|
// nextAddr is in memory
|
||||||
|
if (endAddr.compareTo(range.getMaxAddress()) > 0) {
|
||||||
|
chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
memory.setBytes(nextAddr, bytes, currentOffset, chunkSize);
|
||||||
|
}
|
||||||
|
catch (MemoryAccessException e) {
|
||||||
|
throw new LowlevelError("Unexpected memory write error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// nextAddr not in initialized memory, but is less than some initialized range
|
||||||
|
Address rangeAddr = range.getMinAddress();
|
||||||
|
if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) {
|
||||||
|
handleWriteFault(bytes, currentOffset, remaining, nextAddr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
long gapSize = rangeAddr.subtract(nextAddr);
|
||||||
|
chunkSize = (int) Math.min(gapSize, remaining);
|
||||||
|
handleWriteFault(bytes, currentOffset, chunkSize, nextAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// End change for addressSet changes - wcb
|
||||||
|
///
|
||||||
|
|
||||||
|
if (chunkSize == remaining) {
|
||||||
|
break; // done
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare for next chunk
|
||||||
|
try {
|
||||||
|
nextAddr = nextAddr.addNoWrap(chunkSize);
|
||||||
|
}
|
||||||
|
catch (AddressOverflowException e) {
|
||||||
|
throw new LowlevelError("Unexpected error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
currentOffset += chunkSize;
|
||||||
|
remaining -= chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleWriteFault(byte[] bytes, int currentOffset, int remaining,
|
||||||
|
Address nextAddr) {
|
||||||
|
// TODO: Should we create blocks or convert to initialized as needed ?
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(byte[] bytes, int size, Address addr, int offset,
|
||||||
|
boolean generateInitializedMask) {
|
||||||
|
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
int currentOffset = offset;
|
||||||
|
int remaining = size;
|
||||||
|
Address nextAddr = addr;
|
||||||
|
Address endAddr;
|
||||||
|
byte[] initializedMask = null;
|
||||||
|
try {
|
||||||
|
endAddr = addr.addNoWrap(size - 1);
|
||||||
|
}
|
||||||
|
catch (AddressOverflowException e) {
|
||||||
|
throw new LowlevelError(
|
||||||
|
"Illegal memory read request: " + addr + ", length=" + size + " bytes");
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
int chunkSize = remaining;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Begin change for addressSet changes - wcb
|
||||||
|
///
|
||||||
|
|
||||||
|
AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true);
|
||||||
|
AddressRange range = it.hasNext() ? it.next() : null;
|
||||||
|
|
||||||
|
if (range == null) {
|
||||||
|
if (generateInitializedMask) {
|
||||||
|
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
|
||||||
|
remaining, initializedMask);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleReadFault(bytes, currentOffset, remaining, nextAddr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (range.contains(nextAddr)) {
|
||||||
|
// nextAddr found in initialized memory
|
||||||
|
if (endAddr.compareTo(range.getMaxAddress()) > 0) {
|
||||||
|
chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
memory.getBytes(nextAddr, bytes, currentOffset, chunkSize);
|
||||||
|
}
|
||||||
|
catch (MemoryAccessException e) {
|
||||||
|
//throw new LowlevelError("Unexpected memory read error: " + e.getMessage());
|
||||||
|
Msg.warn(this, "Unexpected memory read error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Address rangeAddr = range.getMinAddress();
|
||||||
|
if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) {
|
||||||
|
if (generateInitializedMask) {
|
||||||
|
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
|
||||||
|
remaining, initializedMask);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleReadFault(bytes, currentOffset, remaining, nextAddr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
long gapSize = rangeAddr.subtract(nextAddr);
|
||||||
|
chunkSize = (gapSize > 0) ? (int) Math.min(gapSize, remaining) : remaining;
|
||||||
|
if (generateInitializedMask) {
|
||||||
|
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
|
||||||
|
chunkSize, initializedMask);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleReadFault(bytes, currentOffset, chunkSize, nextAddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
///
|
||||||
|
/// End change for addressSet changes - wcb
|
||||||
|
///
|
||||||
|
|
||||||
|
if (chunkSize == remaining) {
|
||||||
|
break; // done
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare for next chunk
|
||||||
|
try {
|
||||||
|
nextAddr = nextAddr.addNoWrap(chunkSize);
|
||||||
|
}
|
||||||
|
catch (AddressOverflowException e) {
|
||||||
|
throw new LowlevelError("Unexpected error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
currentOffset += chunkSize;
|
||||||
|
remaining -= chunkSize;
|
||||||
|
}
|
||||||
|
return initializedMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getInitializedMask(int bufsize, int initialOffset,
|
||||||
|
int uninitializedOffset, int uninitializedSize, byte[] initializedMask) {
|
||||||
|
if (initializedMask == null) {
|
||||||
|
initializedMask = MemoryPage.getInitializedMask(bufsize, 0, initialOffset, false);
|
||||||
|
}
|
||||||
|
MemoryPage.setUninitialized(initializedMask, uninitializedOffset, uninitializedSize);
|
||||||
|
return initializedMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleReadFault(byte[] bytes, int offset, int size, Address addr) {
|
||||||
|
// NOTE: This can trigger a load from a different external library depending upon the specific fault handler installed
|
||||||
|
Arrays.fill(bytes, offset, offset + size, (byte) 0);
|
||||||
|
if (faultHandler != null) {
|
||||||
|
faultHandler.uninitializedRead(addr, size, bytes, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressSetView getInitializedAddressSet() {
|
||||||
|
return initializedAddressSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.memory;
|
||||||
|
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
|
||||||
|
public class ProgramMappedLoadImage implements MemoryLoadImage {
|
||||||
|
|
||||||
|
private ProgramMappedMemory pmm;
|
||||||
|
//private Language lang;
|
||||||
|
|
||||||
|
public ProgramMappedLoadImage(ProgramMappedMemory memory) {
|
||||||
|
this.pmm = memory;
|
||||||
|
//this.lang = memory.getProgram().getLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] loadFill(byte[] bytes, int size, Address addr, int offset, boolean generateInitializedMask) {
|
||||||
|
return pmm.read(bytes, size, addr, offset, generateInitializedMask);
|
||||||
|
// boolean initialized = false;
|
||||||
|
// for (byte b : bytes) {
|
||||||
|
// if (b != 0) {
|
||||||
|
// initialized = true;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return generateInitializedMask ? MemoryPage.getInitializedMask(size, initialized) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeBack(byte[] bytes, int size, Address addr, int offset) {
|
||||||
|
pmm.write(bytes, size, addr, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
pmm.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.memory;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import ghidra.pcode.error.LowlevelError;
|
||||||
|
import ghidra.pcode.memstate.MemoryFaultHandler;
|
||||||
|
import ghidra.pcode.memstate.MemoryPage;
|
||||||
|
import ghidra.program.model.address.*;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.mem.*;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
public class ProgramMappedMemory {
|
||||||
|
|
||||||
|
private Program program;
|
||||||
|
|
||||||
|
private AddressSetView initializedAddressSet;
|
||||||
|
|
||||||
|
private MemoryFaultHandler faultHandler;
|
||||||
|
|
||||||
|
public ProgramMappedMemory(Program program, MemoryFaultHandler faultHandler) {
|
||||||
|
this.program = program;
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
|
||||||
|
initializedAddressSet = memory.getLoadedAndInitializedAddressSet();
|
||||||
|
for (MemoryBlock block : memory.getBlocks()) {
|
||||||
|
if (!block.isInitialized() && (block instanceof MappedMemoryBlock)) {
|
||||||
|
initializedAddressSet = addMappedInitializedMemory((MappedMemoryBlock) block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
program.addConsumer(this);
|
||||||
|
this.faultHandler = faultHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AddressSetView addMappedInitializedMemory(MappedMemoryBlock mappedBlock) {
|
||||||
|
long size = mappedBlock.getSize();
|
||||||
|
if (size <= 0) {
|
||||||
|
// TODO: can't handle massive mapped blocks
|
||||||
|
return initializedAddressSet;
|
||||||
|
}
|
||||||
|
AddressSet modifiedSet = new AddressSet(initializedAddressSet);
|
||||||
|
Address mapStart = mappedBlock.getOverlayedMinAddress();
|
||||||
|
Address mapEnd = mapStart.add(size - 1);
|
||||||
|
AddressSet mappedAreas = initializedAddressSet.intersectRange(mapStart, mapEnd);
|
||||||
|
for (AddressRange range : mappedAreas) {
|
||||||
|
Address start = mappedBlock.getStart().add(range.getMinAddress().subtract(mapStart));
|
||||||
|
Address end = mappedBlock.getStart().add(range.getMaxAddress().subtract(mapStart));
|
||||||
|
modifiedSet.add(start, end);
|
||||||
|
}
|
||||||
|
return modifiedSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Program getProgram() {
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
if (program != null) {
|
||||||
|
program.release(this);
|
||||||
|
program = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Need to investigate program write-back transaction issues -
|
||||||
|
// it could also be very expensive writing memory without some form of write-back cache
|
||||||
|
public void write(byte[] bytes, int size, Address addr, int offset) {
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
int currentOffset = offset;
|
||||||
|
int remaining = size;
|
||||||
|
Address nextAddr = addr;
|
||||||
|
Address endAddr;
|
||||||
|
try {
|
||||||
|
endAddr = addr.addNoWrap(size - 1);
|
||||||
|
}
|
||||||
|
catch (AddressOverflowException e) {
|
||||||
|
throw new LowlevelError(
|
||||||
|
"Illegal memory write request: " + addr + ", length=" + size + " bytes");
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
int chunkSize = remaining;
|
||||||
|
AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true);
|
||||||
|
AddressRange range = it.hasNext() ? it.next() : null;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Begin change for addressSet changes - wcb
|
||||||
|
///
|
||||||
|
|
||||||
|
if (range == null) {
|
||||||
|
// nextAddr not in memory and is bigger that any initialized memory
|
||||||
|
handleWriteFault(bytes, currentOffset, remaining, nextAddr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (range.contains(nextAddr)) {
|
||||||
|
// nextAddr is in memory
|
||||||
|
if (endAddr.compareTo(range.getMaxAddress()) > 0) {
|
||||||
|
chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
memory.setBytes(nextAddr, bytes, currentOffset, chunkSize);
|
||||||
|
}
|
||||||
|
catch (MemoryAccessException e) {
|
||||||
|
throw new LowlevelError("Unexpected memory write error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// nextAddr not in initialized memory, but is less than some initialized range
|
||||||
|
Address rangeAddr = range.getMinAddress();
|
||||||
|
if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) {
|
||||||
|
handleWriteFault(bytes, currentOffset, remaining, nextAddr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
long gapSize = rangeAddr.subtract(nextAddr);
|
||||||
|
chunkSize = (int) Math.min(gapSize, remaining);
|
||||||
|
handleWriteFault(bytes, currentOffset, chunkSize, nextAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// End change for addressSet changes - wcb
|
||||||
|
///
|
||||||
|
|
||||||
|
if (chunkSize == remaining) {
|
||||||
|
break; // done
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare for next chunk
|
||||||
|
try {
|
||||||
|
nextAddr = nextAddr.addNoWrap(chunkSize);
|
||||||
|
}
|
||||||
|
catch (AddressOverflowException e) {
|
||||||
|
throw new LowlevelError("Unexpected error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
currentOffset += chunkSize;
|
||||||
|
remaining -= chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleWriteFault(byte[] bytes, int currentOffset, int remaining,
|
||||||
|
Address nextAddr) {
|
||||||
|
// TODO: Should we create blocks or convert to initialized as needed ?
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] read(byte[] bytes, int size, Address addr, int offset,
|
||||||
|
boolean generateInitializedMask) {
|
||||||
|
|
||||||
|
Memory memory = program.getMemory();
|
||||||
|
int currentOffset = offset;
|
||||||
|
int remaining = size;
|
||||||
|
Address nextAddr = addr;
|
||||||
|
Address endAddr;
|
||||||
|
byte[] initializedMask = null;
|
||||||
|
try {
|
||||||
|
endAddr = addr.addNoWrap(size - 1);
|
||||||
|
}
|
||||||
|
catch (AddressOverflowException e) {
|
||||||
|
throw new LowlevelError(
|
||||||
|
"Illegal memory read request: " + addr + ", length=" + size + " bytes");
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
int chunkSize = remaining;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Begin change for addressSet changes - wcb
|
||||||
|
///
|
||||||
|
|
||||||
|
AddressRangeIterator it = initializedAddressSet.getAddressRanges(nextAddr, true);
|
||||||
|
AddressRange range = it.hasNext() ? it.next() : null;
|
||||||
|
|
||||||
|
if (range == null) {
|
||||||
|
if (generateInitializedMask) {
|
||||||
|
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
|
||||||
|
remaining, initializedMask);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleReadFault(bytes, currentOffset, remaining, nextAddr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (range.contains(nextAddr)) {
|
||||||
|
// nextAddr found in initialized memory
|
||||||
|
if (endAddr.compareTo(range.getMaxAddress()) > 0) {
|
||||||
|
chunkSize = (int) (range.getMaxAddress().subtract(nextAddr) + 1);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
memory.getBytes(nextAddr, bytes, currentOffset, chunkSize);
|
||||||
|
}
|
||||||
|
catch (MemoryAccessException e) {
|
||||||
|
//throw new LowlevelError("Unexpected memory read error: " + e.getMessage());
|
||||||
|
Msg.warn(this, "Unexpected memory read error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Address rangeAddr = range.getMinAddress();
|
||||||
|
if (!rangeAddr.getAddressSpace().equals(addr.getAddressSpace())) {
|
||||||
|
if (generateInitializedMask) {
|
||||||
|
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
|
||||||
|
remaining, initializedMask);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleReadFault(bytes, currentOffset, remaining, nextAddr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
long gapSize = rangeAddr.subtract(nextAddr);
|
||||||
|
chunkSize = (gapSize > 0) ? (int) Math.min(gapSize, remaining) : remaining;
|
||||||
|
if (generateInitializedMask) {
|
||||||
|
initializedMask = getInitializedMask(bytes.length, offset, currentOffset,
|
||||||
|
chunkSize, initializedMask);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handleReadFault(bytes, currentOffset, chunkSize, nextAddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
///
|
||||||
|
/// End change for addressSet changes - wcb
|
||||||
|
///
|
||||||
|
|
||||||
|
if (chunkSize == remaining) {
|
||||||
|
break; // done
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare for next chunk
|
||||||
|
try {
|
||||||
|
nextAddr = nextAddr.addNoWrap(chunkSize);
|
||||||
|
}
|
||||||
|
catch (AddressOverflowException e) {
|
||||||
|
throw new LowlevelError("Unexpected error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
currentOffset += chunkSize;
|
||||||
|
remaining -= chunkSize;
|
||||||
|
}
|
||||||
|
return initializedMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getInitializedMask(int bufsize, int initialOffset,
|
||||||
|
int uninitializedOffset, int uninitializedSize, byte[] initializedMask) {
|
||||||
|
if (initializedMask == null) {
|
||||||
|
initializedMask = MemoryPage.getInitializedMask(bufsize, 0, initialOffset, false);
|
||||||
|
}
|
||||||
|
MemoryPage.setUninitialized(initializedMask, uninitializedOffset, uninitializedSize);
|
||||||
|
return initializedMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleReadFault(byte[] bytes, int offset, int size, Address addr) {
|
||||||
|
// NOTE: This can trigger a load from a different external library depending upon the specific fault handler installed
|
||||||
|
Arrays.fill(bytes, offset, offset + size, (byte) 0);
|
||||||
|
if (faultHandler != null) {
|
||||||
|
faultHandler.uninitializedRead(addr, size, bytes, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddressSetView getInitializedAddressSet() {
|
||||||
|
return initializedAddressSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.state;
|
||||||
|
|
||||||
|
import generic.stl.Pair;
|
||||||
|
import ghidra.program.model.lang.Language;
|
||||||
|
import ghidra.util.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class DumpMiscState implements RegisterState {
|
||||||
|
|
||||||
|
private Map<String, Pair<Boolean, byte[]>> context =
|
||||||
|
new HashMap<String, Pair<Boolean, byte[]>>();
|
||||||
|
|
||||||
|
private DataConverter dc;
|
||||||
|
|
||||||
|
public DumpMiscState(Language lang) {
|
||||||
|
dc =
|
||||||
|
lang.isBigEndian() ? BigEndianDataConverter.INSTANCE
|
||||||
|
: LittleEndianDataConverter.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
context.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getKeys() {
|
||||||
|
return context.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<byte[]> getVals(String key) {
|
||||||
|
List<byte[]> list = new ArrayList<byte[]>();
|
||||||
|
Pair<Boolean, byte[]> pair = context.get(key);
|
||||||
|
if (pair != null && pair.second != null) {
|
||||||
|
list.add(pair.second);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Boolean> isInitialized(String key) {
|
||||||
|
List<Boolean> list = new ArrayList<Boolean>();
|
||||||
|
Pair<Boolean, byte[]> pair = context.get(key);
|
||||||
|
if (pair != null && pair.first != null) {
|
||||||
|
list.add(pair.first);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
list.add(Boolean.FALSE);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVals(String key, byte[] vals, boolean setInitiailized) {
|
||||||
|
Pair<Boolean, byte[]> pair = new Pair<Boolean, byte[]>(setInitiailized, vals);
|
||||||
|
context.put(key, pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVals(String key, long val, int size, boolean setInitiailized) {
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
dc.getBytes(val, size, bytes, 0);
|
||||||
|
setVals(key, bytes, setInitiailized);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.state;
|
||||||
|
|
||||||
|
import ghidra.pcode.memstate.MemoryBank;
|
||||||
|
import ghidra.pcode.memstate.MemoryPageOverlay;
|
||||||
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
|
||||||
|
public class FilteredMemoryPageOverlay extends MemoryPageOverlay {
|
||||||
|
|
||||||
|
private boolean writeBack;
|
||||||
|
|
||||||
|
public FilteredMemoryPageOverlay(AddressSpace spc, MemoryBank ul, boolean writeBack) {
|
||||||
|
super(spc, ul, ul.getMemoryFaultHandler());
|
||||||
|
this.writeBack = writeBack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChunk(long offset, int size, byte[] val) {
|
||||||
|
super.setChunk(offset, size, val);
|
||||||
|
if (writeBack) {
|
||||||
|
underlie.setChunk(offset, size, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* ###
|
||||||
|
* 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.emulator.state;
|
||||||
|
|
||||||
|
import ghidra.pcode.memstate.MemoryFaultHandler;
|
||||||
|
import ghidra.pcode.memstate.MemoryPageBank;
|
||||||
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
import ghidra.program.model.lang.Language;
|
||||||
|
|
||||||
|
public class FilteredRegisterBank extends MemoryPageBank {
|
||||||
|
|
||||||
|
//private final RegisterState regState;
|
||||||
|
//private final boolean writeBack;
|
||||||
|
|
||||||
|
public FilteredRegisterBank(AddressSpace spc, int ps, RegisterState initState, Language lang, boolean writeBack, MemoryFaultHandler faultHandler) {
|
||||||
|
super(spc, lang.isBigEndian(), ps, faultHandler);
|
||||||
|
//regState = initState;
|
||||||
|
//this.writeBack = writeBack;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.emulator.state;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface RegisterState {
|
||||||
|
|
||||||
|
public Set<String> getKeys();
|
||||||
|
|
||||||
|
public List<byte[]> getVals(String key);
|
||||||
|
|
||||||
|
public List<Boolean> isInitialized(String key);
|
||||||
|
|
||||||
|
public void setVals(String key, byte[] vals, boolean setInitiailized);
|
||||||
|
|
||||||
|
public void setVals(String key, long val, int size, boolean setInitiailized);
|
||||||
|
|
||||||
|
public void dispose();
|
||||||
|
|
||||||
|
}
|
|
@ -31,6 +31,8 @@ import ghidra.program.model.listing.Instruction;
|
||||||
import ghidra.program.model.pcode.PcodeOp;
|
import ghidra.program.model.pcode.PcodeOp;
|
||||||
import ghidra.program.model.pcode.Varnode;
|
import ghidra.program.model.pcode.Varnode;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
import ghidra.util.task.TaskMonitorAdapter;
|
import ghidra.util.task.TaskMonitorAdapter;
|
||||||
/// \brief A SLEIGH based implementation of the Emulate interface
|
/// \brief A SLEIGH based implementation of the Emulate interface
|
||||||
///
|
///
|
||||||
|
@ -78,7 +80,8 @@ public class Emulate {
|
||||||
pcReg = lang.getProgramCounter();
|
pcReg = lang.getProgramCounter();
|
||||||
breaktable = b;
|
breaktable = b;
|
||||||
breaktable.setEmulate(this);
|
breaktable.setEmulate(this);
|
||||||
memBuffer = new EmulateMemoryStateBuffer(s, addrFactory.getDefaultAddressSpace().getMinAddress());
|
memBuffer =
|
||||||
|
new EmulateMemoryStateBuffer(s, addrFactory.getDefaultAddressSpace().getMinAddress());
|
||||||
|
|
||||||
uniqueBank =
|
uniqueBank =
|
||||||
new UniqueMemoryBank(lang.getAddressFactory().getUniqueSpace(), lang.isBigEndian());
|
new UniqueMemoryBank(lang.getAddressFactory().getUniqueSpace(), lang.isBigEndian());
|
||||||
|
@ -91,24 +94,24 @@ public class Emulate {
|
||||||
|
|
||||||
initInstuctionStateModifier();
|
initInstuctionStateModifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
executionState = EmulateExecutionState.STOPPED;
|
executionState = EmulateExecutionState.STOPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void initInstuctionStateModifier() {
|
private void initInstuctionStateModifier() {
|
||||||
String classname =
|
String classname = language.getProperty(
|
||||||
language.getProperty(GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS);
|
GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS);
|
||||||
if (classname == null) {
|
if (classname == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Class<?> c = Class.forName(classname);
|
Class<?> c = Class.forName(classname);
|
||||||
if (!EmulateInstructionStateModifier.class.isAssignableFrom(c)) {
|
if (!EmulateInstructionStateModifier.class.isAssignableFrom(c)) {
|
||||||
Msg.error(this, "Language " + language.getLanguageID() +
|
Msg.error(this,
|
||||||
" does not specify a valid " +
|
"Language " + language.getLanguageID() + " does not specify a valid " +
|
||||||
GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS);
|
GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS);
|
||||||
throw new RuntimeException(classname + " does not implement interface " +
|
throw new RuntimeException(classname + " does not implement interface " +
|
||||||
EmulateInstructionStateModifier.class.getName());
|
EmulateInstructionStateModifier.class.getName());
|
||||||
}
|
}
|
||||||
|
@ -121,8 +124,9 @@ public class Emulate {
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.error(this, "Language " + language.getLanguageID() + " does not specify a valid " +
|
Msg.error(this, "Language " + language.getLanguageID() + " does not specify a valid " +
|
||||||
GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS);
|
GhidraLanguagePropertyKeys.EMULATE_INSTRUCTION_STATE_MODIFIER_CLASS);
|
||||||
throw new RuntimeException("Failed to instantiate " + classname + " for language " +
|
throw new RuntimeException(
|
||||||
language.getLanguageID(), e);
|
"Failed to instantiate " + classname + " for language " + language.getLanguageID(),
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,11 +371,12 @@ public class Emulate {
|
||||||
/// and invoked as needed for the current address. If this routine is invoked while execution is
|
/// and invoked as needed for the current address. If this routine is invoked while execution is
|
||||||
/// in the middle of a machine instruction, execution is continued until the current instruction
|
/// in the middle of a machine instruction, execution is continued until the current instruction
|
||||||
/// completes.
|
/// completes.
|
||||||
public void executeInstruction(boolean stopAtBreakpoint) throws LowlevelError,
|
public void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
|
||||||
InstructionDecodeException {
|
throws CancelledException, LowlevelError, InstructionDecodeException {
|
||||||
if (executionState == EmulateExecutionState.STOPPED) {
|
if (executionState == EmulateExecutionState.STOPPED) {
|
||||||
if (last_execute_address == null && instructionStateModifier != null) {
|
if (last_execute_address == null && instructionStateModifier != null) {
|
||||||
instructionStateModifier.initialExecuteCallback(this, current_address, nextContextRegisterValue);
|
instructionStateModifier.initialExecuteCallback(this, current_address,
|
||||||
|
nextContextRegisterValue);
|
||||||
}
|
}
|
||||||
if (breaktable.doAddressBreak(current_address) && stopAtBreakpoint) {
|
if (breaktable.doAddressBreak(current_address) && stopAtBreakpoint) {
|
||||||
executionState = EmulateExecutionState.BREAKPOINT;
|
executionState = EmulateExecutionState.BREAKPOINT;
|
||||||
|
@ -400,6 +405,7 @@ public class Emulate {
|
||||||
}
|
}
|
||||||
executionState = EmulateExecutionState.EXECUTE;
|
executionState = EmulateExecutionState.EXECUTE;
|
||||||
do {
|
do {
|
||||||
|
monitor.checkCanceled();
|
||||||
executeCurrentOp();
|
executeCurrentOp();
|
||||||
}
|
}
|
||||||
while (executionState == EmulateExecutionState.EXECUTE);
|
while (executionState == EmulateExecutionState.EXECUTE);
|
||||||
|
@ -441,8 +447,8 @@ public class Emulate {
|
||||||
OpBehavior behave = raw.getBehavior();
|
OpBehavior behave = raw.getBehavior();
|
||||||
if (behave == null) {
|
if (behave == null) {
|
||||||
// unsupported opcode
|
// unsupported opcode
|
||||||
throw new LowlevelError("Unsupported pcode op (opcode=" + op.getOpcode() + ", seq=" +
|
throw new LowlevelError(
|
||||||
op.getSeqnum() + ")");
|
"Unsupported pcode op (opcode=" + op.getOpcode() + ", seq=" + op.getSeqnum() + ")");
|
||||||
}
|
}
|
||||||
if (behave instanceof UnaryOpBehavior) {
|
if (behave instanceof UnaryOpBehavior) {
|
||||||
UnaryOpBehavior uniaryBehave = (UnaryOpBehavior) behave;
|
UnaryOpBehavior uniaryBehave = (UnaryOpBehavior) behave;
|
||||||
|
@ -450,16 +456,14 @@ public class Emulate {
|
||||||
Varnode outvar = op.getOutput();
|
Varnode outvar = op.getOutput();
|
||||||
if (in1var.getSize() > 8 || outvar.getSize() > 8) {
|
if (in1var.getSize() > 8 || outvar.getSize() > 8) {
|
||||||
BigInteger in1 = memstate.getBigInteger(op.getInput(0), false);
|
BigInteger in1 = memstate.getBigInteger(op.getInput(0), false);
|
||||||
BigInteger out =
|
BigInteger out = uniaryBehave.evaluateUnary(op.getOutput().getSize(),
|
||||||
uniaryBehave.evaluateUnary(op.getOutput().getSize(), op.getInput(0).getSize(),
|
op.getInput(0).getSize(), in1);
|
||||||
in1);
|
|
||||||
memstate.setValue(op.getOutput(), out);
|
memstate.setValue(op.getOutput(), out);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
long in1 = memstate.getValue(op.getInput(0));
|
long in1 = memstate.getValue(op.getInput(0));
|
||||||
long out =
|
long out = uniaryBehave.evaluateUnary(op.getOutput().getSize(),
|
||||||
uniaryBehave.evaluateUnary(op.getOutput().getSize(), op.getInput(0).getSize(),
|
op.getInput(0).getSize(), in1);
|
||||||
in1);
|
|
||||||
memstate.setValue(op.getOutput(), out);
|
memstate.setValue(op.getOutput(), out);
|
||||||
}
|
}
|
||||||
fallthruOp();
|
fallthruOp();
|
||||||
|
@ -471,17 +475,15 @@ public class Emulate {
|
||||||
if (in1var.getSize() > 8 || outvar.getSize() > 8) {
|
if (in1var.getSize() > 8 || outvar.getSize() > 8) {
|
||||||
BigInteger in1 = memstate.getBigInteger(op.getInput(0), false);
|
BigInteger in1 = memstate.getBigInteger(op.getInput(0), false);
|
||||||
BigInteger in2 = memstate.getBigInteger(op.getInput(1), false);
|
BigInteger in2 = memstate.getBigInteger(op.getInput(1), false);
|
||||||
BigInteger out =
|
BigInteger out = binaryBehave.evaluateBinary(outvar.getSize(),
|
||||||
binaryBehave.evaluateBinary(outvar.getSize(), op.getInput(0).getSize(), in1,
|
op.getInput(0).getSize(), in1, in2);
|
||||||
in2);
|
|
||||||
memstate.setValue(outvar, out);
|
memstate.setValue(outvar, out);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
long in1 = memstate.getValue(op.getInput(0));
|
long in1 = memstate.getValue(op.getInput(0));
|
||||||
long in2 = memstate.getValue(op.getInput(1));
|
long in2 = memstate.getValue(op.getInput(1));
|
||||||
long out =
|
long out = binaryBehave.evaluateBinary(outvar.getSize(), op.getInput(0).getSize(),
|
||||||
binaryBehave.evaluateBinary(outvar.getSize(), op.getInput(0).getSize(), in1,
|
in1, in2);
|
||||||
in2);
|
|
||||||
memstate.setValue(outvar, out);
|
memstate.setValue(outvar, out);
|
||||||
}
|
}
|
||||||
fallthruOp(); // All binary ops are fallthrus
|
fallthruOp(); // All binary ops are fallthrus
|
||||||
|
@ -762,4 +764,3 @@ public class Emulate {
|
||||||
- PcodeOpRaw and
|
- PcodeOpRaw and
|
||||||
- VarnodeData
|
- VarnodeData
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
See the example emulation scripts contained within Ghidra/Features/Base/ghidra_scripts.
|
||||||
|
|
||||||
|
Sample scripts deobExampleX86 and deobHookExampleX86 may be built under Linux.
|
||||||
|
|
||||||
|
cc -std=c99 -Wimplicit-function-declaration -o deobExampleX86 deobExample.c
|
||||||
|
cc -std=c99 -Wimplicit-function-declaration -o deobHookExampleX86 deobHookExample.c
|
||||||
|
|
||||||
|
Once these examples have been compiled they may be imported into a Ghidra project and the
|
||||||
|
corresponding Ghidra Scripts (EmuX86DeobfuscateExampleScript and EmuX86GccDeobfuscateHookExampleScript)
|
||||||
|
used to demonstrate the use of the EmulatorHelper class.
|
|
@ -0,0 +1,100 @@
|
||||||
|
/* ###
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
int length(char *s) {
|
||||||
|
int len = 0;
|
||||||
|
while (*s++ != 0) {
|
||||||
|
++len;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char data[] = {
|
||||||
|
0xec,
|
||||||
|
0xc3,
|
||||||
|
0xd8,
|
||||||
|
0xd9,
|
||||||
|
0xde,
|
||||||
|
0x8a,
|
||||||
|
0xcf,
|
||||||
|
0xc4,
|
||||||
|
0xde,
|
||||||
|
0xd8,
|
||||||
|
0xd3,
|
||||||
|
0x00,
|
||||||
|
0xf9,
|
||||||
|
0xcf,
|
||||||
|
0xc9,
|
||||||
|
0xc5,
|
||||||
|
0xc4,
|
||||||
|
0xce,
|
||||||
|
0x8a,
|
||||||
|
0xcf,
|
||||||
|
0xc4,
|
||||||
|
0xde,
|
||||||
|
0xd8,
|
||||||
|
0xd3,
|
||||||
|
0x00,
|
||||||
|
0xfe,
|
||||||
|
0xc2,
|
||||||
|
0xc3,
|
||||||
|
0xd8,
|
||||||
|
0xce,
|
||||||
|
0x8a,
|
||||||
|
0xcf,
|
||||||
|
0xc4,
|
||||||
|
0xde,
|
||||||
|
0xd8,
|
||||||
|
0xd3,
|
||||||
|
0x00,
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
char buffer[64];
|
||||||
|
|
||||||
|
char * deobfuscate(char *src, char *dst, int len) {
|
||||||
|
char *ptr = dst;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
*ptr++ = *src++ ^ 0xAA;
|
||||||
|
}
|
||||||
|
*ptr = 0;
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
void use_string(char * str, int index) {
|
||||||
|
// fprintf(stderr, "String[%d]: %s\n", index, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char **argv) {
|
||||||
|
char *ptr = (char *)data;
|
||||||
|
int index = 0;
|
||||||
|
while (*ptr != 0) {
|
||||||
|
int len = length(ptr);
|
||||||
|
char *str = deobfuscate(ptr, buffer, len);
|
||||||
|
use_string(str, index++);
|
||||||
|
ptr += len + 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __x86_64
|
||||||
|
|
||||||
|
|
||||||
|
int _start() {
|
||||||
|
char *argv[] = { "deobExample" };
|
||||||
|
return main(1, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,120 @@
|
||||||
|
/* ###
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
#ifdef __x86_64
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// Library routine not linked in for cross-build
|
||||||
|
|
||||||
|
void * malloc(int size) {
|
||||||
|
// missing implementation
|
||||||
|
return (void *)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void free(void *ptr) {
|
||||||
|
// missing implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
int strlen(char *s) {
|
||||||
|
int len = 0;
|
||||||
|
while (*s++ != 0) {
|
||||||
|
++len;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char data[] = {
|
||||||
|
0xec,
|
||||||
|
0xc3,
|
||||||
|
0xd8,
|
||||||
|
0xd9,
|
||||||
|
0xde,
|
||||||
|
0x8a,
|
||||||
|
0xcf,
|
||||||
|
0xc4,
|
||||||
|
0xde,
|
||||||
|
0xd8,
|
||||||
|
0xd3,
|
||||||
|
0x00,
|
||||||
|
0xf9,
|
||||||
|
0xcf,
|
||||||
|
0xc9,
|
||||||
|
0xc5,
|
||||||
|
0xc4,
|
||||||
|
0xce,
|
||||||
|
0x8a,
|
||||||
|
0xcf,
|
||||||
|
0xc4,
|
||||||
|
0xde,
|
||||||
|
0xd8,
|
||||||
|
0xd3,
|
||||||
|
0x00,
|
||||||
|
0xfe,
|
||||||
|
0xc2,
|
||||||
|
0xc3,
|
||||||
|
0xd8,
|
||||||
|
0xce,
|
||||||
|
0x8a,
|
||||||
|
0xcf,
|
||||||
|
0xc4,
|
||||||
|
0xde,
|
||||||
|
0xd8,
|
||||||
|
0xd3,
|
||||||
|
0x00,
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
char * deobfuscate(char *src, int len) {
|
||||||
|
char *buf = (char *)malloc(len + 1);
|
||||||
|
char *ptr = buf;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
*ptr++ = *src++ ^ 0xAA;
|
||||||
|
}
|
||||||
|
*ptr = 0;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void use_string(char * str, int index) {
|
||||||
|
// fprintf(stderr, "String[%d]: %s\n", index, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main (int argc, char **argv) {
|
||||||
|
char *ptr = (char *)data;
|
||||||
|
int index = 0;
|
||||||
|
while (*ptr != 0) {
|
||||||
|
int len = strlen(ptr);
|
||||||
|
char *str = deobfuscate(ptr, len);
|
||||||
|
use_string(str, index++);
|
||||||
|
free(str);
|
||||||
|
ptr += len + 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __x86_64
|
||||||
|
|
||||||
|
|
||||||
|
int _start() {
|
||||||
|
char *argv[] = { "deobExample" };
|
||||||
|
return main(1, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -39,6 +39,7 @@ GhidraClass/ExerciseFiles/Advanced/override.so||GHIDRA||||END|
|
||||||
GhidraClass/ExerciseFiles/Advanced/setRegister||GHIDRA||||END|
|
GhidraClass/ExerciseFiles/Advanced/setRegister||GHIDRA||||END|
|
||||||
GhidraClass/ExerciseFiles/Advanced/sharedReturn||GHIDRA||||END|
|
GhidraClass/ExerciseFiles/Advanced/sharedReturn||GHIDRA||||END|
|
||||||
GhidraClass/ExerciseFiles/Advanced/switch||GHIDRA||||END|
|
GhidraClass/ExerciseFiles/Advanced/switch||GHIDRA||||END|
|
||||||
|
GhidraClass/ExerciseFiles/Emulation/Source/README.txt||GHIDRA||||END|
|
||||||
GhidraClass/ExerciseFiles/VersionTracking/WallaceSrc.exe||GHIDRA||||END|
|
GhidraClass/ExerciseFiles/VersionTracking/WallaceSrc.exe||GHIDRA||||END|
|
||||||
GhidraClass/ExerciseFiles/VersionTracking/WallaceVersion2.exe||GHIDRA||||END|
|
GhidraClass/ExerciseFiles/VersionTracking/WallaceVersion2.exe||GHIDRA||||END|
|
||||||
GhidraClass/ExerciseFiles/WinhelloCPP/WinHelloCPP.exe||GHIDRA||||END|
|
GhidraClass/ExerciseFiles/WinhelloCPP/WinHelloCPP.exe||GHIDRA||||END|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue