diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java
index 7a6346e203..e6d653e858 100644
--- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java
+++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/manager/GdbManager.java
@@ -48,9 +48,7 @@ public interface GdbManager extends AutoCloseable, GdbConsoleOperations, GdbBrea
RETURN("return"),
STEP("step"),
STEPI("stepi", "step-instruction"),
- UNTIL("until"),
- /** User-defined */
- EXTENDED("echo extended-step?", "???"),;
+ UNTIL("until");
public final String mi2;
public final String cli;
diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java
index fa6a1a990a..abb658c70b 100644
--- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java
+++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetInferior.java
@@ -19,9 +19,8 @@ import java.util.*;
import java.util.concurrent.CompletableFuture;
import agent.gdb.manager.*;
-import agent.gdb.manager.GdbManager.StepCmd;
-import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
import agent.gdb.manager.impl.cmd.GdbConsoleExecCommand.CompletesWithRunning;
+import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
import agent.gdb.manager.reason.*;
import ghidra.async.AsyncFence;
import ghidra.dbg.agent.DefaultTargetObject;
@@ -43,7 +42,7 @@ public class GdbModelTargetInferior
extends DefaultTargetObject This plugin presents actions for controlling targets and modifying machine state. It
+ provides a drop-down action in the main toolbar for choosing what to control for the current
+ target: The live target, the integrated emulator, or the recorded trace. Only those control
+ actions suitable for the selection are displayed. Machine-state edits throughout the UI are
+ directed accordingly. The plugin provides several actions, but only certain ones are displayed, depending on the
+ current mode. This action changes the mode for the active trace and, if applicable, its associated live
+ target. It is always displayed, but only available when a trace is active. The possible modes
+ are: These actions are visible when the "Control Target" or "Control Target w/Edits Disabled"
+ mode is selected. They are available only when the current trace has an associated live target.
+ Commands are directed to the focused object or a suitable substitute. Allow the current target to resume execution. Other debuggers may call this "continue" or
+ "go." If successful, the target enters the "running" state until/if it is interrupted or
+ terminated. This is available when the target is currently stopped. Interrupt the current target's execution. Other debuggers may call this "break," "suspend,"
+ or "stop." If successful, the target enters the "stopped" state. This is available when the
+ target is currently running. Kill the current target. Other debuggers may call this "terminate" or "stop." By default,
+ this will consequently close the current trace. If successful, the target enters the
+ "terminated" state. This is always available for a live target. Disconnect from the current target's debugger. This usually causes the connected debugger to
+ terminate and likely kill its targets. By default, this will consequently close the trace for
+ all affected targets. This is always available for a live target. Step the current target to the next instruction. This is available when the target is
+ currently stopped. If successful the target may briefly enter the "running" state. Step the current target to the next instruction in the current subroutine. This is available
+ when the target is currently stopped. If successful the target may briefly enter the "running"
+ state. Allow the current target to finish the current subroutine, pausing after. This is available
+ when the target is currently stopped. If successful the target may briefly enter the "running"
+ state. Perform a target-defined step, often the last (possibly custom or extended) step. This is
+ available when the target is currently stopped. If successful the target may briefly enter the
+ "running" state. These actions are visible when the "Control Trace" mode is selected. They are available when
+ there is an active trace. This navigates the trace backward one snapshot. All windows displaying machine state will
+ show that recorded in the current snapshot. This is available only when there exists a snapshot
+ previous to the current. This nativates the trace forward one snapshot. All windows displaying machine state will
+ show that recorded in the current snapshot. This is available only when there exists a snapshot
+ after the current. These actions are visible when the "Control Emulator" mode is selected. They are available
+ when there is an active trace. Commands are directed to the integrated emulator for the current
+ trace. Allow the emulator to resume execution. This is available when no other integrated emulator
+ is running. A monitor dialog is presented during execution, but the GUI remains responsive.
+ Only one emulator can be run from the GUI at a time. If the current snapshot represents the
+ live target, the emulator may read additional machine state from the live target. For
+ non-contrived programs, the emulator will likely be interrupted, since some instructions and
+ all system calls are not yet supported. It could also start executing from unmapped memory or
+ enter a infinite loop. If it seems to carry on too long, interrupt it and examine. Interrupt the currently-running emulator. This is available when any integrated emulator is
+ running. In most cases, this is the emulator for the current trace, but it may not be.
+ Canceling the dialog for an emulation task will also interrupt the emulator. Upon interruption,
+ the emulation schedule is recorded and the snapshot displayed in the GUI. Steps the emulator to the previous instruction, by flow. This is available when the current
+ snapshot includes emulated steps. This operates by repeating the current emulation schedule
+ with one less step. Thus, it effectively steps backward, heeding the proper control flow. While
+ not common, if emulation to the current snapshot took a good bit of time, then stepping
+ backward will likely take about the same amount of time. Steps the emulator to the next instruction, by flow. This is available when there is an
+ active thread. At worst, this operates by repeating the current emulation schedule with one
+ more step of the current thread. In most cases, this can use the cached emulator for the
+ current snapshot and advanced it a single step. Note that "Step Over" is not currently
+ supported by the emulator. Skips the emulator over the current instruction, ignoring flow. This is available when there
+ is an active thread. At worst, this operates by repeating the current emulation schedule with
+ an added skip for the current thread. In most cases, this can use the cached emulator for the
+ current snapshot and advance it by skipping once. Note that this skips the
+ instruction. Thus, when used on a "call" instruction, all effects and side-effects of the
+ subroutine are averted. This is not the same as "Step Over," which is not currently
+ supported by the emulator. Write Target is the default mode, because in most cases, this is the desired behavior. When
+ the target dies, Write Target essentially means Read Only. For the most part, modifying the
+ recording itself is discouraged. There are few reasons, perhaps including 1) Hand-generating an
+ experimental trace; 2) Generating a trace from a script, e.g., importing an event log,
+ recording an emulated target; 3) Patching in state missed by the original recording. Be wary
+ when switching from emulation to trace mode, since you could accidentally edit scratch space in
+ the trace. It is allowed but can produce non-intuitive and erroneous results, since the
+ emulator caches its snapshots in scratch space. To prevent accidental edits to a live target, use the Control Target w/Edits Disabled mode.
+ This is only effective against edits from the UI, and only when all plugins and scripts use the
+ state editing service. This cannot prevent a component from accessing Ghidra's Target API to
+ modify a target. Nor can it prevent edits via the connected debugger's command-line
+ interpreter. The following components all use the service: Dynamic Listing, Memory (Dynamic
+ Bytes), Registers, and Watches.Debugger: Control and Machine State
+
+ Actions
+
+ Edit Mode
+
+
+
+
+ Control Target w/Edits Disabled - This
+ presents actions for controlling the live target but rejects all machine-state edits.
Control Target - The default, this presents
+ actions for controlling the live target and directs edits to the live target. To accept
+ edits, the UI must be "live and at the present." If the trace has no associated target, i.e.,
+ it is dead; or if the current view is in the past or includes any steps of emulation, i.e.,
+ it is not at the present; then edits are rejected.
Control Trace - This presents actions for
+ navigating trace snapshots. It directs all edits to the trace database. Edits are generally
+ always accepted, and they are applied directly to the trace.
Control Emulator - This presents actions for
+ controlling the integrated emulator. This can be used for interpolating and extrapolating
+ execution from the current snapshot, without affecting the live target. It directs edits to
+ the integrated emulator by generating patch steps and appending them to the emulation
+ schedule. See the Go To Time
+ action. Essentially, the change is applied in the trace's scratch space, leaving the original
+ recording in tact. Due to implementation details, a thread must be selected, even if edits
+ only affect memory. Additionally, the disassembly context register cannot be modified.
Target Control Actions
+
+
+
+ Resume
+
+ Interrupt
+
+ Kill
+
+ Disconnect
+
+ Step Into
+
+ Step Over
+
+ Finish
+
+ Step Repeat Last /
+ Extended
Trace Navigation Actions
+
+
+
+
+ Snapshot Backward
+
+
+ Snapshot Forward
Emulation Actions
+
+
+
+ Resume
+
+ Interrupt
+
+ Step Back
+
+ Step Into
+
+ Skip Over
Recommendations
+
+
The dynamic listing supports editing memory via the Machine State Editing - Plugin and Service. Such edits are performed as usual: Via the The dynamic listing supports editing memory. See Control and Machine State. + Such edits are performed as usual: Via the Patch actions, or by pasting byte strings. These edits may be directed toward a live target, the trace, or the emulator.
diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html index 8db10e4bb6..1021f472cc 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html @@ -44,13 +44,12 @@ failed, the first address in the failed range is displayed with a pink background. Otherwise, up-to-date contents are displayed with the default background color. -The dynamic listing supports editing memory via the Machine State Editing - Plugin and Service. Such edits are performed as usual: Toggling edits and typing into the - editor, or by pasting byte strings. These edits may be directed toward a live target, the - trace, or the emulator. NOTE: Please be wary of hand-typing large edits into the - emulator, since every keystroke may produce a unique scratch snapshot. It is better to paste - such edits instead.
+The dynamic listing supports editing memory. SeeControl and Machine State. + Such edits are performed as usual: Toggling edits and typing into the editor, or by pasting + byte strings. These edits may be directed toward a live target, the trace, or the emulator. + NOTE: Please be wary of hand-typing large edits into the emulator, since every keystroke + may produce a unique scratch snapshot. It is better to paste such edits instead.
Interrupt the current target's execution.
-Allow the current target to resume execution.
@@ -142,7 +142,7 @@Allow the current target to finish the current subroutine, pausing after.
-Perform a target-defined step, often the last (possibly custom or extended) step.
diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html index 71cb6fa711..2d86a9a2d6 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html @@ -46,9 +46,7 @@This toggle is a write protector for machine state. To modify register values, this toggle must be enabled. Edits are directed according the to State Editing Plugin - and Service. Note: Only the raw "Value" column can be edited directly. The "Repr" - column cannot be edited, yet.
+ "help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State + Plugin. Note: Only the raw "Value" column can be edited directly. The "Repr" column + cannot be edited, yet.This plugin controls the modification of machine state. It provides a multi-state action in - the main toolbar for controlling the editing mode of each trace and associated target. It is - backed by a corresponding service plugin which manages the editing modes and dispatches - machine-state edits accordingly. Scripts can also use the service to perform machine-state - edits that behave consistently with the rest of the UI.
- -The plugin provides a single action:
- -This action is available whenever a trace is active. It changes the machine-state editing - mode for the active trace and, if applicable, its associated target. The possible modes - are:
- -Write Target is the default mode, because in most cases, this is the desired behavior. When - the target dies, Write Target essentially means Read Only. For the most part, modifying the - recording itself is discouraged. There are few reasons, perhaps including 1) Hand-generating an - experimental trace; 2) Generating a trace from a script, e.g., importing an event log, - recording an emulated target; 3) Patching in state missed by the original recording. More often - than not, when experimenting with the emulator, the mode should be Write Emulator. Using Write - Trace with the emulator will almost certainly result in issues with cache staleness.
- -Some background and an example: To display emulated machine state, the emulator executes a - specified schedule and writes the resulting state into the trace's scratch space, keyed by the - schedule. Suppose you emulate a step forward but then realize that some state was incorrect, or - you just want to try the same step with an alternative initial state. If you step back then - edit the trace and then repeat the step forward, the UI will simply recall the cached snapshot, - rendering the state change ineffective. Instead, use Write Emulator to edit the state and then - step forward. Because the patch is encoded in the emulation schedule, the UI will not recall - the stale snapshot. Instead the emulator will execute the new schedule and generate a new - scratch snapshot. Furthermore, the original trace recording remains in tact, while the modified - state is stored in scratch space with the schedule explaining where it came from. Still better, - the first scratch snapshot (for the step taken without first modifying the state) also remains - in tact, and the two can be compared.
- -To prevent edits to a live target, use the Read-Only mode. This will prevent most accidental - edits. This is only effective against edits from the UI, and only when all plugins and scripts - use the state editing service. This cannot prevent a component from accessing Ghidra's Target - API to modify a target. Nor can it prevent edits via the connected debugger's command-line - interpreter. The following components all use the service: Dynamic Listing, Memory (Dynamic - Bytes), Registers, and Watches.
- - diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-disabled.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-disabled.png deleted file mode 100644 index 483d165a27..0000000000 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-disabled.png and /dev/null differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-emulator.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-emulator.png deleted file mode 100644 index 72600b472c..0000000000 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-emulator.png and /dev/null differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-target.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-target.png deleted file mode 100644 index c8f0e047ec..0000000000 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-target.png and /dev/null differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-trace.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-trace.png deleted file mode 100644 index c0b120cd82..0000000000 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-trace.png and /dev/null differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html index b35676df36..9f89fd5bcc 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html @@ -74,11 +74,9 @@This toggle is a write protector for machine state. To modify a watch's value, this toggle must be enabled. Edits are directed according the to State Editing Plugin - and Service. Note: Only the raw "Value" column can be edited directly. The "Repr" - column cannot be edited, yet.
+ "help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control and Machine State + Plugin. Note: Only the raw "Value" column can be edited directly. The "Repr" column + cannot be edited, yet.+ * WARNING: This emulator belongs to this service. You may interrupt it, but stepping + * it, or otherwise manipulating it without the service's knowledge can lead to unintended + * consequences. + * + * @return the emulator + */ + @Override + public DebuggerPcodeMachine> emulator() { + return emulator; + } + } + + /** + * A listener for changes in emulator state + */ + interface EmulatorStateListener { + /** + * An emulator is running + * + * @param emu the emulator + */ + void running(CachedEmulator emu); + + /** + * An emulator has stopped + * + * @param emu the emulator + */ + void stopped(CachedEmulator emu); + } + /** * Get the available emulator factories * @@ -53,7 +120,7 @@ public interface DebuggerEmulationService { * the tool, but the config options for each factory to the program/trace. * *
- * TODO: Should there be some opinion service for choosing default configs? Seem overly + * TODO: Should there be some opinion service for choosing default configs? Seems overly * complicated for what it offers. For now, we won't save anything, we'll default to the * (built-in) {@link BytesDebuggerPcodeEmulatorFactory}, and we won't have configuration * options. @@ -97,35 +164,74 @@ public interface DebuggerEmulationService { * Emulate using the trace's "host" platform * * @see #emulate(TracePlatform, TraceSchedule, TaskMonitor) - * @param trace - * @param time - * @param monitor - * @return - * @throws CancelledException + * @param trace the trace containing the initial state + * @param time the time coordinates, including initial snap, steps, and p-code steps + * @param monitor a monitor for cancellation and progress reporting + * @return the snap in the trace's scratch space where the realize state is stored + * @throws CancelledException if the emulation is cancelled */ default long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor) throws CancelledException { return emulate(trace.getPlatformManager().getHostPlatform(), time, monitor); } + /** + * Allow the emulator to "run free" until it is interrupted or encounters an error + * + *
+ * The service may perform some preliminary emulation to realize the machine's initial state. If + * the monitor cancels during preliminary emulation, this method throws a + * {@link CancelledException}. If the monitor cancels the emulation during the run, it is + * treated the same as interruption. The machine state will be written to the trace in a scratch + * snap and the result returned. Note that the machine could be interrupted having only + * partially executed an instruction. Thus, the schedule may specify p-code operations. The + * schedule will place the program counter on the instruction (or p-code op) causing the + * interruption. Thus, except for breakpoints, attempting to step again will interrupt the + * emulator again. + * + * @param platform the trace platform containing the initial state + * @param from a schedule for the machine's initial state + * @param monitor a monitor cancellation + * @param scheduler a thread scheduler for the emulator + * @return the result of emulation + */ + EmulationResult run(TracePlatform platform, TraceSchedule from, TaskMonitor monitor, + Scheduler scheduler) throws CancelledException; + /** * Invoke {@link #emulate(Trace, TraceSchedule, TaskMonitor)} in the background * *
- * This is the preferred means of performing emulation. Because the underlying emulator may
- * request a blocking read from a target, it is important that
- * {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor)} is never called by the
- * Swing thread.
+ * This is the preferred means of performing definite emulation. Because the underlying emulator
+ * may request a blocking read from a target, it is important that
+ * {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor) emulate} is never called
+ * by the Swing thread.
*
* @param platform the trace platform containing the initial state
* @param time the time coordinates, including initial snap, steps, and p-code steps
* @return a future which completes with the result of
- * {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor)}
+ * {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor) emulate}
*/
CompletableFuture
+ * This is the preferred means of performing indefinite emulation, for the same reasons as
+ * {@link #backgroundEmulate(TracePlatform, TraceSchedule) emulate}.
+ *
+ * @param platform the trace platform containing the initial state
+ * @param from a schedule for the machine's initial state
+ * @param scheduler a thread scheduler for the emulator
+ * @return a future which completes with the result of
+ * {@link #run(TracePlatform, TraceSchedule, TaskMonitor, Scheduler) run}.
+ */
+ CompletableFuture
* To guarantee the emulator is present, call {@link #backgroundEmulate(Trace, TraceSchedule)}
@@ -142,4 +248,25 @@ public interface DebuggerEmulationService {
* @return the copied p-code frame
*/
DebuggerPcodeMachine> getCachedEmulator(Trace trace, TraceSchedule time);
+
+ /**
+ * Get the emulators which are current executing
+ *
+ * @return the collection
+ */
+ Collection
* The recorder copies information in one direction; thus, if a trace UI component needs to affect
@@ -205,48 +217,210 @@ public interface TraceRecorder {
*/
void removeListener(TraceRecorderListener listener);
+ /**
+ * Get the target object corresponding to the given trace object
+ *
+ * @param obj the trace object
+ * @return the target object, or null
+ */
+ TargetObject getTargetObject(TraceObject obj);
+
+ /**
+ * Get the trace object corresponding to the given target object
+ *
+ * @param obj the target object
+ * @return the trace object, or null
+ */
+ TraceObject getTraceObject(TargetObject obj);
+
+ /**
+ * Get the target breakpoint location corresponding to the given trace breakpoint
+ *
+ * @param obj the trace breakpoint
+ * @return the target breakpoint location, or null
+ */
TargetBreakpointLocation getTargetBreakpoint(TraceBreakpoint bpt);
+ /**
+ * Get the trace breakpoint corresponding to the given target breakpoint location
+ *
+ * @param obj the target breakpoint location
+ * @return the trace breakpoint, or null
+ */
TraceBreakpoint getTraceBreakpoint(TargetBreakpointLocation bpt);
+ /**
+ * Get the target memory region corresponding to the given trace memory region
+ *
+ * @param obj the trace memory region
+ * @return the target memory region, or null
+ */
TargetMemoryRegion getTargetMemoryRegion(TraceMemoryRegion region);
+ /**
+ * Get the trace memory region corresponding to the given target memory region
+ *
+ * @param obj the target memory region
+ * @return the trace memory region, or null
+ */
TraceMemoryRegion getTraceMemoryRegion(TargetMemoryRegion region);
+ /**
+ * Get the target module corresponding to the given trace module
+ *
+ * @param obj the trace module
+ * @return the target module, or null
+ */
TargetModule getTargetModule(TraceModule module);
+ /**
+ * Get the trace module corresponding to the given target module
+ *
+ * @param obj the target module
+ * @return the trace module, or null
+ */
TraceModule getTraceModule(TargetModule module);
+ /**
+ * Get the target section corresponding to the given trace section
+ *
+ * @param obj the trace section
+ * @return the target section, or null
+ */
TargetSection getTargetSection(TraceSection section);
+ /**
+ * Get the trace section corresponding to the given target section
+ *
+ * @param obj the target section
+ * @return the trace section, or null
+ */
TraceSection getTraceSection(TargetSection section);
+ /**
+ * Get the target thread corresponding to the given trace thread
+ *
+ * @param obj the trace thread
+ * @return the target thread, or null
+ */
TargetThread getTargetThread(TraceThread thread);
+ /**
+ * Get the execution state of the given target thread
+ *
+ * @param thread the target thread
+ * @return the execution state, or null
+ */
TargetExecutionState getTargetThreadState(TargetThread thread);
+ /**
+ * Get the execution state of the given trace thread
+ *
+ * @param thread the trace thread
+ * @return the execution state, or null
+ */
TargetExecutionState getTargetThreadState(TraceThread thread);
+ /**
+ * Get the target register bank for the given trace thread and frame level
+ *
+ *
+ * If the model doesn't provide a bank for every frame, then this should only return non-null
+ * for frame level 0, in which case it should return the bank for the given thread.
+ *
+ * @param thread the thread
+ * @param frameLevel the frame level
+ * @return the bank, or null
+ */
TargetRegisterBank getTargetRegisterBank(TraceThread thread, int frameLevel);
+ /**
+ * Get the trace thread corresponding to the given target thread
+ *
+ * @param obj the target thread
+ * @return the trace thread, or null
+ */
TraceThread getTraceThread(TargetThread thread);
+ /**
+ * Find the trace thread containing the given successor target object
+ *
+ * @param successor the target object
+ * @return the trace thread containing the object, or null
+ */
TraceThread getTraceThreadForSuccessor(TargetObject successor);
+ /**
+ * Get the trace stack frame for the given target stack frame
+ *
+ * @param frame the target stack frame
+ * @return the trace stack frame, or null
+ */
TraceStackFrame getTraceStackFrame(TargetStackFrame frame);
+ /**
+ * Get the trace stack frame containing the given successor target object
+ *
+ * @param successor the target object
+ * @return the trace stack frame containing the object, or null
+ */
TraceStackFrame getTraceStackFrameForSuccessor(TargetObject successor);
+ /**
+ * Get the target stack frame for the given trace thread and frame level
+ *
+ * @param thread the thread
+ * @param frameLevel the frame level
+ * @return the stack frame, or null
+ */
TargetStackFrame getTargetStackFrame(TraceThread thread, int frameLevel);
+ /**
+ * Get all the target's threads that are currently alive
+ *
+ * @return the set of live target threads
+ */
Set
+ * In these and other machine-state-editing integration tests, we use
+ * {@link StateEditingMode#WRITE_EMULATOR} as a stand-in for any mode. We also use
+ * {@link StateEditingMode#READ_ONLY} just to verify the mode is heeded. Other modes may be tested
+ * if bugs crop up in various combinations.
+ */
+public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITest {
+
+ DebuggerListingPlugin listingPlugin;
+ DebuggerStateEditingService editingService;
+ DebuggerEmulationService emulationService;
+ DebuggerControlPlugin controlPlugin;
+
+ List
- * In these and other machine-state-editing integration tests, we use
- * {@link StateEditingMode#WRITE_EMULATOR} as a stand-in for any mode. We also use
- * {@link StateEditingMode#READ_ONLY} just to verify the mode is heeded. Other modes may be tested
- * if bugs crop up in various combinations.
- */
-public class DebuggerStateEditingPluginIntegrationTest extends AbstractGhidraHeadedDebuggerGUITest {
- @Test
- public void testPatchInstructionActionInDynamicListingEmu() throws Throwable {
- DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
- DebuggerDisassemblerPlugin disassemblerPlugin =
- addPlugin(tool, DebuggerDisassemblerPlugin.class);
- DebuggerStateEditingPlugin editingPlugin =
- addPlugin(tool, DebuggerStateEditingPlugin.class);
- DebuggerStateEditingService editingService =
- tool.getService(DebuggerStateEditingService.class);
-
- assertFalse(editingPlugin.actionEditMode.isEnabled());
-
- createAndOpenTrace();
- TraceVariableSnapProgramView view = tb.trace.getProgramView();
- try (UndoableTransaction tid = tb.startTransaction()) {
- tb.getOrAddThread("Threads[0]", 0);
- tb.trace.getMemoryManager()
- .createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
- Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
- // Dynamic Patch Instruction requires existing code unit for context
- tb.addInstruction(0, tb.addr(0x00400123), tb.host);
- }
-
- CodeViewerProvider listingProvider = listingPlugin.getProvider();
- DebuggerDisassemblerPluginTestHelper helper =
- new DebuggerDisassemblerPluginTestHelper(disassemblerPlugin, listingProvider, view);
-
- traceManager.activateTrace(tb.trace);
- Swing.runNow(
- () -> listingProvider.goTo(view, new ProgramLocation(view, tb.addr(0x00400123))));
- waitForSwing();
-
- assertTrue(editingPlugin.actionEditMode.isEnabled());
-
- runSwing(() -> editingPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
- assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
- assertFalse(
- helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
-
- runSwing(() -> editingPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
- assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
-
- assertTrue(
- helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
- Instruction ins =
- helper.patchInstructionAt(tb.addr(0x00400123), "imm r0,#0x0", "imm r0,#0x3d2");
- assertEquals(2, ins.getLength());
-
- long snap = traceManager.getCurrent().getViewSnap();
- assertTrue(DBTraceUtils.isScratch(snap));
- byte[] bytes = new byte[2];
- view.getMemory().getBytes(tb.addr(0x00400123), bytes);
- assertArrayEquals(tb.arr(0x30, 0xd2), bytes);
- }
-
- @Test
- public void testPatchDataActionInDynamicListingEmu() throws Throwable {
- DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
- AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
- DebuggerStateEditingPlugin editingPlugin =
- addPlugin(tool, DebuggerStateEditingPlugin.class);
- DebuggerStateEditingService editingService =
- tool.getService(DebuggerStateEditingService.class);
-
- assertFalse(editingPlugin.actionEditMode.isEnabled());
-
- createAndOpenTrace();
- TraceVariableSnapProgramView view = tb.trace.getProgramView();
- try (UndoableTransaction tid = tb.startTransaction()) {
- tb.getOrAddThread("Threads[0]", 0);
- tb.trace.getMemoryManager()
- .createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
- Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
- tb.trace.getCodeManager()
- .definedData()
- .create(Range.atLeast(0L), tb.addr(0x00400123), ShortDataType.dataType);
- }
-
- CodeViewerProvider listingProvider = listingPlugin.getProvider();
- AssemblerPluginTestHelper helper =
- new AssemblerPluginTestHelper(assemblerPlugin, listingProvider, view);
-
- traceManager.activateTrace(tb.trace);
- waitForSwing();
-
- assertTrue(editingPlugin.actionEditMode.isEnabled());
-
- runSwing(() -> editingPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
- assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
- assertFalse(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
-
- runSwing(() -> editingPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
- assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
-
- goTo(listingProvider.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
- assertTrue(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
-
- /**
- * TODO: There's a bug in the trace forking: Data units are not replaced when bytes changed.
- * Thus, we'll make no assertions about the data unit.
- */
- /*Data data =*/ helper.patchDataAt(tb.addr(0x00400123), "0h", "5h");
- // assertEquals(2, data.getLength());
-
- long snap = traceManager.getCurrent().getViewSnap();
- assertTrue(DBTraceUtils.isScratch(snap));
- byte[] bytes = new byte[2];
- view.getMemory().getBytes(tb.addr(0x00400123), bytes);
- assertArrayEquals(tb.arr(0, 5), bytes);
- }
-
- @Test
- public void testPasteActionInDynamicListingEmu() throws Throwable {
- DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
- DebuggerStateEditingPlugin editingPlugin =
- addPlugin(tool, DebuggerStateEditingPlugin.class);
- addPlugin(tool, ClipboardPlugin.class);
- DebuggerStateEditingService editingService =
- tool.getService(DebuggerStateEditingService.class);
-
- CodeViewerProvider listingProvider = listingPlugin.getProvider();
- DockingActionIf pasteAction = getLocalAction(listingProvider, "Paste");
-
- assertFalse(editingPlugin.actionEditMode.isEnabled());
-
- createAndOpenTrace();
- TraceVariableSnapProgramView view = tb.trace.getProgramView();
- try (UndoableTransaction tid = tb.startTransaction()) {
- tb.getOrAddThread("Threads[0]", 0);
- tb.trace.getMemoryManager()
- .createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
- Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
- }
-
- traceManager.activateTrace(tb.trace);
- waitForSwing();
-
- ActionContext ctx;
-
- assertTrue(editingPlugin.actionEditMode.isEnabled());
-
- runSwing(() -> editingPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
- assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
- ctx = listingProvider.getActionContext(null);
- assertTrue(pasteAction.isAddToPopup(ctx));
- assertFalse(pasteAction.isEnabledForContext(ctx));
-
- runSwing(() -> editingPlugin.actionEditMode
- .setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
- assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
-
- goTo(listingPlugin.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
- ctx = listingProvider.getActionContext(null);
- assertTrue(pasteAction.isAddToPopup(ctx));
- assertFalse(pasteAction.isEnabledForContext(ctx));
-
- Clipboard clipboard = GClipboard.getSystemClipboard();
- clipboard.setContents(new StringSelection("12 34 56 78"), null);
- ctx = listingProvider.getActionContext(null);
- assertTrue(pasteAction.isAddToPopup(ctx));
- assertTrue(pasteAction.isEnabledForContext(ctx));
-
- performAction(pasteAction, listingProvider, false);
- OptionDialog confirm = waitForDialogComponent(OptionDialog.class);
- pressButtonByText(confirm, "Yes");
-
- byte[] bytes = new byte[4];
- waitForPass(noExc(() -> {
- long snap = traceManager.getCurrent().getViewSnap();
- assertTrue(DBTraceUtils.isScratch(snap));
- view.getMemory().getBytes(tb.addr(0x00400123), bytes);
- assertArrayEquals(tb.arr(0x12, 0x34, 0x56, 0x78), bytes);
- }));
- }
-}
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 4b1d3b6ebb..9cbd4d2521 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
@@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.junit.Before;
@@ -35,20 +36,26 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOpinion;
import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlugin;
+import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStaticMappingService;
+import ghidra.pcode.exec.InterruptPcodeExecutionException;
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.listing.InstructionIterator;
+import ghidra.program.model.listing.ProgramContext;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Trace;
+import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
+import ghidra.trace.model.time.schedule.Scheduler;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.task.TaskMonitor;
@@ -335,4 +342,130 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
.getUnsignedValue()
.toString(16));
}
+
+ @Test
+ public void testExecutionBreakpoint() throws Exception {
+ createProgram();
+ intoProject(program);
+ Assembler asm = Assemblers.getAssembler(program);
+ Memory memory = program.getMemory();
+ Address addrText = addr(program, 0x000400000);
+ Register regPC = program.getRegister("pc");
+ Register regR0 = program.getRegister("r0");
+ Register regR1 = program.getRegister("r1");
+ Register regR2 = program.getRegister("r2");
+ Address addrI2;
+ try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
+ MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
+ (byte) 0, TaskMonitor.DUMMY, false);
+ blockText.setExecute(true);
+ InstructionIterator ii = asm.assemble(addrText,
+ "mov r0, r1",
+ "mov r2, r0");
+ ii.next();
+ addrI2 = ii.next().getMinAddress();
+ program.getProgramContext()
+ .setValue(regR1, addrText, addrText, new BigInteger("1234", 16));
+ }
+
+ programManager.openProgram(program);
+ waitForSwing();
+ codeBrowser.goTo(new ProgramLocation(program, addrText));
+ waitForSwing();
+
+ performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
+
+ Trace trace = traceManager.getCurrentTrace();
+ assertNotNull(trace);
+
+ TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
+ TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
+
+ try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) {
+ trace.getBreakpointManager()
+ .addBreakpoint("Breakpoints[0]", Range.atLeast(0L), addrI2, Set.of(thread),
+ Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
+ }
+
+ EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
+ TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread));
+
+ assertEquals(TraceSchedule.parse("0:t0-1"), result.schedule());
+ assertTrue(result.error() instanceof InterruptPcodeExecutionException);
+
+ long scratch = result.snapshot();
+
+ assertEquals(new BigInteger("00400002", 16),
+ regs.getViewValue(scratch, regPC).getUnsignedValue());
+ assertEquals(new BigInteger("1234", 16),
+ regs.getViewValue(scratch, regR0).getUnsignedValue());
+ assertEquals(new BigInteger("1234", 16),
+ regs.getViewValue(scratch, regR1).getUnsignedValue());
+ assertEquals(new BigInteger("0", 16),
+ regs.getViewValue(scratch, regR2).getUnsignedValue());
+ }
+
+ @Test
+ public void testAccessBreakpoint() throws Exception {
+ createProgram();
+ intoProject(program);
+ Assembler asm = Assemblers.getAssembler(program);
+ Memory memory = program.getMemory();
+ Address addrText = addr(program, 0x000400000);
+ Register regPC = program.getRegister("pc");
+ Register regR0 = program.getRegister("r0");
+ Register regR1 = program.getRegister("r1");
+ Register regR2 = program.getRegister("r2");
+ try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
+ MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
+ (byte) 0, TaskMonitor.DUMMY, false);
+ blockText.setExecute(true);
+ asm.assemble(addrText,
+ "store [r0], r1",
+ "load r2, [r0]");
+ ProgramContext ctx = program.getProgramContext();
+ ctx.setValue(regR0, addrText, addrText, new BigInteger("1234", 16));
+ ctx.setValue(regR1, addrText, addrText, new BigInteger("5678", 16));
+ }
+
+ programManager.openProgram(program);
+ waitForSwing();
+ codeBrowser.goTo(new ProgramLocation(program, addrText));
+ waitForSwing();
+
+ performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
+
+ Trace trace = traceManager.getCurrentTrace();
+ assertNotNull(trace);
+
+ TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
+ TraceMemoryManager mem = trace.getMemoryManager();
+ TraceMemorySpace regs = mem.getMemoryRegisterSpace(thread, false);
+
+ try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) {
+ trace.getBreakpointManager()
+ .addBreakpoint("Breakpoints[0]", Range.atLeast(0L), addr(trace, 0x1234),
+ Set.of(thread), Set.of(TraceBreakpointKind.READ), true, "test");
+ }
+
+ EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
+ TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread));
+
+ assertEquals(TraceSchedule.parse("0:t0-1"), result.schedule());
+ assertTrue(result.error() instanceof InterruptPcodeExecutionException);
+
+ long scratch = result.snapshot();
+
+ assertEquals(new BigInteger("00400002", 16),
+ regs.getViewValue(scratch, regPC).getUnsignedValue());
+ assertEquals(new BigInteger("1234", 16),
+ regs.getViewValue(scratch, regR0).getUnsignedValue());
+ assertEquals(new BigInteger("5678", 16),
+ regs.getViewValue(scratch, regR1).getUnsignedValue());
+ byte[] arr = new byte[8];
+ mem.getViewBytes(scratch, addr(trace, 0x1234), ByteBuffer.wrap(arr));
+ assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 0, 0x56, 0x78 }, arr);
+ assertEquals(new BigInteger("0", 16),
+ regs.getViewValue(scratch, regR2).getUnsignedValue());
+ }
}
diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetExecutionStateful.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetExecutionStateful.java
index 2c225cd8de..b9accd2d84 100644
--- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetExecutionStateful.java
+++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetExecutionStateful.java
@@ -37,22 +37,7 @@ public interface TargetExecutionStateful extends TargetObject {
* This may apply, e.g., to a GDB "Inferior," which has no yet been used to launch or attach
* to a process.
*/
- INACTIVE {
- @Override
- public boolean isAlive() {
- return false;
- }
-
- @Override
- public boolean isRunning() {
- return false;
- }
-
- @Override
- public boolean isStopped() {
- return false;
- }
- },
+ INACTIVE(false, false, false),
/**
* The object is alive, but its execution state is unspecified
@@ -64,42 +49,12 @@ public interface TargetExecutionStateful extends TargetObject {
* when all of its threads are stopped. For the clients' sakes, all models should
* implement these conventions internally.
*/
- ALIVE {
- @Override
- public boolean isAlive() {
- return true;
- }
-
- @Override
- public boolean isRunning() {
- return false;
- }
-
- @Override
- public boolean isStopped() {
- return false;
- }
- },
+ ALIVE(true, false, false),
/**
* The object is alive, but not executing
*/
- STOPPED {
- @Override
- public boolean isAlive() {
- return true;
- }
-
- @Override
- public boolean isRunning() {
- return false;
- }
-
- @Override
- public boolean isStopped() {
- return true;
- }
- },
+ STOPPED(true, false, true),
/**
* The object is alive and executing
@@ -109,22 +64,7 @@ public interface TargetExecutionStateful extends TargetObject {
* thread is currently executing, waiting on an event, or scheduled for execution. It does
* not necessarily mean it is executing on a CPU at this exact moment.
*/
- RUNNING {
- @Override
- public boolean isAlive() {
- return true;
- }
-
- @Override
- public boolean isRunning() {
- return true;
- }
-
- @Override
- public boolean isStopped() {
- return false;
- }
- },
+ RUNNING(true, true, false),
/**
* The object is no longer alive
@@ -134,43 +74,44 @@ public interface TargetExecutionStateful extends TargetObject {
* stale handles to objects which may still be queried (e.g., for a process exit code), or
* e.g., a GDB "Inferior," which could be re-used to launch or attach to another process.
*/
- TERMINATED {
- @Override
- public boolean isAlive() {
- return false;
- }
+ TERMINATED(false, false, false);
- @Override
- public boolean isRunning() {
- return false;
- }
+ private final boolean alive;
+ private final boolean running;
+ private final boolean stopped;
- @Override
- public boolean isStopped() {
- return false;
- }
- };
+ private TargetExecutionState(boolean alive, boolean running, boolean stopped) {
+ this.alive = alive;
+ this.running = running;
+ this.stopped = stopped;
+ }
/**
* Check if this state implies the object is alive
*
* @return true if alive
*/
- public abstract boolean isAlive();
+ public boolean isAlive() {
+ return alive;
+ }
/**
* Check if this state implies the object is running
*
* @return true if running
*/
- public abstract boolean isRunning();
+ public boolean isRunning() {
+ return running;
+ }
/**
* Check if this state implies the object is stopped
*
* @return true if stopped
*/
- public abstract boolean isStopped();
+ public boolean isStopped() {
+ return stopped;
+ }
}
/**
diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java
index 4ea91e4ee3..e2e6501f27 100644
--- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java
+++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java
@@ -1050,6 +1050,22 @@ public interface TargetObject extends Comparable
+ * This searches for the conventional stateful object defining this object's execution state. If
+ * such an object does not exist, null is returned. If one does exist, then its execution state
+ * at the given snap is returned. If that state is null, it is assumed
+ * {@link TargetExecutionState#INACTIVE}.
+ *
+ * @param snap the snap
+ * @return the state or null
+ */
+ default TargetExecutionState getExecutionState(long snap) {
+ TraceObject stateful = querySuitableTargetInterface(TargetExecutionStateful.class);
+ if (stateful == null) {
+ return null;
+ }
+ TraceObjectValue stateVal =
+ stateful.getAttribute(snap, TargetExecutionStateful.STATE_ATTRIBUTE_NAME);
+ if (stateVal == null) {
+ return TargetExecutionState.INACTIVE;
+ }
+ return TargetExecutionState.valueOf((String) stateVal.getValue());
+ }
}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java
index 395847e676..597f59fb09 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java
@@ -312,7 +312,7 @@ public class PatchStep implements Step {
}
@Override
- public
+ * It is possible for the machine to be interrupted mid-instruction. If this is the case,
+ * the trace schedule will indicate the p-code steps taken.
+ *
+ * @return the schedule
+ */
+ public TraceSchedule schedule();
+
+ /**
+ * Get the error that interrupted execution
+ *
+ *
+ * Ideally, this is a {@link InterruptPcodeExecutionException}, indicating a breakpoint
+ * trapped the emulator, but it could be a number of things:
+ *
+ *
+ * This method will drop p-code steps from injections, including those from execution
+ * breakpoints. The goal is to ensure that the returned schedule can be used to recover the same
+ * state on a machine without injections. Unfortunately, injections which modify the machine
+ * state, other than unique variables, will defeat that goal.
+ *
+ * @param trace the trace whose threads to schedule
+ * @param eventThread the first thread to schedule if the scheduler doesn't specify
+ * @param machine the machine to run
+ * @param monitor a monitor for cancellation
+ * @return the result of execution
+ */
+ default RunResult run(Trace trace, TraceThread eventThread, PcodeMachine> machine,
+ TaskMonitor monitor) {
+ TraceThreadManager tm = trace.getThreadManager();
+ TraceSchedule completedSteps = TraceSchedule.snap(0);
+ PcodeThread> emuThread = null;
+ int completedTicks = 0;
+ try {
+ while (true) {
+ TickStep slice = nextSlice(trace);
+ eventThread = slice.getThread(tm, eventThread);
+ emuThread = machine.getThread(eventThread.getPath(), true);
+ for (int i = 0; i < slice.tickCount; i++) {
+ monitor.checkCanceled();
+ emuThread.stepInstruction();
+ completedTicks++;
+ }
+ completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
+ completedTicks = 0;
+ }
+ }
+ catch (PcodeExecutionException e) {
+ completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
+ if (emuThread.getInstruction() == null) {
+ return new RecordRunResult(completedSteps, e);
+ }
+ PcodeFrame frame = e.getFrame();
+ // Rewind one so stepping retries the op causing the error
+ int count = frame.count() - 1;
+ if (frame == null || count == 0) {
+ // If we've decoded, but could execute the first op, just drop the p-code steps
+ return new RecordRunResult(completedSteps, e);
+ }
+ // The +1 accounts for the decode step
+ return new RecordRunResult(
+ completedSteps.steppedPcodeForward(eventThread, count + 1), e);
+ }
+ catch (CancelledException e) {
+ return new RecordRunResult(
+ completedSteps.steppedForward(eventThread, completedTicks), e);
+ }
+ }
+}
diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java
index 5d37590a55..d35b7ad5c0 100644
--- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java
+++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java
@@ -384,8 +384,8 @@ public class Sequence implements Comparable
+ * This operation cannot be used to append instruction steps after p-code steps. Thus, if this
+ * schedule contains any p-code steps and {@code} next has instruction steps, an error will be
+ *
+ * @param next the schedule to append. Its snap is ignored.
+ * @return the complete schedule
+ * @throws IllegalArgumentException if the result would have instruction steps following p-code
+ * steps
+ */
+ public TraceSchedule advanced(TraceSchedule next) {
+ if (this.pSteps.isNop()) {
+ Sequence ticks = this.steps.clone();
+ ticks.advance(next.steps);
+ return new TraceSchedule(this.snap, ticks, next.pSteps.clone());
+ }
+ else if (next.steps.isNop()) {
+ Sequence pTicks = this.steps.clone();
+ pTicks.advance(next.pSteps);
+ return new TraceSchedule(this.snap, this.steps.clone(), pTicks);
+ }
+ throw new IllegalArgumentException("Cannot have instructions steps following p-code steps");
+ }
+
/**
* Get the threads involved in the schedule
*
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java
index 24e9a181da..286aaeed24 100644
--- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java
@@ -20,7 +20,7 @@ import java.util.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
-import ghidra.program.model.address.Address;
+import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.util.classfinder.ClassSearcher;
@@ -70,7 +70,10 @@ public abstract class AbstractPcodeMachine
* This will attempt to compile the given source against this machine's userop library and then
- * will inject it at the given address. The resulting p-code replaces that which would
- * be executed by decoding the instruction at the given address. The means the machine will not
+ * inject it at the given address. The resulting p-code replaces that which would be
+ * executed by decoding the instruction at the given address. The means the machine will not
* decode, nor advance its counter, unless the Sleigh causes it. In most cases, the Sleigh will
* call {@link PcodeEmulationLibrary#emu_exec_decoded()} to cause the machine to decode and
* execute the overridden instruction.
@@ -141,6 +191,11 @@ public interface PcodeMachine
+ * No synchronization is provided on the internal injection storage. Clients should ensure the
+ * machine is not executing when injecting p-code. Additionally, the client must ensure only one
+ * thread is injecting p-code to the machine at a time.
+ *
* @param address the address to inject at
* @param source the Sleigh source to compile and inject
*/
@@ -155,19 +210,65 @@ public interface PcodeMachine
+ * This will clear execution breakpoints, but not access breakpoints. See
+ * {@link #clearAccessBreakpoints()}.
*/
void clearAllInjects();
/**
- * Add a (conditional) breakpoint at the given address
+ * Add a conditional execution breakpoint at the given address
*
*
* Breakpoints are implemented at the p-code level using an inject, without modification to the
* emulated image. As such, it cannot coexist with another inject. A client needing to break
* during an inject must use {@link PcodeEmulationLibrary#emu_swi()} in the injected Sleigh.
*
+ *
+ * No synchronization is provided on the internal breakpoint storage. Clients should ensure the
+ * machine is not executing when adding breakpoints. Additionally, the client must ensure only
+ * one thread is adding breakpoints to the machine at a time.
+ *
* @param address the address at which to break
* @param sleighCondition a Sleigh expression which controls the breakpoint
*/
void addBreakpoint(Address address, String sleighCondition);
+
+ /**
+ * Add an access breakpoint over the given range
+ *
+ *
+ * Access breakpoints are implemented out of band, without modification to the emulated image.
+ * The breakpoints are only effective for p-code {@link PcodeOp#LOAD} and {@link PcodeOp#STORE}
+ * operations with concrete offsets. Thus, an operation that refers directly to a memory
+ * address, e.g., a memory-mapped register, will not be trapped. Similarly, access breakpoints
+ * on registers or unique variables will not work. Access to an abstract offset that cannot be
+ * made concrete, i.e., via {@link PcodeArithmetic#toConcrete(Object, Purpose)} cannot be
+ * trapped. To interrupt on direct and/or abstract accesses, consider wrapping the relevant
+ * state and/or overriding {@link PcodeExecutorStatePiece#getVar(Varnode, Reason)} and related.
+ * For accesses to abstract offsets, consider overriding
+ * {@link AbstractPcodeMachine#checkLoad(AddressSpace, Object)} and/or
+ * {@link AbstractPcodeMachine#checkStore(AddressSpace, Object)} instead.
+ *
+ *
+ * A breakpoint's range cannot cross more than one page boundary. Pages are 4096 bytes each.
+ * This allows implementations to optimize checking for breakpoints. If a breakpoint does not
+ * follow this rule, the behavior is undefined. Breakpoints may overlap, but currently no
+ * indication is given as to which breakpoint interrupted emulation.
+ *
+ *
+ * No synchronization is provided on the internal breakpoint storage. Clients should ensure the
+ * machine is not executing when adding breakpoints. Additionally, the client must ensure only
+ * one thread is adding breakpoints to the machine at a time.
+ *
+ * @param range the address range to trap
+ * @param kind the kind of access to trap
+ */
+ void addAccessBreakpoint(AddressRange range, AccessKind kind);
+
+ /**
+ * Remove all access breakpoints from this machine
+ */
+ void clearAccessBreakpoints();
}
diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/SparseAddressRangeMap.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/SparseAddressRangeMap.java
new file mode 100644
index 0000000000..dc20d6bdd9
--- /dev/null
+++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/SparseAddressRangeMap.java
@@ -0,0 +1,118 @@
+/* ###
+ * 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.*;
+import java.util.Map.Entry;
+import java.util.function.Predicate;
+
+import ghidra.program.model.address.*;
+
+public class SparseAddressRangeMap
+ * Contrast this to {@link #index()}, which marks the next op to be executed. This counts the
+ * number of ops executed, which will differ from index when an internal branch is taken.
+ *
+ * @return the count
+ */
+ public int count() {
+ return count;
+ }
+
/**
* The index of the next p-code op to be executed
*
@@ -190,6 +204,7 @@ public class PcodeFrame {
* @return the value of the index before it was advanced
*/
public int advance() {
+ count++;
return index++;
}
@@ -199,6 +214,7 @@ public class PcodeFrame {
* @return the value of the index before it was stepped back
*/
public int stepBack() {
+ count--;
return index--;
}
diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/SparseAddressRangeMapTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/SparseAddressRangeMapTest.java
new file mode 100644
index 0000000000..906a7e84a2
--- /dev/null
+++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/SparseAddressRangeMapTest.java
@@ -0,0 +1,80 @@
+/* ###
+ * 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import ghidra.program.model.address.*;
+
+public class SparseAddressRangeMapTest {
+ AddressSpace space = new GenericAddressSpace("test", 64, AddressSpace.TYPE_RAM, 0);
+
+ Address addr(long off) {
+ return space.getAddress(off);
+ }
+
+ AddressRange range(long min, long max) {
+ return new AddressRangeImpl(addr(min), addr(max));
+ }
+
+ @Test
+ public void testIsEmpty() {
+ SparseAddressRangeMap
+ *
*
*
+ *
+ *
+ * @return the error
+ */
+ public Throwable error();
+ }
+
+ /**
+ * The result of running a machine
+ */
+ record RecordRunResult(TraceSchedule schedule, Throwable error) implements RunResult {
+ }
+
+ /**
+ * Get the next step to schedule
+ *
+ * @return the (instruction-level) thread and tick count
+ */
+ TickStep nextSlice(Trace trace);
+
+ /**
+ * Run a machine according to the given schedule until it is interrupted
+ *
+ *