diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java index f671683259..5ed403b1f3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java @@ -155,6 +155,11 @@ class TestThread implements PcodeThread { public void setSuspended(boolean suspended) { } + @Override + public boolean isSuspended() { + return false; + } + @Override public PcodeUseropLibrary getUseropLibrary() { return null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/processors/support/EmulatorTestRunner.java b/Ghidra/Features/Base/src/main/java/ghidra/test/processors/support/EmulatorTestRunner.java index 152e735b0c..ba1abd885a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/processors/support/EmulatorTestRunner.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/processors/support/EmulatorTestRunner.java @@ -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,8 +174,9 @@ 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. + * so that the specified memory may be dumped to the log during trace execution mode. * @param dumpAddr memory address which should be dumped * @param dumpSize number elements which should be dumped * @param elementSize size of each element in bytes (be reasonable!) @@ -190,8 +196,9 @@ 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. + * so that the specified memory may be dumped to the log during trace execution mode. * @param dumpAddrReg register containing the memory address offset which should be dumped * @param relativeOffset dump register relative offset * @param dumpAddrSpace address space to which memory offset should be applied @@ -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,7 +250,8 @@ public class EmulatorTestRunner { /** * Execute test group without instruction stepping/tracing - * @param timeLimitMS + * + * @param timeLimitMS * @param monitor * @return * @throws CancelledException @@ -695,8 +703,9 @@ public class EmulatorTestRunner { @Override Address getDumpAddress() { RegisterValue regVal = getRegisterValue(dumpAddrReg); - return dumpAddrSpace.getAddress(regVal.getUnsignedValue().longValue()).add( - relativeOffset); + return dumpAddrSpace.getAddress(regVal.getUnsignedValue().longValue()) + .add( + relativeOffset); } @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java new file mode 100644 index 0000000000..25cc825f2f --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java @@ -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} + * + *

+ * 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 createSharedState() { + return new AdaptedBytesPcodeExecutorState(language, + new StateBacking(faultHandler, loadImage)); + } + + @Override + protected PcodeExecutorState createLocalState(PcodeThread 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 createUseropLibrary() { + return new AdaptedPcodeUseropLibrary(); + } + } + + @Transitional + public class AdaptedPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary { + @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 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 { + private final StateBacking backing; + + public AdaptedBytesPcodeExecutorStatePiece(Language language, StateBacking backing) { + super(language); + this.backing = backing; + } + + @Override + protected AbstractSpaceMap newSpaceMap() { + return new SimpleSpaceMap<>() { + @Override + protected AdaptedBytesPcodeExecutorStateSpace newSpace(AddressSpace space) { + return new AdaptedBytesPcodeExecutorStateSpace(language, space, backing); + } + }; + } + } + + static class AdaptedBytesPcodeExecutorStateSpace + extends BytesPcodeExecutorStateSpace { + 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 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() { + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedMemoryState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedMemoryState.java new file mode 100644 index 0000000000..55381d2e9a --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedMemoryState.java @@ -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}. + * + *

+ * 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: + * + *

    + *
  1. An equivalent state modification system is developed for the {@link PcodeEmulator} system, + * and each {@link EmulateInstructionStateModifier} is ported to it.
  2. + *
  3. The {@link AdaptedEmulator} class is removed.
  4. + *
+ * + *

+ * Guidance for the use of this class is the same as {@link AdaptedEmulator}. + * + * @param 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 extends AbstractMemoryState { + private final PcodeExecutorState state; + private final PcodeArithmetic arithmetic; + private final Reason reason; + + public AdaptedMemoryState(PcodeExecutorState 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 + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/DefaultEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/DefaultEmulator.java new file mode 100644 index 0000000000..1cf0a21966 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/DefaultEmulator.java @@ -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}. + * + *

+ * 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 keys = mstate.getKeys(); + for (String key : keys) { + List vals = mstate.getVals(key); + List 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 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 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 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 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); + } + +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/Emulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/Emulator.java index 70212d57e7..2068304655 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/Emulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/Emulator.java @@ -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 + * + *

+ * 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 word offset of the instruction to execute next. */ - private void initRegisters(boolean restore) { - DataConverter conv = DataConverter.getInstance(language.isBigEndian()); - Set keys = mstate.getKeys(); - for (String key : keys) { - List vals = mstate.getVals(key); - List 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 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 + * + *

+ * 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 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 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 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 + * + *

+ * 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. + * + *

+ * 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(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/EmulatorHelper.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/EmulatorHelper.java index 6aff080129..55edd01da8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/EmulatorHelper.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/EmulatorHelper.java @@ -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}. + * + *

+ * 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,8 +259,9 @@ 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(), - length, false); + int len = emulator.getMemState() + .getChunk(res, addr.getAddressSpace(), addr.getOffset(), + length, false); if (len == 0) { Msg.error(this, "Failed to read memory from Emulator at: " + addr); return null; @@ -256,8 +274,9 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration } public void writeMemory(Address addr, byte[] bytes) { - emulator.getMemState().setChunk(bytes, addr.getAddressSpace(), addr.getOffset(), - bytes.length); + emulator.getMemState() + .setChunk(bytes, addr.getAddressSpace(), addr.getOffset(), + bytes.length); } public void writeMemoryValue(Address addr, int size, long value) { @@ -265,7 +284,8 @@ public class EmulatorHelper implements MemoryFaultHandler, EmulatorConfiguration } /** - * Read a stack value from the memory state. + * Read a stack value from the memory state. + * * @param relativeOffset offset relative to current stack pointer * @param 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 start start address of the block * @param length the size of the block - * @param overlay if true, the block will be created as an OVERLAY which means that a new - * overlay address space will be created and the block will have a starting address at the same - * offset as the given start address parameter, but in the new address space. + * @param overlay if true, the block will be created as an OVERLAY which means that a new + * overlay address space will be created and the block will have a starting address + * at the same offset as the given start address parameter, but in the new address + * space. * @param monitor * @return new memory block * @throws LockException if exclusive lock not in place (see haveLock()) - * @throws MemoryConflictException if the new block overlaps with a - * previous block - * @throws AddressOverflowException if the start is beyond the - * address space + * @throws 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,8 +657,9 @@ 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, - monitor, overlay); + block = program.getMemory() + .createInitializedBlock(name, start, memStateStream, length, + monitor, overlay); success = true; } finally { @@ -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) { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/FilteredMemoryState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/FilteredMemoryState.java index 8629ec11fb..cdbd538ca9 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/FilteredMemoryState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/FilteredMemoryState.java @@ -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 diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/MemoryAccessFilter.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/MemoryAccessFilter.java index 5bf2dbb179..bbd6d931cd 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/MemoryAccessFilter.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/MemoryAccessFilter.java @@ -17,33 +17,59 @@ package ghidra.app.emulator; import ghidra.program.model.address.AddressSpace; +/** + * A means of intercepting and/or modifying the emulator's memory access. + * + *

+ * 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; private MemoryAccessFilter nextFilter; - + protected Emulator emu; - + private boolean filterOnExecutionOnly = true; - - final void filterRead(AddressSpace spc, long off, int size, byte [] values) { - if (filterOnExecutionOnly() && !emu.isExecuting()) return; // do not filter idle queries + + final void filterRead(AddressSpace spc, long off, int size, byte[] values) { + if (filterOnExecutionOnly() && !emu.isExecuting()) + return; // do not filter idle queries processRead(spc, off, size, values); if (nextFilter != null) { nextFilter.filterRead(spc, off, size, values); } } - + + /** + * 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 + final void filterWrite(AddressSpace spc, long off, int size, byte[] values) { + if (filterOnExecutionOnly() && !emu.isExecuting()) + return; // do not filter idle queries processWrite(spc, off, size, values); if (nextFilter != null) { nextFilter.filterWrite(spc, off, size, values); } } + /** + * Invoked after 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) { @@ -53,10 +79,12 @@ public abstract class MemoryAccessFilter { nextFilter.prevFilter = this; } } - + /** * Dispose this filter which will cause it to be removed from the memory state. - * If overriden, be sure to invoke super.dispose(). + * + *

+ * If overriden, be sure to invoke {@code super.dispose()}. */ public void dispose() { if (nextFilter != null) { @@ -77,7 +105,7 @@ public abstract class MemoryAccessFilter { public void setFilterOnExecutionOnly(boolean filterOnExecutionOnly) { this.filterOnExecutionOnly = filterOnExecutionOnly; } - + // public void compare(String id); // public void clear(); // public void updateFlags(String id); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/state/RegisterState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/state/RegisterState.java index c3c832b6cf..dce41b3ca5 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/state/RegisterState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/state/RegisterState.java @@ -22,8 +22,22 @@ public interface RegisterState { public Set 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 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 isInitialized(String key); public void setVals(String key, byte[] vals, boolean setInitiailized); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java index b01c9922c8..42b9e37519 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java @@ -266,6 +266,11 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { 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 implements PcodeMachine { } /** - * 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) { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java index c4b28e6dce..f544127f41 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java @@ -320,15 +320,19 @@ public class DefaultPcodeThread implements PcodeThread { } 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 implements PcodeThread { @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 implements PcodeThread { executor.suspended = suspended; } + @Override + public boolean isSuspended() { + return executor.suspended; + } + @Override public SleighLanguage getLanguage() { return language; @@ -677,8 +686,8 @@ public class DefaultPcodeThread implements PcodeThread { } /** - * 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(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java index e08771f78e..29965f9dfc 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java @@ -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 extends DefaultPcodeThread { } } - /** - * Glue for incorporating state modifiers - * - *

- * 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 extends DefaultPcodeThread { 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 extends DefaultPcodeThread { * 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 extends DefaultPcodeThread { } } - /** - * 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()); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeMachine.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeMachine.java index dc020c7b35..8f4d5a2e4e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeMachine.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeMachine.java @@ -203,6 +203,13 @@ public interface PcodeMachine { */ 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 * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeThread.java index c858148988..6557c6c1fb 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeThread.java @@ -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 { */ 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) * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java index 3499e957de..75b6be335c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java @@ -87,6 +87,15 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { 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 implements PcodeExecutorState { 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 getRegisterValues() { Map result = new HashMap<>(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java index 9dda9b32ac..6a76f9f7be 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java @@ -65,7 +65,9 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * * @return the copy */ - public abstract AbstractSpaceMap fork(); + public AbstractSpaceMap fork() { + throw new UnsupportedOperationException(); + } /** * Deep copy the given space @@ -73,7 +75,9 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @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 diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java index 3c1cf22164..7c1a7cd4cd 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java @@ -69,11 +69,21 @@ public class DefaultPcodeExecutorState implements PcodeExecutorState { 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 getRegisterValues() { return piece.getRegisterValues(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutor.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutor.java index 24ae347595..4ecebbb51e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutor.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutor.java @@ -46,7 +46,7 @@ public class PcodeExecutor { protected final PcodeExecutorState 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 { this.reason = reason; this.pc = language.getProgramCounter(); - this.pointerSize = language.getDefaultSpace().getPointerSize(); + this.pcSize = pc.getNumBytes(); } /** @@ -431,7 +431,7 @@ public class PcodeExecutor { 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 { */ public void executeCall(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary library) { Address target = op.getInput(0).getAddress(); - branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame); + branchToOffset(arithmetic.fromConst(target.getOffset(), pcSize), frame); branchToAddress(target); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java index 4f62aec502..510326d4b8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java @@ -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 { * 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 { */ 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 { * * @return the copy */ - PcodeExecutorStatePiece fork(); + default PcodeExecutorStatePiece fork() { + throw new UnsupportedOperationException(); + } /** * Set the value of a register variable diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/BreakTable.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/BreakTable.java index 3724cbfc9e..ccd99031cc 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/BreakTable.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/BreakTable.java @@ -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 /// diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/BreakTableCallBack.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/BreakTableCallBack.java index 06f597f7ae..918dcb1e8f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/BreakTableCallBack.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/emulate/BreakTableCallBack.java @@ -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 = "*"; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/AbstractMemoryState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/AbstractMemoryState.java new file mode 100644 index 0000000000..cd34f29d71 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/AbstractMemoryState.java @@ -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); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/DefaultMemoryState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/DefaultMemoryState.java new file mode 100644 index 0000000000..77dd280da3 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/DefaultMemoryState.java @@ -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 memspace = new VectorSTL(); + + /** + * 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); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/MemoryState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/MemoryState.java index 48bfadd552..6e8d97a643 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/MemoryState.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/memstate/MemoryState.java @@ -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 memspace = new VectorSTL(); - Map regVarnodeCache = new HashMap(); - - /** - * 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); }