diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerEmulationService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerEmulationService.java index f148775514..b997b13a35 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerEmulationService.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/DebuggerEmulationService.java @@ -19,9 +19,10 @@ import java.io.IOException; import java.util.Collection; import java.util.concurrent.CompletableFuture; -import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; +import ghidra.debug.api.emulation.EmulatorFactory; import ghidra.framework.plugintool.ServiceInfo; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; @@ -68,11 +69,12 @@ public interface DebuggerEmulationService { * * @param trace the trace the emulator is bound to * @param emulator the emulator itself + * @param writer the callbacks with delayed writes for trace/UI integration * @param version the cache version. See {@link #isValid()}. */ - record CachedEmulator(Trace trace, DebuggerPcodeMachine emulator, long version) { - public CachedEmulator(Trace trace, DebuggerPcodeMachine emulator) { - this(trace, emulator, trace.getEmulatorCacheVersion()); + record CachedEmulator(Trace trace, PcodeMachine emulator, Writer writer, long version) { + public CachedEmulator(Trace trace, PcodeMachine emulator, Writer writer) { + this(trace, emulator, writer, trace.getEmulatorCacheVersion()); } /** @@ -96,7 +98,7 @@ public interface DebuggerEmulationService { * @return the emulator */ @Override - public DebuggerPcodeMachine emulator() { + public PcodeMachine emulator() { return emulator; } @@ -136,7 +138,7 @@ public interface DebuggerEmulationService { * * @return the collection of factories */ - Collection getEmulatorFactories(); + Collection getEmulatorFactories(); /** * Set the current emulator factory @@ -153,14 +155,14 @@ public interface DebuggerEmulationService { * * @param factory the chosen factory */ - void setEmulatorFactory(DebuggerPcodeEmulatorFactory factory); + void setEmulatorFactory(EmulatorFactory factory); /** * Get the current emulator factory * * @return the factory */ - DebuggerPcodeEmulatorFactory getEmulatorFactory(); + EmulatorFactory getEmulatorFactory(); /** * Load the given program into a trace suitable for emulation in the UI, starting at the given @@ -290,7 +292,7 @@ public interface DebuggerEmulationService { * @param time the time coordinates, including initial snap, steps, and p-code steps * @return the copied p-code frame */ - DebuggerPcodeMachine getCachedEmulator(Trace trace, TraceSchedule time); + PcodeMachine getCachedEmulator(Trace trace, TraceSchedule time); /** * Get the emulators which are current executing diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeMachine.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeMachine.java deleted file mode 100644 index 6fdc418491..0000000000 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeMachine.java +++ /dev/null @@ -1,26 +0,0 @@ -/* ### - * 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.debug.api.emulation; - -import ghidra.pcode.exec.trace.TracePcodeMachine; - -/** - * A Debugger-integrated emulator (or p-code machine) - * - * @param the type of values in the machine's memory and registers - */ -public interface DebuggerPcodeMachine extends TracePcodeMachine { -} diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/EmulatorFactory.java similarity index 57% rename from Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeEmulatorFactory.java rename to Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/EmulatorFactory.java index 6a825ffee6..6dbac40fcd 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/DebuggerPcodeEmulatorFactory.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/EmulatorFactory.java @@ -15,15 +15,14 @@ */ package ghidra.debug.api.emulation; -import ghidra.debug.api.target.Target; -import ghidra.framework.plugintool.PluginTool; -import ghidra.trace.model.guest.TracePlatform; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.util.classfinder.ExtensionPoint; /** * A factory for configuring and creating a Debugger-integrated emulator */ -public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint { +public interface EmulatorFactory extends ExtensionPoint { // TODO: Config options, use ModelFactory as a model /** @@ -33,23 +32,12 @@ public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint { */ String getTitle(); - /** - * Create the emulator - * - * @param tool the tool creating the emulator - * @param platform the user's current trace platform from which the emulator should load state - * @param snap the user's current snap from which the emulator should load state - * @param target if applicable, the live target - * @return the emulator - */ - DebuggerPcodeMachine create(PluginTool tool, TracePlatform platform, long snap, - Target target); - /** * Create the emulator * * @param access the trace-and-debugger access shim - * @return the emulator + * @param writer the Debugger's emulation callbacks for UI integration + * @return the emulator with callbacks installed */ - DebuggerPcodeMachine create(PcodeDebuggerAccess access); + PcodeMachine create(PcodeDebuggerAccess access, Writer writer); } diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/PcodeDebuggerMemoryAccess.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/PcodeDebuggerMemoryAccess.java index 15a46021e4..e7b16ec375 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/PcodeDebuggerMemoryAccess.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/PcodeDebuggerMemoryAccess.java @@ -4,9 +4,9 @@ * 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. @@ -17,7 +17,7 @@ package ghidra.debug.api.emulation; import java.util.concurrent.CompletableFuture; -import ghidra.generic.util.datastruct.SemisparseByteArray; +import ghidra.pcode.exec.PcodeExecutorStatePiece; import ghidra.pcode.exec.trace.data.PcodeTraceMemoryAccess; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; @@ -53,11 +53,12 @@ public interface PcodeDebuggerMemoryAccess * image was relocated with fixups, reads at those fixups which fall through to static images * will be incorrect, and may lead to undefined behavior in the emulated program. * - * @param bytes the destination byte store + * @param piece the destination state piece * @param unknown the address set to read - * @return true if any bytes were read, false if there was no effect + * @return the parts of {@code unknown} that still haven't been read */ - boolean readFromStaticImages(SemisparseByteArray bytes, AddressSetView unknown); + AddressSetView readFromStaticImages(PcodeExecutorStatePiece piece, + AddressSetView unknown); /** * Instruct the associated recorder to write target memory diff --git a/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest b/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest index 5c3f89f08d..bfe4c6157a 100644 --- a/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest +++ b/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest @@ -2,9 +2,9 @@ AutoMapSpec AutoReadMemorySpecFactory DebuggerMappingOpinion DebuggerModelFactory -DebuggerPcodeEmulatorFactory DebuggerPlatformOpinion DebuggerProgramLaunchOpinion DebuggerRegisterColumnFactory DisassemblyInject +EmulatorFactory LocationTrackingSpecFactory diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java index 6be2c952c7..6db944c954 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java @@ -37,7 +37,6 @@ import ghidra.app.services.DebuggerEmulationService.EmulatorStateListener; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.async.AsyncUtils; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; import ghidra.debug.api.model.DebuggerObjectActionContext; import ghidra.debug.api.target.ActionName; import ghidra.debug.api.target.Target; @@ -47,6 +46,7 @@ import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.pcode.emu.PcodeMachine; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.trace.model.*; @@ -382,7 +382,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin return true; } - private DebuggerPcodeMachine getBusyEmulator() { + private PcodeMachine getBusyEmulator() { /** * NOTE: Could search for current trace, but task manager will only allow one to actually * run at a time. Best not let the user queue a bunch up if another trace's emulator is @@ -430,7 +430,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin if (emulationService == null) { return; } - DebuggerPcodeMachine emu = getBusyEmulator(); + PcodeMachine emu = getBusyEmulator(); emu.setSuspended(true); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java index 59170505f9..c43953ccee 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java @@ -44,11 +44,11 @@ import ghidra.app.util.pcode.AbstractAppender; import ghidra.app.util.pcode.AbstractPcodeFormatter; import ghidra.async.SwingExecutorService; import ghidra.base.widgets.table.DataTypeTableCellEditor; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; +import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.*; import ghidra.program.model.address.AddressSpace; @@ -853,7 +853,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { populateSingleton(EnumPcodeRow.DECODE); return; } - DebuggerPcodeMachine emu = emulationService.getCachedEmulator(trace, time); + PcodeMachine emu = emulationService.getCachedEmulator(trace, time); if (emu != null) { clear(); doLoadPcodeFrameFromEmulator(emu); @@ -868,7 +868,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { }, SwingExecutorService.LATER); } - protected void doLoadPcodeFrameFromEmulator(DebuggerPcodeMachine emu) { + protected void doLoadPcodeFrameFromEmulator(PcodeMachine emu) { PcodeThread thread = emu.getThread(current.getThread().getPath(), false); if (thread == null) { /** diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractDebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractDebuggerPcodeEmulatorFactory.java deleted file mode 100644 index 577cb66c48..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractDebuggerPcodeEmulatorFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -/* ### - * 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.plugin.core.debug.service.emulation; - -import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; -import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; -import ghidra.debug.api.target.Target; -import ghidra.framework.plugintool.PluginTool; -import ghidra.trace.model.guest.TracePlatform; - -public abstract class AbstractDebuggerPcodeEmulatorFactory implements DebuggerPcodeEmulatorFactory { - @Override - public DebuggerPcodeMachine create(PluginTool tool, TracePlatform platform, long snap, - Target target) { - return create(new DefaultPcodeDebuggerAccess(tool, target, platform, snap)); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java deleted file mode 100644 index 42975795ec..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java +++ /dev/null @@ -1,119 +0,0 @@ -/* ### - * 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.plugin.core.debug.service.emulation; - -import java.util.Map; -import java.util.concurrent.*; - -import generic.ULongSpan.ULongSpanSet; -import ghidra.debug.api.emulation.PcodeDebuggerDataAccess; -import ghidra.generic.util.datastruct.SemisparseByteArray; -import ghidra.pcode.exec.AccessPcodeExecutionException; -import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Language; -import ghidra.trace.model.memory.TraceMemoryState; - -/** - * An abstract executor state piece that knows to read live state if applicable - * - *

- * This requires a memory-access shim for the debugger. It will check if the shim is associated with - * a live session. If so, it will direct the recorder to capture the desired state, if they're not - * already {@link TraceMemoryState#KNOWN}. When such a target comments is required, the state will - * wait up to 1 second for it to complete (see - * {@link AbstractRWTargetCachedSpace#waitTimeout(CompletableFuture)}). - */ -public abstract class AbstractRWTargetPcodeExecutorStatePiece - extends BytesTracePcodeExecutorStatePiece { - - abstract class AbstractRWTargetCachedSpace extends CachedSpace { - - public AbstractRWTargetCachedSpace(Language language, AddressSpace space, - PcodeDebuggerDataAccess backing) { - super(language, space, backing); - } - - protected AbstractRWTargetCachedSpace(Language language, AddressSpace space, - PcodeTraceDataAccess backing, SemisparseByteArray bytes, AddressSet written) { - super(language, space, backing, bytes, written); - } - - protected abstract ULongSpanSet readUninitializedFromTarget(ULongSpanSet uninitialized); - - @Override - protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) { - uninitialized = readUninitializedFromTarget(uninitialized); - if (uninitialized.isEmpty()) { - return uninitialized; - } - - return super.readUninitializedFromBacking(uninitialized); - // TODO: What to flush when bytes in the trace change? - } - - protected T waitTimeout(CompletableFuture future) { - try { - return future.get(1, TimeUnit.SECONDS); - } - catch (TimeoutException e) { - throw new AccessPcodeExecutionException("Timed out reading or writing target", e); - } - catch (InterruptedException | ExecutionException e) { - throw new AccessPcodeExecutionException("Error reading or writing target", e); - } - } - } - - protected final PcodeDebuggerDataAccess data; - - /** - * Construct a piece - * - * @param data the trace-data access shim - */ - public AbstractRWTargetPcodeExecutorStatePiece(PcodeDebuggerDataAccess data) { - super(data); - this.data = data; - } - - /** - * A partially implemented space map which retrieves "backing" objects from the trace's memory - * and register spaces. - */ - protected abstract class TargetBackedSpaceMap - extends CacheingSpaceMap { - - public TargetBackedSpaceMap() { - super(); - } - - protected TargetBackedSpaceMap(Map spaces) { - super(spaces); - } - - @Override - public CachedSpace fork(CachedSpace s) { - return s.fork(); - } - - @Override - protected PcodeDebuggerDataAccess getBacking(AddressSpace space) { - return data; - } - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java deleted file mode 100644 index 782b9341b2..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java +++ /dev/null @@ -1,64 +0,0 @@ -/* ### - * 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.plugin.core.debug.service.emulation; - -import ghidra.debug.api.emulation.DebuggerPcodeMachine; -import ghidra.debug.api.emulation.PcodeDebuggerAccess; -import ghidra.pcode.emu.PcodeEmulator; -import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.exec.trace.BytesTracePcodeEmulator; -import ghidra.pcode.exec.trace.TracePcodeExecutorState; - -/** - * A trace emulator that knows how to read target memory when necessary - * - *

- * This is the default emulator used by the Debugger UI to perform interpolation and extrapolation. - * For standalone scripting, consider using {@link BytesTracePcodeEmulator} or {@link PcodeEmulator} - * instead. The former readily reads and records its state to traces, while the latter is the - * simplest use case. See scripts ending in {@code EmuExampleScript} for example uses. - * - *

- * This emulator must always be run in its own thread, or at least a thread that can never lock the - * UI. It blocks on target reads so that execution can proceed synchronously. Probably the most - * suitable option is to use a background task. - */ -public class BytesDebuggerPcodeEmulator extends BytesTracePcodeEmulator - implements DebuggerPcodeMachine { - - protected final PcodeDebuggerAccess access; - - /** - * Create the emulator - * - * @param access the trace-and-debugger access shim - */ - public BytesDebuggerPcodeEmulator(PcodeDebuggerAccess access) { - super(access); - this.access = access; - } - - @Override - public TracePcodeExecutorState createSharedState() { - return new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RO); - } - - @Override - public TracePcodeExecutorState createLocalState(PcodeThread emuThread) { - return new RWTargetRegistersPcodeExecutorState(access.getDataForLocalState(emuThread, 0), - Mode.RO); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationIntegration.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationIntegration.java new file mode 100644 index 0000000000..ecc2da1d92 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationIntegration.java @@ -0,0 +1,196 @@ +/* ### + * 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.plugin.core.debug.service.emulation; + +import java.util.concurrent.*; + +import ghidra.debug.api.emulation.*; +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.trace.TraceEmulationIntegration; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.*; +import ghidra.pcode.exec.trace.data.*; +import ghidra.program.model.address.*; +import ghidra.trace.model.thread.TraceThread; + +/** + * A collection of static methods for integrating an emulator with a trace and target. + */ +public enum DebuggerEmulationIntegration { + ; + + private static Writer createDelayedWrite(PcodeDebuggerAccess acc, Mode mode) { + Writer writer = new TraceWriter(acc); + writer.putHandler(new TargetBytesPieceHandler(mode)); + return writer; + } + + /** + * Create a writer (callbacks) that lazily loads data from the given access shim. + * + *

+ * Reads may be redirected to the target. Writes are logged, but never sent to the + * target. This is used for forking emulation from a chosen snapshot and saving the results into + * (usually scratch) snapshots. This is the pattern used by the UI when emulation schedules are + * requested. + * + * @see TraceEmulationIntegration#bytesDelayedWrite(PcodeTraceAccess) + * @param from the access shim for lazy loads + * @return the writer + */ + public static Writer bytesDelayedWriteTrace(PcodeDebuggerAccess from) { + return createDelayedWrite(from, Mode.RO); + } + + /** + * Create a writer (callbacks) that lazily loads data and immediately writes changes to the + * given access shim. + * + *

+ * Reads may be redirected to the target. If redirected, writes are immediately sent to the + * target and presumably stored into the trace at the same snapshot as state is sourced. + * + * @see TraceEmulationIntegration#bytesImmediateWrite(PcodeTraceAccess) + * @param access the access shim for loads and stores + * @return the writer + */ + public static Writer bytesImmediateWriteTarget(PcodeDebuggerAccess access) { + return createDelayedWrite(access, Mode.RW); + } + + /** + * Create state callbacks that lazily load data and immediately write changes to the given + * access shim. + * + *

+ * Reads may be redirected to the target. If redirected, writes are immediately sent to the + * target and presumably stored into the trace at the same snapshot as state is sourced. + * + *

+ * Use this instead of {@link #bytesImmediateWriteTarget(PcodeDebuggerAccess)} when interfacing + * directly with a {@link PcodeExecutorState} vice a {@link PcodeEmulator}. + * + * @see TraceEmulationIntegration#bytesImmediateWrite(PcodeTraceAccess, TraceThread, int) + * @param access the access shim for loads and stores + * @param thread the trace thread for register accesses + * @param frame the frame for register accesses, usually 0 + * @return the callbacks + */ + public static PcodeStateCallbacks bytesImmediateWriteTarget(PcodeDebuggerAccess access, + TraceThread thread, int frame) { + PcodeDebuggerRegistersAccess regAcc = access.getDataForLocalState(thread, frame); + Writer writer = new TraceWriter(access) { + @Override + protected PcodeTraceRegistersAccess getRegAccess(PcodeThread ignored) { + return regAcc; + } + }; + writer.putHandler(new TargetBytesPieceHandler(Mode.RW)); + return writer.wrapFor(null); + } + + protected static T waitTimeout(CompletableFuture future) { + try { + return future.get(1, TimeUnit.SECONDS); + } + catch (TimeoutException e) { + throw new AccessPcodeExecutionException("Timed out reading or writing target", e); + } + catch (InterruptedException | ExecutionException e) { + throw new AccessPcodeExecutionException("Error reading or writing target", e); + } + } + + /** + * An extension/replacement of the {@link BytesPieceHandler} that may redirect reads and writes + * to/from the target. + * + * @implNote Because piece handlers are keyed by (address-domain, value-domain), adding this to + * a writer will replace the default handler. + */ + public static class TargetBytesPieceHandler extends BytesPieceHandler { + protected final Mode mode; + + public TargetBytesPieceHandler(Mode mode) { + this.mode = mode; + } + + @Override + public AddressSetView readUninitialized(PcodeTraceDataAccess acc, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView set) { + AddressSetView unknown = acc.intersectUnknown(set); + if (unknown.isEmpty()) { + return super.readUninitialized(acc, thread, piece, set); + } + if (acc instanceof PcodeDebuggerRegistersAccess regsAcc) { + if (regsAcc.isLive()) { + waitTimeout(regsAcc.readFromTargetRegisters(unknown)); + } + /** + * Pass `set` to super, because even if regsAcc has just read from target into + * trace, we have yet to read from trace into state piece. + */ + return super.readUninitialized(acc, thread, piece, set); + } + if (acc instanceof PcodeDebuggerMemoryAccess memAcc) { + if (memAcc.isLive() && waitTimeout(memAcc.readFromTargetMemory(unknown))) { + unknown = memAcc.intersectUnknown(set); + if (unknown.isEmpty()) { + return super.readUninitialized(acc, thread, piece, set); + } + } + AddressSetView remains = memAcc.readFromStaticImages(piece, unknown); + /** + * In this case, readFromStaticImages has in fact modified the state piece, so we to + * compute what that was and remove it from the original request. The rest still + * needs to be read from the trace into the piece, which is done by the super call. + */ + AddressSetView readFromStatic = unknown.subtract(remains); + AddressSetView toReadFromTraceToPiece = set.subtract(readFromStatic); + return super.readUninitialized(memAcc, thread, piece, toReadFromTraceToPiece); + } + throw new AssertionError(); + } + + @Override + public boolean dataWritten(PcodeTraceDataAccess acc, AddressSet written, + PcodeThread thread, PcodeExecutorStatePiece piece, + Address address, int length, byte[] value) { + if (!mode.isWriteTarget()) { + return false; // Log it as written, so it goes to the trace + } + if (address.isUniqueAddress()) { + return true; + } + if (acc instanceof PcodeDebuggerRegistersAccess regsAcc) { + if (!regsAcc.isLive()) { + return true; + } + waitTimeout(regsAcc.writeTargetRegister(address, value)); + // Change should get recorded by back-end, if successful + } + else if (acc instanceof PcodeDebuggerMemoryAccess memAcc) { + if (!memAcc.isLive()) { + return true; + } + waitTimeout(memAcc.writeTargetMemory(address, value)); + // Change should get recorded by back-end, if successful + } + return true; + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java index 5b0e66d761..81ec0d7532 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java @@ -40,19 +40,24 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; import ghidra.app.services.*; import ghidra.async.AsyncLazyMap; import ghidra.debug.api.control.ControlMode; -import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; +import ghidra.debug.api.emulation.EmulatorFactory; import ghidra.debug.api.modules.DebuggerStaticMappingChangeListener; +import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeMachine.AccessKind; import ghidra.pcode.emu.PcodeMachine.SwiMode; import ghidra.pcode.exec.InjectionErrorPcodeExecutionException; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; +import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess; +import ghidra.pcode.exec.trace.data.PcodeTraceAccess; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; @@ -64,7 +69,8 @@ import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.*; import ghidra.trace.model.time.schedule.Scheduler.RunResult; -import ghidra.util.*; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.datastruct.ListenerSet; import ghidra.util.exception.CancelledException; @@ -286,8 +292,8 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } } - protected DebuggerPcodeEmulatorFactory emulatorFactory = - new BytesDebuggerPcodeEmulatorFactory(); + protected EmulatorFactory emulatorFactory = + new DefaultEmulatorFactory(); protected final Set eldest = new LinkedHashSet<>(); protected final NavigableMap cache = new TreeMap<>(); @@ -359,8 +365,8 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm DockingAction actionEmulateProgram; DockingAction actionEmulateAddThread; DockingAction actionInvalidateCache; - Map, ToggleDockingAction> // - actionsChooseEmulatorFactory = new HashMap<>(); + Map, ToggleDockingAction> actionsChooseEmulatorFactory = + new HashMap<>(); final ChangeListener classChangeListener = this::classesChanged; @@ -400,7 +406,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm updateConfigureEmulatorStates(); } - private ToggleDockingAction createActionChooseEmulator(DebuggerPcodeEmulatorFactory factory) { + private ToggleDockingAction createActionChooseEmulator(EmulatorFactory factory) { ToggleDockingAction action = ConfigureEmulatorAction.builder(this) .menuPath(DebuggerPluginPackage.NAME, ConfigureEmulatorAction.NAME, factory.getTitle()) @@ -412,21 +418,18 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } private void updateConfigureEmulatorStates() { - Map, DebuggerPcodeEmulatorFactory> byClass = + Map, EmulatorFactory> byClass = getEmulatorFactories().stream() - .collect(Collectors.toMap(DebuggerPcodeEmulatorFactory::getClass, - Objects::requireNonNull)); - Iterator, ToggleDockingAction>> it = + .collect(Collectors.toMap(EmulatorFactory::getClass, Objects::requireNonNull)); + Iterator, ToggleDockingAction>> it = actionsChooseEmulatorFactory.entrySet().iterator(); while (it.hasNext()) { - Entry, ToggleDockingAction> ent = - it.next(); + Entry, ToggleDockingAction> ent = it.next(); if (!byClass.keySet().contains(ent.getKey())) { tool.removeAction(ent.getValue()); } } - for (Entry, DebuggerPcodeEmulatorFactory> ent : byClass - .entrySet()) { + for (Entry, EmulatorFactory> ent : byClass.entrySet()) { if (!actionsChooseEmulatorFactory.containsKey(ent.getKey())) { ToggleDockingAction action = createActionChooseEmulator(ent.getValue()); action.setSelected(ent.getKey() == emulatorFactory.getClass()); @@ -557,7 +560,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm invalidateCache(); } - private void configureEmulatorActivated(DebuggerPcodeEmulatorFactory factory) { + private void configureEmulatorActivated(EmulatorFactory factory) { // TODO: Pull up config page. Tool Options? Program/Trace Options? setEmulatorFactory(factory); } @@ -578,12 +581,12 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } @Override - public Collection getEmulatorFactories() { - return ClassSearcher.getInstances(DebuggerPcodeEmulatorFactory.class); + public Collection getEmulatorFactories() { + return ClassSearcher.getInstances(EmulatorFactory.class); } @Override - public synchronized void setEmulatorFactory(DebuggerPcodeEmulatorFactory factory) { + public synchronized void setEmulatorFactory(EmulatorFactory factory) { emulatorFactory = Objects.requireNonNull(factory); for (ToggleDockingAction toggle : actionsChooseEmulatorFactory.values()) { toggle.setSelected(false); @@ -598,7 +601,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } @Override - public synchronized DebuggerPcodeEmulatorFactory getEmulatorFactory() { + public synchronized EmulatorFactory getEmulatorFactory() { return emulatorFactory; } @@ -640,7 +643,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm return task.future; } - protected void installBreakpoints(Trace trace, long snap, DebuggerPcodeMachine emu) { + protected void installBreakpoints(Trace trace, long snap, PcodeMachine emu) { Lifespan span = Lifespan.at(snap); TraceBreakpointManager bm = trace.getBreakpointManager(); for (AddressSpace as : trace.getBaseAddressFactory().getAddressSpaces()) { @@ -696,7 +699,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm // TODO: Handle errors, and add to proper place in cache? // TODO: Finish partially-executed instructions? try (BusyEmu be = new BusyEmu(ancestor.getValue())) { - DebuggerPcodeMachine emu = be.ce.emulator(); + PcodeMachine emu = be.ce.emulator(); emu.clearAllInjects(); emu.clearAccessBreakpoints(); @@ -710,9 +713,13 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm return be.dup(); } } - DebuggerPcodeMachine emu = emulatorFactory.create(tool, platform, time.getSnap(), - targetService == null ? null : targetService.getTarget(trace)); - try (BusyEmu be = new BusyEmu(new CachedEmulator(key.trace, emu))) { + Target target = targetService == null ? null : targetService.getTarget(trace); + DefaultPcodeDebuggerAccess from = + new DefaultPcodeDebuggerAccess(tool, target, platform, time.getSnap()); + Writer writer = DebuggerEmulationIntegration.bytesDelayedWriteTrace(from); + + PcodeMachine emu = emulatorFactory.create(from, writer); + try (BusyEmu be = new BusyEmu(new CachedEmulator(key.trace, emu, writer))) { installBreakpoints(key.trace, key.time.getSnap(), be.ce.emulator()); monitor.initialize(time.totalTickCount()); createRegisterSpaces(trace, time, monitor); @@ -741,7 +748,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm destSnap = key.trace.getTimeManager().findScratchSnapshot(key.time); destSnap.setDescription("Emulated"); try { - ce.emulator().writeDown(key.platform, destSnap.getKey(), key.time.getSnap()); + PcodeTraceAccess into = new DefaultPcodeTraceAccess(key.platform, destSnap.getKey(), + key.time.getSnap()); + ce.writer().writeDown(into); TraceThread lastThread = key.time.getLastThread(key.trace); destSnap.setEventThread(lastThread); } @@ -870,7 +879,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } @Override - public DebuggerPcodeMachine getCachedEmulator(Trace trace, TraceSchedule time) { + public PcodeMachine getCachedEmulator(Trace trace, TraceSchedule time) { CachedEmulator ce = cache.get(new CacheKey(trace.getPlatformManager().getHostPlatform(), time)); return ce == null || !ce.isValid() ? null : ce.emulator(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DefaultEmulatorFactory.java similarity index 68% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DefaultEmulatorFactory.java index e18a9a1cdf..817fe702d9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DefaultEmulatorFactory.java @@ -4,9 +4,9 @@ * 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. @@ -15,13 +15,16 @@ */ package ghidra.app.plugin.core.debug.service.emulation; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; +import ghidra.debug.api.emulation.EmulatorFactory; import ghidra.debug.api.emulation.PcodeDebuggerAccess; +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; /** * The Debugger's default emulator factory */ -public class BytesDebuggerPcodeEmulatorFactory extends AbstractDebuggerPcodeEmulatorFactory { +public class DefaultEmulatorFactory implements EmulatorFactory { // TODO: Config options: // 1) userop library @@ -31,7 +34,7 @@ public class BytesDebuggerPcodeEmulatorFactory extends AbstractDebuggerPcodeEmul } @Override - public DebuggerPcodeMachine create(PcodeDebuggerAccess access) { - return new BytesDebuggerPcodeEmulator(access); + public PcodeMachine create(PcodeDebuggerAccess access, Writer writer) { + return new PcodeEmulator(access.getLanguage(), writer.callbacks()); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java deleted file mode 100644 index 2a111a5a1d..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java +++ /dev/null @@ -1,44 +0,0 @@ -/* ### - * 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.plugin.core.debug.service.emulation; - -import ghidra.debug.api.emulation.PcodeDebuggerMemoryAccess; -import ghidra.pcode.exec.trace.*; - -/** - * A state composing a single {@link RWTargetMemoryPcodeExecutorStatePiece} - */ -public class RWTargetMemoryPcodeExecutorState extends DefaultTracePcodeExecutorState { - /** - * Create the state - * - * @param data the trace-memory access shim - * @param mode whether to ever write the target - */ - public RWTargetMemoryPcodeExecutorState(PcodeDebuggerMemoryAccess data, Mode mode) { - super(new RWTargetMemoryPcodeExecutorStatePiece(data, mode)); - } - - protected RWTargetMemoryPcodeExecutorState( - TracePcodeExecutorStatePiece piece) { - super(piece); - } - - @Override - public RWTargetMemoryPcodeExecutorState fork() { - return new RWTargetMemoryPcodeExecutorState(piece.fork()); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java deleted file mode 100644 index a6f84bee78..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java +++ /dev/null @@ -1,158 +0,0 @@ -/* ### - * 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.plugin.core.debug.service.emulation; - -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import generic.ULongSpan; -import generic.ULongSpan.ULongSpanSet; -import ghidra.debug.api.emulation.PcodeDebuggerDataAccess; -import ghidra.debug.api.emulation.PcodeDebuggerMemoryAccess; -import ghidra.generic.util.datastruct.SemisparseByteArray; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Language; -import ghidra.trace.model.memory.TraceMemoryState; - -/** - * An executor state piece that knows to read live memory if applicable - * - *

- * This requires a trace-memory access shim for the debugger. It will check if the shim is - * associated with a live session. If so, it will direct the recorder to capture the block(s) - * containing the read, if they're not already {@link TraceMemoryState#KNOWN}. When such a target - * comments is required, the state will wait up to 1 second for it to complete (see - * {@link AbstractRWTargetCachedSpace#waitTimeout(CompletableFuture)}). - * - *

- * This state will also attempt to fill unknown bytes with values from mapped static images. The - * order to retrieve state is: - *

    - *
  1. The cache, i.e., this state object
  2. - *
  3. The trace
  4. - *
  5. The live target, if applicable
  6. - *
  7. Mapped static images, if available
  8. - *
- * - *

- * If all those defer, the state is read as if filled with 0s. - */ -public class RWTargetMemoryPcodeExecutorStatePiece - extends AbstractRWTargetPcodeExecutorStatePiece { - - /** - * A space, corresponding to a memory space, of this state - * - *

- * All of the actual read logic is contained here. We override the space map factory so that it - * creates these spaces. - */ - protected class RWTargetMemoryCachedSpace extends AbstractRWTargetCachedSpace { - - protected final PcodeDebuggerMemoryAccess backing; - - public RWTargetMemoryCachedSpace(Language language, AddressSpace space, - PcodeDebuggerMemoryAccess backing) { - super(language, space, backing); - this.backing = backing; - } - - protected RWTargetMemoryCachedSpace(Language language, AddressSpace space, - PcodeDebuggerMemoryAccess backing, SemisparseByteArray bytes, AddressSet written) { - super(language, space, backing, bytes, written); - this.backing = backing; - } - - @Override - public RWTargetMemoryCachedSpace fork() { - return new RWTargetMemoryCachedSpace(language, space, backing, bytes.fork(), - new AddressSet(written)); - } - - @Override - protected ULongSpanSet readUninitializedFromTarget(ULongSpanSet uninitialized) { - if (space.isUniqueSpace()) { - return uninitialized; - } - AddressSetView unknown; - AddressSet addrsUninit = addrSet(uninitialized); - unknown = backing.intersectUnknown(addrsUninit); - if (unknown.isEmpty()) { - return uninitialized; - } - if (backing.isLive() && waitTimeout(backing.readFromTargetMemory(unknown))) { - unknown = backing.intersectUnknown(addrsUninit); - if (unknown.isEmpty()) { - return uninitialized; - } - } - if (backing.readFromStaticImages(bytes, unknown)) { - ULongSpan bound = uninitialized.bound(); - return bytes.getUninitialized(bound.min(), bound.max()); - } - return uninitialized; - } - - @Override - public void write(long offset, byte[] val, int srcOffset, int length) { - if (mode.isWriteTarget() && !space.isUniqueSpace() && - waitTimeout(backing.writeTargetMemory(space.getAddress(offset), val))) { - // Change should already be recorded, if successful - return; - } - super.write(offset, val, srcOffset, length); - } - } - - private final Mode mode; - - /** - * Construct a piece - * - * @param data the trace-memory access shim - * @param mode whether to ever write the target - */ - public RWTargetMemoryPcodeExecutorStatePiece(PcodeDebuggerMemoryAccess data, Mode mode) { - super(data); - this.mode = mode; - } - - class WRTargetMemorySpaceMap extends TargetBackedSpaceMap { - public WRTargetMemorySpaceMap() { - super(); - } - - protected WRTargetMemorySpaceMap(Map spaceMap) { - super(spaceMap); - } - - @Override - public AbstractSpaceMap fork() { - return new WRTargetMemorySpaceMap(fork(spaces)); - } - - @Override - protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) { - return new RWTargetMemoryCachedSpace(language, space, - (PcodeDebuggerMemoryAccess) data); - } - } - - @Override - protected AbstractSpaceMap newSpaceMap() { - return new WRTargetMemorySpaceMap(); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java deleted file mode 100644 index 14f00dd88d..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java +++ /dev/null @@ -1,44 +0,0 @@ -/* ### - * 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.plugin.core.debug.service.emulation; - -import ghidra.debug.api.emulation.PcodeDebuggerRegistersAccess; -import ghidra.pcode.exec.trace.*; - -/** - * A state composing a single {@link RWTargetRegistersPcodeExecutorStatePiece} - */ -public class RWTargetRegistersPcodeExecutorState extends DefaultTracePcodeExecutorState { - /** - * Create the state - * - * @param data the trace-registers access shim - * @param mode whether to ever write the target - */ - public RWTargetRegistersPcodeExecutorState(PcodeDebuggerRegistersAccess data, Mode mode) { - super(new RWTargetRegistersPcodeExecutorStatePiece(data, mode)); - } - - protected RWTargetRegistersPcodeExecutorState( - TracePcodeExecutorStatePiece piece) { - super(piece); - } - - @Override - public RWTargetRegistersPcodeExecutorState fork() { - return new RWTargetRegistersPcodeExecutorState(piece.fork()); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java deleted file mode 100644 index d6d63e2299..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java +++ /dev/null @@ -1,141 +0,0 @@ -/* ### - * 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.plugin.core.debug.service.emulation; - -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import generic.ULongSpan.ULongSpanSet; -import ghidra.debug.api.emulation.PcodeDebuggerDataAccess; -import ghidra.debug.api.emulation.PcodeDebuggerRegistersAccess; -import ghidra.generic.util.datastruct.SemisparseByteArray; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Language; -import ghidra.trace.model.memory.TraceMemoryState; - -/** - * An executor state piece that knows to read live registers if applicable - * - *

- * This requires a trace-register access shim for the debugger. It will check if the shim is - * associated with a live session. If so, it will direct the recorder to capture the register(s) to - * be read, if they're not already {@link TraceMemoryState#KNOWN}. When such a target comments is - * required, the state will wait up to 1 second for it to complete (see - * {@link AbstractRWTargetCachedSpace#waitTimeout(CompletableFuture)}). - * - *

    - *
  1. The cache, i.e., this state object
  2. - *
  3. The trace
  4. - *
  5. The live target, if applicable
  6. - *
- * - *

- * If all those defer, the state is read as if filled with 0s. - */ -public class RWTargetRegistersPcodeExecutorStatePiece - extends AbstractRWTargetPcodeExecutorStatePiece { - - /** - * A space, corresponding to a register space (really a thread) of this state - * - *

- * All of the actual read logic is contained here. We override the space map factory so that it - * creates these spaces. - */ - protected class RWTargetRegistersCachedSpace extends AbstractRWTargetCachedSpace { - - protected final PcodeDebuggerRegistersAccess backing; - - public RWTargetRegistersCachedSpace(Language language, AddressSpace space, - PcodeDebuggerRegistersAccess backing) { - super(language, space, backing); - this.backing = backing; - } - - protected RWTargetRegistersCachedSpace(Language language, AddressSpace space, - PcodeDebuggerRegistersAccess backing, SemisparseByteArray bytes, - AddressSet written) { - super(language, space, backing, bytes, written); - this.backing = backing; - } - - @Override - public RWTargetRegistersCachedSpace fork() { - return new RWTargetRegistersCachedSpace(language, uniqueSpace, backing, bytes.fork(), - new AddressSet(written)); - } - - @Override - protected ULongSpanSet readUninitializedFromTarget(ULongSpanSet uninitialized) { - if (space.isUniqueSpace() || !backing.isLive()) { - return uninitialized; - } - AddressSet addrsUninit = addrSet(uninitialized); - AddressSetView unknown = backing.intersectUnknown(addrsUninit); - waitTimeout(backing.readFromTargetRegisters(unknown)); - return uninitialized; - } - - @Override - public void write(long offset, byte[] val, int srcOffset, int length) { - if (mode.isWriteTarget() && !space.isUniqueSpace() && - waitTimeout(backing.writeTargetRegister(space.getAddress(offset), val))) { - // Change should already be recorded, if successful - return; - } - super.write(offset, val, srcOffset, length); - } - } - - private final Mode mode; - - /** - * Construct a piece - * - * @param data the trace-register access shim - * @param mode whether to ever write the target - */ - public RWTargetRegistersPcodeExecutorStatePiece(PcodeDebuggerRegistersAccess data, Mode mode) { - super(data); - this.mode = mode; - } - - class WRTargetRegistersSpaceMap extends TargetBackedSpaceMap { - public WRTargetRegistersSpaceMap() { - super(); - } - - protected WRTargetRegistersSpaceMap(Map spaceMap) { - super(spaceMap); - } - - @Override - public AbstractSpaceMap fork() { - return new WRTargetRegistersSpaceMap(fork(spaces)); - } - - @Override - protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) { - return new RWTargetRegistersCachedSpace(language, space, - (PcodeDebuggerRegistersAccess) data); - } - } - - @Override - protected AbstractSpaceMap newSpaceMap() { - return new WRTargetRegistersSpaceMap(); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java index fe3b25cf38..d16a9e4448 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java @@ -4,9 +4,9 @@ * 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. @@ -43,10 +43,25 @@ public abstract class AbstractPcodeDebuggerAccessnot return a Debugger access shim, but a Trace one, since we + * never expect a delayed write to affect the target. + */ + @Override + public PcodeTraceAccess deriveForWrite(long snap) { + return new DefaultPcodeTraceAccess(platform, snap, threadsSnap); + } + @Override protected DefaultPcodeDebuggerMemoryAccess newDataForSharedState() { return new DefaultPcodeDebuggerMemoryAccess(provider, target, platform, snap, viewport); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java index 73fb0d9dd9..9ce860c2a4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java @@ -4,9 +4,9 @@ * 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. @@ -24,7 +24,7 @@ import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.debug.api.emulation.PcodeDebuggerMemoryAccess; import ghidra.debug.api.target.Target; import ghidra.framework.plugintool.ServiceProvider; -import ghidra.generic.util.datastruct.SemisparseByteArray; +import ghidra.pcode.exec.PcodeExecutorStatePiece; import ghidra.pcode.exec.trace.data.DefaultPcodeTraceMemoryAccess; import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; import ghidra.program.model.address.*; @@ -94,16 +94,18 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc } @Override - public boolean readFromStaticImages(SemisparseByteArray bytes, AddressSetView guestView) { - // TODO: Expand to block? DON'T OVERWRITE KNOWN! + public AddressSetView readFromStaticImages(PcodeExecutorStatePiece piece, + AddressSetView guestView) { + // NOTE: If we expand to block, DON'T OVERWRITE KNOWN! DebuggerStaticMappingService mappingService = provider.getService(DebuggerStaticMappingService.class); if (mappingService == null) { - return false; + return guestView; } + AddressSet remains = new AddressSet(guestView); try { - return new AbstractMappedMemoryBytesVisitor(mappingService, new byte[4096]) { + boolean result = new AbstractMappedMemoryBytesVisitor(mappingService, new byte[4096]) { @Override protected int read(Memory memory, Address addr, byte[] dest, int size) throws MemoryAccessException { @@ -128,9 +130,12 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc @Override protected void visitData(Address hostAddr, byte[] data, int size) { Address guestAddr = platform.mapHostToGuest(hostAddr); - bytes.putData(guestAddr.getOffset(), data, 0, size); + piece.setVarInternal(guestAddr.getAddressSpace(), guestAddr.getOffset(), size, + data); + remains.delete(guestAddr, guestAddr.add(size)); } }.visit(platform.getTrace(), snap, platform.mapGuestToHost(guestView)); + return result ? remains : guestView; } catch (MemoryAccessException e) { throw new AssertionError(e); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeArithmetic.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeArithmetic.java index 28830c75dd..0432924b0e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeArithmetic.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeArithmetic.java @@ -42,6 +42,11 @@ class SymPcodeArithmetic implements PcodeArithmetic { this.language = cSpec.getLanguage(); } + @Override + public Class getDomain() { + return Sym.class; + } + @Override public Endian getEndian() { return language.isBigEndian() ? Endian.BIG : Endian.LITTLE; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutorState.java index dc8e49edef..a5cca4703e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/SymPcodeExecutorState.java @@ -16,12 +16,12 @@ package ghidra.app.plugin.core.debug.stack; import java.util.*; +import java.util.stream.Stream; import ghidra.app.plugin.core.debug.stack.Sym.*; import ghidra.app.plugin.core.debug.stack.SymStateSpace.SymEntry; -import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; -import ghidra.pcode.exec.PcodeExecutorState; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.*; @@ -53,6 +53,8 @@ public class SymPcodeExecutorState implements PcodeExecutorState { /** * Construct a new state for the given program + * + * @param program the program under analysis */ public SymPcodeExecutorState(Program program) { this.program = program; @@ -101,6 +103,11 @@ public class SymPcodeExecutorState implements PcodeExecutorState { return arithmetic; } + @Override + public Stream> streamPieces() { + return Stream.of(this); + } + @Override public void setVar(AddressSpace space, Sym offset, int size, boolean quantize, Sym val) { @@ -123,6 +130,11 @@ public class SymPcodeExecutorState implements PcodeExecutorState { } } + @Override + public void setVarInternal(AddressSpace space, Sym offset, int size, Sym val) { + setVar(space, offset, size, false, val); + } + @Override public Sym getVar(AddressSpace space, Sym offset, int size, boolean quantize, Reason reason) { @@ -142,6 +154,11 @@ public class SymPcodeExecutorState implements PcodeExecutorState { return Sym.opaque(); } + @Override + public Sym getVarInternal(AddressSpace space, Sym offset, int size, Reason reason) { + return getVar(space, offset, size, false, reason); + } + @Override public Map getRegisterValues() { return Map.of(); @@ -159,13 +176,15 @@ public class SymPcodeExecutorState implements PcodeExecutorState { } @Override - public SymPcodeExecutorState fork() { + public SymPcodeExecutorState fork(PcodeStateCallbacks cb) { return new SymPcodeExecutorState(program, arithmetic, stackSpace.fork(), registerSpace.fork(), uniqueSpace.fork()); } /** * Create a new state whose registers are forked from those of this state + * + * @return this fork */ public SymPcodeExecutorState forkRegs() { return new SymPcodeExecutorState(program, arithmetic, new SymStateSpace(), @@ -281,7 +300,7 @@ public class SymPcodeExecutorState implements PcodeExecutorState { * Any entry of the form (reg, v:Deref) is collected as (reg, [Stack]:v.offset). Note that the * size of the stack entry is implied by the size of the register. * - * @return + * @return the map from register to stack address */ public Map computeMapUsingRegisters() { Map result = new HashMap<>(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java index 20992e9fc4..535003f848 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java @@ -18,16 +18,16 @@ package ghidra.pcode.exec; import java.math.BigInteger; import java.util.*; import java.util.Map.Entry; +import java.util.stream.Stream; import ghidra.app.nav.NavigationUtils; -import ghidra.app.plugin.core.debug.service.emulation.*; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationIntegration; import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; import ghidra.app.plugin.processors.sleigh.SleighException; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.ServiceProvider; -import ghidra.pcode.emu.ThreadPcodeExecutorState; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.exec.SleighProgramCompiler.ErrorCollectingPcodeParser; @@ -241,28 +241,15 @@ public enum DebuggerPcodeUtils { throw new IllegalArgumentException("Coordinates have no trace"); } TracePlatform platform = coordinates.getPlatform(); - Language language = platform.getLanguage(); - if (!(language instanceof SleighLanguage)) { + if (!(platform.getLanguage() instanceof SleighLanguage language)) { throw new IllegalArgumentException( "Given trace or platform does not use a Sleigh language"); } DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(provider, coordinates.getTarget(), platform, coordinates.getViewSnap()); - PcodeExecutorState shared = - new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RW); - if (coordinates.getThread() == null) { - return shared; - } - PcodeExecutorState local = new RWTargetRegistersPcodeExecutorState( - access.getDataForLocalState(coordinates.getThread(), coordinates.getFrame()), - Mode.RW); - return new ThreadPcodeExecutorState<>(shared, local) { - @Override - public void clear() { - shared.clear(); - local.clear(); - } - }; + PcodeStateCallbacks cb = DebuggerEmulationIntegration.bytesImmediateWriteTarget(access, + coordinates.getThread(), coordinates.getFrame()); + return new BytesPcodeExecutorState(language, cb); } /** @@ -279,9 +266,8 @@ public enum DebuggerPcodeUtils { public static PcodeExecutor executorForCoordinates(ServiceProvider provider, DebuggerCoordinates coordinates) { PcodeExecutorState state = executorStateForCoordinates(provider, coordinates); - - SleighLanguage slang = (SleighLanguage) state.getLanguage(); - return new PcodeExecutor<>(slang, BytesPcodeArithmetic.forLanguage(slang), state, + SleighLanguage language = (SleighLanguage) state.getLanguage(); + return new PcodeExecutor<>(language, BytesPcodeArithmetic.forLanguage(language), state, Reason.INSPECT); } @@ -475,6 +461,11 @@ public enum DebuggerPcodeUtils { this.location = location; } + @Override + public Class getDomain() { + return WatchValue.class; + } + @Override public Endian getEndian() { return bytes.getEndian(); @@ -590,9 +581,25 @@ public enum DebuggerPcodeUtils { } @Override - public WatchValuePcodeExecutorStatePiece fork() { + public Stream> streamPieces() { + return Stream.of(bytesPiece, statePiece, locationPiece, readsPiece); + } + + @Override + public WatchValuePcodeExecutorStatePiece fork(PcodeStateCallbacks cb) { return new WatchValuePcodeExecutorStatePiece( - bytesPiece.fork(), statePiece.fork(), locationPiece.fork(), readsPiece.fork()); + bytesPiece.fork(cb), + statePiece.fork(cb), + locationPiece.fork(cb), + readsPiece.fork(cb)); + } + + @Override + public void setVarInternal(AddressSpace space, byte[] offset, int size, WatchValue val) { + bytesPiece.setVarInternal(space, offset, size, val.bytes.bytes); + statePiece.setVarInternal(space, offset, size, val.state); + locationPiece.setVarInternal(space, offset, size, val.location); + readsPiece.setVarInternal(space, offset, size, val.reads); } @Override @@ -615,6 +622,17 @@ public enum DebuggerPcodeUtils { readsPiece.getVar(space, offset, size, quantize, reason)); } + @Override + public WatchValue getVarInternal(AddressSpace space, byte[] offset, int size, + Reason reason) { + return new WatchValue( + new PrettyBytes(getLanguage().isBigEndian(), + bytesPiece.getVarInternal(space, offset, size, reason)), + statePiece.getVarInternal(space, offset, size, reason), + locationPiece.getVarInternal(space, offset, size, reason), + readsPiece.getVarInternal(space, offset, size, reason)); + } + @Override public Map getRegisterValues() { Map result = new HashMap<>(); @@ -646,54 +664,21 @@ public enum DebuggerPcodeUtils { } } - public static class WatchValuePcodeExecutorState implements PcodeExecutorState { - private WatchValuePcodeExecutorStatePiece piece; + public static class WatchValuePcodeExecutorState + extends AbstractPcodeExecutorState { - public WatchValuePcodeExecutorState(WatchValuePcodeExecutorStatePiece piece) { - this.piece = piece; + public WatchValuePcodeExecutorState(PcodeExecutorStatePiece piece) { + super(piece); } @Override - public Language getLanguage() { - return piece.getLanguage(); + protected byte[] extractAddress(WatchValue value) { + return value.bytes.bytes; } @Override - public PcodeArithmetic getArithmetic() { - return piece.arithmetic; - } - - @Override - public WatchValuePcodeExecutorState fork() { - return new WatchValuePcodeExecutorState(piece.fork()); - } - - @Override - public void setVar(AddressSpace space, WatchValue offset, int size, boolean quantize, - WatchValue val) { - piece.setVar(space, offset.bytes.bytes, size, quantize, val); - } - - @Override - public WatchValue getVar(AddressSpace space, WatchValue offset, int size, - boolean quantize, - Reason reason) { - return piece.getVar(space, offset.bytes.bytes, size, quantize, reason); - } - - @Override - public Map getRegisterValues() { - return piece.getRegisterValues(); - } - - @Override - public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { - return piece.getConcreteBuffer(address, purpose); - } - - @Override - public void clear() { - piece.clear(); + public WatchValuePcodeExecutorState fork(PcodeStateCallbacks cb) { + return new WatchValuePcodeExecutorState(piece.fork(cb)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java deleted file mode 100644 index e75d75a5b0..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java +++ /dev/null @@ -1,102 +0,0 @@ -/* ### - * 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.exec.debug.auxiliary; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.app.plugin.core.debug.service.emulation.*; -import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory; -import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; -import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; -import ghidra.pcode.exec.trace.*; -import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; -import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; - -/** - * The most capable auxiliary emulator parts factory - * - *

- * This can manufacture parts for an emulator that is fully integrated with the Debugger UI, as well - * as all the parts for the less integrated forms of the same emulator. The pattern of use is - * generally to implement {@link DebuggerPcodeEmulatorFactory}, allowing the UI to discover and - * instantiate the emulator, though they could also be created directly by scripts or plugins. - * - *

- * For an example of a fully-integrated solution using this interface, see the Taint Analyzer. Its - * project serves as an archetype for similar dynamic analysis employing p-code emulation. - * - *

- * We recommend implementors start with the methods declared in {@link AuxEmulatorPartsFactory} with - * the aim of creating a derivative of {@link AuxPcodeEmulator}. Note that one Debugger-integrated - * emulator parts factory can be used with all three of {@link AuxPcodeEmulator}, - * {@link AuxTracePcodeEmulator}, {@link AuxTraceEmulatorPartsFactory}. Once the stand-alone - * emulator has been tested, proceed to the methods in {@link AuxTraceEmulatorPartsFactory} with the - * aim of creating a derivative of {@link AuxTracePcodeEmulator}. Most of the work here is in - * factoring the state objects and pieces to reduce code duplication among the stand-alone and - * trace-integrated states. Once the trace-integrated emulator is tested, then proceed to the - * methods declared here in {@link AuxDebuggerEmulatorPartsFactory} with the aim of creating a - * derivative of {@link AuxDebuggerPcodeEmulator}. Again, most of the work is in factoring the - * states to avoid code duplication. Once the Debugger-integrated emulator is tested, the final bit - * is to implement a {@link DebuggerPcodeEmulatorFactory} so that users can configure and create the - * emulator. Other UI pieces, e.g., actions, fields, and table columns, may be needed to facilitate - * user access to the emulator's auxiliary state. Furthermore, a userop library for accessing the - * auxiliary state is recommended, since Sleigh code can be executed by the user. - * - * @param the type of auxiliary values - */ -public interface AuxDebuggerEmulatorPartsFactory extends AuxTraceEmulatorPartsFactory { - /** - * Create the shared (memory) state of a new Debugger-integrated emulator - * - *

- * This state is usually composed of pieces using {@link PairedTracePcodeExecutorStatePiece}, - * but it does not have to be. It must incorporate the concrete piece provided. The state must - * be capable of lazily loading state from a trace, from a live target, and from mapped static - * programs. It must also be able to write its cache into the trace at another snapshot. The - * given concrete piece is already capable of doing that for concrete values. The auxiliary - * piece can, at its discretion, delegate to the concrete piece in order to derive its values. - * It should be able to independently load its state from the trace and mapped static program, - * since this is one way a user expects to initialize the auxiliary values. It ought to use the - * same data-access shim as the given concrete state. See - * {@link TracePcodeExecutorStatePiece#getData()}. - * - * @param emulator the emulator - * @param concrete the concrete piece - * @return the composed state - */ - TracePcodeExecutorState> createDebuggerSharedState( - AuxDebuggerPcodeEmulator emulator, - RWTargetMemoryPcodeExecutorStatePiece concrete); - - /** - * Create the local (register) state of a new Debugger-integrated thread - * - *

- * Like - * {@link #createDebuggerSharedState(AuxDebuggerPcodeEmulator, RWTargetMemoryPcodeExecutorStatePiece)} - * this state must also be capable of lazily loading state from a trace and from a live target. - * Static programs can't be mapped into register space, so they do not apply here. - * - * @param emulator the emulator - * @param thread the new thread - * @param concrete the concrete piece - * @return the composed state - */ - TracePcodeExecutorState> createDebuggerLocalState( - AuxDebuggerPcodeEmulator emulator, PcodeThread> thread, - RWTargetRegistersPcodeExecutorStatePiece concrete); -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java deleted file mode 100644 index 5ac7843ae6..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java +++ /dev/null @@ -1,74 +0,0 @@ -/* ### - * 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.exec.debug.auxiliary; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.app.plugin.core.debug.service.emulation.*; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; -import ghidra.debug.api.emulation.PcodeDebuggerAccess; -import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; -import ghidra.pcode.exec.trace.TracePcodeExecutorState; -import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; -import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; - -/** - * An Debugger-integrated emulator whose parts are manufactured by a - * {@link AuxDebuggerEmulatorPartsFactory} - * - *

- * See the parts factory interface and its super interfaces: - *

    - *
  • {@link AuxDebuggerEmulatorPartsFactory}
  • - *
  • {@link AuxTraceEmulatorPartsFactory}
  • - *
  • {@link AuxEmulatorPartsFactory}
  • - *
- * - * @param the type of auxiliary values - */ -public abstract class AuxDebuggerPcodeEmulator extends AuxTracePcodeEmulator - implements DebuggerPcodeMachine> { - - protected final PcodeDebuggerAccess access; - - /** - * Create a new emulator - * - * @param access the trace-and-debugger access shim - */ - public AuxDebuggerPcodeEmulator(PcodeDebuggerAccess access) { - super(access); - this.access = access; - } - - @Override - protected abstract AuxDebuggerEmulatorPartsFactory getPartsFactory(); - - @Override - public TracePcodeExecutorState> createSharedState() { - return getPartsFactory().createDebuggerSharedState(this, - new RWTargetMemoryPcodeExecutorStatePiece(access.getDataForSharedState(), Mode.RO)); - } - - @Override - public TracePcodeExecutorState> createLocalState( - PcodeThread> thread) { - return getPartsFactory().createDebuggerLocalState(this, thread, - new RWTargetRegistersPcodeExecutorStatePiece(access.getDataForLocalState(thread, 0), - Mode.RO)); - } -} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java index 06eb9218cb..f94597381a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java @@ -33,16 +33,17 @@ import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.gui.pcode.DebuggerPcodeStepperProvider.PcodeRowHtmlFormatter; -import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator; -import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulatorFactory; +import ghidra.app.plugin.core.debug.service.emulation.DefaultEmulatorFactory; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.services.DebuggerEmulationService; import ghidra.app.services.DebuggerTraceManagerService; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; import ghidra.debug.api.emulation.PcodeDebuggerAccess; +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.pcode.exec.trace.TraceSleighUtils; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Instruction; @@ -103,7 +104,7 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), tb.host, 1); PcodeExecutor init = TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0); - init.executeSleigh("pc = 0x00400000;"); + init.executeSleigh("pc = 0x%s;".formatted(start)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); iit = asm.assemble(start, "imm r0, #0x123"); @@ -154,10 +155,10 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg public void testCustomUseropDisplay() throws Exception { populateTrace(); - emuService.setEmulatorFactory(new BytesDebuggerPcodeEmulatorFactory() { + emuService.setEmulatorFactory(new DefaultEmulatorFactory() { @Override - public DebuggerPcodeMachine create(PcodeDebuggerAccess access) { - BytesDebuggerPcodeEmulator emu = new BytesDebuggerPcodeEmulator(access) { + public PcodeMachine create(PcodeDebuggerAccess access, Writer writer) { + PcodeEmulator emu = new PcodeEmulator(access.getLanguage(), writer.callbacks()) { @Override protected PcodeUseropLibrary createUseropLibrary() { return new AnnotatedPcodeUseropLibrary() { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java index 88060f2732..c3b01344ae 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java @@ -38,11 +38,11 @@ import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlug import ghidra.app.services.DebuggerEmulationService.EmulationResult; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; import ghidra.debug.api.platform.DebuggerPlatformMapper; import ghidra.debug.api.tracemgr.DebuggerCoordinates; -import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.*; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.pcode.utils.Utils; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; @@ -533,7 +533,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe assertTrue(result1.error() instanceof InterruptPcodeExecutionException); // Save this for comparison later - DebuggerPcodeMachine emu = Unique.assertOne(emulationPlugin.cache.values()).emulator(); + PcodeMachine emu = Unique.assertOne(emulationPlugin.cache.values()).emulator(); // This will test if the one just hit gets ignored EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), @@ -604,7 +604,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe assertTrue(result1.error() instanceof InterruptPcodeExecutionException); // Save this for comparison later - DebuggerPcodeMachine emu = Unique.assertOne(emulationPlugin.cache.values()).emulator(); + PcodeMachine emu = Unique.assertOne(emulationPlugin.cache.values()).emulator(); // Now, step it forward to complete the instruction emulationPlugin.emulate(trace.getPlatformManager().getHostPlatform(), @@ -671,7 +671,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe assertTrue(result1.error() instanceof PcodeExecutionException); // Save this for comparison later - DebuggerPcodeMachine emu = Unique.assertOne(emulationPlugin.cache.values()).emulator(); + PcodeMachine emu = Unique.assertOne(emulationPlugin.cache.values()).emulator(); // We shouldn't get any further EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), @@ -979,12 +979,13 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe TracePlatform host = tb.trace.getPlatformManager().getHostPlatform(); DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool, null, host, restartEmuSnap); - BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(access); + Writer writer = DebuggerEmulationIntegration.bytesDelayedWriteTrace(access); + PcodeEmulator emulator = new PcodeEmulator(access.getLanguage(), writer.callbacks()); TraceSnapshot snapshot = tb.trace.getTimeManager().createSnapshot("created new emulator thread"); long newSnap = snapshot.getKey(); - emulator.writeDown(host, newSnap, newSnap); + writer.writeDown(newSnap); TraceThread newTraceThread = ProgramEmulationUtils.doLaunchEmulationThread(tb.trace, newSnap, program, tb.addr(0x00400000), addr(program, 0x00400000)); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java deleted file mode 100644 index a71d8e8dfa..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java +++ /dev/null @@ -1,117 +0,0 @@ -/* ### - * 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.exec.trace; - -import java.util.Map; - -import generic.ULongSpan.ULongSpanSet; -import ghidra.generic.util.datastruct.SemisparseByteArray; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Language; - -/** - * A state piece which can check for uninitialized reads - * - *

- * Depending on the use case, it may be desirable to ensure all reads through the course of - * emulation are from initialized parts of memory. For traces, there's an additional consideration - * as to whether the values are present, but stale. Again, depending on the use case, that may be - * acceptable. See the extensions of this class for "stock" implementations. - */ -public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece - extends BytesTracePcodeExecutorStatePiece { - - protected class CheckedCachedSpace extends CachedSpace { - public CheckedCachedSpace(Language language, AddressSpace space, - PcodeTraceDataAccess backing) { - super(language, space, backing); - } - - protected CheckedCachedSpace(Language language, AddressSpace space, - PcodeTraceDataAccess backing, SemisparseByteArray bytes, AddressSet written) { - super(language, space, backing, bytes, written); - } - - @Override - public CachedSpace fork() { - return new CheckedCachedSpace(language, space, backing, bytes.fork(), - new AddressSet(written)); - } - - @Override - public byte[] read(long offset, int size, Reason reason) { - ULongSpanSet uninitialized = - bytes.getUninitialized(offset, offset + size - 1); - if (!uninitialized.isEmpty()) { - size = checkUninitialized(backing, space.getAddress(offset), size, - addrSet(uninitialized)); - } - return super.read(offset, size, reason); - } - } - - /** - * Construct a piece - * - * @param data the trace-data access shim - */ - public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) { - super(data); - } - - protected AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data, - AbstractSpaceMap spaceMap) { - super(data, spaceMap); - } - - protected class CheckedCachedSpaceMap extends TraceBackedSpaceMap { - public CheckedCachedSpaceMap() { - super(); - } - - protected CheckedCachedSpaceMap(Map spaces) { - super(spaces); - } - - @Override - protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) { - return new CheckedCachedSpace(language, space, backing); - } - - @Override - public CheckedCachedSpaceMap fork() { - return new CheckedCachedSpaceMap(fork(spaces)); - } - } - - @Override - protected AbstractSpaceMap newSpaceMap() { - return new CheckedCachedSpaceMap(); - } - - /** - * Decide what to do, given that a portion of a read is uninitialized - * - * @param backing the shim backing the address space that was read - * @param start the starting address of the requested read - * @param size the size of the requested read - * @param uninitialized the portion of the read that is uninitialized - * @return the adjusted size of the read - */ - protected abstract int checkUninitialized(PcodeTraceDataAccess backing, Address start, - int size, AddressSet uninitialized); -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java index 7d5110f24b..9fe03e1afd 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AddressesReadTracePcodeExecutorStatePiece.java @@ -17,8 +17,6 @@ package ghidra.pcode.exec.trace; import java.util.*; -import javax.help.UnsupportedOperationException; - import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; @@ -40,29 +38,31 @@ import ghidra.program.model.mem.MemBuffer; */ public class AddressesReadTracePcodeExecutorStatePiece extends AbstractLongOffsetPcodeExecutorStatePiece - implements TracePcodeExecutorStatePiece { + implements PcodeExecutorStatePiece { protected final PcodeTraceDataAccess data; private final Map unique; + protected AddressesReadTracePcodeExecutorStatePiece(PcodeTraceDataAccess data, + Map unique) { + super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()), + AddressesReadPcodeArithmetic.INSTANCE, PcodeStateCallbacks.NONE); + this.data = data; + this.unique = unique; + } + /** * Construct the state piece * * @param data the trace data access shim */ public AddressesReadTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()), - AddressesReadPcodeArithmetic.INSTANCE); - this.data = data; - this.unique = new HashMap<>(); + this(data, new HashMap<>()); } - protected AddressesReadTracePcodeExecutorStatePiece(PcodeTraceDataAccess data, - Map unique) { - super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()), - AddressesReadPcodeArithmetic.INSTANCE); - this.data = data; - this.unique = unique; + @Override + protected AddressSetView checkSize(int size, AddressSetView val) { + return val; } @Override @@ -71,20 +71,10 @@ public class AddressesReadTracePcodeExecutorStatePiece } @Override - public PcodeTraceDataAccess getData() { - return data; - } - - @Override - public AddressesReadTracePcodeExecutorStatePiece fork() { + public AddressesReadTracePcodeExecutorStatePiece fork(PcodeStateCallbacks cb) { return new AddressesReadTracePcodeExecutorStatePiece(data, new HashMap<>(unique)); } - @Override - public void writeDown(PcodeTraceDataAccess into) { - throw new UnsupportedOperationException(); - } - @Override protected Map getRegisterValuesFromSpace(AddressSpace s, List registers) { @@ -102,7 +92,8 @@ public class AddressesReadTracePcodeExecutorStatePiece } @Override - protected void setInSpace(AddressSpace space, long offset, int size, AddressSetView val) { + protected void setInSpace(AddressSpace space, long offset, int size, AddressSetView val, + PcodeStateCallbacks cb) { if (!space.isUniqueSpace()) { return; } @@ -112,7 +103,7 @@ public class AddressesReadTracePcodeExecutorStatePiece @Override protected AddressSetView getFromSpace(AddressSpace space, long offset, int size, - Reason reason) { + Reason reason, PcodeStateCallbacks cb) { if (space.isUniqueSpace()) { AddressSetView result = unique.get(offset); if (result == null) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java deleted file mode 100644 index 933988ec9a..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java +++ /dev/null @@ -1,68 +0,0 @@ -/* ### - * 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.exec.trace; - -import ghidra.pcode.emu.*; -import ghidra.pcode.exec.trace.data.*; -import ghidra.trace.model.guest.TracePlatform; - -/** - * An emulator that can read initial state from a trace and record its state back into it - */ -public class BytesTracePcodeEmulator extends PcodeEmulator implements TracePcodeMachine { - protected final PcodeTraceAccess access; - - /** - * Create a trace-bound emulator - * - * @param access the trace access shim - */ - public BytesTracePcodeEmulator(PcodeTraceAccess access) { - super(access.getLanguage()); - this.access = access; - } - - /** - * Create a trace-bound emulator - * - * @param platform the platform to emulate - * @param snap the source snap - */ - public BytesTracePcodeEmulator(TracePlatform platform, long snap) { - this(new DefaultPcodeTraceAccess(platform, snap)); - } - - @Override - protected BytesPcodeThread createThread(String name) { - BytesPcodeThread thread = super.createThread(name); - access.getDataForLocalState(thread, 0).initializeThreadContext(thread); - return thread; - } - - protected TracePcodeExecutorState newState(PcodeTraceDataAccess data) { - return new BytesTracePcodeExecutorState(data); - } - - @Override - public TracePcodeExecutorState createSharedState() { - return newState(access.getDataForSharedState()); - } - - @Override - public TracePcodeExecutorState createLocalState(PcodeThread thread) { - return newState(access.getDataForLocalState(thread, 0)); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java deleted file mode 100644 index 79f29a4d01..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java +++ /dev/null @@ -1,41 +0,0 @@ -/* ### - * 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.exec.trace; - -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; - -/** - * A state composing a single {@link BytesTracePcodeExecutorStatePiece} - */ -public class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState { - /** - * Create the state - * - * @param data the trace-data access shim - */ - public BytesTracePcodeExecutorState(PcodeTraceDataAccess data) { - super(new BytesTracePcodeExecutorStatePiece(data)); - } - - protected BytesTracePcodeExecutorState(TracePcodeExecutorStatePiece piece) { - super(piece); - } - - @Override - public BytesTracePcodeExecutorState fork() { - return new BytesTracePcodeExecutorState(piece.fork()); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java deleted file mode 100644 index c4e08694c5..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java +++ /dev/null @@ -1,214 +0,0 @@ -/* ### - * 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.exec.trace; - -import java.nio.ByteBuffer; -import java.util.Map; - -import generic.ULongSpan; -import generic.ULongSpan.ULongSpanSet; -import ghidra.generic.util.datastruct.SemisparseByteArray; -import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece; -import ghidra.pcode.exec.BytesPcodeExecutorStateSpace; -import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Language; -import ghidra.util.MathUtilities; - -/** - * A state piece which reads bytes from a trace, but caches writes internally. - * - *

- * This provides for "read-only" emulation on a trace. Writes do not affect the source trace, but - * rather are cached in this state. If desired, those cached writes can be written back out at a - * later time. - */ -public class BytesTracePcodeExecutorStatePiece - extends AbstractBytesPcodeExecutorStatePiece - implements TracePcodeExecutorStatePiece { - - protected static class CachedSpace - extends BytesPcodeExecutorStateSpace { - protected final AddressSet written; - - public CachedSpace(Language language, AddressSpace space, PcodeTraceDataAccess backing) { - // Backing could be null, so we need language parameter - super(language, space, backing); - this.written = new AddressSet(); - } - - protected CachedSpace(Language language, AddressSpace space, PcodeTraceDataAccess backing, - SemisparseByteArray bytes, AddressSet written) { - super(language, space, backing, bytes); - this.written = written; - } - - @Override - public CachedSpace fork() { - return new CachedSpace(language, space, backing, bytes.fork(), new AddressSet(written)); - } - - @Override - public void write(long offset, byte[] val, int srcOffset, int length) { - super.write(offset, val, srcOffset, length); - Address loc = space.getAddress(offset); - Address end = loc.addWrap(length - 1); - if (loc.compareTo(end) <= 0) { - written.add(loc, end); - } - else { - written.add(loc, space.getMaxAddress()); - written.add(space.getMinAddress(), end); - } - } - - protected AddressSetView intersectViewKnown(AddressSetView set) { - return backing.intersectViewKnown(set, true); - } - - @Override - protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) { - if (uninitialized.isEmpty()) { - return uninitialized; - } - // TODO: Warn or bail when reading UNKNOWN bytes - // NOTE: Read without regard to gaps - // NOTE: Cannot write those gaps, though!!! - AddressSetView knownButUninit = intersectViewKnown(addrSet(uninitialized)); - if (knownButUninit.isEmpty()) { - return uninitialized; - } - AddressRange knownBound = new AddressRangeImpl( - knownButUninit.getMinAddress(), - knownButUninit.getMaxAddress()); - ByteBuffer buf = ByteBuffer.allocate((int) knownBound.getLength()); - backing.getBytes(knownBound.getMinAddress(), buf); - for (AddressRange range : knownButUninit) { - bytes.putData(range.getMinAddress().getOffset(), buf.array(), - (int) (range.getMinAddress().subtract(knownBound.getMinAddress())), - (int) range.getLength()); - } - ULongSpan uninitBound = uninitialized.bound(); - return bytes.getUninitialized(uninitBound.min(), uninitBound.max()); - } - - protected void warnUnknown(AddressSetView unknown) { - warnAddressSet("Emulator state initialized from UNKNOWN", unknown); - } - - // Must already have started a transaction - protected void writeDown(PcodeTraceDataAccess into) { - if (space.isUniqueSpace()) { - return; - } - byte[] data = new byte[4096]; - ByteBuffer buf = ByteBuffer.wrap(data); - for (AddressRange range : written) { - long lower = range.getMinAddress().getOffset(); - long fullLen = range.getLength(); - while (fullLen > 0) { - int len = MathUtilities.unsignedMin(data.length, fullLen); - bytes.getData(lower, data, 0, len); - buf.position(0); - buf.limit(len); - into.putBytes(space.getAddress(lower), buf); - - lower += len; - fullLen -= len; - } - } - } - } - - protected final PcodeTraceDataAccess data; - - /** - * Create a concrete state piece backed by a trace - * - * @param data the trace-data access shim - */ - public BytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - super(data.getLanguage()); - this.data = data; - } - - protected BytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data, - AbstractSpaceMap spaceMap) { - super(data.getLanguage(), spaceMap); - this.data = data; - } - - @Override - public PcodeTraceDataAccess getData() { - return data; - } - - @Override - public BytesTracePcodeExecutorStatePiece fork() { - return new BytesTracePcodeExecutorStatePiece(data, spaceMap.fork()); - } - - @Override - public void writeDown(PcodeTraceDataAccess into) { - if (into.getLanguage() != language) { - throw new IllegalArgumentException( - "Destination platform must be same language as source"); - } - for (CachedSpace cached : spaceMap.values()) { - cached.writeDown(into); - } - } - - /** - * A space map which binds spaces to corresponding spaces in the trace - */ - protected class TraceBackedSpaceMap - extends CacheingSpaceMap { - public TraceBackedSpaceMap() { - super(); - } - - protected TraceBackedSpaceMap(Map spaces) { - super(spaces); - } - - @Override - protected PcodeTraceDataAccess getBacking(AddressSpace space) { - return data; - } - - @Override - protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) { - return new CachedSpace(language, space, backing); - } - - @Override - public TraceBackedSpaceMap fork() { - return new TraceBackedSpaceMap(fork(spaces)); - } - - @Override - public CachedSpace fork(CachedSpace s) { - return s.fork(); - } - } - - @Override - protected AbstractSpaceMap newSpaceMap() { - return new TraceBackedSpaceMap(); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java deleted file mode 100644 index 6b2dfe9e75..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java +++ /dev/null @@ -1,56 +0,0 @@ -/* ### - * 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.exec.trace; - -import ghidra.pcode.exec.DefaultPcodeExecutorState; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; - -/** - * An adapter that implements {@link TracePcodeExecutorState} given a - * {@link TracePcodeExecutorStatePiece} whose address and value types already match - * - * @param the type of values - */ -public class DefaultTracePcodeExecutorState extends DefaultPcodeExecutorState - implements TracePcodeExecutorState { - - protected final TracePcodeExecutorStatePiece piece; - - /** - * Wrap a state piece - * - * @param piece the piece - */ - public DefaultTracePcodeExecutorState(TracePcodeExecutorStatePiece piece) { - super(piece); - this.piece = piece; - } - - @Override - public PcodeTraceDataAccess getData() { - return piece.getData(); - } - - @Override - public DefaultTracePcodeExecutorState fork() { - return new DefaultTracePcodeExecutorState<>(piece.fork()); - } - - @Override - public void writeDown(PcodeTraceDataAccess into) { - piece.writeDown(into); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java deleted file mode 100644 index f77f970d48..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java +++ /dev/null @@ -1,96 +0,0 @@ -/* ### - * 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.exec.trace; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.pcode.exec.PairedPcodeExecutorState; -import ghidra.pcode.exec.PcodeExecutorState; -import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; -import ghidra.trace.model.Trace; -import ghidra.trace.model.guest.TracePlatform; -import ghidra.trace.model.memory.TraceMemoryState; -import ghidra.trace.model.thread.TraceThread; - -/** - * A state composing a single {@link DirectBytesTracePcodeExecutorStatePiece} - * - * @see TraceSleighUtils - */ -public class DirectBytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState { - - /** - * Get a trace-data access shim suitable for evaluating Sleigh expressions with thread context - * - *

- * Do not use the returned shim for emulation, but only for one-off p-code execution, e.g., - * Sleigh expression evaluation. - * - * @param platform the platform whose language and address mappings to use - * @param snap the source snap - * @param thread the thread for register context - * @param frame the frame for register context, 0 if not applicable - * @return the trace-data access shim - */ - public static PcodeTraceDataAccess getDefaultThreadAccess(TracePlatform platform, long snap, - TraceThread thread, int frame) { - return new DefaultPcodeTraceAccess(platform, snap).getDataForThreadState(thread, frame); - } - - /** - * Create the state - * - * @param data the trace-data access shim - */ - public DirectBytesTracePcodeExecutorState(PcodeTraceDataAccess data) { - super(new DirectBytesTracePcodeExecutorStatePiece(data)); - } - - protected DirectBytesTracePcodeExecutorState( - TracePcodeExecutorStatePiece piece) { - super(piece); - } - - /** - * Create the state - * - * @param platform the platform whose language and address mappings to use - * @param snap the snap the executor will access - * @param thread the thread for reading and writing registers - * @param frame the frame for reading and writing registers - */ - public DirectBytesTracePcodeExecutorState(TracePlatform platform, long snap, TraceThread thread, - int frame) { - this(getDefaultThreadAccess(platform, snap, thread, frame)); - } - - /** - * Pair this state with an auxiliary {@link TraceMemoryState} piece - * - * @return the new state, composing this state with the new piece - * @see TraceSleighUtils#buildByteWithStateExecutor(Trace, long, TraceThread, int) - */ - public PcodeExecutorState> withMemoryState() { - return new PairedPcodeExecutorState<>(this, - new TraceMemoryStatePcodeExecutorStatePiece(getData())); - } - - @Override - public DirectBytesTracePcodeExecutorState fork() { - return new DirectBytesTracePcodeExecutorState(piece.fork()); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java deleted file mode 100644 index 7e5fa46de3..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java +++ /dev/null @@ -1,166 +0,0 @@ -/* ### - * 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.exec.trace; - -import java.nio.ByteBuffer; -import java.util.List; -import java.util.Map; - -import javax.help.UnsupportedOperationException; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.generic.util.datastruct.SemisparseByteArray; -import ghidra.pcode.exec.*; -import ghidra.pcode.exec.PcodeArithmetic.Purpose; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.lang.Register; -import ghidra.program.model.mem.MemBuffer; -import ghidra.trace.model.memory.TraceMemoryState; - -/** - * An executor state piece that operates directly on trace memory and registers - * - *

- * This differs from {@link BytesTracePcodeExecutorStatePiece} in that writes performed by the - * emulator immediately affect the trace. There is no caching. In effect, the trace is the - * state. This is used primarily in testing to initialize trace state using Sleigh, which is more - * succinct than accessing trace memory and registers via the trace API. It may also be incorporated - * into the UI at a later time. - * - * @see TraceSleighUtils - */ -public class DirectBytesTracePcodeExecutorStatePiece - extends AbstractLongOffsetPcodeExecutorStatePiece - implements TracePcodeExecutorStatePiece { - - protected final PcodeTraceDataAccess data; - - protected final SemisparseByteArray unique; - - /** - * Construct a piece - * - * @param arithmetic the arithmetic for byte arrays - * @param data the trace-data access shim - */ - protected DirectBytesTracePcodeExecutorStatePiece(PcodeArithmetic arithmetic, - PcodeTraceDataAccess data, SemisparseByteArray unique) { - super(data.getLanguage(), arithmetic, arithmetic); - this.data = data; - this.unique = unique; - } - - /** - * Construct a piece - * - * @param data the trace-data access shim - */ - public DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data, new SemisparseByteArray()); - } - - @Override - public PcodeTraceDataAccess getData() { - return data; - } - - @Override - public DirectBytesTracePcodeExecutorStatePiece fork() { - return new DirectBytesTracePcodeExecutorStatePiece(arithmetic, data, unique.fork()); - } - - /** - * Create a state which computes an expression's {@link TraceMemoryState} as an auxiliary - * attribute - * - *

- * If every part of every input to the expression is {@link TraceMemoryState#KNOWN}, then the - * expression's value will be marked {@link TraceMemoryState#KNOWN}. Otherwise, it's marked - * {@link TraceMemoryState#UNKNOWN}. - * - * @return the paired executor state - */ - public PcodeExecutorStatePiece> withMemoryState() { - return new PairedPcodeExecutorStatePiece<>(this, - new TraceMemoryStatePcodeExecutorStatePiece(data)); - } - - @Override - protected void setUnique(long offset, int size, byte[] val) { - assert size == val.length; - unique.putData(offset, val); - } - - @Override - protected byte[] getUnique(long offset, int size, Reason reason) { - byte[] data = new byte[size]; - unique.getData(offset, data); - return data; - } - - @Override - protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) { - return space; - } - - @Override - protected void setInSpace(AddressSpace space, long offset, int size, byte[] val) { - assert size == val.length; - int wrote = data.putBytes(space.getAddress(offset), ByteBuffer.wrap(val)); - if (wrote != size) { - throw new RuntimeException("Could not write full value to trace"); - } - } - - @Override - protected byte[] getFromSpace(AddressSpace space, long offset, int size, Reason reason) { - ByteBuffer buf = ByteBuffer.allocate(size); - int read = data.getBytes(space.getAddress(offset), buf); - if (read != size) { - throw new RuntimeException("Could not read full value from trace"); - } - return buf.array(); - } - - @Override - protected Map getRegisterValuesFromSpace(AddressSpace s, - List registers) { - return Map.of(); - } - - @Override - public Map getRegisterValues() { - return Map.of(); - } - - @Override - public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { - throw new UnsupportedOperationException(); - } - - @Override - public void writeDown(PcodeTraceDataAccess into) { - // Writes directly, so just ignore - } - - @Override - public void clear() { - unique.clear(); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/IndependentPairedTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/IndependentPairedTracePcodeExecutorState.java deleted file mode 100644 index 12a6bdc308..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/IndependentPairedTracePcodeExecutorState.java +++ /dev/null @@ -1,60 +0,0 @@ -/* ### - * 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.exec.trace; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.pcode.exec.IndependentPairedPcodeExecutorState; -import ghidra.pcode.exec.PairedPcodeExecutorState; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; - -/** - * A trace-bound state composed of another trace-bound state and a piece - * - * @param the type of values for the left state - * @param the type of values for the right piece - * @see PairedPcodeExecutorState - */ -public class IndependentPairedTracePcodeExecutorState - extends IndependentPairedPcodeExecutorState - implements TracePcodeExecutorState> { - - private final TracePcodeExecutorStatePiece left; - private final TracePcodeExecutorStatePiece right; - - public IndependentPairedTracePcodeExecutorState(TracePcodeExecutorStatePiece left, - TracePcodeExecutorStatePiece right) { - super(left, right); - this.left = left; - this.right = right; - } - - @Override - public PcodeTraceDataAccess getData() { - return left.getData(); - } - - @Override - public void writeDown(PcodeTraceDataAccess into) { - left.writeDown(into); - right.writeDown(into); - } - - @Override - public IndependentPairedTracePcodeExecutorState fork() { - return new IndependentPairedTracePcodeExecutorState<>(left.fork(), right.fork()); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java deleted file mode 100644 index 8ad244b217..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java +++ /dev/null @@ -1,59 +0,0 @@ -/* ### - * 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.exec.trace; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.pcode.exec.PairedPcodeExecutorState; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; - -/** - * A trace-bound state composed of another trace-bound state and a piece - * - * @param the type of values for the left state - * @param the type of values for the right piece - * @see PairedPcodeExecutorState - */ -public class PairedTracePcodeExecutorState extends PairedPcodeExecutorState - implements TracePcodeExecutorState> { - - private final PairedTracePcodeExecutorStatePiece piece; - - public PairedTracePcodeExecutorState(PairedTracePcodeExecutorStatePiece piece) { - super(piece); - this.piece = piece; - } - - public PairedTracePcodeExecutorState(TracePcodeExecutorState left, - TracePcodeExecutorStatePiece right) { - this(new PairedTracePcodeExecutorStatePiece<>(left, right)); - } - - @Override - public PcodeTraceDataAccess getData() { - return piece.getData(); - } - - @Override - public PairedTracePcodeExecutorState fork() { - return new PairedTracePcodeExecutorState<>(piece.fork()); - } - - @Override - public void writeDown(PcodeTraceDataAccess into) { - piece.writeDown(into); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java deleted file mode 100644 index ec9fdd5aaa..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java +++ /dev/null @@ -1,80 +0,0 @@ -/* ### - * 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.exec.trace; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.pcode.exec.PairedPcodeExecutorStatePiece; -import ghidra.pcode.exec.PcodeArithmetic; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; - -/** - * A trace-bound state piece composed of two other trace-bound pieces sharing the same address type - * - * @see PairedPcodeExecutorStatePiece - * @param the type of addresses - * @param the type of values for the left piece - * @param the type of values for the right piece - */ -public class PairedTracePcodeExecutorStatePiece - extends PairedPcodeExecutorStatePiece - implements TracePcodeExecutorStatePiece> { - - protected final TracePcodeExecutorStatePiece left; - protected final TracePcodeExecutorStatePiece right; - - public PairedTracePcodeExecutorStatePiece(TracePcodeExecutorStatePiece left, - TracePcodeExecutorStatePiece right) { - super(left, right); - this.left = left; - this.right = right; - } - - public PairedTracePcodeExecutorStatePiece(TracePcodeExecutorStatePiece left, - TracePcodeExecutorStatePiece right, PcodeArithmetic addressArithmetic, - PcodeArithmetic> arithmetic) { - super(left, right, addressArithmetic, arithmetic); - this.left = left; - this.right = right; - } - - @Override - public PcodeTraceDataAccess getData() { - return left.getData(); - } - - @Override - public PairedTracePcodeExecutorStatePiece fork() { - return new PairedTracePcodeExecutorStatePiece<>(left.fork(), right.fork(), - getAddressArithmetic(), getArithmetic()); - } - - @Override - public void writeDown(PcodeTraceDataAccess into) { - left.writeDown(into); - right.writeDown(into); - } - - @Override - public TracePcodeExecutorStatePiece getLeft() { - return left; - } - - @Override - public TracePcodeExecutorStatePiece getRight() { - return right; - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceEmulationIntegration.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceEmulationIntegration.java new file mode 100644 index 0000000000..4fc58d32bd --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceEmulationIntegration.java @@ -0,0 +1,802 @@ +/* ### + * 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.exec.trace; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import ghidra.generic.util.datastruct.SemisparseByteArray; +import ghidra.pcode.emu.*; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.trace.data.*; +import ghidra.program.model.address.*; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.MathUtilities; +import ghidra.util.Msg; + +/** + * A collection of static methods for integrating an emulator with a trace. + */ +public enum TraceEmulationIntegration { + ; + + /** + * Create a writer (callbacks) that lazily loads data from the given access shim. + * + *

+ * Writes are logged, but not written to the trace. Instead, the client should call + * {@link Writer#writeDown(PcodeTraceAccess)} to write the logged changes to another given + * snapshot. This is used for forking emulation from a chosen snapshot and saving the results + * into (usually scratch) snapshots. Scripts might also use this pattern to save a series of + * snapshots resulting from an emulation experiment. + * + * @param from the access shim for lazy loads + * @return the writer + */ + public static Writer bytesDelayedWrite(PcodeTraceAccess from) { + Writer writer = new TraceWriter(from); + writer.putHandler(new BytesPieceHandler()); + return writer; + } + + /** + * Create a writer (callbacks) that lazily loads data and immediately writes changes to the + * given access shim. + * + *

+ * Writes are immediately stored into the trace at the same snapshot as state is sourced. + * + * @param access the access shim for loads and stores + * @return the writer + */ + public static Writer bytesImmediateWrite(PcodeTraceAccess access) { + Writer writer = new TraceWriter(access); + writer.putHandler(new ImmediateBytesPieceHandler()); + return writer; + } + + /** + * Create state callbacks that lazily load data and immediately write changes to the given + * access shim. + * + *

+ * Writes are immediately stored into the trace at the same snapshot as state is sourced. + * + *

+ * Use this instead of {@link #bytesImmediateWrite(PcodeTraceAccess)} when interfacing directly + * with a {@link PcodeExecutorState} vice a {@link PcodeEmulator}. + * + * @param access the access shim for loads and stores + * @param thread the trace thread for register accesses + * @param frame the frame for register accesses, usually 0 + * @return the callbacks + */ + public static PcodeStateCallbacks bytesImmediateWrite(PcodeTraceAccess access, + TraceThread thread, int frame) { + Writer writer = new TraceWriter(access) { + @Override + protected PcodeTraceRegistersAccess getRegAccess(PcodeThread ignored) { + return access.getDataForLocalState(thread, frame); + } + }; + writer.putHandler(new ImmediateBytesPieceHandler()); + return writer.wrapFor(null); + } + + /** + * The key when selecting a handler for a given piece: (address-domain, value-domain) + * + * @param the address domain + * @param the value domain + */ + record PieceType(Class addressDomain, Class valueDomain) { + /** + * Get the key for a given piece + * + * @param the address domain + * @param the value domain + * @param piece the piece + * @return the key + */ + public static PieceType forPiece(PcodeExecutorStatePiece piece) { + return new PieceType<>(piece.getAddressArithmetic().getDomain(), + piece.getArithmetic().getDomain()); + } + + /** + * Get the key for a given handler + * + * @param the address domain + * @param the value domain + * @param handler the handler + * @return the key + */ + public static PieceType forHandler(PieceHandler handler) { + return new PieceType<>(handler.getAddressDomain(), handler.getValueDomain()); + } + } + + /** + * The primary mechanism for integrating emulators and traces + * + *

+ * This implements callbacks for the emulator and provides a method for recording logged writes + * after some number of emulation steps. The client must pass this writer in as the callbacks + * and then later invoke {@link #writeDown(PcodeTraceAccess)}. This also permits the addition of + * state piece handlers via {@link #putHandler(PieceHandler)}, should the emulator be operating + * on other value domains. + */ + public interface Writer extends PcodeEmulationCallbacks { + /** + * Record state changes into the trace via the given access shim + * + * @param into the access shim + */ + void writeDown(PcodeTraceAccess into); + + /** + * Record state changes into the trace at the given snapshot. + * + *

+ * The destination trace is the same as from the source access shim. + * + * @param snap the destination snapshot key + */ + void writeDown(long snap); + + /** + * Add or replace a handler + * + *

+ * The handler must identify the address and value domains for which it is applicable. If + * there is already a handler for the same domains, the old handler is replaced by this one. + * Otherwise, this handler is added without removing any others. The handler is invoked if + * and only if the emulator's state contains a piece for the same domains. Otherwise, the + * handler may be silently ignored. + * + * @param handler the handler + */ + void putHandler(PieceHandler handler); + + /** + * Cast this writer to fit the emulator's value domain + * + *

+ * Use this as the callbacks parameter when constructing the trace-integrated emulator. We + * assert this cast is safe, because none of the callbacks actually depend on the emulator's + * value domain. Instead, the states are accessed generically and invocations doled out to + * respective {@link PieceHandler}s based on their applicable domain types. + * + * @param the emulator's value domain + * @return this + */ + @SuppressWarnings("unchecked") + default PcodeEmulationCallbacks callbacks() { + return (PcodeEmulationCallbacks) this; + } + } + + /** + * The handler for a specific piece within an emulator's (or executor's) state. + * + * @see PcodeExecutorStatePiece + * @param the address domain of pieces this can handle + * @param the value domain of pieces this can handle + */ + public interface PieceHandler { + /** A handler that does nothing */ + public static PieceHandler NONE = VoidPieceHandler.INSTANCE; + + /** + * Get the address domain this can handle + * + * @return the address domain + */ + Class getAddressDomain(); + + /** + * Get the value domain this can handle + * + * @return the value domain + */ + Class getValueDomain(); + + /** + * An uninitialized portion of a state piece is being read (concrete addressing). + * + * @param acc the trace access shim for the relevant state (shared or local) + * @param thread the thread, if applicable. This is null if either the state being accessed + * is the emulator's shared state, or if the state is bound to a plain + * {@link PcodeExecutor}. + * @param piece the state piece being handled + * @param set the uninitialized portion required + * @return the addresses in {@code set} that remain uninitialized + * @see PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece, + * AddressSetView) + */ + AddressSetView readUninitialized(PcodeTraceDataAccess acc, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView set); + + /** + * An uninitialized portion of a state piece is being read (abstract addressing). + * + * @param acc the trace access shim for the relevant state (shared or local) + * @param thread the thread, if applicable. This is null if either the state being accessed + * is the emulator's shared state, or if the state is bound to a plain + * {@link PcodeExecutor}. + * @param piece the state piece being handled + * @param space the address space + * @param offset the offset at the start of the uninitialized portion + * @param length the size in bytes of the uninitialized portion + * @return the number of bytes just initialized, typically 0 or {@code length} + * @see PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece, + * AddressSpace, Object, int) + */ + default int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, A offset, int length) { + return 0; + } + + /** + * Data was written (concrete addressing). + * + * @param acc the trace access shim for the relevant state (shared or local) + * @param written the {@link Writer}'s current log of written addresses (mutable). + * Typically, this is not accessed but rather passed to delegate methods. + * @param thread the thread, if applicable. This is null if either the state being accessed + * is the emulator's shared state, or if the state is bound to a plain + * {@link PcodeExecutor}. + * @param piece the state piece being handled + * @param address the start address of the write + * @param length the size in bytes of the write + * @param value the value written + * @return true to prevent the {@link Writer} from updating its log. + * @see PcodeEmulationCallbacks#dataWritten(PcodeThread, PcodeExecutorStatePiece, Address, + * int, Object) + */ + default boolean dataWritten(PcodeTraceDataAccess acc, AddressSet written, + PcodeThread thread, PcodeExecutorStatePiece piece, Address address, + int length, T value) { + return false; + } + + /** + * Data was written (abstract addressing). + * + * @param acc the trace access shim for the relevant state (shared or local) + * @param written the {@link Writer}'s current log of written addresses (mutable). + * Typically, this is not accessed but rather passed to delegate methods. + * @param thread the thread, if applicable. This is null if either the state being accessed + * is the emulator's shared state, or if the state is bound to a plain + * {@link PcodeExecutor}. + * @param piece the state piece being handled + * @param space the address space + * @param offset the offset of the start of the write + * @param length the size in bytes of the write + * @param value the value written + * @see PcodeEmulationCallbacks#dataWritten(PcodeThread, PcodeExecutorStatePiece, + * AddressSpace, Object, int, Object) + */ + default void abstractWritten(PcodeTraceDataAccess acc, AddressSet written, + PcodeThread thread, PcodeExecutorStatePiece piece, AddressSpace space, + A offset, int length, T value) { + } + + /** + * Serialize a given portion of the state to the trace database. + * + *

+ * The "given portion" refers to the address set provided in {@code written}. Pieces may + * also have state assigned to abstract addresses. In such cases, it is up to the handler to + * track what has been written. + * + * @param into the destination trace access + * @param thread the thread associated with the piece's state + * @param piece the source state piece + * @param written the portion that is known to have been written + */ + void writeDown(PcodeTraceDataAccess into, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView written); + } + + /** + * An implementation of {@link PieceHandler} that does nothing. + * + * @implNote This is the object returned when a handler is not found for a given piece. It + * removes the need for a null check. + */ + private enum VoidPieceHandler implements PieceHandler { + /** The handler that does nothing */ + INSTANCE; + + @Override + public Class getAddressDomain() { + return Void.class; + } + + @Override + public Class getValueDomain() { + return Void.class; + } + + @Override + public AddressSetView readUninitialized(PcodeTraceDataAccess acc, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView set) { + return set; + } + + @Override + public void writeDown(PcodeTraceDataAccess into, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView written) { + } + } + + /** + * A handler that implements the lazy-read-writer-later pattern of trace integration for a + * concrete emulator's bytes. + */ + public static class BytesPieceHandler implements PieceHandler { + /** + * The maximum number of bytes to buffer at a time + */ + public static final int CHUNK_SIZE = 4096; + + @Override + public Class getAddressDomain() { + return byte[].class; + } + + @Override + public Class getValueDomain() { + return byte[].class; + } + + @Override + public AddressSetView readUninitialized(PcodeTraceDataAccess acc, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView set) { + // NOTE: For simplicity, read without regard to gaps + // NOTE: We cannot write those gaps, though!!! + AddressSetView knownButUninit = acc.intersectViewKnown(set, true); + if (knownButUninit.isEmpty()) { + return set; + } + AddressSet remains = new AddressSet(set); + AddressRange knownBound = new AddressRangeImpl( + knownButUninit.getMinAddress(), + knownButUninit.getMaxAddress()); + ByteBuffer buf = ByteBuffer.allocate((int) knownBound.getLength()); + acc.getBytes(knownBound.getMinAddress(), buf); + for (AddressRange range : knownButUninit) { + piece.setVarInternal(range.getAddressSpace(), range.getMinAddress().getOffset(), + (int) range.getLength(), buf.array()); + remains.delete(range); + } + return remains; + } + + @Override + public void writeDown(PcodeTraceDataAccess into, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView written) { + for (AddressRange range : written) { + AddressSpace space = range.getAddressSpace(); + if (space.isUniqueSpace()) { + continue; + } + long lower = range.getMinAddress().getOffset(); + long fullLen = range.getLength(); + while (fullLen > 0) { + int len = MathUtilities.unsignedMin(CHUNK_SIZE, fullLen); + // NOTE: Would prefer less copying and less heap garbage.... + byte[] bytes = piece.getVarInternal(space, lower, len, Reason.INSPECT); + into.putBytes(space.getAddress(lower), ByteBuffer.wrap(bytes)); + + lower += bytes.length; + fullLen -= bytes.length; + } + } + } + } + + /** + * A handler that implements the lazy-read-write-immediately pattern of trace integration for a + * concrete emulator's bytes. + */ + public static class ImmediateBytesPieceHandler extends BytesPieceHandler { + @Override + public boolean dataWritten(PcodeTraceDataAccess acc, AddressSet written, + PcodeThread thread, PcodeExecutorStatePiece piece, + Address address, int length, byte[] value) { + if (address.isUniqueAddress()) { + return true; + } + acc.putBytes(address, ByteBuffer.wrap(value)); + return true; // Avoid any delayed write + } + } + + /** + * An abstract implementation of {@link PieceHandler} that seeks to simplify integration of + * abstract domains where the state is serialized into a trace's property map. + * + *

+ * Generally, such abstract domains should follow a byte-wise access pattern. That is, it should + * be capable of reading and writing to overlapping variables. This implementation is aimed at + * that pattern. The state piece will need to implement at least + * {@link PcodeExecutorStatePiece#getNextEntryInternal(AddressSpace, long)}. Each state entry + * should be serialized as an entry at the same address and size in the property map. + * Uninitialized reads should search the full range for any applicable entries. Entries may need + * to be subpieced, depending on what part of the state is already initialized. + * + *

+ * If the address domain is also abstract, the recommended pattern is to attempt to concretize + * it (see {@link PcodeArithmetic#toAddress(Object, AddressSpace, Purpose)}) and delegate to the + * concrete callback. Failing that, you must choose some other means of storing the state. Our + * current recommendation is to use {@link Address#NO_ADDRESS} in a string map, where you can + * serialize any number of (address, value) pairs. This will not work for thread-local states, + * but it is unlikely you should encounter non-concretizable addresses in a thread-local state. + * + * @param the address domain + * @param the value domain + * @param

the type of values in the property map, often {@link String} + */ + public static abstract class AbstractPropertyBasedPieceHandler + implements PieceHandler { + + /** + * Get the name of the property map. + * + *

+ * This should be unique among all possible domains. Nor should it collide with map names + * used for other purposes. + */ + protected abstract String getPropertyName(); + + /** + * Get the type of values in the property map + * + * @return the type, often {@link String}{@code .class} + */ + protected abstract Class

getPropertyType(); + + /** + * Decode a property entry and set appropriate variable(s) in the piece + *

+ * The found property entry may cover more addresses than are actually required, either + * because they've not been requested or because the value has already been set. Writing a + * value that wasn't requested isn't too bad, but writing one that was already initialized + * could be catastrophic. + * + * @param piece the piece with uninitialized variables to decode from a property + * @param limit the portion that is requested and uninitialized. DO NOT initialize + * any address outside of this set. + * @param range the range covered by the found property entry + * @param propertyValue the value of the property entry + */ + protected abstract void decodeFrom(PcodeExecutorStatePiece piece, + AddressSetView limit, AddressRange range, P propertyValue); + + /** + * Encode a variable's value into a property entry + * + * @param property the property map (access shim) + * @param range the variable's address range (this may optionally be coalesced from several + * variables by the piece's internals) + * @param value the variable's value + */ + protected abstract void encodeInto(PcodeTracePropertyAccess

property, AddressRange range, + T value); + + @Override + public AddressSetView readUninitialized(PcodeTraceDataAccess acc, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView set) { + PcodeTracePropertyAccess

property = + acc.getPropertyAccess(getPropertyName(), getPropertyType()); + AddressSet remains = new AddressSet(set); + AddressSet cursor = new AddressSet(set); + boolean result = false; + while (!cursor.isEmpty()) { + Address cur = cursor.getMinAddress(); + Entry entry = property.getEntry(cur); + if (entry == null) { + // Not the most efficient.... + cursor.delete(cur, cur); + continue; + } + AddressRange physical = new AddressRangeImpl( + entry.getKey().getMinAddress().getPhysicalAddress(), + entry.getKey().getMaxAddress().getPhysicalAddress()); + decodeFrom(piece, set, physical, entry.getValue()); + result = true; + remains.delete(physical); + cursor.delete(physical); + } + return result ? remains : set; + } + + /** + * {@inheritDoc} + *

+ * This should be overridden by developers needing to store abstract state into the trace. + * Conventionally, if the address cannot be made concrete (see + * {@link PcodeArithmetic#toLong(Object, Purpose)}), then it should be stored at + * {@link Address#NO_ADDRESS}. It is up to the developer to determine how to (de)serialize + * all of the abstract states. + */ + @Override + public int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, A offset, int length) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + *

+ * This method handles serializing the concrete portion and associating the states to their + * respective addresses in the property. Handlers needing to serialize abstracts portions + * must both implement the means of tracking what has been written (see + * {@link #abstractWritten(PcodeTraceDataAccess, AddressSet, PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)}), + * and the placement of that state information into the property. The latter is accomplished + * by overriding this method, taking care to invoke the super method for the concrete + * portion. + */ + @Override + public void writeDown(PcodeTraceDataAccess into, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView written) { + PcodeTracePropertyAccess

property = + into.getPropertyAccess(getPropertyName(), getPropertyType()); + AddressSet remains = new AddressSet(written); + while (!remains.isEmpty()) { + Address cur = remains.getMinAddress(); + AddressSpace space = cur.getAddressSpace(); + Entry entry = piece.getNextEntryInternal(space, cur.getOffset()); + if (entry == null) { + remains.delete(space.getMinAddress(), space.getMaxAddress()); + continue; + } + if (Long.compareUnsigned(entry.getKey(), cur.getOffset()) < 0) { + Msg.error(this, "getNextEntryInternal return an incorrect entry."); + remains.delete(space.getMinAddress(), space.getMaxAddress()); + continue; + } + Address min = space.getAddress(entry.getKey()); + AddressRange range = new AddressRangeImpl(min, + min.add(piece.getArithmetic().sizeOf(entry.getValue()) - 1)); + encodeInto(property, range, entry.getValue()); + + // Delete everything preceding and including the range, within the same space + remains.delete(space.getMinAddress(), range.getMaxAddress()); + } + } + + @Override + public void abstractWritten(PcodeTraceDataAccess acc, AddressSet written, + PcodeThread thread, PcodeExecutorStatePiece piece, AddressSpace space, + A offset, int length, T value) { + throw new UnsupportedOperationException(); + } + } + + /** + * A misguided simplification of {@link AbstractPropertyBasedPieceHandler} that reduces the + * requirement to a simple codec. + * + *

+ * For cases where subpiecing of variables is not of concern, this simplification may suffice. + * This is usually okay for proofs of concept or very simplistic architectures. However, once + * you introduce structured/aliased registers (e.g., {@code EAX} is the lower 32 bits of + * {@code RAX}), or you're dealing with off-cut memory references, you have to deal with + * subpiecing and this simplification is no longer viable. + * + * @param the address domain of the piece + * @param the value domain of the piece + * @param

the type of the property map + */ + public static abstract class AbstractSimplePropertyBasedPieceHandler + extends AbstractPropertyBasedPieceHandler { + + /** + * Decode a state value from the given property value + * + * @param propertyValue the property value + * @return the decoded state value + */ + protected abstract T decode(P propertyValue); + + @Override + protected void decodeFrom(PcodeExecutorStatePiece piece, AddressSetView limit, + AddressRange range, P propertyValue) { + piece.setVarInternal(range.getAddressSpace(), range.getMinAddress().getOffset(), + (int) range.getLength(), decode(propertyValue)); + } + + /** + * Encode a state value into a property value + * + * @param value the state value + * @return the encoded property value + */ + protected abstract P encode(T value); + + @Override + protected void encodeInto(PcodeTracePropertyAccess

property, AddressRange range, + T value) { + property.put(range, encode(value)); + } + } + + /** + * The implementation of {@link Writer} for traces. + * + *

+ * The interface is already somewhat trace-centric in that it requires + * {@link Writer#writeDown(PcodeTraceAccess)}, but those may technically do nothing (as is the + * case for the write-immediately implementations). NOTE: Perhaps we should replace the + * interface with this class (renamed to {@link Writer}). + */ + public static class TraceWriter implements Writer { + protected final PcodeTraceAccess access; + protected final PcodeTraceMemoryAccess memAccess; + protected final Map, PcodeTraceRegistersAccess> regAccess = new HashMap<>(); + + /** + * An address set to track what has actually been written. It's not enough to just use the + * {@link SemisparseByteArray}'s initialized set, as that may be caching bytes from the + * trace which are still {@link TraceMemoryState#UNKNOWN}. + */ + protected final AddressSet memWritten = new AddressSet(); + protected final Map, AddressSet> regsWritten = new HashMap<>(); + + protected final Map, PieceHandler> handlers = new HashMap<>(); + + private PcodeMachine emulator; + + /** + * Construct a writer which sources state from the given access shim + * + * @param access the source access shim + */ + public TraceWriter(PcodeTraceAccess access) { + this.access = access; + this.memAccess = access.getDataForSharedState(); + } + + @Override + public void putHandler(PieceHandler handler) { + handlers.put(PieceType.forHandler(handler), handler); + } + + @Override + public void emulatorCreated(PcodeMachine emulator) { + this.emulator = emulator; + } + + @Override + public void threadCreated(PcodeThread thread) { + access.getDataForLocalState(thread, 0).initializeThreadContext(thread); + } + + @SuppressWarnings("unchecked") + protected PieceHandler handlerFor(PcodeExecutorStatePiece piece) { + return (PieceHandler) handlers.getOrDefault(PieceType.forPiece(piece), + PieceHandler.NONE); + } + + /** + * Record the given piece's state into the trace + * + * @param the piece's address domain + * @param the piece's value domain + * @param into the destination trace access shim + * @param thread the thread, if applicable + * @param piece the piece + * @param written the logged portions written + */ + protected void writePieceDown(PcodeTraceDataAccess into, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView written) { + PieceHandler handler = handlerFor(piece); + handler.writeDown(into, thread, piece, written); + } + + @Override + public void writeDown(PcodeTraceAccess into) { + PcodeTraceMemoryAccess memInto = into.getDataForSharedState(); + for (PcodeExecutorStatePiece piece : emulator.getSharedState() + .streamPieces() + .toList()) { + writePieceDown(memInto, null, piece, memWritten); + } + for (PcodeThread thread : emulator.getAllThreads()) { + PcodeTraceRegistersAccess regInto = into.getDataForLocalState(thread, 0); + AddressSetView written = regsWritten.getOrDefault(thread, new AddressSet()); + for (PcodeExecutorStatePiece piece : thread.getState() + .streamPieces() + .toList()) { + writePieceDown(regInto, thread, piece, written); + } + } + } + + @Override + public void writeDown(long snap) { + writeDown(access.deriveForWrite(snap)); + } + + protected PcodeTraceRegistersAccess getRegAccess(PcodeThread thread) { + // Always use frame 0 + return regAccess.computeIfAbsent(thread, t -> access.getDataForLocalState(t, 0)); + } + + @Override + public void dataWritten(PcodeThread thread, + PcodeExecutorStatePiece piece, + Address address, int length, U value) { + PcodeTraceDataAccess acc = address.isRegisterAddress() + ? getRegAccess(thread) + : memAccess; + AddressSet written = address.isRegisterAddress() + ? regsWritten.computeIfAbsent(thread, t -> new AddressSet()) + : memWritten; + if (handlerFor(piece).dataWritten(acc, written, thread, piece, address, length, + value)) { + return; + } + Address end = address.addWrap(length - 1); + if (address.compareTo(end) <= 0) { + written.add(address, end); + } + else { + AddressSpace space = address.getAddressSpace(); + written.add(address, space.getMaxAddress()); + written.add(space.getMinAddress(), end); + } + } + + @Override + public void dataWritten(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, B offset, int length, + U value) { + PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess; + AddressSet written = space.isRegisterSpace() + ? regsWritten.computeIfAbsent(thread, t -> new AddressSet()) + : memWritten; + handlerFor(piece).abstractWritten(acc, written, thread, piece, space, offset, length, + value); + } + + @Override + public int readUninitialized(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, B offset, int length) { + PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess; + return handlerFor(piece).abstractReadUninit(acc, thread, piece, space, offset, length); + } + + @Override + public AddressSetView readUninitialized(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView set) { + if (set.isEmpty()) { + return set; + } + AddressSpace space = set.getMinAddress().getAddressSpace(); + PcodeTraceDataAccess acc = space.isRegisterSpace() ? getRegAccess(thread) : memAccess; + return handlerFor(piece).readUninitialized(acc, thread, piece, set); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeArithmetic.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeArithmetic.java index 2293d8107d..f2129fab63 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeArithmetic.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeArithmetic.java @@ -38,6 +38,11 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic getDomain() { + return TraceMemoryState.class; + } + @Override public Endian getEndian() { return null; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java index 7b700d1e90..343e79c987 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java @@ -33,14 +33,16 @@ import ghidra.trace.model.memory.TraceMemoryState; * The p-code execute state piece for {@link TraceMemoryState} * *

- * This state piece is meant to be used as an auxiliary to a concrete trace-bound state. See - * {@link DirectBytesTracePcodeExecutorState#withMemoryState()}. It should be used with - * {@link TraceMemoryStatePcodeArithmetic} as a means of computing the "state" of a Sleigh - * expression's value. It essentially works like a rudimentary taint analyzer: If any part of any - * input to the expression in tainted, i.e., not {@link TraceMemoryState#KNOWN}, then the result is - * {@link TraceMemoryState#UNKNOWN}. This is best exemplified in - * {@link #getUnique(long, int, Reason)}, though it's also exemplified in - * {@link #getFromSpace(AddressSpace, long, int, Reason)}. + * This state piece is meant to be used as an auxiliary to a concrete trace-bound state. It should + * be used with {@link TraceMemoryStatePcodeArithmetic} as a means of computing the "state" of a + * Sleigh expression's value. It essentially works like a rudimentary taint analyzer: If any part of + * any input to the expression in tainted, i.e., not {@link TraceMemoryState#KNOWN}, then the result + * is {@link TraceMemoryState#UNKNOWN}. This is best exemplified in + * {@link #getUnique(long, int, Reason, PcodeStateCallbacks)}, though it's also exemplified in + * {@link #getFromSpace(AddressSpace, long, int, Reason, PcodeStateCallbacks)}. + * + *

+ * NOTE: This is backed directly by the trace rather than using {@link PcodeStateCallbacks}. */ public class TraceMemoryStatePcodeExecutorStatePiece extends AbstractLongOffsetPcodeExecutorStatePiece { @@ -48,29 +50,30 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends protected final MutableULongSpanMap unique; protected final PcodeTraceDataAccess data; + protected TraceMemoryStatePcodeExecutorStatePiece(PcodeTraceDataAccess data, + MutableULongSpanMap unique) { + super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()), + TraceMemoryStatePcodeArithmetic.INSTANCE, PcodeStateCallbacks.NONE); + this.data = data; + this.unique = unique; + } + /** * Construct a piece * * @param data the trace-data access shim */ public TraceMemoryStatePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - super(data.getLanguage(), - BytesPcodeArithmetic.forLanguage(data.getLanguage()), - TraceMemoryStatePcodeArithmetic.INSTANCE); - this.data = data; - this.unique = new DefaultULongSpanMap<>(); - } - - protected TraceMemoryStatePcodeExecutorStatePiece(PcodeTraceDataAccess data, - MutableULongSpanMap unique) { - super(data.getLanguage(), BytesPcodeArithmetic.forLanguage(data.getLanguage()), - TraceMemoryStatePcodeArithmetic.INSTANCE); - this.data = data; - this.unique = unique; + this(data, new DefaultULongSpanMap<>()); } @Override - public TraceMemoryStatePcodeExecutorStatePiece fork() { + protected TraceMemoryState checkSize(int size, TraceMemoryState val) { + return val; + } + + @Override + public TraceMemoryStatePcodeExecutorStatePiece fork(PcodeStateCallbacks cb) { MutableULongSpanMap copyUnique = new DefaultULongSpanMap<>(); copyUnique.putAll(unique); return new TraceMemoryStatePcodeExecutorStatePiece(data, copyUnique); @@ -86,12 +89,13 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends } @Override - protected void setUnique(long offset, int size, TraceMemoryState val) { + protected void setUnique(long offset, int size, TraceMemoryState val, PcodeStateCallbacks cb) { unique.put(ULongSpan.extent(offset, size), val); } @Override - protected TraceMemoryState getUnique(long offset, int size, Reason reason) { + protected TraceMemoryState getUnique(long offset, int size, Reason reason, + PcodeStateCallbacks cb) { MutableULongSpanSet remains = new DefaultULongSpanSet(); ULongSpan span = ULongSpan.extent(offset, size); remains.add(span); @@ -110,19 +114,20 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends } @Override - protected void setInSpace(AddressSpace space, long offset, int size, TraceMemoryState val) { + protected void setInSpace(AddressSpace space, long offset, int size, TraceMemoryState val, + PcodeStateCallbacks cb) { // NB. Will ensure writes with unknown state are still marked unknown data.setState(range(space, offset, size), val); } @Override protected TraceMemoryState getFromSpace(AddressSpace space, long offset, int size, - Reason reason) { + Reason reason, PcodeStateCallbacks cb) { return data.getViewportState(range(space, offset, size)); } @Override - protected TraceMemoryState getFromNullSpace(int size, Reason reason) { + protected TraceMemoryState getFromNullSpace(int size, Reason reason, PcodeStateCallbacks cb) { return TraceMemoryState.UNKNOWN; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java deleted file mode 100644 index 6bfe5301e3..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java +++ /dev/null @@ -1,36 +0,0 @@ -/* ### - * 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.exec.trace; - -import ghidra.pcode.exec.PcodeExecutorState; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; - -/** - * An interface for trace-bound states - * - *

- * In particular, because this derives from {@link TracePcodeExecutorStatePiece}, such states are - * required to implement {@link #writeDown(PcodeTraceDataAccess)}. This interface also derives from - * {@link PcodeExecutorState} so that, as the name implies, they can be used where a state is - * required. - * - * @param the type of values - */ -public interface TracePcodeExecutorState - extends PcodeExecutorState, TracePcodeExecutorStatePiece { - @Override - TracePcodeExecutorState fork(); -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java deleted file mode 100644 index 79c7de62d0..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java +++ /dev/null @@ -1,55 +0,0 @@ -/* ### - * 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.exec.trace; - -import ghidra.pcode.exec.PcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.data.PcodeTraceAccess; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; - -/** - * A state piece which knows how to write its values back into a trace - * - * @param the type of address offsets - * @param the type of values - */ -public interface TracePcodeExecutorStatePiece extends PcodeExecutorStatePiece { - - /** - * Get the state's trace-data access shim - * - *

- * This method is meant for auxiliary state pieces, so that it can access the same trace data as - * this piece. - * - * @return the trace-data access shim - */ - PcodeTraceDataAccess getData(); - - @Override - TracePcodeExecutorStatePiece fork(); - - /** - * Write the accumulated values (cache) into the given trace - * - *

- * NOTE: This method requires a transaction to have already been started on the - * destination trace. - * - * @param into the destination data-access shim - * @see TracePcodeMachine#writeDown(PcodeTraceAccess) - */ - void writeDown(PcodeTraceDataAccess into); -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java deleted file mode 100644 index cbf07f63a1..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java +++ /dev/null @@ -1,81 +0,0 @@ -/* ### - * 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.exec.trace; - -import ghidra.pcode.emu.PcodeMachine; -import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.exec.trace.data.*; -import ghidra.trace.model.guest.TracePlatform; - -/** - * A p-code machine which sources its state from a trace and can record back into it - * - * @param the type of values manipulated by the machine - */ -public interface TracePcodeMachine extends PcodeMachine { - - /** - * Create a shared state - * - * @return the shared state - */ - TracePcodeExecutorState createSharedState(); - - /** - * Create a local state - * - * @param thread the thread whose state is being created - * @return the local state - */ - TracePcodeExecutorState createLocalState(PcodeThread thread); - - /** - * Write the accumulated emulator state via the given trace access shim - * - *

- * NOTE: This method requires a transaction to have already been started on the - * destination trace. The destination threads must have equal names/paths at the given - * threadsSnap. When using scratch space, threadsSnap should be the source snap. If populating a - * new trace, threadsSnap should probably be the destination snap. - * - * @param into the destination trace-data access shim - */ - default void writeDown(PcodeTraceAccess into) { - TracePcodeExecutorState sharedState = (TracePcodeExecutorState) getSharedState(); - sharedState.writeDown(into.getDataForSharedState()); - for (PcodeThread emuThread : getAllThreads()) { - PcodeTraceDataAccess localInto = into.getDataForLocalState(emuThread, 0); - if (localInto == null) { - throw new IllegalArgumentException( - "Given trace does not have thread with name/path '" + emuThread.getName() + - "' at source snap"); - } - TracePcodeExecutorState localState = - (TracePcodeExecutorState) emuThread.getState().getLocalState(); - localState.writeDown(localInto); - } - } - - /** - * @see #writeDown(PcodeTraceAccess) - * @param platform the platform whose trace to modify - * @param destSnap the destination snap within the trace - * @param threadsSnap the snap at which to find corresponding threads - */ - default void writeDown(TracePlatform platform, long destSnap, long threadsSnap) { - writeDown(new DefaultPcodeTraceAccess(platform, destSnap, threadsSnap)); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java index b53f5929d7..d8007bea5c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java @@ -4,9 +4,9 @@ * 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. @@ -22,8 +22,10 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.emu.PcodeEmulator; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess; import ghidra.pcode.utils.Utils; import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressSpace; @@ -43,9 +45,10 @@ public enum TraceSleighUtils { * Build a p-code executor that operates directly on bytes of the given trace * *

- * This execute is most suitable for evaluating Sleigh expression on a given trace snapshot, and - * for manipulating or initializing variables using Sleigh code. It is generally not suitable - * for use in an emulator. For that, consider {@link BytesTracePcodeEmulator}. + * This executor is most suitable for evaluating Sleigh expression on a given trace snapshot, + * and for manipulating or initializing variables using Sleigh code. It is generally not + * suitable for use in an emulator. For that, use {@link PcodeEmulator} with + * {@link TraceEmulationIntegration}. * * @param platform the platform * @param snap the snap @@ -55,14 +58,15 @@ public enum TraceSleighUtils { */ public static PcodeExecutor buildByteExecutor(TracePlatform platform, long snap, TraceThread thread, int frame) { - DirectBytesTracePcodeExecutorState state = - new DirectBytesTracePcodeExecutorState(platform, snap, thread, frame); - Language language = platform.getLanguage(); - if (!(language instanceof SleighLanguage)) { + if (!(platform.getLanguage() instanceof SleighLanguage language)) { throw new IllegalArgumentException("TracePlatform must use a Sleigh language"); } - return new PcodeExecutor<>((SleighLanguage) language, - BytesPcodeArithmetic.forLanguage(language), state, Reason.INSPECT); + DefaultPcodeTraceAccess access = new DefaultPcodeTraceAccess(platform, snap); + PcodeStateCallbacks cb = + TraceEmulationIntegration.bytesImmediateWrite(access, thread, frame); + BytesPcodeExecutorState state = new BytesPcodeExecutorState(language, cb); + return new PcodeExecutor<>(language, BytesPcodeArithmetic.forLanguage(language), state, + Reason.INSPECT); } /** @@ -94,14 +98,17 @@ public enum TraceSleighUtils { */ public static PcodeExecutor> buildByteWithStateExecutor( TracePlatform platform, long snap, TraceThread thread, int frame) { - DirectBytesTracePcodeExecutorState state = - new DirectBytesTracePcodeExecutorState(platform, snap, thread, frame); - PcodeExecutorState> paired = state.withMemoryState(); - Language language = platform.getLanguage(); - if (!(language instanceof SleighLanguage)) { + if (!(platform.getLanguage() instanceof SleighLanguage language)) { throw new IllegalArgumentException("TracePlatform must use a Sleigh language"); } - return new PcodeExecutor<>((SleighLanguage) language, new PairedPcodeArithmetic<>( + DefaultPcodeTraceAccess access = new DefaultPcodeTraceAccess(platform, snap); + PcodeStateCallbacks cb = + TraceEmulationIntegration.bytesImmediateWrite(access, thread, frame); + BytesPcodeExecutorState state = new BytesPcodeExecutorState(language, cb); + PcodeExecutorState> paired = + state.paired(new TraceMemoryStatePcodeExecutorStatePiece( + access.getDataForThreadState(thread, frame))); + return new PcodeExecutor<>(language, new PairedPcodeArithmetic<>( BytesPcodeArithmetic.forLanguage(language), TraceMemoryStatePcodeArithmetic.INSTANCE), paired, Reason.INSPECT); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java deleted file mode 100644 index 9d5909a846..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java +++ /dev/null @@ -1,71 +0,0 @@ -/* ### - * 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.exec.trace.auxiliary; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; -import ghidra.pcode.exec.trace.*; - -/** - * An auxiliary emulator parts factory capable of integrating with a trace - * - *

- * This can manufacture parts for an emulator that reads and writes its state (concrete and - * auxiliary pieces) from and to a trace, as well as all the parts for the less integrated forms of - * the same emulator. The pattern of use is generally to read from a given "source" snap, execute - * some stepping schedule, then write the cache to a given "destination" snap. - * - * @param the type of auxiliary values - */ -public interface AuxTraceEmulatorPartsFactory extends AuxEmulatorPartsFactory { - /** - * Create the shared (memory) state of a new trace-integrated emulator - * - *

- * This is usually composed of pieces using {@link PairedTracePcodeExecutorStatePiece}, but it - * does not have to be. It must incorporate the concrete piece provided. The state must be - * capable of lazily loading state from a trace and later writing its cache back into the trace - * at another snapshot. The given concrete piece is already capable of doing that for concrete - * values. The auxiliary piece should be able to independently load its state from the trace, - * since this is one way a user expects to initialize the auxiliary values. It ought to use the - * same data-access shim as the given concrete state. See - * {@link TracePcodeExecutorStatePiece#getData()}. - * - * @param emulator the emulator - * @param concrete the concrete piece - * @return the composed state - */ - TracePcodeExecutorState> createTraceSharedState( - AuxTracePcodeEmulator emulator, BytesTracePcodeExecutorStatePiece concrete); - - /** - * Create the local (register) state of a new trace-integrated thread - * - *

- * This must have the same capabilities as - * {@link #createTraceSharedState(AuxTracePcodeEmulator, BytesTracePcodeExecutorStatePiece)}. - * - * @param emulator the emulator - * @param thread the new thread - * @param concrete the concrete piece - * @return the composed state - */ - TracePcodeExecutorState> createTraceLocalState( - AuxTracePcodeEmulator emulator, PcodeThread> thread, - BytesTracePcodeExecutorStatePiece concrete); -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java deleted file mode 100644 index 63e8a1b6be..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java +++ /dev/null @@ -1,87 +0,0 @@ -/* ### - * 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.exec.trace.auxiliary; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; -import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; -import ghidra.pcode.exec.trace.*; -import ghidra.pcode.exec.trace.data.*; -import ghidra.trace.model.guest.TracePlatform; - -/** - * An trace-integrated emulator whose parts are manufactured by a - * {@link AuxTraceEmulatorPartsFactory} - * - *

- * See the parts factory interface and its super interfaces: - *

- * - * @param the type of auxiliary values - */ -public abstract class AuxTracePcodeEmulator extends AuxPcodeEmulator - implements TracePcodeMachine> { - - protected final PcodeTraceAccess access; - - /** - * Create a new emulator - * - * @param access the trace access shim - */ - public AuxTracePcodeEmulator(PcodeTraceAccess access) { - super(access.getLanguage()); - this.access = access; - } - - /** - * Create a new emulator - * - * @param platform the platform to emulate - * @param snap the source snap - */ - public AuxTracePcodeEmulator(TracePlatform platform, long snap) { - this(new DefaultPcodeTraceAccess(platform, snap)); - } - - @Override - protected abstract AuxTraceEmulatorPartsFactory getPartsFactory(); - - @Override - protected PcodeThread> createThread(String name) { - PcodeThread> thread = super.createThread(name); - access.getDataForLocalState(thread, 0).initializeThreadContext(thread); - return thread; - } - - @Override - public TracePcodeExecutorState> createSharedState() { - return getPartsFactory().createTraceSharedState(this, - new BytesTracePcodeExecutorStatePiece(access.getDataForSharedState())); - } - - @Override - public TracePcodeExecutorState> createLocalState( - PcodeThread> thread) { - return getPartsFactory().createTraceLocalState(this, thread, - new BytesTracePcodeExecutorStatePiece(access.getDataForLocalState(thread, 0))); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java index fc06a9659f..92d08a9acb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java @@ -4,9 +4,9 @@ * 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. @@ -45,6 +45,11 @@ public class DefaultPcodeTraceAccess extends AbstractPcodeTraceAccess // super(platform, snap); } + @Override + public PcodeTraceAccess deriveForWrite(long snap) { + return new DefaultPcodeTraceAccess(platform, snap, threadsSnap); + } + @Override protected DefaultPcodeTraceMemoryAccess newDataForSharedState() { return new DefaultPcodeTraceMemoryAccess(platform, snap, viewport); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTracePropertyAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTracePropertyAccess.java index 81311aa29a..4a33b8f2ce 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTracePropertyAccess.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTracePropertyAccess.java @@ -15,9 +15,13 @@ */ package ghidra.pcode.exec.trace.data; +import java.util.Map; +import java.util.Map.Entry; + import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.trace.model.Lifespan; +import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.property.*; /** @@ -93,31 +97,64 @@ public class DefaultPcodeTracePropertyAccess return ops.get(data.getSnap(), overlayAddr); } + @Override + public Entry getEntry(Address address) { + Address hostAddr = data.getPlatform().mapGuestToHost(address); + if (hostAddr == null) { + return null; + } + TracePropertyMapOperations ops = getPropertyOperations(false); + if (ops == null) { + return null; + } + Address overlayAddr = toOverlay(ops, hostAddr); + Entry entry = ops.getEntry(data.getSnap(), overlayAddr); + return entry == null ? null : Map.entry(entry.getKey().getRange(), entry.getValue()); + } + @Override public void put(Address address, T value) { Address hostAddr = data.getPlatform().mapGuestToHost(address); if (hostAddr == null) { - // TODO: Warn? + // Warn? return; } Lifespan span = Lifespan.nowOnMaybeScratch(data.getSnap()); TracePropertyMapOperations ops = getPropertyOperations(true); - ops.set(span, toOverlay(ops, hostAddr), value); + if (value == null) { + if (ops == null) { + return; + } + ops.clear(span, toOverlay(ops, new AddressRangeImpl(hostAddr, hostAddr))); + } + else { + ops.set(span, toOverlay(ops, hostAddr), value); + } + } + + @Override + public void put(AddressRange range, T value) { + AddressRange hostRange = data.getPlatform().mapGuestToHost(range); + if (hostRange == null) { + // Warn? + return; + } + Lifespan span = Lifespan.nowOnMaybeScratch(data.getSnap()); + TracePropertyMapOperations ops = getPropertyOperations(true); + if (value == null) { + if (ops == null) { + return; + } + ops.clear(span, toOverlay(ops, hostRange)); + } + else { + ops.set(span, toOverlay(ops, hostRange), value); + } } @Override public void clear(AddressRange range) { - AddressRange hostRange = data.getPlatform().mapGuestToHost(range); - if (hostRange == null) { - // TODO: Warn? - return; - } - Lifespan span = Lifespan.nowOnMaybeScratch(data.getSnap()); - TracePropertyMapOperations ops = getPropertyOperations(false); - if (ops == null) { - return; - } - ops.clear(span, toOverlay(ops, hostRange)); + put(range, null); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java index db4b30a331..b61a7e460b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java @@ -16,9 +16,7 @@ package ghidra.pcode.exec.trace.data; import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.exec.trace.TracePcodeMachine; import ghidra.program.model.lang.Language; -import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.thread.TraceThread; /** @@ -35,50 +33,17 @@ import ghidra.trace.model.thread.TraceThread; * trace. The shim is associated with a chosen platform and snapshot. All methods are with respect * to that platform. In particular the addresses must all be in spaces of the platform's language. * Note that the platform may be the trace's host platform. - * - *

- * Typically, each component of an emulator and/or its state will accept a corresponding access - * shim. Thus, each method in the chain of obtaining the shim is invoked during that piece's - * construction or invoked and passed into the constructor by a factory method. A complete chain - * starts with {@link DefaultPcodeTraceAccess}. Each method is listed with notes about where it is - * typically invoked below: - * - *

    - *
  • Typically invoked by an overloaded constructor, which then passes it to {@code this(...)}. - * Clients can also construct the shim and pass it to the shim-accepting constructor manually. - * Similarly, {@link TracePcodeMachine#writeDown(TracePlatform, long, long)} will construct one and - * pass it to the overloaded method, which can instead be done by the client. - * - *
    - * PcodeTraceAccess access =
    - * 	new DefaultPcodeTraceAccess(trace.getPlatformManager().getHostPlatform(), 0, 0);
    - * 
    - * - *
  • - *
  • Typically invoked by a factory method for an emulator's shared executor state - * - *
    - * PcodeTraceMemoryAccess sharedData = access.getDataForSharedState();
    - * 
    - * - *
  • - *
  • Typically invoked by a factory method for an emulator thread's local executor state - * - *
    - * PcodeTraceRegisterAccess localData = access.getDataForLocalState(thread, 0);
    - * 
    - * - *
  • - *
  • Typically invoked by an auxiliary emulator state piece - * - *
    {@code
    - * PcodeTracePropertyAccess property = data.getPropertyAccess("MyProperty", String.class);
    - * }
    - * - *
  • - *
*/ public interface PcodeTraceAccess { + + /** + * Derive an access for writing a snapshot, where this access was the emulator's source + * + * @param snap the destination snapshot key + * @return the derived access shim + */ + PcodeTraceAccess deriveForWrite(long snap); + /** * Get the language of the associated platform * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTracePropertyAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTracePropertyAccess.java index 0439fbc29d..3e8f2af100 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTracePropertyAccess.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTracePropertyAccess.java @@ -15,6 +15,8 @@ */ package ghidra.pcode.exec.trace.data; +import java.util.Map.Entry; + import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; @@ -28,7 +30,7 @@ import ghidra.program.model.lang.Language; */ public interface PcodeTracePropertyAccess { /** - * @see PcodeTraceDataAccess#getLanguage() + * {@return the language} */ Language getLanguage(); @@ -44,11 +46,19 @@ public interface PcodeTracePropertyAccess { */ T get(Address address); + /** + * Get the property's entry at the given address + * + * @param address the address + * @return the entry, or null if not set + */ + Entry getEntry(Address address); + /** * Set the property's value at the given address * *

- * The value is affective for future snapshots up to but excluding the next snapshot where + * The value is effective for future snapshots up to but excluding the next snapshot where * another value is set at the same address. * * @param address the address @@ -56,6 +66,18 @@ public interface PcodeTracePropertyAccess { */ void put(Address address, T value); + /** + * Set the property's value at the given range + * + *

+ * The value is effective for future snapshots up to but excluding the next snapshot where + * another value is set at the same address. + * + * @param range the range + * @param value the value to set + */ + void put(AddressRange range, T value); + /** * Clear the property's value across a range * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java index 969dd02a9b..50af30d9d7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java @@ -22,12 +22,16 @@ import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; +import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess; +import ghidra.pcode.exec.trace.data.PcodeTraceAccess; import ghidra.program.model.address.AddressRange; import ghidra.program.model.listing.Instruction; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.ToyDBTraceBuilder.ToySchemaBuilder; import ghidra.trace.model.Lifespan; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.memory.TraceMemoryManager; import ghidra.trace.model.target.schema.SchemaContext; @@ -36,6 +40,14 @@ import ghidra.util.Msg; public class AbstractTracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTest { + protected PcodeTraceAccess createAccess(TracePlatform platform, long snap) { + return new DefaultPcodeTraceAccess(platform, snap); + } + + protected Writer createWriter(TracePlatform platform, long snap) { + return TraceEmulationIntegration.bytesDelayedWrite(createAccess(platform, snap)); + } + public TraceThread initTrace(ToyDBTraceBuilder tb, String stateInit, List assembly) throws Throwable { return initTrace(tb, tb.range(0x00400000, 0x0040ffff), tb.range(0x00100000, 0x0010ffff), diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java index 0e5ec59dd0..de68c57550 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java @@ -28,9 +28,11 @@ import org.junit.Test; import db.Transaction; import ghidra.app.plugin.assembler.*; import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; +import ghidra.pcode.emu.PcodeEmulator; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRangeImpl; import ghidra.program.model.lang.*; @@ -39,6 +41,7 @@ import ghidra.trace.database.context.DBTraceRegisterContextManager; import ghidra.trace.database.target.DBTraceObjectManager; import ghidra.trace.model.Lifespan; import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.*; import ghidra.trace.model.target.TraceObject.ConflictResolution; import ghidra.trace.model.target.path.KeyPath; @@ -48,6 +51,10 @@ import ghidra.util.NumericUtilities; @SuppressWarnings("javadoc") public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest { + PcodeEmulator createEmulator(TracePlatform platform, Writer writer) { + return new PcodeEmulator(platform.getLanguage(), writer.callbacks()); + } + /** * Test a single instruction * @@ -66,7 +73,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "PUSH 0x0dedbeef")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); @@ -77,7 +85,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest TraceSleighUtils.evaluate("*:8 0x0010fff8:8", tb.trace, 0, thread, 0)); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x0010fff8), @@ -105,13 +113,14 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0x0dedbeef", "PUSH 0x0badf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x0010fff0), @@ -145,7 +154,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "MOV EAX,0x0dedbeef", // 5 bytes "MOV ECX,0x0badf00d")); // 5 bytes - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); @@ -156,7 +166,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -206,7 +216,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest asm.patchProgram(mov, tb.addr(0x00401000)); } - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); emuThread.stepInstruction(); @@ -222,7 +233,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -249,12 +260,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "imm r0, #911")); // decimal - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -282,13 +294,14 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "imm r0, #860", "imm r1, #861")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); // brds and 1st imm executed emuThread.stepInstruction(); // 3rd imm executed try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x00400008), @@ -325,14 +338,15 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "XOR byte ptr [0x00400007], 0xcc", // 7 bytes "MOV EAX,0x0dedbeef")); // 5 bytes - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -362,7 +376,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0x0dedbeef", "PUSH 0x0badf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); assertNull(emuThread.getFrame()); @@ -385,7 +400,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest assertNull(emuThread.getFrame()); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x0040000a), @@ -429,7 +444,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0x0dedbeef", "PUSH 0x0badf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0) { + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = new PcodeEmulator(tb.language, writer.callbacks()) { @Override protected PcodeUseropLibrary createUseropLibrary() { return hexLib; @@ -476,7 +492,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0x0dedbeef", "PUSH 0x0badf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0) { + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = new PcodeEmulator(tb.language, writer.callbacks()) { @Override protected PcodeUseropLibrary createUseropLibrary() { return hexLib; @@ -509,7 +526,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest dumped.delete(0, dumped.length()); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x0badf00dL), @@ -532,7 +549,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0x0dedbeef", "PUSH 0x0badf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); emu.addBreakpoint(tb.addr(0x00400000), "RAX == 1"); emu.addBreakpoint(tb.addr(0x00400005), "RAX == 0"); PcodeThread emuThread = emu.newThread(thread.getPath()); @@ -564,12 +582,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "clz r1, r0")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(16), @@ -597,7 +616,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOVAPS XMM0, xmmword ptr [0x00600000]")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); @@ -606,7 +626,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.getState().getVar(pc, Reason.INSPECT)); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(new BigInteger("0123456789abcdeffedcba9876543210", 16), @@ -634,7 +654,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "SAR EAX, CL")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); @@ -643,7 +664,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.getState().getVar(pc, Reason.INSPECT)); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x7ffffff), @@ -663,13 +684,14 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "XOR AH, AH", "MOV RCX, RAX")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x12340078), @@ -703,7 +725,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest tb.trace.getMemoryManager().getBytes(0, tb.addr(0x00400000), buf); assertArrayEquals(tb.arr(0x48, 0x89, 0xc1), buf.array()); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); // TODO: Seems the Trace-bound thread ought to know to do this in reInitialize() ctxVal = ctxManager.getValueWithDefault(tb.host, ctxReg, 0, tb.addr(0x00400000)); @@ -711,7 +734,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x00400003), @@ -740,12 +763,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOV EAX, dword ptr [RBP + -0x4]")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x12345678), @@ -773,7 +797,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOV EAX, dword ptr [RBP + -0x2]")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); } @@ -795,7 +820,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOV EAX, dword ptr [EBP + -0x2]")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); } @@ -817,7 +843,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "unimpl")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); } @@ -834,7 +861,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest """, List.of()); // An empty, uninitialized program - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); } @@ -856,12 +884,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "mov.w [W1], W0")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x000102), @@ -918,7 +947,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest mm.putBytes(0, tb.addr(0x00000000), ByteBuffer.wrap(buf.getBytes())); } - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(x64, 0); + Writer writer = createWriter(x64, 0); + PcodeEmulator emu = createEmulator(x64, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); @@ -931,7 +961,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest TraceSleighUtils.evaluate(changedExpr, tb.trace, 0, thread, 0)); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(x64, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x0010fff8), @@ -957,7 +987,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "mov r0,r7", // Assembler doesn't handle context flow "mov r1,r7")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); emuThread.stepPcodeOp(); // decode second @@ -968,7 +999,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.finishInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x00400006), @@ -997,7 +1028,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "mov r1,r7", // " "mov r2,r7")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); emuThread.stepPcodeOp(); // decode second @@ -1011,7 +1043,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.finishInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 1); + writer.writeDown(1); } assertEquals(BigInteger.valueOf(0x00400008), diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java index 051e4520d5..cf7da92583 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java @@ -27,7 +27,6 @@ import org.junit.Test; import db.Transaction; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.exec.*; -import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.program.model.lang.*; import ghidra.program.util.DefaultLanguageService; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; @@ -241,10 +240,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest thread = b.getOrAddThread("Threads[1]", 0); b.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), b.host, 1); PcodeExecutor executor = - new PcodeExecutor<>(sp.getLanguage(), - BytesPcodeArithmetic.forLanguage(b.language), - new DirectBytesTracePcodeExecutorState(b.host, 0, thread, 0), - Reason.EXECUTE_READ); + TraceSleighUtils.buildByteExecutor(b.host, 0, thread, 0); sp.execute(executor, PcodeUseropLibrary.nil()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java index 9ea82ce756..dee76e223f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java @@ -4,9 +4,9 @@ * 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. @@ -18,8 +18,7 @@ package ghidra.trace.model.time.schedule; import java.util.ArrayList; import java.util.List; -import ghidra.pcode.emu.AbstractPcodeMachine; -import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.*; import ghidra.pcode.exec.*; /** @@ -34,7 +33,7 @@ class TestMachine extends AbstractPcodeMachine { protected final List record = new ArrayList<>(); public TestMachine() { - super(TraceScheduleTest.TOY_BE_64_LANG); + super(TraceScheduleTest.TOY_BE_64_LANG, PcodeEmulationCallbacks.none()); } @Override diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java deleted file mode 100644 index 938bdad68b..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java +++ /dev/null @@ -1,135 +0,0 @@ -/* ### - * 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.emu.taint; - -import java.util.List; -import java.util.Map; - -import ghidra.pcode.emu.taint.plain.TaintSpace; -import ghidra.pcode.exec.*; -import ghidra.pcode.exec.PcodeArithmetic.Purpose; -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.mem.MemBuffer; -import ghidra.taint.model.TaintVec; - -/** - * An abstract taint state piece - * - *

- * Because we want to reduce code repetition, we use the type hierarchy to increase the capabilities - * of the state piece as we progress from stand-alone to Debugger-integrated. The framework-provided - * class from which this derives, however, introduces the idea of a space map, whose values have - * type {@code }. We'll be using types derived from {@link TaintSpace}, which is where all the - * taint storage logic is actually located. Because that logic is what we're actually extending with - * each more capable state piece, we have to ensure that type can be substituted. Thus, we have to - * create these abstract classes from which the actual state pieces are derived, leaving {@code } - * bounded, but unspecified. - * - * @param the type of spaces - */ -public abstract class AbstractTaintPcodeExecutorStatePiece - extends AbstractLongOffsetPcodeExecutorStatePiece { - - /** - * The map from address space to storage space - * - *

- * While the concept is introduced in the super class, we're not required to actually use one. - * We just have to implement {@link #getForSpace(AddressSpace, boolean)}. Nevertheless, the - * provided map is probably the best way, so we'll follow the pattern. - */ - protected final AbstractSpaceMap spaceMap = newSpaceMap(); - - /** - * Create a state piece - * - * @param language the emulator's language - * @param addressArithmetic the arithmetic for the address type - * @param arithmetic the arithmetic for the value type - */ - public AbstractTaintPcodeExecutorStatePiece(Language language, - PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic) { - super(language, addressArithmetic, arithmetic); - } - - /** - * Extension point: Create the actual space map - * - *

- * This will need to be implemented by each state piece, i.e., non-abstract derivative class. - * The space map will provide instances of {@code }, which will provide the actual (extended) - * storage logic. - * - * @return the space map - */ - protected abstract AbstractSpaceMap newSpaceMap(); - - @Override - public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { - throw new ConcretionError("Cannot make Taint concrete", purpose); - } - - /** - * {@inheritDoc} - * - *

- * Here, we just follow the pattern: delegate to the space map. - */ - @Override - protected S getForSpace(AddressSpace space, boolean toWrite) { - return spaceMap.getForSpace(space, toWrite); - } - - /** - * {@inheritDoc} - * - *

- * Because the super class places no bound on {@code }, we have to provide the delegation to - * the storage space. - */ - @Override - protected void setInSpace(S space, long offset, int size, TaintVec val) { - space.set(offset, val); - } - - /** - * {@inheritDoc} - * - *

- * Because the super class places no bound on {@code }, we have to provide the delegation to - * the storage space. - */ - @Override - protected TaintVec getFromSpace(S space, long offset, int size, Reason reason) { - return space.get(offset, size); - } - - @Override - protected Map getRegisterValuesFromSpace(S space, - List registers) { - return space.getRegisterValues(registers); - } - - @Override - public void clear() { - for (S space : spaceMap.values()) { - space.clear(); - } - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintEmulatorFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintEmulatorFactory.java new file mode 100644 index 0000000000..846e2fd0bc --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintEmulatorFactory.java @@ -0,0 +1,70 @@ +/* ### + * 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.emu.taint; + +import ghidra.debug.api.emulation.EmulatorFactory; +import ghidra.debug.api.emulation.PcodeDebuggerAccess; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.taint.state.TaintPieceHandler; +import ghidra.pcode.exec.trace.TraceEmulationIntegration; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; +import ghidra.pcode.exec.trace.data.PcodeTraceAccess; + +/** + * An emulator factory for making the {@link TaintPcodeEmulator} discoverable to the UI + * + *

+ * This is the final class to create a full Debugger-integrated emulator. This class is what makes + * it appear in the menu of possible emulators the user may configure. + */ +public class TaintEmulatorFactory implements EmulatorFactory { + + /** + * This is conventionally available for testing and for scripts that would like to create a + * trace-integrated emulator without using the service. + * + * @param access the means of accessing the integrated trace + * @return a writer with callbacks for trace integration + */ + public static Writer delayedWriteTrace(PcodeTraceAccess access) { + Writer writer = TraceEmulationIntegration.bytesDelayedWrite(access); + addHandlers(writer); + return writer; + } + + /** + * A common place to factor addition of the required handler. + * + *

+ * It is presumed something else has or will add the other handlers, e.g., for the bytes. + * + * @param writer the writer to add handlers to + */ + public static void addHandlers(Writer writer) { + writer.putHandler(new TaintPieceHandler()); + } + + @Override + public String getTitle() { + return "Taint Analyzer with Concrete Emulation"; + } + + @Override + public PcodeMachine create(PcodeDebuggerAccess access, Writer writer) { + addHandlers(writer); + return new TaintPcodeEmulator(access.getLanguage(), writer.callbacks()); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java index 52ff65142a..0569c3d158 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java @@ -17,19 +17,12 @@ package ghidra.pcode.emu.taint; import org.apache.commons.lang3.tuple.Pair; -import ghidra.app.plugin.core.debug.service.emulation.RWTargetMemoryPcodeExecutorStatePiece; -import ghidra.app.plugin.core.debug.service.emulation.RWTargetRegistersPcodeExecutorStatePiece; import ghidra.pcode.emu.*; import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor; +import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; -import ghidra.pcode.emu.taint.plain.TaintPcodeExecutorState; -import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorState; +import ghidra.pcode.emu.taint.state.TaintPcodeExecutorState; import ghidra.pcode.exec.*; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; -import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.TracePcodeExecutorState; -import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; import ghidra.program.model.lang.Language; import ghidra.taint.model.TaintVec; @@ -51,22 +44,10 @@ import ghidra.taint.model.TaintVec; *

  • P-code Arithmetic: {@link TaintPcodeArithmetic}
  • *
  • Userop Library: {@link TaintPcodeUseropLibrary}
  • *
  • P-code Executor: {@link TaintPcodeThreadExecutor}
  • - *
  • Machine State - *
      - *
    • Stand alone: {@link TaintPcodeExecutorState}
    • - *
    • Trace integrated: {@link TaintTracePcodeExecutorState}
    • - *
    • Debugger integrated: {@link TaintTracePcodeExecutorState} (same as Trace integrated)
    • + *
    • Machine State: {@link TaintPcodeExecutorState}
    • *
    - *
  • - * - * - *

    - * If you're following from the {@link ghidra.taint} package documentation, you'll want to return to - * {@link ghidra.pcode.emu.taint.plain} before you examine the trace-integrated state. Similarly, - * you'll want to return to {@link ghidra.pcode.emu.taint.trace} before you examine the - * Debugger-integrated state. */ -public enum TaintPartsFactory implements AuxDebuggerEmulatorPartsFactory { +public enum TaintPartsFactory implements AuxEmulatorPartsFactory { /** This singleton factory instance */ INSTANCE; @@ -145,8 +126,9 @@ public enum TaintPartsFactory implements AuxDebuggerEmulatorPartsFactory> createSharedState( - AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete) { - return new TaintPcodeExecutorState(emulator.getLanguage(), concrete); + AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete, + PcodeStateCallbacks cb) { + return new TaintPcodeExecutorState(emulator.getLanguage(), concrete, cb); } /** @@ -159,79 +141,7 @@ public enum TaintPartsFactory implements AuxDebuggerEmulatorPartsFactory> createLocalState( AuxPcodeEmulator emulator, PcodeThread> thread, - BytesPcodeExecutorStatePiece concrete) { - return new TaintPcodeExecutorState(emulator.getLanguage(), concrete); - } - - /** - * {@inheritDoc} - * - *

    - * If you're following the {@link ghidra.taint} package documentation, please finish reading - * about the stand-alone emulator before proceeding to this trace-integrated part. This part - * extends the shared state of the stand-alone emulator so that it can lazily deserialize taint - * sets from a trace database. It can also serialize intermediate and/or final taint sets back - * into a trace database. - */ - @Override - public TracePcodeExecutorState> createTraceSharedState( - AuxTracePcodeEmulator emulator, BytesTracePcodeExecutorStatePiece concrete) { - return new TaintTracePcodeExecutorState(concrete); - } - - /** - * {@inheritDoc} - * - *

    - * If you're following the {@link ghidra.taint} package documentation, please finish reading - * about the stand-alone emulator before proceeding to this trace-integrated part. This part - * extends the local state of the stand-alone emulator so that it can lazily deserialize taint - * sets from a trace database. It can also serialize intermediate and/or final taint sets back - * into a trace database. - */ - @Override - public TracePcodeExecutorState> createTraceLocalState( - AuxTracePcodeEmulator emulator, PcodeThread> emuThread, - BytesTracePcodeExecutorStatePiece concrete) { - return new TaintTracePcodeExecutorState(concrete); - } - - /** - * {@inheritDoc} - * - *

    - * If you're following the {@link ghidra.taint} package documentation, please finish reading - * about the stand-alone and trace-integrated emulators before proceeding to this - * Debugger-integrated part. This part uses the same shared state as the trace-integrated - * emulator but takes a different data access shim, so that it can also deserialize taint sets - * from mapped static programs. Since taint is not generally a concept understood by a live - * debugger, we need not retrieve anything new from the target. Note that the shim is passed to - * us implicitly via the {@code concrete} state. - */ - @Override - public TracePcodeExecutorState> createDebuggerSharedState( - AuxDebuggerPcodeEmulator emulator, - RWTargetMemoryPcodeExecutorStatePiece concrete) { - return new TaintTracePcodeExecutorState(concrete); - } - - /** - * {@inheritDoc} - * - *

    - * If you're following the {@link ghidra.taint} package documentation, please finish reading - * about the stand-alone and trace-integrated emulators before proceeding to this method. Since - * taint is not generally a concept understood by a live debugger, we need not retrieve anything - * new from the target. Furthermore, because static program mappings do not apply to registers, - * we need not consider them. Thus, we can just re-use the trace-integrated local state. The - * concrete piece given to us, which we just pass to our paired state, will handle retrieving - * concrete values from the live target, if applicable. - */ - @Override - public TracePcodeExecutorState> createDebuggerLocalState( - AuxDebuggerPcodeEmulator emulator, - PcodeThread> emuThread, - RWTargetRegistersPcodeExecutorStatePiece concrete) { - return new TaintTracePcodeExecutorState(concrete); + BytesPcodeExecutorStatePiece concrete, PcodeStateCallbacks cb) { + return new TaintPcodeExecutorState(emulator.getLanguage(), concrete, cb); } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeArithmetic.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeArithmetic.java index 1eb3dd987f..3f83bf7f08 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeArithmetic.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeArithmetic.java @@ -4,9 +4,9 @@ * 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. @@ -23,6 +23,7 @@ import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Endian; import ghidra.program.model.lang.Language; import ghidra.program.model.pcode.PcodeOp; +import ghidra.taint.model.TaintSet; import ghidra.taint.model.TaintVec; import ghidra.taint.model.TaintVec.ShiftMode; @@ -73,6 +74,11 @@ public enum TaintPcodeArithmetic implements PcodeArithmetic { this.endian = endian; } + @Override + public Class getDomain() { + return TaintVec.class; + } + @Override public Endian getEndian() { return endian; @@ -94,20 +100,13 @@ public enum TaintPcodeArithmetic implements PcodeArithmetic { */ @Override public TaintVec unaryOp(int opcode, int sizeout, int sizein1, TaintVec in1) { - switch (opcode) { - case PcodeOp.COPY: - case PcodeOp.BOOL_NEGATE: - case PcodeOp.INT_NEGATE: - return in1; - case PcodeOp.INT_ZEXT: - return in1.extended(sizeout, endian.isBigEndian(), false); - case PcodeOp.INT_SEXT: - return in1.extended(sizeout, endian.isBigEndian(), true); - case PcodeOp.INT_2COMP: - return in1.copy().setCascade(endian.isBigEndian()); - default: - return TaintVec.copies(in1.union(), sizeout); - } + return switch (opcode) { + case PcodeOp.COPY, PcodeOp.BOOL_NEGATE, PcodeOp.INT_NEGATE -> in1; + case PcodeOp.INT_ZEXT -> in1.extended(sizeout, endian.isBigEndian(), false); + case PcodeOp.INT_SEXT -> in1.extended(sizeout, endian.isBigEndian(), true); + case PcodeOp.INT_2COMP -> in1.copy().setCascade(endian.isBigEndian()); + default -> TaintVec.copies(in1.union(), sizeout); + }; } /** @@ -127,13 +126,11 @@ public enum TaintPcodeArithmetic implements PcodeArithmetic { public TaintVec binaryOp(PcodeOp op, TaintVec in1, TaintVec in2) { // TODO: Detect immediate operands and be more precise switch (op.getOpcode()) { - case PcodeOp.INT_XOR: - case PcodeOp.INT_SUB: - case PcodeOp.BOOL_XOR: + case PcodeOp.INT_XOR, PcodeOp.INT_SUB, PcodeOp.BOOL_XOR -> { if (Objects.equals(op.getInput(0), op.getInput(1))) { return fromConst(0, op.getOutput().getSize()); } - default: + } } return PcodeArithmetic.super.binaryOp(op, in1, in2); } @@ -152,29 +149,33 @@ public enum TaintPcodeArithmetic implements PcodeArithmetic { @Override public TaintVec binaryOp(int opcode, int sizeout, int sizein1, TaintVec in1, int sizein2, TaintVec in2) { - switch (opcode) { - case PcodeOp.BOOL_AND: - case PcodeOp.BOOL_OR: - case PcodeOp.BOOL_XOR: - case PcodeOp.INT_AND: - case PcodeOp.INT_OR: - case PcodeOp.INT_XOR: - return in1.zipUnion(in2); - case PcodeOp.INT_ADD: - case PcodeOp.INT_SUB: { - TaintVec temp = in1.zipUnion(in2); - return temp.setCascade(endian.isBigEndian()); + return switch (opcode) { + case PcodeOp.BOOL_AND, PcodeOp.BOOL_OR, PcodeOp.BOOL_XOR, PcodeOp.INT_AND, // + PcodeOp.INT_OR, PcodeOp.INT_XOR -> { + yield in1.zipUnion(in2); } - case PcodeOp.PIECE: { + case PcodeOp.INT_ADD, PcodeOp.INT_SUB -> { + TaintVec temp = in1.zipUnion(in2); + yield temp.setCascade(endian.isBigEndian()); + } + case PcodeOp.INT_SLESS, PcodeOp.INT_SLESSEQUAL, // + PcodeOp.INT_LESS, PcodeOp.INT_LESSEQUAL, // + PcodeOp.INT_EQUAL, PcodeOp.INT_NOTEQUAL, // + PcodeOp.FLOAT_LESS, PcodeOp.FLOAT_LESSEQUAL, // + PcodeOp.FLOAT_EQUAL, PcodeOp.FLOAT_NOTEQUAL -> { + TaintSet temp = in1.union().union(in2.union()); + yield TaintVec.copies(temp, sizeout); + } + case PcodeOp.PIECE -> { TaintVec temp = in1.extended(sizeout, endian.isBigEndian(), false); temp.setShifted(endian.isBigEndian() ? -sizein2 : sizein2, ShiftMode.UNBOUNDED); - return temp.set(endian.isBigEndian() ? sizeout - sizein2 : 0, in2); + yield temp.set(endian.isBigEndian() ? sizeout - sizein2 : 0, in2); } - default: { + default -> { TaintVec temp = in1.zipUnion(in2); - return temp.setCopies(temp.union()); + yield temp.setCopies(temp.union()); } - } + }; } /** diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulator.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeEmulator.java similarity index 74% rename from Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulator.java rename to Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeEmulator.java index 3313782a60..2ce32f19a7 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulator.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeEmulator.java @@ -4,34 +4,48 @@ * 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.emu.taint.plain; +package ghidra.pcode.emu.taint; +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.PcodeEmulationCallbacks; import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; -import ghidra.pcode.emu.taint.TaintPartsFactory; +import ghidra.pcode.emu.taint.state.TaintPcodeExecutorState; import ghidra.program.model.lang.Language; import ghidra.taint.model.TaintVec; /** - * A stand-alone emulator with taint analysis + * An emulator with taint analysis */ public class TaintPcodeEmulator extends AuxPcodeEmulator { + /** + * Create an emulator + * + * @param language the language (processor model) + * @param cb callbacks to receive emulation events + */ + public TaintPcodeEmulator(Language language, + PcodeEmulationCallbacks> cb) { + super(language, cb); + } + /** * Create an emulator * * @param language the language (processor model) */ public TaintPcodeEmulator(Language language) { - super(language); + this(language, PcodeEmulationCallbacks.none()); } /** diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeUseropLibrary.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeUseropLibrary.java index 9316791f0c..d269987ffc 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeUseropLibrary.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeUseropLibrary.java @@ -4,9 +4,9 @@ * 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. @@ -72,8 +72,8 @@ public class TaintPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary taint_arr(Pair in) { diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java deleted file mode 100644 index 8ec3e02589..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java +++ /dev/null @@ -1,51 +0,0 @@ -/* ### - * 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.emu.taint.full; - -import ghidra.debug.api.emulation.PcodeDebuggerAccess; -import ghidra.pcode.emu.taint.TaintPartsFactory; -import ghidra.pcode.emu.taint.plain.TaintPcodeEmulator; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; -import ghidra.taint.model.TaintVec; - -/** - * A Debugger-integrated emulator with taint analysis - */ -public class TaintDebuggerPcodeEmulator extends AuxDebuggerPcodeEmulator { - /** - * Create an emulator - * - * @param data the trace-and-debugger access shim - */ - public TaintDebuggerPcodeEmulator(PcodeDebuggerAccess data) { - super(data); - } - - /** - * {@inheritDoc} - * - *

    - * Here, we just return the singleton parts factory. This appears simple because all the - * complexity is encapsulated in the factory. See {@link TaintPartsFactory} to see everything - * the implementation actually entails. Notice that this is the same parts factory used by - * {@link TaintPcodeEmulator}. - */ - @Override - protected AuxDebuggerEmulatorPartsFactory getPartsFactory() { - return TaintPartsFactory.INSTANCE; - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorFactory.java deleted file mode 100644 index 83a44a8748..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/* ### - * 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.emu.taint.full; - -import ghidra.app.plugin.core.debug.service.emulation.AbstractDebuggerPcodeEmulatorFactory; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; -import ghidra.debug.api.emulation.PcodeDebuggerAccess; - -/** - * An emulator factory for making the {@link TaintDebuggerPcodeEmulator} discoverable to the UI - * - *

    - * This is the final class to create a full Debugger-integrated emulator. This class is what makes - * it appear in the menu of possible emulators the user may configure. - */ -public class TaintDebuggerPcodeEmulatorFactory extends AbstractDebuggerPcodeEmulatorFactory { - - @Override - public String getTitle() { - return "Taint Analyzer with Concrete Emulation"; - } - - @Override - public DebuggerPcodeMachine create(PcodeDebuggerAccess data) { - return new TaintDebuggerPcodeEmulator(data); - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/package-info.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/package-info.java similarity index 55% rename from Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/package-info.java rename to Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/package-info.java index d565238183..6ea10fe1e3 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/package-info.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/package-info.java @@ -4,9 +4,9 @@ * 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. @@ -14,20 +14,16 @@ * limitations under the License. */ /** - * The stand-alone Taint Emulator + * The Taint Emulator * *

    - * This and the {@link ghidra.pcode.emu.taint} packages contain all the parts necessary to construct - * a stand-alone emulator. Because this is a working solution, the state components already have - * provisions in place for extension to support the fully-integrated solution. Generally, it's a bit - * easier to just get the basic state components implemented, put tests in place, and then re-factor - * them to permit extension as you address each more integrated emulator. + * This and the {@link ghidra.pcode.emu.taint.state} packages contain all the parts necessary to + * construct the emulator. * *

    * For this package, I recommend a top-down approach, since the top component provides a flat * catalog of the lower components. That top piece is actually in a separate package. See * {@link ghidra.pcode.emu.taint.TaintPartsFactory}. That factory is then used in - * {@link TaintPcodeEmulator} to realize the stand-alone emulator. When you get to the state pieces, - * you may want to pause and read {@link TaintSpace} first. + * {@link ghidra.pcode.emu.taint.TaintPcodeEmulator} to realize the emulator. */ -package ghidra.pcode.emu.taint.plain; +package ghidra.pcode.emu.taint; diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java deleted file mode 100644 index 228da37bba..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java +++ /dev/null @@ -1,91 +0,0 @@ -/* ### - * 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.emu.taint.plain; - -import org.apache.commons.lang3.tuple.Pair; - -import ghidra.pcode.emu.taint.AbstractTaintPcodeExecutorStatePiece; -import ghidra.pcode.emu.taint.TaintPcodeArithmetic; -import ghidra.pcode.exec.*; -import ghidra.pcode.exec.trace.TracePcodeExecutorState; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.lang.Language; - -/** - * The state piece for holding taint marks in the emulator's machine state - * - *

    - * Because this is already a working solution, most of the logic has already been abstracted into a - * super class {@link AbstractTaintPcodeExecutorStatePiece}. This class serves only to choose the - * type {@link TaintSpace}, which implements the real storage logic, and provide a map from address - * space to that type. Note the concept of a space map is introduced by - * {@link AbstractLongOffsetPcodeExecutorStatePiece}, which is provided by the p-code emulation - * framework. This is suitable for state pieces with concrete addresses. This likely fits your - * auxiliary piece, but may not. If you choose to use abstract addresses for your auxiliary piece, - * then your implementation of state will not follow the archetype presented here. You'll instead - * want to implement {@link TracePcodeExecutorState} directly, take the concrete piece provided, and - * wrap it as you see fit. You may still benefit by referring to the implementation of - * {@link PairedPcodeExecutorState}. When implementing your flavor of - * {@link PairedPcodeExecutorState#getVar(AddressSpace, Pair, int, boolean, Reason)}, still consider - * that you could benefit from the concrete element of the offset pair passed in. - */ -public class TaintPcodeExecutorStatePiece extends AbstractTaintPcodeExecutorStatePiece { - /** - * Create the taint piece - * - * @param language the language of the emulator - * @param addressArithmetic the address arithmetic, likely taken from the concrete piece - */ - public TaintPcodeExecutorStatePiece(Language language, - PcodeArithmetic addressArithmetic) { - super(language, addressArithmetic, TaintPcodeArithmetic.forLanguage(language)); - } - - /** - * {@inheritDoc} - * - *

    - * Here we use the simplest scheme for creating a map of {@link TaintSpace}s. This is - * essentially a lazy map from address space to some object for managing taint marks in that - * address space. The space could be a memory space, register space, unique space, etc. This - * piece will look up the space, creating it if necessary, and then delegate the get and set - * methods. - */ - @Override - protected AbstractSpaceMap newSpaceMap() { - return new SimpleSpaceMap() { - @Override - protected TaintSpace newSpace(AddressSpace space) { - return new TaintSpace(); - } - - @Override - public AbstractSpaceMap fork() { - throw new UnsupportedOperationException(); - } - - @Override - public TaintSpace fork(TaintSpace s) { - throw new UnsupportedOperationException(); - } - }; - } - - @Override - public TaintPcodeExecutorStatePiece fork() { - throw new UnsupportedOperationException(); - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorState.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/state/TaintPcodeExecutorState.java similarity index 88% rename from Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorState.java rename to Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/state/TaintPcodeExecutorState.java index 383ef14a51..071f0b206e 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorState.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/state/TaintPcodeExecutorState.java @@ -4,16 +4,16 @@ * 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.emu.taint.plain; +package ghidra.pcode.emu.taint.state; import ghidra.pcode.exec.*; import ghidra.program.model.lang.Language; @@ -48,8 +48,11 @@ public class TaintPcodeExecutorState extends PairedPcodeExecutorState + * The framework-provided class from which this derives expects us to implement state for each + * address space using a separate storage object. We do this by providing {@link TaintSpace}, which + * is where all the taint storage logic is actually located. We then use a map {@link #spaceMap} to + * lazily create a keep each of those spaces. + */ +public class TaintPcodeExecutorStatePiece + extends AbstractLongOffsetPcodeExecutorStatePiece { + + /** + * A lazily-populated map of address space to taint storage. + */ + protected final Map spaceMap = new HashMap<>(); + + /** + * Create a state piece + * + * @param language the emulator's language + * @param addressArithmetic the arithmetic for the address type + * @param arithmetic the arithmetic for the value type + * @param cb callbacks to receive emulation events + */ + public TaintPcodeExecutorStatePiece(Language language, + PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic, + PcodeStateCallbacks cb) { + super(language, addressArithmetic, arithmetic, cb); + } + + /** + * Create the taint piece + * + * @param language the language of the emulator + * @param addressArithmetic the address arithmetic, likely taken from the concrete piece + * @param cb callbacks to receive emulation events + */ + public TaintPcodeExecutorStatePiece(Language language, + PcodeArithmetic addressArithmetic, PcodeStateCallbacks cb) { + super(language, addressArithmetic, TaintPcodeArithmetic.forLanguage(language), cb); + } + + @Override + public TaintPcodeExecutorStatePiece fork(PcodeStateCallbacks cb) { + throw new UnsupportedOperationException(); + } + + @Override + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { + throw new ConcretionError("Cannot make Taint concrete", purpose); + } + + /** + * {@inheritDoc} + * + *

    + * Here, we just follow the pattern: delegate to the space map. + */ + @Override + protected TaintSpace getForSpace(AddressSpace space, boolean toWrite) { + if (toWrite) { + return spaceMap.computeIfAbsent(space, s -> new TaintSpace(space, this)); + } + return spaceMap.get(space); + } + + /** + * {@inheritDoc} + * + *

    + * Because the super class places no bound on {@code }, we have to provide the delegation to + * the storage space. + */ + @Override + protected void setInSpace(TaintSpace space, long offset, int size, TaintVec val, + PcodeStateCallbacks cb) { + space.set(offset, val, cb); + } + + /** + * {@inheritDoc} + * + *

    + * Because the super class places no bound on {@code }, we have to provide the delegation to + * the storage space. + */ + @Override + protected TaintVec getFromSpace(TaintSpace space, long offset, int size, Reason reason, + PcodeStateCallbacks cb) { + return space.get(offset, size, cb); + } + + @Override + protected Map getRegisterValuesFromSpace(TaintSpace space, + List registers) { + return space.getRegisterValues(registers); + } + + @Override + public void clear() { + for (TaintSpace space : spaceMap.values()) { + space.clear(); + } + } + + @Override + public Entry getNextEntryInternal(AddressSpace space, long offset) { + TaintSpace s = getForSpace(space, false); + if (s == null) { + return null; + } + return s.getNextEntry(offset); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/state/TaintPieceHandler.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/state/TaintPieceHandler.java new file mode 100644 index 0000000000..e5ec000a19 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/state/TaintPieceHandler.java @@ -0,0 +1,122 @@ +/* ### + * 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.emu.taint.state; + +import ghidra.pcode.exec.PcodeExecutorStatePiece; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.AbstractPropertyBasedPieceHandler; +import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; +import ghidra.program.model.address.*; +import ghidra.taint.model.TaintSet; +import ghidra.taint.model.TaintVec; + +/** + * The piece handler for {@link TaintVec} + * + *

    + * This contains the logic for integrating the Taint emulator with traces. That is, it is the + * mechanism that loads previous taint analysis from a trace and stores new results back into the + * trace. The object passed into these methods as {@code piece} is almost certainly a + * {@link TaintPcodeExecutorStatePiece}, but not necessarily. As a matter of best practice, it + * should not be necessary to cast. The given {@link PcodeExecutorStatePiece} interface should be + * sufficient as internals can often be reached via + * {@link PcodeExecutorStatePiece#getVarInternal(AddressSpace, long, int, Reason)}. + */ +public class TaintPieceHandler extends AbstractPropertyBasedPieceHandler { + /** + * The name we will use for the property map + */ + public static final String NAME = "Taint"; + + @Override + public Class getAddressDomain() { + return byte[].class; + } + + @Override + public Class getValueDomain() { + return TaintVec.class; + } + + @Override + protected String getPropertyName() { + return NAME; + } + + @Override + protected Class getPropertyType() { + return String.class; + } + + /** + * {@inheritDoc} + *

    + * The super class takes care of visiting each property map entry that may be involved. This + * method gets invoked for each one found, identifying the range to which the property value + * applies and the value itself. IMPORTANT: This implementation must still ensure it only + * modifies addresses that are not yet initialized. The set of such addresses is given by + * {@code limit}. Thus, we have the if-else to determine whether or not the found property entry + * is wholly contained within that limit. If not, then we have to piecemeal it. + *

    + * To insert each resulting entry into the state piece, we use + * {@link PcodeExecutorStatePiece#setVarInternal(AddressSpace, long, int, Object)}, so that we + * do not issue any follow-on callbacks. + */ + @Override + protected void decodeFrom(PcodeExecutorStatePiece piece, AddressSetView limit, + AddressRange range, String propertyValue) { + TaintVec vec = TaintVec.copies(TaintSet.parse(propertyValue), (int) range.getLength()); + if (limit.contains(range.getMaxAddress(), range.getMaxAddress())) { + piece.setVarInternal(range.getAddressSpace(), range.getMinAddress().getOffset(), + vec.length, vec); + } + else { + for (AddressRange sub : limit.intersectRange(range.getMinAddress(), + range.getMaxAddress())) { + int offset = (int) sub.getMinAddress().subtract(range.getMinAddress()); + TaintVec sv = vec.sub(offset, (int) sub.getLength()); + piece.setVarInternal(sub.getAddressSpace(), sub.getMinAddress().getOffset(), + sv.length, sv); + } + } + } + + /** + * {@inheritDoc} + *

    + * The super class takes care of iterating over the entries in the state piece, using + * {@link PcodeExecutorStatePiece#getNextEntryInternal(AddressSpace, long)}. In our case, since + * we coalesce identically-tainted contiguous bytes, serialization is fairly straightforward. + * The one nuances is that we'd rather not waste entries for addresses without any taint, and so + * we check for that and use {@code null} instead, which will cause the shim to clear the + * property on those addresses. + */ + @Override + protected void encodeInto(PcodeTracePropertyAccess property, AddressRange range, + TaintVec value) { + Address min = range.getMinAddress(); + for (int i = 0; i < value.length; i++) { + TaintSet s = value.get(i); + Address address = min.add(i); + if (s.isEmpty()) { + property.clear(new AddressRangeImpl(address, address)); + } + else { + property.put(address, s.toString()); + } + } + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/state/TaintSpace.java similarity index 62% rename from Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java rename to Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/state/TaintSpace.java index e5c1cdaa66..1ea4865618 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/state/TaintSpace.java @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.taint.plain; +package ghidra.pcode.emu.taint.state; import java.util.*; +import java.util.Map.Entry; -import ghidra.pcode.emu.taint.trace.TaintTraceSpace; +import ghidra.pcode.exec.PcodeStateCallbacks; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; import ghidra.taint.model.TaintSet; import ghidra.taint.model.TaintVec; +import ghidra.util.MathUtilities; /** * The storage space for taint sets in a single address space (possibly the register space) @@ -28,12 +31,19 @@ import ghidra.taint.model.TaintVec; *

    * This is the actual implementation of the in-memory storage for taint marks. For a stand-alone * emulator, this is the full state. For a trace- or Debugger-integrated emulator, this is a cache - * of taints loaded from a trace backing this emulator. Most likely, that trace is the user's - * current trace. + * of taints loaded from a trace backing this emulator. (See {@link TaintPieceHandler}.) Most + * likely, that trace is the user's current trace. */ public class TaintSpace { + protected final AddressSpace space; + protected final TaintPcodeExecutorStatePiece piece; // TODO: There must be a better way. Similar to SemisparseByteArray? - protected final Map taints = new HashMap<>(); + protected final NavigableMap taints = new TreeMap<>(Long::compareUnsigned); + + public TaintSpace(AddressSpace space, TaintPcodeExecutorStatePiece piece) { + this.space = space; + this.piece = piece; + } /** * Mark the variable at offset with the given taint sets @@ -46,8 +56,9 @@ public class TaintSpace { * * @param offset the starting offset * @param val the vector of taint sets + * @param cb callbacks to receive emulation events */ - public void set(long offset, TaintVec val) { + public void set(long offset, TaintVec val, PcodeStateCallbacks cb) { for (int i = 0; i < val.length; i++) { TaintSet s = val.get(i); /* @@ -56,6 +67,7 @@ public class TaintSpace { */ taints.put(offset + i, s); } + cb.dataWritten(piece, space.getAddress(offset), val.length, val); } /** @@ -70,11 +82,21 @@ public class TaintSpace { * * @param offset the offset * @param buf the vector to receive taint sets + * @param cb callbacks to receive emulation events */ - public void getInto(long offset, TaintVec buf) { + public void getInto(long offset, TaintVec buf, PcodeStateCallbacks cb) { for (int i = 0; i < buf.length; i++) { TaintSet s = taints.get(offset + i); - buf.set(i, s == null ? whenNull(offset + i) : s); + if (s == null) { + if (cb.readUninitialized(piece, PcodeStateCallbacks.rngSet(space, offset + i, 1)) + .isEmpty()) { + s = taints.get(offset + i); + } + } + if (s == null) { // still + s = TaintSet.EMPTY; + } + buf.set(i, s); } } @@ -82,33 +104,20 @@ public class TaintSpace { * Retrieve the taint sets for the variable at the given offset * *

    - * This works the same as {@link #getInto(long, TaintVec)}, but creates a new vector of the - * given size, reads the taint sets, and returns the vector. + * This works the same as {@link #getInto(long, TaintVec, PcodeStateCallbacks)}, but creates a + * new vector of the given size, reads the taint sets, and returns the vector. * * @param offset the offset * @param size the size of the variable + * @param cb callbacks to receive emulation events * @return the taint vector for that variable */ - public TaintVec get(long offset, int size) { + public TaintVec get(long offset, int size, PcodeStateCallbacks cb) { TaintVec vec = new TaintVec(size); - getInto(offset, vec); + getInto(offset, vec, cb); return vec; } - /** - * Extension point: Behavior when there is no in-memory taint set at the given offset - * - *

    - * This will be overridden by {@link TaintTraceSpace} to implement the lazy loading and - * deserialization from a trace. - * - * @param offset the offset - * @return the taint set to use - */ - protected TaintSet whenNull(long offset) { - return TaintSet.EMPTY; - } - public void clear() { taints.clear(); } @@ -128,4 +137,19 @@ public class TaintSpace { } return result; } + + public Entry getNextEntry(long offset) { + Long check = taints.ceilingKey(offset); + if (check == null) { + return null; + } + offset = check; + long end = offset; + while (taints.get(end) != null) { + end++; + } + TaintVec vec = new TaintVec(MathUtilities.unsignedMin(1024, end - offset)); + getInto(offset, vec, PcodeStateCallbacks.NONE); + return Map.entry(offset, vec); + } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java deleted file mode 100644 index 8a39341046..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java +++ /dev/null @@ -1,63 +0,0 @@ -/* ### - * 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.emu.taint.trace; - -import ghidra.pcode.emu.taint.TaintPartsFactory; -import ghidra.pcode.emu.taint.plain.TaintPcodeEmulator; -import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; -import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; -import ghidra.pcode.exec.trace.data.*; -import ghidra.taint.model.TaintVec; -import ghidra.trace.model.guest.TracePlatform; - -/** - * A trace-integrated emulator with taint analysis - */ -public class TaintTracePcodeEmulator extends AuxTracePcodeEmulator { - /** - * Create an emulator - * - * @param access the trace access shim - */ - public TaintTracePcodeEmulator(PcodeTraceAccess access) { - super(access); - } - - /** - * Create an emulator - * - * @param platform the platform to emulate - * @param snap the source snap - */ - public TaintTracePcodeEmulator(TracePlatform platform, long snap) { - super(platform, snap); - } - - /** - * {@inheritDoc} - * - *

    - * Here, we just return the singleton parts factory. This appears simple because all the - * complexity is encapsulated in the factory. See {@link TaintPartsFactory} to see everything - * the implementation actually entails. Notice that this is the same parts factory used by - * {@link TaintPcodeEmulator}. The {@link AuxTracePcodeEmulator} knows to use the more capable - * state parts. - */ - @Override - protected AuxTraceEmulatorPartsFactory getPartsFactory() { - return TaintPartsFactory.INSTANCE; - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java deleted file mode 100644 index d31022ac3c..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java +++ /dev/null @@ -1,54 +0,0 @@ -/* ### - * 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.emu.taint.trace; - -import ghidra.pcode.emu.taint.plain.TaintPcodeExecutorState; -import ghidra.pcode.exec.trace.*; -import ghidra.taint.model.TaintVec; - -/** - * A paired concrete-plus-taint trace-integrated state - * - *

    - * This contains the emulator's machine state along with the taint markings, just like - * {@link TaintPcodeExecutorState}, except that it can read and write state from a trace. In - * reality, this just composes concrete and taint state pieces, which actually do all the work. - */ -public class TaintTracePcodeExecutorState extends PairedTracePcodeExecutorState { - - /** - * Create a state from the two given pieces - * - * @param concrete the concrete piece - * @param taint the taint piece - */ - public TaintTracePcodeExecutorState(BytesTracePcodeExecutorStatePiece concrete, - TaintTracePcodeExecutorStatePiece taint) { - super(new PairedTracePcodeExecutorStatePiece<>(concrete, taint)); - } - - /** - * Create a state from the given concrete piece and an internally constructed taint piece - * - *

    - * We take the data access shim needed by the taint piece from the concrete piece. - * - * @param concrete the concrete piece - */ - public TaintTracePcodeExecutorState(BytesTracePcodeExecutorStatePiece concrete) { - this(concrete, new TaintTracePcodeExecutorStatePiece(concrete.getData())); - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java deleted file mode 100644 index 8ac03935c9..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java +++ /dev/null @@ -1,118 +0,0 @@ -/* ### - * 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.emu.taint.trace; - -import ghidra.pcode.emu.taint.AbstractTaintPcodeExecutorStatePiece; -import ghidra.pcode.emu.taint.TaintPcodeArithmetic; -import ghidra.pcode.exec.BytesPcodeArithmetic; -import ghidra.pcode.exec.trace.TracePcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; -import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; -import ghidra.program.model.address.AddressSpace; -import ghidra.taint.model.TaintVec; -import ghidra.trace.model.property.TracePropertyMapSpace; - -/** - * The trace-integrated state piece for holding taint marks - * - *

    - * See {@link AbstractTaintPcodeExecutorStatePiece} for framing. We'll store taint sets in the - * trace's address property map, which is the recommended scheme for auxiliary state. - */ -public class TaintTracePcodeExecutorStatePiece - extends AbstractTaintPcodeExecutorStatePiece - implements TracePcodeExecutorStatePiece { - public static final String NAME = "Taint"; - - protected final PcodeTraceDataAccess data; - protected final PcodeTracePropertyAccess property; - - /** - * Create a state piece - * - * @param data the trace-data access shim - */ - public TaintTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - super(data.getLanguage(), - BytesPcodeArithmetic.forLanguage(data.getLanguage()), - TaintPcodeArithmetic.forLanguage(data.getLanguage())); - this.data = data; - this.property = data.getPropertyAccess(NAME, String.class); - } - - @Override - public PcodeTraceDataAccess getData() { - return data; - } - - /** - * {@inheritDoc} - * - *

    - * Here we create a map that uses {@link TaintTraceSpace}s. The framework provides the concept - * of a space map where storage is actually a cache backed by some other object. The backing - * object we'll use here is {@link TracePropertyMapSpace}, which is provided by the - * TraceModeling module. We'll need a little bit of extra logic for fetching a register space - * vs. a plain memory space, but after that, we need not care which address space the backing - * object is for. - */ - @Override - protected AbstractSpaceMap newSpaceMap() { - return new CacheingSpaceMap, TaintTraceSpace>() { - @Override - protected PcodeTracePropertyAccess getBacking(AddressSpace space) { - return property; - } - - @Override - protected TaintTraceSpace newSpace(AddressSpace space, - PcodeTracePropertyAccess backing) { - return new TaintTraceSpace(space, property); - } - - @Override - public AbstractSpaceMap fork() { - throw new UnsupportedOperationException(); - } - - @Override - public TaintTraceSpace fork(TaintTraceSpace s) { - throw new UnsupportedOperationException(); - } - }; - } - - @Override - public TaintTracePcodeExecutorStatePiece fork() { - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} - * - *

    - * This does the inverse of the lazy loading. Serialize the state and store it back into the - * trace. Technically, it could be a different trace, but it must have identically-named - * threads. - */ - @Override - public void writeDown(PcodeTraceDataAccess into) { - PcodeTracePropertyAccess property = into.getPropertyAccess(NAME, String.class); - for (TaintTraceSpace space : spaceMap.values()) { - space.writeDown(property); - } - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java deleted file mode 100644 index 3a87b2a840..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java +++ /dev/null @@ -1,91 +0,0 @@ -/* ### - * 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.emu.taint.trace; - -import java.util.Map.Entry; - -import ghidra.pcode.emu.taint.plain.TaintSpace; -import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; -import ghidra.program.model.address.*; -import ghidra.taint.model.TaintSet; - -/** - * The storage space for taint sets in a trace's address space - * - *

    - * This adds to {@link TaintSpace} the ability to load taint sets from a trace and the ability to - * save them back into a trace. - */ -public class TaintTraceSpace extends TaintSpace { - protected final AddressSpace space; - protected final PcodeTracePropertyAccess property; - - /** - * Create the space - * - * @param space the address space - * @param property the trace property backing this space - */ - public TaintTraceSpace(AddressSpace space, PcodeTracePropertyAccess property) { - this.space = space; - this.property = property; - } - - /** - * {@inheritDoc} - * - *

    - * The taint space will call this when the cache misses, allowing us to populate it with a taint - * set stored in the trace. Note that if the emulator writes to this offset before - * reading it, this will not get called for that offset. Here we simply get the string property - * and parse the taint set. - */ - @Override - protected TaintSet whenNull(long offset) { - String string = property.get(space.getAddress(offset)); - if (string == null) { - return TaintSet.EMPTY; - } - return TaintSet.parse(string); - } - - /** - * Write this cache back down into a trace - * - *

    - * Here we simply iterate over every entry in this space, serialize the taint, and put it into - * the property at the entry's offset. If the taint set is empty, we clear the property rather - * than putting the empty taint set into the property. - * - * @param into the trace-property access to write into - */ - public void writeDown(PcodeTracePropertyAccess into) { - if (space.isUniqueSpace()) { - return; - } - - for (Entry entry : taints.entrySet()) { - TaintSet taint = entry.getValue(); - Address address = space.getAddress(entry.getKey()); - if (taint.isEmpty()) { - into.clear(new AddressRangeImpl(address, address)); - } - else { - into.put(address, taint.toString()); - } - } - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/package-info.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/package-info.java deleted file mode 100644 index 990d0bc1a9..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/package-info.java +++ /dev/null @@ -1,35 +0,0 @@ -/* ### - * 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. - */ -/** - * The trace-integrated Taint Emulator - * - *

    - * This package builds on the emulation framework to construct a trace-integrated emulator. The - * framework's state components were designed to accommodate the state components introduced by this - * package. - * - *

    - * For this package, I recommend a bottom-up approach, since you should already be familiar with the - * parts factory and the structure of the stand-alone state part. - * {@link ghidra.pcode.emu.taint.trace.TaintTraceSpace} adds the ability to read and write taint - * sets from a trace. {@link ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece} works - * that into a state piece derived from - * {@link ghidra.pcode.emu.taint.plain.TaintPcodeExecutorStatePiece}. Then, - * {@link ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorState} composes that with a given - * concrete state piece. The factory creates that state for use by the - * {@link ghidra.pcode.emu.taint.trace.TaintTracePcodeEmulator}. - */ -package ghidra.pcode.emu.taint.trace; diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java index 9ad98e2380..acd1ca8411 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java @@ -4,9 +4,9 @@ * 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. @@ -22,7 +22,7 @@ import ghidra.app.plugin.core.debug.gui.register.RegisterRow; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.ServiceProvider; -import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; +import ghidra.pcode.emu.taint.state.TaintPieceHandler; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; @@ -38,7 +38,7 @@ import ghidra.trace.model.property.TracePropertyMapSpace; * screen. */ public class TaintDebuggerRegisterColumnFactory implements DebuggerRegisterColumnFactory { - protected static final String PROP_NAME = TaintTracePcodeExecutorStatePiece.NAME; + protected static final String PROP_NAME = TaintPieceHandler.NAME; public static final String COL_NAME = "Taint"; @Override diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java index a3cb5b9b41..a19672b322 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java @@ -4,9 +4,9 @@ * 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. @@ -27,7 +27,7 @@ import ghidra.app.util.viewer.format.FieldFormatModel; import ghidra.app.util.viewer.proxy.ProxyObj; import ghidra.framework.options.Options; import ghidra.framework.options.ToolOptions; -import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; +import ghidra.pcode.emu.taint.state.TaintPieceHandler; import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.util.StringPropertyMap; import ghidra.program.util.ProgramLocation; @@ -42,7 +42,7 @@ import ghidra.taint.model.TaintVec; * framework. I used the "sample" module's {@code EntropyFieldFactory} for reference. */ public class TaintFieldFactory extends FieldFactory { - public static final String PROPERTY_NAME = TaintTracePcodeExecutorStatePiece.NAME; + public static final String PROPERTY_NAME = TaintPieceHandler.NAME; public static final GColor COLOR = new GColor("color.fg.listing.taint"); public static final String FIELD_NAME = "Taint"; @@ -50,7 +50,8 @@ public class TaintFieldFactory extends FieldFactory { super(FIELD_NAME); } - protected TaintFieldFactory(FieldFormatModel formatModel, ListingHighlightProvider highlightProvider, + protected TaintFieldFactory(FieldFormatModel formatModel, + ListingHighlightProvider highlightProvider, Options displayOptions, Options fieldOptions) { super(FIELD_NAME, formatModel, highlightProvider, displayOptions, fieldOptions); } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintVec.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintVec.java index 6800bfdbe3..eade39ed16 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintVec.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintVec.java @@ -4,9 +4,9 @@ * 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. @@ -32,6 +32,11 @@ import java.util.stream.Stream; * do not (yet) have a {@code parse(String)} method. */ public class TaintVec { + + public static TaintVec of(TaintSet... taints) { + return new TaintVec(taints); + } + /** * Create a vector of empty taint sets * @@ -75,15 +80,19 @@ public class TaintVec { private List setsView; public final int length; + private TaintVec(TaintSet[] sets) { + this.sets = sets; + this.setsView = Collections.unmodifiableList(Arrays.asList(sets)); + this.length = sets.length; + } + /** * Create a new uninitialized taint vector of the given length * * @param length the length */ public TaintVec(int length) { - this.sets = new TaintSet[length]; - this.setsView = Collections.unmodifiableList(Arrays.asList(sets)); - this.length = sets.length; + this(new TaintSet[length]); } @Override @@ -356,7 +365,25 @@ public class TaintVec { return this; } + /** + * Common shifting behaviors + */ public enum ShiftMode { + /** + * No bound is applied to the shift. Values that fall off the edge are dropped. Furthermore, + * if the shift is greater than the length, all the values will fall off the edge and be + * dropped. + * + *

    +		 * +---+------+
    +		 * | 0 | 1234 |
    +		 * | 1 | _123 |
    +		 * | 2 | __12 |
    +		 * | 3 | ___1 |
    +		 * | 4 | ____ |
    +		 * +---+------+
    +		 * 
    + */ UNBOUNDED { @Override int adjustRight(int right, int length) { @@ -368,6 +395,20 @@ public class TaintVec { return src; } }, + /** + * Only the lowest required bits are taken for the shift amount, i.e., the remainder when + * divided by the length, often a power of 2. Values that fall off the edge are dropped. + * + *
    +		 * +---+------+
    +		 * | 0 | 1234 |
    +		 * | 1 | _123 |
    +		 * | 2 | __12 |
    +		 * | 3 | ___1 |
    +		 * | 4 | 1234 | (Only the lowest 2 bits of the shift amount are considered)
    +		 * +---+------+
    +		 * 
    + */ REMAINDER { @Override int adjustRight(int right, int length) { @@ -379,6 +420,21 @@ public class TaintVec { return src; } }, + /** + * Only the lowest required bits are taken for the shift amount, i.e., the remainder when + * divided by the length, often a power of 2. (Even if unbounded, a circular shift yields + * the same result.) Values that fall off the edge are cycled to the opposite end. + * + *
    +		 * +---+------+
    +		 * | 0 | 1234 |
    +		 * | 1 | 4123 |
    +		 * | 2 | 3412 |
    +		 * | 3 | 2341 |
    +		 * | 4 | 1234 |
    +		 * +---+------+
    +		 * 
    + */ CIRCULAR { @Override int adjustRight(int right, int length) { @@ -404,6 +460,7 @@ public class TaintVec { * Shift this vector some number of elements, in place * * @param right the number of elements to shift right, or negative for left + * @param mode the behavior of the shift * @return this vector */ public TaintVec setShifted(int right, ShiftMode mode) { @@ -451,7 +508,7 @@ public class TaintVec { TaintVec vec = new TaintVec(length); int shift = isBigEndian ? this.length - length : 0; for (int i = 0; i < length; i++) { - vec.sets[i] = vec.sets[i + shift]; + vec.sets[i] = this.sets[i + shift]; } return vec; } @@ -501,4 +558,19 @@ public class TaintVec { } return vec; } + + /** + * Extract a subpiece of this vector + * + * @param offset the offset into this vector + * @param length the number of sets to extract + * @return the resulting vector + */ + public TaintVec sub(int offset, int length) { + TaintVec vec = new TaintVec(length); + for (int i = 0; i < length; i++) { + vec.sets[i] = this.sets[i + offset]; + } + return vec; + } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/package-info.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/package-info.java index 49b44fecd7..748f12f686 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/package-info.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/package-info.java @@ -4,9 +4,9 @@ * 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. @@ -31,31 +31,17 @@ * domain, see the {@link ghidra.taint.model} package. * *

    - * Next, we implement the stand-alone emulator using - * {@link ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory}. Technically, that interface - * requires more than necessary for a stand-alone emulator, but because a fully-integrated emulator - * is the goal, you can start with it and leave its methods stubbed until you actually need them. + * Next, we implement the emulator using {@link ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory}. * The implementation of each method will move our attention to each part necessary to construct the - * emulator. See the {@link ghidra.pcode.emu.taint.plain} package. The emulator itself - * {@link ghidra.pcode.emu.taint.plain.TaintPcodeEmulator} is trivially derived from + * emulator. See the {@link ghidra.pcode.emu.taint.state} package. The emulator itself + * {@link ghidra.pcode.emu.taint.TaintPcodeEmulator} is trivially derived from * {@link ghidra.pcode.emu.auxiliary.AuxPcodeEmulator} and our factory. * *

    - * Next, we implement the trace-integrated emulator. For this, we just implement two more methods: - * {@link ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory#createTraceSharedState(ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator, ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece)} - * and - * {@link ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory#createTraceLocalState(ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator, ghidra.pcode.emu.PcodeThread, ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece)}. - * Then we derive {@link ghidra.pcode.emu.taint.trace.TaintTracePcodeEmulator} trivially from - * {@link ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator} and our factory. See the - * {@link ghidra.pcode.emu.taint.trace} package. - * - *

    - * Next, in like fashion, we implement and derive the Debugger-integrated emulator. See the - * {@link ghidra.pcode.emu.taint.full} package. - * - *

    - * Finally, we add some UI components to make the emulator's machine state visible to the user. - * These are in the {@link ghidra.taint.gui.field} package. + * Next, we provide trace integration by implementing + * {@link ghidra.pcode.emu.taint.state.TaintPieceHandler}. Finally, we add some UI components to + * make the emulator's machine state visible to the user. These are in the + * {@link ghidra.taint.gui.field} package. * *

    * There is a not-yet-integrated user-op library for tainting file reads. See diff --git a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java index c8f8a8b942..dad4a21044 100644 --- a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java +++ b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java @@ -32,9 +32,11 @@ import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingService import ghidra.app.services.DebuggerEmulationService; import ghidra.app.services.DebuggerEmulationService.EmulationResult; import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.taint.TaintEmulatorFactory; +import ghidra.pcode.emu.taint.TaintPcodeEmulator; +import ghidra.pcode.emu.taint.state.TaintPieceHandler; import ghidra.pcode.emu.taint.trace.TaintTracePcodeEmulatorTest; -import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; import ghidra.program.model.util.StringPropertyMap; import ghidra.program.util.ProgramLocation; import ghidra.trace.database.ToyDBTraceBuilder.ToySchemaBuilder; @@ -61,7 +63,7 @@ public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger assertEquals(1, emuService.getEmulatorFactories() .stream() - .filter(f -> f instanceof TaintDebuggerPcodeEmulatorFactory) + .filter(f -> f instanceof TaintEmulatorFactory) .count()); } @@ -74,7 +76,7 @@ public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger @Test public void testFactoryCreate() throws Exception { - emuService.setEmulatorFactory(new TaintDebuggerPcodeEmulatorFactory()); + emuService.setEmulatorFactory(new TaintEmulatorFactory()); createAndOpenTrace(); @@ -99,13 +101,13 @@ public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger } }); - DebuggerPcodeMachine emu = emuService.getCachedEmulator(tb.trace, result.schedule()); - assertTrue(emu instanceof TaintDebuggerPcodeEmulator); + PcodeMachine emu = emuService.getCachedEmulator(tb.trace, result.schedule()); + assertTrue(emu instanceof TaintPcodeEmulator); } - @Test + // @Test // I've decided to remove this feature. public void testReadsProgramUsrProperties() throws Exception { - emuService.setEmulatorFactory(new TaintDebuggerPcodeEmulatorFactory()); + emuService.setEmulatorFactory(new TaintEmulatorFactory()); createAndOpenTrace("x86:LE:64:default"); createProgramFromTrace(); @@ -135,7 +137,7 @@ public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger .createInitializedBlock(".text", tb.addr(0x00400000), 0x1000, (byte) 0, TaskMonitor.DUMMY, false); StringPropertyMap progTaintMap = program.getUsrPropertyManager() - .createStringPropertyMap(TaintTracePcodeExecutorStatePiece.NAME); + .createStringPropertyMap(TaintPieceHandler.NAME); progTaintMap.add(tb.addr(0x00400800), "test_0"); Assembler asm = Assemblers.getAssembler(program); @@ -147,7 +149,7 @@ public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger long scratch = emuService.emulate(tb.trace, time, TaskMonitor.DUMMY); TracePropertyMap traceTaintMap = tb.trace.getAddressPropertyManager() - .getPropertyMap(TaintTracePcodeExecutorStatePiece.NAME, String.class); + .getPropertyMap(TaintPieceHandler.NAME, String.class); TracePropertyMapSpace taintRegSpace = traceTaintMap.getPropertyMapRegisterSpace(thread, 0, false); diff --git a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulatorTest.java b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulatorTest.java index b715a3453e..bd90de8c9c 100644 --- a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulatorTest.java +++ b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulatorTest.java @@ -29,6 +29,7 @@ import ghidra.pcode.emu.linux.AbstractEmuLinuxSyscallUseropLibrary; import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibraryTest; import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibraryTest.Syscall; import ghidra.pcode.emu.sys.EmuProcessExitedException; +import ghidra.pcode.emu.taint.TaintPcodeEmulator; import ghidra.pcode.emu.taint.lib.TaintEmuUnixFileSystem; import ghidra.pcode.emu.taint.lib.TaintFileReadsLinuxAmd64SyscallLibrary; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; diff --git a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java index 4387924161..be03b6773a 100644 --- a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java +++ b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java @@ -29,8 +29,11 @@ import org.junit.Test; import db.Transaction; import ghidra.app.plugin.assembler.*; import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.taint.TaintEmulatorFactory; +import ghidra.pcode.emu.taint.TaintPcodeEmulator; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.exec.trace.AbstractTracePcodeEmulatorTest; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; @@ -39,6 +42,7 @@ import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.target.DBTraceObjectManager; import ghidra.trace.model.*; import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryManager; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.property.TracePropertyMap; @@ -49,6 +53,17 @@ import ghidra.trace.model.thread.TraceThread; public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest { + @Override + protected Writer createWriter(TracePlatform platform, long snap) { + Writer writer = super.createWriter(platform, snap); + TaintEmulatorFactory.addHandlers(writer); + return writer; + } + + TaintPcodeEmulator createEmulator(TracePlatform platform, Writer writer) { + return new TaintPcodeEmulator(platform.getLanguage(), writer.callbacks()); + } + public static Map.Entry makeTaintEntry(Trace trace, Lifespan span, AddressSpace space, long offset, String taint) { Address addr = space.getAddress(offset); @@ -68,6 +83,8 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest * *

    * We isolate exactly a read by executing sleigh. + * + * @throws Throwable because */ @Test public void testReadStateMemory() throws Throwable { @@ -80,7 +97,8 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest taintMap.set(Lifespan.nowOn(0), tb.range(0x00400000, 0x00400003), "test_0"); } - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + TaintPcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getExecutor().executeSleigh("RAX = *0x00400000:8;"); @@ -110,7 +128,8 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest mapSpace.set(Lifespan.nowOn(0), regEBX, "test_0"); } - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + TaintPcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getExecutor().executeSleigh("RAX = RBX;"); @@ -130,7 +149,8 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { initTrace(tb, "", List.of()); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + TaintPcodeEmulator emu = createEmulator(tb.host, writer); TaintVec taintVal = TaintVec.empties(8); TaintSet testTaint = TaintSet.of(new TaintMark("test_0", Set.of())); for (int i = 0; i < 4; i++) { @@ -141,7 +161,7 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest Pair.of(tb.arr(0, 0, 0, 0, 0, 0, 0, 0), taintVal)); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 0); + writer.writeDown(1); } TracePropertyMap taintMap = tb.trace.getAddressPropertyManager().getPropertyMap("Taint", String.class); @@ -156,9 +176,10 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { TraceThread thread = initTrace(tb, "", List.of()); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + TaintPcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); - TaintVec taintVal = TaintVec.empties(8); + TaintVec taintVal = TaintVec.empties(4); TaintSet testTaint = TaintSet.of(new TaintMark("test_0", Set.of())); for (int i = 0; i < 4; i++) { taintVal.set(i, testTaint); @@ -166,7 +187,7 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.getState().setVar(tb.reg("EAX"), Pair.of(tb.arr(0, 0, 0, 0), taintVal)); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 0); + writer.writeDown(1); } TracePropertyMap taintMap = tb.trace.getAddressPropertyManager().getPropertyMap("Taint", String.class); @@ -192,7 +213,8 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "MOV qword ptr [0x00600000], RAX", "MOV qword ptr [0x00600000], RBX")); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + TaintPcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getState() .setVar(tb.reg("RAX"), Pair.of( @@ -201,11 +223,11 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 0); + writer.writeDown(1); } emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 2, 0); + writer.writeDown(2); } TracePropertyMap taintMap = @@ -229,19 +251,20 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "XOR RAX, RAX")); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + TaintPcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getState() .setVar(tb.reg("RAX"), Pair.of( tb.arr(1, 2, 3, 4, 5, 6, 7, 8), TaintVec.copies(TaintSet.parse("test_0"), 8))); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 0, 0); + writer.writeDown(0); } emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 0); + writer.writeDown(1); } TracePropertyMap taintMap = @@ -263,19 +286,20 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "XOR EAX, EAX")); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + TaintPcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getState() .setVar(tb.reg("RAX"), Pair.of( tb.arr(1, 2, 3, 4, 5, 6, 7, 8), TaintVec.copies(TaintSet.parse("test_0"), 8))); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 0, 0); + writer.writeDown(0); } emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 0); + writer.writeDown(1); } TracePropertyMap taintMap = @@ -319,7 +343,8 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest mm.putBytes(0, tb.addr(0x00000000), ByteBuffer.wrap(buf.getBytes())); } - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(x64, 0); + Writer writer = createWriter(x64, 0); + TaintPcodeEmulator emu = createEmulator(x64, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getState() .setVar(tb.reg(x64, "RAX"), Pair.of( @@ -328,11 +353,11 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(x64, 1, 0); + writer.writeDown(1); } emuThread.stepInstruction(); try (Transaction tx = tb.startTransaction()) { - emu.writeDown(x64, 2, 0); + writer.writeDown(2); } TracePropertyMap taintMap = diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/AbstractSymZ3OffsetPcodeExecutorStatePiece.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/AbstractSymZ3OffsetPcodeExecutorStatePiece.java index 88a7fc6d7a..59d220d9b4 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/AbstractSymZ3OffsetPcodeExecutorStatePiece.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/AbstractSymZ3OffsetPcodeExecutorStatePiece.java @@ -16,13 +16,12 @@ package ghidra.pcode.emu.symz3; import java.math.BigInteger; -import java.util.*; +import java.util.stream.Stream; import com.microsoft.z3.BitVecNum; import com.microsoft.z3.Context; -import ghidra.pcode.exec.PcodeArithmetic; -import ghidra.pcode.exec.PcodeExecutorStatePiece; +import ghidra.pcode.exec.*; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; import ghidra.symz3.model.SymValueZ3; @@ -39,82 +38,11 @@ import ghidra.util.Msg; */ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece implements PcodeExecutorStatePiece { - /** - * A map of address spaces to objects which store or cache state for that space - * - * @param the type of object for each address space - */ - public abstract static class AbstractSpaceMap { - protected final Map spaces = new HashMap<>(); - - public abstract S getForSpace(AddressSpace space, boolean toWrite); - - public Collection values() { - return spaces.values(); - } - } - - /** - * Use this when each S contains the complete state for the address space - * - * @param the type of object for each address space - */ - public abstract static class SimpleSpaceMap extends AbstractSpaceMap { - /** - * Construct a new space internally associated with the given address space - * - *

    - * As the name implies, this often simply wraps {@code S}'s constructor - * - * @param space the address space - * @return the new space - */ - protected abstract S newSpace(AddressSpace space); - - @Override - public S getForSpace(AddressSpace space, boolean toWrite) { - return spaces.computeIfAbsent(space, s -> newSpace(s)); - } - } - - /** - * Use this when each S is possibly a cache to some other state (backing) object - * - * @param the type of the object backing the cache for each address space - * @param the type of cache for each address space - */ - public abstract static class CacheingSpaceMap extends AbstractSpaceMap { - /** - * Get the object backing the cache for the given address space - * - * @param space the space - * @return the backing object - */ - protected abstract B getBacking(AddressSpace space); - - /** - * Construct a new space internally associated with the given address space, having the - * given backing - * - *

    - * As the name implies, this often simply wraps {@code S}'s constructor - * - * @param space the address space - * @param backing the backing, if applicable. null for the unique space - * @return the new space - */ - protected abstract S newSpace(AddressSpace space, B backing); - - @Override - public S getForSpace(AddressSpace space, boolean toWrite) { - return spaces.computeIfAbsent(space, - s -> newSpace(s, s.isUniqueSpace() ? null : getBacking(s))); - } - } protected final Language language; protected final PcodeArithmetic addressArithmetic; protected final PcodeArithmetic arithmetic; + protected final PcodeStateCallbacks cb; protected final AddressSpace uniqueSpace; /** @@ -123,12 +51,15 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece * @param language the language (used for its memory model) * @param addressArithmetic the arithmetic used for addresses * @param arithmetic an arithmetic used to generate default values of {@code T} + * @param cb callbacks to receive emulation events */ public AbstractSymZ3OffsetPcodeExecutorStatePiece(Language language, - PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic) { + PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic, + PcodeStateCallbacks cb) { this.language = language; this.addressArithmetic = addressArithmetic; this.arithmetic = arithmetic; + this.cb = cb; uniqueSpace = language.getAddressFactory().getUniqueSpace(); } @@ -147,6 +78,11 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece return arithmetic; } + @Override + public Stream> streamPieces() { + return Stream.of(this); + } + /** * Set a value in the unique space * @@ -158,9 +94,9 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece * @param size the number of bytes to write (the size of the value) * @param val the value to store */ - protected void setUnique(SymValueZ3 offset, int size, SymValueZ3 val) { + protected void setUnique(SymValueZ3 offset, int size, SymValueZ3 val, PcodeStateCallbacks cb) { S s = getForSpace(uniqueSpace, true); - setInSpace(s, offset, size, val); + setInSpace(s, offset, size, val, cb); } /** @@ -173,9 +109,9 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece * @param size the number of bytes to read (the size of the value) * @return the read value */ - protected SymValueZ3 getUnique(SymValueZ3 offset, int size) { + protected SymValueZ3 getUnique(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { S s = getForSpace(uniqueSpace, false); - return getFromSpace(s, offset, size); + return getFromSpace(s, offset, size, cb); } /** @@ -196,8 +132,10 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece * @param offset the offset within the space * @param size the number of bytes to write (the size of the value) * @param val the value to store + * @param cb callbacks to receive emulation events */ - protected abstract void setInSpace(S space, SymValueZ3 offset, int size, SymValueZ3 val); + protected abstract void setInSpace(S space, SymValueZ3 offset, int size, SymValueZ3 val, + PcodeStateCallbacks cb); /** * Get a value from the given space @@ -205,9 +143,11 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece * @param space the address space * @param offset the offset within the space * @param size the number of bytes to read (the size of the value) + * @param cb callbacks to receive emulation events * @return the read value */ - protected abstract SymValueZ3 getFromSpace(S space, SymValueZ3 offset, int size); + protected abstract SymValueZ3 getFromSpace(S space, SymValueZ3 offset, int size, + PcodeStateCallbacks cb); /** * In case spaces are generated lazily, and we're reading from a space that doesn't yet exist, @@ -219,16 +159,14 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece * @param size the number of bytes to read (the size of the value) * @return the default value */ - protected SymValueZ3 getFromNullSpace(int size) { + protected SymValueZ3 getFromNullSpace(int size, PcodeStateCallbacks cb) { Msg.warn(this, "getFromNullSpace is returning 0 but that might not be what we want for symz3"); return arithmetic.fromConst(0, size); } - @Override - public void setVar(AddressSpace space, SymValueZ3 offset, int size, boolean quantize, - SymValueZ3 val) { - + protected void setVarInternal(AddressSpace space, SymValueZ3 offset, int size, boolean quantize, + SymValueZ3 val, PcodeStateCallbacks cb) { //Msg.info(this, "setVar for space: " + space + " offset: " + offset + " size: " + size + " val: " + val); assert val != null; assert offset != null; @@ -246,7 +184,7 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece throw new IllegalArgumentException("Cannot write to constant space"); } if (space.isUniqueSpace()) { - setUnique(offset, size, val); + setUnique(offset, size, val, cb); return; } S s = getForSpace(space, true); @@ -256,12 +194,22 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece * convert to long, and quantize. You could also express the quantization symbolically, but * it rarely comes up. */ - setInSpace(s, offset, size, val); + setInSpace(s, offset, size, val, cb); } @Override - public SymValueZ3 getVar(AddressSpace space, SymValueZ3 offset, int size, boolean quantize, - Reason reason) { + public void setVar(AddressSpace space, SymValueZ3 offset, int size, boolean quantize, + SymValueZ3 val) { + setVarInternal(space, offset, size, quantize, val, cb); + } + + @Override + public void setVarInternal(AddressSpace space, SymValueZ3 offset, int size, SymValueZ3 val) { + setVarInternal(space, offset, size, false, val, PcodeStateCallbacks.NONE); + } + + protected SymValueZ3 getVarInternal(AddressSpace space, SymValueZ3 offset, int size, + boolean quantize, Reason reason, PcodeStateCallbacks cb) { //checkRange(space, offset, size); //Msg.info(this, "getVar for space: " + space + " offset: " + offset + " size: " + size + " quantize: " + quantize); if (space.isConstantSpace()) { @@ -281,14 +229,26 @@ public abstract class AbstractSymZ3OffsetPcodeExecutorStatePiece } } if (space.isUniqueSpace()) { - return getUnique(offset, size); + return getUnique(offset, size, cb); } S s = getForSpace(space, false); //Msg.info(this, "Now we likely have a space to get from: " + s); if (s == null) { - return getFromNullSpace(size); + return getFromNullSpace(size, cb); } //offset = quantizeOffset(space, offset); - return getFromSpace(s, offset, size); + return getFromSpace(s, offset, size, cb); + } + + @Override + public SymValueZ3 getVar(AddressSpace space, SymValueZ3 offset, int size, boolean quantize, + Reason reason) { + return getVarInternal(space, offset, size, quantize, reason, cb); + } + + @Override + public SymValueZ3 getVarInternal(AddressSpace space, SymValueZ3 offset, int size, + Reason reason) { + return getVarInternal(space, offset, size, false, reason, PcodeStateCallbacks.NONE); } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulatorFactory.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3EmulatorFactory.java similarity index 52% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulatorFactory.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3EmulatorFactory.java index 13ee57a27b..e410508a41 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulatorFactory.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3EmulatorFactory.java @@ -13,11 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.symz3.full; +package ghidra.pcode.emu.symz3; -import ghidra.app.plugin.core.debug.service.emulation.AbstractDebuggerPcodeEmulatorFactory; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; +import ghidra.debug.api.emulation.EmulatorFactory; import ghidra.debug.api.emulation.PcodeDebuggerAccess; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.symz3.state.SymZ3PcodeEmulator; +import ghidra.pcode.emu.symz3.state.SymZ3PieceHandler; +import ghidra.pcode.exec.trace.TraceEmulationIntegration; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; +import ghidra.pcode.exec.trace.data.PcodeTraceAccess; /** * An emulator factory for making the {@link SymZ3DebuggerPcodeEmulator} discoverable to the UI @@ -26,7 +31,17 @@ import ghidra.debug.api.emulation.PcodeDebuggerAccess; * This is the final class to create a full Debugger-integrated emulator. This class is what makes * it appear in the menu of possible emulators the user may configure. */ -public class SymZ3DebuggerPcodeEmulatorFactory extends AbstractDebuggerPcodeEmulatorFactory { +public class SymZ3EmulatorFactory implements EmulatorFactory { + + public static Writer delayedWriteTrace(PcodeTraceAccess access) { + Writer writer = TraceEmulationIntegration.bytesDelayedWrite(access); + addHandlers(writer); + return writer; + } + + public static void addHandlers(Writer writer) { + writer.putHandler(new SymZ3PieceHandler()); + } @Override public String getTitle() { @@ -34,7 +49,8 @@ public class SymZ3DebuggerPcodeEmulatorFactory extends AbstractDebuggerPcodeEmul } @Override - public DebuggerPcodeMachine create(PcodeDebuggerAccess access) { - return new SymZ3DebuggerPcodeEmulator(access); + public PcodeMachine create(PcodeDebuggerAccess access, Writer writer) { + addHandlers(writer); + return new SymZ3PcodeEmulator(access.getLanguage(), writer.callbacks()); } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3MemoryMap.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3MemoryMap.java index 57a76b6c8d..3db137f2fd 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3MemoryMap.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3MemoryMap.java @@ -24,6 +24,7 @@ import com.microsoft.z3.*; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; import ghidra.pcode.emu.symz3.lib.Z3MemoryWitness; import ghidra.pcode.emu.symz3.lib.Z3MemoryWitness.WitnessType; +import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.program.model.lang.Language; import ghidra.symz3.model.SymValueZ3; import ghidra.util.Msg; @@ -67,6 +68,8 @@ import ghidra.util.Msg; public class SymZ3MemoryMap { // TODO ... encapsulate traversal of memvals so it can become private public Map memvals; + private NavigableMap byOffset; + private List witnesses; private Language language; @@ -80,9 +83,9 @@ public class SymZ3MemoryMap { } public SymZ3MemoryMap(Language language) { - memvals = new HashMap(); + memvals = new HashMap<>(); this.language = language; - witnesses = new ArrayList(); + witnesses = new ArrayList<>(); } protected Entry valuationForMemval(Context ctx, Z3InfixPrinter z3p, @@ -110,7 +113,7 @@ public class SymZ3MemoryMap { if (!reported.add(addressExpr)) { return null; } - SymValueZ3 vv = load(w.address(), w.bytesMoved(), false); + SymValueZ3 vv = load(w.address(), w.bytesMoved(), false, PcodeStateCallbacks.NONE); BitVecExpr v = vv.getBitVecExpr(ctx); if (v == null) { return Map.entry("MEM " + z3p.infixWithBrackets(addressExpr), "null (?)"); @@ -159,7 +162,8 @@ public class SymZ3MemoryMap { continue; } reported.add(addressExpr); - SymValueZ3 value = load(w.address(), w.bytesMoved(), false); + SymValueZ3 value = + load(w.address(), w.bytesMoved(), false, PcodeStateCallbacks.NONE); BitVecExpr vexpr = value.getBitVecExpr(ctx); if (vexpr == null) { result.append("MEM " + z3p.infixWithBrackets(addressExpr) + " is null (?)"); @@ -192,7 +196,8 @@ public class SymZ3MemoryMap { return Stream.concat(forMemVals, forWitnesses); } - public SymValueZ3 load(SymValueZ3 offset, int size, boolean addWitness) { + public SymValueZ3 load(SymValueZ3 offset, int size, boolean addWitness, + PcodeStateCallbacks cb) { try (Context ctx = new Context()) { if (addWitness) { witnesses.add(new Z3MemoryWitness(offset, size, WitnessType.LOAD)); @@ -273,6 +278,7 @@ public class SymZ3MemoryMap { // this is the primary advantage of the non-byte based model, storage is super easy Msg.debug(this, "set memory location " + address + " size " + size + " to " + val); memvals.put(offset.bitVecExprString, val); + byOffset = null; } else { // for the byte-based model, we simply must store each byte separately. @@ -299,6 +305,7 @@ public class SymZ3MemoryMap { } BitVecExpr valportion = ctx.mkExtract(high, low, bval); memvals.put(byteAddressAsString, new SymValueZ3(ctx, valportion)); + byOffset = null; } } } @@ -314,4 +321,19 @@ public class SymZ3MemoryMap { } return false; } + + public Entry getNextEntry(long offset) { + if (byOffset == null) { + byOffset = new TreeMap<>(Long::compareUnsigned); + try (Context ctx = new Context()) { + for (Entry ent : memvals.entrySet()) { + BitVecExpr bvOff = SymValueZ3.deserializeBitVecExpr(ctx, ent.getKey()); + if (bvOff.isNumeral()) { + byOffset.put(((BitVecNum) bvOff).getLong(), ent.getValue()); + } + } + } + } + return byOffset.ceilingEntry(offset); + } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PairedPcodeExecutorState.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PairedPcodeExecutorState.java index 8a4d77ed85..f3b510f628 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PairedPcodeExecutorState.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PairedPcodeExecutorState.java @@ -17,7 +17,6 @@ package ghidra.pcode.emu.symz3; import org.apache.commons.lang3.tuple.Pair; -import ghidra.pcode.emu.symz3.plain.SymZ3Space; import ghidra.pcode.exec.PcodeExecutorState; import ghidra.pcode.exec.PcodeExecutorStatePiece; import ghidra.symz3.model.SymValueZ3; @@ -27,5 +26,5 @@ public interface SymZ3PairedPcodeExecutorState PcodeExecutorStatePiece getLeft(); - AbstractSymZ3PcodeExecutorStatePiece getRight(); + SymZ3PcodeExecutorStatePiece getRight(); } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PartsFactory.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PartsFactory.java index 980d3995f8..f110dfb1c4 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PartsFactory.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PartsFactory.java @@ -17,19 +17,12 @@ package ghidra.pcode.emu.symz3; import org.apache.commons.lang3.tuple.Pair; -import ghidra.app.plugin.core.debug.service.emulation.RWTargetMemoryPcodeExecutorStatePiece; -import ghidra.app.plugin.core.debug.service.emulation.RWTargetRegistersPcodeExecutorStatePiece; import ghidra.pcode.emu.*; import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor; +import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; -import ghidra.pcode.emu.symz3.plain.SymZ3PcodeExecutorState; -import ghidra.pcode.emu.symz3.trace.SymZ3TracePcodeExecutorState; +import ghidra.pcode.emu.symz3.state.SymZ3PcodeExecutorState; import ghidra.pcode.exec.*; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; -import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.TracePcodeExecutorState; -import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; import ghidra.program.model.lang.Language; import ghidra.symz3.model.SymValueZ3; @@ -48,21 +41,16 @@ import ghidra.symz3.model.SymValueZ3; *

  • P-code Arithmetic: {@link SymZ3PcodeArithmetic}
  • *
  • Userop Library: {@link SymZ3PcodeUseropLibrary}
  • *
  • P-code Executor: {@link SymZ3PcodeThreadExecutor}
  • - *
  • Machine State
  • - *
      - *
    • Stand alone: {@link SymZ3PcodeExecutorState}
    • - *
    • Trace integrated: {@link SymZ3TracePcodeExecutorState}
    • - *
    • Debugger integrated: Not applicable. Uses trace integration only.
    • - *
    + *
  • Machine State: {@link SymZ3PcodeExecutorState}
  • * * *

    * If you're following from the {@link ghidra.symz3} package documentation, you'll want to return to - * {@link ghidra.pcode.emu.symz3.plain} before you examine the trace-integrated state. Similarly, + * {@link ghidra.pcode.emu.symz3.state} before you examine the trace-integrated state. Similarly, * you'll want to return to {@link ghidra.pcode.emu.symz3.trace} before you examine the * Debugger-integrated state. */ -public enum SymZ3PartsFactory implements AuxDebuggerEmulatorPartsFactory { +public enum SymZ3PartsFactory implements AuxEmulatorPartsFactory { /** This singleton factory instance */ INSTANCE; @@ -140,44 +128,15 @@ public enum SymZ3PartsFactory implements AuxDebuggerEmulatorPartsFactory> createSharedState( - AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete) { - return new SymZ3PcodeExecutorState(emulator.getLanguage(), concrete); + AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete, + PcodeStateCallbacks cb) { + return new SymZ3PcodeExecutorState(emulator.getLanguage(), concrete, cb); } @Override public PcodeExecutorState> createLocalState( AuxPcodeEmulator emulator, PcodeThread> thread, - BytesPcodeExecutorStatePiece concrete) { - return new SymZ3PcodeExecutorState(emulator.getLanguage(), concrete); - } - - @Override - public TracePcodeExecutorState> createTraceSharedState( - AuxTracePcodeEmulator emulator, - BytesTracePcodeExecutorStatePiece concrete) { - return new SymZ3TracePcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createTraceLocalState( - AuxTracePcodeEmulator emulator, - PcodeThread> emuThread, - BytesTracePcodeExecutorStatePiece concrete) { - return new SymZ3TracePcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createDebuggerSharedState( - AuxDebuggerPcodeEmulator emulator, - RWTargetMemoryPcodeExecutorStatePiece concrete) { - return new SymZ3TracePcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createDebuggerLocalState( - AuxDebuggerPcodeEmulator emulator, - PcodeThread> emuThread, - RWTargetRegistersPcodeExecutorStatePiece concrete) { - return new SymZ3TracePcodeExecutorState(concrete); + BytesPcodeExecutorStatePiece concrete, PcodeStateCallbacks cb) { + return new SymZ3PcodeExecutorState(emulator.getLanguage(), concrete, cb); } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeArithmetic.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeArithmetic.java index 9253dbb49d..89fadfb116 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeArithmetic.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeArithmetic.java @@ -77,6 +77,11 @@ public enum SymZ3PcodeArithmetic implements PcodeArithmetic { this.endian = endian; } + @Override + public Class getDomain() { + return SymValueZ3.class; + } + @Override public Endian getEndian() { return endian; diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeEmulatorTrait.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeEmulatorTrait.java index e0ab9624c5..1e263b40ab 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeEmulatorTrait.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeEmulatorTrait.java @@ -30,7 +30,6 @@ import ghidra.app.plugin.processors.sleigh.template.OpTpl; import ghidra.app.util.pcode.StringPcodeFormatter; import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; -import ghidra.pcode.emu.symz3.plain.SymZ3Space; import ghidra.symz3.model.SymValueZ3; public interface SymZ3PcodeEmulatorTrait @@ -48,7 +47,7 @@ public interface SymZ3PcodeEmulatorTrait @Override SymZ3PairedPcodeExecutorState getSharedState(); - default AbstractSymZ3PcodeExecutorStatePiece getSharedSymbolicState() { + default SymZ3PcodeExecutorStatePiece getSharedSymbolicState() { return getSharedState().getRight(); } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/AbstractSymZ3PcodeExecutorStatePiece.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeExecutorStatePiece.java similarity index 67% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/AbstractSymZ3PcodeExecutorStatePiece.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeExecutorStatePiece.java index a2e7486277..469b899a9c 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/AbstractSymZ3PcodeExecutorStatePiece.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeExecutorStatePiece.java @@ -17,19 +17,19 @@ package ghidra.pcode.emu.symz3; import java.io.PrintStream; import java.util.*; +import java.util.Map.Entry; import java.util.stream.Stream; import com.microsoft.z3.Context; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; -import ghidra.pcode.emu.symz3.plain.SymZ3Preconditions; -import ghidra.pcode.emu.symz3.plain.SymZ3Space; -import ghidra.pcode.exec.ConcretionError; -import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.emu.symz3.state.*; +import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; 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.listing.Instruction; import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.pcode.PcodeOp; @@ -47,22 +47,12 @@ import ghidra.symz3.model.SymValueZ3; * more capable state piece, we have to ensure that type can be substituted. Thus, we have to create * these abstract classes from which the actual state pieces are derived, leaving {@code } * bounded, but unspecified. - * - * @param the type of spaces */ -public abstract class AbstractSymZ3PcodeExecutorStatePiece - extends AbstractSymZ3OffsetPcodeExecutorStatePiece +public class SymZ3PcodeExecutorStatePiece + extends AbstractSymZ3OffsetPcodeExecutorStatePiece implements InternalSymZ3RecordsPreconditions, InternalSymZ3RecordsExecution { - /** - * The map from address space to storage space - * - *

    - * While the concept is introduced in the super class, we're not required to actually use one. - * We just have to implement {@link #getForSpace(AddressSpace, boolean)}. Nevertheless, the - * provided map is probably the best way, so we'll follow the pattern. - */ - protected final AbstractSpaceMap spaceMap = newSpaceMap(this.language); + protected final Map spaceMap = new HashMap<>(); protected final SymZ3Preconditions preconditions = new SymZ3Preconditions(); // LATER: These two are a recurring concern, and should be separated out @@ -75,23 +65,44 @@ public abstract class AbstractSymZ3PcodeExecutorStatePiece * @param language the emulator's language * @param addressArithmetic the arithmetic for the address type * @param arithmetic the arithmetic for the value type + * @param cb callbacks to receive emulation events */ - public AbstractSymZ3PcodeExecutorStatePiece(Language language, - PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic) { - super(language, addressArithmetic, arithmetic); + public SymZ3PcodeExecutorStatePiece(Language language, + PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic, + PcodeStateCallbacks cb) { + super(language, addressArithmetic, arithmetic, cb); } /** - * Extension point: Create the actual space map + * Create the SymZ3 piece * - *

    - * This will need to be implemented by each state piece, i.e., non-abstract derivating class. - * The space map will provide instances of {@code }, which will provide the actual (extended) - * storage logic. - * - * @return the space map + * @param language the language of the emulator + * @param addressArithmetic the address arithmetic, likely taken from the concrete piece + * @param cb callbacks to receive emulation events */ - protected abstract AbstractSpaceMap newSpaceMap(Language language); + public SymZ3PcodeExecutorStatePiece(Language language, + PcodeArithmetic addressArithmetic, PcodeStateCallbacks cb) { + this(language, addressArithmetic, SymZ3PcodeArithmetic.forLanguage(language), cb); + } + + protected SymZ3Space newSpace(AddressSpace space) { + if (space.isConstantSpace()) { + throw new AssertionError(); + } + else if (space.isRegisterSpace()) { + return new SymZ3RegisterSpace(language, space, + SymZ3PcodeExecutorStatePiece.this); + } + else if (space.isUniqueSpace()) { + return new SymZ3UniqueSpace(); + } + else if (space.isLoadedMemorySpace()) { + return new SymZ3MemorySpace(language, space, SymZ3PcodeExecutorStatePiece.this); + } + else { + throw new AssertionError("not yet supported space: " + space.toString()); + } + } @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { @@ -105,8 +116,11 @@ public abstract class AbstractSymZ3PcodeExecutorStatePiece * Here, we just follow the pattern: delegate to the space map. */ @Override - protected S getForSpace(AddressSpace space, boolean toWrite) { - return spaceMap.getForSpace(space, toWrite); + protected SymZ3Space getForSpace(AddressSpace space, boolean toWrite) { + if (toWrite) { + return spaceMap.computeIfAbsent(space, this::newSpace); + } + return spaceMap.get(space); } /** @@ -117,8 +131,9 @@ public abstract class AbstractSymZ3PcodeExecutorStatePiece * the storage space. */ @Override - protected void setInSpace(SymZ3Space space, SymValueZ3 offset, int size, SymValueZ3 val) { - space.set(offset, size, val); + protected void setInSpace(SymZ3Space space, SymValueZ3 offset, int size, SymValueZ3 val, + PcodeStateCallbacks cb) { + space.set(offset, size, val, cb); } /** @@ -129,13 +144,28 @@ public abstract class AbstractSymZ3PcodeExecutorStatePiece * the storage space. */ @Override - protected SymValueZ3 getFromSpace(SymZ3Space space, SymValueZ3 offset, int size) { - return space.get(offset, size); + protected SymValueZ3 getFromSpace(SymZ3Space space, SymValueZ3 offset, int size, + PcodeStateCallbacks cb) { + return space.get(offset, size, cb); + } + + @Override + public Map getRegisterValues() { + throw new UnsupportedOperationException(); + } + + @Override + public Entry getNextEntryInternal(AddressSpace space, long offset) { + SymZ3Space s = getForSpace(space, false); + if (s == null) { + return null; + } + return s.getNextEntry(offset); } public String printableSummary() { StringBuilder result = new StringBuilder(); - for (S space : spaceMap.values()) { + for (SymZ3Space space : spaceMap.values()) { result.append(space.printableSummary()); } result.append(this.preconditions.printableSummary()); @@ -182,7 +212,7 @@ public abstract class AbstractSymZ3PcodeExecutorStatePiece @Override public void clear() { - spaceMap.spaces.clear(); + spaceMap.clear(); preconditions.clear(); ops.clear(); instructions.clear(); diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeThread.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeThread.java index d7c501bf4c..390470197a 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeThread.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3PcodeThread.java @@ -30,7 +30,6 @@ import ghidra.pcode.emu.ThreadPcodeExecutorState; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; import ghidra.pcode.emu.auxiliary.AuxPcodeThread; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; -import ghidra.pcode.emu.symz3.plain.SymZ3Space; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; @@ -76,7 +75,7 @@ public class SymZ3PcodeThread extends AuxPcodeThread return getState().getSharedState().getLeft(); } - public AbstractSymZ3PcodeExecutorStatePiece getSharedSymbolicState() { + public SymZ3PcodeExecutorStatePiece getSharedSymbolicState() { return getState().getSharedState().getRight(); } @@ -84,7 +83,7 @@ public class SymZ3PcodeThread extends AuxPcodeThread return getState().getLocalState().getLeft(); } - public AbstractSymZ3PcodeExecutorStatePiece getLocalSymbolicState() { + public SymZ3PcodeExecutorStatePiece getLocalSymbolicState() { return getState().getLocalState().getRight(); } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3RegisterMap.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3RegisterMap.java index d2e9c13cdb..96a391ea42 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3RegisterMap.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/SymZ3RegisterMap.java @@ -34,7 +34,8 @@ public class SymZ3RegisterMap { // TODO: make this be private and provide appropriate methods // in the map, all registers are base registers. - public Map regvals = new HashMap(); + public Map regvals = new HashMap<>(); + private NavigableMap byOffset; //private List createdSymbolics = new ArrayList(); private final Set registerNamesRead = new HashSet(); @@ -85,6 +86,7 @@ public class SymZ3RegisterMap { private void updateRegisterHelper(Context ctx, Register r, SymValueZ3 update) { if (r.isBaseRegister()) { regvals.put(r, update); + byOffset = null; return; } // so, we want to update the base, but also need to keep portions of it. @@ -114,6 +116,7 @@ public class SymZ3RegisterMap { result = ctx.mkConcat(result, right); } regvals.put(base, new SymValueZ3(ctx, result)); + byOffset = null; } public SymValueZ3 getRegister(Register r) { @@ -228,4 +231,14 @@ public class SymZ3RegisterMap { return valuationFor(ctx, z3p, r); }); } + + public Entry getNextEntry(long offset) { + if (byOffset == null) { + byOffset = new TreeMap<>(); + for (Entry ent : regvals.entrySet()) { + byOffset.put(ent.getKey().getAddress().getOffset(), ent.getValue()); + } + } + return byOffset.ceilingEntry(offset); + } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulator.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulator.java deleted file mode 100644 index 0b4cecb164..0000000000 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulator.java +++ /dev/null @@ -1,76 +0,0 @@ -/* ### - * 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.emu.symz3.full; - -import java.util.Collection; - -import ghidra.debug.api.emulation.PcodeDebuggerAccess; -import ghidra.pcode.emu.symz3.*; -import ghidra.pcode.emu.symz3.plain.SymZ3PcodeEmulator; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; -import ghidra.symz3.model.SymValueZ3; - -/** - * A Debugger-integrated emulator with symbolic z3 summarization - */ -public class SymZ3DebuggerPcodeEmulator extends AuxDebuggerPcodeEmulator - implements SymZ3PcodeEmulatorTrait { - /** - * Create an emulator - * - * @param access the trace-and-debugger access shim - */ - public SymZ3DebuggerPcodeEmulator(PcodeDebuggerAccess access) { - super(access); - } - - /** - * {@inheritDoc} - * - *

    - * Here, we just return the singleton parts factory. This appears simple because all the - * complexity is encapsulated in the factory. See {@link SymZ3PartsFactory} to see everything - * the implementation actually entails. Notice that this is the same parts factory used by - * {@link SymZ3PcodeEmulator}. The {@link AuxDebugggerPcodeEmulator} knows to use the more - * capable state parts. - */ - @Override - protected AuxDebuggerEmulatorPartsFactory getPartsFactory() { - return SymZ3PartsFactory.INSTANCE; - } - - @Override - public SymZ3PcodeThread newThread() { - return (SymZ3PcodeThread) super.newThread(); - } - - @Override - public SymZ3PcodeThread newThread(String name) { - return (SymZ3PcodeThread) super.newThread(name); - } - - @Override - @SuppressWarnings("unchecked") - public Collection getAllThreads() { - return (Collection) super.getAllThreads(); - } - - @Override - public SymZ3PairedPcodeExecutorState getSharedState() { - return (SymZ3PairedPcodeExecutorState) super.getSharedState(); - } -} diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeExecutorStatePiece.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeExecutorStatePiece.java deleted file mode 100644 index 38f493fa52..0000000000 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeExecutorStatePiece.java +++ /dev/null @@ -1,101 +0,0 @@ -/* ### - * 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.emu.symz3.plain; - -import java.util.Map; - -import ghidra.pcode.emu.symz3.AbstractSymZ3PcodeExecutorStatePiece; -import ghidra.pcode.emu.symz3.SymZ3PcodeArithmetic; -import ghidra.pcode.exec.PcodeArithmetic; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.lang.Language; -import ghidra.program.model.lang.Register; -import ghidra.symz3.model.SymValueZ3; - -/** - * The state piece for holding symbolic values in the emulator's machine state - * - */ -public class SymZ3PcodeExecutorStatePiece extends AbstractSymZ3PcodeExecutorStatePiece { - /** - * Create the SymZ3 piece - * - * @param language the language of the emulator - * @param addressArithmetic the address arithmetic, likely taken from the concrete piece - */ - public SymZ3PcodeExecutorStatePiece(Language language, - PcodeArithmetic addressArithmetic) { - super(language, addressArithmetic, SymZ3PcodeArithmetic.forLanguage(language)); - } - - /** - * {@inheritDoc} - * - *

    - * Here we use the simplest scheme for creating a map of {@link SymZ3Space}s. This is - * essentially a lazy map from address space to some object for managing symbolic values in that - * address space. The space could be a memory space, register space, unique space, etc. This - * piece will look up the space, creating it if necessary, and then delegate the get and set - * methods. - */ - @Override - protected AbstractSpaceMap newSpaceMap(Language language) { - return new SimpleSpaceMap() { - @Override - protected SymZ3Space newSpace(AddressSpace space) { - if (space.isConstantSpace()) { - throw new AssertionError(); - } - else if (space.isRegisterSpace()) { - return new SymZ3RegisterSpace(space, language); - } - else if (space.isUniqueSpace()) { - return new SymZ3UniqueSpace(); - } - else if (space.isLoadedMemorySpace()) { - return new SymZ3MemorySpace(language); - } - else { - throw new AssertionError("not yet supported space: " + space.toString()); - } - } - }; - } - - @Override - public String printableSummary() { - StringBuilder result = new StringBuilder(); - for (SymZ3Space space : spaceMap.values()) { - result.append(space.printableSummary()); - } - result.append(this.preconditions.printableSummary()); - return result.toString(); - } - - @Override - public Map getRegisterValues() { - throw new UnsupportedOperationException(); - } - - @Override - public void clear() { - /** - * In addition to clearing out all the state, you would probably also want to clear the - * instruction and op lists. - */ - throw new UnsupportedOperationException(); - } -} diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3MemorySpace.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3MemorySpace.java similarity index 65% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3MemorySpace.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3MemorySpace.java index 195152a40b..e3f0c6964d 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3MemorySpace.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3MemorySpace.java @@ -13,15 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.symz3.plain; +package ghidra.pcode.emu.symz3.state; import java.util.Map.Entry; import java.util.stream.Stream; import com.microsoft.z3.Context; +import ghidra.pcode.emu.symz3.AbstractSymZ3OffsetPcodeExecutorStatePiece; import ghidra.pcode.emu.symz3.SymZ3MemoryMap; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; +import ghidra.pcode.exec.PcodeStateCallbacks; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; import ghidra.symz3.model.SymValueZ3; @@ -37,22 +40,35 @@ import ghidra.symz3.model.SymValueZ3; * to the SymZ3MemoryMap and there is just a bit of plumbing here. */ public class SymZ3MemorySpace extends SymZ3Space { + private final AddressSpace space; + private final AbstractSymZ3OffsetPcodeExecutorStatePiece piece; + private final SymZ3MemoryMap mmap; - private SymZ3MemoryMap mmap; - - public SymZ3MemorySpace(Language language) { + public SymZ3MemorySpace(Language language, AddressSpace space, + AbstractSymZ3OffsetPcodeExecutorStatePiece piece) { super(); + this.space = space; + this.piece = piece; mmap = new SymZ3MemoryMap(language); } @Override - public SymValueZ3 get(SymValueZ3 offset, int size) { - return mmap.load(offset, size, true); + public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { + if (!mmap.hasValueFor(offset, size)) { + cb.readUninitialized(piece, space, offset, size); + } + return mmap.load(offset, size, true, cb); } @Override - public void set(SymValueZ3 offset, int size, SymValueZ3 val) { + public void set(SymValueZ3 offset, int size, SymValueZ3 val, PcodeStateCallbacks cb) { mmap.store(offset, size, val); + cb.dataWritten(piece, space, offset, size, val); + } + + @Override + public Entry getNextEntry(long offset) { + return mmap.getNextEntry(offset); } @Override diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeEmulator.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3PcodeEmulator.java similarity index 79% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeEmulator.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3PcodeEmulator.java index 53ae5a0b78..968fac148d 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeEmulator.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3PcodeEmulator.java @@ -13,10 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.symz3.plain; +package ghidra.pcode.emu.symz3.state; import java.util.Collection; +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.PcodeEmulationCallbacks; import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; import ghidra.pcode.emu.symz3.*; @@ -24,17 +27,28 @@ import ghidra.program.model.lang.Language; import ghidra.symz3.model.SymValueZ3; /** - * A stand-alone emulator with symbolic Z3 summarization analysis + * An emulator with symbolic Z3 summarization analysis */ public class SymZ3PcodeEmulator extends AuxPcodeEmulator implements SymZ3PcodeEmulatorTrait { + /** + * Create an emulator + * + * @param language the language (processor model) + * @param cb callbacks to receive emulation events + */ + public SymZ3PcodeEmulator(Language language, + PcodeEmulationCallbacks> cb) { + super(language, cb); + } + /** * Create an emulator * * @param language the language (processor model) */ public SymZ3PcodeEmulator(Language language) { - super(language); + this(language, PcodeEmulationCallbacks.none()); } /** diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeExecutorState.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3PcodeExecutorState.java similarity index 84% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeExecutorState.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3PcodeExecutorState.java index bba8ae3227..625dc84963 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeExecutorState.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3PcodeExecutorState.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.symz3.plain; +package ghidra.pcode.emu.symz3.state; import ghidra.pcode.emu.symz3.*; -import ghidra.pcode.exec.BytesPcodeExecutorStatePiece; -import ghidra.pcode.exec.IndependentPairedPcodeExecutorState; +import ghidra.pcode.exec.*; import ghidra.program.model.lang.Language; import ghidra.symz3.model.SymValueZ3; @@ -52,14 +51,16 @@ public class SymZ3PcodeExecutorState * * @param language the language for creating the symz3 piece * @param concrete the concrete piece + * @param cb callbacks to receive emulation events */ - public SymZ3PcodeExecutorState(Language language, BytesPcodeExecutorStatePiece concrete) { - this(concrete, - new SymZ3PcodeExecutorStatePiece(language, SymZ3PcodeArithmetic.forLanguage(language))); + public SymZ3PcodeExecutorState(Language language, BytesPcodeExecutorStatePiece concrete, + PcodeStateCallbacks cb) { + this(concrete, new SymZ3PcodeExecutorStatePiece(language, + SymZ3PcodeArithmetic.forLanguage(language), cb)); } @Override - public AbstractSymZ3PcodeExecutorStatePiece getRight() { + public SymZ3PcodeExecutorStatePiece getRight() { return (SymZ3PcodeExecutorStatePiece) super.getRight(); } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3PieceHandler.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3PieceHandler.java new file mode 100644 index 0000000000..5125df4dfb --- /dev/null +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3PieceHandler.java @@ -0,0 +1,139 @@ +/* ### + * 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.emu.symz3.state; + +import java.util.*; + +import com.microsoft.z3.Context; + +import ghidra.lifecycle.Unfinished; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.ConcretionError; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.PcodeExecutorStatePiece; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.AbstractPropertyBasedPieceHandler; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; +import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; +import ghidra.program.model.address.*; +import ghidra.symz3.model.SymValueZ3; + +public class SymZ3PieceHandler + extends AbstractPropertyBasedPieceHandler { + public static final String NAME = "SymValueZ3"; + + private record SymZ3Varnode(AddressSpace space, String offset, int size) { + public SymZ3Varnode(AddressSpace space, SymValueZ3 offset, int size) { + this(space, offset.bitVecExprString, size); + } + + public SymValueZ3 offset(Context ctx) { + return new SymValueZ3(ctx, SymValueZ3.deserializeBitVecExpr(ctx, offset)); + } + } + + private final Map, Set> abstractWritten = new HashMap<>(); + + @Override + public Class getAddressDomain() { + return SymValueZ3.class; + } + + @Override + public Class getValueDomain() { + return SymValueZ3.class; + } + + @Override + protected String getPropertyName() { + return NAME; + } + + @Override + protected Class getPropertyType() { + return String.class; + } + + @Override + public void abstractWritten(PcodeTraceDataAccess acc, AddressSet written, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, + SymValueZ3 offset, int length, SymValueZ3 value) { + try { + Address address = piece.getAddressArithmetic().toAddress(offset, space, Purpose.STORE); + dataWritten(acc, written, thread, piece, address, length, value); + } + catch (ConcretionError e) { + abstractWritten.computeIfAbsent(thread, t -> new HashSet<>()) + .add(new SymZ3Varnode(space, offset, length)); + } + } + + @Override + public int abstractReadUninit(PcodeTraceDataAccess acc, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, + SymValueZ3 offset, int length) { + String string = acc.getPropertyAccess(NAME, String.class).get(Address.NO_ADDRESS); + if (string == null) { + return 0; + } + return Unfinished.TODO("need to implement extraction from: " + string); + } + + @Override + protected void decodeFrom(PcodeExecutorStatePiece piece, + AddressSetView limit, AddressRange range, String propertyValue) { + /** + * NOTE: We're ignoring limit here, because we've not really implemented byte-wise property + * access. + */ + SymValueZ3 result = SymValueZ3.parse(propertyValue); + piece.setVarInternal(range.getAddressSpace(), range.getMinAddress().getOffset(), + (int) range.getLength(), result); + } + + @Override + public void writeDown(PcodeTraceDataAccess into, PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView written) { + super.writeDown(into, thread, piece, written); + Set symWritten = abstractWritten.get(thread); + if (symWritten == null) { + return; + } + StringBuffer buf = new StringBuffer(); + try (Context ctx = new Context()) { + for (SymZ3Varnode vn : symWritten) { + SymValueZ3 offset = vn.offset(ctx); + SymValueZ3 value = piece.getVarInternal(vn.space, offset, vn.size, Reason.INSPECT); + buf.append("::"); + buf.append(offset); + buf.append("<==>"); + buf.append(value.serialize()); + } + } + /** + * NOTE: This won't work for threads, but then again, how would one address a register + * abstractly? + */ + String val = buf.isEmpty() ? null : buf.toString(); + into.getPropertyAccess(NAME, String.class).put(Address.NO_ADDRESS, val); + } + + @Override + protected void encodeInto(PcodeTracePropertyAccess property, AddressRange range, + SymValueZ3 value) { + property.put(range.getMinAddress(), value.serialize()); + } +} diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3Preconditions.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3Preconditions.java similarity index 98% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3Preconditions.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3Preconditions.java index 17af9f7a8f..95debeb29c 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3Preconditions.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3Preconditions.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.symz3.plain; +package ghidra.pcode.emu.symz3.state; import java.util.*; import java.util.stream.Stream; diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3RegisterSpace.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3RegisterSpace.java similarity index 78% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3RegisterSpace.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3RegisterSpace.java index 111cfbdc70..3428dc3226 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3RegisterSpace.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3RegisterSpace.java @@ -13,15 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.symz3.plain; +package ghidra.pcode.emu.symz3.state; import java.util.Map.Entry; import java.util.stream.Stream; import com.microsoft.z3.Context; +import ghidra.pcode.emu.symz3.AbstractSymZ3OffsetPcodeExecutorStatePiece; import ghidra.pcode.emu.symz3.SymZ3RegisterMap; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; +import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; @@ -41,13 +43,16 @@ import ghidra.util.Msg; public class SymZ3RegisterSpace extends SymZ3Space { private final SymZ3RegisterMap rmap = new SymZ3RegisterMap(); - private final AddressSpace space; private final Language language; + private final AddressSpace space; + private final AbstractSymZ3OffsetPcodeExecutorStatePiece piece; - public SymZ3RegisterSpace(AddressSpace space, Language language) { + public SymZ3RegisterSpace(Language language, AddressSpace space, + AbstractSymZ3OffsetPcodeExecutorStatePiece piece) { super(); this.space = space; this.language = language; + this.piece = piece; } @Override @@ -70,7 +75,7 @@ public class SymZ3RegisterSpace extends SymZ3Space { } @Override - public void set(SymValueZ3 offset, int size, SymValueZ3 val) { + public void set(SymValueZ3 offset, int size, SymValueZ3 val, PcodeStateCallbacks cb) { Register r = getRegister(offset, size); if (r == null) { Msg.warn(this, "set is ignoring set register with offset: " + offset + " and size: " + @@ -78,17 +83,26 @@ public class SymZ3RegisterSpace extends SymZ3Space { return; } this.rmap.updateRegister(r, val); + cb.dataWritten(piece, space, offset, size, val); } @Override - public SymValueZ3 get(SymValueZ3 offset, int size) { + public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { Register r = getRegister(offset, size); if (r == null) { Msg.warn(this, "unable to get register with space: " + space.getSpaceID() + " offset_long: " + offset + " size: " + size); return null; } + if (!rmap.hasValueForRegister(r)) { + cb.readUninitialized(piece, space, offset, size); + } SymValueZ3 result = this.rmap.getRegister(r); return result; } + + @Override + public Entry getNextEntry(long offset) { + return rmap.getNextEntry(offset); + } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3Space.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3Space.java similarity index 82% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3Space.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3Space.java index 3bb728ff77..97089c006c 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3Space.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3Space.java @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.symz3.plain; +package ghidra.pcode.emu.symz3.state; import java.util.Map; +import java.util.Map.Entry; import java.util.stream.Stream; import com.microsoft.z3.Context; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; +import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.symz3.model.SymValueZ3; /** @@ -32,12 +34,14 @@ import ghidra.symz3.model.SymValueZ3; * user's current trace. */ public abstract class SymZ3Space { - public abstract void set(SymValueZ3 offset, int size, SymValueZ3 val); + public abstract void set(SymValueZ3 offset, int size, SymValueZ3 val, PcodeStateCallbacks cb); - public abstract SymValueZ3 get(SymValueZ3 offset, int size); + public abstract SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb); public abstract String printableSummary(); public abstract Stream> streamValuations(Context ctx, Z3InfixPrinter z3p); + + public abstract Entry getNextEntry(long offset); } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3UniqueSpace.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3UniqueSpace.java similarity index 77% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3UniqueSpace.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3UniqueSpace.java index f8022ad221..2d14fe05a9 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/SymZ3UniqueSpace.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3UniqueSpace.java @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.symz3.plain; +package ghidra.pcode.emu.symz3.state; -import java.util.HashMap; -import java.util.Map; import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.TreeMap; import java.util.stream.Stream; import com.microsoft.z3.*; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; +import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.symz3.model.SymValueZ3; /** @@ -35,28 +36,29 @@ import ghidra.symz3.model.SymValueZ3; * user's current trace. */ public class SymZ3UniqueSpace extends SymZ3Space { - private final Map uniqvals = new HashMap();; - - private String label(long offset, int size) { - return "" + offset + ":" + size; - } + private final NavigableMap uniqvals = new TreeMap<>(); public void set(long offset, int size, SymValueZ3 val) { - this.updateUnique(label(offset, size), val); + this.updateUnique(offset, val); } public SymValueZ3 get(long offset, int size) { - return this.getUnique(label(offset, size)); + return this.getUnique(offset); } - public void updateUnique(String s, SymValueZ3 value) { + public void updateUnique(long s, SymValueZ3 value) { uniqvals.put(s, value); } - public SymValueZ3 getUnique(String s) { + public SymValueZ3 getUnique(long s) { return uniqvals.get(s); } + @Override + public Entry getNextEntry(long offset) { + return uniqvals.ceilingEntry(offset); + } + @Override public String printableSummary() { return ""; @@ -68,7 +70,7 @@ public class SymZ3UniqueSpace extends SymZ3Space { } @Override - public void set(SymValueZ3 offset, int size, SymValueZ3 val) { + public void set(SymValueZ3 offset, int size, SymValueZ3 val, PcodeStateCallbacks cb) { assert val != null; try (Context ctx = new Context()) { BitVecExpr b = offset.getBitVecExpr(ctx); @@ -84,7 +86,7 @@ public class SymZ3UniqueSpace extends SymZ3Space { } @Override - public SymValueZ3 get(SymValueZ3 offset, int size) { + public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { assert offset != null; try (Context ctx = new Context()) { BitVecExpr b = offset.getBitVecExpr(ctx); diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3WriteDownHelper.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3WriteDownHelper.java similarity index 94% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3WriteDownHelper.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3WriteDownHelper.java index 83eb6676ca..c9a6ff5555 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3WriteDownHelper.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/SymZ3WriteDownHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.symz3.trace; +package ghidra.pcode.emu.symz3.state; import java.math.BigInteger; import java.util.Map.Entry; @@ -27,6 +27,9 @@ import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.symz3.model.SymValueZ3; +/** + * TODO: Delete me + */ public class SymZ3WriteDownHelper { public static void writeDown(SymZ3RegisterMap rmap, PcodeTracePropertyAccess property) { for (Entry entry : rmap.regvals.entrySet()) { @@ -37,8 +40,8 @@ public class SymZ3WriteDownHelper { } Register key = entry.getKey(); Address address = key.getAddress(); - String serialized_value = symval.serialize(); - property.put(address, serialized_value); + String serializedValue = symval.serialize(); + property.put(address, serializedValue); } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/package-info.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/package-info.java similarity index 90% rename from Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/package-info.java rename to Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/package-info.java index dd487e3398..6920cfeb95 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/plain/package-info.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/state/package-info.java @@ -27,8 +27,8 @@ * For this package, I recommend a top-down approach, since the top component provides a flat * catalog of the lower components. That top piece is actually in a separate package. See * {@link ghidra.pcode.emu.symz3.SymZ3PartsFactory}. That factory is then used in - * {@link ghidra.pcode.emu.symz3.plain.SymZ3PcodeEmulator} to realize the stand-alone emulator. When + * {@link ghidra.pcode.emu.symz3.state.SymZ3PcodeEmulator} to realize the stand-alone emulator. When * you get to the state pieces, you may want to pause and read - * {@link ghidra.pcode.emu.symz3.plain.SymZ3Space} first. + * {@link ghidra.pcode.emu.symz3.state.SymZ3Space} first. */ -package ghidra.pcode.emu.symz3.plain; +package ghidra.pcode.emu.symz3.state; diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/AbstractSymZ3TracePcodeExecutorStatePiece.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/AbstractSymZ3TracePcodeExecutorStatePiece.java deleted file mode 100644 index cb3217a606..0000000000 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/AbstractSymZ3TracePcodeExecutorStatePiece.java +++ /dev/null @@ -1,125 +0,0 @@ -/* ### - * 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.emu.symz3.trace; - -import ghidra.pcode.emu.symz3.AbstractSymZ3PcodeExecutorStatePiece; -import ghidra.pcode.emu.symz3.SymZ3PcodeArithmetic; -import ghidra.pcode.exec.trace.TracePcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; -import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.lang.Language; -import ghidra.symz3.model.SymValueZ3; -import ghidra.trace.model.property.TracePropertyMapSpace; - -/** - * An abstract trace-integrated state piece - * - *

    - * See {@link AbstractSymZ3TracePcodeExecutorStatePiece} for framing. This class must remain - * abstract since we need to derive the Debugger-integrated state piece from it. Thus it tightens - * the bound on {@code } and introduces the parameters necessary to source state from a trace. - * We'll store SymValueZ3s in the trace's address property map, which is the recommended scheme for - * auxiliary state. - */ -public abstract class AbstractSymZ3TracePcodeExecutorStatePiece - extends AbstractSymZ3PcodeExecutorStatePiece - implements TracePcodeExecutorStatePiece { - public static final String NAME = "SymValueZ3"; - - protected final PcodeTraceDataAccess data; - protected final PcodeTracePropertyAccess property; - - /** - * Create a state piece - * - * @param data the trace-data access shim - */ - public AbstractSymZ3TracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - super(data.getLanguage(), - SymZ3PcodeArithmetic.forLanguage(data.getLanguage()), - SymZ3PcodeArithmetic.forLanguage(data.getLanguage())); - this.data = data; - this.property = data.getPropertyAccess(NAME, String.class); - } - - @Override - public PcodeTraceDataAccess getData() { - return data; - } - - /** - * {@inheritDoc} - * - *

    - * Here we create a map that uses {@link SymZ3TraceSpace}s. The framework provides the concept - * of a space map where storage is actually a cache backed by some other object. The backing - * object we'll use here is {@link TracePropertyMapSpace}, which is provided by the - * TraceModeling module. We'll need a little bit of extra logic for fetching a register space - * vs. a plain memory space, but after that, we need not care which address space the backing - * object is for. - */ - @Override - protected AbstractSpaceMap newSpaceMap(Language language) { - return new CacheingSpaceMap, SymZ3TraceSpace>() { - @Override - protected PcodeTracePropertyAccess getBacking(AddressSpace space) { - return property; - } - - @Override - protected SymZ3TraceSpace newSpace(AddressSpace space, - PcodeTracePropertyAccess backing) { - - if (space.isConstantSpace()) { - throw new AssertionError( - "request for a trace constant space needs to be implemented"); - //return new SymZ3TraceConstantSpace(backing, snap); - } - else if (space.isRegisterSpace()) { - return new SymZ3TraceRegisterSpace(space, backing); - } - else if (space.isUniqueSpace()) { - return new SymZ3TraceUniqueSpace(space, backing); - } - else if (space.isLoadedMemorySpace()) { - return new SymZ3TraceMemorySpace(space, backing); - - } - else { - throw new AssertionError("not yet supported space: " + space.toString()); - } - - } - }; - } - - /** - * {@inheritDoc} - * - *

    - * This does the inverse of the lazy loading. Serialize the state and store it back into the - * trace. Technically, it could be a different trace, but it must have identically-named - * threads. - */ - @Override - public void writeDown(PcodeTraceDataAccess into) { - PcodeTracePropertyAccess intoProp = into.getPropertyAccess(NAME, String.class); - for (SymZ3TraceSpace space : spaceMap.values()) { - space.writeDown(intoProp); - } - } -} diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceMemorySpace.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceMemorySpace.java index 8196a25374..8fedf39277 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceMemorySpace.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceMemorySpace.java @@ -21,13 +21,23 @@ import java.util.stream.Stream; import com.microsoft.z3.Context; +import ghidra.lifecycle.Unfinished; import ghidra.pcode.emu.symz3.SymZ3MemoryMap; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; +import ghidra.pcode.emu.symz3.state.SymZ3PieceHandler; +import ghidra.pcode.emu.symz3.state.SymZ3WriteDownHelper; +import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; import ghidra.program.model.address.*; import ghidra.symz3.model.SymValueZ3; import ghidra.util.Msg; +/** + * TODO: Delete me + * + * NOTE: Cannot delete this yet. Needed as reference when fixing {@link SymZ3PieceHandler}. + */ +@Deprecated(forRemoval = true) public class SymZ3TraceMemorySpace extends SymZ3TraceSpace { private final SymZ3MemoryMap mmap = new SymZ3MemoryMap(property.getLanguage());; @@ -43,7 +53,7 @@ public class SymZ3TraceMemorySpace extends SymZ3TraceSpace { if (!this.property.hasSpace(space)) { // our map will create a symbolic value Msg.info(this, "no backing, so our map created a missing symbolic value"); - return mmap.load(offset, size, true); + return mmap.load(offset, size, true, Unfinished.TODO("Delete me")); } // if the address is concrete, we fetch using the address BigInteger bi = offset.toBigInteger(); @@ -72,22 +82,27 @@ public class SymZ3TraceMemorySpace extends SymZ3TraceSpace { } Msg.info(this, "we had a backing, but couldn't find the address, using map to create symbolic value"); - return mmap.load(offset, size, true); + return mmap.load(offset, size, true, Unfinished.TODO("Delete me")); } @Override - public SymValueZ3 get(SymValueZ3 offset, int size) { + public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { if (mmap.hasValueFor(offset, size)) { - return mmap.load(offset, size, true); + return mmap.load(offset, size, true, cb); } return whenMissing(offset, size); } @Override - public void set(SymValueZ3 offset, int size, SymValueZ3 val) { + public void set(SymValueZ3 offset, int size, SymValueZ3 val, PcodeStateCallbacks cb) { mmap.store(offset, size, val); } + @Override + public Entry getNextEntry(long offset) { + return mmap.getNextEntry(offset); + } + @Override public String printableSummary() { return mmap.printableSummary(); diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeEmulator.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeEmulator.java deleted file mode 100644 index 2b17044ea6..0000000000 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeEmulator.java +++ /dev/null @@ -1,81 +0,0 @@ -/* ### - * 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.emu.symz3.trace; - -import java.util.Collection; - -import ghidra.pcode.emu.symz3.*; -import ghidra.pcode.emu.symz3.plain.SymZ3PcodeEmulator; -import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; -import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; -import ghidra.pcode.exec.trace.data.PcodeTraceAccess; -import ghidra.symz3.model.SymValueZ3; -import ghidra.trace.model.guest.TracePlatform; - -/** - * A trace-integrated emulator with symbolic value analysis - */ -public class SymZ3TracePcodeEmulator extends AuxTracePcodeEmulator - implements SymZ3PcodeEmulatorTrait { - /** - * Create an emulator - * - * @param access the trace access shim - */ - public SymZ3TracePcodeEmulator(PcodeTraceAccess access) { - super(access); - } - - public SymZ3TracePcodeEmulator(TracePlatform platform, long snap) { - super(platform, snap); - } - - /** - * {@inheritDoc} - * - *

    - * Here, we just return the singleton parts factory. This appears simple because all the - * complexity is encapsulated in the factory. See {@link SymZ3PartsFactory} to see everything - * the implementation actually entails. Notice that this is the same parts factory used by - * {@link SymZ3PcodeEmulator}. The {@link AuxTracePcodeEmulator} knows to use the more capable - * state parts. - */ - @Override - protected AuxTraceEmulatorPartsFactory getPartsFactory() { - return SymZ3PartsFactory.INSTANCE; - } - - @Override - public SymZ3PcodeThread newThread() { - return (SymZ3PcodeThread) super.newThread(); - } - - @Override - public SymZ3PcodeThread newThread(String name) { - return (SymZ3PcodeThread) super.newThread(name); - } - - @Override - @SuppressWarnings("unchecked") - public Collection getAllThreads() { - return (Collection) super.getAllThreads(); - } - - @Override - public SymZ3PairedPcodeExecutorState getSharedState() { - return (SymZ3PairedPcodeExecutorState) super.getSharedState(); - } -} diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeExecutorState.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeExecutorState.java deleted file mode 100644 index 8525f72a6c..0000000000 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeExecutorState.java +++ /dev/null @@ -1,63 +0,0 @@ -/* ### - * 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.emu.symz3.trace; - -import ghidra.pcode.emu.symz3.SymZ3PairedPcodeExecutorState; -import ghidra.pcode.emu.symz3.plain.SymZ3PcodeExecutorState; -import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.IndependentPairedTracePcodeExecutorState; -import ghidra.symz3.model.SymValueZ3; - -/** - * A paired concrete-plus-symz3 trace-integrated state - * - *

    - * This contains the emulator's machine state along with the symbolic values, just like - * {@link SymZ3PcodeExecutorState}, except that it can read and write state from a trace. In - * reality, this just composes concrete and symz3 state pieces, which actually do all the work. - */ -public class SymZ3TracePcodeExecutorState - extends IndependentPairedTracePcodeExecutorState - implements SymZ3PairedPcodeExecutorState { - - /** - * Create a state from the two given pieces - * - * @param concrete the concrete piece - * @param symz3 the symz3 piece - */ - public SymZ3TracePcodeExecutorState(BytesTracePcodeExecutorStatePiece concrete, - AbstractSymZ3TracePcodeExecutorStatePiece symz3) { - super(concrete, symz3); - } - - /** - * Create a state from the given concrete piece and an internally constructed symz3 piece - * - *

    - * We take all the parameters needed by the symz3 piece from the concrete piece. - * - * @param concrete the concrete piece - */ - public SymZ3TracePcodeExecutorState(BytesTracePcodeExecutorStatePiece concrete) { - this(concrete, new SymZ3TracePcodeExecutorStatePiece(concrete.getData())); - } - - @Override - public SymZ3TracePcodeExecutorStatePiece getRight() { - return (SymZ3TracePcodeExecutorStatePiece) super.getRight(); - } -} diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeExecutorStatePiece.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeExecutorStatePiece.java deleted file mode 100644 index b15a8fbeba..0000000000 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeExecutorStatePiece.java +++ /dev/null @@ -1,49 +0,0 @@ -/* ### - * 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.emu.symz3.trace; - -import java.util.Map; - -import ghidra.pcode.exec.trace.TracePcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; -import ghidra.program.model.lang.Register; -import ghidra.symz3.model.SymValueZ3; - -/** - * The trace-integrated state piece for holding symbolic values - */ -public class SymZ3TracePcodeExecutorStatePiece - extends AbstractSymZ3TracePcodeExecutorStatePiece { - - public SymZ3TracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - super(data); - } - - @Override - public TracePcodeExecutorStatePiece fork() { - throw new UnsupportedOperationException(); - } - - @Override - public Map getRegisterValues() { - throw new UnsupportedOperationException(); - } - - @Override - public void clear() { - throw new UnsupportedOperationException(); - } -} diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceRegisterSpace.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceRegisterSpace.java index c8862b05df..693af64f3b 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceRegisterSpace.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceRegisterSpace.java @@ -22,6 +22,8 @@ import com.microsoft.z3.Context; import ghidra.pcode.emu.symz3.SymZ3RegisterMap; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; +import ghidra.pcode.emu.symz3.state.SymZ3WriteDownHelper; +import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; @@ -37,6 +39,8 @@ import ghidra.util.Msg; * stand-alone emulator, this is the full state. For a trace- or Debugger-integrated emulator, this * is a cache of values loaded from a trace backing this emulator. Most likely, that trace is the * user's current trace. + * + * TODO: Delete me */ public class SymZ3TraceRegisterSpace extends SymZ3TraceSpace { private final SymZ3RegisterMap rmap = new SymZ3RegisterMap(); @@ -79,7 +83,7 @@ public class SymZ3TraceRegisterSpace extends SymZ3TraceSpace { } @Override - public void set(SymValueZ3 offset, int size, SymValueZ3 val) { + public void set(SymValueZ3 offset, int size, SymValueZ3 val, PcodeStateCallbacks cb) { assert offset != null; assert val != null; Register r = getRegister(offset, size); @@ -92,7 +96,7 @@ public class SymZ3TraceRegisterSpace extends SymZ3TraceSpace { } @Override - public SymValueZ3 get(SymValueZ3 offset, int size) { + public SymValueZ3 get(SymValueZ3 offset, int size, PcodeStateCallbacks cb) { assert offset != null; Register r = getRegister(offset, size); if (r == null) { @@ -112,4 +116,9 @@ public class SymZ3TraceRegisterSpace extends SymZ3TraceSpace { public void writeDown(PcodeTracePropertyAccess into) { SymZ3WriteDownHelper.writeDown(rmap, into); } + + @Override + public Entry getNextEntry(long offset) { + return rmap.getNextEntry(offset); + } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceSpace.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceSpace.java index ade3a9d4c4..ca3ad906fe 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceSpace.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceSpace.java @@ -15,7 +15,7 @@ */ package ghidra.pcode.emu.symz3.trace; -import ghidra.pcode.emu.symz3.plain.SymZ3Space; +import ghidra.pcode.emu.symz3.state.SymZ3Space; import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; import ghidra.program.model.address.AddressSpace; @@ -25,6 +25,8 @@ import ghidra.program.model.address.AddressSpace; *

    * This adds to {@link SymZ3Space} the ability to load symbolic values from a trace and the ability * to save them back into a trace. + * + * TODO: Delete me */ public abstract class SymZ3TraceSpace extends SymZ3Space { protected final AddressSpace space; diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceUniqueSpace.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceUniqueSpace.java deleted file mode 100644 index 16e7c8a6c4..0000000000 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/pcode/emu/symz3/trace/SymZ3TraceUniqueSpace.java +++ /dev/null @@ -1,108 +0,0 @@ -/* ### - * 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.emu.symz3.trace; - -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Stream; - -import com.microsoft.z3.*; - -import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; -import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; -import ghidra.program.model.address.AddressSpace; -import ghidra.symz3.model.SymValueZ3; - -/** - * The storage space for unique registers - * - *

    - * This is the actual implementation of the in-memory storage for symbolic z3 values. For a - * stand-alone emulator, this is the full state. For a trace- or Debugger-integrated emulator, this - * is a cache of values loaded from a trace backing this emulator. Most likely, that trace is the - * user's current trace. - */ -public class SymZ3TraceUniqueSpace extends SymZ3TraceSpace { - private final Map uniqvals = new HashMap(); - - public SymZ3TraceUniqueSpace(AddressSpace space, PcodeTracePropertyAccess property) { - super(space, property); - } - - private String label(long offset, int size) { - return "" + offset + ":" + size; - } - - public void set(long offset, int size, SymValueZ3 val) { - this.updateUnique(label(offset, size), val); - } - - public SymValueZ3 get(long offset, int size) { - return this.getUnique(label(offset, size)); - } - - public void updateUnique(String s, SymValueZ3 value) { - uniqvals.put(s, value); - } - - public SymValueZ3 getUnique(String s) { - return uniqvals.get(s); - } - - @Override - public String printableSummary() { - return ""; - } - - @Override - public Stream> streamValuations(Context ctx, Z3InfixPrinter z3p) { - return Stream.of(); - } - - @Override - public void set(SymValueZ3 offset, int size, SymValueZ3 val) { - assert val != null; - try (Context ctx = new Context()) { - BitVecExpr b = offset.getBitVecExpr(ctx); - if (b.isNumeral()) { - BitVecNum bvn = (BitVecNum) b; - this.set(bvn.getLong(), size, val); - } - else { - throw new AssertionError("how can we have a symbolic offset for a unique set:" + - offset + "is numeral? " + b.isNumeral() + " is BV numeral: " + b.isBVNumeral()); - } - } - } - - @Override - public SymValueZ3 get(SymValueZ3 offset, int size) { - try (Context ctx = new Context()) { - BitVecExpr b = offset.getBitVecExpr(ctx); - if (b.isNumeral()) { - BitVecNum bvn = (BitVecNum) b; - return this.get(bvn.getLong(), size); - } - throw new AssertionError("how can we have a symbolic offset for unique get?"); - } - } - - @Override - public void writeDown(PcodeTracePropertyAccess into) { - return; - } -} diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/Z3SummaryProvider.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/Z3SummaryProvider.java index edc40fc90c..fd767269e1 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/Z3SummaryProvider.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/Z3SummaryProvider.java @@ -30,16 +30,16 @@ import ghidra.app.services.DebuggerEmulationService.CachedEmulator; import ghidra.app.services.DebuggerEmulationService.EmulatorStateListener; import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.util.pcode.StringPcodeFormatter; -import ghidra.debug.api.emulation.DebuggerPcodeEmulatorFactory; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; +import ghidra.debug.api.emulation.EmulatorFactory; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.options.AutoOptions; import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; -import ghidra.pcode.emu.symz3.full.SymZ3DebuggerPcodeEmulator; -import ghidra.pcode.emu.symz3.full.SymZ3DebuggerPcodeEmulatorFactory; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.symz3.SymZ3EmulatorFactory; import ghidra.pcode.emu.symz3.lib.Z3InfixPrinter; +import ghidra.pcode.emu.symz3.state.SymZ3PcodeEmulator; import ghidra.program.model.address.Address; import ghidra.program.util.ProgramLocation; import ghidra.trace.model.Trace; @@ -63,7 +63,7 @@ public class Z3SummaryProvider extends ComponentProviderAdapter { private final EmulatorStateListener emuListener = new EmulatorStateListener() { @Override public void stopped(CachedEmulator emu) { - if (!(emu.emulator() instanceof SymZ3DebuggerPcodeEmulator z3emu)) { + if (!(emu.emulator() instanceof SymZ3PcodeEmulator z3emu)) { setFactoryToZ3(); return; } @@ -145,8 +145,8 @@ public class Z3SummaryProvider extends ComponentProviderAdapter { } TraceSchedule time = current.getTime(); - DebuggerPcodeMachine emu = emulationService.getCachedEmulator(trace, time); - if (!(emu instanceof SymZ3DebuggerPcodeEmulator z3Emu)) { + PcodeMachine emu = emulationService.getCachedEmulator(trace, time); + if (!(emu instanceof SymZ3PcodeEmulator z3Emu)) { /** LATER: It'd be nice if the summary were written down somewhere */ setFactoryToZ3(); return; @@ -156,8 +156,8 @@ public class Z3SummaryProvider extends ComponentProviderAdapter { } private void setFactoryToZ3() { - for (DebuggerPcodeEmulatorFactory factory : emulationService.getEmulatorFactories()) { - if (factory instanceof SymZ3DebuggerPcodeEmulatorFactory z3factory) { + for (EmulatorFactory factory : emulationService.getEmulatorFactories()) { + if (factory instanceof SymZ3EmulatorFactory z3factory) { emulationService.setEmulatorFactory(z3factory); emulationService.invalidateCache(); return; @@ -165,7 +165,7 @@ public class Z3SummaryProvider extends ComponentProviderAdapter { } } - public void populateSummaryFromEmulator(SymZ3DebuggerPcodeEmulator emu) { + public void populateSummaryFromEmulator(SymZ3PcodeEmulator emu) { try (Context ctx = new Context()) { Z3InfixPrinter z3p = new Z3InfixPrinter(ctx); information.setInformation(emu.streamValuations(ctx, z3p), diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/field/SymZ3DebuggerRegisterColumnFactory.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/field/SymZ3DebuggerRegisterColumnFactory.java index fae8f85290..276e90fa5c 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/field/SymZ3DebuggerRegisterColumnFactory.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/field/SymZ3DebuggerRegisterColumnFactory.java @@ -22,7 +22,7 @@ import ghidra.app.plugin.core.debug.gui.register.RegisterRow; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.ServiceProvider; -import ghidra.pcode.emu.symz3.trace.SymZ3TracePcodeExecutorStatePiece; +import ghidra.pcode.emu.symz3.state.SymZ3PieceHandler; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; import ghidra.trace.model.Trace; @@ -37,7 +37,7 @@ import ghidra.trace.model.property.TracePropertyMapSpace; * screen. */ public class SymZ3DebuggerRegisterColumnFactory implements DebuggerRegisterColumnFactory { - protected static final String PROP_NAME = SymZ3TracePcodeExecutorStatePiece.NAME; + protected static final String PROP_NAME = SymZ3PieceHandler.NAME; public static final String COL_NAME = "Symbolic Expression"; @Override diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/field/SymZ3FieldFactory.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/field/SymZ3FieldFactory.java index 8db260a0a4..a6cf8794ba 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/field/SymZ3FieldFactory.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/main/java/ghidra/symz3/gui/field/SymZ3FieldFactory.java @@ -27,7 +27,7 @@ import ghidra.app.util.viewer.format.FieldFormatModel; import ghidra.app.util.viewer.proxy.ProxyObj; import ghidra.framework.options.Options; import ghidra.framework.options.ToolOptions; -import ghidra.pcode.emu.symz3.trace.SymZ3TracePcodeExecutorStatePiece; +import ghidra.pcode.emu.symz3.state.SymZ3PieceHandler; import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.util.StringPropertyMap; import ghidra.program.util.ProgramLocation; @@ -41,7 +41,7 @@ import ghidra.symz3.model.SymValueZ3; * framework. I used the "sample" module's {@code EntropyFieldFactory} for reference. */ public class SymZ3FieldFactory extends FieldFactory { - public static final String PROPERTY_NAME = SymZ3TracePcodeExecutorStatePiece.NAME; + public static final String PROPERTY_NAME = SymZ3PieceHandler.NAME; public static final GColor COLOR = new GColor("color.fg.listing.z3symbolic"); public static final String FIELD_NAME = "Z3 Symbolic Value"; diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/bugTest/SymZ3PcodeEmulatorBugTest.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/bugTest/SymZ3PcodeEmulatorBugTest.java index a6fc6fe6a0..64edc4cb9b 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/bugTest/SymZ3PcodeEmulatorBugTest.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/bugTest/SymZ3PcodeEmulatorBugTest.java @@ -28,7 +28,7 @@ import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibraryTest; import ghidra.pcode.emu.symz3.SymZ3PcodeThread; import ghidra.pcode.emu.symz3.lib.SymZ3EmuUnixFileSystem; import ghidra.pcode.emu.symz3.lib.SymZ3LinuxAmd64SyscallLibrary; -import ghidra.pcode.emu.symz3.plain.SymZ3PcodeEmulator; +import ghidra.pcode.emu.symz3.state.SymZ3PcodeEmulator; import ghidra.pcode.exec.PcodeUseropLibrary; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulatorTest.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulatorTest.java index 19f606566b..8606a472e3 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulatorTest.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/full/SymZ3DebuggerPcodeEmulatorTest.java @@ -22,36 +22,27 @@ import org.junit.Before; import org.junit.Test; import db.Transaction; -import ghidra.app.plugin.assembler.Assembler; -import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; -import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; import ghidra.app.services.DebuggerEmulationService; import ghidra.app.services.DebuggerEmulationService.EmulationResult; -import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; -import ghidra.pcode.emu.symz3.trace.SymZ3TracePcodeExecutorState; -import ghidra.pcode.emu.symz3.trace.SymZ3TracePcodeExecutorStatePiece; -import ghidra.program.model.util.StringPropertyMap; -import ghidra.program.util.ProgramLocation; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.symz3.SymZ3EmulatorFactory; +import ghidra.pcode.emu.symz3.state.SymZ3PcodeEmulator; +import ghidra.pcode.emu.symz3.state.SymZ3PcodeExecutorState; import ghidra.trace.database.ToyDBTraceBuilder.ToySchemaBuilder; -import ghidra.trace.model.*; -import ghidra.trace.model.property.TracePropertyMap; -import ghidra.trace.model.property.TracePropertyMapSpace; +import ghidra.trace.model.Lifespan; +import ghidra.trace.model.Trace; import ghidra.trace.model.target.schema.SchemaContext; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.schedule.*; import ghidra.util.Msg; -import ghidra.util.task.TaskMonitor; public class SymZ3DebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebuggerTest { - private DebuggerStaticMappingService mappingService; private DebuggerEmulationService emuService; @Before public void setUpSymTest() throws Throwable { - mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class); emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); } @@ -59,7 +50,7 @@ public class SymZ3DebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger public void testFactoryDiscovered() { assertEquals(1, emuService.getEmulatorFactories() .stream() - .filter(f -> f instanceof SymZ3DebuggerPcodeEmulatorFactory) + .filter(f -> f instanceof SymZ3EmulatorFactory) .count()); } @@ -72,7 +63,7 @@ public class SymZ3DebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger @Test public void testFactoryCreate() throws Exception { - emuService.setEmulatorFactory(new SymZ3DebuggerPcodeEmulatorFactory()); + emuService.setEmulatorFactory(new SymZ3EmulatorFactory()); createAndOpenTrace(); @@ -97,69 +88,10 @@ public class SymZ3DebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger } }); - DebuggerPcodeMachine emu = emuService.getCachedEmulator(tb.trace, result.schedule()); - assertTrue(emu instanceof SymZ3DebuggerPcodeEmulator); + PcodeMachine emu = emuService.getCachedEmulator(tb.trace, result.schedule()); + assertTrue(emu instanceof SymZ3PcodeEmulator); - SymZ3TracePcodeExecutorState state = (SymZ3TracePcodeExecutorState) emu.getSharedState(); + SymZ3PcodeExecutorState state = (SymZ3PcodeExecutorState) emu.getSharedState(); Msg.debug(this, "here is your state: " + state); } - - @Test - public void testReadsProgramUsrProperties() throws Exception { - emuService.setEmulatorFactory(new SymZ3DebuggerPcodeEmulatorFactory()); - - createAndOpenTrace("x86:LE:64:default"); - createProgramFromTrace(); - - intoProject(program); - intoProject(tb.trace); - - programManager.openProgram(program); - - TraceThread thread; - try (Transaction tid = tb.startTransaction()) { - tb.createRootObject(buildContext(), "Target"); - mappingService.addMapping( - new DefaultTraceLocation(tb.trace, null, Lifespan.nowOn(0), tb.addr(0x55550000)), - new ProgramLocation(program, tb.addr(0x00400000)), 0x1000, false); - thread = tb.getOrAddThread("Threads[0]", 0); - tb.createObjectsFramesAndRegs(thread, Lifespan.nowOn(0), tb.host, 1); - tb.exec(0, thread, 0, """ - RIP = 0x55550000; - """); - } - waitForDomainObject(tb.trace); - waitForPass(() -> assertEquals(new ProgramLocation(program, tb.addr(0x00400000)), - mappingService.getOpenMappedLocation( - new DefaultTraceLocation(tb.trace, null, Lifespan.at(0), tb.addr(0x55550000))))); - - try (Transaction tid = program.openTransaction("Assemble")) { - program.getMemory() - .createInitializedBlock(".text", tb.addr(0x00400000), 0x1000, (byte) 0, - TaskMonitor.DUMMY, false); - StringPropertyMap progSymMap = program.getUsrPropertyManager() - .createStringPropertyMap(SymZ3TracePcodeExecutorStatePiece.NAME); - - progSymMap.add(tb.addr(0x00400800), "test_0"); - Assembler asm = Assemblers.getAssembler(program); - - // TODO: I should be able to make this use a RIP-relative address - asm.assemble(tb.addr(0x00400000), - "MOV RAX, [0x55550800]"); // was [0x00400800], but fixed address is a problem. - } - - TraceSchedule time = TraceSchedule.parse("0:t%d-1".formatted(thread.getKey())); - long scratch = emuService.emulate(tb.trace, time, TaskMonitor.DUMMY); - - TracePropertyMap traceSymMap = tb.trace.getAddressPropertyManager() - .getPropertyMap(SymZ3TracePcodeExecutorStatePiece.NAME, String.class); - TracePropertyMapSpace symRegSpace = - traceSymMap.getPropertyMapRegisterSpace(thread, 0, false); - - Msg.info(this, symRegSpace.getEntries(Lifespan.at(scratch), tb.reg("RAX"))); - - //SymZ3DebuggerPcodeEmulator emu = - // (SymZ3DebuggerPcodeEmulator) emuService.getCachedEmulator(tb.trace, time); - //emu.printSymbolicSummary(); - } } diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeEmulatorTest.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeEmulatorTest.java index 790ffd01aa..a1d10d1e44 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeEmulatorTest.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/plain/SymZ3PcodeEmulatorTest.java @@ -34,6 +34,7 @@ import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibraryTest; import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibraryTest.Syscall; import ghidra.pcode.emu.symz3.SymZ3PcodeThread; import ghidra.pcode.emu.symz3.lib.*; +import ghidra.pcode.emu.symz3.state.SymZ3PcodeEmulator; import ghidra.pcode.emu.sys.EmuProcessExitedException; import ghidra.pcode.exec.PcodeUseropLibrary; import ghidra.program.model.address.Address; diff --git a/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeEmulatorTest.java b/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeEmulatorTest.java index f4e2a4e3b5..a8fb1713fd 100644 --- a/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeEmulatorTest.java +++ b/Ghidra/Extensions/SymbolicSummaryZ3/src/test/java/ghidra/pcode/emu/symz3/trace/SymZ3TracePcodeEmulatorTest.java @@ -25,14 +25,19 @@ import com.microsoft.z3.Context; import db.Transaction; import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.symz3.SymZ3EmulatorFactory; +import ghidra.pcode.emu.symz3.state.SymZ3PcodeEmulator; +import ghidra.pcode.emu.symz3.state.SymZ3PieceHandler; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.exec.trace.AbstractTracePcodeEmulatorTest; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; import ghidra.symz3.model.SymValueZ3; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.model.Lifespan; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.property.TracePropertyMap; import ghidra.trace.model.property.TracePropertyMapSpace; import ghidra.trace.model.thread.TraceThread; @@ -40,6 +45,17 @@ import ghidra.util.Msg; public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest { + @Override + protected Writer createWriter(TracePlatform platform, long snap) { + Writer writer = super.createWriter(platform, snap); + SymZ3EmulatorFactory.addHandlers(writer); + return writer; + } + + SymZ3PcodeEmulator createEmulator(TracePlatform platform, Writer writer) { + return new SymZ3PcodeEmulator(platform.getLanguage(), writer.callbacks()); + } + /** * Test that state is properly read from trace memory * @@ -55,8 +71,7 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest try (Transaction tid = tb.startTransaction()) { TracePropertyMap symMap = tb.trace.getAddressPropertyManager() - .getOrCreatePropertyMap(SymZ3TracePcodeExecutorStatePiece.NAME, - String.class); + .getOrCreatePropertyMap(SymZ3PieceHandler.NAME, String.class); try (Context ctx = new Context()) { SymValueZ3 test = new SymValueZ3(ctx, ctx.mkBV(0, 8)); @@ -65,7 +80,8 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest } } - SymZ3TracePcodeEmulator emu = new SymZ3TracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + SymZ3PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getExecutor().executeSleigh("RAX = *0x00400000:8;"); @@ -92,14 +108,14 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest try (Transaction tid = tb.startTransaction()) { TracePropertyMap symZ3Map = tb.trace.getAddressPropertyManager() - .getOrCreatePropertyMap(SymZ3TracePcodeExecutorStatePiece.NAME, - String.class); + .getOrCreatePropertyMap(SymZ3PieceHandler.NAME, String.class); TracePropertyMapSpace mapSpace = symZ3Map.getPropertyMapRegisterSpace(thread, 0, true); mapSpace.set(Lifespan.nowOn(0), regRBX, "test_0"); } - SymZ3TracePcodeEmulator emu = new SymZ3TracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + SymZ3PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); try (Context ctx = new Context()) { @@ -128,7 +144,8 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest public void testWriteStateMemory() throws Throwable { try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { initTrace(tb, "", List.of()); - SymZ3TracePcodeEmulator emu = new SymZ3TracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + SymZ3PcodeEmulator emu = createEmulator(tb.host, writer); Address addr = tb.addr(0x00400000); try (Context ctx = new Context()) { @@ -142,11 +159,11 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest } try (Transaction tid = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 0); + writer.writeDown(1); } TracePropertyMap map = tb.trace.getAddressPropertyManager() - .getPropertyMap(SymZ3TracePcodeExecutorStatePiece.NAME, String.class); + .getPropertyMap(SymZ3PieceHandler.NAME, String.class); TracePropertyMapSpace backing = map.getPropertyMapSpace(addr.getAddressSpace(), false); @@ -168,7 +185,8 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest TraceThread thread = initTrace(tb, "", List.of()); - SymZ3TracePcodeEmulator emu = new SymZ3TracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + SymZ3PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); try (Context ctx = new Context()) { @@ -178,13 +196,13 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest } try (Transaction tid = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 0); + writer.writeDown(1); // grab the name property from the abstract class.... TracePropertyMap symMap = tb.trace.getAddressPropertyManager() - .getPropertyMap(SymZ3TracePcodeExecutorStatePiece.NAME, String.class); + .getPropertyMap(SymZ3PieceHandler.NAME, String.class); Msg.info(this, "we have a symMap: " + symMap); @@ -216,16 +234,17 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "MOV qword ptr [RBP + -0x28],RAX", "MOV RBP, qword ptr [RBP + -0x28]")); - SymZ3TracePcodeEmulator emu = new SymZ3TracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + SymZ3PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); try (Transaction tid = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 0); + writer.writeDown(1); } emuThread.stepInstruction(); try (Transaction tid = tb.startTransaction()) { - emu.writeDown(tb.host, 2, 0); + writer.writeDown(2); } emuThread.stepInstruction(); emuThread.stepInstruction(); @@ -239,12 +258,12 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emu.printSymbolicSummary(System.out); try (Transaction tid = tb.startTransaction()) { - emu.writeDown(tb.host, 5, 0); + writer.writeDown(5); } TracePropertyMap symMap = tb.trace.getAddressPropertyManager() - .getPropertyMap(SymZ3TracePcodeExecutorStatePiece.NAME, String.class); + .getPropertyMap(SymZ3PieceHandler.NAME, String.class); TracePropertyMapSpace backing = symMap.getPropertyMapSpace(ram, false); @@ -263,27 +282,28 @@ public class SymZ3TracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "MOV dword ptr [RBP + -0x18],EAX", "MOVZX EAX, byte ptr [RBP + -0x28]")); - SymZ3TracePcodeEmulator emu = new SymZ3TracePcodeEmulator(tb.host, 0); + Writer writer = createWriter(tb.host, 0); + SymZ3PcodeEmulator emu = createEmulator(tb.host, writer); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.stepInstruction(); try (Transaction tid = tb.startTransaction()) { - emu.writeDown(tb.host, 1, 0); + writer.writeDown(1); } emuThread.stepInstruction(); try (Transaction tid = tb.startTransaction()) { - emu.writeDown(tb.host, 2, 0); + writer.writeDown(2); } emu.printSymbolicSummary(System.out); try (Transaction tid = tb.startTransaction()) { - emu.writeDown(tb.host, 5, 0); + writer.writeDown(5); } TracePropertyMap symMap = tb.trace.getAddressPropertyManager() - .getPropertyMap(SymZ3TracePcodeExecutorStatePiece.NAME, String.class); + .getPropertyMap(SymZ3PieceHandler.NAME, String.class); AddressSpace noSpace = Address.NO_ADDRESS.getAddressSpace(); TracePropertyMapSpace backing = symMap.getPropertyMapSpace(noSpace, false); diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java index 4abbd1329f..bdebd2aad4 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java @@ -4,9 +4,9 @@ * 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. @@ -28,16 +28,18 @@ import java.nio.charset.Charset; import db.Transaction; import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; -import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationIntegration; import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.script.GhidraScript; import ghidra.debug.flatapi.FlatDebuggerAPI; import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.emu.PcodeEmulator; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.pcode.utils.Utils; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.Address; @@ -135,7 +137,8 @@ public class DebuggerEmuExampleScript extends GhidraScript implements FlatDebugg */ TracePlatform host = trace.getPlatformManager().getHostPlatform(); DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool, null, host, 0); - BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(access) { + Writer writer = DebuggerEmulationIntegration.bytesDelayedWriteTrace(access); + PcodeEmulator emulator = new PcodeEmulator(access.getLanguage(), writer.callbacks()) { @Override protected PcodeUseropLibrary createUseropLibrary() { return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this); @@ -168,7 +171,7 @@ public class DebuggerEmuExampleScript extends GhidraScript implements FlatDebugg println("Executing: " + thread.getCounter()); thread.stepInstruction(); snapshot = time.createSnapshot("Stepped to " + thread.getCounter()); - emulator.writeDown(host, snapshot.getKey(), 0); + writer.writeDown(snapshot.getKey()); } printerr("We should not have completed 10 steps!"); } diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/EmuDeskCheckScript.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/EmuDeskCheckScript.java index 635334ea5b..4559616de9 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/EmuDeskCheckScript.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/EmuDeskCheckScript.java @@ -4,9 +4,9 @@ * 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. @@ -19,7 +19,7 @@ import java.util.*; import org.apache.commons.lang3.tuple.Pair; -import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationIntegration; import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.script.GhidraScript; @@ -29,8 +29,10 @@ import ghidra.debug.api.watch.WatchRow; import ghidra.debug.flatapi.FlatDebuggerAPI; import ghidra.docking.settings.*; import ghidra.pcode.emu.BytesPcodeThread; +import ghidra.pcode.emu.PcodeEmulator; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.pcode.struct.StructuredSleigh; import ghidra.pcode.utils.Utils; import ghidra.program.model.address.Address; @@ -113,14 +115,17 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI } } - BytesDebuggerPcodeEmulator emu = new BytesDebuggerPcodeEmulator( - new DefaultPcodeDebuggerAccess(state.getTool(), null, platform, snap)) { + DefaultPcodeDebuggerAccess access = + new DefaultPcodeDebuggerAccess(state.getTool(), null, platform, snap); + Writer writer = DebuggerEmulationIntegration.bytesDelayedWriteTrace(access); + PcodeEmulator emu = new PcodeEmulator(access.getLanguage(), writer.callbacks()) { TraceSchedule position = TraceSchedule.snap(snap); @Override protected BytesPcodeThread createThread(String name) { return new BytesPcodeThread(name, this) { - TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, name); + TraceThread thread = + trace.getThreadManager().getLiveThreadByPath(snap, name); PcodeExecutor> inspector = new PcodeExecutor<>(language, new PairedPcodeArithmetic<>(arithmetic, @@ -189,8 +194,7 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI // Configuration and support kruft below // /////////////////////////////////////////// - public record Watch(String expression, TypeRec type, Settings settings) { - } + public record Watch(String expression, TypeRec type, Settings settings) {} interface Setting { void set(Settings settings); 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 index 1fba558ff1..3dd0cf967e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java @@ -15,8 +15,8 @@ */ package ghidra.app.emulator; -import generic.ULongSpan; -import generic.ULongSpan.ULongSpanSet; +import java.util.Arrays; + import ghidra.app.emulator.memory.MemoryLoadImage; import ghidra.app.emulator.state.RegisterState; import ghidra.app.plugin.processors.sleigh.SleighLanguage; @@ -33,8 +33,7 @@ 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.address.*; import ghidra.program.model.lang.*; import ghidra.program.model.pcode.PcodeOp; import ghidra.util.Msg; @@ -50,7 +49,8 @@ import ghidra.util.task.TaskMonitor; * 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(...)`. + * such old use cases, {@code new Emulator(...)} must be replaced by + * {@code new DefaultEmulator(...)}. */ @Transitional public class AdaptedEmulator implements Emulator { @@ -67,14 +67,12 @@ public class AdaptedEmulator implements Emulator { @Override protected PcodeExecutorState createSharedState() { - return new AdaptedBytesPcodeExecutorState(language, - new StateBacking(faultHandler, loadImage)); + return new AdaptedBytesPcodeExecutorState(language, faultHandler, loadImage); } @Override protected PcodeExecutorState createLocalState(PcodeThread thread) { - return new AdaptedBytesPcodeExecutorState(language, - new StateBacking(faultHandler, null)); + return new AdaptedBytesPcodeExecutorState(language, faultHandler, null); } @Override @@ -147,11 +145,50 @@ public class AdaptedEmulator implements Emulator { } } - record StateBacking(MemoryFaultHandler faultHandler, MemoryLoadImage loadImage) {} + class AdaptedStateCallbacks implements PcodeStateCallbacks { + private final MemoryLoadImage loadImage; + + public AdaptedStateCallbacks(MemoryLoadImage loadImage) { + this.loadImage = loadImage; + } + + @Override + public AddressSetView readUninitialized(PcodeExecutorStatePiece piece, + AddressSetView set) { + PcodeExecutorStatePiece bytesPiece = + PcodeStateCallbacks.checkValueDomain(piece, byte[].class); + if (bytesPiece == null) { + return set; + } + if (set.isEmpty()) { + return set; + } + AddressSpace space = set.getMinAddress().getAddressSpace(); + if (loadImage == null) { + if (space.isUniqueSpace()) { + throw new AccessPcodeExecutionException( + "Attempted to read from uninitialized unique: " + set); + } + return set; + } + AddressRange bound = new AddressRangeImpl(set.getMinAddress(), set.getMaxAddress()); + byte[] data = new byte[(int) bound.getLength()]; + loadImage.loadFill(data, data.length, bound.getMinAddress(), 0, false); + for (AddressRange range : set) { + int offset = (int) range.getMinAddress().subtract(bound.getMinAddress()); + byte[] portion = Arrays.copyOfRange(data, offset, offset + (int) range.getLength()); + bytesPiece.setVarInternal(space, range.getMinAddress().getOffset(), portion.length, + portion); + } + return new AddressSet(); + } + } class AdaptedBytesPcodeExecutorState extends BytesPcodeExecutorState { - public AdaptedBytesPcodeExecutorState(Language language, StateBacking backing) { - super(new AdaptedBytesPcodeExecutorStatePiece(language, backing)); + public AdaptedBytesPcodeExecutorState(Language language, MemoryFaultHandler faultHandler, + MemoryLoadImage loadImage) { + super(new AdaptedBytesPcodeExecutorStatePiece(language, + new AdaptedStateCallbacks(loadImage), faultHandler)); } @Override @@ -193,63 +230,41 @@ public class AdaptedEmulator implements Emulator { static class AdaptedBytesPcodeExecutorStatePiece extends AbstractBytesPcodeExecutorStatePiece { - private final StateBacking backing; + private final MemoryFaultHandler faultHandler; - public AdaptedBytesPcodeExecutorStatePiece(Language language, StateBacking backing) { - super(language); - this.backing = backing; + public AdaptedBytesPcodeExecutorStatePiece(Language language, PcodeStateCallbacks cb, + MemoryFaultHandler faultHandler) { + super(language, cb); + this.faultHandler = faultHandler; } @Override - protected AbstractSpaceMap newSpaceMap() { - return new SimpleSpaceMap<>() { - @Override - protected AdaptedBytesPcodeExecutorStateSpace newSpace(AddressSpace space) { - return new AdaptedBytesPcodeExecutorStateSpace(language, space, backing); - } - }; + protected AdaptedBytesPcodeExecutorStateSpace newSpace(AddressSpace space) { + return new AdaptedBytesPcodeExecutorStateSpace(language, space, this, faultHandler); } } - static class AdaptedBytesPcodeExecutorStateSpace - extends BytesPcodeExecutorStateSpace { + static class AdaptedBytesPcodeExecutorStateSpace extends BytesPcodeExecutorStateSpace { + private final MemoryFaultHandler faultHandler; + public AdaptedBytesPcodeExecutorStateSpace(Language language, AddressSpace space, - StateBacking backing) { - super(language, space, backing); + AbstractBytesPcodeExecutorStatePiece piece, MemoryFaultHandler faultHandler) { + super(language, space, piece); + this.faultHandler = faultHandler; } @Override - protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) { - if (uninitialized.isEmpty()) { - return uninitialized; - } - if (backing.loadImage == null) { - if (space.isUniqueSpace()) { - throw new AccessPcodeExecutionException( - "Attempted to read from uninitialized unique: " + uninitialized); - } - return uninitialized; - } - 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()); - } - return bytes.getUninitialized(bound.min(), bound.max()); - } - - @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()); + protected void warnUninit(AddressSetView uninitialized) { + AddressRange bound = new AddressRangeImpl( + uninitialized.getMinAddress(), + uninitialized.getMaxAddress()); + byte[] data = new byte[(int) bound.getLength()]; + if (faultHandler.uninitializedRead(bound.getMinAddress(), data.length, data, 0)) { + for (AddressRange range : uninitialized) { + int offset = (int) range.getMinAddress().subtract(bound.getMinAddress()); + byte[] portion = + Arrays.copyOfRange(data, offset, offset + (int) range.getLength()); + bytes.putData(range.getMinAddress().getOffset(), portion, 0, portion.length); } } } 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 5d991ef90b..ec8af866d6 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 @@ -54,10 +54,10 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { * @return the same language, cast to Sleigh */ protected static SleighLanguage assertSleigh(Language language) { - if (!(language instanceof SleighLanguage)) { + if (!(language instanceof SleighLanguage slang)) { throw new IllegalArgumentException("Emulation requires a sleigh language"); } - return (SleighLanguage) language; + return slang; } protected final SleighLanguage language; @@ -78,14 +78,18 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { protected final Map injects = new HashMap<>(); protected final SparseAddressRangeMap accessBreakpoints = new SparseAddressRangeMap<>(); + protected final PcodeEmulationCallbacks cb; /** * Construct a p-code machine with the given language and arithmetic * * @param language the processor language to be emulated + * @param cb callbacks to receive emulation events */ - public AbstractPcodeMachine(Language language) { + public AbstractPcodeMachine(Language language, PcodeEmulationCallbacks cb) { this.language = assertSleigh(language); + this.cb = cb; + cb.emulatorCreated(this); this.arithmetic = createArithmetic(); this.library = createUseropLibrary(); @@ -205,6 +209,7 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { * @param language the language requiring pluggable initialization * @return the initializer */ + @Deprecated(forRemoval = true, since = "12.0") protected static PcodeStateInitializer getPluggableInitializer(Language language) { for (PcodeStateInitializer init : ClassSearcher.getInstances(PcodeStateInitializer.class)) { if (init.isApplicable(language)) { @@ -219,6 +224,7 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { * * @see #getPluggableInitializer(Language) */ + @Deprecated(forRemoval = true, since = "12.0") protected void doPluggableInitialization() { if (initializer != null) { initializer.initializeMachine(this); @@ -237,6 +243,7 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { } PcodeThread thread = createThread(name); threads.put(name, thread); + cb.threadCreated(thread); return thread; } @@ -259,6 +266,7 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { if (sharedState == null) { sharedState = createSharedState(); doPluggableInitialization(); + cb.sharedStateCreated(this); } return sharedState; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ComposedPcodeEmulationCallbacks.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ComposedPcodeEmulationCallbacks.java new file mode 100644 index 0000000000..7852ceb51e --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ComposedPcodeEmulationCallbacks.java @@ -0,0 +1,241 @@ +/* ### + * 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.emu; + +import java.util.List; + +import ghidra.pcode.exec.*; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.RegisterValue; +import ghidra.program.model.listing.Instruction; +import ghidra.program.model.pcode.PcodeOp; + +/** + * A mechanism for broadcasting (mostly) callbacks among several receivers. + * + *

    + * The two (currently) methods that do not implement a pure broadcast pattern are + * {@link #handleMissingUserop(PcodeThread, PcodeOp, PcodeFrame, String, PcodeUseropLibrary)} and + * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)}. For a missing + * userop, it will terminate after the first delegate returns {@code true}, in which case it also + * returns {@code true}. If all delegates return {@code false}, then it returns {@code false}. For + * an uninitialized read, each delegate's returned "still-uninitialized" set is passed to the + * subsequent delegate. The first delegate gets the set as passed into the composition. The set + * returned by the composition is that returned by the last delegate. This terminates early when any + * delegate returns the empty set. + * + *

    + * One (currently) other method has a non-void return type: + * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int)}. The + * callback is broadcast as expected, and the return value is the max returned by the delegates. + * + * @param the emulator's value domain + */ +public class ComposedPcodeEmulationCallbacks implements PcodeEmulationCallbacks { + private final List> delegates; + + /** + * Construct a composition of delegate callbacks + * + * @param delegates the delegates + */ + @SafeVarargs + public ComposedPcodeEmulationCallbacks(PcodeEmulationCallbacks... delegates) { + this.delegates = List.of(delegates); + } + + @Override + public void emulatorCreated(PcodeMachine machine) { + for (PcodeEmulationCallbacks d : delegates) { + d.emulatorCreated(machine); + } + } + + @Override + public void sharedStateCreated(PcodeMachine machine) { + for (PcodeEmulationCallbacks d : delegates) { + d.sharedStateCreated(machine); + } + } + + @Override + public void threadCreated(PcodeThread thread) { + for (PcodeEmulationCallbacks d : delegates) { + d.threadCreated(thread); + } + } + + @Override + public PcodeProgram getInject(PcodeThread thread, Address address) { + for (PcodeEmulationCallbacks d : delegates) { + PcodeProgram inject = d.getInject(thread, address); + if (inject != null) { + return inject; + } + } + return null; + } + + @Override + public void beforeExecuteInject(PcodeThread thread, Address address, PcodeProgram program) { + for (PcodeEmulationCallbacks d : delegates) { + d.beforeExecuteInject(thread, address, program); + } + } + + @Override + public void afterExecuteInject(PcodeThread thread, Address address) { + for (PcodeEmulationCallbacks d : delegates) { + d.afterExecuteInject(thread, address); + } + } + + @Override + public void beforeDecodeInstruction(PcodeThread thread, Address counter, + RegisterValue context) { + for (PcodeEmulationCallbacks d : delegates) { + d.beforeDecodeInstruction(thread, counter, context); + } + } + + @Override + public void beforeExecuteInstruction(PcodeThread thread, Instruction instruction, + PcodeProgram program) { + for (PcodeEmulationCallbacks d : delegates) { + d.beforeExecuteInstruction(thread, instruction, program); + } + } + + @Override + public void afterExecuteInstruction(PcodeThread thread, Instruction instruction) { + for (PcodeEmulationCallbacks d : delegates) { + d.afterExecuteInstruction(thread, instruction); + } + } + + @Override + public void beforeStepOp(PcodeThread thread, PcodeOp op, PcodeFrame frame) { + for (PcodeEmulationCallbacks d : delegates) { + d.beforeStepOp(thread, op, frame); + } + } + + @Override + public void afterStepOp(PcodeThread thread, PcodeOp op, PcodeFrame frame) { + for (PcodeEmulationCallbacks d : delegates) { + d.afterStepOp(thread, op, frame); + } + } + + @Override + public void beforeLoad(PcodeThread thread, PcodeOp op, AddressSpace space, T offset, + int size) { + for (PcodeEmulationCallbacks d : delegates) { + d.beforeLoad(thread, op, space, offset, size); + } + } + + @Override + public void afterLoad(PcodeThread thread, PcodeOp op, AddressSpace space, T offset, int size, + T value) { + for (PcodeEmulationCallbacks d : delegates) { + d.afterLoad(thread, op, space, offset, size, value); + } + } + + @Override + public void beforeStore(PcodeThread thread, PcodeOp op, AddressSpace space, T offset, + int size, T value) { + for (PcodeEmulationCallbacks d : delegates) { + d.beforeStore(thread, op, space, offset, size, value); + } + } + + @Override + public void afterStore(PcodeThread thread, PcodeOp op, AddressSpace space, T offset, + int size, T value) { + for (PcodeEmulationCallbacks d : delegates) { + d.afterStore(thread, op, space, offset, size, value); + } + } + + @Override + public void afterBranch(PcodeThread thread, PcodeOp op, Address target) { + for (PcodeEmulationCallbacks d : delegates) { + d.afterBranch(thread, op, target); + } + } + + @Override + public boolean handleMissingUserop(PcodeThread thread, PcodeOp op, PcodeFrame frame, + String opName, PcodeUseropLibrary library) { + for (PcodeEmulationCallbacks d : delegates) { + if (d.handleMissingUserop(thread, op, frame, opName, library)) { + return true; + } + } + return false; + } + + @Override + public void dataWritten(PcodeThread thread, PcodeExecutorStatePiece piece, + AddressSpace space, A offset, int length, U value) { + for (PcodeEmulationCallbacks d : delegates) { + d.dataWritten(thread, piece, space, offset, length, value); + } + } + + @Override + public void dataWritten(PcodeThread thread, PcodeExecutorStatePiece piece, + Address address, int length, U value) { + for (PcodeEmulationCallbacks d : delegates) { + d.dataWritten(thread, piece, address, length, value); + } + } + + @Override + public int readUninitialized(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, A offset, int length) { + /** + * NOTE: This could use some work. It's a bit onerous to specify arbitrary, possibly + * disjoint, sets of offsets of type A. Can only be guaranteed to mean something if A is + * comparable, and I don't want to start imposing that requirements formally. What I have + * here is already complicated enough, if it even gets used. I'll wait for the need to arise + * to make this any more sophisticated. + */ + int maxL = 0; + for (PcodeEmulationCallbacks d : delegates) { + maxL = Math.max(maxL, d.readUninitialized(thread, piece, space, offset, length)); + if (maxL == length) { + return maxL; + } + } + return maxL; + } + + @Override + public AddressSetView readUninitialized(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView set) { + AddressSetView remains = set; + for (PcodeEmulationCallbacks d : delegates) { + remains = d.readUninitialized(thread, piece, remains); + if (remains.isEmpty()) { + return remains; + } + } + return remains; + } +} 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 ba553c5398..76886f2953 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 @@ -182,30 +182,47 @@ public class DefaultPcodeThread implements PcodeThread { if (suspended || thread.machine.suspended) { throw new SuspendedPcodeExecutionException(frame, null); } + thread.machine.cb.beforeStepOp(thread, op, frame); super.stepOp(op, frame, library); thread.stepped(); + thread.machine.cb.afterStepOp(thread, op, frame); } @Override - protected void checkLoad(AddressSpace space, T offset, int size) { + protected void beforeLoad(PcodeOp op, AddressSpace space, T offset, int size) { thread.checkLoad(space, offset, size); + thread.machine.cb.beforeLoad(thread, op, space, offset, size); } @Override - protected void checkStore(AddressSpace space, T offset, int size) { + protected void afterLoad(PcodeOp op, AddressSpace space, T offset, int size, T value) { + thread.machine.cb.afterLoad(thread, op, space, offset, size, value); + } + + @Override + protected void beforeStore(PcodeOp op, AddressSpace space, T offset, int size, T value) { thread.checkStore(space, offset, size); + thread.machine.cb.beforeStore(thread, op, space, offset, size, value); + } + + @Override + protected void afterStore(PcodeOp op, AddressSpace space, T offset, int size, T value) { + thread.machine.cb.afterStore(thread, op, space, offset, size, value); } @Override protected void branchToAddress(PcodeOp op, Address target) { thread.branchToAddress(target); + thread.machine.cb.afterBranch(thread, op, target); } @Override protected void onMissingUseropDef(PcodeOp op, PcodeFrame frame, String opName, PcodeUseropLibrary library) { - if (!thread.onMissingUseropDef(op, opName)) { - super.onMissingUseropDef(op, frame, opName, library); + if (!thread.machine.cb.handleMissingUserop(thread, op, frame, opName, library)) { + if (!thread.onMissingUseropDef(op, opName)) { + super.onMissingUseropDef(op, frame, opName, library); + } } } @@ -423,9 +440,11 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void stepInstruction() { assertCompletedInstruction(); + Address counter = this.counter; PcodeProgram inj = getInject(counter); if (inj != null) { instruction = null; + machine.cb.beforeExecuteInject(this, counter, inj); try { executor.execute(inj, getUseropLibrary()); } @@ -433,6 +452,7 @@ public class DefaultPcodeThread implements PcodeThread { frame = e.getFrame(); throw e; } + machine.cb.afterExecuteInject(this, counter); } else { executeInstruction(); @@ -525,6 +545,7 @@ public class DefaultPcodeThread implements PcodeThread { */ protected void advanceAfterFinished() { if (instruction == null) { // Frame resulted from an inject + machine.cb.afterExecuteInject(this, counter); frame = null; return; } @@ -539,6 +560,7 @@ public class DefaultPcodeThread implements PcodeThread { writeContext(ctx); } postExecuteInstruction(); + machine.cb.afterExecuteInstruction(this, instruction); frame = null; instruction = null; } @@ -605,9 +627,11 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void executeInstruction() { + machine.cb.beforeDecodeInstruction(this, counter, context); instruction = decoder.decodeInstruction(counter, context); PcodeProgram insProg = PcodeProgram.fromInstruction(instruction); preExecuteInstruction(); + machine.cb.beforeExecuteInstruction(this, instruction, insProg); try { frame = executor.execute(insProg, getUseropLibrary()); } @@ -628,6 +652,7 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void skipInstruction() { assertCompletedInstruction(); + machine.cb.beforeDecodeInstruction(this, counter, context); instruction = decoder.decodeInstruction(counter, context); overrideCounter(counter.addWrap(decoder.getLastLengthWithDelays())); } @@ -696,7 +721,11 @@ public class DefaultPcodeThread implements PcodeThread { * @return the injected program, most likely {@code null} */ protected PcodeProgram getInject(Address address) { - PcodeProgram inj = injects.get(address); + PcodeProgram inj = machine.cb.getInject(this, address); + if (inj != null) { + return inj; + } + inj = injects.get(address); if (inj != null) { return inj; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulationCallbacks.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulationCallbacks.java new file mode 100644 index 0000000000..1b6b219143 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulationCallbacks.java @@ -0,0 +1,524 @@ +/* ### + * 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.emu; + +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.RegisterValue; +import ghidra.program.model.listing.Instruction; +import ghidra.program.model.pcode.PcodeOp; + +/** + * A set of callbacks available for p-code emulation. + * + *

    + * Note that some emulator extensions (notably the JIT-accelerated emulator) may disable and/or + * slightly change the specification of these callbacks. Read an emulator's documentation carefully. + * That said, an extension should strive to adhere to this specification as closely as possible. + * + *

    + * See {@link PcodeEmulator} for advice regarding extending the emulator versus integrating with an + * emulator. Use of these callbacks is favored over extending an emulator when possible, as this + * favors composition of such integrations. + * + * @param the type of values in the emulator + */ +public interface PcodeEmulationCallbacks { + /** + * A singleton implementation of the callbacks that does nothing. + */ + enum NoPcodeEmulationCallbacks implements PcodeEmulationCallbacks { + /** Callbacks that do nothing */ + INSTANCE; + } + + /** + * Obtain callbacks that do nothing. + * + * @param the domain + * @return {@link NoPcodeEmulationCallbacks#INSTANCE} + */ + @SuppressWarnings("unchecked") + static PcodeEmulationCallbacks none() { + return (PcodeEmulationCallbacks) NoPcodeEmulationCallbacks.INSTANCE; + } + + /** + * The emulator has been created, but not yet finished construction. + * + *

    + * WARNING: At this point, the emulator has not been fully constructed. The most the + * callback ought to do is save a pointer to the machine. Attempting to access the machine or + * invoke any of its methods will likely result in a {@link NullPointerException}. Use the + * {@link #sharedStateCreated(PcodeMachine)} callback to access the machine after it has been + * constructed + * + * @param machine the emulator or abstract machine. + */ + default void emulatorCreated(PcodeMachine machine) { + } + + /** + * The emulator's shared state has been created. + * + *

    + * NOTE: It is possible for clients to interact with other parts of the machine, e.g., to + * create a thread, before this callback gets invoked. The shared state is created lazily, i.e., + * the first time {@link PcodeMachine#getSharedState()} gets called, whether by the client or + * the machine's internals. If a pointer to the machine is needed early, consider + * {@link #emulatorCreated(PcodeMachine)}. + * + * @param machine the emulator or abstract machine + */ + default void sharedStateCreated(PcodeMachine machine) { + } + + /** + * A new thread has just been created. + * + *

    + * The thread is fully constructed. This callback may access it. + * + * @param thread the new thread + */ + default void threadCreated(PcodeThread thread) { + } + + /** + * The emulator is preparing to decode an instruction, but is checking for injected overrides + * first. + * + * @see PcodeMachine#inject(Address, String) + * @param thread the thread + * @param address the thread's program counter + * @return null, or a p-code program to override the instruction + */ + default PcodeProgram getInject(PcodeThread thread, Address address) { + return null; + } + + /** + * The emulator is preparing to execute an injected program. + * + * @see PcodeMachine#inject(Address, String) + * @param thread the thread + * @param address the thread's program counter + * @param program the injected p-code program + */ + default void beforeExecuteInject(PcodeThread thread, Address address, PcodeProgram program) { + } + + /** + * The emulator has just finished executing an injected p-code program. + * + *

    + * If the program executed a branch, then {@code address} will be the target address. Note that + * any sane inject ought to execute a branch, even to effect fall-through, otherwise the program + * counter cannot advance. + * + * @param thread the thread + * @param address the thread's program counter + */ + default void afterExecuteInject(PcodeThread thread, Address address) { + } + + /** + * The emulator, having found no injects, is preparing to decode an instruction. + * + * @param thread the thread + * @param counter the thread's program counter + * @param context the decode contextreg value + */ + default void beforeDecodeInstruction(PcodeThread thread, Address counter, + RegisterValue context) { + } + + /** + * The emulator is preparing to execute a decoded instruction. + * + * @param thread the thread + * @param instruction the decoded instruction + * @param program the instruction's p-code program + */ + default void beforeExecuteInstruction(PcodeThread thread, Instruction instruction, + PcodeProgram program) { + } + + /** + * The emulator has finished executing an instruction. + * + * @param thread the thread + * @param instruction the just-executed instruction + */ + default void afterExecuteInstruction(PcodeThread thread, Instruction instruction) { + } + + /** + * The emulator is preparing to execute a p-code op. + * + * @param thread the thread + * @param op the op + * @param frame the frame for the current p-code program + */ + default void beforeStepOp(PcodeThread thread, PcodeOp op, PcodeFrame frame) { + } + + /** + * The emulator has just executed a p-code op. + * + * @param thread the thread + * @param op the op + * @param frame the frame for the current p-code program + */ + default void afterStepOp(PcodeThread thread, PcodeOp op, PcodeFrame frame) { + } + + /** + * The emulator is preparing to load a value from its execution state. + * + * @param thread the thread + * @param op the {@link PcodeOp#LOAD} op + * @param space the address space of the operand + * @param offset the offset of the operand + * @param size the size of the operand + */ + default void beforeLoad(PcodeThread thread, PcodeOp op, AddressSpace space, T offset, + int size) { + } + + /** + * The emulator has just loaded a value from its execution state. + * + * @param thread the thread + * @param op the {@link PcodeOp#LOAD} op + * @param space the address space of the operand + * @param offset the offset of the operand + * @param size the size of the operand + * @param value the value loaded + */ + default void afterLoad(PcodeThread thread, PcodeOp op, AddressSpace space, T offset, + int size, T value) { + } + + /** + * The emulator is preparing to store a value into its execution state. + * + * @param thread the thread + * @param op the {@link PcodeOp#STORE} op + * @param space the address space of the operand + * @param offset the offset of the operand + * @param size the size of the operand + * @param value the value to store + */ + default void beforeStore(PcodeThread thread, PcodeOp op, AddressSpace space, T offset, + int size, T value) { + } + + /** + * The emulator has just stored a value into its execution state. + * + * @param thread the thread + * @param op the {@link PcodeOp#STORE} op + * @param space the address space of the operand + * @param offset the offset of the operand + * @param size the size of the operand + * @param value the value stored + */ + default void afterStore(PcodeThread thread, PcodeOp op, AddressSpace space, T offset, + int size, T value) { + } + + /** + * The emulator has just branched to an address. + * + * @param thread the thread + * @param op the branching op + * @param target the new program counter + */ + default void afterBranch(PcodeThread thread, PcodeOp op, Address target) { + } + + /** + * The emulator has encountered a userop for which it has no definition. + * + *

    + * Emulation has not yet been interrupted at this point. If a callback returns true, indicating + * the fault has been handled, then the emulator will proceed. If not, then emulation for this + * thread will be interrupted + * + * @param thread the thread + * @param op the {@link PcodeOp#CALLOTHER} op + * @param frame the frame for the current p-code program + * @param opName the name of the userop being called + * @param library the thread's userop library + * @return true if handled, false if not + */ + default boolean handleMissingUserop(PcodeThread thread, PcodeOp op, PcodeFrame frame, + String opName, PcodeUseropLibrary library) { + return false; + } + + /** + * Data was written into the given state piece (abstract addressing). + * + *

    + * NOTE: In contrast to the operation-driven callbacks, e.g., + * {@link #beforeStore(PcodeThread, PcodeOp, AddressSpace, Object, int, Object)}, the + * {@code thread} parameter here may be null. It is not necessarily the thread executing the op, + * but the thread associated to the state being accessed. In particular, when this is the + * shared state, {@code thread} will be null. When this is the local state, + * {@code thread} will be the thread of execution. If this behavior poses a serious limitation, + * then we may consider changing this to always be the thread of execution. + * + * @param the piece's address domain + * @param the piece's value domain + * @param thread the thread associated to the piece + * @param piece the state piece + * @param space the address space of the operand + * @param offset the offset of the operand + * @param length the size of the operand + * @param value the value written + */ + default void dataWritten(PcodeThread thread, PcodeExecutorStatePiece piece, + AddressSpace space, A offset, int length, U value) { + } + + /** + * Typically used from within + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} + * to forward the call to the callback for concrete addressing + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, Address, int, Object)}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param thread the thread associated to the piece. See + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} + * @param piece the state piece + * @param space the address space of the operand + * @param offset the offset of the operand + * @param length the size of the operand + * @param value the value written + */ + default void delegateDataWritten(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, A offset, int length, + U value) { + dataWritten(thread, piece, + piece.getAddressArithmetic().toAddress(offset, space, Purpose.STORE), length, value); + } + + /** + * Data was written into the given state piece (concrete addressing). + * + * @param the piece's address domain + * @param the piece's value domain + * @param thread the thread associated to the piece. See + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} + * @param piece the state piece + * @param address the address of the operand + * @param length the size of the operand + * @param value the value written + */ + default void dataWritten(PcodeThread thread, PcodeExecutorStatePiece piece, + Address address, int length, U value) { + } + + /** + * Typically used from within + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, Address, int, Object)} to forward + * the call to the callback for abstract addressing + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param thread the thread associated to the piece. See + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} + * @param piece the state piece + * @param address the address of the operand + * @param length the size of the operand + * @param value the value written + */ + default void delegateDataWritten(PcodeThread thread, + PcodeExecutorStatePiece piece, Address address, int length, U value) { + dataWritten(thread, piece, address.getAddressSpace(), + piece.getAddressArithmetic().fromConst(address), length, value); + } + + /** + * The emulator is preparing to read from uninitialized portions of the given state piece + * (abstract addressing). + * + *

    + * This callback provides an opportunity for something to initialize the required portion + * lazily. In most cases, this should either return 0 indicating the requested portion remains + * uninitialized, or the full {@code length} indicating the full requested portion is now + * initialized. If, for some reason, the requested portion could only be partially initialized, + * this can return a smaller length. Partial initializations are only recognized from the + * starting offset. Other parts could be initialized; however, there is no mechanism for + * communicating that result to the emulator. + * + * @param the piece's address domain + * @param the piece's value domain + * @param thread the thread associated to the piece. See + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} + * @param piece the state piece + * @param space the address space of the operand + * @param offset the offset of the operand + * @param length the size of the operand + * @return the length of the operand just initialized, typically 0 or {@code length} + */ + default int readUninitialized(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, A offset, int length) { + return 0; + } + + /** + * Typically used from within + * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int)} + * to forward to the callback for concrete addressing + * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param thread the thread associated to the piece. See + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} + * @param piece the state piece + * @param space the address space of the operand + * @param offset the offset of the operand + * @param length the size of the operand + * @return the length of the operand just initialized, typically 0 or {@code length} + */ + default int delegateReadUninitialized(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSpace space, A offset, int length) { + long lOffset = piece.getAddressArithmetic().toLong(offset, Purpose.LOAD); + AddressSet set = PcodeStateCallbacks.rngSet(space, lOffset, length); + AddressSetView remains = readUninitialized(thread, piece, set); + if (set == remains) { + return 0; + } + set.delete(remains); + AddressRange first = set.getFirstRange(); + return first == null ? 0 : (int) first.getLength(); + } + + /** + * The emulator is preparing to read from uninitialized portions of the given state piece + * (concrete addressing). + * + *

    + * This callback provides an opportunity for something to initialize the required portion + * lazily. This method must return the address set that remains uninitialized. If no part of the + * required portion was initialized, this should return {@code set} identically, so that the + * caller can quickly recognize that nothing has changed. Otherwise, this should copy + * {@code set}, remove those parts it was able to initialize, and return the copy. DO NOT + * modify the given {@code set}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param thread the thread associated to the piece. See + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} + * @param piece the state piece + * @param set the uninitialized portion required + * @return the addresses in {@code set} that remain uninitialized + */ + default AddressSetView readUninitialized(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView set) { + return set; + } + + /** + * Typically used from within + * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)} to forward + * to the callback for abstract addressing + * {@link #readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int)}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param thread the thread associated to the piece. See + * {@link #dataWritten(PcodeThread, PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} + * @param piece the state piece + * @param set the uninitialized portion required + * @return the addresses in {@code set} that remain uninitialized + */ + default AddressSetView delegateReadUninitialized(PcodeThread thread, + PcodeExecutorStatePiece piece, AddressSetView set) { + if (set.isEmpty()) { + return set; + } + boolean result = false; + AddressSet remains = new AddressSet(set); + for (AddressRange range : set) { + int l = readUninitialized(thread, piece, range.getAddressSpace(), + piece.getAddressArithmetic().fromConst(range.getMinAddress()), + (int) range.getLength()); + if (l == 0) { + continue; + } + remains.delete(range.getMinAddress(), range.getMinAddress().add(l - 1)); + result = true; + } + return result ? remains : set; + } + + /** + * A wrapper that can forward callbacks from state pieces to callbacks for the emulator, for a + * given thread. + * + * @param the emulator's domain + * @param thread the thread to include in forwarded callbacks + * @param cb the emulator callbacks to receive forwarded calls + */ + record Wrapper(PcodeThread thread, PcodeEmulationCallbacks cb) + implements PcodeStateCallbacks { + @Override + public void dataWritten(PcodeExecutorStatePiece piece, Address address, + int length, U value) { + cb.dataWritten(thread, piece, address, length, value); + } + + @Override + public void dataWritten(PcodeExecutorStatePiece piece, AddressSpace space, + A offset, int length, U value) { + cb.dataWritten(thread, piece, space, offset, length, value); + } + + @Override + public int readUninitialized(PcodeExecutorStatePiece piece, + AddressSpace space, A offset, int length) { + return cb.readUninitialized(thread, piece, space, offset, length); + } + + @Override + public AddressSetView readUninitialized(PcodeExecutorStatePiece piece, + AddressSetView set) { + return cb.readUninitialized(thread, piece, set); + } + } + + /** + * Obtain a callback wrapper suitable for passing into an emulator's execution states + * + *

    + * This will forward the calls from the state's pieces to this set of emulator callbacks, + * passing the given thread + * + * @param thread the thread to include in forwarded callbacks + * @return the wrapper + */ + default PcodeStateCallbacks wrapFor(PcodeThread thread) { + return new Wrapper<>(thread, this); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulator.java index f6ec12dc5a..2df5e19b9c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulator.java @@ -4,9 +4,9 @@ * 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. @@ -38,6 +38,19 @@ import ghidra.program.model.lang.Language; * straightforward to extend the emulator via composition. Consider using {@link AuxPcodeEmulator} * or one of its derivatives to create a concrete-plus-auxiliary style emulator. * + *

    + * Note that extending the emulator and integrating with emulation use two different patterns. + * Extending the emulator generally means advancing its core behavior in some way, e.g., replacing + * its execution engine or adapting it to a new domain. For such cases, extend this class or + * {@link AbstractPcodeMachine} or similar. Integrating with an emulator means reacting to events, + * tweaking initialization, serializing state, etc. For such cases, implement + * {@link PcodeEmulationCallbacks} and pass it into the emulator's constructor. In general, favor + * the callbacks mechanism, as this is more easily composed with other integrations. By their + * nature, and due to restrictions on inheritance in Java, different extensions cannot be easily + * composed. Though encouraged, it is possible an extended emulator does not accept/issue callbacks. + * Read its documentation to learn which, if any, of the callbacks are actually supported. The base + * {@link PcodeEmulator} supports all the callbacks in {@link PcodeEmulationCallbacks}. + * *

      * emulator      : {@link PcodeMachine}{@code }
      *  - language     : {@link SleighLanguage}
    @@ -86,9 +99,9 @@ import ghidra.program.model.lang.Language;
      * 

    * This concrete emulator chooses a {@link BytesPcodeArithmetic} based on the endianness of the * target language. Its threads are {@link BytesPcodeThread}. The shared and thread-local states are - * all {@link BytesPcodeExecutorState}. That pieces of that state can be extended to read through to - * some other backing object. For example, the memory state could read through to an imported - * program image, which allows the emulator's memory to be loaded lazily. + * all {@link BytesPcodeExecutorState}. Callbacks may be provided to read through to some other + * backing object. For example, the memory state could read through to an imported program image, + * which allows the emulator's memory to be loaded lazily. * *

    * The default userop library is empty. For many use cases, it will be necessary to override @@ -104,9 +117,22 @@ import ghidra.program.model.lang.Language; * by injecting p-code at the import address. See {@link PcodeMachine#inject(Address, String)}. The * inject will need to replicate the semantics of that call to the desired fidelity. * IMPORTANT: The inject must also return control to the calling function, usually by - * replicating the conventions of the target platform. + * replicating the conventions of the target platform. See the Debugger course for more information. */ public class PcodeEmulator extends AbstractPcodeMachine { + /** + * Construct a new concrete emulator + * + *

    + * Yes, it is customary to invoke this constructor directly. + * + * @param language the language of the target processor + * @param cb callbacks to receive emulation events + */ + public PcodeEmulator(Language language, PcodeEmulationCallbacks cb) { + super(language, cb); + } + /** * Construct a new concrete emulator * @@ -116,7 +142,7 @@ public class PcodeEmulator extends AbstractPcodeMachine { * @param language the language of the target processor */ public PcodeEmulator(Language language) { - super(language); + this(language, PcodeEmulationCallbacks.none()); } @Override @@ -131,12 +157,14 @@ public class PcodeEmulator extends AbstractPcodeMachine { @Override protected PcodeExecutorState createSharedState() { - return new BytesPcodeExecutorState(language); + PcodeStateCallbacks scb = cb.wrapFor(null); + return new BytesPcodeExecutorState(language, scb); } @Override protected PcodeExecutorState createLocalState(PcodeThread thread) { - return new BytesPcodeExecutorState(language); + PcodeStateCallbacks scb = cb.wrapFor(thread); + return new BytesPcodeExecutorState(language, scb); } @Override 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 3d22ca833b..d2f575b7b1 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 @@ -16,10 +16,10 @@ package ghidra.pcode.emu; import java.util.*; +import java.util.stream.Stream; -import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; -import ghidra.pcode.exec.PcodeExecutorState; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; @@ -63,9 +63,19 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { return arithmetic; } + /** + * {@inheritDoc} + *

    + * This will only include the pieces in the thread's local state. + */ @Override - public ThreadPcodeExecutorState fork() { - return new ThreadPcodeExecutorState<>(sharedState.fork(), localState.fork()); + public Stream> streamPieces() { + return localState.streamPieces(); + } + + @Override + public ThreadPcodeExecutorState fork(PcodeStateCallbacks cb) { + return new ThreadPcodeExecutorState<>(sharedState.fork(cb), localState.fork(cb)); } /** @@ -87,6 +97,14 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { sharedState.setVar(space, offset, size, quantize, val); } + @Override + public void setVarInternal(AddressSpace space, T offset, int size, T val) { + if (isThreadLocalSpace(space)) { + localState.setVarInternal(space, offset, size, val); + } + sharedState.setVarInternal(space, offset, size, val); + } + @Override public void setVar(AddressSpace space, long offset, int size, boolean quantize, T val) { if (isThreadLocalSpace(space)) { @@ -96,6 +114,14 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { sharedState.setVar(space, offset, size, quantize, val); } + @Override + public void setVarInternal(AddressSpace space, long offset, int size, T val) { + if (isThreadLocalSpace(space)) { + localState.setVarInternal(space, offset, size, val); + } + sharedState.setVarInternal(space, offset, size, val); + } + @Override public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) { if (isThreadLocalSpace(space)) { @@ -104,6 +130,14 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { return sharedState.getVar(space, offset, size, quantize, reason); } + @Override + public T getVarInternal(AddressSpace space, T offset, int size, Reason reason) { + if (isThreadLocalSpace(space)) { + return localState.getVarInternal(space, offset, size, reason); + } + return sharedState.getVarInternal(space, offset, size, reason); + } + @Override public T getVar(AddressSpace space, long offset, int size, boolean quantize, Reason reason) { if (isThreadLocalSpace(space)) { @@ -112,6 +146,14 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { return sharedState.getVar(space, offset, size, quantize, reason); } + @Override + public T getVarInternal(AddressSpace space, long offset, int size, Reason reason) { + if (isThreadLocalSpace(space)) { + return localState.getVarInternal(space, offset, size, reason); + } + return sharedState.getVarInternal(space, offset, size, reason); + } + @Override public Map getRegisterValues() { Map result = new HashMap<>(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java index 199df80b2c..043fe6c869 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java @@ -4,9 +4,9 @@ * 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. @@ -23,16 +23,13 @@ import ghidra.pcode.exec.*; import ghidra.program.model.lang.Language; /** - * An auxiliary emulator parts factory for stand-alone emulation + * An auxiliary emulator parts factory * *

    - * This can manufacture all the parts needed for a stand-alone emulator with concrete and some + * This can manufacture all the parts needed for an emulator with concrete and some * implementation-defined auxiliary state. More capable emulators may also use many of these parts. * Usually, the additional capabilities deal with how state is loaded and stored or otherwise made - * available to the user. The pattern of use for a stand-alone emulator is usually in a script: - * Create an emulator, initialize its state, write instructions to its memory, create and initialize - * a thread, point its counter at the instructions, instrument, step/run, inspect, and finally - * terminate. + * available to the user. * *

    * This "parts factory" pattern aims to flatten the extension points of the @@ -49,7 +46,7 @@ import ghidra.program.model.lang.Language; */ public interface AuxEmulatorPartsFactory { /** - * Get the arithmetic for the emulator given a target langauge + * Get the arithmetic for the emulator given a target language * * @param language the language * @return the arithmetic @@ -111,7 +108,7 @@ public interface AuxEmulatorPartsFactory { } /** - * Create the shared (memory) state of a new stand-alone emulator + * Create the shared (memory) state of a new emulator * *

    * This is usually composed of pieces using {@link PairedPcodeExecutorStatePiece}, but it does @@ -120,13 +117,14 @@ public interface AuxEmulatorPartsFactory { * * @param emulator the emulator * @param concrete the concrete piece + * @param cb callbacks to receive emulation events * @return the composed state */ PcodeExecutorState> createSharedState(AuxPcodeEmulator emulator, - BytesPcodeExecutorStatePiece concrete); + BytesPcodeExecutorStatePiece concrete, PcodeStateCallbacks cb); /** - * Create the local (register) state of a new stand-alone emulator + * Create the local (register) state of a new emulator * *

    * This is usually composed of pieces using {@link PairedPcodeExecutorStatePiece}, but it does @@ -136,8 +134,10 @@ public interface AuxEmulatorPartsFactory { * @param emulator the emulator * @param thread the thread * @param concrete the concrete piece + * @param cb callbacks to receive emulation events * @return the composed state */ PcodeExecutorState> createLocalState(AuxPcodeEmulator emulator, - PcodeThread> thread, BytesPcodeExecutorStatePiece concrete); + PcodeThread> thread, BytesPcodeExecutorStatePiece concrete, + PcodeStateCallbacks cb); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java index 07f31b67a4..86e76ba505 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java @@ -4,9 +4,9 @@ * 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. @@ -17,17 +17,16 @@ package ghidra.pcode.emu.auxiliary; import org.apache.commons.lang3.tuple.Pair; -import ghidra.pcode.emu.AbstractPcodeMachine; -import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.*; import ghidra.pcode.exec.*; import ghidra.program.model.lang.Language; /** - * A stand-alone emulator whose parts are manufactured by a {@link AuxEmulatorPartsFactory} + * An emulator whose parts are manufactured by a {@link AuxEmulatorPartsFactory} * *

    * See the parts factory interface: {@link AuxEmulatorPartsFactory}. Also see the Taint Analyzer for - * a complete solution based on this class. + * a complete example based on this class. * * @param the type of auxiliary values */ @@ -36,9 +35,10 @@ public abstract class AuxPcodeEmulator extends AbstractPcodeMachine> cb) { + super(language, cb); } /** @@ -72,15 +72,17 @@ public abstract class AuxPcodeEmulator extends AbstractPcodeMachine> createSharedState() { + PcodeStateCallbacks scb = cb.wrapFor(null); return getPartsFactory().createSharedState(this, - new BytesPcodeExecutorStatePiece(language)); + new BytesPcodeExecutorStatePiece(language, scb), scb); } @Override protected PcodeExecutorState> createLocalState( PcodeThread> thread) { + PcodeStateCallbacks scb = cb.wrapFor(thread); return getPartsFactory().createLocalState(this, thread, - new BytesPcodeExecutorStatePiece(language)); + new BytesPcodeExecutorStatePiece(language, scb), scb); } @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitBytesPcodeExecutorStatePiece.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitBytesPcodeExecutorStatePiece.java index 5ba5ebadaf..e1aaa9b8ba 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitBytesPcodeExecutorStatePiece.java @@ -16,8 +16,7 @@ package ghidra.pcode.emu.jit; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorStatePiece.JitBytesPcodeExecutorStateSpace; -import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece; -import ghidra.pcode.exec.BytesPcodeExecutorStateSpace; +import ghidra.pcode.exec.*; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; @@ -36,18 +35,18 @@ public class JitBytesPcodeExecutorStatePiece /** * An object to manage state for a specific {@link AddressSpace} */ - public class JitBytesPcodeExecutorStateSpace extends BytesPcodeExecutorStateSpace { + public class JitBytesPcodeExecutorStateSpace extends BytesPcodeExecutorStateSpace { /** * Construct a state space * * @param language the emulation target language * @param space the address space - * @param backing any extra read-through backing (not used) + * @param piece the owning piece */ public JitBytesPcodeExecutorStateSpace(Language language, AddressSpace space, - Void backing) { - super(language, space, backing); + AbstractBytesPcodeExecutorStatePiece piece) { + super(language, space, piece); } /** @@ -83,18 +82,20 @@ public class JitBytesPcodeExecutorStatePiece * @return the value of the variable as a byte array */ public byte[] read(long offset, int size) { - return read(offset, size, Reason.EXECUTE_READ); + return read(offset, size, Reason.EXECUTE_READ, cb); } - } - /** - * A state space map that creates a {@link JitBytesPcodeExecutorStateSpace} for each needed - * {@link AddressSpace} - */ - class JitBytesSpaceMap extends SimpleSpaceMap { - @Override - protected JitBytesPcodeExecutorStateSpace newSpace(AddressSpace space) { - return new JitBytesPcodeExecutorStateSpace(language, space, null); + /** + * Write a variable to this (pre-fetched) state space + * + * @see #read(long, int) + * @param offset the offset + * @param val the value + * @param srcOffset offset within val to start + * @param length the number of bytes to write + */ + public void write(long offset, byte[] val, int srcOffset, int length) { + write(offset, val, srcOffset, length, cb); } } @@ -102,14 +103,16 @@ public class JitBytesPcodeExecutorStatePiece * Construct a state piece * * @param language the emulation target language + * @param cb callbacks to receive emulation events. Note that many accesses by the JIT are + * direct and so will not generate a callback. DO NOT rely on state callbacks yet. */ - public JitBytesPcodeExecutorStatePiece(Language language) { - super(language); + public JitBytesPcodeExecutorStatePiece(Language language, PcodeStateCallbacks cb) { + super(language, cb); } @Override - protected AbstractSpaceMap newSpaceMap() { - return new JitBytesSpaceMap(); + protected JitBytesPcodeExecutorStateSpace newSpace(AddressSpace space) { + return new JitBytesPcodeExecutorStateSpace(language, space, this); } @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitDefaultBytesPcodeExecutorState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitDefaultBytesPcodeExecutorState.java index f791ff73bb..d824085528 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitDefaultBytesPcodeExecutorState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitDefaultBytesPcodeExecutorState.java @@ -17,8 +17,8 @@ package ghidra.pcode.emu.jit; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorStatePiece.JitBytesPcodeExecutorStateSpace; import ghidra.pcode.emu.jit.analysis.JitDataFlowState; -import ghidra.pcode.exec.BytesPcodeArithmetic; import ghidra.pcode.exec.DefaultPcodeExecutorState; +import ghidra.pcode.exec.PcodeStateCallbacks; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; @@ -44,10 +44,11 @@ public class JitDefaultBytesPcodeExecutorState extends DefaultPcodeExecutorState * Construct a new state for the given language * * @param language the emulation target language + * @param cb callbacks to receive emulation events. Because the JIT often accesses state + * directly, it will bypass several callbacks. DO NOT rely on callbacks, yet. */ - public JitDefaultBytesPcodeExecutorState(Language language) { - super(new JitBytesPcodeExecutorStatePiece(language), - BytesPcodeArithmetic.forLanguage(language)); + public JitDefaultBytesPcodeExecutorState(Language language, PcodeStateCallbacks cb) { + super(new JitBytesPcodeExecutorStatePiece(language, cb)); } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeEmulator.java index a82c84bf86..59a1ab9757 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeEmulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeEmulator.java @@ -25,14 +25,14 @@ import java.util.concurrent.ExecutionException; import org.apache.commons.lang3.exception.ExceptionUtils; import org.objectweb.asm.MethodTooLargeException; -import ghidra.pcode.emu.PcodeEmulator; -import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.*; +import ghidra.pcode.emu.PcodeMachine.AccessKind; import ghidra.pcode.emu.jit.JitPassage.AddrCtx; import ghidra.pcode.emu.jit.analysis.JitDataFlowModel; import ghidra.pcode.emu.jit.analysis.JitDataFlowUseropLibrary; import ghidra.pcode.emu.jit.decode.JitPassageDecoder; -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPointPrototype; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassageClass; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPointPrototype; import ghidra.pcode.emu.jit.var.JitVal; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; @@ -48,8 +48,8 @@ import ghidra.util.Msg; * *

    * This is meant as a near drop-in replacement for the class it extends. Aside from some additional - * configuration, and some annotations you might add to a {@link PcodeUseropLibrary}, if applicable, - * you can simply replace {@code new PcodeEmulator()} with {@code new JitPcodeEmulator(...)}. + * configuration, and some annotations you might add to a {@link PcodeUseropLibrary}, you can simply + * replace {@code new PcodeEmulator()} with {@code new JitPcodeEmulator(...)}. * *

    A JIT-Accelerated P-code Emulator for the Java Virtual Machine

    * @@ -118,7 +118,7 @@ import ghidra.util.Msg; *
  • Translation target: The target of the JIT translation, usually the emulation * host. For our purposes, this is always JVM bytecode.
  • * - *
  • Varnode: The triple (space,offset,size) giving the address and size of a variable in + *
  • Varnode: The triple (space, offset, size) giving the address and size of a variable in * the emulation target's machine state. This is distinct from a variable node (see {@link JitVal}) * in the {@link JitDataFlowModel use-def} graph. The name "{@link Varnode}" is an unfortunate * inheritance from the Ghidra API, where they can represent genuine variable nodes in the @@ -179,25 +179,44 @@ public class JitPcodeEmulator extends PcodeEmulator { /** * Create a JIT-accelerated p-code emulator * - * @param language the emulation target langauge + * @param language the emulation target language + * @param cb callbacks to receive emulation events. WARNING. Callbacks are not completely + * implemented, and so are not recommended, yet. For that reason, this constructor is + * made private until the caveats are completely documented and/or some alternatives + * made available. + * @param config configuration options for this emulator + * @param lookup a lookup in case the emulator (or its target) needs access to non-public + * elements, e.g., to access a nested {@link PcodeUseropLibrary}. + */ + private JitPcodeEmulator(Language language, PcodeEmulationCallbacks cb, + JitConfiguration config, Lookup lookup) { + super(language, cb); + this.compiler = new JitCompiler(config); + this.lookup = lookup; + } + + /** + * Create a JIT-accelerated p-code emulator + * + * @param language the emulation target language * @param config configuration options for this emulator * @param lookup a lookup in case the emulator (or its target) needs access to non-public * elements, e.g., to access a nested {@link PcodeUseropLibrary}. */ public JitPcodeEmulator(Language language, JitConfiguration config, Lookup lookup) { - super(language); - this.compiler = new JitCompiler(config); - this.lookup = lookup; + this(language, PcodeEmulationCallbacks.none(), config, lookup); } @Override protected PcodeExecutorState createSharedState() { - return new JitDefaultBytesPcodeExecutorState(language); + PcodeStateCallbacks scb = cb.wrapFor(null); + return new JitDefaultBytesPcodeExecutorState(language, scb); } @Override protected PcodeExecutorState createLocalState(PcodeThread thread) { - return new JitDefaultBytesPcodeExecutorState(language); + PcodeStateCallbacks scb = cb.wrapFor(thread); + return new JitDefaultBytesPcodeExecutorState(language, scb); } @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java index 2c576a4d75..ab8814bd11 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java @@ -69,6 +69,11 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { this.endian = context.getEndian(); } + @Override + public Class getDomain() { + return JitVal.class; + } + @Override public Endian getEndian() { return endian; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java index df3ebe2a44..a69de6514a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java @@ -17,13 +17,14 @@ package ghidra.pcode.emu.jit.analysis; import java.util.*; import java.util.Map.Entry; +import java.util.stream.Stream; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.op.*; import ghidra.pcode.emu.jit.var.*; +import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; -import ghidra.pcode.exec.PcodeExecutorState; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; @@ -399,6 +400,11 @@ public class JitDataFlowState implements PcodeExecutorState { return arithmetic; } + @Override + public Stream> streamPieces() { + return Stream.of(this); + } + /** * {@inheritDoc} * @@ -444,6 +450,11 @@ public class JitDataFlowState implements PcodeExecutorState { mini.set(varnode, val); } + @Override + public void setVarInternal(AddressSpace space, JitVal offset, int size, JitVal val) { + setVar(space, offset, size, false, val); + } + /** * Get an ordered list of all values involved in the latest definition of the given varnode. * @@ -518,6 +529,11 @@ public class JitDataFlowState implements PcodeExecutorState { return mini.getVar(varnode); } + @Override + public JitVal getVarInternal(AddressSpace space, JitVal offset, int size, Reason reason) { + return getVar(space, offset, size, false, reason); + } + @Override public Map getRegisterValues() { throw new UnsupportedOperationException(); @@ -534,7 +550,7 @@ public class JitDataFlowState implements PcodeExecutorState { } @Override - public PcodeExecutorState fork() { + public PcodeExecutorState fork(PcodeStateCallbacks cb) { throw new UnsupportedOperationException(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java index 32ba5a4acd..07799ae5fb 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java @@ -16,24 +16,20 @@ package ghidra.pcode.exec; import java.nio.ByteBuffer; -import java.util.List; -import java.util.Map; +import java.util.*; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; import ghidra.program.model.mem.*; -import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; -import ghidra.util.Msg; /** * An abstract p-code executor state piece for storing and retrieving bytes as arrays * * @param the type of an executor state space, internally associated with an address space */ -public abstract class AbstractBytesPcodeExecutorStatePiece> +public abstract class AbstractBytesPcodeExecutorStatePiece extends AbstractLongOffsetPcodeExecutorStatePiece { /** @@ -41,19 +37,18 @@ public abstract class AbstractBytesPcodeExecutorStatePiece source; + protected BytesPcodeExecutorStateSpace source; protected final Reason reason; /** * Construct a buffer bound to the given space, at the given address * * @param address the address - * @param source the space + * @param source the space (null will cause readUninit and re-fetch on read attempts) * @param reason the reason this buffer reads from the state, as in * {@link PcodeExecutorStatePiece#getVar(Varnode, Reason)} */ - public StateMemBuffer(Address address, BytesPcodeExecutorStateSpace source, - Reason reason) { + public StateMemBuffer(Address address, BytesPcodeExecutorStateSpace source, Reason reason) { this.address = address; this.source = source; this.reason = reason; @@ -76,82 +71,68 @@ public abstract class AbstractBytesPcodeExecutorStatePiece spaceMap; - - /** - * Construct a state for the given language - * - * @param language the language, used for its memory model and arithmetic - */ - public AbstractBytesPcodeExecutorStatePiece(Language language) { - this(language, BytesPcodeArithmetic.forLanguage(language)); - } - - protected AbstractBytesPcodeExecutorStatePiece(Language language, - AbstractSpaceMap spaceMap) { - this(language, BytesPcodeArithmetic.forLanguage(language), spaceMap); - } + protected final Map spaceMap = new HashMap<>(); /** * Construct a state for the given language * * @param language the language, used for its memory model * @param arithmetic the arithmetic + * @param cb callbacks to receive emulation events */ public AbstractBytesPcodeExecutorStatePiece(Language language, - PcodeArithmetic arithmetic) { - super(language, arithmetic, arithmetic); - spaceMap = newSpaceMap(); - } - - protected AbstractBytesPcodeExecutorStatePiece(Language language, - PcodeArithmetic arithmetic, AbstractSpaceMap spaceMap) { - super(language, arithmetic, arithmetic); - this.spaceMap = spaceMap; + PcodeArithmetic arithmetic, PcodeStateCallbacks cb) { + super(language, arithmetic, arithmetic, cb); } /** - * A factory method for this state's space map. + * Construct a state for the given language * - *

    - * Because most of the special logic for extensions is placed in the "state space," i.e., an - * object assigned to a particular address space in the state's language, this factory method - * must provide the map to create and maintain those spaces. That map will in turn be the - * factory of the spaces themselves, allowing extensions to provide additional read/write logic. - * - * @return the new space map + * @param language the language, used for its memory model and arithmetic + * @param cb callbacks to receive emulation events */ - protected abstract AbstractSpaceMap newSpaceMap(); + public AbstractBytesPcodeExecutorStatePiece(Language language, PcodeStateCallbacks cb) { + this(language, BytesPcodeArithmetic.forLanguage(language), cb); + } + + protected abstract S newSpace(AddressSpace space); @Override protected S getForSpace(AddressSpace space, boolean toWrite) { - return spaceMap.getForSpace(space, toWrite); + if (toWrite) { + return spaceMap.computeIfAbsent(space, this::newSpace); + } + return spaceMap.get(space); } @Override - protected void setInSpace(S space, long offset, int size, byte[] val) { - if (val.length > size) { - throw new IllegalArgumentException( - "Value is larger than variable: " + val.length + " > " + size); - } - if (val.length < size) { - Msg.warn(this, "Value is smaller than variable: " + val.length + " < " + size + - ". Zero extending"); - val = arithmetic.unaryOp(PcodeOp.INT_ZEXT, size, val.length, val); - } - space.write(offset, val, 0, size); + protected void setInSpace(S space, long offset, int size, byte[] val, PcodeStateCallbacks cb) { + space.write(offset, val, 0, size, cb); } @Override - protected byte[] getFromSpace(S space, long offset, int size, Reason reason) { - byte[] read = space.read(offset, size, reason); + protected byte[] getFromSpace(S space, long offset, int size, Reason reason, + PcodeStateCallbacks cb) { + byte[] read = space.read(offset, size, reason, cb); if (read.length != size) { throw new AccessPcodeExecutionException("Incomplete read (" + read.length + " of " + size + " bytes)"); 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 2cc8c5007d..661e6ebd01 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 @@ -17,12 +17,17 @@ package ghidra.pcode.exec; import java.util.*; import java.util.Map.Entry; +import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.util.Msg; /** * An abstract executor state piece which internally uses {@code long} to address contents @@ -38,150 +43,36 @@ import ghidra.program.model.lang.Register; public abstract class AbstractLongOffsetPcodeExecutorStatePiece implements PcodeExecutorStatePiece { - /** - * A map of address spaces to objects which store or cache state for that space - * - * @param the type of object for each address space - */ - public abstract static class AbstractSpaceMap { - protected final Map spaces; - - public AbstractSpaceMap() { - this.spaces = new HashMap<>(); - } - - protected AbstractSpaceMap(Map spaces) { - this.spaces = spaces; - } - - public abstract S getForSpace(AddressSpace space, boolean toWrite); - - public Collection values() { - return spaces.values(); - } - - /** - * Deep copy this map, for use in a forked state (or piece) - * - * @return the copy - */ - public AbstractSpaceMap fork() { - throw new UnsupportedOperationException(); - } - - /** - * Deep copy the given space - * - * @param s the space - * @return the copy - */ - public S fork(S s) { - throw new UnsupportedOperationException(); - } - - /** - * Produce a deep copy of the given map - * - * @param spaces the map to copy - * @return the copy - */ - public Map fork(Map spaces) { - return spaces.entrySet() - .stream() - .collect(Collectors.toMap(Entry::getKey, e -> fork(e.getValue()))); - } - } - - /** - * Use this when each S contains the complete state for the address space - * - * @param the type of object for each address space - */ - public abstract static class SimpleSpaceMap extends AbstractSpaceMap { - public SimpleSpaceMap() { - super(); - } - - protected SimpleSpaceMap(Map spaces) { - super(spaces); - } - - /** - * Construct a new space internally associated with the given address space - * - *

    - * As the name implies, this often simply wraps {@code S}'s constructor - * - * @param space the address space - * @return the new space - */ - protected abstract S newSpace(AddressSpace space); - - @Override - public synchronized S getForSpace(AddressSpace space, boolean toWrite) { - return spaces.computeIfAbsent(space, s -> newSpace(s)); - } - } - - /** - * Use this when each S is possibly a cache to some other state (backing) object - * - * @param the type of the object backing the cache for each address space - * @param the type of cache for each address space - */ - public abstract static class CacheingSpaceMap extends AbstractSpaceMap { - public CacheingSpaceMap() { - super(); - } - - protected CacheingSpaceMap(Map spaces) { - super(spaces); - } - - /** - * Get the object backing the cache for the given address space - * - * @param space the space - * @return the backing object - */ - protected abstract B getBacking(AddressSpace space); - - /** - * Construct a new space internally associated with the given address space, having the - * given backing - * - *

    + * Extensions may override this and do nothing when the abstract type has no defined size + * + * @param size the size in bytes + * @param val the value + * @return the value, possibly adjusted + */ + protected T checkSize(int size, T val) { + int valSize = (int) arithmetic.sizeOf(val); + if (valSize > size) { + throw new IllegalArgumentException( + "Value is larger than variable: " + valSize + " > " + size); + } + if (valSize < size) { + Msg.warn(this, "Value is smaller than variable: " + valSize + " < " + size + + ". Zero extending"); + val = arithmetic.unaryOp(PcodeOp.INT_ZEXT, size, valSize, val); + } + return val; + } + @Override public void setVar(AddressSpace space, long offset, int size, boolean quantize, T val) { checkRange(space, offset, size); + val = checkSize(size, val); + setVarInternal(space, offset, size, quantize, val, cb); + } + + @Override + public void setVarInternal(AddressSpace space, long offset, int size, T val) { + setVarInternal(space, offset, size, false, val, PcodeStateCallbacks.NONE); + } + + protected T getVarInternal(AddressSpace space, long offset, int size, boolean quantize, + Reason reason, PcodeStateCallbacks cb) { if (space.isConstantSpace()) { - throw new IllegalArgumentException("Cannot write to constant space"); + return arithmetic.fromConst(offset, size); } if (space.isUniqueSpace()) { - setUnique(offset, size, val); - return; + return getUnique(offset, size, reason, cb); + } + S s = getForSpace(space, false); + if (s == null) { + AddressSet set = PcodeStateCallbacks.rngSet(space, offset, size); + if (set.equals(cb.readUninitialized(this, set))) { + return getFromNullSpace(size, reason, cb); + } + s = getForSpace(space, false); + if (s == null) { + return getFromNullSpace(size, reason, cb); + } } - S s = getForSpace(space, true); if (quantize) { offset = quantizeOffset(space, offset); } - setInSpace(s, offset, size, val); + return getFromSpace(s, offset, size, reason, cb); } @Override @@ -309,23 +277,20 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece } @Override - public T getVar(AddressSpace space, long offset, int size, boolean quantize, - Reason reason) { + public T getVarInternal(AddressSpace space, A offset, int size, Reason reason) { + long lOffset = addressArithmetic.toLong(offset, Purpose.LOAD); + return getVarInternal(space, lOffset, size, reason); + } + + @Override + public T getVar(AddressSpace space, long offset, int size, boolean quantize, Reason reason) { checkRange(space, offset, size); - if (space.isConstantSpace()) { - return arithmetic.fromConst(offset, size); - } - if (space.isUniqueSpace()) { - return getUnique(offset, size, reason); - } - S s = getForSpace(space, false); - if (s == null) { - return getFromNullSpace(size, reason); - } - if (quantize) { - offset = quantizeOffset(space, offset); - } - return getFromSpace(s, offset, size, reason); + return getVarInternal(space, offset, size, quantize, reason, cb); + } + + @Override + public T getVarInternal(AddressSpace space, long offset, int size, Reason reason) { + return getVarInternal(space, offset, size, false, reason, PcodeStateCallbacks.NONE); } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractPcodeExecutorState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractPcodeExecutorState.java new file mode 100644 index 0000000000..f56d61ef90 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractPcodeExecutorState.java @@ -0,0 +1,106 @@ +/* ### + * 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.exec; + +import java.util.Map; +import java.util.stream.Stream; + +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +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.mem.MemBuffer; + +public abstract class AbstractPcodeExecutorState implements PcodeExecutorState { + protected final PcodeExecutorStatePiece piece; + + public AbstractPcodeExecutorState(PcodeExecutorStatePiece piece) { + this.piece = piece; + } + + @Override + public Language getLanguage() { + return piece.getLanguage(); + } + + @Override + public PcodeArithmetic getArithmetic() { + return piece.getArithmetic(); + } + + @Override + public Stream> streamPieces() { + return piece.streamPieces(); + } + + protected abstract A extractAddress(T value); + + @Override + public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) { + return piece.getVar(space, extractAddress(offset), size, quantize, reason); + } + + @Override + public T getVarInternal(AddressSpace space, T offset, int size, Reason reason) { + return piece.getVarInternal(space, extractAddress(offset), size, 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 T getVarInternal(AddressSpace space, long offset, int size, Reason reason) { + return piece.getVarInternal(space, offset, size, reason); + } + + @Override + public void setVar(AddressSpace space, T offset, int size, boolean quantize, T val) { + piece.setVar(space, extractAddress(offset), size, quantize, val); + } + + @Override + public void setVarInternal(AddressSpace space, T offset, int size, T val) { + piece.setVarInternal(space, extractAddress(offset), size, val); + } + + @Override + public void setVar(AddressSpace space, long offset, int size, boolean quantize, T val) { + piece.setVar(space, offset, size, quantize, val); + } + + @Override + public void setVarInternal(AddressSpace space, long offset, int size, T val) { + piece.setVarInternal(space, offset, size, val); + } + + @Override + public Map getRegisterValues() { + return piece.getRegisterValues(); + } + + @Override + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { + return piece.getConcreteBuffer(address, purpose); + } + + @Override + public void clear() { + piece.clear(); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java index 316a46b2b5..f54f701a5e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AddressesReadPcodeArithmetic.java @@ -30,6 +30,11 @@ public enum AddressesReadPcodeArithmetic implements PcodeArithmetic getDomain() { + return AddressSetView.class; + } + @Override public Endian getEndian() { return null; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeArithmetic.java index 4806d47874..33fbd33e87 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeArithmetic.java @@ -4,9 +4,9 @@ * 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. @@ -40,6 +40,11 @@ public enum BytesPcodeArithmetic implements PcodeArithmetic { */ LITTLE_ENDIAN(Endian.LITTLE); + @Override + public Class getDomain() { + return byte[].class; + } + /** * Obtain the instance for the given endianness * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java index f4071d2d8f..e8f0dc90f7 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java @@ -4,9 +4,9 @@ * 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. @@ -25,10 +25,10 @@ public class BytesPcodeExecutorState extends DefaultPcodeExecutorState { * Create the state * * @param language the language (processor model) + * @param cb callbacks to receive emulation events */ - public BytesPcodeExecutorState(Language language) { - super(new BytesPcodeExecutorStatePiece(language), - BytesPcodeArithmetic.forLanguage(language)); + public BytesPcodeExecutorState(Language language, PcodeStateCallbacks cb) { + super(new BytesPcodeExecutorStatePiece(language, cb)); } protected BytesPcodeExecutorState(PcodeExecutorStatePiece piece) { @@ -36,7 +36,7 @@ public class BytesPcodeExecutorState extends DefaultPcodeExecutorState { } @Override - public BytesPcodeExecutorState fork() { - return new BytesPcodeExecutorState(piece.fork()); + public BytesPcodeExecutorState fork(PcodeStateCallbacks cb) { + return new BytesPcodeExecutorState(piece.fork(cb)); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java index 2b0a7fed75..db6984f6b0 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java @@ -4,9 +4,9 @@ * 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. @@ -15,8 +15,6 @@ */ package ghidra.pcode.exec; -import java.util.Map; - import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; @@ -24,54 +22,27 @@ import ghidra.program.model.lang.Language; * A plain concrete state piece without any backing objects */ public class BytesPcodeExecutorStatePiece - extends AbstractBytesPcodeExecutorStatePiece> { + extends AbstractBytesPcodeExecutorStatePiece { /** * Construct a state for the given language * * @param language the language (used for its memory model) + * @param cb callbacks to receive emulation events */ - public BytesPcodeExecutorStatePiece(Language language) { - super(language); - } - - protected BytesPcodeExecutorStatePiece(Language language, - AbstractSpaceMap> spaceMap) { - super(language, spaceMap); + public BytesPcodeExecutorStatePiece(Language language, PcodeStateCallbacks cb) { + super(language, cb); } @Override - public BytesPcodeExecutorStatePiece fork() { - return new BytesPcodeExecutorStatePiece(language, spaceMap.fork()); - } - - class BytesSpaceMap extends SimpleSpaceMap> { - BytesSpaceMap() { - super(); - } - - BytesSpaceMap(Map> spaces) { - super(spaces); - } - - @Override - protected BytesPcodeExecutorStateSpace newSpace(AddressSpace space) { - return new BytesPcodeExecutorStateSpace<>(language, space, null); - } - - @Override - public AbstractSpaceMap> fork() { - return new BytesSpaceMap(fork(spaces)); - } - - @Override - public BytesPcodeExecutorStateSpace fork(BytesPcodeExecutorStateSpace s) { - return s.fork(); - } + public BytesPcodeExecutorStatePiece fork(PcodeStateCallbacks cb) { + BytesPcodeExecutorStatePiece result = new BytesPcodeExecutorStatePiece(language, cb); + forkMap(result.spaceMap, this.spaceMap, s -> s.fork(result)); + return result; } @Override - protected AbstractSpaceMap> newSpaceMap() { - return new BytesSpaceMap(); + protected BytesPcodeExecutorStateSpace newSpace(AddressSpace space) { + return new BytesPcodeExecutorStateSpace(language, space, this); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java index 45f627b4fe..62d78c7d4b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java @@ -19,7 +19,9 @@ import java.util.*; import generic.ULongSpan; import generic.ULongSpan.*; +import ghidra.app.emulator.AdaptedEmulator; import ghidra.generic.util.datastruct.SemisparseByteArray; +import ghidra.pcode.emu.PcodeEmulationCallbacks; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; @@ -28,40 +30,37 @@ import ghidra.util.Msg; /** * A p-code executor state space for storing and retrieving bytes as arrays - * - * @param if this space is a cache, the type of object backing this space */ -public class BytesPcodeExecutorStateSpace { +public class BytesPcodeExecutorStateSpace { protected final static byte[] EMPTY = new byte[] {}; - protected final SemisparseByteArray bytes; + protected final Language language; // for logging diagnostics protected final AddressSpace space; - protected final B backing; + protected final AbstractBytesPcodeExecutorStatePiece piece; + protected final SemisparseByteArray bytes; /** * Construct an internal space for the given address space * * @param language the language, for logging diagnostics * @param space the address space - * @param backing the backing object, possibly {@code null} + * @param piece the owning piece */ - public BytesPcodeExecutorStateSpace(Language language, AddressSpace space, B backing) { - this.language = language; - this.space = space; - this.backing = backing; - this.bytes = new SemisparseByteArray(); + public BytesPcodeExecutorStateSpace(Language language, AddressSpace space, + AbstractBytesPcodeExecutorStatePiece piece) { + this(language, space, piece, new SemisparseByteArray()); } - protected BytesPcodeExecutorStateSpace(Language language, AddressSpace space, B backing, - SemisparseByteArray bytes) { + protected BytesPcodeExecutorStateSpace(Language language, AddressSpace space, + AbstractBytesPcodeExecutorStatePiece piece, SemisparseByteArray bytes) { this.language = language; this.space = space; - this.backing = backing; + this.piece = piece; this.bytes = bytes; } - public BytesPcodeExecutorStateSpace fork() { - return new BytesPcodeExecutorStateSpace<>(language, space, backing, bytes.fork()); + public BytesPcodeExecutorStateSpace fork(AbstractBytesPcodeExecutorStatePiece piece) { + return new BytesPcodeExecutorStateSpace(language, space, piece, bytes.fork()); } /** @@ -71,9 +70,11 @@ public class BytesPcodeExecutorStateSpace { * @param val the value * @param srcOffset offset within val to start * @param length the number of bytes to write + * @param cb callbacks to receive emulation events */ - public void write(long offset, byte[] val, int srcOffset, int length) { + public void write(long offset, byte[] val, int srcOffset, int length, PcodeStateCallbacks cb) { bytes.putData(offset, val, srcOffset, length); + cb.dataWritten(piece, space.getAddress(offset), length, val); } /** @@ -81,7 +82,14 @@ public class BytesPcodeExecutorStateSpace { * * @param uninitialized the ranges which need to be read. * @return the ranges which remain uninitialized + * @deprecated Please use the {@link PcodeEmulationCallbacks} and/or {@link PcodeStateCallbacks} + * instead + * @implNote This only remains because of {@link AdaptedEmulator}. Perhaps that should be + * refactored to use callbacks, too. That's only supposed to exist as an interim, + * though, so is it worth the effort? We could remove the entire {@code backing} + * concept if we did, though. */ + @Deprecated(forRemoval = true) protected ULongSpanSet readUninitializedFromBacking(ULongSpanSet uninitialized) { return uninitialized; } @@ -111,12 +119,15 @@ public class BytesPcodeExecutorStateSpace { range.getMaxAddress().getOffset()); } - protected AddressSet addrSet(ULongSpanSet set) { - AddressSet result = new AddressSet(); - for (ULongSpan span : set.spans()) { - result.add(addrRng(span)); + protected AddressSet addInPlace(AddressSet set, ULongSpanSet spanSet) { + for (ULongSpan span : spanSet.spans()) { + set.add(addrRng(span)); } - return result; + return set; + } + + protected AddressSet addrSet(ULongSpanSet set) { + return addInPlace(new AddressSet(), set); } /** @@ -157,8 +168,7 @@ public class BytesPcodeExecutorStateSpace { } } - protected void warnUninit(ULongSpanSet uninit) { - AddressSet uninitialized = addrSet(uninit); + protected void warnUninit(AddressSetView uninitialized) { warnAddressSet("Emulator read from uninitialized state", uninitialized); } @@ -169,16 +179,16 @@ public class BytesPcodeExecutorStateSpace { * @param size the number of bytes * @return the uninitialized offset ranges */ - protected ULongSpanSet computeUninitialized(long offset, int size) { + protected AddressSetView computeUninitialized(long offset, int size) { long max = offset + size - 1; if (Long.compareUnsigned(max, space.getMaxAddress().getOffset()) <= 0 && Long.compareUnsigned(offset, max) <= 0) { - return bytes.getUninitialized(offset, max); + return addrSet(bytes.getUninitialized(offset, max)); } long end = space.getMinAddress().getOffset() + max - space.getMaxAddress().getOffset() - 1; - MutableULongSpanSet result = new DefaultULongSpanSet(); - result.addAll(bytes.getUninitialized(offset, space.getMaxAddress().getOffset())); - result.addAll(bytes.getUninitialized(space.getMinAddress().getOffset(), end)); + AddressSet result = new AddressSet(); + addInPlace(result, bytes.getUninitialized(offset, space.getMaxAddress().getOffset())); + addInPlace(result, bytes.getUninitialized(space.getMinAddress().getOffset(), end)); return result; } @@ -193,18 +203,17 @@ public class BytesPcodeExecutorStateSpace { * @param offset the offset * @param size the number of bytes to read (the size of the value) * @param reason the reason for reading state + * @param cb callbacks to receive emulation events * @return the bytes read */ - public byte[] read(long offset, int size, Reason reason) { - ULongSpanSet uninitialized = computeUninitialized(offset, size); + public byte[] read(long offset, int size, Reason reason, PcodeStateCallbacks cb) { + AddressSetView uninitialized = computeUninitialized(offset, size); if (uninitialized.isEmpty()) { return readBytes(offset, size, reason); } - if (backing != null) { - uninitialized = readUninitializedFromBacking(uninitialized); - if (uninitialized.isEmpty()) { - return readBytes(offset, size, reason); - } + uninitialized = cb.readUninitialized(piece, uninitialized); + if (uninitialized.isEmpty()) { + return readBytes(offset, size, reason); } /** @@ -212,12 +221,12 @@ public class BytesPcodeExecutorStateSpace { * initialized. If it's a (non-decode) read, give it everything, but invoke the warning. */ if (reason == Reason.EXECUTE_DECODE) { - Iterator it = - uninitialized.complement(ULongSpan.extent(offset, size)).iterator(); - if (it.hasNext()) { - ULongSpan init = it.next(); - if (init.min().longValue() == offset) { - return readBytes(offset, (int) init.length(), reason); + Address min = space.getAddress(offset); + AddressSet init = new AddressSet(min, min.add(size - 1)); + init.delete(uninitialized); + if (!init.isEmpty()) { + if (init.getMinAddress().equals(min)) { + return readBytes(offset, (int) init.getFirstRange().getLength(), reason); } } } 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 b2563334b6..e5f54db6f7 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 @@ -15,81 +15,26 @@ */ package ghidra.pcode.exec; -import java.util.Map; - -import ghidra.pcode.exec.PcodeArithmetic.Purpose; -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.mem.MemBuffer; - /** * A p-code executor state formed from a piece whose address and value types are the same * * @param the type of values and addresses in the state */ -public class DefaultPcodeExecutorState implements PcodeExecutorState { - protected final PcodeExecutorStatePiece piece; +public class DefaultPcodeExecutorState extends AbstractPcodeExecutorState { protected final PcodeArithmetic arithmetic; - public DefaultPcodeExecutorState(PcodeExecutorStatePiece piece, - PcodeArithmetic arithmetic) { - this.piece = piece; - this.arithmetic = arithmetic; - } - public DefaultPcodeExecutorState(PcodeExecutorStatePiece piece) { - this(piece, piece.getArithmetic()); + super(piece); + this.arithmetic = piece.getArithmetic(); } @Override - public Language getLanguage() { - return piece.getLanguage(); + protected T extractAddress(T value) { + return value; } @Override - public PcodeArithmetic getArithmetic() { - return arithmetic; - } - - @Override - public DefaultPcodeExecutorState fork() { - return new DefaultPcodeExecutorState<>(piece.fork(), arithmetic); - } - - @Override - public T getVar(AddressSpace space, T offset, int size, boolean quantize, Reason reason) { - return piece.getVar(space, offset, size, quantize, reason); - } - - @Override - public T getVar(AddressSpace space, long offset, int size, boolean quantize, Reason reason) { - return piece.getVar(space, offset, size, quantize, reason); - } - - @Override - 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(); - } - - @Override - public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { - return piece.getConcreteBuffer(address, purpose); - } - - @Override - public void clear() { - piece.clear(); + public PcodeExecutorState fork(PcodeStateCallbacks cb) { + return new DefaultPcodeExecutorState<>(piece.fork(cb)); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/IndependentPairedPcodeExecutorState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/IndependentPairedPcodeExecutorState.java index 16a1d3439e..157d2d064d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/IndependentPairedPcodeExecutorState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/IndependentPairedPcodeExecutorState.java @@ -16,6 +16,7 @@ package ghidra.pcode.exec; import java.util.*; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; @@ -79,8 +80,13 @@ public class IndependentPairedPcodeExecutorState } @Override - public IndependentPairedPcodeExecutorState fork() { - return new IndependentPairedPcodeExecutorState<>(left.fork(), right.fork(), arithmetic); + public Stream> streamPieces() { + return Stream.of(left, right).flatMap(p -> p.streamPieces()); + } + + @Override + public IndependentPairedPcodeExecutorState fork(PcodeStateCallbacks cb) { + return new IndependentPairedPcodeExecutorState<>(left.fork(cb), right.fork(cb), arithmetic); } @Override @@ -104,6 +110,12 @@ public class IndependentPairedPcodeExecutorState right.setVar(space, offset.getRight(), size, quantize, val.getRight()); } + @Override + public void setVarInternal(AddressSpace space, Pair offset, int size, Pair val) { + left.setVarInternal(space, offset.getLeft(), size, val.getLeft()); + right.setVarInternal(space, offset.getRight(), size, val.getRight()); + } + @Override public Pair getVar(AddressSpace space, Pair offset, int size, boolean quantize, Reason reason) { @@ -112,6 +124,14 @@ public class IndependentPairedPcodeExecutorState right.getVar(space, offset.getRight(), size, quantize, reason)); } + @Override + public Pair getVarInternal(AddressSpace space, Pair offset, int size, + Reason reason) { + return Pair.of( + left.getVarInternal(space, offset.getLeft(), size, reason), + right.getVarInternal(space, offset.getRight(), size, reason)); + } + @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { return left.getConcreteBuffer(address, purpose); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/LocationPcodeArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/LocationPcodeArithmetic.java index f84049fda7..83a0113358 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/LocationPcodeArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/LocationPcodeArithmetic.java @@ -4,9 +4,9 @@ * 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. @@ -45,6 +45,11 @@ public enum LocationPcodeArithmetic implements PcodeArithmetic { this.endian = endian; } + @Override + public Class getDomain() { + return ValueLocation.class; + } + @Override public Endian getEndian() { return endian; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/LocationPcodeExecutorStatePiece.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/LocationPcodeExecutorStatePiece.java index 8ecc486eec..0ac0b8f038 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/LocationPcodeExecutorStatePiece.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/LocationPcodeExecutorStatePiece.java @@ -4,9 +4,9 @@ * 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. @@ -17,6 +17,7 @@ package ghidra.pcode.exec; import java.util.HashMap; import java.util.Map; +import java.util.stream.Stream; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; @@ -79,7 +80,12 @@ public class LocationPcodeExecutorStatePiece } @Override - public LocationPcodeExecutorStatePiece fork() { + public Stream> streamPieces() { + return Stream.of(this); + } + + @Override + public LocationPcodeExecutorStatePiece fork(PcodeStateCallbacks cb) { return new LocationPcodeExecutorStatePiece(language, addressArithmetic, new HashMap<>(unique)); } @@ -95,6 +101,11 @@ public class LocationPcodeExecutorStatePiece unique.put(lOffset, val); } + @Override + public void setVarInternal(AddressSpace space, byte[] offset, int size, ValueLocation val) { + setVar(space, offset, size, false, val); + } + @Override public ValueLocation getVar(AddressSpace space, byte[] offset, int size, boolean quantize, Reason reason) { @@ -105,6 +116,12 @@ public class LocationPcodeExecutorStatePiece return unique.get(lOffset); } + @Override + public ValueLocation getVarInternal(AddressSpace space, byte[] offset, int size, + Reason reason) { + return getVar(space, offset, size, false, reason); + } + @Override public Map getRegisterValues() { return Map.of(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java index 93b146be27..78cebff1ee 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java @@ -81,6 +81,12 @@ public class PairedPcodeArithmetic implements PcodeArithmetic> return true; } + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Class> getDomain() { + return (Class) Pair.class; + } + @Override public Endian getEndian() { return endian; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java index f0656f8d8a..c918ed1c62 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java @@ -16,6 +16,7 @@ package ghidra.pcode.exec; import java.util.Map; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; @@ -86,14 +87,19 @@ public class PairedPcodeExecutorState implements PcodeExecutorState> streamPieces() { + return piece.streamPieces(); + } + @Override public Map> getRegisterValues() { return piece.getRegisterValues(); } @Override - public PairedPcodeExecutorState fork() { - return new PairedPcodeExecutorState<>(piece.fork()); + public PairedPcodeExecutorState fork(PcodeStateCallbacks cb) { + return new PairedPcodeExecutorState<>(piece.fork(cb)); } @Override @@ -125,12 +131,45 @@ public class PairedPcodeExecutorState implements PcodeExecutorState offset, int size, Pair val) { + piece.setVarInternal(space, offset.getLeft(), size, val); + } + + @Override + public void setVar(AddressSpace space, long offset, int size, boolean quantize, + Pair val) { + piece.setVar(space, offset, size, quantize, val); + } + + @Override + public void setVarInternal(AddressSpace space, long offset, int size, Pair val) { + piece.setVarInternal(space, offset, size, val); + } + @Override public Pair getVar(AddressSpace space, Pair offset, int size, boolean quantize, Reason reason) { return piece.getVar(space, offset.getLeft(), size, quantize, reason); } + @Override + public Pair getVarInternal(AddressSpace space, Pair offset, int size, + Reason reason) { + return piece.getVarInternal(space, offset.getLeft(), size, reason); + } + + @Override + public Pair getVar(AddressSpace space, long offset, int size, boolean quantize, + Reason reason) { + return piece.getVar(space, offset, size, quantize, reason); + } + + @Override + public Pair getVarInternal(AddressSpace space, long offset, int size, Reason reason) { + return piece.getVarInternal(space, offset, size, reason); + } + @Override public void clear() { piece.clear(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java index 6e63f83f13..6f44ae65c7 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java @@ -4,9 +4,9 @@ * 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. @@ -16,6 +16,7 @@ package ghidra.pcode.exec; import java.util.*; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; @@ -91,6 +92,11 @@ public class PairedPcodeExecutorStatePiece return arithmetic; } + @Override + public Stream> streamPieces() { + return Stream.of(left, right).flatMap(p -> p.streamPieces()); + } + @Override public Map> getRegisterValues() { Map leftRVs = left.getRegisterValues(); @@ -106,8 +112,8 @@ public class PairedPcodeExecutorStatePiece } @Override - public PairedPcodeExecutorStatePiece fork() { - return new PairedPcodeExecutorStatePiece<>(left.fork(), right.fork(), addressArithmetic, + public PairedPcodeExecutorStatePiece fork(PcodeStateCallbacks cb) { + return new PairedPcodeExecutorStatePiece<>(left.fork(cb), right.fork(cb), addressArithmetic, arithmetic); } @@ -117,6 +123,25 @@ public class PairedPcodeExecutorStatePiece right.setVar(space, offset, size, quantize, val.getRight()); } + @Override + public void setVarInternal(AddressSpace space, A offset, int size, Pair val) { + left.setVarInternal(space, offset, size, val.getLeft()); + right.setVarInternal(space, offset, size, val.getRight()); + } + + @Override + public void setVar(AddressSpace space, long offset, int size, boolean quantize, + Pair val) { + left.setVar(space, offset, size, quantize, val.getLeft()); + right.setVar(space, offset, size, quantize, val.getRight()); + } + + @Override + public void setVarInternal(AddressSpace space, long offset, int size, Pair val) { + left.setVarInternal(space, offset, size, val.getLeft()); + right.setVarInternal(space, offset, size, val.getRight()); + } + @Override public Pair getVar(AddressSpace space, A offset, int size, boolean quantize, Reason reason) { @@ -125,6 +150,28 @@ public class PairedPcodeExecutorStatePiece right.getVar(space, offset, size, quantize, reason)); } + @Override + public Pair getVarInternal(AddressSpace space, A offset, int size, Reason reason) { + return Pair.of( + left.getVarInternal(space, offset, size, reason), + right.getVarInternal(space, offset, size, reason)); + } + + @Override + public Pair getVar(AddressSpace space, long offset, int size, boolean quantize, + Reason reason) { + return Pair.of( + left.getVar(space, offset, size, quantize, reason), + right.getVar(space, offset, size, quantize, reason)); + } + + @Override + public Pair getVarInternal(AddressSpace space, long offset, int size, Reason reason) { + return Pair.of( + left.getVarInternal(space, offset, size, reason), + right.getVarInternal(space, offset, size, reason)); + } + @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { return left.getConcreteBuffer(address, purpose); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java index 43f09f1215..a29910e88a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java @@ -20,6 +20,7 @@ import java.math.BigInteger; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.opbehavior.*; 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; @@ -80,6 +81,13 @@ public interface PcodeArithmetic { } } + /** + * Get the type of values over which this arithmetic operates. + * + * @return the domain + */ + Class getDomain(); + /** * Get the endianness of this arithmetic * @@ -454,6 +462,20 @@ public interface PcodeArithmetic { return fromConst(value, size, false); } + /** + * Convert the given concrete address to type {@code T}. + * + *

    + * The value will have the pointer size of the address' space. Other than deriving that size, + * the returned value has nothing to do with the address space. + * + * @param address the address + * @return the value + */ + default T fromConst(Address address) { + return fromConst(address.getOffset(), address.getAddressSpace().getPointerSize()); + } + /** * Convert, if possible, the given abstract value to a concrete byte array * @@ -563,6 +585,18 @@ public interface PcodeArithmetic { return Double.longBitsToDouble(toLong(value, purpose)); } + /** + * Convert, if possible, the given abstract value to a concrete address in the given space + * + * @param value the abstract value + * @param space the destination address space + * @param purpose the reason why the emulator needs a concrete value + * @return the address + */ + default Address toAddress(T value, AddressSpace space, Purpose purpose) { + return space.getAddress(toLong(value, purpose)); + } + /** * Get the size in bytes, if possible, of the given abstract value * @@ -578,7 +612,7 @@ public interface PcodeArithmetic { * Get the size in bytes, if possible, of the given abstract value, as an abstract value * *

    - * The returned size should itself has a size of {@link #SIZEOF_SIZEOF}. + * The returned size should have a size of {@link #SIZEOF_SIZEOF}. * * @param value the abstract value * @return the size in bytes, as an abstract value 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 bdfe6f07ad..1136dacdb8 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 @@ -307,11 +307,24 @@ public class PcodeExecutor { /** * Extension point: logic preceding a load * + * @param op the op performing the load * @param space the address space to be loaded from * @param offset the offset about to be loaded from * @param size the size in bytes to be loaded */ - protected void checkLoad(AddressSpace space, T offset, int size) { + protected void beforeLoad(PcodeOp op, AddressSpace space, T offset, int size) { + } + + /** + * Extension point: logic proceeding a load + * + * @param op the op performing the load + * @param space the address space loaded from + * @param offset the offset about loaded from + * @param size the size in bytes loaded + * @param value the value loaded + */ + protected void afterLoad(PcodeOp op, AddressSpace space, T offset, int size, T value) { } /** @@ -345,21 +358,35 @@ public class PcodeExecutor { Varnode inOffset = getLoadStoreOffset(op); T offset = state.getVar(inOffset, reason); Varnode outVar = op.getOutput(); - checkLoad(space, offset, outVar.getSize()); + beforeLoad(op, space, offset, outVar.getSize()); T out = state.getVar(space, offset, outVar.getSize(), true, reason); T mod = arithmetic.modAfterLoad(op, space, offset, out); state.setVar(outVar, mod); + afterLoad(op, space, offset, outVar.getSize(), mod); } /** * Extension point: logic preceding a store * + * @param op the op performing the store * @param space the address space to be stored to * @param offset the offset about to be stored to * @param size the size in bytes to be stored */ - protected void checkStore(AddressSpace space, T offset, int size) { + protected void beforeStore(PcodeOp op, AddressSpace space, T offset, int size, T value) { + } + + /** + * Extension point: logic proceeding a store + * + * @param op the op performing the store + * @param space the address space to be stored to + * @param offset the offset about to be stored to + * @param size the size in bytes to be stored + * @param value the value stored + */ + protected void afterStore(PcodeOp op, AddressSpace space, T offset, int size, T value) { } /** @@ -382,11 +409,12 @@ public class PcodeExecutor { Varnode inOffset = getLoadStoreOffset(op); T offset = state.getVar(inOffset, reason); Varnode valVar = getStoreValue(op); - checkStore(space, offset, valVar.getSize()); - T val = state.getVar(valVar, reason); T mod = arithmetic.modBeforeStore(op, space, offset, val); + beforeStore(op, space, offset, valVar.getSize(), mod); + state.setVar(space, offset, valVar.getSize(), true, mod); + afterStore(op, space, offset, valVar.getSize(), mod); } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java index ee49cb0098..1262d83f49 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java @@ -4,9 +4,9 @@ * 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. @@ -38,7 +38,7 @@ public interface PcodeExecutorState extends PcodeExecutorStatePiece { } @Override - PcodeExecutorState fork(); + PcodeExecutorState fork(PcodeStateCallbacks cb); /** * Use this state as the control, paired with the given auxiliary state. 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 cc47f67073..198afe82ee 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 @@ -4,9 +4,9 @@ * 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. @@ -16,6 +16,8 @@ package ghidra.pcode.exec; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Stream; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.*; @@ -96,12 +98,25 @@ public interface PcodeExecutorStatePiece { */ PcodeArithmetic getArithmetic(); + /** + * Stream over the pieces within. + * + *

    + * If this piece is not a composition of others, then simply stream this piece in a singleton. + * Otherwise, stream the component pieces. (Do not include the composition itself, just the + * component pieces.) + * + * @return the stream + */ + Stream> streamPieces(); + /** * Create a deep copy of this state * + * @param cb callbacks to receive emulation events * @return the copy */ - default PcodeExecutorStatePiece fork() { + default PcodeExecutorStatePiece fork(PcodeStateCallbacks cb) { throw new UnsupportedOperationException(); } @@ -138,6 +153,16 @@ public interface PcodeExecutorStatePiece { */ void setVar(AddressSpace space, A offset, int size, boolean quantize, T val); + /** + * Set the value of a variable without issuing callbacks + * + * @param space the address space + * @param offset the offset within the space + * @param size the size of the variable + * @param val the value + */ + void setVarInternal(AddressSpace space, A offset, int size, T val); + /** * Set the value of a variable * @@ -153,6 +178,19 @@ public interface PcodeExecutorStatePiece { setVar(space, aOffset, size, quantize, val); } + /** + * Set the value of a variable without issuing callbacks + * + * @param space the address space + * @param offset the offset within the space + * @param size the size of the variable + * @param val the value + */ + default void setVarInternal(AddressSpace space, long offset, int size, T val) { + A aOffset = getAddressArithmetic().fromConst(offset, space.getPointerSize()); + setVarInternal(space, aOffset, size, val); + } + /** * Set the value of a variable * @@ -202,6 +240,33 @@ public interface PcodeExecutorStatePiece { */ T getVar(AddressSpace space, A offset, int size, boolean quantize, Reason reason); + /** + * Get the value of a variable without issuing callbacks + * + * @param space the address space + * @param offset the offset within the space + * @param size the size of the variable + * @param reason the reason for reading the variable + * @return the value + */ + T getVarInternal(AddressSpace space, A offset, int size, Reason reason); + + /** + * Get the entry at or after a given offset (without issuing callbacks) + * + *

    + * (Optional operation) For pieces where each value is effective over a range, it is common to + * use an internal map (vice a byte array). When serializing the state, or otherwise seeking a + * complete examination, it is useful to retrieve those internal entries. + * + * @param space the address space + * @param offset the offset within the space + * @return the entry + */ + default Entry getNextEntryInternal(AddressSpace space, A offset) { + throw new UnsupportedOperationException(); + } + /** * Get the value of a variable * @@ -221,6 +286,38 @@ public interface PcodeExecutorStatePiece { return getVar(space, aOffset, size, quantize, reason); } + /** + * Get the value of a variable without issuing callbacks + * + * @param space the address space + * @param offset the offset within the space + * @param size the size of the variable + * @param reason the reason for reading the variable + * @return the value + */ + default T getVarInternal(AddressSpace space, long offset, int size, Reason reason) { + A aOffset = getAddressArithmetic().fromConst(offset, space.getPointerSize()); + return getVarInternal(space, aOffset, size, reason); + } + + /** + * Get the entry at a given offset (without issuing callbacks) + * + *

    + * (Optional operation) For pieces where each value is effective over a range, it is common to + * use an internal map (vice a byte array). When serializing the state, or otherwise seeking a + * complete examination, it is useful to retrieve those internal entries. This returns the next + * entry at or after the given offset within the given space. NOTE the returned entry + * must be for the given space. If no such entry exists, return {@code null}. + * + * @param space the address space + * @param offset the offset within the space + * @return the entry or null + */ + default Entry getNextEntryInternal(AddressSpace space, long offset) { + throw new UnsupportedOperationException(); + } + /** * Get the value of a variable * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeStateCallbacks.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeStateCallbacks.java new file mode 100644 index 0000000000..f6a737c855 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeStateCallbacks.java @@ -0,0 +1,253 @@ +/* ### + * 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.exec; + +import ghidra.pcode.emu.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.program.model.address.*; + +/** + * A set of callbacks available for state changes during p-code execution. + * + *

    + * When dealing with emulation (as opposed to just p-code execution), consider + * {@link PcodeEmulationCallbacks} instead. See {@link PcodeEmulator} for advice regarding extension + * versus integration. In particular, these callbacks were introduced to avert the need to extend + * {@link PcodeExecutorState}s and/or {@link PcodeExecutorStatePiece}s just to introduce + * integration-driven behaviors. E.g., to lazily load state from an external machine-state snapshot, + * the client should implement the + * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView)} or + * {@link PcodeEmulationCallbacks#readUninitialized(PcodeThread, PcodeExecutorStatePiece, AddressSetView)} + * callback rather than extending {@link BytesPcodeExecutorStatePiece}. + */ +public interface PcodeStateCallbacks { + /** + * A singleton implementation of the callbacks that does nothing. + */ + enum NoPcodeStateCallbacks implements PcodeStateCallbacks { + /** Callbacks that do nothing */ + INSTANCE; + } + + /** Callbacks that do nothing */ + PcodeStateCallbacks NONE = NoPcodeStateCallbacks.INSTANCE; + + /** + * A convenience for constructing an address set from a varnode-like triple + * + * @param space the address space + * @param offset the offset + * @param length the size in bytes, at least 1 + * @return the address set + */ + static AddressSet rngSet(AddressSpace space, long offset, int length) { + Address min = space.getAddress(offset); + return new AddressSet(min, min.add(length - 1)); + } + + /** + * Check that the given piece has a required value domain + * + * @param the piece's address domain + * @param the piece's value domain + * @param piece the piece + * @param domain the required value domain + * @return the piece cast to the required value domain if it matched, or null if the piece has a + * different value domain. + */ + @SuppressWarnings("unchecked") + static PcodeExecutorStatePiece checkValueDomain( + PcodeExecutorStatePiece piece, Class domain) { + if (piece.getArithmetic().getDomain() == domain) { + return (PcodeExecutorStatePiece) piece; + } + return null; + } + + /** + * Data was written into the given state piece (abstract addressing.) + * + * @param the piece's address domain + * @param the piece's value domain + * @param piece the state piece + * @param space the address space of the operand + * @param offset the offset of the operand + * @param length the size of the operand + * @param value the value written + */ + default void dataWritten(PcodeExecutorStatePiece piece, AddressSpace space, + A offset, int length, T value) { + } + + /** + * Typically used from within + * {@link #dataWritten(PcodeExecutorStatePiece, AddressSpace, Object, int, Object)} to forward + * the call to the callback for concrete addressing + * {@link #dataWritten(PcodeExecutorStatePiece, Address, int, Object)}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param piece the state piece + * @param space the address space of the operand + * @param offset the offset of the operand + * @param length the size of the operand + * @param value the value written + */ + default void delegateDataWritten(PcodeExecutorStatePiece piece, AddressSpace space, + A offset, int length, T value) { + dataWritten(piece, piece.getAddressArithmetic().toAddress(offset, space, Purpose.STORE), + length, value); + } + + /** + * Data was written into the given state piece (concrete addressing). + * + * @param the piece's address domain + * @param the piece's value domain + * @param piece the state piece + * @param address the address of the operand + * @param length the size of the operand + * @param value the value written + */ + default void dataWritten(PcodeExecutorStatePiece piece, Address address, + int length, T value) { + } + + /** + * Typically used from within + * {@link #dataWritten(PcodeExecutorStatePiece, Address, int, Object)} to forward the call to + * the callback for abstract addressing + * {@link #dataWritten(PcodeExecutorStatePiece, AddressSpace, Object, int, Object)}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param piece the state piece + * @param address the address of the operand + * @param length the size of the operand + * @param value the value written + */ + default void delegateDataWritten(PcodeExecutorStatePiece piece, Address address, + int length, T value) { + dataWritten(piece, address.getAddressSpace(), + piece.getAddressArithmetic().fromConst(address), length, value); + } + + /** + * The executor is preparing to read from uninitialized portions of the given state piece + * (abstract addressing). + * + *

    + * This callback provides an opportunity for something to initialize the required portion + * lazily. In most cases, this should either return 0 indicating the requested portion remains + * uninitialized, or the full {@code length} indicating the full requested portion is now + * initialized. If, for some reason, the requested portion could only be partially initialized, + * this can return a smaller length. Partial initializations are only recognized from the + * starting offset. Other parts could be initialized; however, there is no mechanism for + * communicating that result to the executor. + * + * @param the piece's address domain + * @param the piece's value domain + * @param piece the state piece + * @param space the address space of the operand + * @param offset the offset of the operand + * @param length the size of the operand + * @return the length of the operand just initialized, typically 0 or {@code length} + */ + default int readUninitialized(PcodeExecutorStatePiece piece, + AddressSpace space, A offset, int length) { + return 0; + } + + /** + * Typically used from within + * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int)} to forward to + * the callback for concrete addressing + * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView)}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param piece the state piece + * @param space the address space of the operand + * @param offset the offset of the operand + * @param length the size of the operand + * @return the length of the operand just initialized, typically 0 or {@code length} + */ + default int delegateReadUninitialized(PcodeExecutorStatePiece piece, + AddressSpace space, A offset, int length) { + long lOffset = piece.getAddressArithmetic().toLong(offset, Purpose.LOAD); + AddressSet set = PcodeStateCallbacks.rngSet(space, lOffset, length); + AddressSetView remains = readUninitialized(piece, set); + if (set == remains) { + return 0; + } + set.delete(remains); + AddressRange first = set.getFirstRange(); + return first == null ? 0 : (int) first.getLength(); + } + + /** + * The executor is preparing to read from uninitialized portions of the given state piece + * (concrete addressing). + * + *

    + * This callback provides an opportunity for something to initialize the required portion + * lazily. This method must return the address set that remains uninitialized. If no part of the + * required portion was initialized, this should return {@code set} identically, so that the + * caller can quickly recognize that nothing has changed. Otherwise, this should copy + * {@code set}, remove those parts it was able to initialize, and return the copy. DO NOT + * modify the given {@code set}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param piece the state piece + * @param set the uninitialized portion required + * @return the addresses in {@code set} that remain uninitialized + */ + default AddressSetView readUninitialized(PcodeExecutorStatePiece piece, + AddressSetView set) { + return set; + } + + /** + * Typically used from within + * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSetView)} to forward to the + * callback for abstract addressing + * {@link #readUninitialized(PcodeExecutorStatePiece, AddressSpace, Object, int)}. + * + * @param the piece's address domain + * @param the piece's value domain + * @param piece the state piece + * @param set the uninitialized portion required + * @return the addresses in {@code set} that remain uninitialized + */ + default AddressSetView delegateReadUninitialized(PcodeExecutorStatePiece piece, + AddressSetView set) { + if (set.isEmpty()) { + return set; + } + AddressSet remains = new AddressSet(set); + for (AddressRange range : set) { + int l = readUninitialized(piece, range.getAddressSpace(), + piece.getAddressArithmetic().fromConst(range.getMinAddress()), + (int) range.getLength()); + if (l == 0) { + continue; + } + remains.delete(range.getMinAddress(), range.getMinAddress().add(l - 1)); + } + return remains; + } +} diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java index 7af33d6627..f9d6f4a1d3 100644 --- a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java @@ -58,7 +58,8 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGTest { } protected PcodeExecutor createBytesExecutor(SleighLanguage language) throws Exception { - PcodeExecutorState state = new BytesPcodeExecutorState(language); + PcodeExecutorState state = + new BytesPcodeExecutorState(language, PcodeStateCallbacks.NONE); PcodeArithmetic arithmetic = BytesPcodeArithmetic.forLanguage(language); return new PcodeExecutor<>(language, arithmetic, state, Reason.EXECUTE_READ); } diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpaceTest.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpaceTest.java index b5701539f9..929eb6ca09 100644 --- a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpaceTest.java +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpaceTest.java @@ -30,10 +30,22 @@ import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper; import ghidra.framework.Application; import ghidra.framework.ApplicationConfiguration; -import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.address.*; public class BytesPcodeExecutorStateSpaceTest extends AbstractGTest { + AddressSetView set(AddressRange... ranges) { + AddressSet set = new AddressSet(); + for (AddressRange rng : ranges) { + set.add(rng); + } + return set; + } + + AddressRange rng(AddressSpace space, long min, long max) { + return new AddressRangeImpl(space.getAddress(min), space.getAddress(max)); + } + @Before public void setUp() throws Exception { if (!Application.isInitialized()) { @@ -47,26 +59,26 @@ public class BytesPcodeExecutorStateSpaceTest extends AbstractGTest { public void testComputeUninitialized64() throws Exception { SleighLanguage language = SleighLanguageHelper.getMockBE64Language(); AddressSpace space = language.getDefaultSpace(); - BytesPcodeExecutorStateSpace stateSpace = - new BytesPcodeExecutorStateSpace(language, space, null); + BytesPcodeExecutorStatePiece piece = + new BytesPcodeExecutorStatePiece(language, PcodeStateCallbacks.NONE); + BytesPcodeExecutorStateSpace stateSpace = + new BytesPcodeExecutorStateSpace(language, space, piece); - ULongSpanSet.of(ULongSpan.span(0, 9)); - - assertEquals(ULongSpanSet.of( - ULongSpan.span(0, 7)), + assertEquals(set( + rng(space, 0, 7)), stateSpace.computeUninitialized(0, 8)); - assertEquals(ULongSpanSet.of( - ULongSpan.span(0x7fff_ffff_ffff_fff8L, 0x7fff_ffff_ffff_ffffL)), + assertEquals(set( + rng(space, 0x7fff_ffff_ffff_fff8L, 0x7fff_ffff_ffff_ffffL)), stateSpace.computeUninitialized(0x7fff_ffff_ffff_fff8L, 8)); - assertEquals(ULongSpanSet.of( - ULongSpan.span(0x7fff_ffff_ffff_fffcL, 0x8000_0000_0000_0003L)), + assertEquals(set( + rng(space, 0x7fff_ffff_ffff_fffcL, 0x8000_0000_0000_0003L)), stateSpace.computeUninitialized(0x7fff_ffff_ffff_fffcL, 8)); - assertEquals(ULongSpanSet.of( - ULongSpan.span(0x8000_0000_0000_0008L, 0x8000_0000_0000_000fL)), + assertEquals(set( + rng(space, 0x8000_0000_0000_0008L, 0x8000_0000_0000_000fL)), stateSpace.computeUninitialized(0x8000_0000_0000_0008L, 8)); - assertEquals(ULongSpanSet.of( - ULongSpan.span(0xffff_ffff_ffff_fffcL, 0xffff_ffff_ffff_ffffL), - ULongSpan.span(0, 3)), + assertEquals(set( + rng(space, 0xffff_ffff_ffff_fffcL, 0xffff_ffff_ffff_ffffL), + rng(space, 0, 3)), stateSpace.computeUninitialized(0xffff_ffff_ffff_fffcL, 8)); } @@ -74,26 +86,28 @@ public class BytesPcodeExecutorStateSpaceTest extends AbstractGTest { public void testComputeUninitialized32() throws Exception { SleighLanguage language = SleighLanguageHelper.getMockBE64Language(); AddressSpace space = language.getAddressFactory().getRegisterSpace(); - BytesPcodeExecutorStateSpace stateSpace = - new BytesPcodeExecutorStateSpace(language, space, null); + BytesPcodeExecutorStatePiece piece = + new BytesPcodeExecutorStatePiece(language, PcodeStateCallbacks.NONE); + BytesPcodeExecutorStateSpace stateSpace = + new BytesPcodeExecutorStateSpace(language, space, piece); ULongSpanSet.of(ULongSpan.span(0, 9)); - assertEquals(ULongSpanSet.of( - ULongSpan.span(0, 7)), + assertEquals(set( + rng(space, 0, 7)), stateSpace.computeUninitialized(0, 8)); - assertEquals(ULongSpanSet.of( - ULongSpan.span(0x7fff_fff8L, 0x7fff_ffffL)), + assertEquals(set( + rng(space, 0x7fff_fff8L, 0x7fff_ffffL)), stateSpace.computeUninitialized(0x7fff_fff8L, 8)); - assertEquals(ULongSpanSet.of( - ULongSpan.span(0x7fff_fffcL, 0x8000_0003L)), + assertEquals(set( + rng(space, 0x7fff_fffcL, 0x8000_0003L)), stateSpace.computeUninitialized(0x7fff_fffcL, 8)); - assertEquals(ULongSpanSet.of( - ULongSpan.span(0x8000_0008L, 0x8000_000fL)), + assertEquals(set( + rng(space, 0x8000_0008L, 0x8000_000fL)), stateSpace.computeUninitialized(0x8000_0008L, 8)); - assertEquals(ULongSpanSet.of( - ULongSpan.span(0xffff_fffcL, 0xffff_ffffL), - ULongSpan.span(0, 3)), + assertEquals(set( + rng(space, 0xffff_fffcL, 0xffff_ffffL), + rng(space, 0, 3)), stateSpace.computeUninitialized(0xffff_fffcL, 8)); } } diff --git a/GhidraDocs/GhidraClass/Debugger/B4-Modeling.html b/GhidraDocs/GhidraClass/Debugger/B4-Modeling.html index 837eb91088..e0b81936f6 100644 --- a/GhidraDocs/GhidraClass/Debugger/B4-Modeling.html +++ b/GhidraDocs/GhidraClass/Debugger/B4-Modeling.html @@ -445,16 +445,19 @@ class="sourceCode numberSource java numberLines">< __libc_strlen(); __X86_64_RET(); """); - // TODO: Initialize the emulator's memory from the current program - PcodeThread<byte[]> thread = emu.newThread(); - // TODO: Initialize the thread's registers - - while (true) { - monitor.checkCancelled(); - thread.stepInstruction(100); - } - } -}

    - * As the name implies, this often simply wraps {@code S}'s constructor - * - * @param space the address space - * @param backing the backing, if applicable. null for the unique space - * @return the new space - */ - protected abstract S newSpace(AddressSpace space, B backing); - - @Override - public synchronized S getForSpace(AddressSpace space, boolean toWrite) { - return spaces.computeIfAbsent(space, - s -> newSpace(s, s.isUniqueSpace() ? null : getBacking(s))); + protected static void forkMap(Map into, Map from, + Function forker) { + for (Entry ent : from.entrySet()) { + into.put(ent.getKey(), forker.apply(ent.getValue())); } } protected final Language language; protected final PcodeArithmetic addressArithmetic; protected final PcodeArithmetic arithmetic; + protected final PcodeStateCallbacks cb; protected final AddressSpace uniqueSpace; /** * Construct a state piece for the given language and arithmetic * * @param language the language (used for its memory model) - * @param arithmetic an arithmetic used to generate default values of {@code T} + * @param addressArithmetic an arithmetic used to generate default values of {@code A} + * @param arithmetic an arithmetic used to generate default values of {@code T}. It must be able + * to derive concrete sizes, i.e., {@link PcodeArithmetic#sizeOf(Object)} must always + * return the correct value. + * @param cb callbacks to receive emulation events */ public AbstractLongOffsetPcodeExecutorStatePiece(Language language, - PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic) { + PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic, + PcodeStateCallbacks cb) { this.language = language; this.addressArithmetic = addressArithmetic; this.arithmetic = arithmetic; + this.cb = cb; uniqueSpace = language.getAddressFactory().getUniqueSpace(); } @@ -200,6 +91,11 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece return arithmetic; } + @Override + public Stream> streamPieces() { + return Stream.of(this); + } + /** * Set a value in the unique space * @@ -210,10 +106,11 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @param offset the offset in unique space to store the value * @param size the number of bytes to write (the size of the value) * @param val the value to store + * @param cb callbacks to receive emulation events */ - protected void setUnique(long offset, int size, T val) { + protected void setUnique(long offset, int size, T val, PcodeStateCallbacks cb) { S s = getForSpace(uniqueSpace, true); - setInSpace(s, offset, size, val); + setInSpace(s, offset, size, val, cb); } /** @@ -225,11 +122,12 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @param offset the offset in unique space to get the value * @param size the number of bytes to read (the size of the value) * @param reason the reason for reading state + * @param cb callbacks to receive emulation events * @return the read value */ - protected T getUnique(long offset, int size, Reason reason) { + protected T getUnique(long offset, int size, Reason reason, PcodeStateCallbacks cb) { S s = getForSpace(uniqueSpace, false); - return getFromSpace(s, offset, size, reason); + return getFromSpace(s, offset, size, reason, cb); } /** @@ -239,7 +137,6 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @param toWrite in case internal spaces are generated lazily, this indicates the space must be * present, because it is going to be written to. * @return the space, or {@code null} - * @see AbstractSpaceMap */ protected abstract S getForSpace(AddressSpace space, boolean toWrite); @@ -250,8 +147,10 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @param offset the offset within the space * @param size the number of bytes to write (the size of the value) * @param val the value to store + * @param cb callbacks to receive emulation events */ - protected abstract void setInSpace(S space, long offset, int size, T val); + protected abstract void setInSpace(S space, long offset, int size, T val, + PcodeStateCallbacks cb); /** * Get a value from the given space @@ -260,9 +159,11 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @param offset the offset within the space * @param size the number of bytes to read (the size of the value) * @param reason the reason for reading state + * @param cb callbacks to receive emulation events * @return the read value */ - protected abstract T getFromSpace(S space, long offset, int size, Reason reason); + protected abstract T getFromSpace(S space, long offset, int size, Reason reason, + PcodeStateCallbacks cb); /** * In case spaces are generated lazily, and we're reading from a space that doesn't yet exist, @@ -273,33 +174,100 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * * @param size the number of bytes to read (the size of the value) * @param reason the reason for reading state + * @param cb callbacks to receive emulation events * @return the default value */ - protected T getFromNullSpace(int size, Reason reason) { + protected T getFromNullSpace(int size, Reason reason, PcodeStateCallbacks cb) { return arithmetic.fromConst(0, size); } + protected void setVarInternal(AddressSpace space, long offset, int size, boolean quantize, + T val, PcodeStateCallbacks cb) { + if (space.isConstantSpace()) { + throw new IllegalArgumentException("Cannot write to constant space"); + } + if (space.isUniqueSpace()) { + setUnique(offset, size, val, cb); + return; + } + S s = getForSpace(space, true); + if (quantize) { + offset = quantizeOffset(space, offset); + } + setInSpace(s, offset, size, val, cb); + } + @Override public void setVar(AddressSpace space, A offset, int size, boolean quantize, T val) { long lOffset = addressArithmetic.toLong(offset, Purpose.STORE); setVar(space, lOffset, size, quantize, val); } + @Override + public void setVarInternal(AddressSpace space, A offset, int size, T val) { + long lOffset = addressArithmetic.toLong(offset, Purpose.STORE); + setVarInternal(space, lOffset, size, val); + } + + /** + * Check that the size of the value matches that given + * + *

  • + + // TODO: Initialize the emulator's memory from the current program + + PcodeThread<byte[]> thread = emu.newThread(); + + // TODO: Initialize the thread's registers + + while (true) { + monitor.checkCancelled(); + thread.stepInstruction(100); + } + } +}

    The key is to override createUseropLibrary() in an anonymous extension of the PcodeEmulator. It is polite to compose your library with the one already provided by the super class, @@ -471,32 +474,31 @@ injection. Refer to the example scripts in Ghidra’s userop library, you can by setting the GUI’s emulator factory:

    public class InstallCustomLibraryScript extends GhidraScript implements FlatDebuggerAPI {
    -    public static class CustomBytesDebuggerPcodeEmulator extends BytesDebuggerPcodeEmulator {
    -        private CustomBytesDebuggerPcodeEmulator(PcodeDebuggerAccess access) {
    -            super(access);
    +    public static class CustomPcodeEmulator extends PcodeEmulator {
    +        private CustomPcodeEmulator(Language language, PcodeEmulationCallbacks<byte[]> cb) {
    +            super(language, cb);
             }
     
             @Override
             protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
                 return super.createUseropLibrary()
    -                    .compose(new ModelingScript.SleighStdLibPcodeUseropLibrary<>(
    -                        (SleighLanguage) access.getLanguage()));
    -        }
    -    }
    -
    -    public static class CustomBytesDebuggerPcodeEmulatorFactory
    -            extends BytesDebuggerPcodeEmulatorFactory {
    -        @Override
    -        public DebuggerPcodeMachine<?> create(PcodeDebuggerAccess access) {
    -            return new CustomBytesDebuggerPcodeEmulator(access);
    -        }
    -    }
    -
    -    @Override
    -    protected void run() throws Exception {
    -        getEmulationService().setEmulatorFactory(new CustomBytesDebuggerPcodeEmulatorFactory());
    -    }
    -}
    + .compose(new ModelingScript.SleighStdLibPcodeUseropLibrary<>(getLanguage())); + } + } + + public static class CustomBytesDebuggerPcodeEmulatorFactory + extends DefaultEmulatorFactory { + @Override + public PcodeMachine<?> create(PcodeDebuggerAccess access, Writer writer) { + return new CustomPcodeEmulator(access.getLanguage(), writer.callbacks()); + } + } + + @Override + protected void run() throws Exception { + getEmulationService().setEmulatorFactory(new CustomBytesDebuggerPcodeEmulatorFactory()); + } +}

    This will make your custom userops available in Sleigh injections. NOTE: There is currently no way to introduce custom userops to Watches or the Go To dialog.

    @@ -537,20 +539,20 @@ implement any Ghidra-specific interface, but they can.

    public class ModelingScript extends GhidraScript {
         interface Expr {
    -    }
    -
    -    interface UnExpr extends Expr {
    -        Expr u();
    -    }
    -
    -    interface BinExpr extends Expr {
    -        Expr l();
    -
    -        Expr r();
    -    }
    -
    -    record LitExpr(BigInteger val, int size) implements Expr {
    -    }
    +        int size();
    +    }
    +
    +    interface UnExpr extends Expr {
    +        Expr u();
    +    }
    +
    +    interface BinExpr extends Expr {
    +        Expr l();
    +
    +        Expr r();
    +    }
    +
    +    record LitExpr(BigInteger val, int size) implements Expr {}
     
         record VarExpr(Varnode vn) implements Expr {
             public VarExpr(AddressSpace space, long offset, int size) {
    @@ -560,23 +562,25 @@ class="sourceCode numberSource java numberLines"><
             public VarExpr(Address address, int size) {
                 this(new Varnode(address, size));
             }
    -    }
    -
    -    record InvExpr(Expr u) implements UnExpr {
    -    }
    -
    -    record AddExpr(Expr l, Expr r) implements BinExpr {
    -    }
    -
    -    record SubExpr(Expr l, Expr r) implements BinExpr {
    -    }
    +
    +        @Override
    +        public int size() {
    +            return vn.getSize();
    +        }
    +    }
    +
    +    record InvExpr(Expr u, int size) implements UnExpr {}
    +
    +    record AddExpr(Expr l, Expr r, int size) implements BinExpr {}
     
    -    @Override
    -    protected void run() throws Exception {
    -        // TODO Auto-generated method stub
    -
    -    }
    -}
    + record SubExpr(Expr l, Expr r, int size) implements BinExpr {} + + @Override + protected void run() throws Exception { + // TODO Auto-generated method stub + + } +}

    It should be fairly apparent how you could add more expression types to complete the model. There is some odd nuance in the naming of p-code operations, so do read the documentation carefully. If you are not @@ -612,70 +616,75 @@ class="sourceCode numberSource java numberLines">< } @Override - public Endian getEndian() { - return endian; + public Class<Expr> getDomain() { + return Expr.class; } @Override - public Expr unaryOp(int opcode, int sizeout, int sizein1, Expr in1) { - return switch (opcode) { - case PcodeOp.INT_NEGATE -> new InvExpr(in1); - default -> throw new UnsupportedOperationException(PcodeOp.getMnemonic(opcode)); - }; - } - - @Override - public Expr binaryOp(int opcode, int sizeout, int sizein1, Expr in1, int sizein2, - Expr in2) { - return switch (opcode) { - case PcodeOp.INT_ADD -> new AddExpr(in1, in2); - case PcodeOp.INT_SUB -> new SubExpr(in1, in2); - default -> throw new UnsupportedOperationException(PcodeOp.getMnemonic(opcode)); - }; - } - - @Override - public Expr modBeforeStore(int sizeinOffset, AddressSpace space, Expr inOffset, - int sizeinValue, Expr inValue) { - return inValue; - } - - @Override - public Expr modAfterLoad(int sizeinOffset, AddressSpace space, Expr inOffset, - int sizeinValue, Expr inValue) { - return inValue; - } - - @Override - public Expr fromConst(byte[] value) { - if (endian.isBigEndian()) { - return new LitExpr(new BigInteger(1, value), value.length); - } - byte[] reversed = Arrays.copyOf(value, value.length); - ArrayUtils.reverse(reversed); - return new LitExpr(new BigInteger(1, reversed), reversed.length); - } - - @Override - public Expr fromConst(BigInteger value, int size, boolean isContextreg) { - return new LitExpr(value, size); + public Endian getEndian() { + return endian; + } + + @Override + public Expr unaryOp(int opcode, int sizeout, int sizein1, Expr in1) { + return switch (opcode) { + case PcodeOp.INT_NEGATE -> new InvExpr(in1, sizeout); + default -> throw new UnsupportedOperationException(PcodeOp.getMnemonic(opcode)); + }; + } + + @Override + public Expr binaryOp(int opcode, int sizeout, int sizein1, Expr in1, int sizein2, + Expr in2) { + return switch (opcode) { + case PcodeOp.INT_ADD -> new AddExpr(in1, in2, sizeout); + case PcodeOp.INT_SUB -> new SubExpr(in1, in2, sizeout); + default -> throw new UnsupportedOperationException(PcodeOp.getMnemonic(opcode)); + }; + } + + @Override + public Expr modBeforeStore(int sizeinOffset, AddressSpace space, Expr inOffset, + int sizeinValue, Expr inValue) { + return inValue; + } + + @Override + public Expr modAfterLoad(int sizeinOffset, AddressSpace space, Expr inOffset, + int sizeinValue, Expr inValue) { + return inValue; + } + + @Override + public Expr fromConst(byte[] value) { + if (endian.isBigEndian()) { + return new LitExpr(new BigInteger(1, value), value.length); + } + byte[] reversed = Arrays.copyOf(value, value.length); + ArrayUtils.reverse(reversed); + return new LitExpr(new BigInteger(1, reversed), reversed.length); } @Override - public Expr fromConst(long value, int size) { - return fromConst(BigInteger.valueOf(value), size); + public Expr fromConst(BigInteger value, int size, boolean isContextreg) { + return new LitExpr(value, size); } @Override - public byte[] toConcrete(Expr value, Purpose purpose) { - throw new UnsupportedOperationException(); + public Expr fromConst(long value, int size) { + return fromConst(BigInteger.valueOf(value), size); } @Override - public long sizeOf(Expr value) { + public byte[] toConcrete(Expr value, Purpose purpose) { throw new UnsupportedOperationException(); } -} + + @Override + public long sizeOf(Expr value) { + return value.size(); + } +}

    We have implemented two arithmetic models: one for big-endian languages and one for little-endian. The endianness comes into play when we encode constant values passed to fromConst(). We must @@ -752,106 +761,108 @@ applications” or our particular implementation of them, you are about to see it on full display.

    public static class ExprSpace {
    -    protected final NavigableMap<Long, Expr> map;
    -    protected final AddressSpace space;
    -
    -    protected ExprSpace(AddressSpace space, NavigableMap<Long, Expr> map) {
    -        this.space = space;
    -        this.map = map;
    -    }
    -
    -    public ExprSpace(AddressSpace space) {
    -        this(space, new TreeMap<>());
    -    }
    -
    -    public void clear() {
    -        map.clear();
    -    }
    -
    -    public void set(long offset, Expr val) {
    -        // TODO: Handle overlaps / offcut gets and sets
    -        map.put(offset, val);
    -    }
    -
    -    protected Expr whenNull(long offset, int size) {
    -        return new VarExpr(space, offset, size);
    -    }
    -
    -    public Expr get(long offset, int size) {
    -        // TODO: Handle overlaps / offcut gets and sets
    -        Expr expr = map.get(offset);
    -        return expr != null ? expr : whenNull(offset, size);
    -    }
    -}
    +    protected final NavigableMap<Long, Expr> map = new TreeMap<>(Long::compareUnsigned);
    +    protected final ExprPcodeExecutorStatePiece piece;
    +    protected final AddressSpace space;
    +
    +    protected ExprSpace(AddressSpace space, ExprPcodeExecutorStatePiece piece) {
    +        this.space = space;
    +        this.piece = piece;
    +    }
    +
    +    public void clear() {
    +        map.clear();
    +    }
    +
    +    public void set(long offset, int size, Expr val, PcodeStateCallbacks cb) {
    +        // TODO: Handle overlaps / offcut gets and sets
    +        map.put(offset, val);
    +        cb.dataWritten(piece, space.getAddress(offset), size, val);
    +    }
    +
    +    public Expr get(long offset, int size, PcodeStateCallbacks cb) {
    +        // TODO: Handle overlaps / offcut gets and sets
    +        Expr expr = map.get(offset);
    +        if (expr == null) {
    +            byte[] aOffset =
    +                piece.getAddressArithmetic().fromConst(offset, space.getPointerSize());
    +            if (cb.readUninitialized(piece, space, aOffset, size) != 0) {
    +                return map.get(offset);
    +            }
    +        }
    +        return null;
    +    }
     
    -public static abstract class AbstractExprPcodeExecutorStatePiece<S extends ExprSpace> extends
    -        AbstractLongOffsetPcodeExecutorStatePiece<byte[], Expr, S> {
    -
    -    protected final AbstractSpaceMap<S> spaceMap = newSpaceMap();
    +    public Entry<Long, Expr> getNextEntry(long offset) {
    +        return map.ceilingEntry(offset);
    +    }
    +}
     
    -    public AbstractExprPcodeExecutorStatePiece(Language language) {
    -        super(language, BytesPcodeArithmetic.forLanguage(language),
    -            ExprPcodeArithmetic.forLanguage(language));
    -    }
    +public static class ExprPcodeExecutorStatePiece
    +        extends AbstractLongOffsetPcodeExecutorStatePiece<byte[], Expr, ExprSpace> {
    +
    +    protected final Map<AddressSpace, ExprSpace> spaceMap = new HashMap<>();
     
    -    protected abstract AbstractSpaceMap<S> newSpaceMap();
    -
    -    @Override
    -    public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
    -        throw new UnsupportedOperationException();
    -    }
    -
    -    @Override
    -    public void clear() {
    -        for (S space : spaceMap.values()) {
    -            space.clear();
    -        }
    -    }
    -
    -    @Override
    -    protected S getForSpace(AddressSpace space, boolean toWrite) {
    -        return spaceMap.getForSpace(space, toWrite);
    -    }
    -
    -    @Override
    -    protected void setInSpace(ExprSpace space, long offset, int size, Expr val) {
    -        space.set(offset, val);
    -    }
    -
    -    @Override
    -    protected Expr getFromSpace(S space, long offset, int size, Reason reason) {
    -        return space.get(offset, size);
    -    }
    -
    -    @Override
    -    protected Map<Register, Expr> getRegisterValuesFromSpace(S s, List<Register> registers) {
    -        throw new UnsupportedOperationException();
    +    public ExprPcodeExecutorStatePiece(Language language, PcodeStateCallbacks cb) {
    +        super(language, BytesPcodeArithmetic.forLanguage(language),
    +            ExprPcodeArithmetic.forLanguage(language), cb);
    +    }
    +
    +    @Override
    +    public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
    +        throw new UnsupportedOperationException();
    +    }
    +
    +    @Override
    +    public void clear() {
    +        for (ExprSpace space : spaceMap.values()) {
    +            space.clear();
    +        }
    +    }
    +
    +    @Override
    +    protected ExprSpace getForSpace(AddressSpace space, boolean toWrite) {
    +        if (toWrite) {
    +            return spaceMap.computeIfAbsent(space, s -> new ExprSpace(s, this));
    +        }
    +        return spaceMap.get(space);
    +    }
    +
    +    @Override
    +    public Entry<Long, Expr> getNextEntryInternal(AddressSpace space, long offset) {
    +        ExprSpace s = getForSpace(space, false);
    +        if (s == null) {
    +            return null;
    +        }
    +        return s.getNextEntry(offset);
         }
    -}
    -
    -public static class ExprPcodeExecutorStatePiece
    -        extends AbstractExprPcodeExecutorStatePiece<ExprSpace> {
    -    public ExprPcodeExecutorStatePiece(Language language) {
    -        super(language);
    -    }
    -
    -    @Override
    -    protected AbstractSpaceMap<ExprSpace> newSpaceMap() {
    -        return new SimpleSpaceMap<ExprSpace>() {
    -            @Override
    -            protected ExprSpace newSpace(AddressSpace space) {
    -                return new ExprSpace(space);
    -            }
    -        };
    -    }
    -}
    -
    -public static class BytesExprPcodeExecutorState extends PairedPcodeExecutorState<byte[], Expr> {
    -    public BytesExprPcodeExecutorState(PcodeExecutorStatePiece<byte[], byte[]> concrete) {
    -        super(new PairedPcodeExecutorStatePiece<>(concrete,
    -            new ExprPcodeExecutorStatePiece(concrete.getLanguage())));
    -    }
    -}
    + + @Override + protected void setInSpace(ExprSpace space, long offset, int size, Expr val, + PcodeStateCallbacks cb) { + space.set(offset, size, val, cb); + } + + @Override + protected Expr getFromSpace(ExprSpace space, long offset, int size, Reason reason, + PcodeStateCallbacks cb) { + return space.get(offset, size, cb); + } + + @Override + protected Map<Register, Expr> getRegisterValuesFromSpace(ExprSpace s, + List<Register> registers) { + throw new UnsupportedOperationException(); + } +} + +public static class BytesExprPcodeExecutorState extends PairedPcodeExecutorState<byte[], Expr> { + public BytesExprPcodeExecutorState(PcodeExecutorStatePiece<byte[], byte[]> concrete, + PcodeStateCallbacks cb) { + super(new PairedPcodeExecutorStatePiece<>(concrete, + new ExprPcodeExecutorStatePiece(concrete.getLanguage(), cb))); + } +}

    The abstract class implements a strategy where a dedicated object handles each address space. Each object typically maintains of map of offsets (type long) to the model type, i.e., @@ -864,12 +875,9 @@ from the variables actually stored there. This may not seem like a huge problem, but it is actually quite common, esp., since x86 registers are structured. A write to RAX followed by a read from EAX will immediately demonstrate this issue. Nevertheless, -we leave those details as an exercise. We factor whenNull -so that it can be overridden later.

    +we leave those details as an exercise.

    The remaining parts are mostly boilerplate. We implement the “state -piece” interface by creating another abstract class. An abstract class -is not absolutely necessary, but it will be useful when we integrate the -model with traces and the Debugger GUI later. We are given the language +piece” interface by creating another class. We are given the language and applicable arithmetics, which we just pass to the super constructor. We need not implement a concrete buffer. This would only be required if we needed to decode instructions from the abstract storage model. For @@ -886,10 +894,9 @@ for user inspection, so it need not be implemented, at least not yet.

    Finally, we complete the implementation of the state piece with ExprPcodeExecutorStatePiece, which provides the actual map -and an ExprSpace factory method newSpace(). -The implementation of ExprPcodeExecutorState is simple. It -takes the concrete piece and pairs it with a new piece for our -model.

    +of ExprSpaces. The implementation of +ExprPcodeExecutorState is simple. It takes the concrete +piece and pairs it with a new piece for our model.

    Model-Specific Userops

    @@ -946,28 +953,34 @@ class="sourceCode numberSource java numberLines">< @Override public PcodeExecutorState<Pair<byte[], Expr>> createSharedState( - AuxPcodeEmulator<Expr> emulator, BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); - } - - @Override - public PcodeExecutorState<Pair<byte[], Expr>> createLocalState( - AuxPcodeEmulator<Expr> emulator, PcodeThread<Pair<byte[], Expr>> thread, - BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); - } -} - -public class BytesExprPcodeEmulator extends AuxPcodeEmulator<Expr> { - public BytesExprPcodeEmulator(Language language) { - super(language); - } - - @Override - protected AuxEmulatorPartsFactory<ModelingScript.Expr> getPartsFactory() { - return BytesExprEmulatorPartsFactory.INSTANCE; - } -} + AuxPcodeEmulator<Expr> emulator, BytesPcodeExecutorStatePiece concrete, + PcodeStateCallbacks cb) { + return new BytesExprPcodeExecutorState(concrete, cb); + } + + @Override + public PcodeExecutorState<Pair<byte[], Expr>> createLocalState( + AuxPcodeEmulator<Expr> emulator, PcodeThread<Pair<byte[], Expr>> thread, + BytesPcodeExecutorStatePiece concrete, PcodeStateCallbacks cb) { + return new BytesExprPcodeExecutorState(concrete, cb); + } +} + +public static class BytesExprPcodeEmulator extends AuxPcodeEmulator<Expr> { + public BytesExprPcodeEmulator(Language language, + PcodeEmulationCallbacks<Pair<byte[], Expr>> cb) { + super(language, cb); + } + + public BytesExprPcodeEmulator(Language language) { + this(language, PcodeEmulationCallbacks.none()); + } + + @Override + protected AuxEmulatorPartsFactory<Expr> getPartsFactory() { + return BytesExprEmulatorPartsFactory.INSTANCE; + } +}

    Lots of boilerplate. Essentially, all the parts factory does is give us a flat interface for providing all the parts necessary to construct our augmented emulator: the model arithmetic, userop libraries for the @@ -1029,212 +1042,73 @@ and its sibling files.

    GUI Integration

    -

    This part is rather tedious. It is mostly boilerplate, and the only -real functionality we need to provide is a means of serializing +

    This part is much less onerous than it had been in previous versions. +The only functionality we need to provide is a means of serializing Expr to the trace database. Ideally, this serialization is also human readable, since that will make it straightforward to display -in the UI. Typically, there are two more stages of integration. First is -integration with traces, which involves the aforementioned -serialization. Second is integration with targets, which often does not -apply to abstract models, but could. Each stage involves an extension to -the lower stage’s state. Java does not allow multiple inheritance, so we -will have to be clever in our factoring, but we generally cannot escape -the boilerplate.

    +in the UI. We need only provide a PieceHandler for our new +state piece.

    public static class ExprTraceSpace extends ExprSpace {
    -    protected final PcodeTracePropertyAccess<String> property;
    -
    -    public ExprTraceSpace(AddressSpace space, PcodeTracePropertyAccess<String> property) {
    -        super(space);
    -        this.property = property;
    -    }
    -
    -    @Override
    -    protected Expr whenNull(long offset, int size) {
    -        String string = property.get(space.getAddress(offset));
    -        return deserialize(string);
    -    }
    -
    -    public void writeDown(PcodeTracePropertyAccess<String> into) {
    -        if (space.isUniqueSpace()) {
    -            return;
    -        }
    -
    -        for (Entry<Long, Expr> entry : map.entrySet()) {
    -            // TODO: Ignore and/or clear non-entries
    -            into.put(space.getAddress(entry.getKey()), serialize(entry.getValue()));
    -        }
    -    }
    -
    -    protected String serialize(Expr expr) {
    -        return Unfinished.TODO();
    -    }
    -
    -    protected Expr deserialize(String string) {
    -        return Unfinished.TODO();
    -    }
    -}
    -
    -public static class ExprTracePcodeExecutorStatePiece
    -        extends AbstractExprPcodeExecutorStatePiece<ExprTraceSpace>
    -        implements TracePcodeExecutorStatePiece<byte[], Expr> {
    -    public static final String NAME = "Expr";
    -
    -    protected final PcodeTraceDataAccess data;
    -    protected final PcodeTracePropertyAccess<String> property;
    -
    -    public ExprTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
    -        super(data.getLanguage());
    -        this.data = data;
    -        this.property = data.getPropertyAccess(NAME, String.class);
    -    }
    -
    -    @Override
    -    public PcodeTraceDataAccess getData() {
    -        return data;
    -    }
    -
    -    @Override
    -    protected AbstractSpaceMap<ExprTraceSpace> newSpaceMap() {
    -        return new CacheingSpaceMap<PcodeTracePropertyAccess<String>, ExprTraceSpace>() {
    -            @Override
    -            protected PcodeTracePropertyAccess<String> getBacking(AddressSpace space) {
    -                return property;
    -            }
    -
    -            @Override
    -            protected ExprTraceSpace newSpace(AddressSpace space,
    -                    PcodeTracePropertyAccess<String> backing) {
    -                return new ExprTraceSpace(space, property);
    -            }
    -        };
    -    }
    -
    -    @Override
    -    public ExprTracePcodeExecutorStatePiece fork() {
    -        throw new UnsupportedOperationException();
    -    }
    -
    -    @Override
    -    public void writeDown(PcodeTraceDataAccess into) {
    -        PcodeTracePropertyAccess<String> property = into.getPropertyAccess(NAME, String.class);
    -        for (ExprTraceSpace space : spaceMap.values()) {
    -            space.writeDown(property);
    -        }
    -    }
    -}
    -
    -public static class ExprTracePcodeExecutorState
    -        extends PairedTracePcodeExecutorState<byte[], Expr> {
    -    public ExprTracePcodeExecutorState(TracePcodeExecutorStatePiece<byte[], byte[]> concrete) {
    -        super(new PairedTracePcodeExecutorStatePiece<>(concrete,
    -            new ExprTracePcodeExecutorStatePiece(concrete.getData())));
    -    }
    -}
    -

    Because we do not need any additional logic for target integration, -we do not need to extend the state pieces any further. The concrete -pieces that we augment will contain all the target integration needed. -We have left the serialization as an exercise, though. Last, we -implement the full parts factory and use it to construct and install a -full Expr-augmented emulator factory:

    +class="sourceCode numberSource java numberLines">public static class ExprPieceHandler + extends AbstractSimplePropertyBasedPieceHandler<byte[], Expr, String> { + @Override + public Class<byte[]> getAddressDomain() { + return byte[].class; + } + + @Override + public Class<Expr> getValueDomain() { + return Expr.class; + } + + @Override + protected String getPropertyName() { + return "Expr"; + } + + @Override + protected Class<String> getPropertyType() { + return String.class; + } + + @Override + protected Expr decode(String propertyValue) { + return Unfinished.TODO("Left as an exercise"); + } + + @Override + protected String encode(Expr value) { + return Unfinished.TODO("Left as an exercise"); + } +} +

    This piece handler identifies itself as suitable for handling pieces +where the address domain is concrete byte[] and the value +domain is our abstract Expr. It then claims the property +name "Expr" and tells the framework that the property map +should use Strings. Finally, it provides the actual codec, +which we have left as an exercise. NOTE: You should +also consider using AbstractPropertyBasedPieceHandler if +you’d like to do the exercise of implementing the piecewise and/or +overlapping variable access.

    +

    Last, we implement the final Expr-augmented emulator +factory:

    public enum BytesExprDebuggerEmulatorPartsFactory
    -    implements AuxDebuggerEmulatorPartsFactory<Expr> {
    -    INSTANCE;
    -
    -    @Override
    -    public PcodeArithmetic<Expr> getArithmetic(Language language) {
    -        return ExprPcodeArithmetic.forLanguage(language);
    -    }
    -
    -    @Override
    -    public PcodeUseropLibrary<Pair<byte[], Expr>> createSharedUseropLibrary(
    -            AuxPcodeEmulator<Expr> emulator) {
    -        return PcodeUseropLibrary.nil();
    -    }
    -
    -    @Override
    -    public PcodeUseropLibrary<Pair<byte[], Expr>> createLocalUseropStub(
    -            AuxPcodeEmulator<Expr> emulator) {
    -        return PcodeUseropLibrary.nil();
    -    }
    -
    -    @Override
    -    public PcodeUseropLibrary<Pair<byte[], Expr>> createLocalUseropLibrary(
    -            AuxPcodeEmulator<Expr> emulator, PcodeThread<Pair<byte[], Expr>> thread) {
    -        return PcodeUseropLibrary.nil();
    -    }
    -
    -    @Override
    -    public PcodeExecutorState<Pair<byte[], Expr>> createSharedState(
    -            AuxPcodeEmulator<Expr> emulator, BytesPcodeExecutorStatePiece concrete) {
    -        return new BytesExprPcodeExecutorState(concrete);
    -    }
    -
    -    @Override
    -    public PcodeExecutorState<Pair<byte[], Expr>> createLocalState(
    -            AuxPcodeEmulator<Expr> emulator, PcodeThread<Pair<byte[], Expr>> thread,
    -            BytesPcodeExecutorStatePiece concrete) {
    -        return new BytesExprPcodeExecutorState(concrete);
    -    }
    -
    -    @Override
    -    public TracePcodeExecutorState<Pair<byte[], ModelingScript.Expr>> createTraceSharedState(
    -            AuxTracePcodeEmulator<ModelingScript.Expr> emulator,
    -            BytesTracePcodeExecutorStatePiece concrete) {
    -        return new ExprTracePcodeExecutorState(concrete);
    -    }
    -
    -    @Override
    -    public TracePcodeExecutorState<Pair<byte[], ModelingScript.Expr>> createTraceLocalState(
    -            AuxTracePcodeEmulator<ModelingScript.Expr> emulator,
    -            PcodeThread<Pair<byte[], ModelingScript.Expr>> thread,
    -            BytesTracePcodeExecutorStatePiece concrete) {
    -        return new ExprTracePcodeExecutorState(concrete);
    -    }
    -
    -    @Override
    -    public TracePcodeExecutorState<Pair<byte[], ModelingScript.Expr>> createDebuggerSharedState(
    -            AuxDebuggerPcodeEmulator<ModelingScript.Expr> emulator,
    -            RWTargetMemoryPcodeExecutorStatePiece concrete) {
    -        return new ExprTracePcodeExecutorState(concrete);
    -    }
    -
    -    @Override
    -    public TracePcodeExecutorState<Pair<byte[], ModelingScript.Expr>> createDebuggerLocalState(
    -            AuxDebuggerPcodeEmulator<ModelingScript.Expr> emulator,
    -            PcodeThread<Pair<byte[], ModelingScript.Expr>> thread,
    -            RWTargetRegistersPcodeExecutorStatePiece concrete) {
    -        return new ExprTracePcodeExecutorState(concrete);
    -    }
    -}
    -
    -public static class BytesExprDebuggerPcodeEmulator extends AuxDebuggerPcodeEmulator<Expr> {
    -    public BytesExprDebuggerPcodeEmulator(PcodeDebuggerAccess access) {
    -        super(access);
    -    }
    -
    -    @Override
    -    protected AuxDebuggerEmulatorPartsFactory<Expr> getPartsFactory() {
    -        return BytesExprDebuggerEmulatorPartsFactory.INSTANCE;
    -    }
    -}
    -
    -public static class BytesExprDebuggerPcodeEmulatorFactory
    -        extends AbstractDebuggerPcodeEmulatorFactory {
    -
    -    @Override
    -    public String getTitle() {
    -        return "Expr";
    -    }
    -
    -    @Override
    -    public DebuggerPcodeMachine<?> create(PcodeDebuggerAccess access) {
    -        return new BytesExprDebuggerPcodeEmulator(access);
    -    }
    -}
    -

    The factory can then be installed using a script. The script will set -your factory as the current emulator factory for the whole tool; +class="sourceCode numberSource java numberLines">public static class BytesExprEmulatorFactory implements EmulatorFactory { + @Override + public String getTitle() { + return "Expr"; + } + + @Override + public PcodeMachine<?> create(PcodeDebuggerAccess access, Writer writer) { + writer.putHandler(new ExprPieceHandler()); + return new BytesExprPcodeEmulator(access.getLanguage(), writer.callbacks()); + } +} +

    It merely takes the framework-provided trace Writer and +adds our ExprPieceHandler to it.

    +

    This factory can then be installed using a script. The script will +set your factory as the current emulator factory for the whole tool; however, your script-based factory will not be listed in the menus. Also, if you change your emulator, you must re-run the script to install those modifications. You might also want to invalidate the emulation @@ -1244,7 +1118,7 @@ class="sourceCode numberSource java numberLines">< @Override protected void run() throws Exception { getEmulationService() - .setEmulatorFactory(new ModelingScript.BytesExprDebuggerPcodeEmulatorFactory()); + .setEmulatorFactory(new ModelingScript.BytesExprEmulatorFactory()); } }

    Alternatively, and this is recommended once your emulator is @@ -1252,8 +1126,8 @@ class="sourceCode numberSource java numberLines">< GhidraDev plugin for Eclipse. You will need to break all the nested classes from your script out into separate files. So long as your factory class is public, named with the suffix -DebuggerPcodeEmulatorFactory, implements the interface, and -included in Ghidra’s classpath, Ghidra should find and list it in the +EmulatorFactory, implements the interface, and included in +Ghidra’s classpath, Ghidra should find and list it in the Debugger → Configure Emulator menu.

    Displaying and Manipulating Abstract State

    diff --git a/GhidraDocs/GhidraClass/Debugger/B4-Modeling.md b/GhidraDocs/GhidraClass/Debugger/B4-Modeling.md index aa5931a2ea..14310ec9d9 100644 --- a/GhidraDocs/GhidraClass/Debugger/B4-Modeling.md +++ b/GhidraDocs/GhidraClass/Debugger/B4-Modeling.md @@ -256,8 +256,11 @@ public class CustomLibraryScript extends GhidraScript { __libc_strlen(); __X86_64_RET(); """); + // TODO: Initialize the emulator's memory from the current program + PcodeThread thread = emu.newThread(); + // TODO: Initialize the thread's registers while (true) { @@ -280,24 +283,23 @@ If you would like to (temporarily) override the GUI with a custom userop library ```java {.numberLines} public class InstallCustomLibraryScript extends GhidraScript implements FlatDebuggerAPI { - public static class CustomBytesDebuggerPcodeEmulator extends BytesDebuggerPcodeEmulator { - private CustomBytesDebuggerPcodeEmulator(PcodeDebuggerAccess access) { - super(access); + public static class CustomPcodeEmulator extends PcodeEmulator { + private CustomPcodeEmulator(Language language, PcodeEmulationCallbacks cb) { + super(language, cb); } @Override protected PcodeUseropLibrary createUseropLibrary() { return super.createUseropLibrary() - .compose(new ModelingScript.SleighStdLibPcodeUseropLibrary<>( - (SleighLanguage) access.getLanguage())); + .compose(new ModelingScript.SleighStdLibPcodeUseropLibrary<>(getLanguage())); } } public static class CustomBytesDebuggerPcodeEmulatorFactory - extends BytesDebuggerPcodeEmulatorFactory { + extends DefaultEmulatorFactory { @Override - public DebuggerPcodeMachine create(PcodeDebuggerAccess access) { - return new CustomBytesDebuggerPcodeEmulator(access); + public PcodeMachine create(PcodeDebuggerAccess access, Writer writer) { + return new CustomPcodeEmulator(access.getLanguage(), writer.callbacks()); } } @@ -339,6 +341,7 @@ These need not extend from nor implement any Ghidra-specific interface, but they ```java {.numberLines} public class ModelingScript extends GhidraScript { interface Expr { + int size(); } interface UnExpr extends Expr { @@ -351,8 +354,7 @@ public class ModelingScript extends GhidraScript { Expr r(); } - record LitExpr(BigInteger val, int size) implements Expr { - } + record LitExpr(BigInteger val, int size) implements Expr {} record VarExpr(Varnode vn) implements Expr { public VarExpr(AddressSpace space, long offset, int size) { @@ -362,16 +364,18 @@ public class ModelingScript extends GhidraScript { public VarExpr(Address address, int size) { this(new Varnode(address, size)); } + + @Override + public int size() { + return vn.getSize(); + } } - record InvExpr(Expr u) implements UnExpr { - } + record InvExpr(Expr u, int size) implements UnExpr {} - record AddExpr(Expr l, Expr r) implements BinExpr { - } + record AddExpr(Expr l, Expr r, int size) implements BinExpr {} - record SubExpr(Expr l, Expr r) implements BinExpr { - } + record SubExpr(Expr l, Expr r, int size) implements BinExpr {} @Override protected void run() throws Exception { @@ -411,6 +415,11 @@ public enum ExprPcodeArithmetic implements PcodeArithmetic { this.endian = endian; } + @Override + public Class getDomain() { + return Expr.class; + } + @Override public Endian getEndian() { return endian; @@ -419,7 +428,7 @@ public enum ExprPcodeArithmetic implements PcodeArithmetic { @Override public Expr unaryOp(int opcode, int sizeout, int sizein1, Expr in1) { return switch (opcode) { - case PcodeOp.INT_NEGATE -> new InvExpr(in1); + case PcodeOp.INT_NEGATE -> new InvExpr(in1, sizeout); default -> throw new UnsupportedOperationException(PcodeOp.getMnemonic(opcode)); }; } @@ -428,8 +437,8 @@ public enum ExprPcodeArithmetic implements PcodeArithmetic { public Expr binaryOp(int opcode, int sizeout, int sizein1, Expr in1, int sizein2, Expr in2) { return switch (opcode) { - case PcodeOp.INT_ADD -> new AddExpr(in1, in2); - case PcodeOp.INT_SUB -> new SubExpr(in1, in2); + case PcodeOp.INT_ADD -> new AddExpr(in1, in2, sizeout); + case PcodeOp.INT_SUB -> new SubExpr(in1, in2, sizeout); default -> throw new UnsupportedOperationException(PcodeOp.getMnemonic(opcode)); }; } @@ -473,7 +482,7 @@ public enum ExprPcodeArithmetic implements PcodeArithmetic { @Override public long sizeOf(Expr value) { - throw new UnsupportedOperationException(); + return value.size(); } } ``` @@ -527,50 +536,53 @@ If you are not already familiar with Java naming conventions for "enterprise app ```java {.numberLines} public static class ExprSpace { - protected final NavigableMap map; + protected final NavigableMap map = new TreeMap<>(Long::compareUnsigned); + protected final ExprPcodeExecutorStatePiece piece; protected final AddressSpace space; - protected ExprSpace(AddressSpace space, NavigableMap map) { + protected ExprSpace(AddressSpace space, ExprPcodeExecutorStatePiece piece) { this.space = space; - this.map = map; - } - - public ExprSpace(AddressSpace space) { - this(space, new TreeMap<>()); + this.piece = piece; } public void clear() { map.clear(); } - public void set(long offset, Expr val) { + public void set(long offset, int size, Expr val, PcodeStateCallbacks cb) { // TODO: Handle overlaps / offcut gets and sets map.put(offset, val); + cb.dataWritten(piece, space.getAddress(offset), size, val); } - protected Expr whenNull(long offset, int size) { - return new VarExpr(space, offset, size); - } - - public Expr get(long offset, int size) { + public Expr get(long offset, int size, PcodeStateCallbacks cb) { // TODO: Handle overlaps / offcut gets and sets Expr expr = map.get(offset); - return expr != null ? expr : whenNull(offset, size); + if (expr == null) { + byte[] aOffset = + piece.getAddressArithmetic().fromConst(offset, space.getPointerSize()); + if (cb.readUninitialized(piece, space, aOffset, size) != 0) { + return map.get(offset); + } + } + return null; + } + + public Entry getNextEntry(long offset) { + return map.ceilingEntry(offset); } } -public static abstract class AbstractExprPcodeExecutorStatePiece extends - AbstractLongOffsetPcodeExecutorStatePiece { +public static class ExprPcodeExecutorStatePiece + extends AbstractLongOffsetPcodeExecutorStatePiece { - protected final AbstractSpaceMap spaceMap = newSpaceMap(); + protected final Map spaceMap = new HashMap<>(); - public AbstractExprPcodeExecutorStatePiece(Language language) { + public ExprPcodeExecutorStatePiece(Language language, PcodeStateCallbacks cb) { super(language, BytesPcodeArithmetic.forLanguage(language), - ExprPcodeArithmetic.forLanguage(language)); + ExprPcodeArithmetic.forLanguage(language), cb); } - protected abstract AbstractSpaceMap newSpaceMap(); - @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { throw new UnsupportedOperationException(); @@ -578,53 +590,52 @@ public static abstract class AbstractExprPcodeExecutorStatePiece new ExprSpace(s, this)); + } + return spaceMap.get(space); } @Override - protected void setInSpace(ExprSpace space, long offset, int size, Expr val) { - space.set(offset, val); + public Entry getNextEntryInternal(AddressSpace space, long offset) { + ExprSpace s = getForSpace(space, false); + if (s == null) { + return null; + } + return s.getNextEntry(offset); } @Override - protected Expr getFromSpace(S space, long offset, int size, Reason reason) { - return space.get(offset, size); + protected void setInSpace(ExprSpace space, long offset, int size, Expr val, + PcodeStateCallbacks cb) { + space.set(offset, size, val, cb); } @Override - protected Map getRegisterValuesFromSpace(S s, List registers) { + protected Expr getFromSpace(ExprSpace space, long offset, int size, Reason reason, + PcodeStateCallbacks cb) { + return space.get(offset, size, cb); + } + + @Override + protected Map getRegisterValuesFromSpace(ExprSpace s, + List registers) { throw new UnsupportedOperationException(); } } -public static class ExprPcodeExecutorStatePiece - extends AbstractExprPcodeExecutorStatePiece { - public ExprPcodeExecutorStatePiece(Language language) { - super(language); - } - - @Override - protected AbstractSpaceMap newSpaceMap() { - return new SimpleSpaceMap() { - @Override - protected ExprSpace newSpace(AddressSpace space) { - return new ExprSpace(space); - } - }; - } -} - public static class BytesExprPcodeExecutorState extends PairedPcodeExecutorState { - public BytesExprPcodeExecutorState(PcodeExecutorStatePiece concrete) { + public BytesExprPcodeExecutorState(PcodeExecutorStatePiece concrete, + PcodeStateCallbacks cb) { super(new PairedPcodeExecutorStatePiece<>(concrete, - new ExprPcodeExecutorStatePiece(concrete.getLanguage()))); + new ExprPcodeExecutorStatePiece(concrete.getLanguage(), cb))); } } ``` @@ -638,11 +649,9 @@ Notably, we have neglected the possibility that writes overlap or that reads are This may not seem like a huge problem, but it is actually quite common, esp., since x86 registers are structured. A write to `RAX` followed by a read from `EAX` will immediately demonstrate this issue. Nevertheless, we leave those details as an exercise. -We factor `whenNull` so that it can be overridden later. The remaining parts are mostly boilerplate. -We implement the "state piece" interface by creating another abstract class. -An abstract class is not absolutely necessary, but it will be useful when we integrate the model with traces and the Debugger GUI later. +We implement the "state piece" interface by creating another class. We are given the language and applicable arithmetics, which we just pass to the super constructor. We need not implement a concrete buffer. This would only be required if we needed to decode instructions from the abstract storage model. @@ -653,7 +662,7 @@ Note that the abstract implementation does not provide that map for us, so we mu The next three methods are for getting spaces from that map and then setting and getting values in them. The last method `getRegisterValuesFromSpace()` is more for user inspection, so it need not be implemented, at least not yet. -Finally, we complete the implementation of the state piece with `ExprPcodeExecutorStatePiece`, which provides the actual map and an `ExprSpace` factory method `newSpace()`. +Finally, we complete the implementation of the state piece with `ExprPcodeExecutorStatePiece`, which provides the actual map of `ExprSpace`s. The implementation of `ExprPcodeExecutorState` is simple. It takes the concrete piece and pairs it with a new piece for our model. @@ -705,25 +714,31 @@ public enum BytesExprEmulatorPartsFactory implements AuxEmulatorPartsFactory> createSharedState( - AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); + AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete, + PcodeStateCallbacks cb) { + return new BytesExprPcodeExecutorState(concrete, cb); } @Override public PcodeExecutorState> createLocalState( AuxPcodeEmulator emulator, PcodeThread> thread, - BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); + BytesPcodeExecutorStatePiece concrete, PcodeStateCallbacks cb) { + return new BytesExprPcodeExecutorState(concrete, cb); } } -public class BytesExprPcodeEmulator extends AuxPcodeEmulator { +public static class BytesExprPcodeEmulator extends AuxPcodeEmulator { + public BytesExprPcodeEmulator(Language language, + PcodeEmulationCallbacks> cb) { + super(language, cb); + } + public BytesExprPcodeEmulator(Language language) { - super(language); + this(language, PcodeEmulationCallbacks.none()); } @Override - protected AuxEmulatorPartsFactory getPartsFactory() { + protected AuxEmulatorPartsFactory getPartsFactory() { return BytesExprEmulatorPartsFactory.INSTANCE; } } @@ -778,212 +793,71 @@ See [UnwindAnalysis](../../../Ghidra/Debug/Debugger/src/main/java/ghidra/app/plu ## GUI Integration -This part is rather tedious. -It is mostly boilerplate, and the only real functionality we need to provide is a means of serializing `Expr` to the trace database. +This part is much less onerous than it had been in previous versions. +The only functionality we need to provide is a means of serializing `Expr` to the trace database. Ideally, this serialization is also human readable, since that will make it straightforward to display in the UI. -Typically, there are two more stages of integration. -First is integration with traces, which involves the aforementioned serialization. -Second is integration with targets, which often does not apply to abstract models, but could. -Each stage involves an extension to the lower stage's state. -Java does not allow multiple inheritance, so we will have to be clever in our factoring, but we generally cannot escape the boilerplate. +We need only provide a `PieceHandler` for our new state piece. ```java {.numberLines} -public static class ExprTraceSpace extends ExprSpace { - protected final PcodeTracePropertyAccess property; - - public ExprTraceSpace(AddressSpace space, PcodeTracePropertyAccess property) { - super(space); - this.property = property; +public static class ExprPieceHandler + extends AbstractSimplePropertyBasedPieceHandler { + @Override + public Class getAddressDomain() { + return byte[].class; } @Override - protected Expr whenNull(long offset, int size) { - String string = property.get(space.getAddress(offset)); - return deserialize(string); - } - - public void writeDown(PcodeTracePropertyAccess into) { - if (space.isUniqueSpace()) { - return; - } - - for (Entry entry : map.entrySet()) { - // TODO: Ignore and/or clear non-entries - into.put(space.getAddress(entry.getKey()), serialize(entry.getValue())); - } - } - - protected String serialize(Expr expr) { - return Unfinished.TODO(); - } - - protected Expr deserialize(String string) { - return Unfinished.TODO(); - } -} - -public static class ExprTracePcodeExecutorStatePiece - extends AbstractExprPcodeExecutorStatePiece - implements TracePcodeExecutorStatePiece { - public static final String NAME = "Expr"; - - protected final PcodeTraceDataAccess data; - protected final PcodeTracePropertyAccess property; - - public ExprTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - super(data.getLanguage()); - this.data = data; - this.property = data.getPropertyAccess(NAME, String.class); + public Class getValueDomain() { + return Expr.class; } @Override - public PcodeTraceDataAccess getData() { - return data; + protected String getPropertyName() { + return "Expr"; } @Override - protected AbstractSpaceMap newSpaceMap() { - return new CacheingSpaceMap, ExprTraceSpace>() { - @Override - protected PcodeTracePropertyAccess getBacking(AddressSpace space) { - return property; - } - - @Override - protected ExprTraceSpace newSpace(AddressSpace space, - PcodeTracePropertyAccess backing) { - return new ExprTraceSpace(space, property); - } - }; + protected Class getPropertyType() { + return String.class; } @Override - public ExprTracePcodeExecutorStatePiece fork() { - throw new UnsupportedOperationException(); + protected Expr decode(String propertyValue) { + return Unfinished.TODO("Left as an exercise"); } @Override - public void writeDown(PcodeTraceDataAccess into) { - PcodeTracePropertyAccess property = into.getPropertyAccess(NAME, String.class); - for (ExprTraceSpace space : spaceMap.values()) { - space.writeDown(property); - } - } -} - -public static class ExprTracePcodeExecutorState - extends PairedTracePcodeExecutorState { - public ExprTracePcodeExecutorState(TracePcodeExecutorStatePiece concrete) { - super(new PairedTracePcodeExecutorStatePiece<>(concrete, - new ExprTracePcodeExecutorStatePiece(concrete.getData()))); + protected String encode(Expr value) { + return Unfinished.TODO("Left as an exercise"); } } ``` -Because we do not need any additional logic for target integration, we do not need to extend the state pieces any further. -The concrete pieces that we augment will contain all the target integration needed. -We have left the serialization as an exercise, though. -Last, we implement the full parts factory and use it to construct and install a full `Expr`-augmented emulator factory: +This piece handler identifies itself as suitable for handling pieces where the address domain is concrete `byte[]` and the value domain is our abstract `Expr`. +It then claims the property name `"Expr"` and tells the framework that the property map should use `String`s. +Finally, it provides the actual codec, which we have left as an exercise. +**NOTE**: You should also consider using `AbstractPropertyBasedPieceHandler` if you'd like to do the exercise of implementing the piecewise and/or overlapping variable access. + +Last, we implement the final `Expr`-augmented emulator factory: ```java {.numberLines} -public enum BytesExprDebuggerEmulatorPartsFactory - implements AuxDebuggerEmulatorPartsFactory { - INSTANCE; - - @Override - public PcodeArithmetic getArithmetic(Language language) { - return ExprPcodeArithmetic.forLanguage(language); - } - - @Override - public PcodeUseropLibrary> createSharedUseropLibrary( - AuxPcodeEmulator emulator) { - return PcodeUseropLibrary.nil(); - } - - @Override - public PcodeUseropLibrary> createLocalUseropStub( - AuxPcodeEmulator emulator) { - return PcodeUseropLibrary.nil(); - } - - @Override - public PcodeUseropLibrary> createLocalUseropLibrary( - AuxPcodeEmulator emulator, PcodeThread> thread) { - return PcodeUseropLibrary.nil(); - } - - @Override - public PcodeExecutorState> createSharedState( - AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); - } - - @Override - public PcodeExecutorState> createLocalState( - AuxPcodeEmulator emulator, PcodeThread> thread, - BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createTraceSharedState( - AuxTracePcodeEmulator emulator, - BytesTracePcodeExecutorStatePiece concrete) { - return new ExprTracePcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createTraceLocalState( - AuxTracePcodeEmulator emulator, - PcodeThread> thread, - BytesTracePcodeExecutorStatePiece concrete) { - return new ExprTracePcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createDebuggerSharedState( - AuxDebuggerPcodeEmulator emulator, - RWTargetMemoryPcodeExecutorStatePiece concrete) { - return new ExprTracePcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createDebuggerLocalState( - AuxDebuggerPcodeEmulator emulator, - PcodeThread> thread, - RWTargetRegistersPcodeExecutorStatePiece concrete) { - return new ExprTracePcodeExecutorState(concrete); - } -} - -public static class BytesExprDebuggerPcodeEmulator extends AuxDebuggerPcodeEmulator { - public BytesExprDebuggerPcodeEmulator(PcodeDebuggerAccess access) { - super(access); - } - - @Override - protected AuxDebuggerEmulatorPartsFactory getPartsFactory() { - return BytesExprDebuggerEmulatorPartsFactory.INSTANCE; - } -} - -public static class BytesExprDebuggerPcodeEmulatorFactory - extends AbstractDebuggerPcodeEmulatorFactory { - +public static class BytesExprEmulatorFactory implements EmulatorFactory { @Override public String getTitle() { return "Expr"; } @Override - public DebuggerPcodeMachine create(PcodeDebuggerAccess access) { - return new BytesExprDebuggerPcodeEmulator(access); + public PcodeMachine create(PcodeDebuggerAccess access, Writer writer) { + writer.putHandler(new ExprPieceHandler()); + return new BytesExprPcodeEmulator(access.getLanguage(), writer.callbacks()); } } ``` -The factory can then be installed using a script. +It merely takes the framework-provided trace `Writer` and adds our `ExprPieceHandler` to it. + +This factory can then be installed using a script. The script will set your factory as the current emulator factory for the whole tool; however, your script-based factory will not be listed in the menus. Also, if you change your emulator, you must re-run the script to install those modifications. You might also want to invalidate the emulation cache. @@ -993,14 +867,14 @@ public class InstallExprEmulatorScript extends GhidraScript implements FlatDebug @Override protected void run() throws Exception { getEmulationService() - .setEmulatorFactory(new ModelingScript.BytesExprDebuggerPcodeEmulatorFactory()); + .setEmulatorFactory(new ModelingScript.BytesExprEmulatorFactory()); } } ``` Alternatively, and this is recommended once your emulator is "production ready," you should create a proper Module project using the GhidraDev plugin for Eclipse. You will need to break all the nested classes from your script out into separate files. -So long as your factory class is public, named with the suffix `DebuggerPcodeEmulatorFactory`, implements the interface, and included in Ghidra's classpath, Ghidra should find and list it in the **Debugger → Configure Emulator** menu. +So long as your factory class is public, named with the suffix `EmulatorFactory`, implements the interface, and included in Ghidra's classpath, Ghidra should find and list it in the **Debugger → Configure Emulator** menu. ### Displaying and Manipulating Abstract State diff --git a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/CustomLibraryScript.java b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/CustomLibraryScript.java index 0f14fa38c8..fe8ab24338 100644 --- a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/CustomLibraryScript.java +++ b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/CustomLibraryScript.java @@ -4,9 +4,9 @@ * 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. @@ -41,7 +41,7 @@ public class CustomLibraryScript extends GhidraScript { // TODO: Initialize the thread's registers while (true) { - monitor.checkCanceled(); + monitor.checkCancelled(); thread.stepInstruction(100); } } diff --git a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/InstallCustomLibraryScript.java b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/InstallCustomLibraryScript.java index 1554d2b57e..887f8ade77 100644 --- a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/InstallCustomLibraryScript.java +++ b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/InstallCustomLibraryScript.java @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator; -import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulatorFactory; +import ghidra.app.plugin.core.debug.service.emulation.DefaultEmulatorFactory; import ghidra.app.script.GhidraScript; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; import ghidra.debug.api.emulation.PcodeDebuggerAccess; import ghidra.debug.flatapi.FlatDebuggerAPI; +import ghidra.pcode.emu.*; import ghidra.pcode.exec.PcodeUseropLibrary; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; +import ghidra.program.model.lang.Language; public class InstallCustomLibraryScript extends GhidraScript implements FlatDebuggerAPI { - public static class CustomBytesDebuggerPcodeEmulator extends BytesDebuggerPcodeEmulator { - private CustomBytesDebuggerPcodeEmulator(PcodeDebuggerAccess access) { - super(access); + public static class CustomPcodeEmulator extends PcodeEmulator { + private CustomPcodeEmulator(Language language, PcodeEmulationCallbacks cb) { + super(language, cb); } @Override @@ -35,10 +36,10 @@ public class InstallCustomLibraryScript extends GhidraScript implements FlatDebu } public static class CustomBytesDebuggerPcodeEmulatorFactory - extends BytesDebuggerPcodeEmulatorFactory { + extends DefaultEmulatorFactory { @Override - public DebuggerPcodeMachine create(PcodeDebuggerAccess access) { - return new CustomBytesDebuggerPcodeEmulator(access); + public PcodeMachine create(PcodeDebuggerAccess access, Writer writer) { + return new CustomPcodeEmulator(access.getLanguage(), writer.callbacks()); } } diff --git a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/InstallExprEmulatorScript.java b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/InstallExprEmulatorScript.java index c1dde686da..3fea5cd59f 100644 --- a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/InstallExprEmulatorScript.java +++ b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/InstallExprEmulatorScript.java @@ -4,9 +4,9 @@ * 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. @@ -20,6 +20,6 @@ public class InstallExprEmulatorScript extends GhidraScript implements FlatDebug @Override protected void run() throws Exception { getEmulationService() - .setEmulatorFactory(new ModelingScript.BytesExprDebuggerPcodeEmulatorFactory()); + .setEmulatorFactory(new ModelingScript.BytesExprEmulatorFactory()); } } diff --git a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/ModelingScript.java b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/ModelingScript.java index 016aebb1bd..7e33a5978c 100644 --- a/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/ModelingScript.java +++ b/GhidraDocs/GhidraClass/Debugger/ghidra_scripts/ModelingScript.java @@ -4,9 +4,9 @@ * 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. @@ -20,25 +20,20 @@ import java.util.Map.Entry; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.tuple.Pair; -import ghidra.app.plugin.core.debug.service.emulation.*; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.script.GhidraScript; -import ghidra.debug.api.emulation.DebuggerPcodeMachine; +import ghidra.debug.api.emulation.EmulatorFactory; import ghidra.debug.api.emulation.PcodeDebuggerAccess; import ghidra.lifecycle.Unfinished; +import ghidra.pcode.emu.*; import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor; -import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; -import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; -import ghidra.pcode.exec.trace.*; -import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; -import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; -import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.AbstractSimplePropertyBasedPieceHandler; +import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.pcode.struct.StructuredSleigh; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; @@ -193,6 +188,7 @@ public class ModelingScript extends GhidraScript { // ---------------------- interface Expr { + int size(); } interface UnExpr extends Expr { @@ -205,8 +201,7 @@ public class ModelingScript extends GhidraScript { Expr r(); } - record LitExpr(BigInteger val, int size) implements Expr { - } + record LitExpr(BigInteger val, int size) implements Expr {} record VarExpr(Varnode vn) implements Expr { public VarExpr(AddressSpace space, long offset, int size) { @@ -216,16 +211,18 @@ public class ModelingScript extends GhidraScript { public VarExpr(Address address, int size) { this(new Varnode(address, size)); } + + @Override + public int size() { + return vn.getSize(); + } } - record InvExpr(Expr u) implements UnExpr { - } + record InvExpr(Expr u, int size) implements UnExpr {} - record AddExpr(Expr l, Expr r) implements BinExpr { - } + record AddExpr(Expr l, Expr r, int size) implements BinExpr {} - record SubExpr(Expr l, Expr r) implements BinExpr { - } + record SubExpr(Expr l, Expr r, int size) implements BinExpr {} // ---------------------- @@ -246,6 +243,11 @@ public class ModelingScript extends GhidraScript { this.endian = endian; } + @Override + public Class getDomain() { + return Expr.class; + } + @Override public Endian getEndian() { return endian; @@ -254,7 +256,7 @@ public class ModelingScript extends GhidraScript { @Override public Expr unaryOp(int opcode, int sizeout, int sizein1, Expr in1) { return switch (opcode) { - case PcodeOp.INT_NEGATE -> new InvExpr(in1); + case PcodeOp.INT_NEGATE -> new InvExpr(in1, sizeout); default -> throw new UnsupportedOperationException(PcodeOp.getMnemonic(opcode)); }; } @@ -263,8 +265,8 @@ public class ModelingScript extends GhidraScript { public Expr binaryOp(int opcode, int sizeout, int sizein1, Expr in1, int sizein2, Expr in2) { return switch (opcode) { - case PcodeOp.INT_ADD -> new AddExpr(in1, in2); - case PcodeOp.INT_SUB -> new SubExpr(in1, in2); + case PcodeOp.INT_ADD -> new AddExpr(in1, in2, sizeout); + case PcodeOp.INT_SUB -> new SubExpr(in1, in2, sizeout); default -> throw new UnsupportedOperationException(PcodeOp.getMnemonic(opcode)); }; } @@ -308,58 +310,60 @@ public class ModelingScript extends GhidraScript { @Override public long sizeOf(Expr value) { - throw new UnsupportedOperationException(); + return value.size(); } } // ---------------------- public static class ExprSpace { - protected final NavigableMap map; + protected final NavigableMap map = new TreeMap<>(Long::compareUnsigned); + protected final ExprPcodeExecutorStatePiece piece; protected final AddressSpace space; - protected ExprSpace(AddressSpace space, NavigableMap map) { + protected ExprSpace(AddressSpace space, ExprPcodeExecutorStatePiece piece) { this.space = space; - this.map = map; - } - - public ExprSpace(AddressSpace space) { - this(space, new TreeMap<>()); + this.piece = piece; } public void clear() { map.clear(); } - public void set(long offset, Expr val) { + public void set(long offset, int size, Expr val, PcodeStateCallbacks cb) { // TODO: Handle overlaps / offcut gets and sets map.put(offset, val); + cb.dataWritten(piece, space.getAddress(offset), size, val); } - public Expr get(long offset, int size) { + public Expr get(long offset, int size, PcodeStateCallbacks cb) { // TODO: Handle overlaps / offcut gets and sets Expr expr = map.get(offset); - return expr != null ? expr : whenNull(offset, size); + if (expr == null) { + byte[] aOffset = + piece.getAddressArithmetic().fromConst(offset, space.getPointerSize()); + if (cb.readUninitialized(piece, space, aOffset, size) != 0) { + return map.get(offset); + } + } + return null; } - protected Expr whenNull(long offset, int size) { - return new VarExpr(space, offset, size); + public Entry getNextEntry(long offset) { + return map.ceilingEntry(offset); } } - public static abstract class AbstractBytesExprPcodeExecutorStatePiece - extends - AbstractLongOffsetPcodeExecutorStatePiece { + public static class ExprPcodeExecutorStatePiece + extends AbstractLongOffsetPcodeExecutorStatePiece { - protected final AbstractSpaceMap spaceMap = newSpaceMap(); + protected final Map spaceMap = new HashMap<>(); - public AbstractBytesExprPcodeExecutorStatePiece(Language language) { + public ExprPcodeExecutorStatePiece(Language language, PcodeStateCallbacks cb) { super(language, BytesPcodeArithmetic.forLanguage(language), - ExprPcodeArithmetic.forLanguage(language)); + ExprPcodeArithmetic.forLanguage(language), cb); } - protected abstract AbstractSpaceMap newSpaceMap(); - @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { throw new UnsupportedOperationException(); @@ -367,53 +371,52 @@ public class ModelingScript extends GhidraScript { @Override public void clear() { - for (S space : spaceMap.values()) { + for (ExprSpace space : spaceMap.values()) { space.clear(); } } @Override - protected S getForSpace(AddressSpace space, boolean toWrite) { - return spaceMap.getForSpace(space, toWrite); + protected ExprSpace getForSpace(AddressSpace space, boolean toWrite) { + if (toWrite) { + return spaceMap.computeIfAbsent(space, s -> new ExprSpace(s, this)); + } + return spaceMap.get(space); } @Override - protected void setInSpace(ExprSpace space, long offset, int size, Expr val) { - space.set(offset, val); + public Entry getNextEntryInternal(AddressSpace space, long offset) { + ExprSpace s = getForSpace(space, false); + if (s == null) { + return null; + } + return s.getNextEntry(offset); } @Override - protected Expr getFromSpace(S space, long offset, int size, Reason reason) { - return space.get(offset, size); + protected void setInSpace(ExprSpace space, long offset, int size, Expr val, + PcodeStateCallbacks cb) { + space.set(offset, size, val, cb); } @Override - protected Map getRegisterValuesFromSpace(S s, List registers) { + protected Expr getFromSpace(ExprSpace space, long offset, int size, Reason reason, + PcodeStateCallbacks cb) { + return space.get(offset, size, cb); + } + + @Override + protected Map getRegisterValuesFromSpace(ExprSpace s, + List registers) { throw new UnsupportedOperationException(); } } - public static class ExprPcodeExecutorStatePiece - extends AbstractBytesExprPcodeExecutorStatePiece { - public ExprPcodeExecutorStatePiece(Language language) { - super(language); - } - - @Override - protected AbstractSpaceMap newSpaceMap() { - return new SimpleSpaceMap() { - @Override - protected ExprSpace newSpace(AddressSpace space) { - return new ExprSpace(space); - } - }; - } - } - public static class BytesExprPcodeExecutorState extends PairedPcodeExecutorState { - public BytesExprPcodeExecutorState(PcodeExecutorStatePiece concrete) { + public BytesExprPcodeExecutorState(PcodeExecutorStatePiece concrete, + PcodeStateCallbacks cb) { super(new PairedPcodeExecutorStatePiece<>(concrete, - new ExprPcodeExecutorStatePiece(concrete.getLanguage()))); + new ExprPcodeExecutorStatePiece(concrete.getLanguage(), cb))); } } @@ -447,21 +450,27 @@ public class ModelingScript extends GhidraScript { @Override public PcodeExecutorState> createSharedState( - AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); + AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete, + PcodeStateCallbacks cb) { + return new BytesExprPcodeExecutorState(concrete, cb); } @Override public PcodeExecutorState> createLocalState( AuxPcodeEmulator emulator, PcodeThread> thread, - BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); + BytesPcodeExecutorStatePiece concrete, PcodeStateCallbacks cb) { + return new BytesExprPcodeExecutorState(concrete, cb); } } - public class BytesExprPcodeEmulator extends AuxPcodeEmulator { + public static class BytesExprPcodeEmulator extends AuxPcodeEmulator { + public BytesExprPcodeEmulator(Language language, + PcodeEmulationCallbacks> cb) { + super(language, cb); + } + public BytesExprPcodeEmulator(Language language) { - super(language); + this(language, PcodeEmulationCallbacks.none()); } @Override @@ -472,194 +481,49 @@ public class ModelingScript extends GhidraScript { // ---------------------- - public static class ExprTraceSpace extends ExprSpace { - protected final PcodeTracePropertyAccess property; - - public ExprTraceSpace(AddressSpace space, PcodeTracePropertyAccess property) { - super(space); - this.property = property; + public static class ExprPieceHandler + extends AbstractSimplePropertyBasedPieceHandler { + @Override + public Class getAddressDomain() { + return byte[].class; } @Override - protected Expr whenNull(long offset, int size) { - String string = property.get(space.getAddress(offset)); - return deserialize(string); + public Class getValueDomain() { + return Expr.class; } - public void writeDown(PcodeTracePropertyAccess into) { - if (space.isUniqueSpace()) { - return; - } - - for (Entry entry : map.entrySet()) { - // TODO: Ignore and/or clear non-entries - into.put(space.getAddress(entry.getKey()), serialize(entry.getValue())); - } + @Override + protected String getPropertyName() { + return "Expr"; } - protected String serialize(Expr expr) { - return Unfinished.TODO(); + @Override + protected Class getPropertyType() { + return String.class; } - protected Expr deserialize(String string) { - return Unfinished.TODO(); + @Override + protected Expr decode(String propertyValue) { + return Unfinished.TODO("Left as an exercise"); + } + + @Override + protected String encode(Expr value) { + return Unfinished.TODO("Left as an exercise"); } } - public static class BytesExprTracePcodeExecutorStatePiece - extends AbstractBytesExprPcodeExecutorStatePiece - implements TracePcodeExecutorStatePiece { - public static final String NAME = "Taint"; - - protected final PcodeTraceDataAccess data; - protected final PcodeTracePropertyAccess property; - - public BytesExprTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { - super(data.getLanguage()); - this.data = data; - this.property = data.getPropertyAccess(NAME, String.class); - } - - @Override - public PcodeTraceDataAccess getData() { - return data; - } - - @Override - protected AbstractSpaceMap newSpaceMap() { - return new CacheingSpaceMap, ExprTraceSpace>() { - @Override - protected PcodeTracePropertyAccess getBacking(AddressSpace space) { - return property; - } - - @Override - protected ExprTraceSpace newSpace(AddressSpace space, - PcodeTracePropertyAccess backing) { - return new ExprTraceSpace(space, property); - } - }; - } - - @Override - public BytesExprTracePcodeExecutorStatePiece fork() { - throw new UnsupportedOperationException(); - } - - @Override - public void writeDown(PcodeTraceDataAccess into) { - PcodeTracePropertyAccess property = into.getPropertyAccess(NAME, String.class); - for (ExprTraceSpace space : spaceMap.values()) { - space.writeDown(property); - } - } - } - - public static class BytesExprTracePcodeExecutorState - extends PairedTracePcodeExecutorState { - - public BytesExprTracePcodeExecutorState( - TracePcodeExecutorStatePiece concrete) { - super(new PairedTracePcodeExecutorStatePiece<>(concrete, - new BytesExprTracePcodeExecutorStatePiece(concrete.getData()))); - } - } - - enum BytesExprDebuggerEmulatorPartsFactory implements AuxDebuggerEmulatorPartsFactory { - INSTANCE; - - @Override - public PcodeArithmetic getArithmetic(Language language) { - return ExprPcodeArithmetic.forLanguage(language); - } - - @Override - public PcodeUseropLibrary> createSharedUseropLibrary( - AuxPcodeEmulator emulator) { - return PcodeUseropLibrary.nil(); - } - - @Override - public PcodeUseropLibrary> createLocalUseropStub( - AuxPcodeEmulator emulator) { - return PcodeUseropLibrary.nil(); - } - - @Override - public PcodeUseropLibrary> createLocalUseropLibrary( - AuxPcodeEmulator emulator, - PcodeThread> thread) { - return PcodeUseropLibrary.nil(); - } - - @Override - public PcodeExecutorState> createSharedState( - AuxPcodeEmulator emulator, - BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); - } - - @Override - public PcodeExecutorState> createLocalState( - AuxPcodeEmulator emulator, - PcodeThread> thread, - BytesPcodeExecutorStatePiece concrete) { - return new BytesExprPcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createTraceSharedState( - AuxTracePcodeEmulator emulator, - BytesTracePcodeExecutorStatePiece concrete) { - return new BytesExprTracePcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createTraceLocalState( - AuxTracePcodeEmulator emulator, - PcodeThread> thread, - BytesTracePcodeExecutorStatePiece concrete) { - return new BytesExprTracePcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createDebuggerSharedState( - AuxDebuggerPcodeEmulator emulator, - RWTargetMemoryPcodeExecutorStatePiece concrete) { - return new BytesExprTracePcodeExecutorState(concrete); - } - - @Override - public TracePcodeExecutorState> createDebuggerLocalState( - AuxDebuggerPcodeEmulator emulator, - PcodeThread> thread, - RWTargetRegistersPcodeExecutorStatePiece concrete) { - return new BytesExprTracePcodeExecutorState(concrete); - } - } - - public static class BytesExprDebuggerPcodeEmulator extends AuxDebuggerPcodeEmulator { - public BytesExprDebuggerPcodeEmulator(PcodeDebuggerAccess access) { - super(access); - } - - @Override - protected AuxDebuggerEmulatorPartsFactory getPartsFactory() { - return BytesExprDebuggerEmulatorPartsFactory.INSTANCE; - } - } - - public static class BytesExprDebuggerPcodeEmulatorFactory - extends AbstractDebuggerPcodeEmulatorFactory { - + public static class BytesExprEmulatorFactory implements EmulatorFactory { @Override public String getTitle() { return "Expr"; } @Override - public DebuggerPcodeMachine create(PcodeDebuggerAccess access) { - return new BytesExprDebuggerPcodeEmulator(access); + public PcodeMachine create(PcodeDebuggerAccess access, Writer writer) { + writer.putHandler(new ExprPieceHandler()); + return new BytesExprPcodeEmulator(access.getLanguage(), writer.callbacks()); } }