mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
Merge remote-tracking branch 'origin/GP-2642_Dan_compatEmulatorHelper--SQUASHED'
This commit is contained in:
commit
254e749f95
25 changed files with 1815 additions and 741 deletions
|
@ -155,6 +155,11 @@ class TestThread implements PcodeThread<Void> {
|
|||
public void setSuspended(boolean suspended) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuspended() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeUseropLibrary<Void> getUseropLibrary() {
|
||||
return null;
|
||||
|
|
|
@ -63,7 +63,12 @@ public class EmulatorTestRunner {
|
|||
this.program = program;
|
||||
this.testGroup = testGroup;
|
||||
this.executionListener = executionListener;
|
||||
emuHelper = new EmulatorHelper(program);
|
||||
emuHelper = new EmulatorHelper(program) {
|
||||
@Override
|
||||
protected Emulator newEmulator() {
|
||||
return new AdaptedEmulator(this);
|
||||
}
|
||||
};
|
||||
emu = emuHelper.getEmulator();
|
||||
emuHelper.setMemoryFaultHandler(new MyMemoryFaultHandler(executionListener));
|
||||
|
||||
|
@ -71,7 +76,7 @@ public class EmulatorTestRunner {
|
|||
@Override
|
||||
public boolean pcodeCallback(PcodeOpRaw op) throws LowlevelError {
|
||||
int userOp = (int) op.getInput(0).getOffset();
|
||||
String pcodeOpName = emulate.getLanguage().getUserDefinedOpName(userOp);
|
||||
String pcodeOpName = program.getLanguage().getUserDefinedOpName(userOp);
|
||||
unimplementedSet.add(pcodeOpName);
|
||||
String outStr = "";
|
||||
Varnode output = op.getOutput();
|
||||
|
@ -169,6 +174,7 @@ public class EmulatorTestRunner {
|
|||
|
||||
/**
|
||||
* Add memory dump point
|
||||
*
|
||||
* @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.
|
||||
* @param dumpAddr memory address which should be dumped
|
||||
|
@ -190,6 +196,7 @@ public class EmulatorTestRunner {
|
|||
|
||||
/**
|
||||
* Add memory dump point
|
||||
*
|
||||
* @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.
|
||||
* @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.
|
||||
* This number should be subtracted from the pass count and possibly added
|
||||
* to the failure count. Number does not reflect total number of CALLOTHER
|
||||
* pcodeops encountered but only the number of passed tests affected.
|
||||
* See log for all CALLOTHER executions detected.
|
||||
* Get number of CALLOTHER errors detected when a test pass was registered. This number should
|
||||
* be subtracted from the pass count and possibly added to the failure count. Number does not
|
||||
* reflect total number of CALLOTHER pcodeops encountered but only the number of passed tests
|
||||
* affected. See log for all CALLOTHER executions detected.
|
||||
*
|
||||
* @return number of CALLOTHER errors
|
||||
*/
|
||||
public int getCallOtherErrors() {
|
||||
|
@ -243,6 +250,7 @@ public class EmulatorTestRunner {
|
|||
|
||||
/**
|
||||
* Execute test group without instruction stepping/tracing
|
||||
*
|
||||
* @param timeLimitMS
|
||||
* @param monitor
|
||||
* @return
|
||||
|
@ -695,7 +703,8 @@ public class EmulatorTestRunner {
|
|||
@Override
|
||||
Address getDumpAddress() {
|
||||
RegisterValue regVal = getRegisterValue(dumpAddrReg);
|
||||
return dumpAddrSpace.getAddress(regVal.getUnsignedValue().longValue()).add(
|
||||
return dumpAddrSpace.getAddress(regVal.getUnsignedValue().longValue())
|
||||
.add(
|
||||
relativeOffset);
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -15,453 +15,162 @@
|
|||
*/
|
||||
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.pcode.memstate.MemoryState;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class Emulator {
|
||||
|
||||
private final MemoryFaultHandler faultHandler;
|
||||
|
||||
private SleighLanguage language;
|
||||
private AddressFactory addrFactory;
|
||||
|
||||
private CompositeLoadImage loadImage = new CompositeLoadImage();
|
||||
|
||||
private RegisterState mstate;
|
||||
private MemoryPageBank registerState;
|
||||
private FilteredMemoryState memState;
|
||||
private ghidra.pcode.emulate.BreakTableCallBack breakTable;
|
||||
private Emulate emulator;
|
||||
|
||||
private boolean emuHalt = true;
|
||||
private boolean isExecuting = false;
|
||||
|
||||
private boolean writeBack = false;
|
||||
private int pageSize; // 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");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The emulator interface
|
||||
*
|
||||
* <p>
|
||||
* This interface may soon be deprecated. It was extracted from what has now been renamed
|
||||
* {@link DefaultEmulator}. Please consider using {@link PcodeEmulator} instead.
|
||||
*/
|
||||
public interface Emulator {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Get the name of the program counter register
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
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);
|
||||
}
|
||||
String getPCRegisterName();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Set the value of the program counter
|
||||
*
|
||||
* @param addressableWordOffset the <em>word</em> offset of the instruction to execute next.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
void setExecuteAddress(long addressableWordOffset);
|
||||
|
||||
/**
|
||||
* @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() {
|
||||
return getHalt() && emulator.getExecutionState() == EmulateExecutionState.BREAKPOINT;
|
||||
}
|
||||
Address getExecuteAddress();
|
||||
|
||||
/**
|
||||
* @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).
|
||||
* Get the address of the last instruction executed (or the instructed currently being executed)
|
||||
*
|
||||
* @return the address
|
||||
*/
|
||||
public EmulateExecutionState getEmulateExecutionState() {
|
||||
return emulator.getExecutionState();
|
||||
}
|
||||
Address getLastExecuteAddress();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public boolean isExecuting() {
|
||||
return isExecuting;
|
||||
}
|
||||
|
||||
public SleighLanguage getLanguage() {
|
||||
return language;
|
||||
}
|
||||
boolean isExecuting();
|
||||
|
||||
/**
|
||||
* Disassemble from the current execute address
|
||||
* @param count number of contiguous instructions to disassemble
|
||||
* @return list of instructions
|
||||
* Get the low-level execution state
|
||||
*
|
||||
* <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) {
|
||||
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;
|
||||
}
|
||||
EmulateExecutionState getEmulateExecutionState();
|
||||
|
||||
/**
|
||||
* Returns the current context register value. The context value returned reflects
|
||||
* its state when the previously executed instruction was
|
||||
* parsed/executed. The context value returned will feed into the next
|
||||
* instruction to be parsed with its non-flowing bits cleared and
|
||||
* any future context state merged in.
|
||||
* @return context as a RegisterValue object
|
||||
* Get the memory state
|
||||
*
|
||||
* @return the state
|
||||
*/
|
||||
public RegisterValue getContextRegisterValue() {
|
||||
return emulator.getContextRegisterValue();
|
||||
}
|
||||
MemoryState getMemState();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The Emulator should not be running when this method is invoked.
|
||||
* Only flowing context bits should be set, as non-flowing bits
|
||||
* will be cleared prior to parsing on instruction. In addition,
|
||||
* any future context state set by the pcode emitter will
|
||||
* take precedence over context set using this method. This method
|
||||
* is primarily intended to be used to establish the initial
|
||||
*
|
||||
* <p>
|
||||
* The Emulator should not be running when this method is invoked. Only flowing context bits
|
||||
* should be set, as non-flowing bits will be cleared prior to parsing on instruction. In
|
||||
* addition, any future context state set by the pcode emitter will take precedence over context
|
||||
* set using this method. This method is primarily intended to be used to establish the initial
|
||||
* context state.
|
||||
*
|
||||
* @param regValue is the value to set context to
|
||||
*/
|
||||
public void setContextRegisterValue(RegisterValue regValue) {
|
||||
emulator.setContextRegisterValue(regValue);
|
||||
}
|
||||
void setContextRegisterValue(RegisterValue regValue);
|
||||
|
||||
/**
|
||||
* Add memory load image provider
|
||||
* @param provider memory load image provider
|
||||
* @param view memory region which corresponds to provider
|
||||
* Returns the current context register value.
|
||||
*
|
||||
* <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) {
|
||||
loadImage.addProvider(provider, view);
|
||||
}
|
||||
RegisterValue getContextRegisterValue();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import ghidra.app.emulator.memory.*;
|
|||
import ghidra.app.emulator.state.DumpMiscState;
|
||||
import ghidra.app.emulator.state.RegisterState;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.pcode.emu.PcodeEmulator;
|
||||
import ghidra.pcode.emulate.BreakCallBack;
|
||||
import ghidra.pcode.emulate.EmulateExecutionState;
|
||||
import ghidra.pcode.memstate.MemoryFaultHandler;
|
||||
|
@ -38,6 +39,17 @@ import ghidra.util.exception.CancelledException;
|
|||
import ghidra.util.exception.DuplicateNameException;
|
||||
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 {
|
||||
|
||||
private final Program program;
|
||||
|
@ -68,11 +80,15 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
stackPtrReg = program.getCompilerSpec().getStackPointer();
|
||||
stackMemorySpace = program.getCompilerSpec().getStackBaseSpace();
|
||||
|
||||
emulator = new Emulator(this);
|
||||
emulator = newEmulator();
|
||||
|
||||
converter = DataConverter.getInstance(program.getMemory().isBigEndian());
|
||||
}
|
||||
|
||||
protected Emulator newEmulator() {
|
||||
return new DefaultEmulator(this);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
emulator.dispose();
|
||||
if (memoryWriteTracker != null) {
|
||||
|
@ -115,6 +131,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Get Program Counter (PC) register defined by applicable processor specification
|
||||
*
|
||||
* @return Program Counter register
|
||||
*/
|
||||
public Register getPCRegister() {
|
||||
|
@ -123,6 +140,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Get Stack Pointer register defined by applicable compiler specification
|
||||
*
|
||||
* @return Stack Pointer register
|
||||
*/
|
||||
public Register getStackPointerRegister() {
|
||||
|
@ -130,14 +148,12 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
}
|
||||
|
||||
/**
|
||||
* Provides ability to install a low-level memory fault handler.
|
||||
* The handler methods should generally return 'false' to allow
|
||||
* the default handler to generate the appropriate target error.
|
||||
* Within the fault handler, the EmulateExecutionState can be used
|
||||
* to distinguish the pcode-emit state and the actual execution state
|
||||
* since an attempt to execute an instruction at an uninitialized
|
||||
* memory location will cause an uninitializedRead during the PCODE_EMIT
|
||||
* state.
|
||||
* Provides ability to install a low-level memory fault handler. The handler methods should
|
||||
* generally return 'false' to allow the default handler to generate the appropriate target
|
||||
* error. Within the fault handler, the EmulateExecutionState can be used to distinguish the
|
||||
* pcode-emit state and the actual execution state since an attempt to execute an instruction at
|
||||
* an uninitialized memory location will cause an uninitializedRead during the PCODE_EMIT state.
|
||||
*
|
||||
* @param handler memory fault handler.
|
||||
*/
|
||||
public void setMemoryFaultHandler(MemoryFaultHandler handler) {
|
||||
|
@ -215,9 +231,10 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Read string from memory state.
|
||||
*
|
||||
* @param addr memory address
|
||||
* @param maxLength limit string read to this length. If return string is
|
||||
* truncated, "..." will be appended.
|
||||
* @param maxLength limit string read to this length. If return string is truncated, "..." will
|
||||
* be appended.
|
||||
* @return string read from memory state
|
||||
*/
|
||||
public String readNullTerminatedString(Address addr, int maxLength) {
|
||||
|
@ -242,7 +259,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
public byte[] readMemory(Address addr, int 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);
|
||||
if (len == 0) {
|
||||
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) {
|
||||
emulator.getMemState().setChunk(bytes, addr.getAddressSpace(), addr.getOffset(),
|
||||
emulator.getMemState()
|
||||
.setChunk(bytes, addr.getAddressSpace(), addr.getOffset(),
|
||||
bytes.length);
|
||||
}
|
||||
|
||||
|
@ -266,6 +285,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Read a stack value from the memory state.
|
||||
*
|
||||
* @param relativeOffset offset relative to current stack pointer
|
||||
* @param size data size in bytes
|
||||
* @param signed true if value read is signed, false if unsigned
|
||||
|
@ -281,6 +301,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Write a value onto the stack
|
||||
*
|
||||
* @param relativeOffset offset relative to current stack pointer
|
||||
* @param size data size in bytes
|
||||
* @param value
|
||||
|
@ -295,6 +316,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Write a value onto the stack
|
||||
*
|
||||
* @param relativeOffset offset relative to current stack pointer
|
||||
* @param size data size in bytes
|
||||
* @param value
|
||||
|
@ -309,6 +331,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Establish breakpoint
|
||||
*
|
||||
* @param addr memory address for new breakpoint
|
||||
*/
|
||||
public void setBreakpoint(Address addr) {
|
||||
|
@ -317,6 +340,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Clear breakpoint
|
||||
*
|
||||
* @param addr memory address for breakpoint to be cleared
|
||||
*/
|
||||
public void clearBreakpoint(Address addr) {
|
||||
|
@ -324,8 +348,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
}
|
||||
|
||||
/**
|
||||
* Set current context register value.
|
||||
* Keep in mind that any non-flowing context values will be stripped.
|
||||
* Set current context register value. Keep in mind that any non-flowing context values will be
|
||||
* stripped.
|
||||
*
|
||||
* @param ctxRegValue
|
||||
*/
|
||||
public void setContextRegister(RegisterValue ctxRegValue) {
|
||||
|
@ -333,8 +358,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
}
|
||||
|
||||
/**
|
||||
* Set current context register value.
|
||||
* Keep in mind that any non-flowing context values will be stripped.
|
||||
* Set current context register value. Keep in mind that any non-flowing context values will be
|
||||
* stripped.
|
||||
*
|
||||
* @param ctxReg context register
|
||||
* @param value context value
|
||||
*/
|
||||
|
@ -344,6 +370,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Get the current context register value
|
||||
*
|
||||
* @return context register value or null if not set or unknown
|
||||
*/
|
||||
public RegisterValue getContextRegister() {
|
||||
|
@ -351,9 +378,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
}
|
||||
|
||||
/**
|
||||
* Register callback for language defined pcodeop (call other).
|
||||
* WARNING! Using this method may circumvent the default CALLOTHER emulation support
|
||||
* when supplied by the Processor module.
|
||||
* Register callback for language defined pcodeop (call other). WARNING! Using this method may
|
||||
* circumvent the default CALLOTHER emulation support when supplied by the Processor module.
|
||||
*
|
||||
* @param pcodeOpName the name of the pcode op
|
||||
* @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).
|
||||
* WARNING! Using this method may circumvent the default CALLOTHER emulation support
|
||||
* when supplied by the Processor module.
|
||||
* Register default callback for language defined pcodeops (call other). WARNING! Using this
|
||||
* method may circumvent the default CALLOTHER emulation support when supplied by the Processor
|
||||
* module.
|
||||
*
|
||||
* @param callback the default callback to register
|
||||
*/
|
||||
public void registerDefaultCallOtherCallback(BreakCallBack callback) {
|
||||
|
@ -373,6 +401,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Unregister callback for language defined pcodeop (call other).
|
||||
*
|
||||
* @param pcodeOpName the name of the pcode op
|
||||
*/
|
||||
public void unregisterCallOtherCallback(String pcodeOpName) {
|
||||
|
@ -380,9 +409,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
}
|
||||
|
||||
/**
|
||||
* Unregister default callback for language defined pcodeops (call other).
|
||||
* WARNING! Using this method may circumvent the default CALLOTHER emulation support
|
||||
* when supplied by the Processor module.
|
||||
* Unregister default callback for language defined pcodeops (call other). WARNING! Using this
|
||||
* method may circumvent the default CALLOTHER emulation support when supplied by the Processor
|
||||
* module.
|
||||
*/
|
||||
public void unregisterDefaultCallOtherCallback() {
|
||||
emulator.getBreakTable().unregisterPcodeCallback("*");
|
||||
|
@ -390,6 +419,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Get current execution address
|
||||
*
|
||||
* @return current execution address
|
||||
*/
|
||||
public Address getExecutionAddress() {
|
||||
|
@ -397,11 +427,11 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
}
|
||||
|
||||
/**
|
||||
* Start execution at the specified address using the initial context specified.
|
||||
* Method will block until execution stops. This method will initialize context
|
||||
* register based upon the program stored context if not already done. In addition,
|
||||
* both general register value and the context register may be further modified
|
||||
* via the context parameter if specified.
|
||||
* Start execution at the specified address using the initial context specified. Method will
|
||||
* block until execution stops. This method will initialize context register based upon the
|
||||
* program stored context if not already done. In addition, both general register value and the
|
||||
* context register may be further modified via the context parameter if specified.
|
||||
*
|
||||
* @param addr initial program address
|
||||
* @param context optional context settings which override current program context
|
||||
* @param monitor
|
||||
|
@ -463,10 +493,10 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
}
|
||||
|
||||
/**
|
||||
* Continue execution from the current execution address.
|
||||
* No adjustment will be made to the context beyond the normal
|
||||
* context flow behavior defined by the language.
|
||||
* Method will block until execution stops.
|
||||
* Continue execution from the current execution address. No adjustment will be made to the
|
||||
* context beyond the normal context flow behavior defined by the language. Method will block
|
||||
* until execution stops.
|
||||
*
|
||||
* @param monitor
|
||||
* @return true if execution completes without error (i.e., is at breakpoint)
|
||||
* @throws CancelledException if execution cancelled via monitor
|
||||
|
@ -482,6 +512,7 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Continue execution and block until either a breakpoint hits or error occurs.
|
||||
*
|
||||
* @throws CancelledException if execution was cancelled
|
||||
*/
|
||||
private void continueExecution(TaskMonitor monitor) throws CancelledException {
|
||||
|
@ -494,8 +525,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private void executeInstruction(boolean stopAtBreakpoint, TaskMonitor monitor)
|
||||
|
@ -515,8 +547,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
lastError = t.toString();
|
||||
}
|
||||
emulator.setHalt(true); // force execution to stop
|
||||
if (t instanceof CancelledException) {
|
||||
throw (CancelledException) t;
|
||||
if (t instanceof CancelledException ce) {
|
||||
throw ce;
|
||||
}
|
||||
Msg.error(this,
|
||||
"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
|
||||
* make sure it has a context consistent with the program context
|
||||
* if there is one.
|
||||
* Used when the emulator has had the execution address changed to make sure it has a context
|
||||
* consistent with the program context if there is one.
|
||||
*/
|
||||
private void setProcessorContext() {
|
||||
// this assumes you have set the emulation address
|
||||
|
@ -554,10 +585,10 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
}
|
||||
|
||||
/**
|
||||
* Step execution one instruction which may consist of multiple
|
||||
* pcode operations. No adjustment will be made to the context beyond the normal
|
||||
* context flow behavior defined by the language.
|
||||
* Step execution one instruction which may consist of multiple pcode operations. No adjustment
|
||||
* will be made to the context beyond the normal context flow behavior defined by the language.
|
||||
* Method will block until execution stops.
|
||||
*
|
||||
* @return true if execution completes without error
|
||||
* @throws CancelledException if execution cancelled via monitor
|
||||
*/
|
||||
|
@ -568,19 +599,19 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
|
||||
/**
|
||||
* Create a new initialized memory block using the current emulator memory state
|
||||
*
|
||||
* @param name block name
|
||||
* @param start start address of the block
|
||||
* @param length the size of the block
|
||||
* @param overlay if true, the block will be created as an OVERLAY which means that a new
|
||||
* overlay address space will be created and the block will have a starting address at the same
|
||||
* offset as the given start address parameter, but in the new address space.
|
||||
* overlay address space will be created and the block will have a starting address
|
||||
* at the same offset as the given start address parameter, but in the new address
|
||||
* space.
|
||||
* @param monitor
|
||||
* @return new memory block
|
||||
* @throws LockException if exclusive lock not in place (see haveLock())
|
||||
* @throws MemoryConflictException if the new block overlaps with a
|
||||
* previous block
|
||||
* @throws AddressOverflowException if the start is beyond the
|
||||
* address space
|
||||
* @throws MemoryConflictException if the new block overlaps with a previous block
|
||||
* @throws AddressOverflowException if the start is beyond the address space
|
||||
* @throws CancelledException user cancelled operation
|
||||
* @throws DuplicateNameException
|
||||
*/
|
||||
|
@ -626,7 +657,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
boolean success = false;
|
||||
int txId = program.startTransaction("Create Memory Block");
|
||||
try {
|
||||
block = program.getMemory().createInitializedBlock(name, start, memStateStream, length,
|
||||
block = program.getMemory()
|
||||
.createInitializedBlock(name, start, memStateStream, length,
|
||||
monitor, overlay);
|
||||
success = true;
|
||||
}
|
||||
|
@ -637,8 +669,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration
|
|||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable tracking of memory writes in the form of an
|
||||
* address set.
|
||||
* Enable/Disable tracking of memory writes in the form of an address set.
|
||||
*
|
||||
* @param 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
|
||||
* if memory write tracking is enabled, otherwise null is returned.
|
||||
* The address set returned will continue to be updated unless
|
||||
* memory write tracking becomes disabled.
|
||||
* @return address set of memory locations written by the emulator if memory write tracking is
|
||||
* enabled, otherwise null is returned. The address set returned will continue to be
|
||||
* updated unless memory write tracking becomes disabled.
|
||||
*/
|
||||
public AddressSetView getTrackedMemoryWriteSet() {
|
||||
if (memoryWriteTracker != null) {
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
*/
|
||||
package ghidra.app.emulator;
|
||||
|
||||
import ghidra.pcode.memstate.MemoryState;
|
||||
import ghidra.pcode.memstate.DefaultMemoryState;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.lang.Language;
|
||||
|
||||
class FilteredMemoryState extends MemoryState {
|
||||
class FilteredMemoryState extends DefaultMemoryState {
|
||||
|
||||
private MemoryAccessFilter filter;
|
||||
private boolean filterEnabled = true; // used to prevent filtering filter queries
|
||||
|
|
|
@ -17,6 +17,14 @@ package ghidra.app.emulator;
|
|||
|
||||
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 {
|
||||
|
||||
private MemoryAccessFilter prevFilter;
|
||||
|
@ -27,23 +35,41 @@ public abstract class MemoryAccessFilter {
|
|||
private boolean filterOnExecutionOnly = true;
|
||||
|
||||
final void filterRead(AddressSpace spc, long off, int size, byte[] values) {
|
||||
if (filterOnExecutionOnly() && !emu.isExecuting()) return; // do not filter idle queries
|
||||
if (filterOnExecutionOnly() && !emu.isExecuting())
|
||||
return; // do not filter idle queries
|
||||
processRead(spc, off, size, values);
|
||||
if (nextFilter != null) {
|
||||
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);
|
||||
|
||||
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);
|
||||
if (nextFilter != null) {
|
||||
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);
|
||||
|
||||
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.
|
||||
* If overriden, be sure to invoke super.dispose().
|
||||
*
|
||||
* <p>
|
||||
* If overriden, be sure to invoke {@code super.dispose()}.
|
||||
*/
|
||||
public void dispose() {
|
||||
if (nextFilter != null) {
|
||||
|
|
|
@ -22,8 +22,22 @@ public interface RegisterState {
|
|||
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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 void setVals(String key, byte[] vals, boolean setInitiailized);
|
||||
|
|
|
@ -266,6 +266,11 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
|
|||
this.suspended = suspended;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuspended() {
|
||||
return suspended;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* if applicable
|
||||
* Notify the machine a thread has been stepped a p-code op, so that it may re-enable software
|
||||
* interrupts, if applicable
|
||||
*/
|
||||
protected void stepped() {
|
||||
if (swiMode == SwiMode.IGNORE_STEP) {
|
||||
|
|
|
@ -320,15 +320,19 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
|
|||
}
|
||||
|
||||
protected void branchToAddress(Address target) {
|
||||
overrideCounter(target);
|
||||
writeCounter(target);
|
||||
decoder.branched(counter);
|
||||
}
|
||||
|
||||
protected void writeCounter(Address counter) {
|
||||
setCounter(counter);
|
||||
state.setVar(pc,
|
||||
arithmetic.fromConst(counter.getAddressableWordOffset(), pc.getMinimumByteSize()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void overrideCounter(Address counter) {
|
||||
setCounter(counter);
|
||||
state.setVar(pc,
|
||||
arithmetic.fromConst(counter.getAddressableWordOffset(), pc.getMinimumByteSize()));
|
||||
writeCounter(counter);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -375,13 +379,13 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
|
|||
|
||||
@Override
|
||||
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));
|
||||
|
||||
if (contextreg != Register.NO_CONTEXT) {
|
||||
try {
|
||||
BigInteger ctx = arithmetic.toBigInteger(state.getVar(
|
||||
contextreg, executor.getReason()), Purpose.CONTEXT);
|
||||
BigInteger ctx = arithmetic.toBigInteger(state.getVar(contextreg, Reason.RE_INIT),
|
||||
Purpose.CONTEXT);
|
||||
assignContext(new RegisterValue(contextreg, ctx));
|
||||
}
|
||||
catch (AccessPcodeExecutionException e) {
|
||||
|
@ -582,6 +586,11 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
|
|||
executor.suspended = suspended;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuspended() {
|
||||
return executor.suspended;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SleighLanguage getLanguage() {
|
||||
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,
|
||||
* if applicable
|
||||
* Notify the machine a thread has been stepped a p-code op, so that it may re-enable software
|
||||
* interrupts, if applicable
|
||||
*/
|
||||
protected void stepped() {
|
||||
machine.stepped();
|
||||
|
|
|
@ -17,14 +17,15 @@ package ghidra.pcode.emu;
|
|||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import ghidra.app.emulator.AdaptedMemoryState;
|
||||
import ghidra.app.emulator.Emulator;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emulate.*;
|
||||
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.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;
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -125,8 +98,6 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
|
|||
protected final EmulateInstructionStateModifier modifier;
|
||||
protected final Emulate emulate;
|
||||
|
||||
protected Address savedCounter;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* written for {@link Emulator}.
|
||||
*/
|
||||
emulate = new GlueEmulate(language, new GlueMemoryState(language),
|
||||
new BreakTableCallBack(language));
|
||||
emulate = new GlueEmulate(language, new AdaptedMemoryState<>(state, Reason.EXECUTE) {
|
||||
@Override
|
||||
public void setMemoryBank(MemoryBank bank) {
|
||||
// Ignore
|
||||
}
|
||||
}, new BreakTableCallBack(language));
|
||||
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
|
||||
public void reInitialize() {
|
||||
super.reInitialize();
|
||||
public void overrideCounter(Address counter) {
|
||||
super.overrideCounter(counter);
|
||||
if (modifier != null) {
|
||||
savedCounter = getCounter();
|
||||
modifier.initialExecuteCallback(emulate, savedCounter, getContext());
|
||||
modifier.initialExecuteCallback(emulate, counter, getContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postExecuteInstruction() {
|
||||
if (modifier != null) {
|
||||
modifier.postExecuteCallback(emulate, savedCounter, frame.copyCode(),
|
||||
modifier.postExecuteCallback(emulate,
|
||||
instruction == null ? null : instruction.getAddress(), frame.copyCode(),
|
||||
frame.getBranched(), getCounter());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,6 +203,13 @@ public interface PcodeMachine<T> {
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
|
|
@ -19,7 +19,6 @@ import java.util.List;
|
|||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary;
|
||||
import ghidra.pcode.emu.PcodeMachine.SwiMode;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.lang.Register;
|
||||
|
@ -286,6 +285,13 @@ public interface PcodeThread<T> {
|
|||
*/
|
||||
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)
|
||||
*
|
||||
|
|
|
@ -87,6 +87,15 @@ public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
|
|||
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
|
||||
public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) {
|
||||
if (isThreadLocalSpace(space)) {
|
||||
|
@ -95,6 +104,14 @@ public class ThreadPcodeExecutorState<T> implements PcodeExecutorState<T> {
|
|||
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
|
||||
public Map<Register, T> getRegisterValues() {
|
||||
Map<Register, T> result = new HashMap<>();
|
||||
|
|
|
@ -65,7 +65,9 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
|
|||
*
|
||||
* @return the copy
|
||||
*/
|
||||
public abstract AbstractSpaceMap<S> fork();
|
||||
public AbstractSpaceMap<S> fork() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep copy the given space
|
||||
|
@ -73,7 +75,9 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece<A, T, S>
|
|||
* @param s the space
|
||||
* @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
|
||||
|
|
|
@ -69,11 +69,21 @@ public class DefaultPcodeExecutorState<T> implements PcodeExecutorState<T> {
|
|||
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
|
||||
public void setVar(AddressSpace space, T offset, int size, boolean quantize, T 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
|
||||
public Map<Register, T> getRegisterValues() {
|
||||
return piece.getRegisterValues();
|
||||
|
|
|
@ -46,7 +46,7 @@ public class PcodeExecutor<T> {
|
|||
protected final PcodeExecutorState<T> state;
|
||||
protected final Reason reason;
|
||||
protected final Register pc;
|
||||
protected final int pointerSize;
|
||||
protected final int pcSize;
|
||||
|
||||
/**
|
||||
* Construct an executor with the given bindings
|
||||
|
@ -64,7 +64,7 @@ public class PcodeExecutor<T> {
|
|||
this.reason = reason;
|
||||
|
||||
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());
|
||||
}
|
||||
else {
|
||||
branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame);
|
||||
branchToOffset(arithmetic.fromConst(target.getOffset(), pcSize), frame);
|
||||
branchToAddress(target);
|
||||
}
|
||||
}
|
||||
|
@ -510,7 +510,7 @@ public class PcodeExecutor<T> {
|
|||
*/
|
||||
public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary<T> library) {
|
||||
Address target = op.getInput(0).getAddress();
|
||||
branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame);
|
||||
branchToOffset(arithmetic.fromConst(target.getOffset(), pcSize), frame);
|
||||
branchToAddress(target);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.pcode.exec;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
|
@ -44,6 +45,8 @@ public interface PcodeExecutorStatePiece<A, T> {
|
|||
* Reasons for reading state
|
||||
*/
|
||||
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 */
|
||||
EXECUTE,
|
||||
/** The value is being inspected */
|
||||
|
@ -59,6 +62,9 @@ public interface PcodeExecutorStatePiece<A, T> {
|
|||
*/
|
||||
default void checkRange(AddressSpace space, long offset, int size) {
|
||||
// TODO: Perhaps get/setVar should just take an AddressRange?
|
||||
if (space.isConstantSpace()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
new AddressRangeImpl(space.getAddress(offset), size);
|
||||
}
|
||||
|
@ -93,7 +99,9 @@ public interface PcodeExecutorStatePiece<A, T> {
|
|||
*
|
||||
* @return the copy
|
||||
*/
|
||||
PcodeExecutorStatePiece<A, T> fork();
|
||||
default PcodeExecutorStatePiece<A, T> fork() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of a register variable
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (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
|
||||
|
||||
public abstract class BreakTable {
|
||||
public interface BreakTable {
|
||||
|
||||
/// \brief Associate a particular emulator with breakpoints in this table
|
||||
///
|
||||
|
|
|
@ -29,7 +29,7 @@ import ghidra.program.model.address.Address;
|
|||
///
|
||||
/// Breakpoints are stored in map containers, and the core BreakTable methods
|
||||
/// are implemented to search in these containers
|
||||
public class BreakTableCallBack extends BreakTable {
|
||||
public class BreakTableCallBack implements BreakTable {
|
||||
|
||||
public static final String DEFAULT_NAME = "*";
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -16,47 +16,13 @@
|
|||
package ghidra.pcode.memstate;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import generic.stl.VectorSTL;
|
||||
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.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
public interface 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.
|
||||
* @param bank is a pointer to the MemoryBank to be registered
|
||||
*/
|
||||
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);
|
||||
}
|
||||
void setMemoryBank(MemoryBank bank);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the MemoryBank or null if no bank is associated with spc.
|
||||
*/
|
||||
public final MemoryBank getMemoryBank(AddressSpace spc) {
|
||||
int index = spc.getUnique();
|
||||
if (index >= memspace.size())
|
||||
return null;
|
||||
return memspace.get(index);
|
||||
}
|
||||
MemoryBank getMemoryBank(AddressSpace spc);
|
||||
|
||||
/**
|
||||
* 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 cval the value to write into the varnode location
|
||||
*/
|
||||
public final void setValue(Varnode vn, long cval) {
|
||||
Address addr = vn.getAddress();
|
||||
setValue(addr.getAddressSpace(), addr.getOffset(), vn.getSize(), cval);
|
||||
}
|
||||
void setValue(Varnode vn, long cval);
|
||||
|
||||
/**
|
||||
* 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 cval the value to write into the register location
|
||||
*/
|
||||
public final void setValue(Register reg, long cval) {
|
||||
Address addr = reg.getAddress();
|
||||
setValue(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize(), cval);
|
||||
}
|
||||
void setValue(Register reg, long cval);
|
||||
|
||||
/**
|
||||
* 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 cval is the value to write to the register
|
||||
*/
|
||||
public final 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);
|
||||
}
|
||||
void setValue(String nm, long cval);
|
||||
|
||||
/**
|
||||
* 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 cval is the value to be written
|
||||
*/
|
||||
public final void setValue(AddressSpace spc, long off, int size, long cval) {
|
||||
setChunk(Utils.longToBytes(cval, size, language.isBigEndian()), spc, off, size);
|
||||
}
|
||||
void setValue(AddressSpace spc, long off, int size, long cval);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the value read from the varnode location
|
||||
*/
|
||||
public final long getValue(Varnode vn) {
|
||||
Address addr = vn.getAddress();
|
||||
return getValue(addr.getAddressSpace(), addr.getOffset(), vn.getSize());
|
||||
}
|
||||
long getValue(Varnode vn);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the value read from the register location
|
||||
*/
|
||||
public final long getValue(Register reg) {
|
||||
Address addr = reg.getAddress();
|
||||
return getValue(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize());
|
||||
}
|
||||
long getValue(Register reg);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the value associated with that register
|
||||
*/
|
||||
public final 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());
|
||||
}
|
||||
long getValue(String nm);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the queried value
|
||||
*/
|
||||
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());
|
||||
}
|
||||
long getValue(AddressSpace spc, long off, int size);
|
||||
|
||||
/**
|
||||
* 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 cval the value to write into the varnode location
|
||||
*/
|
||||
public final void setValue(Varnode vn, BigInteger cval) {
|
||||
Address addr = vn.getAddress();
|
||||
setValue(addr.getAddressSpace(), addr.getOffset(), vn.getSize(), cval);
|
||||
}
|
||||
void setValue(Varnode vn, BigInteger cval);
|
||||
|
||||
/**
|
||||
* 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 cval the value to write into the register location
|
||||
*/
|
||||
public final void setValue(Register reg, BigInteger cval) {
|
||||
Address addr = reg.getAddress();
|
||||
setValue(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize(), cval);
|
||||
}
|
||||
void setValue(Register reg, BigInteger cval);
|
||||
|
||||
/**
|
||||
* 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 cval is the value to write to the register
|
||||
*/
|
||||
public final 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);
|
||||
}
|
||||
void setValue(String nm, BigInteger cval);
|
||||
|
||||
/**
|
||||
* 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 cval is the value to be written
|
||||
*/
|
||||
public final void setValue(AddressSpace spc, long off, int size, BigInteger cval) {
|
||||
setChunk(Utils.bigIntegerToBytes(cval, size, language.isBigEndian()), spc, off, size);
|
||||
}
|
||||
void setValue(AddressSpace spc, long off, int size, BigInteger cval);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the unsigned value read from the varnode location
|
||||
*/
|
||||
public final BigInteger getBigInteger(Varnode vn, boolean signed) {
|
||||
Address addr = vn.getAddress();
|
||||
return getBigInteger(addr.getAddressSpace(), addr.getOffset(), vn.getSize(), signed);
|
||||
}
|
||||
BigInteger getBigInteger(Varnode vn, boolean signed);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the unsigned value read from the register location
|
||||
*/
|
||||
public final BigInteger getBigInteger(Register reg) {
|
||||
Address addr = reg.getAddress();
|
||||
return getBigInteger(addr.getAddressSpace(), addr.getOffset(), reg.getMinimumByteSize(),
|
||||
false);
|
||||
}
|
||||
BigInteger getBigInteger(Register reg);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the unsigned value associated with that register
|
||||
*/
|
||||
public final 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);
|
||||
}
|
||||
BigInteger getBigInteger(String nm);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return the queried unsigned value
|
||||
*/
|
||||
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);
|
||||
}
|
||||
BigInteger getBigInteger(AddressSpace spc, long off, int size, boolean signed);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* handler generated error
|
||||
*/
|
||||
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);
|
||||
}
|
||||
int getChunk(byte[] res, AddressSpace spc, long off, int size,
|
||||
boolean stopOnUnintialized);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws LowlevelError if spc has not been mapped within this MemoryState
|
||||
*/
|
||||
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);
|
||||
}
|
||||
void setChunk(byte[] val, AddressSpace spc, long off, int size);
|
||||
|
||||
/**
|
||||
* 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 size the number of bytes to write
|
||||
*/
|
||||
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);
|
||||
}
|
||||
void setInitialized(boolean initialized, AddressSpace spc, long off, int size);
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue