Merge remote-tracking branch 'origin/GP-2642_Dan_compatEmulatorHelper--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-03-14 06:23:07 -04:00
commit 254e749f95
25 changed files with 1815 additions and 741 deletions

View file

@ -155,6 +155,11 @@ class TestThread implements PcodeThread<Void> {
public void setSuspended(boolean suspended) { public void setSuspended(boolean suspended) {
} }
@Override
public boolean isSuspended() {
return false;
}
@Override @Override
public PcodeUseropLibrary<Void> getUseropLibrary() { public PcodeUseropLibrary<Void> getUseropLibrary() {
return null; return null;

View file

@ -63,7 +63,12 @@ public class EmulatorTestRunner {
this.program = program; this.program = program;
this.testGroup = testGroup; this.testGroup = testGroup;
this.executionListener = executionListener; this.executionListener = executionListener;
emuHelper = new EmulatorHelper(program); emuHelper = new EmulatorHelper(program) {
@Override
protected Emulator newEmulator() {
return new AdaptedEmulator(this);
}
};
emu = emuHelper.getEmulator(); emu = emuHelper.getEmulator();
emuHelper.setMemoryFaultHandler(new MyMemoryFaultHandler(executionListener)); emuHelper.setMemoryFaultHandler(new MyMemoryFaultHandler(executionListener));
@ -71,7 +76,7 @@ public class EmulatorTestRunner {
@Override @Override
public boolean pcodeCallback(PcodeOpRaw op) throws LowlevelError { public boolean pcodeCallback(PcodeOpRaw op) throws LowlevelError {
int userOp = (int) op.getInput(0).getOffset(); int userOp = (int) op.getInput(0).getOffset();
String pcodeOpName = emulate.getLanguage().getUserDefinedOpName(userOp); String pcodeOpName = program.getLanguage().getUserDefinedOpName(userOp);
unimplementedSet.add(pcodeOpName); unimplementedSet.add(pcodeOpName);
String outStr = ""; String outStr = "";
Varnode output = op.getOutput(); Varnode output = op.getOutput();
@ -169,6 +174,7 @@ public class EmulatorTestRunner {
/** /**
* Add memory dump point * Add memory dump point
*
* @param breakAddr instruction address at which execution should pause (before it is executed) * @param breakAddr instruction address at which execution should pause (before it is executed)
* so that the specified memory may be dumped to the log during trace execution mode. * so that the specified memory may be dumped to the log during trace execution mode.
* @param dumpAddr memory address which should be dumped * @param dumpAddr memory address which should be dumped
@ -190,6 +196,7 @@ public class EmulatorTestRunner {
/** /**
* Add memory dump point * Add memory dump point
*
* @param breakAddr instruction address at which execution should pause (before it is executed) * @param breakAddr instruction address at which execution should pause (before it is executed)
* so that the specified memory may be dumped to the log during trace execution mode. * so that the specified memory may be dumped to the log during trace execution mode.
* @param dumpAddrReg register containing the memory address offset which should be dumped * @param dumpAddrReg register containing the memory address offset which should be dumped
@ -230,11 +237,11 @@ public class EmulatorTestRunner {
} }
/** /**
* Get number of CALLOTHER errors detected when a test pass was registered. * Get number of CALLOTHER errors detected when a test pass was registered. This number should
* This number should be subtracted from the pass count and possibly added * be subtracted from the pass count and possibly added to the failure count. Number does not
* to the failure count. Number does not reflect total number of CALLOTHER * reflect total number of CALLOTHER pcodeops encountered but only the number of passed tests
* pcodeops encountered but only the number of passed tests affected. * affected. See log for all CALLOTHER executions detected.
* See log for all CALLOTHER executions detected. *
* @return number of CALLOTHER errors * @return number of CALLOTHER errors
*/ */
public int getCallOtherErrors() { public int getCallOtherErrors() {
@ -243,6 +250,7 @@ public class EmulatorTestRunner {
/** /**
* Execute test group without instruction stepping/tracing * Execute test group without instruction stepping/tracing
*
* @param timeLimitMS * @param timeLimitMS
* @param monitor * @param monitor
* @return * @return
@ -695,7 +703,8 @@ public class EmulatorTestRunner {
@Override @Override
Address getDumpAddress() { Address getDumpAddress() {
RegisterValue regVal = getRegisterValue(dumpAddrReg); RegisterValue regVal = getRegisterValue(dumpAddrReg);
return dumpAddrSpace.getAddress(regVal.getUnsignedValue().longValue()).add( return dumpAddrSpace.getAddress(regVal.getUnsignedValue().longValue())
.add(
relativeOffset); relativeOffset);
} }

View file

@ -0,0 +1,403 @@
/* ###
* 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 generic.ULongSpan;
import generic.ULongSpan.ULongSpanSet;
import ghidra.app.emulator.memory.MemoryLoadImage;
import ghidra.app.emulator.state.RegisterState;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.lifecycle.Transitional;
import ghidra.pcode.emu.*;
import ghidra.pcode.emu.PcodeMachine.SwiMode;
import ghidra.pcode.emulate.*;
import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.memstate.MemoryFaultHandler;
import ghidra.pcode.memstate.MemoryState;
import ghidra.pcode.pcoderaw.PcodeOpRaw;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* An implementation of {@link Emulator} that wraps the newer {@link PcodeEmulator}
*
* <p>
* This is a transitional utility only. It is currently used only by the pcode tests until that is
* ported to use the new {@link PcodeEmulator} directly. New use cases based on p-code emulation
* should use the {@link PcodeEmulator} directly. Older use cases still being actively maintained
* should begin work porting to {@link PcodeEmulator}. Old use cases without active maintenance may
* try this wrapper, but may have to remain using {@link DefaultEmulator}. At a minimum, to update
* such old use cases, `new Emulator(...)` must be replaced by `new DefaultEmulator(...)`.
*/
@Transitional
public class AdaptedEmulator implements Emulator {
class AdaptedPcodeEmulator extends PcodeEmulator {
private final MemoryLoadImage loadImage;
private final MemoryFaultHandler faultHandler;
public AdaptedPcodeEmulator(Language language, MemoryLoadImage loadImage,
MemoryFaultHandler faultHandler) {
super(language);
this.loadImage = loadImage;
this.faultHandler = faultHandler;
}
@Override
protected PcodeExecutorState<byte[]> createSharedState() {
return new AdaptedBytesPcodeExecutorState(language,
new StateBacking(faultHandler, loadImage));
}
@Override
protected PcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> thread) {
return new AdaptedBytesPcodeExecutorState(language,
new StateBacking(faultHandler, null));
}
@Override
protected AdaptedPcodeThread createThread(String name) {
return new AdaptedPcodeThread(name, this);
}
@Override
public AdaptedPcodeThread newThread() {
return (AdaptedPcodeThread) super.newThread();
}
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return new AdaptedPcodeUseropLibrary();
}
}
@Transitional
public class AdaptedPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary<byte[]> {
@PcodeUserop
public void __addr_cb() {
adaptedBreakTable.doAddressBreak(thread.getCounter());
if (thread.isSuspended()) {
/**
* This is the convention in DefaultEmulator: A breakpoint sets halt on the emulator
* to cause it to actually break. We'll translate that into an interrupt.
*/
throw new InterruptPcodeExecutionException(null, null);
}
}
}
class AdaptedPcodeThread extends BytesPcodeThread {
Address lastExecuteAddress;
public AdaptedPcodeThread(String name, AbstractPcodeMachine<byte[]> machine) {
super(name, machine);
}
@Override
protected void preExecuteInstruction() {
super.preExecuteInstruction();
lastExecuteAddress = getCounter();
}
@Override
protected boolean onMissingUseropDef(PcodeOp op, String opName) {
if (super.onMissingUseropDef(op, opName)) {
return true;
}
return adaptedBreakTable.doPcodeOpBreak(new PcodeOpRaw(op));
}
}
record StateBacking(MemoryFaultHandler faultHandler, MemoryLoadImage loadImage) {
}
static class AdaptedBytesPcodeExecutorState extends BytesPcodeExecutorState {
public AdaptedBytesPcodeExecutorState(Language language, StateBacking backing) {
super(new AdaptedBytesPcodeExecutorStatePiece(language, backing));
}
}
static class AdaptedBytesPcodeExecutorStatePiece
extends AbstractBytesPcodeExecutorStatePiece<AdaptedBytesPcodeExecutorStateSpace> {
private final StateBacking backing;
public AdaptedBytesPcodeExecutorStatePiece(Language language, StateBacking backing) {
super(language);
this.backing = backing;
}
@Override
protected AbstractSpaceMap<AdaptedBytesPcodeExecutorStateSpace> newSpaceMap() {
return new SimpleSpaceMap<>() {
@Override
protected AdaptedBytesPcodeExecutorStateSpace newSpace(AddressSpace space) {
return new AdaptedBytesPcodeExecutorStateSpace(language, space, backing);
}
};
}
}
static class AdaptedBytesPcodeExecutorStateSpace
extends BytesPcodeExecutorStateSpace<StateBacking> {
public AdaptedBytesPcodeExecutorStateSpace(Language language, AddressSpace space,
StateBacking backing) {
super(language, space, backing);
}
@Override
protected void readUninitializedFromBacking(ULongSpanSet uninitialized) {
if (uninitialized.isEmpty()) {
return;
}
if (backing.loadImage == null) {
if (space.isUniqueSpace()) {
throw new AccessPcodeExecutionException(
"Attempted to read from uninitialized unique: " + uninitialized);
}
return;
}
ULongSpan bound = uninitialized.bound();
byte[] data = new byte[(int) bound.length()];
backing.loadImage.loadFill(data, data.length, space.getAddress(bound.min()), 0,
false);
for (ULongSpan span : uninitialized.spans()) {
bytes.putData(span.min(), data, (int) (span.min() - bound.min()),
(int) span.length());
}
}
@Override
protected void warnUninit(ULongSpanSet uninit) {
ULongSpan bound = uninit.bound();
byte[] data = new byte[(int) bound.length()];
if (backing.faultHandler.uninitializedRead(space.getAddress(bound.min()), data.length,
data, 0)) {
for (ULongSpan span : uninit.spans()) {
bytes.putData(span.min(), data, (int) (span.min() - bound.min()),
(int) span.length());
}
}
}
}
class AdaptedBreakTableCallback extends BreakTableCallBack {
public AdaptedBreakTableCallback() {
super((SleighLanguage) language);
}
@Override
public void registerAddressCallback(Address addr, BreakCallBack func) {
super.registerAddressCallback(addr, func);
emu.inject(addr, """
__addr_cb();
emu_exec_decoded();
""");
}
@Override
public void unregisterAddressCallback(Address addr) {
emu.clearInject(addr);
super.unregisterAddressCallback(addr);
}
}
private final Language language;
private final Register pcReg;
private final AdaptedPcodeEmulator emu;
private final AdaptedPcodeThread thread;
private final MemoryState adaptedMemState;
private final AdaptedBreakTableCallback adaptedBreakTable;
private boolean isExecuting = false;
private RuntimeException lastError;
public AdaptedEmulator(EmulatorConfiguration config) {
this.language = config.getLanguage();
this.pcReg = language.getProgramCounter();
if (config.isWriteBackEnabled()) {
throw new IllegalArgumentException("write-back is not supported");
}
// I don't think we use page size directly.
this.emu = newPcodeEmulator(config);
this.thread = emu.newThread();
initializeRegisters(config);
this.adaptedMemState = new AdaptedMemoryState<>(thread.getState(), Reason.INSPECT);
this.adaptedBreakTable = new AdaptedBreakTableCallback();
}
protected AdaptedPcodeEmulator newPcodeEmulator(EmulatorConfiguration config) {
return new AdaptedPcodeEmulator(language, config.getLoadData().getMemoryLoadImage(),
config.getMemoryFaultHandler());
}
protected void initializeRegisters(EmulatorConfiguration config) {
RegisterState initRegs = config.getLoadData().getInitialRegisterState();
PcodeExecutorState<byte[]> regState = thread.getState(); // NB. No .getLocalState()
for (String key : initRegs.getKeys()) {
if (!initRegs.isInitialized(key).get(0)) {
continue;
}
Register register = language.getRegister(key);
if (register == null) {
Msg.warn(this, "No such register '" + key + "' in language " + language);
continue;
}
// Yes, allow memory-mapped registers to be initialized in this manner
byte[] val = initRegs.getVals(key).get(0);
// TODO: GDTR/IDTR/LDTR _Limit, _Address stuff.... Is that arch specific?
regState.setVar(register, val);
}
}
@Override
public String getPCRegisterName() {
return pcReg.getName();
}
@Override
public void setExecuteAddress(long addressableWordOffset) {
Address address =
language.getDefaultSpace().getTruncatedAddress(addressableWordOffset, true);
thread.overrideCounter(address);
}
@Override
public Address getExecuteAddress() {
return thread.getCounter();
}
@Override
public Address getLastExecuteAddress() {
return thread.lastExecuteAddress;
}
@Override
public long getPC() {
return Utils.bytesToLong(thread.getState().getVar(pcReg, Reason.INSPECT),
pcReg.getNumBytes(), language.isBigEndian());
}
@Override
public void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
throws CancelledException, LowlevelError, InstructionDecodeException {
if (!(lastError == null || lastError instanceof InterruptPcodeExecutionException)) {
throw lastError;
}
try {
emu.setSoftwareInterruptMode(stopAtBreakpoint ? SwiMode.ACTIVE : SwiMode.IGNORE_ALL);
isExecuting = true;
if (thread.getFrame() != null) {
thread.finishInstruction();
}
else {
thread.stepInstruction();
}
lastError = null;
}
catch (RuntimeException e) {
lastError = e;
}
finally {
emu.setSoftwareInterruptMode(SwiMode.ACTIVE);
isExecuting = false;
}
}
@Override
public boolean isExecuting() {
return isExecuting;
}
@Override
public EmulateExecutionState getEmulateExecutionState() {
if (lastError instanceof InterruptPcodeExecutionException) {
return EmulateExecutionState.BREAKPOINT;
}
if (lastError != null) {
return EmulateExecutionState.FAULT;
}
PcodeFrame frame = thread.getFrame();
if (frame != null) {
return EmulateExecutionState.EXECUTE;
}
if (isExecuting) {
return EmulateExecutionState.INSTRUCTION_DECODE;
}
return EmulateExecutionState.STOPPED;
}
@Override
public MemoryState getMemState() {
return adaptedMemState;
}
@Override
public void addMemoryAccessFilter(MemoryAccessFilter filter) {
filter.addFilter(this);
}
@Override
public FilteredMemoryState getFilteredMemState() {
// Just a dummy to prevent NPEs
return new FilteredMemoryState(language);
}
@Override
public void setContextRegisterValue(RegisterValue regValue) {
if (regValue == null) {
return;
}
thread.overrideContext(regValue);
}
@Override
public RegisterValue getContextRegisterValue() {
return thread.getContext();
}
@Override
public BreakTableCallBack getBreakTable() {
return adaptedBreakTable;
}
@Override
public boolean isAtBreakpoint() {
return lastError instanceof InterruptPcodeExecutionException;
}
@Override
public void setHalt(boolean halt) {
thread.setSuspended(halt);
}
@Override
public boolean getHalt() {
return thread.isSuspended();
}
@Override
public void dispose() {
}
}

View file

@ -0,0 +1,95 @@
/* ###
* 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 javax.help.UnsupportedOperationException;
import ghidra.lifecycle.Transitional;
import ghidra.pcode.emu.ModifiedPcodeThread;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emulate.EmulateInstructionStateModifier;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.memstate.AbstractMemoryState;
import ghidra.pcode.memstate.MemoryBank;
import ghidra.program.model.address.AddressSpace;
/**
* An implementation of {@link MemoryState} which wraps a newer {@link PcodeExecutorState}.
*
* <p>
* This is a transitional component used internally by the {@link AdaptedEmulator}. It is also used
* in the {@link ModifiedPcodeThread}, which is part of the newer {@link PcodeEmulator} system, as a
* means of incorporating {@link EmulateInstructionStateModifier}, which is part of the older
* {@link EmulatorHelper} system. This class will be removed once both conditions are met:
*
* <ol>
* <li>An equivalent state modification system is developed for the {@link PcodeEmulator} system,
* and each {@link EmulateInstructionStateModifier} is ported to it.</li>
* <li>The {@link AdaptedEmulator} class is removed.</li>
* </ol>
*
* <p>
* Guidance for the use of this class is the same as {@link AdaptedEmulator}.
*
* @param <T> the type of values in the wrapped state. This matters not to {@link EmulatorHelper},
* so long as {@link T} can be made concrete.
*/
@Transitional
public class AdaptedMemoryState<T> extends AbstractMemoryState {
private final PcodeExecutorState<T> state;
private final PcodeArithmetic<T> arithmetic;
private final Reason reason;
public AdaptedMemoryState(PcodeExecutorState<T> state, Reason reason) {
super(state.getLanguage());
this.state = state;
this.arithmetic = state.getArithmetic();
this.reason = reason;
}
@Override
public void setMemoryBank(MemoryBank bank) {
throw new UnsupportedOperationException();
}
@Override
public MemoryBank getMemoryBank(AddressSpace spc) {
throw new UnsupportedOperationException();
}
@Override
public int getChunk(byte[] res, AddressSpace spc, long off, int size,
boolean stopOnUnintialized) {
T t = state.getVar(spc, off, size, true, reason);
byte[] val = arithmetic.toConcrete(t, Purpose.OTHER);
System.arraycopy(val, 0, res, 0, val.length);
return val.length;
}
@Override
public void setChunk(byte[] val, AddressSpace spc, long off, int size) {
T t = arithmetic.fromConst(val);
state.setVar(spc, off, size, true, t);
}
@Override
public void setInitialized(boolean initialized, AddressSpace spc, long off, int size) {
// Do nothing
}
}

View file

@ -0,0 +1,479 @@
/* ###
* 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.emu.PcodeEmulator;
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;
/**
* The default implementation of {@link Emulator}.
*
* <p>
* This class used to be named {@code Emulator}, until it was replaced by an interface extracted
* from this class. There is now a second implementation named {@link AdaptedEmulator}, which wraps
* the newer {@link PcodeEmulator} system. If you are developing a new use case based on p-code
* emulation, please consider using {@link PcodeEmulator} directly. There are several example
* scripts in the {@code SystemEmulation} module. If you are maintaining an existing use case
* currently based on {@link Emulator}, you will at least need to change {@code new Emulator(...)}
* to {@code new DefaultEmulator(...)}. It is highly recommended to port to the newer
* {@link PcodeEmulator}. You may find the {@link AdaptedEmulator} useful during the transition, but
* that class is only transitional.
*/
public class DefaultEmulator implements 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; // The preferred page size for a paged memory state
private String pcName;
private long initialPC;
private int instExecuted = 0;
public DefaultEmulator(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");
}
}
/**
* Get the page size to use with a specific AddressSpace. The page containers (MemoryBank)
* assume page size is always power of 2. Any address space is assigned at least 8-bits of
* addressable locations, so at the very least, the size is divisible by 256. Starting with this
* minimum, this method finds the power of 2 that is closest to the preferred page size
* (pageSize) but that still divides the size of the space.
*
* @param space is the specific AddressSpace
* @return the page size to use
*/
private int getValidPageSize(AddressSpace space) {
int ps = 256; // Minimum page size supported
long spaceSize = space.getMaxAddress().getOffset() + 1; // Number of bytes in the space (0 if 2^64 bytes)
if ((spaceSize & 0xff) != 0) {
Msg.warn(this, "Emulator using page size of 256 bytes for " + space.getName() +
" which is NOT a multiple of 256");
return ps;
}
spaceSize >>>= 8; // Divide required size by 256 (evenly)
while (ps < pageSize) { // If current page size is smaller than preferred page size
if ((spaceSize & 1) != 0) {
break; // a bigger page size does not divide the space size evenly, so use current size
}
ps <<= 1; // Bump up current page size to next power of 2
spaceSize >>>= 1; // Divide (evenly) by 2
}
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 = DataConverter.getInstance(language.isBigEndian());
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 (byte b : bytes) {
String byteStr = Integer.toHexString(b & 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();
}
@Override
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));
}
@Override
public long getPC() {
return memState.getValue(pcName);
}
@Override
public String getPCRegisterName() {
return pcName;
}
@Override
public MemoryState getMemState() {
return memState;
}
@Override
public FilteredMemoryState getFilteredMemState() {
return memState;
}
@Override
public void addMemoryAccessFilter(MemoryAccessFilter filter) {
filter.addFilter(this);
}
@Override
public BreakTableCallBack getBreakTable() {
return breakTable;
}
@Override
public void setExecuteAddress(long addressableWordOffset) {
AddressSpace space = addrFactory.getDefaultAddressSpace();
Address address = space.getTruncatedAddress(addressableWordOffset, true);
emulator.setExecuteAddress(address);
}
@Override
public Address getExecuteAddress() {
return emulator.getExecuteAddress();
}
@Override
public Address getLastExecuteAddress() {
return emulator.getLastExecuteAddress();
}
public Set<String> getDefaultContext() {
return mstate.getKeys();
}
@Override
public void setHalt(boolean halt) {
emuHalt = halt;
}
@Override
public boolean getHalt() {
return emuHalt;
}
@Override
public void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
throws CancelledException, LowlevelError, InstructionDecodeException {
isExecuting = true;
try {
emulator.executeInstruction(stopAtBreakpoint, monitor);
instExecuted++;
}
finally {
isExecuting = false;
}
}
@Override
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).
*/
@Override
public EmulateExecutionState getEmulateExecutionState() {
return emulator.getExecutionState();
}
@Override
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,
TaskMonitor.DUMMY, 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;
}
@Override
public RegisterValue getContextRegisterValue() {
return emulator.getContextRegisterValue();
}
@Override
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);
}
}

View file

@ -15,453 +15,162 @@
*/ */
package ghidra.app.emulator; package ghidra.app.emulator;
import java.util.*; import ghidra.pcode.emu.PcodeEmulator;
import ghidra.app.emulator.memory.*;
import ghidra.app.emulator.state.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emulate.*; import ghidra.pcode.emulate.*;
import ghidra.pcode.error.LowlevelError; import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.memstate.*; import ghidra.pcode.memstate.MemoryState;
import ghidra.program.disassemble.Disassembler; import ghidra.program.model.address.Address;
import ghidra.program.model.address.*; import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.util.*;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class Emulator { /**
* The emulator interface
private final MemoryFaultHandler faultHandler; *
* <p>
private SleighLanguage language; * This interface may soon be deprecated. It was extracted from what has now been renamed
private AddressFactory addrFactory; * {@link DefaultEmulator}. Please consider using {@link PcodeEmulator} instead.
*/
private CompositeLoadImage loadImage = new CompositeLoadImage(); public interface Emulator {
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; // The preferred page size for a paged memory state
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");
}
}
/** /**
* Get the page size to use with a specific AddressSpace. The page containers (MemoryBank) * Get the name of the program counter register
* assume page size is always power of 2. Any address space is assigned at least 8-bits of *
* addressable locations, so at the very least, the size is divisible by 256. Starting with this * @return the name
* minimum, this method finds the power of 2 that is closest to the preferred page size (pageSize)
* but that still divides the size of the space.
* @param space is the specific AddressSpace
* @return the page size to use
*/ */
private int getValidPageSize(AddressSpace space) { String getPCRegisterName();
int ps = 256; // Minimum page size supported
long spaceSize = space.getMaxAddress().getOffset() + 1; // Number of bytes in the space (0 if 2^64 bytes)
if ((spaceSize & 0xff) != 0) {
Msg.warn(this, "Emulator using page size of 256 bytes for " + space.getName() +
" which is NOT a multiple of 256");
return ps;
}
spaceSize >>>= 8; // Divide required size by 256 (evenly)
while (ps < pageSize) { // If current page size is smaller than preferred page size
if ((spaceSize & 1) != 0) {
break; // a bigger page size does not divide the space size evenly, so use current size
}
ps <<= 1; // Bump up current page size to next power of 2
spaceSize >>>= 1; // Divide (evenly) by 2
}
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, * Set the value of the program counter
* only those registers within the register space which have been modified will *
* be reported and restored to their initial state. * @param addressableWordOffset the <em>word</em> offset of the instruction to execute next.
* @param restore if true restore modified registers within the register space only
*/ */
private void initRegisters(boolean restore) { void setExecuteAddress(long addressableWordOffset);
DataConverter conv = DataConverter.getInstance(language.isBigEndian());
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 (byte b : bytes) {
String byteStr = Integer.toHexString(b & 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 * Get current execution address (or the address of the next instruction to be executed)
*
* @return current execution address
*/ */
public boolean isAtBreakpoint() { Address getExecuteAddress();
return getHalt() && emulator.getExecutionState() == EmulateExecutionState.BREAKPOINT;
}
/** /**
* @return emulator execution state. This can be useful within a memory fault handler to * Get the address of the last instruction executed (or the instructed currently being executed)
* determine if a memory read was associated with instruction parsing (i.e., PCODE_EMIT) or *
* normal an actual emulated read (i.e., EXECUTE). * @return the address
*/ */
public EmulateExecutionState getEmulateExecutionState() { Address getLastExecuteAddress();
return emulator.getExecutionState();
} /**
* Get the value of the program counter
*
* @return the value, i.e., offset in code space
*/
long getPC();
/**
* 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
*/
void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
throws CancelledException, LowlevelError, InstructionDecodeException;
/** /**
* @return true if emulator is busy executing an instruction * @return true if emulator is busy executing an instruction
*/ */
public boolean isExecuting() { boolean isExecuting();
return isExecuting;
}
public SleighLanguage getLanguage() {
return language;
}
/** /**
* Disassemble from the current execute address * Get the low-level execution state
* @param count number of contiguous instructions to disassemble *
* @return list of instructions * <p>
* This can be useful within a memory fault handler to determine if a memory read was associated
* with instruction parsing (i.e., {@link EmulateExecutionState#INSTRUCTION_DECODE}) or an
* actual emulated read (i.e., {@link EmulateExecutionState#EXECUTE}).
*
* @return emulator execution state.
*/ */
public List<String> disassemble(Integer count) { EmulateExecutionState getEmulateExecutionState();
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,
TaskMonitor.DUMMY, 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 * Get the memory state
* its state when the previously executed instruction was *
* parsed/executed. The context value returned will feed into the next * @return the state
* instruction to be parsed with its non-flowing bits cleared and
* any future context state merged in.
* @return context as a RegisterValue object
*/ */
public RegisterValue getContextRegisterValue() { MemoryState getMemState();
return emulator.getContextRegisterValue();
} /**
* Add a filter on memory access
*
* @param filter the filter
*/
void addMemoryAccessFilter(MemoryAccessFilter filter);
/**
* Get the memory state, modified by all installed access filters
*
* @return the state
*/
FilteredMemoryState getFilteredMemState();
/** /**
* Sets the context register value at the current execute address. * 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 * <p>
* will be cleared prior to parsing on instruction. In addition, * The Emulator should not be running when this method is invoked. Only flowing context bits
* any future context state set by the pcode emitter will * should be set, as non-flowing bits will be cleared prior to parsing on instruction. In
* take precedence over context set using this method. This method * addition, any future context state set by the pcode emitter will take precedence over context
* is primarily intended to be used to establish the initial * set using this method. This method is primarily intended to be used to establish the initial
* context state. * context state.
*
* @param regValue is the value to set context to * @param regValue is the value to set context to
*/ */
public void setContextRegisterValue(RegisterValue regValue) { void setContextRegisterValue(RegisterValue regValue);
emulator.setContextRegisterValue(regValue);
}
/** /**
* Add memory load image provider * Returns the current context register value.
* @param provider memory load image provider *
* @param view memory region which corresponds to provider * <p>
* 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.
*
* @return context as a RegisterValue object
*/ */
public void addProvider(MemoryLoadImage provider, AddressSetView view) { RegisterValue getContextRegisterValue();
loadImage.addProvider(provider, view);
} /**
* Get the breakpoint table
*
* @return the breakpoint table
*/
BreakTableCallBack getBreakTable();
/**
* @return true if halted at a breakpoint
*/
boolean isAtBreakpoint();
/**
* Halt or un-halt the emulator
*
* @param halt true to halt
*/
void setHalt(boolean halt);
/**
* Check if the emulator has been halted
*
* @return true if halted
*/
boolean getHalt();
/**
* Clean up resources used by the emulator
*/
void dispose();
} }

View file

@ -23,6 +23,7 @@ import ghidra.app.emulator.memory.*;
import ghidra.app.emulator.state.DumpMiscState; import ghidra.app.emulator.state.DumpMiscState;
import ghidra.app.emulator.state.RegisterState; import ghidra.app.emulator.state.RegisterState;
import ghidra.framework.store.LockException; import ghidra.framework.store.LockException;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emulate.BreakCallBack; import ghidra.pcode.emulate.BreakCallBack;
import ghidra.pcode.emulate.EmulateExecutionState; import ghidra.pcode.emulate.EmulateExecutionState;
import ghidra.pcode.memstate.MemoryFaultHandler; import ghidra.pcode.memstate.MemoryFaultHandler;
@ -38,6 +39,17 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/**
* This is the primary "entry point" for using an {@link Emulator}.
*
* <p>
* This is part of the older p-code emulation system. For information about the newer p-code
* emulation system, see {@link PcodeEmulator}. There are several example scripts in the
* {@code SystemEmulation} module.
*
* @see PcodeEmulator
* @see Emulator
*/
public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration { public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration {
private final Program program; private final Program program;
@ -68,11 +80,15 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
stackPtrReg = program.getCompilerSpec().getStackPointer(); stackPtrReg = program.getCompilerSpec().getStackPointer();
stackMemorySpace = program.getCompilerSpec().getStackBaseSpace(); stackMemorySpace = program.getCompilerSpec().getStackBaseSpace();
emulator = new Emulator(this); emulator = newEmulator();
converter = DataConverter.getInstance(program.getMemory().isBigEndian()); converter = DataConverter.getInstance(program.getMemory().isBigEndian());
} }
protected Emulator newEmulator() {
return new DefaultEmulator(this);
}
public void dispose() { public void dispose() {
emulator.dispose(); emulator.dispose();
if (memoryWriteTracker != null) { if (memoryWriteTracker != null) {
@ -115,6 +131,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Get Program Counter (PC) register defined by applicable processor specification * Get Program Counter (PC) register defined by applicable processor specification
*
* @return Program Counter register * @return Program Counter register
*/ */
public Register getPCRegister() { public Register getPCRegister() {
@ -123,6 +140,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Get Stack Pointer register defined by applicable compiler specification * Get Stack Pointer register defined by applicable compiler specification
*
* @return Stack Pointer register * @return Stack Pointer register
*/ */
public Register getStackPointerRegister() { public Register getStackPointerRegister() {
@ -130,14 +148,12 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Provides ability to install a low-level memory fault handler. * Provides ability to install a low-level memory fault handler. The handler methods should
* The handler methods should generally return 'false' to allow * generally return 'false' to allow the default handler to generate the appropriate target
* the default handler to generate the appropriate target error. * error. Within the fault handler, the EmulateExecutionState can be used to distinguish the
* Within the fault handler, the EmulateExecutionState can be used * pcode-emit state and the actual execution state since an attempt to execute an instruction at
* to distinguish the pcode-emit state and the actual execution state * an uninitialized memory location will cause an uninitializedRead during the PCODE_EMIT 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. * @param handler memory fault handler.
*/ */
public void setMemoryFaultHandler(MemoryFaultHandler handler) { public void setMemoryFaultHandler(MemoryFaultHandler handler) {
@ -215,9 +231,10 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Read string from memory state. * Read string from memory state.
*
* @param addr memory address * @param addr memory address
* @param maxLength limit string read to this length. If return string is * @param maxLength limit string read to this length. If return string is truncated, "..." will
* truncated, "..." will be appended. * be appended.
* @return string read from memory state * @return string read from memory state
*/ */
public String readNullTerminatedString(Address addr, int maxLength) { public String readNullTerminatedString(Address addr, int maxLength) {
@ -242,7 +259,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
public byte[] readMemory(Address addr, int length) { public byte[] readMemory(Address addr, int length) {
byte[] res = new byte[length]; byte[] res = new byte[length];
int len = emulator.getMemState().getChunk(res, addr.getAddressSpace(), addr.getOffset(), int len = emulator.getMemState()
.getChunk(res, addr.getAddressSpace(), addr.getOffset(),
length, false); length, false);
if (len == 0) { if (len == 0) {
Msg.error(this, "Failed to read memory from Emulator at: " + addr); Msg.error(this, "Failed to read memory from Emulator at: " + addr);
@ -256,7 +274,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
public void writeMemory(Address addr, byte[] bytes) { public void writeMemory(Address addr, byte[] bytes) {
emulator.getMemState().setChunk(bytes, addr.getAddressSpace(), addr.getOffset(), emulator.getMemState()
.setChunk(bytes, addr.getAddressSpace(), addr.getOffset(),
bytes.length); bytes.length);
} }
@ -266,6 +285,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Read a stack value from the memory state. * Read a stack value from the memory state.
*
* @param relativeOffset offset relative to current stack pointer * @param relativeOffset offset relative to current stack pointer
* @param size data size in bytes * @param size data size in bytes
* @param signed true if value read is signed, false if unsigned * @param signed true if value read is signed, false if unsigned
@ -281,6 +301,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Write a value onto the stack * Write a value onto the stack
*
* @param relativeOffset offset relative to current stack pointer * @param relativeOffset offset relative to current stack pointer
* @param size data size in bytes * @param size data size in bytes
* @param value * @param value
@ -295,6 +316,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Write a value onto the stack * Write a value onto the stack
*
* @param relativeOffset offset relative to current stack pointer * @param relativeOffset offset relative to current stack pointer
* @param size data size in bytes * @param size data size in bytes
* @param value * @param value
@ -309,6 +331,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Establish breakpoint * Establish breakpoint
*
* @param addr memory address for new breakpoint * @param addr memory address for new breakpoint
*/ */
public void setBreakpoint(Address addr) { public void setBreakpoint(Address addr) {
@ -317,6 +340,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Clear breakpoint * Clear breakpoint
*
* @param addr memory address for breakpoint to be cleared * @param addr memory address for breakpoint to be cleared
*/ */
public void clearBreakpoint(Address addr) { public void clearBreakpoint(Address addr) {
@ -324,8 +348,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Set current context register value. * Set current context register value. Keep in mind that any non-flowing context values will be
* Keep in mind that any non-flowing context values will be stripped. * stripped.
*
* @param ctxRegValue * @param ctxRegValue
*/ */
public void setContextRegister(RegisterValue ctxRegValue) { public void setContextRegister(RegisterValue ctxRegValue) {
@ -333,8 +358,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Set current context register value. * Set current context register value. Keep in mind that any non-flowing context values will be
* Keep in mind that any non-flowing context values will be stripped. * stripped.
*
* @param ctxReg context register * @param ctxReg context register
* @param value context value * @param value context value
*/ */
@ -344,6 +370,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Get the current context register value * Get the current context register value
*
* @return context register value or null if not set or unknown * @return context register value or null if not set or unknown
*/ */
public RegisterValue getContextRegister() { public RegisterValue getContextRegister() {
@ -351,9 +378,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Register callback for language defined pcodeop (call other). * Register callback for language defined pcodeop (call other). WARNING! Using this method may
* WARNING! Using this method may circumvent the default CALLOTHER emulation support * circumvent the default CALLOTHER emulation support when supplied by the Processor module.
* when supplied by the Processor module. *
* @param pcodeOpName the name of the pcode op * @param pcodeOpName the name of the pcode op
* @param callback the callback to register * @param callback the callback to register
*/ */
@ -362,9 +389,10 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Register default callback for language defined pcodeops (call other). * Register default callback for language defined pcodeops (call other). WARNING! Using this
* WARNING! Using this method may circumvent the default CALLOTHER emulation support * method may circumvent the default CALLOTHER emulation support when supplied by the Processor
* when supplied by the Processor module. * module.
*
* @param callback the default callback to register * @param callback the default callback to register
*/ */
public void registerDefaultCallOtherCallback(BreakCallBack callback) { public void registerDefaultCallOtherCallback(BreakCallBack callback) {
@ -373,6 +401,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Unregister callback for language defined pcodeop (call other). * Unregister callback for language defined pcodeop (call other).
*
* @param pcodeOpName the name of the pcode op * @param pcodeOpName the name of the pcode op
*/ */
public void unregisterCallOtherCallback(String pcodeOpName) { public void unregisterCallOtherCallback(String pcodeOpName) {
@ -380,9 +409,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Unregister default callback for language defined pcodeops (call other). * Unregister default callback for language defined pcodeops (call other). WARNING! Using this
* WARNING! Using this method may circumvent the default CALLOTHER emulation support * method may circumvent the default CALLOTHER emulation support when supplied by the Processor
* when supplied by the Processor module. * module.
*/ */
public void unregisterDefaultCallOtherCallback() { public void unregisterDefaultCallOtherCallback() {
emulator.getBreakTable().unregisterPcodeCallback("*"); emulator.getBreakTable().unregisterPcodeCallback("*");
@ -390,6 +419,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Get current execution address * Get current execution address
*
* @return current execution address * @return current execution address
*/ */
public Address getExecutionAddress() { public Address getExecutionAddress() {
@ -397,11 +427,11 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Start execution at the specified address using the initial context specified. * Start execution at the specified address using the initial context specified. Method will
* Method will block until execution stops. This method will initialize context * block until execution stops. This method will initialize context register based upon the
* register based upon the program stored context if not already done. In addition, * program stored context if not already done. In addition, both general register value and the
* both general register value and the context register may be further modified * context register may be further modified via the context parameter if specified.
* via the context parameter if specified. *
* @param addr initial program address * @param addr initial program address
* @param context optional context settings which override current program context * @param context optional context settings which override current program context
* @param monitor * @param monitor
@ -463,10 +493,10 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Continue execution from the current execution address. * Continue execution from the current execution address. No adjustment will be made to the
* No adjustment will be made to the context beyond the normal * context beyond the normal context flow behavior defined by the language. Method will block
* context flow behavior defined by the language. * until execution stops.
* Method will block until execution stops. *
* @param monitor * @param monitor
* @return true if execution completes without error (i.e., is at breakpoint) * @return true if execution completes without error (i.e., is at breakpoint)
* @throws CancelledException if execution cancelled via monitor * @throws CancelledException if execution cancelled via monitor
@ -482,6 +512,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Continue execution and block until either a breakpoint hits or error occurs. * Continue execution and block until either a breakpoint hits or error occurs.
*
* @throws CancelledException if execution was cancelled * @throws CancelledException if execution was cancelled
*/ */
private void continueExecution(TaskMonitor monitor) throws CancelledException { private void continueExecution(TaskMonitor monitor) throws CancelledException {
@ -494,8 +525,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Execute instruction at current address * Execute instruction at current address
* @param stopAtBreakpoint if true and breakpoint hits at current execution address *
* execution will halt without executing instruction. * @param stopAtBreakpoint if true and breakpoint hits at current execution address execution
* will halt without executing instruction.
* @throws CancelledException if execution was cancelled * @throws CancelledException if execution was cancelled
*/ */
private void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor) private void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
@ -515,8 +547,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
lastError = t.toString(); lastError = t.toString();
} }
emulator.setHalt(true); // force execution to stop emulator.setHalt(true); // force execution to stop
if (t instanceof CancelledException) { if (t instanceof CancelledException ce) {
throw (CancelledException) t; throw ce;
} }
Msg.error(this, Msg.error(this,
"Emulation failure at " + emulator.getExecuteAddress() + ": " + program.getName(), "Emulation failure at " + emulator.getExecuteAddress() + ": " + program.getName(),
@ -525,9 +557,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Used when the emulator has had the execution address changed to * Used when the emulator has had the execution address changed to make sure it has a context
* make sure it has a context consistent with the program context * consistent with the program context if there is one.
* if there is one.
*/ */
private void setProcessorContext() { private void setProcessorContext() {
// this assumes you have set the emulation address // this assumes you have set the emulation address
@ -554,10 +585,10 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Step execution one instruction which may consist of multiple * Step execution one instruction which may consist of multiple pcode operations. No adjustment
* pcode operations. No adjustment will be made to the context beyond the normal * will be made to the context beyond the normal context flow behavior defined by the language.
* context flow behavior defined by the language.
* Method will block until execution stops. * Method will block until execution stops.
*
* @return true if execution completes without error * @return true if execution completes without error
* @throws CancelledException if execution cancelled via monitor * @throws CancelledException if execution cancelled via monitor
*/ */
@ -568,19 +599,19 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
/** /**
* Create a new initialized memory block using the current emulator memory state * Create a new initialized memory block using the current emulator memory state
*
* @param name block name * @param name block name
* @param start start address of the block * @param start start address of the block
* @param length the size 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 * @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 * overlay address space will be created and the block will have a starting address
* offset as the given start address parameter, but in the new address space. * at the same offset as the given start address parameter, but in the new address
* space.
* @param monitor * @param monitor
* @return new memory block * @return new memory block
* @throws LockException if exclusive lock not in place (see haveLock()) * @throws LockException if exclusive lock not in place (see haveLock())
* @throws MemoryConflictException if the new block overlaps with a * @throws MemoryConflictException if the new block overlaps with a previous block
* previous block * @throws AddressOverflowException if the start is beyond the address space
* @throws AddressOverflowException if the start is beyond the
* address space
* @throws CancelledException user cancelled operation * @throws CancelledException user cancelled operation
* @throws DuplicateNameException * @throws DuplicateNameException
*/ */
@ -626,7 +657,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
boolean success = false; boolean success = false;
int txId = program.startTransaction("Create Memory Block"); int txId = program.startTransaction("Create Memory Block");
try { try {
block = program.getMemory().createInitializedBlock(name, start, memStateStream, length, block = program.getMemory()
.createInitializedBlock(name, start, memStateStream, length,
monitor, overlay); monitor, overlay);
success = true; success = true;
} }
@ -637,8 +669,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* Enable/Disable tracking of memory writes in the form of an * Enable/Disable tracking of memory writes in the form of an address set.
* address set. *
* @param enable * @param enable
*/ */
public void enableMemoryWriteTracking(boolean enable) { public void enableMemoryWriteTracking(boolean enable) {
@ -656,10 +688,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
} }
/** /**
* @return address set of memory locations written by the emulator * @return address set of memory locations written by the emulator if memory write tracking is
* if memory write tracking is enabled, otherwise null is returned. * enabled, otherwise null is returned. The address set returned will continue to be
* The address set returned will continue to be updated unless * updated unless memory write tracking becomes disabled.
* memory write tracking becomes disabled.
*/ */
public AddressSetView getTrackedMemoryWriteSet() { public AddressSetView getTrackedMemoryWriteSet() {
if (memoryWriteTracker != null) { if (memoryWriteTracker != null) {

View file

@ -15,11 +15,11 @@
*/ */
package ghidra.app.emulator; package ghidra.app.emulator;
import ghidra.pcode.memstate.MemoryState; import ghidra.pcode.memstate.DefaultMemoryState;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
class FilteredMemoryState extends MemoryState { class FilteredMemoryState extends DefaultMemoryState {
private MemoryAccessFilter filter; private MemoryAccessFilter filter;
private boolean filterEnabled = true; // used to prevent filtering filter queries private boolean filterEnabled = true; // used to prevent filtering filter queries

View file

@ -17,6 +17,14 @@ package ghidra.app.emulator;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
/**
* A means of intercepting and/or modifying the emulator's memory access.
*
* <p>
* Several of these filters may be chained together, each being invoked in the reverse of the order
* added. In this way, the first added gets the "final say," but it also is farthest from the
* original request.
*/
public abstract class MemoryAccessFilter { public abstract class MemoryAccessFilter {
private MemoryAccessFilter prevFilter; private MemoryAccessFilter prevFilter;
@ -27,23 +35,41 @@ public abstract class MemoryAccessFilter {
private boolean filterOnExecutionOnly = true; private boolean filterOnExecutionOnly = true;
final void filterRead(AddressSpace spc, long off, int size, byte[] values) { final void filterRead(AddressSpace spc, long off, int size, byte[] values) {
if (filterOnExecutionOnly() && !emu.isExecuting()) return; // do not filter idle queries if (filterOnExecutionOnly() && !emu.isExecuting())
return; // do not filter idle queries
processRead(spc, off, size, values); processRead(spc, off, size, values);
if (nextFilter != null) { if (nextFilter != null) {
nextFilter.filterRead(spc, off, size, values); nextFilter.filterRead(spc, off, size, values);
} }
} }
/**
* Invoked after a read
*
* @param spc the space read from
* @param off the offset within the space
* @param size the number of bytes read
* @param values the bytes read
*/
protected abstract void processRead(AddressSpace spc, long off, int size, byte[] values); protected abstract void processRead(AddressSpace spc, long off, int size, byte[] values);
final void filterWrite(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 if (filterOnExecutionOnly() && !emu.isExecuting())
return; // do not filter idle queries
processWrite(spc, off, size, values); processWrite(spc, off, size, values);
if (nextFilter != null) { if (nextFilter != null) {
nextFilter.filterWrite(spc, off, size, values); nextFilter.filterWrite(spc, off, size, values);
} }
} }
/**
* Invoked <em>after</em> a write
*
* @param spc the space written to
* @param off the offset within the space
* @param size the number of bytes written
* @param values the bytes written
*/
protected abstract void processWrite(AddressSpace spc, long off, int size, byte[] values); protected abstract void processWrite(AddressSpace spc, long off, int size, byte[] values);
final void addFilter(Emulator emu) { final void addFilter(Emulator emu) {
@ -56,7 +82,9 @@ public abstract class MemoryAccessFilter {
/** /**
* Dispose this filter which will cause it to be removed from the memory state. * Dispose this filter which will cause it to be removed from the memory state.
* If overriden, be sure to invoke super.dispose(). *
* <p>
* If overriden, be sure to invoke {@code super.dispose()}.
*/ */
public void dispose() { public void dispose() {
if (nextFilter != null) { if (nextFilter != null) {

View file

@ -22,8 +22,22 @@ public interface RegisterState {
public Set<String> getKeys(); public Set<String> getKeys();
/**
* Get the byte array value for a register name
*
* @param key the register name
* @return a list (used as an optional) containing at most the one byte array giving the
* register's value. If empty, the value if unspecified.
*/
public List<byte[]> getVals(String key); public List<byte[]> getVals(String key);
/**
* Check if the register is initialized
*
* @param key the register name
* @return a list (used an an optional) containing at most the one initialization state. True if
* initialized, false if not. Empty if unspecified.
*/
public List<Boolean> isInitialized(String key); public List<Boolean> isInitialized(String key);
public void setVals(String key, byte[] vals, boolean setInitiailized); public void setVals(String key, byte[] vals, boolean setInitiailized);

View file

@ -266,6 +266,11 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
this.suspended = suspended; this.suspended = suspended;
} }
@Override
public boolean isSuspended() {
return suspended;
}
/** /**
* Check for a p-code injection (override) at the given address * Check for a p-code injection (override) at the given address
* *
@ -391,8 +396,8 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
} }
/** /**
* Notify the machine a thread has been stepped, so that it may re-enable software interrupts, * Notify the machine a thread has been stepped a p-code op, so that it may re-enable software
* if applicable * interrupts, if applicable
*/ */
protected void stepped() { protected void stepped() {
if (swiMode == SwiMode.IGNORE_STEP) { if (swiMode == SwiMode.IGNORE_STEP) {

View file

@ -320,15 +320,19 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
} }
protected void branchToAddress(Address target) { protected void branchToAddress(Address target) {
overrideCounter(target); writeCounter(target);
decoder.branched(counter); decoder.branched(counter);
} }
protected void writeCounter(Address counter) {
setCounter(counter);
state.setVar(pc,
arithmetic.fromConst(counter.getAddressableWordOffset(), pc.getMinimumByteSize()));
}
@Override @Override
public void overrideCounter(Address counter) { public void overrideCounter(Address counter) {
setCounter(counter); writeCounter(counter);
state.setVar(pc,
arithmetic.fromConst(counter.getAddressableWordOffset(), pc.getMinimumByteSize()));
} }
@Override @Override
@ -375,13 +379,13 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override @Override
public void reInitialize() { public void reInitialize() {
long offset = arithmetic.toLong(state.getVar(pc, executor.getReason()), Purpose.BRANCH); long offset = arithmetic.toLong(state.getVar(pc, Reason.RE_INIT), Purpose.BRANCH);
setCounter(language.getDefaultSpace().getAddress(offset, true)); setCounter(language.getDefaultSpace().getAddress(offset, true));
if (contextreg != Register.NO_CONTEXT) { if (contextreg != Register.NO_CONTEXT) {
try { try {
BigInteger ctx = arithmetic.toBigInteger(state.getVar( BigInteger ctx = arithmetic.toBigInteger(state.getVar(contextreg, Reason.RE_INIT),
contextreg, executor.getReason()), Purpose.CONTEXT); Purpose.CONTEXT);
assignContext(new RegisterValue(contextreg, ctx)); assignContext(new RegisterValue(contextreg, ctx));
} }
catch (AccessPcodeExecutionException e) { catch (AccessPcodeExecutionException e) {
@ -582,6 +586,11 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
executor.suspended = suspended; executor.suspended = suspended;
} }
@Override
public boolean isSuspended() {
return executor.suspended;
}
@Override @Override
public SleighLanguage getLanguage() { public SleighLanguage getLanguage() {
return language; return language;
@ -677,8 +686,8 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
} }
/** /**
* Notify the machine a thread has been stepped, so that it may re-enable software interrupts, * Notify the machine a thread has been stepped a p-code op, so that it may re-enable software
* if applicable * interrupts, if applicable
*/ */
protected void stepped() { protected void stepped() {
machine.stepped(); machine.stepped();

View file

@ -17,14 +17,15 @@ package ghidra.pcode.emu;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import ghidra.app.emulator.AdaptedMemoryState;
import ghidra.app.emulator.Emulator; import ghidra.app.emulator.Emulator;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emulate.*; import ghidra.pcode.emulate.*;
import ghidra.pcode.exec.ConcretionError; import ghidra.pcode.exec.ConcretionError;
import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.memstate.MemoryBank;
import ghidra.pcode.memstate.MemoryState; import ghidra.pcode.memstate.MemoryState;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.PcodeOp;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -85,34 +86,6 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
} }
} }
/**
* Glue for incorporating state modifiers
*
* <p>
* This allows the modifiers to access the thread's state (memory and registers).
*/
protected class GlueMemoryState extends MemoryState {
public GlueMemoryState(Language language) {
super(language);
}
@Override
public int getChunk(byte[] res, AddressSpace spc, long off, int size,
boolean stopOnUnintialized) {
return getBytesChunk(res, spc, off, size, stopOnUnintialized);
}
@Override
public void setChunk(byte[] val, AddressSpace spc, long off, int size) {
setBytesChunk(val, spc, off, size);
}
@Override
public void setInitialized(boolean initialized, AddressSpace spc, long off, int size) {
// Do nothing
}
}
/** /**
* Part of the glue that makes existing state modifiers work in new emulation framework * Part of the glue that makes existing state modifiers work in new emulation framework
* *
@ -125,8 +98,6 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
protected final EmulateInstructionStateModifier modifier; protected final EmulateInstructionStateModifier modifier;
protected final Emulate emulate; protected final Emulate emulate;
protected Address savedCounter;
/** /**
* Construct a new thread with the given name belonging to the given machine * Construct a new thread with the given name belonging to the given machine
* *
@ -141,8 +112,12 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
* These two exist as a way to integrate the language-specific injects that are already * These two exist as a way to integrate the language-specific injects that are already
* written for {@link Emulator}. * written for {@link Emulator}.
*/ */
emulate = new GlueEmulate(language, new GlueMemoryState(language), emulate = new GlueEmulate(language, new AdaptedMemoryState<>(state, Reason.EXECUTE) {
new BreakTableCallBack(language)); @Override
public void setMemoryBank(MemoryBank bank) {
// Ignore
}
}, new BreakTableCallBack(language));
modifier = createModifier(); modifier = createModifier();
} }
@ -178,42 +153,19 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
} }
} }
/**
* Called by a state modifier to read concrete bytes from the thread's state
*
* @see {@link MemoryState#getChunk(byte[], AddressSpace, long, int, boolean)}
*/
protected int getBytesChunk(byte[] res, AddressSpace spc, long off, int size,
boolean stopOnUnintialized) {
T t = state.getVar(spc, off, size, true, executor.getReason());
byte[] val = arithmetic.toConcrete(t, Purpose.OTHER);
System.arraycopy(val, 0, res, 0, val.length);
return val.length;
}
/**
* Called by a state modifier to write concrete bytes to the thread's state
*
* @see {@link MemoryState#setChunk(byte[], AddressSpace, long, int)}
*/
protected void setBytesChunk(byte[] val, AddressSpace spc, long off, int size) {
T t = arithmetic.fromConst(val);
state.setVar(spc, off, size, true, t);
}
@Override @Override
public void reInitialize() { public void overrideCounter(Address counter) {
super.reInitialize(); super.overrideCounter(counter);
if (modifier != null) { if (modifier != null) {
savedCounter = getCounter(); modifier.initialExecuteCallback(emulate, counter, getContext());
modifier.initialExecuteCallback(emulate, savedCounter, getContext());
} }
} }
@Override @Override
protected void postExecuteInstruction() { protected void postExecuteInstruction() {
if (modifier != null) { if (modifier != null) {
modifier.postExecuteCallback(emulate, savedCounter, frame.copyCode(), modifier.postExecuteCallback(emulate,
instruction == null ? null : instruction.getAddress(), frame.copyCode(),
frame.getBranched(), getCounter()); frame.getBranched(), getCounter());
} }
} }

View file

@ -203,6 +203,13 @@ public interface PcodeMachine<T> {
*/ */
void setSuspended(boolean suspended); void setSuspended(boolean suspended);
/**
* Check the suspension state of the machine
*
* @see PcodeThread#getSuspended()
*/
boolean isSuspended();
/** /**
* Compile the given Sleigh code for execution by a thread of this machine * Compile the given Sleigh code for execution by a thread of this machine
* *

View file

@ -19,7 +19,6 @@ import java.util.List;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary; import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
import ghidra.pcode.emu.PcodeMachine.SwiMode;
import ghidra.pcode.exec.*; import ghidra.pcode.exec.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
@ -286,6 +285,13 @@ public interface PcodeThread<T> {
*/ */
void setSuspended(boolean suspended); void setSuspended(boolean suspended);
/**
* Check the suspension state of the thread's executor
*
* @return true if suspended
*/
boolean isSuspended();
/** /**
* Get the thread's Sleigh language (processor model) * Get the thread's Sleigh language (processor model)
* *

View file

@ -87,6 +87,15 @@ public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
sharedState.setVar(space, offset, size, quantize, val); sharedState.setVar(space, offset, size, quantize, val);
} }
@Override
public void setVar(AddressSpace space, long offset, int size, boolean quantize, T val) {
if (isThreadLocalSpace(space)) {
localState.setVar(space, offset, size, quantize, val);
return;
}
sharedState.setVar(space, offset, size, quantize, val);
}
@Override @Override
public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) { public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) {
if (isThreadLocalSpace(space)) { if (isThreadLocalSpace(space)) {
@ -95,6 +104,14 @@ public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
return sharedState.getVar(space, offset, size, quantize, reason); return sharedState.getVar(space, offset, size, quantize, reason);
} }
@Override
public T getVar(AddressSpace space, long offset, int size, boolean quantize, Reason reason) {
if (isThreadLocalSpace(space)) {
return localState.getVar(space, offset, size, quantize, reason);
}
return sharedState.getVar(space, offset, size, quantize, reason);
}
@Override @Override
public Map<Register, T> getRegisterValues() { public Map<Register, T> getRegisterValues() {
Map<Register, T> result = new HashMap<>(); Map<Register, T> result = new HashMap<>();

View file

@ -65,7 +65,9 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
* *
* @return the copy * @return the copy
*/ */
public abstract AbstractSpaceMap<S> fork(); public AbstractSpaceMap<S> fork() {
throw new UnsupportedOperationException();
}
/** /**
* Deep copy the given space * Deep copy the given space
@ -73,7 +75,9 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
* @param s the space * @param s the space
* @return the copy * @return the copy
*/ */
public abstract S fork(S s); public S fork(S s) {
throw new UnsupportedOperationException();
}
/** /**
* Produce a deep copy of the given map * Produce a deep copy of the given map

View file

@ -69,11 +69,21 @@ public class DefaultPcodeExecutorState<T> implements PcodeExecutorState<T> {
return piece.getVar(space, offset, size, quantize, reason); return piece.getVar(space, offset, size, quantize, reason);
} }
@Override
public T getVar(AddressSpace space, long offset, int size, boolean quantize, Reason reason) {
return piece.getVar(space, offset, size, quantize, reason);
}
@Override @Override
public void setVar(AddressSpace space, T offset, int size, boolean quantize, T val) { public void setVar(AddressSpace space, T offset, int size, boolean quantize, T val) {
piece.setVar(space, offset, size, quantize, val); piece.setVar(space, offset, size, quantize, val);
} }
@Override
public void setVar(AddressSpace space, long offset, int size, boolean quantize, T val) {
piece.setVar(space, offset, size, quantize, val);
}
@Override @Override
public Map<Register, T> getRegisterValues() { public Map<Register, T> getRegisterValues() {
return piece.getRegisterValues(); return piece.getRegisterValues();

View file

@ -46,7 +46,7 @@ public class PcodeExecutor<T> {
protected final PcodeExecutorState<T> state; protected final PcodeExecutorState<T> state;
protected final Reason reason; protected final Reason reason;
protected final Register pc; protected final Register pc;
protected final int pointerSize; protected final int pcSize;
/** /**
* Construct an executor with the given bindings * Construct an executor with the given bindings
@ -64,7 +64,7 @@ public class PcodeExecutor<T> {
this.reason = reason; this.reason = reason;
this.pc = language.getProgramCounter(); this.pc = language.getProgramCounter();
this.pointerSize = language.getDefaultSpace().getPointerSize(); this.pcSize = pc.getNumBytes();
} }
/** /**
@ -431,7 +431,7 @@ public class PcodeExecutor<T> {
frame.branch((int) target.getOffset()); frame.branch((int) target.getOffset());
} }
else { else {
branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame); branchToOffset(arithmetic.fromConst(target.getOffset(), pcSize), frame);
branchToAddress(target); branchToAddress(target);
} }
} }
@ -510,7 +510,7 @@ public class PcodeExecutor<T> {
*/ */
public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) { public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
Address target = op.getInput(0).getAddress(); Address target = op.getInput(0).getAddress();
branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame); branchToOffset(arithmetic.fromConst(target.getOffset(), pcSize), frame);
branchToAddress(target); branchToAddress(target);
} }

View file

@ -16,6 +16,7 @@
package ghidra.pcode.exec; package ghidra.pcode.exec;
import java.util.Map; import java.util.Map;
import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
@ -44,6 +45,8 @@ public interface PcodeExecutorStatePiece<A, T> {
* Reasons for reading state * Reasons for reading state
*/ */
enum Reason { enum Reason {
/** The value is needed as the default program counter or disassembly context */
RE_INIT,
/** The value is needed by the emulator in the course of execution */ /** The value is needed by the emulator in the course of execution */
EXECUTE, EXECUTE,
/** The value is being inspected */ /** The value is being inspected */
@ -59,6 +62,9 @@ public interface PcodeExecutorStatePiece<A, T> {
*/ */
default void checkRange(AddressSpace space, long offset, int size) { default void checkRange(AddressSpace space, long offset, int size) {
// TODO: Perhaps get/setVar should just take an AddressRange? // TODO: Perhaps get/setVar should just take an AddressRange?
if (space.isConstantSpace()) {
return;
}
try { try {
new AddressRangeImpl(space.getAddress(offset), size); new AddressRangeImpl(space.getAddress(offset), size);
} }
@ -93,7 +99,9 @@ public interface PcodeExecutorStatePiece<A, T> {
* *
* @return the copy * @return the copy
*/ */
PcodeExecutorStatePiece<A, T> fork(); default PcodeExecutorStatePiece<A, T> fork() {
throw new UnsupportedOperationException();
}
/** /**
* Set the value of a register variable * Set the value of a register variable

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -31,7 +30,7 @@ import ghidra.program.model.address.Address;
/// ///
/// depending on the type of breakpoint they currently want to invoke /// depending on the type of breakpoint they currently want to invoke
public abstract class BreakTable { public interface BreakTable {
/// \brief Associate a particular emulator with breakpoints in this table /// \brief Associate a particular emulator with breakpoints in this table
/// ///

View file

@ -29,7 +29,7 @@ import ghidra.program.model.address.Address;
/// ///
/// Breakpoints are stored in map containers, and the core BreakTable methods /// Breakpoints are stored in map containers, and the core BreakTable methods
/// are implemented to search in these containers /// are implemented to search in these containers
public class BreakTableCallBack extends BreakTable { public class BreakTableCallBack implements BreakTable {
public static final String DEFAULT_NAME = "*"; public static final String DEFAULT_NAME = "*";

View file

@ -0,0 +1,269 @@
/* ###
* 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.pcode.memstate;
import java.math.BigInteger;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.Varnode;
public abstract class AbstractMemoryState implements MemoryState {
final Language language;
public AbstractMemoryState(Language language) {
this.language = language;
}
/**
* A convenience method for setting a value directly on a varnode rather than breaking out the
* components
*
* @param vn the varnode location to be written
* @param cval the value to write into the varnode location
*/
@Override
public final void setValue(Varnode vn, long cval) {
Address addr = vn.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), vn.getSize(), cval);
}
/**
* A convenience method for setting a value directly on a register rather than breaking out the
* components
*
* @param reg the register location to be written
* @param cval the value to write into the register location
*/
@Override
public final void setValue(Register reg, long cval) {
Address addr = reg.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize(), cval);
}
/**
* This is a convenience method for setting registers by name. Any register name known to the
* language can be used as a write location. The associated address space, offset, and size is
* looked up and automatically passed to the main setValue routine.
*
* @param nm is the name of the register
* @param cval is the value to write to the register
*/
@Override
public final void setValue(String nm, long cval) {
// Set a "register" value
setValue(language.getRegister(nm), cval);
}
/**
* This is the main interface for writing values to the MemoryState. If there is no registered
* MemoryBank for the desired address space, or if there is some other error, an exception is
* thrown.
*
* @param spc is the address space to write to
* @param off is the offset where the value should be written
* @param size is the number of bytes to be written
* @param cval is the value to be written
*/
@Override
public final void setValue(AddressSpace spc, long off, int size, long cval) {
setChunk(Utils.longToBytes(cval, size, language.isBigEndian()), spc, off, size);
}
/**
* A convenience method for reading a value directly from a varnode rather than querying for the
* offset and space
*
* @param vn the varnode location to be read
* @return the value read from the varnode location
*/
@Override
public final long getValue(Varnode vn) {
Address addr = vn.getAddress();
return getValue(addr.getAddressSpace(), addr.getOffset(), vn.getSize());
}
/**
* A convenience method for reading a value directly from a register rather than querying for
* the offset and space
*
* @param reg the register location to be read
* @return the value read from the register location
*/
@Override
public final long getValue(Register reg) {
Address addr = reg.getAddress();
return getValue(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize());
}
/**
* This is a convenience method for reading registers by name. any register name known to the
* language can be used as a read location. The associated address space, offset, and size is
* looked up and automatically passed to the main getValue routine.
*
* @param nm is the name of the register
* @return the value associated with that register
*/
@Override
public final long getValue(String nm) {
// Get a "register" value
return getValue(language.getRegister(nm));
}
/**
* This is the main interface for reading values from the MemoryState. If there is no registered
* MemoryBank for the desired address space, or if there is some other error, an exception is
* thrown.
*
* @param spc is the address space being queried
* @param off is the offset of the value being queried
* @param size is the number of bytes to query
* @return the queried value
*/
@Override
public final long getValue(AddressSpace spc, long off, int size) {
if (spc.isConstantSpace()) {
return off;
}
byte[] bytes = new byte[size];
getChunk(bytes, spc, off, size, false);
return Utils.bytesToLong(bytes, size, language.isBigEndian());
}
/**
* A convenience method for setting a value directly on a varnode rather than breaking out the
* components
*
* @param vn the varnode location to be written
* @param cval the value to write into the varnode location
*/
@Override
public final void setValue(Varnode vn, BigInteger cval) {
Address addr = vn.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), vn.getSize(), cval);
}
/**
* A convenience method for setting a value directly on a register rather than breaking out the
* components
*
* @param reg the register location to be written
* @param cval the value to write into the register location
*/
@Override
public final void setValue(Register reg, BigInteger cval) {
Address addr = reg.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize(), cval);
}
/**
* This is a convenience method for setting registers by name. Any register name known to the
* language can be used as a write location. The associated address space, offset, and size is
* looked up and automatically passed to the main setValue routine.
*
* @param nm is the name of the register
* @param cval is the value to write to the register
*/
@Override
public final void setValue(String nm, BigInteger cval) {
// Set a "register" value
setValue(language.getRegister(nm), cval);
}
/**
* This is the main interface for writing values to the MemoryState. If there is no registered
* MemoryBank for the desired address space, or if there is some other error, an exception is
* thrown.
*
* @param spc is the address space to write to
* @param off is the offset where the value should be written
* @param size is the number of bytes to be written
* @param cval is the value to be written
*/
@Override
public final void setValue(AddressSpace spc, long off, int size, BigInteger cval) {
setChunk(Utils.bigIntegerToBytes(cval, size, language.isBigEndian()), spc, off, size);
}
/**
* A convenience method for reading a value directly from a varnode rather than querying for the
* offset and space
*
* @param vn the varnode location to be read
* @param signed true if signed value should be returned, false for unsigned value
* @return the unsigned value read from the varnode location
*/
@Override
public final BigInteger getBigInteger(Varnode vn, boolean signed) {
Address addr = vn.getAddress();
return getBigInteger(addr.getAddressSpace(), addr.getOffset(), vn.getSize(), signed);
}
/**
* A convenience method for reading a value directly from a register rather than querying for
* the offset and space
*
* @param reg the register location to be read
* @return the unsigned value read from the register location
*/
@Override
public final BigInteger getBigInteger(Register reg) {
Address addr = reg.getAddress();
return getBigInteger(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize(),
false);
}
/**
* This is a convenience method for reading registers by name. any register name known to the
* language can be used as a read location. The associated address space, offset, and size is
* looked up and automatically passed to the main getValue routine.
*
* @param nm is the name of the register
* @return the unsigned value associated with that register
*/
@Override
public final BigInteger getBigInteger(String nm) {
// Get a "register" value
return getBigInteger(language.getRegister(nm));
}
/**
* This is the main interface for reading values from the MemoryState. If there is no registered
* MemoryBank for the desired address space, or if there is some other error, an exception is
* thrown.
*
* @param spc is the address space being queried
* @param off is the offset of the value being queried
* @param size is the number of bytes to query
* @param signed true if signed value should be returned, false for unsigned value
* @return the queried unsigned value
*/
@Override
public final BigInteger getBigInteger(AddressSpace spc, long off, int size, boolean signed) {
if (spc.isConstantSpace()) {
if (!signed && off < 0) {
return new BigInteger(1, Utils.longToBytes(off, 8, true));
}
return BigInteger.valueOf(off);
}
byte[] bytes = new byte[size];
getChunk(bytes, spc, off, size, false);
return Utils.bytesToBigInteger(bytes, size, language.isBigEndian(), signed);
}
}

View file

@ -0,0 +1,148 @@
/* ###
* 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.pcode.memstate;
import generic.stl.VectorSTL;
import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
/**
* All storage/state for a pcode emulator machine
*
* Every piece of information in a pcode emulator machine is representable as a triple
* (AddressSpace,offset,size). This class allows getting and setting of all state information of
* this form.
*/
public class DefaultMemoryState extends AbstractMemoryState {
VectorSTL<MemoryBank> memspace = new VectorSTL<MemoryBank>();
/**
* MemoryState constructor for a specified processor language
*
* @param language
*/
public DefaultMemoryState(Language language) {
super(language);
}
/**
* MemoryBanks associated with specific address spaces must be registers with this MemoryState
* via this method. Each address space that will be used during emulation must be registered
* separately. The MemoryState object does not assume responsibility for freeing the MemoryBank.
*
* @param bank is a pointer to the MemoryBank to be registered
*/
@Override
public final void setMemoryBank(MemoryBank bank) {
AddressSpace spc = bank.getSpace();
int index = spc.getUnique();
while (index >= memspace.size())
memspace.push_back(null);
memspace.set(index, bank);
}
/**
* Any MemoryBank that has been registered with this MemoryState can be retrieved via this
* method if the MemoryBank's associated address space is known.
*
* @param spc is the address space of the desired MemoryBank
* @return the MemoryBank or null if no bank is associated with spc.
*/
@Override
public final MemoryBank getMemoryBank(AddressSpace spc) {
int index = spc.getUnique();
if (index >= memspace.size())
return null;
return memspace.get(index);
}
/**
* This is the main interface for reading a range of bytes from the MemorySate. The MemoryBank
* associated with the address space of the query is looked up and the request is forwarded to
* the getChunk method on the MemoryBank. If there is no registered MemoryBank or some other
* error, an exception is thrown. All getLongValue methods utilize this method to read the bytes
* from the appropriate memory bank.
*
* @param res the result buffer for storing retrieved bytes
* @param spc the desired address space
* @param off the starting offset of the byte range being read
* @param size the number of bytes being read
* @param stopOnUnintialized if true a partial read is permitted and returned size may be
* smaller than size requested
* @return number of bytes actually read
* @throws LowlevelError if spc has not been mapped within this MemoryState or memory fault
* handler generated error
*/
@Override
public int getChunk(byte[] res, AddressSpace spc, long off, int size,
boolean stopOnUnintialized) {
if (spc.isConstantSpace()) {
System.arraycopy(Utils.longToBytes(off, size, language.isBigEndian()), 0, res, 0, size);
return size;
}
MemoryBank mspace = getMemoryBank(spc);
if (mspace == null)
throw new LowlevelError("Getting chunk from unmapped memory space: " + spc.getName());
return mspace.getChunk(off, size, res, stopOnUnintialized);
}
/**
* This is the main interface for setting values for a range of bytes in the MemoryState. The
* MemoryBank associated with the desired address space is looked up and the write is forwarded
* to the setChunk method on the MemoryBank. If there is no registered MemoryBank or some other
* error, an exception is throw. All setValue methods utilize this method to read the bytes from
* the appropriate memory bank.
*
* @param val the byte values to be written into the MemoryState
* @param spc the address space being written
* @param off the starting offset of the range being written
* @param size the number of bytes to write
* @throws LowlevelError if spc has not been mapped within this MemoryState
*/
@Override
public void setChunk(byte[] val, AddressSpace spc, long off, int size) {
MemoryBank mspace = getMemoryBank(spc);
if (mspace == null)
throw new LowlevelError("Setting chunk of unmapped memory space: " + spc.getName());
mspace.setChunk(off, size, val);
}
/**
* This is the main interface for setting the initialization status for a range of bytes in the
* MemoryState. The MemoryBank associated with the desired address space is looked up and the
* write is forwarded to the setInitialized method on the MemoryBank. If there is no registered
* MemoryBank or some other error, an exception is throw. All setValue methods utilize this
* method to read the bytes from the appropriate memory bank.
*
* @param initialized indicates if range should be marked as initialized or not
* @param spc the address space being written
* @param off the starting offset of the range being written
* @param size the number of bytes to write
*/
@Override
public void setInitialized(boolean initialized, AddressSpace spc, long off, int size) {
MemoryBank mspace = getMemoryBank(spc);
if (mspace == null)
throw new LowlevelError("Setting intialization status of unmapped memory space: " +
spc.getName());
mspace.setInitialized(off, size, initialized);
}
}

View file

@ -16,47 +16,13 @@
package ghidra.pcode.memstate; package ghidra.pcode.memstate;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import generic.stl.VectorSTL;
import ghidra.pcode.error.LowlevelError; import ghidra.pcode.error.LowlevelError;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.program.model.pcode.Varnode; import ghidra.program.model.pcode.Varnode;
/** public interface MemoryState {
* All storage/state for a pcode emulator machine
*
* Every piece of information in a pcode emulator machine is representable as a triple
* (AddressSpace,offset,size). This class allows getting and setting
* of all state information of this form.
*/
public class MemoryState {
Language language;
VectorSTL<MemoryBank> memspace = new VectorSTL<MemoryBank>();
Map<Register, Varnode> regVarnodeCache = new HashMap<Register, Varnode>();
/**
* MemoryState constructor for a specified processor language
* @param language
*/
public MemoryState(Language language) {
this.language = language;
}
private Varnode getVarnode(Register reg) {
Varnode varnode = regVarnodeCache.get(reg);
if (varnode == null) {
varnode = new Varnode(reg.getAddress(), reg.getMinimumByteSize());
regVarnodeCache.put(reg, varnode);
}
return varnode;
}
/** /**
* MemoryBanks associated with specific address spaces must be registers with this MemoryState * MemoryBanks associated with specific address spaces must be registers with this MemoryState
@ -64,15 +30,7 @@ public class MemoryState {
* separately. The MemoryState object does not assume responsibility for freeing the MemoryBank. * separately. The MemoryState object does not assume responsibility for freeing the MemoryBank.
* @param bank is a pointer to the MemoryBank to be registered * @param bank is a pointer to the MemoryBank to be registered
*/ */
public final void setMemoryBank(MemoryBank bank) { void setMemoryBank(MemoryBank bank);
AddressSpace spc = bank.getSpace();
int index = spc.getUnique();
while (index >= memspace.size())
memspace.push_back(null);
memspace.set(index, bank);
}
/** /**
* Any MemoryBank that has been registered with this MemoryState can be retrieved via this * Any MemoryBank that has been registered with this MemoryState can be retrieved via this
@ -80,12 +38,7 @@ public class MemoryState {
* @param spc is the address space of the desired MemoryBank * @param spc is the address space of the desired MemoryBank
* @return the MemoryBank or null if no bank is associated with spc. * @return the MemoryBank or null if no bank is associated with spc.
*/ */
public final MemoryBank getMemoryBank(AddressSpace spc) { MemoryBank getMemoryBank(AddressSpace spc);
int index = spc.getUnique();
if (index >= memspace.size())
return null;
return memspace.get(index);
}
/** /**
* A convenience method for setting a value directly on a varnode rather than * A convenience method for setting a value directly on a varnode rather than
@ -93,10 +46,7 @@ public class MemoryState {
* @param vn the varnode location to be written * @param vn the varnode location to be written
* @param cval the value to write into the varnode location * @param cval the value to write into the varnode location
*/ */
public final void setValue(Varnode vn, long cval) { void setValue(Varnode vn, long cval);
Address addr = vn.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), vn.getSize(), cval);
}
/** /**
* A convenience method for setting a value directly on a register rather than * A convenience method for setting a value directly on a register rather than
@ -104,10 +54,7 @@ public class MemoryState {
* @param reg the register location to be written * @param reg the register location to be written
* @param cval the value to write into the register location * @param cval the value to write into the register location
*/ */
public final void setValue(Register reg, long cval) { void setValue(Register reg, long cval);
Address addr = reg.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize(), cval);
}
/** /**
* This is a convenience method for setting registers by name. * This is a convenience method for setting registers by name.
@ -117,12 +64,7 @@ public class MemoryState {
* @param nm is the name of the register * @param nm is the name of the register
* @param cval is the value to write to the register * @param cval is the value to write to the register
*/ */
public final void setValue(String nm, long cval) { void setValue(String nm, long cval);
// Set a "register" value
Varnode vdata = getVarnode(language.getRegister(nm));
Address addr = vdata.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), vdata.getSize(), cval);
}
/** /**
* This is the main interface for writing values to the MemoryState. * This is the main interface for writing values to the MemoryState.
@ -133,9 +75,7 @@ public class MemoryState {
* @param size is the number of bytes to be written * @param size is the number of bytes to be written
* @param cval is the value to be written * @param cval is the value to be written
*/ */
public final void setValue(AddressSpace spc, long off, int size, long cval) { void setValue(AddressSpace spc, long off, int size, long cval);
setChunk(Utils.longToBytes(cval, size, language.isBigEndian()), spc, off, size);
}
/** /**
* A convenience method for reading a value directly from a varnode rather * A convenience method for reading a value directly from a varnode rather
@ -143,10 +83,7 @@ public class MemoryState {
* @param vn the varnode location to be read * @param vn the varnode location to be read
* @return the value read from the varnode location * @return the value read from the varnode location
*/ */
public final long getValue(Varnode vn) { long getValue(Varnode vn);
Address addr = vn.getAddress();
return getValue(addr.getAddressSpace(), addr.getOffset(), vn.getSize());
}
/** /**
* A convenience method for reading a value directly from a register rather * A convenience method for reading a value directly from a register rather
@ -154,10 +91,7 @@ public class MemoryState {
* @param reg the register location to be read * @param reg the register location to be read
* @return the value read from the register location * @return the value read from the register location
*/ */
public final long getValue(Register reg) { long getValue(Register reg);
Address addr = reg.getAddress();
return getValue(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize());
}
/** /**
* This is a convenience method for reading registers by name. * This is a convenience method for reading registers by name.
@ -167,12 +101,7 @@ public class MemoryState {
* @param nm is the name of the register * @param nm is the name of the register
* @return the value associated with that register * @return the value associated with that register
*/ */
public final long getValue(String nm) { long getValue(String nm);
// Get a "register" value
Varnode vdata = getVarnode(language.getRegister(nm));
Address addr = vdata.getAddress();
return getValue(addr.getAddressSpace(), addr.getOffset(), vdata.getSize());
}
/** /**
* This is the main interface for reading values from the MemoryState. * This is the main interface for reading values from the MemoryState.
@ -183,14 +112,7 @@ public class MemoryState {
* @param size is the number of bytes to query * @param size is the number of bytes to query
* @return the queried value * @return the queried value
*/ */
public final long getValue(AddressSpace spc, long off, int size) { long getValue(AddressSpace spc, long off, int size);
if (spc.isConstantSpace()) {
return off;
}
byte[] bytes = new byte[size];
getChunk(bytes, spc, off, size, false);
return Utils.bytesToLong(bytes, size, language.isBigEndian());
}
/** /**
* A convenience method for setting a value directly on a varnode rather than * A convenience method for setting a value directly on a varnode rather than
@ -198,10 +120,7 @@ public class MemoryState {
* @param vn the varnode location to be written * @param vn the varnode location to be written
* @param cval the value to write into the varnode location * @param cval the value to write into the varnode location
*/ */
public final void setValue(Varnode vn, BigInteger cval) { void setValue(Varnode vn, BigInteger cval);
Address addr = vn.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), vn.getSize(), cval);
}
/** /**
* A convenience method for setting a value directly on a register rather than * A convenience method for setting a value directly on a register rather than
@ -209,10 +128,7 @@ public class MemoryState {
* @param reg the register location to be written * @param reg the register location to be written
* @param cval the value to write into the register location * @param cval the value to write into the register location
*/ */
public final void setValue(Register reg, BigInteger cval) { void setValue(Register reg, BigInteger cval);
Address addr = reg.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize(), cval);
}
/** /**
* This is a convenience method for setting registers by name. * This is a convenience method for setting registers by name.
@ -222,12 +138,7 @@ public class MemoryState {
* @param nm is the name of the register * @param nm is the name of the register
* @param cval is the value to write to the register * @param cval is the value to write to the register
*/ */
public final void setValue(String nm, BigInteger cval) { void setValue(String nm, BigInteger cval);
// Set a "register" value
Varnode vdata = getVarnode(language.getRegister(nm));
Address addr = vdata.getAddress();
setValue(addr.getAddressSpace(), addr.getOffset(), vdata.getSize(), cval);
}
/** /**
* This is the main interface for writing values to the MemoryState. * This is the main interface for writing values to the MemoryState.
@ -238,9 +149,7 @@ public class MemoryState {
* @param size is the number of bytes to be written * @param size is the number of bytes to be written
* @param cval is the value to be written * @param cval is the value to be written
*/ */
public final void setValue(AddressSpace spc, long off, int size, BigInteger cval) { void setValue(AddressSpace spc, long off, int size, BigInteger cval);
setChunk(Utils.bigIntegerToBytes(cval, size, language.isBigEndian()), spc, off, size);
}
/** /**
* A convenience method for reading a value directly from a varnode rather * A convenience method for reading a value directly from a varnode rather
@ -249,10 +158,7 @@ public class MemoryState {
* @param signed true if signed value should be returned, false for unsigned value * @param signed true if signed value should be returned, false for unsigned value
* @return the unsigned value read from the varnode location * @return the unsigned value read from the varnode location
*/ */
public final BigInteger getBigInteger(Varnode vn, boolean signed) { BigInteger getBigInteger(Varnode vn, boolean signed);
Address addr = vn.getAddress();
return getBigInteger(addr.getAddressSpace(), addr.getOffset(), vn.getSize(), signed);
}
/** /**
* A convenience method for reading a value directly from a register rather * A convenience method for reading a value directly from a register rather
@ -260,11 +166,7 @@ public class MemoryState {
* @param reg the register location to be read * @param reg the register location to be read
* @return the unsigned value read from the register location * @return the unsigned value read from the register location
*/ */
public final BigInteger getBigInteger(Register reg) { BigInteger getBigInteger(Register reg);
Address addr = reg.getAddress();
return getBigInteger(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize(),
false);
}
/** /**
* This is a convenience method for reading registers by name. * This is a convenience method for reading registers by name.
@ -274,12 +176,7 @@ public class MemoryState {
* @param nm is the name of the register * @param nm is the name of the register
* @return the unsigned value associated with that register * @return the unsigned value associated with that register
*/ */
public final BigInteger getBigInteger(String nm) { BigInteger getBigInteger(String nm);
// Get a "register" value
Varnode vdata = getVarnode(language.getRegister(nm));
Address addr = vdata.getAddress();
return getBigInteger(addr.getAddressSpace(), addr.getOffset(), vdata.getSize(), false);
}
/** /**
* This is the main interface for reading values from the MemoryState. * This is the main interface for reading values from the MemoryState.
@ -291,17 +188,7 @@ public class MemoryState {
* @param signed true if signed value should be returned, false for unsigned value * @param signed true if signed value should be returned, false for unsigned value
* @return the queried unsigned value * @return the queried unsigned value
*/ */
public final BigInteger getBigInteger(AddressSpace spc, long off, int size, boolean signed) { BigInteger getBigInteger(AddressSpace spc, long off, int size, boolean signed);
if (spc.isConstantSpace()) {
if (!signed && off < 0) {
return new BigInteger(1, Utils.longToBytes(off, 8, true));
}
return BigInteger.valueOf(off);
}
byte[] bytes = new byte[size];
getChunk(bytes, spc, off, size, false);
return Utils.bytesToBigInteger(bytes, size, language.isBigEndian(), signed);
}
/** /**
* This is the main interface for reading a range of bytes from the MemorySate. * This is the main interface for reading a range of bytes from the MemorySate.
@ -320,17 +207,8 @@ public class MemoryState {
* @throws LowlevelError if spc has not been mapped within this MemoryState or memory fault * @throws LowlevelError if spc has not been mapped within this MemoryState or memory fault
* handler generated error * handler generated error
*/ */
public int getChunk(byte[] res, AddressSpace spc, long off, int size, int getChunk(byte[] res, AddressSpace spc, long off, int size,
boolean stopOnUnintialized) { boolean stopOnUnintialized);
if (spc.isConstantSpace()) {
System.arraycopy(Utils.longToBytes(off, size, language.isBigEndian()), 0, res, 0, size);
return size;
}
MemoryBank mspace = getMemoryBank(spc);
if (mspace == null)
throw new LowlevelError("Getting chunk from unmapped memory space: " + spc.getName());
return mspace.getChunk(off, size, res, stopOnUnintialized);
}
/** /**
* This is the main interface for setting values for a range of bytes in the MemoryState. * This is the main interface for setting values for a range of bytes in the MemoryState.
@ -345,12 +223,7 @@ public class MemoryState {
* @param size the number of bytes to write * @param size the number of bytes to write
* @throws LowlevelError if spc has not been mapped within this MemoryState * @throws LowlevelError if spc has not been mapped within this MemoryState
*/ */
public void setChunk(byte[] val, AddressSpace spc, long off, int size) { void setChunk(byte[] val, AddressSpace spc, long off, int size);
MemoryBank mspace = getMemoryBank(spc);
if (mspace == null)
throw new LowlevelError("Setting chunk of unmapped memory space: " + spc.getName());
mspace.setChunk(off, size, val);
}
/** /**
* This is the main interface for setting the initialization status for a range of bytes * This is the main interface for setting the initialization status for a range of bytes
@ -365,12 +238,6 @@ public class MemoryState {
* @param off the starting offset of the range being written * @param off the starting offset of the range being written
* @param size the number of bytes to write * @param size the number of bytes to write
*/ */
public void setInitialized(boolean initialized, AddressSpace spc, long off, int size) { void setInitialized(boolean initialized, AddressSpace spc, long off, int size);
MemoryBank mspace = getMemoryBank(spc);
if (mspace == null)
throw new LowlevelError("Setting intialization status of unmapped memory space: " +
spc.getName());
mspace.setInitialized(off, size, initialized);
}
} }