The dynamic listing is analogous to Ghidra's listing for static
- analysis, but in the dynamic context. That is, it displays memory contents from a target. More
+ analysis, but in the dynamic context. It displays annotated memory contents from a target. More
precisely, it displays recorded memory contents in a trace. In most use cases, that trace is
"at the present," meaning it is the most recent memory from a live target. Multiple listings
can be displayed simultaneously, using the same pattern as many other Ghidra windows. The
"primary" listing is always displayed and generally tracks with the rest of the tool. Any
- listing can be "snapshotted," i.e., duplicated. This is where dynamic listings differ from
- static listings. Static snapshots remain in place; they do not automatically navigate. Dynamic
- snapshots can still be configured to navigate, following the rest of the tool. A common use is
- to configure a "snapshot" to follow the stack pointer. Still, you can disable a listing's
- automatic navigation, so it behaves like a true snapshot. A current limitation is that you
- cannot use snapshots to display different points in time for the same trace.
+ listing can be "snapshotted," i.e., cloned. This is where dynamic listings differ from static
+ listings. Static clones remain in place; they do not automatically navigate. Dynamic clones can
+ still be configured to navigate, following the rest of the tool. A common use is to configure a
+ clone to follow the stack pointer. Still, you can disable a listing's automatic navigation, so
+ it behaves like a true clone. A current limitation is that you cannot use clones to display
+ different points in time for the same trace.
Where applicable, the static listing's location is synchronized to the dynamic listing, and
vice versa. The dynamic listing permits most of the same mark-up as the static listing. Any
@@ -58,6 +58,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: Via the Patch actions, or by pasting byte strings.
+ These edits may be directed toward a live target, the trace, or the emulator.
+
Actions
The listing provides a variety of actions, some for managing and configuring listings, and
@@ -67,7 +73,7 @@
This action is always available in the Window → Debugger
menu. It creates a new listing window with the same configuration as the primary dynamic
- listing. It is equivalent to "snapshotting" the primary dynamic listing.
+ listing. It is equivalent to cloning the primary dynamic listing.
Export Trace View
@@ -78,11 +84,11 @@
Follows Selected Thread
-
This action is only available on snapshot dynamic listings. The primary listing always
- follows the tool's current thread. Disabling this toggle causes the snapshot to remain on its
- own current thread rather than following the tool's. The current thread is used when computing
- a location to navigate to automatically. It is only applicable when "Track Location" is set to
- something other than "Do Not Track."
+
This action is only available on cloned dynamic listings. The primary listing always follows
+ the tool's current thread. Disabling this toggle causes the clone to remain on its own current
+ thread rather than following the tool's. The current thread is used when computing a location
+ to navigate to automatically. It is only applicable when "Track Location" is set to something
+ other than "Do Not Track."
The memory, or dynamic bytes, window is analogous to Ghidra's
- bytes window for static analysis, but in the dynamic context. That is, it displays memory
- contents from a target. More precisely, it displays recorded memory contents in a trace. In
- most use cases, that trace is "at the present," meaning it is the most recent memory from a
- live target. Multiple memory windows can be displayed simultaneously, using the same pattern as
- many other Ghidra windows. The "primary" window is always displayed and generally tracks with
- the rest of the tool. Any window can be "snapshotted," i.e., duplicated. This is where memory
- windows differ from static bytes windows. Static snapshots remain in place; they do not
- automatically navigate. Dynamic snapshots can still be configured to navigate, following the
- rest of the tool. A common use is to configure a "snapshot" to follow the stack pointer. Still,
- you can disable a window's automatic navigation, so it behaves like a true snapshot. A current
- limitation is that you cannot use snapshots to display different points in time for the same
- trace.
+ bytes window for static analysis, but in the dynamic context. It displays memory contents from
+ a target. More precisely, it displays recorded memory contents in a trace. In most use cases,
+ that trace is "at the present," meaning it is the most recent memory from a live target.
+ Multiple memory windows can be displayed simultaneously, using the same pattern as many other
+ Ghidra windows. The "primary" window is always displayed and generally tracks with the rest of
+ the tool. Any window can be "snapshotted," i.e., cloned. This is where memory windows differ
+ from static bytes windows. Static clones remain in place; they do not automatically navigate.
+ Dynamic clones can still be configured to navigate, following the rest of the tool. A common
+ use is to configure a clone to follow the stack pointer. Still, you can disable a window's
+ automatic navigation, so it behaves like a true clone. A current limitation is that you cannot
+ use clones to display different points in time for the same trace.
Because not all memory is recorded, some background coloring is used to indicate the state
of attempted memory reads. Regardless of state, the most-recent contents, as recorded in the
@@ -45,11 +44,13 @@
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.
-
NOTE: Modification of trace byte contents is a work in progress. At the moment, the feature
- is simply disabled. In general, modifications to bytes when the window is "at the present" are
- directed to the target. Otherwise, they simply modify the historical or emulated values stored
- in the trace. A modification at a given time remains in effect, but stale, for all future times
- up to but excluding the time of the next recorded value.
+
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.
Actions
@@ -60,24 +61,24 @@
This action is always available in the Window → Debugger
menu. It creates a new memory window with the same configuration as the primary memory window.
- It is equivalent to "snapshotting" the primary memory window.
+ It is equivalent to cloning the primary memory window.
Follows Selected Thread
-
This action is only available on snapshot memory windows. The primary window always follows
- the tool's current thread. Disabling this toggle causes the snapshot to remain on its own
- current thread rather than following the tool's. The current thread is used when computing a
- location to navigate to automatically. It is only applicable when "Track Location" is set to
- something other than "Do Not Track."
+
This action is only available on cloned memory windows. The primary window always follows
+ the tool's current thread. Disabling this toggle causes the clone to remain on its own current
+ thread rather than following the tool's. The current thread is used when computing a location
+ to navigate to automatically. It is only applicable when "Track Location" is set to something
+ other than "Do Not Track."
Track Location
This action is always available on all memory windows. It configures automatic navigation
for the window. When location tracking is enabled, the window is automatically navigated to an
- address computed from the trace's or target's machine state. The address is also highlighted in
- green. The computed address is affected by the tool's current "coordinates," that is the
- selected thread, frame, and point in time. The options are pluggable, but currently consist
- of:
+ address computed from the trace's or target's machine state. NOTE: This feature is
+ disabled when the edit toggle is on. The address is also highlighted in green. The computed
+ address is affected by the tool's current "coordinates," that is the selected thread, frame,
+ and point in time. The options are pluggable, but currently consist of:
Do Not Track - disables automatic navigation.
@@ -143,8 +144,10 @@
Toggle Editing
-
This action does the same as it does for the static context. NOTE: This feature is currently
- disabled, as it is a work in progress.
+
This action does the same as it does for the static context. Edits may be rejected if the
+ trace's editing mode is set to Read-Only in the tool. NOTE: This toggle also disables
+ automatic navigation in order to prevent the cursor from being moved unexpectedly while typing
+ edits.
Tool Options: Colors
diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/images/DebuggerMemoryBytesPlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/images/DebuggerMemoryBytesPlugin.png
index fcfa2b60a2..f3a01f60b9 100644
Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/images/DebuggerMemoryBytesPlugin.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/images/DebuggerMemoryBytesPlugin.png differ
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 00c9beb5c4..0823dbf8e7 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,10 @@
Value - the value of the register as recorded in the trace. When the value refers to a
valid memory offset, right-clicking the row allows the user to navigate to that offset in a
selected memory space. This field is user modifiable when the Enable Edits toggle is
- on, and the register is not part of contextreg. Changes are sent to the target
- if the trace is live and "at the present." Otherwise, the change is materialized via
- emulation. Values changed by the last event are displayed in editing service
+ reports the register as modifiable. Edits may be directed toward a live target, the trace, or
+ the emulator. Values changed by the last event are displayed in red.
Type - the type of the register as marked up in the trace. There is generally no default
@@ -92,21 +93,17 @@
Enable Edits
-
This toggle is a write protector for target machine state. To modify register values, this
- toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
- the value to be modified on the target. Editing emulated values is permitted, but ity has no
- effect on the target. Editing historical values is not permitted. All edits to non-live trace
- values are performed in emulation. Specifically, it appends a patch command to the current
- emulation schedule. This keeps trace history in tact, and it allows patched emulated states to
- be annotated and recalled later, since they are stored in the trace's scratch space. Note that
- only the raw "Value" column can be edited directory. The "Repr" column cannot be edited,
- yet.
+
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.
Snapshot Window
This button is analogous to the "snapshot" action of other Ghidra windows. It generates a
- copy of this window. The copy will no longer follow the current thread, but it will follow the
- current time.
+ clone of this window. The clone will no longer follow the current thread, but it will follow
+ the current time.
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.
+
+
Actions
+
+
The plugin provides a single action:
+
+
Edit Mode
+
+
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:
+
+
+
Read-Only - Rejects all edits.
+
+
Write Target - The default, this directs edits
+ to the live target. To accept changes, 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.
+
+
Write Trace - Directs all edits to the trace.
+ Edits are generally always accepted, and they are applied directly to the trace.
+
+
Write Emulator - Materializes edits via
+ emulation. Instead of editing the trace, this generates a patch and appends it to the current
+ coordinates' 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.
+
+
+
Recommendations
+
+
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
new file mode 100644
index 0000000000..483d165a27
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-disabled.png 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
new file mode 100644
index 0000000000..72600b472c
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-emulator.png 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
new file mode 100644
index 0000000000..c8f0e047ec
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-target.png 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
new file mode 100644
index 0000000000..c0b120cd82
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-trace.png differ
diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html
index 6f49e8d8ee..a871f1ef1b 100644
--- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html
+++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html
@@ -48,7 +48,9 @@
snapshot. This always applies to "scratch" snapshots produced by emulation, but may also
apply if the stepping schedule between recorded events is somehow known. Typically, it is
just the number of steps of the source snapshot's event thread; however, the notation does
- allow other threads to be stepped, too.
+ allow other threads to be stepped, too. See the Go To Time
+ action.
Description - a user-modifiable description of the snapshot or event. This defaults to
the debugger's description of the event.
Watches refer to expressions which are evaluated each pause in order to monitor the value of
variables in the target machine state. The watch variables are expressed in Sleigh and
evaluated in the current thread's context at the current point in time. If the current trace is
- live and at the present, then the necessary target state is queried and recorded. The watch can
- be assigned a data type so that the raw data is rendered in a meaningful way. When applicable,
- that data type can optionally be applied to the trace database. Some metadata about the watch
- is also given, e.g., the address of the value.
+ live and at the present, then the necessary target state is retrieved and recorded. The watch
+ can be assigned a data type so that the raw data is rendered in a meaningful way. When
+ applicable, that data type can optionally be applied to the trace database. Some metadata about
+ the watch is also given, e.g., the address of the value.
Examples
For those less familiar with Sleigh, here are some example expressions:
-
*:4 (RSP+8): Display 4 bytes of [ram] starting 8 bytes after the offset
- given by register RSP.
+
RSP: Display the value of register RSP.
-
*:4 0x7fff0004:8: Display 4 bytes starting at ram:7fff0004. The extraneous,
- but required, size specifier on constant derefs is a known issue. Just use the target's
- pointer size in bytes.
+
*:4 0x7fff0004:8: Display 4 bytes starting at ram:7fff0004, e.g., to read
+ the int at address 7fff0004. The extraneous but required size specifier on the
+ constant 0x7fff0004 is a known issue. Just use the target's pointer size in bytes — 8
+ in the example.
*:8 RSP: Display 8 bytes of [ram] starting at the offset given by register
- RSP.
+ RSP, e.g., to read a long on the stack.
-
RSP: Display the value of register RSP.
+
*:4 (RSP+8): Display 4 bytes of [ram] starting 8 bytes after the offset
+ given by register RSP, e.g., to read a int on the stack.
+
+
*:4 ((*:8 RSP)+4): Display 4 bytes of [ram] starting 4 bytes after the
+ offset given by reading 8 bytes of [ram] starting at the offset given by register RSP. This
+ might be used to read the second int of an array whose base pointer is on the
+ stack.
Table Columns
@@ -62,9 +68,11 @@
Value - the raw bytes of the watched buffer. If the expression is a register, then this
is its hexadecimal value. This field is user modifiable when the Enable Edits toggle
- is on. Changes are sent to the target if the trace is live and "at the present." Otherwise,
- the change is materialized via emulation. If the value has changed since the last navigation
- event, this cell is rendered in red.
+ is on, and the editing service
+ reports the register as modifiable. Edits may be directed toward a live target, the trace, or
+ the emulator. If the value has changed since the last navigation event, this cell is rendered
+ in red.
Type - the user-modifiable type of the watch. Note the type is not marked up in the
trace. Clicking the Apply Data Type action will apply it to the current trace, if
@@ -135,15 +143,11 @@
Enable Edits
-
This toggle is a write protector for target machine state. To modify a watch's value, this
- toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
- the value to be modified on the target. Editing emulated values is permitted, but it has no
- effect on the target. Editing historical values is not permitted. All edits to non-live trace
- values are performed in emulation. Specifically, it appends a patch command to the current
- emulation schedule. This keeps trace history in tact, and it allows patched emulated states to
- be annotated and recalled later, since they are stored in the trace's scratch space. Note that
- only the raw "Value" column can be edited directly. The "Repr" column cannot be edited,
- yet.
+
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.
Tool Options: Colors
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java
index ddaa613ae9..4e9eccadd2 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java
@@ -28,6 +28,7 @@ import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.database.DBTraceContentHandler;
+import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
@@ -81,9 +82,28 @@ public class DebuggerCoordinates {
null, null, null);
}
+ public static DebuggerCoordinates rawView(TraceProgramView view) {
+ return all(view.getTrace(), null, null, view, TraceSchedule.snap(view.getSnap()), null);
+ }
+
public static DebuggerCoordinates view(TraceProgramView view) {
- return all(view == null ? null : view.getTrace(), null, null, view,
- view == null ? null : TraceSchedule.snap(view.getSnap()), null);
+ if (view == null) {
+ return NOWHERE;
+ }
+ long snap = view.getSnap();
+ if (!DBTraceUtils.isScratch(snap)) {
+ return rawView(view);
+ }
+ Trace trace = view.getTrace();
+ TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(snap, false);
+ if (snapshot == null) {
+ return rawView(view);
+ }
+ TraceSchedule schedule = snapshot.getSchedule();
+ if (schedule == null) {
+ return rawView(view);
+ }
+ return trace(trace).withTime(schedule);
}
public static DebuggerCoordinates snap(long snap) {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java
index 04c96a6e67..980e65edb1 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java
@@ -22,12 +22,15 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.swing.*;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.action.builder.*;
+import docking.menu.ActionState;
import docking.widgets.table.*;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
@@ -47,6 +50,7 @@ import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPlugin;
import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin;
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
+import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
import ghidra.app.services.MarkerService;
import ghidra.async.AsyncUtils;
@@ -162,7 +166,7 @@ public interface DebuggerResources {
// TODO: Draw an icon?
ImageIcon ICON_CAPTURE_SYMBOLS = ResourceManager.loadImage("images/closedFolderLabels.png");
- ImageIcon ICON_LOG_FATAL = ResourceManager.loadImage("images/edit-bomg.png");
+ ImageIcon ICON_LOG_FATAL = ResourceManager.loadImage("images/edit-bomb.png");
ImageIcon ICON_LOG_ERROR = ResourceManager.loadImage("images/dialog-warning_red.png");
ImageIcon ICON_LOG_WARN = ResourceManager.loadImage("images/dialog-warning.png");
@@ -182,6 +186,17 @@ public interface DebuggerResources {
ImageIcon ICON_DIFF_PREV = ResourceManager.loadImage("images/up.png");
ImageIcon ICON_DIFF_NEXT = ResourceManager.loadImage("images/down.png");
+ ImageIcon ICON_EDIT_MODE_READ_ONLY = ResourceManager.loadImage("images/write-disabled.png");
+ ImageIcon ICON_EDIT_MODE_WRITE_TARGET = ResourceManager.loadImage("images/write-target.png");
+ ImageIcon ICON_EDIT_MODE_WRITE_TRACE = ResourceManager.loadImage("images/write-trace.png");
+ ImageIcon ICON_EDIT_MODE_WRITE_EMULATOR =
+ ResourceManager.loadImage("images/write-emulator.png");
+
+ String NAME_EDIT_MODE_READ_ONLY = "Read Only";
+ String NAME_EDIT_MODE_WRITE_TARGET = "Write Target";
+ String NAME_EDIT_MODE_WRITE_TRACE = "Write Trace";
+ String NAME_EDIT_MODE_WRITE_EMULATOR = "Write Emulator";
+
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
String HELP_ANCHOR_PLUGIN = "plugin";
@@ -2034,6 +2049,26 @@ public interface DebuggerResources {
}
}
+ interface EditModeAction {
+ String NAME = "Edit Mode";
+ String DESCRIPTION = "Choose what to edit in dynamic views";
+ String GROUP = GROUP_GENERAL;
+ Icon ICON = StateEditingMode.values()[0].icon;
+ String HELP_ANCHOR = "edit_mode";
+
+ static MultiStateActionBuilder builder(Plugin owner) {
+ String ownerName = owner.getName();
+ return new MultiStateActionBuilder(NAME, ownerName)
+ .description(DESCRIPTION)
+ .toolBarGroup(GROUP)
+ .toolBarIcon(ICON_EDIT_MODE_WRITE_TARGET)
+ .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR))
+ .addStates(Stream.of(StateEditingMode.values())
+ .map(m -> new ActionState<>(m.name, m.icon, m))
+ .collect(Collectors.toList()));
+ }
+ }
+
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
@Override
public String getName() {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java
index 7a064623ab..32270ca5ad 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerTrackLocationTrait.java
@@ -192,7 +192,9 @@ public class DebuggerTrackLocationTrait {
public void setSpec(LocationTrackingSpec spec) {
// TODO: What if action == null?
- action.setCurrentActionStateByUserData(spec);
+ if (action != null) {
+ action.setCurrentActionStateByUserData(spec);
+ }
}
public LocationTrackingSpec getSpec() {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java
index 3dcf0ca4b5..39f44e02bb 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPlugin.java
@@ -24,22 +24,21 @@ import ghidra.app.services.DebuggerModelService;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
-@PluginInfo( //
- shortDescription = "Debugger breakpoints manager", //
- description = "GUI to manage breakpoints", //
- category = PluginCategoryNames.DEBUGGER, //
- packageName = DebuggerPluginPackage.NAME, //
- status = PluginStatus.RELEASED, //
- servicesRequired = { //
- DebuggerLogicalBreakpointService.class, //
- DebuggerModelService.class, //
+@PluginInfo(
+ shortDescription = "Debugger breakpoints manager",
+ description = "GUI to manage breakpoints",
+ category = PluginCategoryNames.DEBUGGER,
+ packageName = DebuggerPluginPackage.NAME,
+ status = PluginStatus.RELEASED,
+ servicesRequired = {
+ DebuggerLogicalBreakpointService.class,
+ DebuggerModelService.class,
},
eventsConsumed = {
- TraceOpenedPluginEvent.class, //
- TraceClosedPluginEvent.class, //
- TraceActivatedPluginEvent.class, //
- } //
-)
+ TraceOpenedPluginEvent.class,
+ TraceClosedPluginEvent.class,
+ TraceActivatedPluginEvent.class,
+ })
public class DebuggerBreakpointsPlugin extends AbstractDebuggerPlugin {
protected DebuggerBreakpointsProvider provider;
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/editing/DebuggerStateEditingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/editing/DebuggerStateEditingPlugin.java
new file mode 100644
index 0000000000..97db748681
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/editing/DebuggerStateEditingPlugin.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.app.plugin.core.debug.gui.editing;
+
+import docking.menu.ActionState;
+import docking.menu.MultiStateDockingAction;
+import docking.widgets.EventTrigger;
+import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.app.plugin.core.debug.*;
+import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
+import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
+import ghidra.app.plugin.core.debug.gui.DebuggerResources.EditModeAction;
+import ghidra.app.services.DebuggerStateEditingService;
+import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
+import ghidra.app.services.DebuggerStateEditingService.StateEditingModeChangeListener;
+import ghidra.framework.plugintool.*;
+import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
+import ghidra.framework.plugintool.util.PluginStatus;
+import ghidra.trace.model.Trace;
+
+@PluginInfo(
+ shortDescription = "Debugger machine-state Editing GUI",
+ description = "GUI to edit target, trace, and/or emulation machine state",
+ category = PluginCategoryNames.DEBUGGER,
+ packageName = DebuggerPluginPackage.NAME,
+ status = PluginStatus.RELEASED,
+ eventsConsumed = {
+ TraceActivatedPluginEvent.class,
+ TraceClosedPluginEvent.class,
+ },
+ servicesRequired = {
+ DebuggerStateEditingService.class,
+ })
+public class DebuggerStateEditingPlugin extends AbstractDebuggerPlugin {
+
+ private final StateEditingModeChangeListener listenerForModeChanges = this::modeChanged;
+
+ protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
+
+ protected MultiStateDockingAction actionEditMode;
+
+ // @AutoServiceConsumed // via method
+ private DebuggerStateEditingService editingService;
+
+ public DebuggerStateEditingPlugin(PluginTool tool) {
+ super(tool);
+
+ createActions();
+ }
+
+ protected void createActions() {
+ actionEditMode = EditModeAction.builder(this)
+ .enabled(false)
+ .enabledWhen(c -> current.getTrace() != null)
+ .onActionStateChanged(this::activateEditMode)
+ .buildAndInstall(tool);
+ }
+
+ protected void activateEditMode(ActionState state, EventTrigger trigger) {
+ if (current.getTrace() == null) {
+ return;
+ }
+ editingService.setCurrentMode(current.getTrace(), state.getUserData());
+ // TODO: Limit selectable modes?
+ // No sense showing Write Target, if the trace can never be live, again....
+ }
+
+ private void modeChanged(Trace trace, StateEditingMode mode) {
+ if (current.getTrace() == trace) {
+ refreshActionMode();
+ }
+ }
+
+ protected void coordinatesActivated(DebuggerCoordinates coords) {
+ current = coords;
+ refreshActionMode();
+ // tool.contextChanged(null);
+ }
+
+ private StateEditingMode computeCurrentEditingMode() {
+ if (editingService == null) {
+ return StateEditingMode.READ_ONLY;
+ }
+ if (current.getTrace() == null) {
+ return StateEditingMode.READ_ONLY;
+ }
+ return editingService.getCurrentMode(current.getTrace());
+ }
+
+ private void refreshActionMode() {
+ actionEditMode.setCurrentActionStateByUserData(computeCurrentEditingMode());
+ }
+
+ protected void traceClosed(Trace trace) {
+ if (current.getTrace() == trace) {
+ current = DebuggerCoordinates.NOWHERE;
+ }
+ refreshActionMode();
+ // tool.contextChanged(null);
+ }
+
+ @AutoServiceConsumed
+ protected void setEditingService(DebuggerStateEditingService editingService) {
+ if (this.editingService != null) {
+ this.editingService.removeModeChangeListener(listenerForModeChanges);
+ }
+ this.editingService = editingService;
+ if (this.editingService != null) {
+ this.editingService.addModeChangeListener(listenerForModeChanges);
+ }
+ refreshActionMode();
+ }
+
+ @Override
+ public void processEvent(PluginEvent event) {
+ super.processEvent(event);
+ if (event instanceof TraceActivatedPluginEvent) {
+ TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
+ coordinatesActivated(ev.getActiveCoordinates());
+ }
+ else if (event instanceof TraceClosedPluginEvent) {
+ TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
+ traceClosed(ev.getTrace());
+ }
+ }
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
index ab43439db9..265bd1042f 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java
@@ -19,9 +19,6 @@ import static ghidra.app.plugin.core.debug.gui.DebuggerResources.ICON_REGISTER_M
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS;
import java.awt.Color;
-import java.awt.datatransfer.Transferable;
-import java.awt.datatransfer.UnsupportedFlavorException;
-import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.*;
import java.util.stream.Collectors;
@@ -41,7 +38,6 @@ import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.nav.ListingPanelContainer;
-import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
@@ -56,6 +52,7 @@ import ghidra.app.plugin.core.marker.MarkerMarginProvider;
import ghidra.app.plugin.core.marker.MarkerOverviewProvider;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
+import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.model.DomainFile;
@@ -223,6 +220,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
@AutoServiceConsumed
+ private DebuggerStateEditingService editingService;
+ @AutoServiceConsumed
private ProgramManager programManager;
@AutoServiceConsumed
private FileImporterService importerService;
@@ -351,7 +350,15 @@ public class DebuggerListingProvider extends CodeViewerProvider {
@Override
public boolean isReadOnly() {
- return current.isAliveAndPresent();
+ if (editingService == null) {
+ return true;
+ }
+ Trace trace = current.getTrace();
+ if (trace == null) {
+ return true;
+ }
+ StateEditingMode mode = editingService.getCurrentMode(trace);
+ return !mode.canEdit(current);
}
@Override
@@ -429,28 +436,6 @@ public class DebuggerListingProvider extends CodeViewerProvider {
super.addToTool();
}
- @Override
- protected CodeBrowserClipboardProvider newClipboardProvider() {
- /**
- * TODO: Ensure memory writes via paste are properly directed to the target process. In the
- * meantime, just prevent byte pastes altogether. I cannot disable the clipboard altogether,
- * because there are still excellent cases for copying from the dynamic listing, and we
- * should still permit pastes of annotations.
- */
- return new CodeBrowserClipboardProvider(tool, this) {
- @Override
- protected boolean pasteBytes(Transferable pasteData)
- throws UnsupportedFlavorException, IOException {
- return false;
- }
-
- @Override
- protected boolean pasteByteString(String string) {
- return false;
- }
- };
- }
-
protected void updateMarkerServiceColorModel() {
colorModel.removeModel(markerServiceColorModel);
if (markerService != null) {
@@ -825,7 +810,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
if (loc == null) { // Redundant?
return;
}
- if (mappingService == null) {
+ if (mappingService == null) {
return;
}
ProgramLocation mapped = mappingService.getStaticLocationFromDynamic(loc);
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java
index 0eebd902f6..5de29e8d1d 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProvider.java
@@ -23,7 +23,8 @@ import java.util.*;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
-import docking.action.*;
+import docking.action.DockingAction;
+import docking.action.MenuData;
import docking.menu.MultiStateDockingAction;
import docking.widgets.fieldpanel.support.ViewerPosition;
import ghidra.app.plugin.core.byteviewer.*;
@@ -42,6 +43,8 @@ import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.Memory;
+import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace;
@@ -187,6 +190,25 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
setHelpLocation(DebuggerResources.HELP_PROVIDER_MEMORY_BYTES);
}
+ @Override
+ protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) {
+ if (program == null) {
+ return null;
+ }
+ // A bit of work to get it to ignore existing instructions. Let them be clobbered!
+ return new ProgramByteBlockSet(this, program, changeManager) {
+ @Override
+ protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) {
+ return new MemoryByteBlock(program, memory, memBlock) {
+ @Override
+ protected boolean editAllowed(Address addr, long length) {
+ return true;
+ }
+ };
+ }
+ };
+ }
+
/**
* TODO: I'd rather this not be here
*/
@@ -209,6 +231,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
@Override
protected ByteViewerPanel newByteViewerPanel() {
initTraits();
+ // For highlighting, e.g., state, pc
return new DebuggerMemoryBytesPanel(this);
}
@@ -351,6 +374,9 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
}
protected void doGoToTracked() {
+ if (editModeAction.isSelected()) {
+ return;
+ }
ProgramLocation loc = trackingTrait.getTrackedLocation();
if (loc == null) {
return;
@@ -439,26 +465,4 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
newProvider.panel.setViewerPosition(vp);
});
}
-
- @Override
- protected ProgramByteBlockSet newByteBlockSet(ByteBlockChangeManager changeManager) {
- if (program == null) {
- return null;
- }
- return new WritesTargetProgramByteBlockSet(this, program, changeManager);
- }
-
- @Override
- public void addLocalAction(DockingActionIf action) {
- /**
- * TODO This is a terrible hack, but it's temporary. We do not yet support writing target
- * memory from the bytes provider. Once we do, we should obviously take this hack out. I
- * don't think we'll forget, because the only way to get the write toggle button back is to
- * delete this override.
- */
- if (action == editModeAction) {
- return;
- }
- super.addLocalAction(action);
- }
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetMemoryByteBlock.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetMemoryByteBlock.java
deleted file mode 100644
index 5251d1a4b6..0000000000
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetMemoryByteBlock.java
+++ /dev/null
@@ -1,179 +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.gui.memory;
-
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-import ghidra.app.plugin.core.byteviewer.MemoryByteBlock;
-import ghidra.app.plugin.core.format.ByteBlockAccessException;
-import ghidra.app.services.TraceRecorder;
-import ghidra.program.model.address.Address;
-import ghidra.program.model.listing.Program;
-import ghidra.program.model.mem.Memory;
-import ghidra.program.model.mem.MemoryBlock;
-
-/**
- * An extension of MemoryByteBlock that redirects applicable writes to a debug target
- */
-public class WritesTargetMemoryByteBlock extends MemoryByteBlock {
- protected final WritesTargetProgramByteBlockSet blockSet;
-
- /**
- * Constructor.
- *
- * @param blockSet the containing block set
- * @param program the trace program view for the block
- * @param memory the view's memory
- * @param block the view's memory block
- */
- public WritesTargetMemoryByteBlock(WritesTargetProgramByteBlockSet blockSet, Program program,
- Memory memory, MemoryBlock block) {
- super(program, memory, block);
- this.blockSet = blockSet;
- }
-
- /**
- * Check writes should be redirected, based on the provider's coordinates.
- *
- *
- * Note that redirecting the write prevents the edit from being written (dirtectly) into the
- * trace. If the edit is successful, the trace recorder will record it to the trace.
- *
- * @return true to redirect
- */
- protected boolean shouldWriteTarget() {
- return blockSet.provider.current.isAliveAndPresent();
- }
-
- /**
- * Get the recorder.
- *
- *
- * This should only be used for redirected writes. If we're not live, this will return null.
- *
- * @return the recorder
- */
- protected TraceRecorder getTraceRecorder() {
- return blockSet.provider.current.getRecorder();
- }
-
- @Override
- public void setByte(BigInteger index, byte value)
- throws ByteBlockAccessException {
- if (!shouldWriteTarget()) {
- super.setByte(index, value);
- return;
- }
- Address addr = getAddress(index);
- writeTargetByte(addr, value);
- }
-
- @Override
- public void setInt(BigInteger index, int value)
- throws ByteBlockAccessException {
- if (!shouldWriteTarget()) {
- super.setInt(index, value);
- return;
- }
- Address addr = getAddress(index);
- writeTargetInt(addr, value, isBigEndian());
- }
-
- @Override
- public void setLong(BigInteger index, long value)
- throws ByteBlockAccessException {
- if (!shouldWriteTarget()) {
- super.setLong(index, value);
- return;
- }
- Address addr = getAddress(index);
- writeTargetLong(addr, value, isBigEndian());
- }
-
- /**
- * Write an array of bytes to the target's memory.
- *
- * @param addr the starting address
- * @param data the data to write, prepared in correct endianness
- */
- public void writeTarget(Address addr, byte[] data) {
- TraceRecorder recorder = getTraceRecorder();
- recorder.writeProcessMemory(addr, data);
- }
-
- /**
- * Allocate a buffer for encoding values into bytes.
- *
- * @param size the number of bytes to allocate
- * @param bigEndian true to order the buffer in {@link ByteOrder#BIG_ENDIAN}.
- * @return the buffer, allocated and configured.
- */
- protected ByteBuffer newBuffer(int size, boolean bigEndian) {
- ByteBuffer buf = ByteBuffer.allocate(size);
- buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
- return buf;
- }
-
- /**
- * Write a single byte to the target.
- *
- *
- * Endianness is meaningless
- *
- * @param addr the address
- * @param value the byte
- */
- public void writeTargetByte(Address addr, byte value) {
- writeTarget(addr, new byte[] { value });
- }
-
- /**
- * Write a single int to the target
- *
- * @param addr the minimum address to modify
- * @param value the integer
- * @param bigEndian true for big endian, false for little
- */
- public void writeTargetInt(Address addr, int value, boolean bigEndian) {
- ByteBuffer buf = newBuffer(Integer.BYTES, bigEndian);
- buf.putInt(value);
- writeTarget(addr, buf.array());
- }
-
- /**
- * Write a single long to the target
- *
- * @param addr the minimum address to modify
- * @param value the long
- * @param bigEndian true for big endian, false for little
- */
- public void writeTargetLong(Address addr, long value, boolean bigEndian) {
- ByteBuffer buf = newBuffer(Long.BYTES, bigEndian);
- buf.putLong(value);
- writeTarget(addr, buf.array());
- }
-
- @Override
- protected boolean editAllowed(Address addr, long length) {
- /**
- * Traces are much more permissive when it comes to writes. The instruction will just get
- * clobbered, from this time forward.
- */
- return true;
- }
-}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetProgramByteBlockSet.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetProgramByteBlockSet.java
deleted file mode 100644
index 9c717cd1de..0000000000
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/WritesTargetProgramByteBlockSet.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.app.plugin.core.debug.gui.memory;
-
-import ghidra.app.plugin.core.byteviewer.*;
-import ghidra.program.model.listing.Program;
-import ghidra.program.model.mem.Memory;
-import ghidra.program.model.mem.MemoryBlock;
-
-public class WritesTargetProgramByteBlockSet extends ProgramByteBlockSet {
- protected final DebuggerMemoryBytesProvider provider;
-
- public WritesTargetProgramByteBlockSet(DebuggerMemoryBytesProvider provider,
- Program program, ByteBlockChangeManager bbcm) {
- super(provider, program, bbcm);
- this.provider = provider;
- }
-
- @Override
- protected MemoryByteBlock newMemoryByteBlock(Memory memory, MemoryBlock memBlock) {
- return new WritesTargetMemoryByteBlock(this, program, memory, memBlock);
- }
-}
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 ee6425e57f..d6674076c4 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
@@ -897,7 +897,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
return;
}
doLoadPcodeFrameFromEmulator(emulationService.getCachedEmulator(trace, time));
- }, SwingExecutorService.INSTANCE);
+ }, SwingExecutorService.LATER);
}
protected void doLoadPcodeFrameFromEmulator(DebuggerTracePcodeEmulator emu) {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java
index e99824b765..1f0a5500ea 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java
@@ -46,6 +46,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerProvider;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.app.services.*;
+import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.async.AsyncLazyValue;
import ghidra.async.AsyncUtils;
import ghidra.base.widgets.table.DataTypeTableCellEditor;
@@ -72,7 +73,6 @@ import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
-import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.*;
import ghidra.util.Msg;
import ghidra.util.Swing;
@@ -388,6 +388,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
@AutoServiceConsumed
private DebuggerListingService listingService;
@AutoServiceConsumed
+ private DebuggerStateEditingService editingService;
+ @AutoServiceConsumed
private MarkerService markerService; // TODO: Mark address types (separate plugin?)
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@@ -758,37 +760,15 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
}
- boolean canWriteTarget() {
- if (!current.isAliveAndPresent()) {
- return false;
- }
- TraceRecorder recorder = current.getRecorder();
- TargetRegisterBank targetRegs =
- recorder.getTargetRegisterBank(current.getThread(), current.getFrame());
- if (targetRegs == null) {
- return false;
- }
- return true;
- }
-
boolean canWriteRegister(Register register) {
if (!isEditsEnabled()) {
return false;
}
- if (register.isProcessorContext()) {
- return false; // TODO: Limitation from using Sleigh for patching
- }
- return true;
- }
-
- boolean canWriteTargetRegister(Register register) {
- if (!isEditsEnabled()) {
+ if (editingService == null) {
return false;
}
- if (!canWriteTarget()) {
- return false;
- }
- return current.getRecorder().isRegisterOnTarget(current.getThread(), register);
+ StateEditor editor = editingService.createStateEditor(current);
+ return editor.isRegisterEditable(register);
}
BigInteger getRegisterValue(Register register) {
@@ -804,37 +784,40 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
void writeRegisterValue(RegisterValue rv) {
- if (canWriteTargetRegister(rv.getRegister())) {
- rv = combineWithTraceBaseRegisterValue(rv);
- CompletableFuture future = current.getRecorder()
- .writeThreadRegisters(current.getThread(), current.getFrame(),
- Map.of(rv.getRegister(), rv));
- future.exceptionally(ex -> {
- ex = AsyncUtils.unwrapThrowable(ex);
- if (ex instanceof DebuggerModelAccessException) {
- Msg.error(this, "Could not write target register", ex);
- plugin.getTool()
- .setStatusInfo("Could not write target register: " + ex.getMessage());
- }
- else {
- Msg.showError(this, getComponent(), "Edit Register",
- "Could not write target register", ex);
- }
- return null;
- });
+ if (editingService == null) {
+ Msg.showError(this, getComponent(), "Edit Register", "No editing service.");
+ return;
+ }
+ StateEditor editor = editingService.createStateEditor(current);
+ if (!editor.isRegisterEditable(rv.getRegister())) {
+ rv = combineWithTraceBaseRegisterValue(rv);
+ }
+ if (!editor.isRegisterEditable(rv.getRegister())) {
+ Msg.showError(this, getComponent(), "Edit Register",
+ "Neither the register nor its base can be edited.");
return;
}
- TraceSchedule time = current.getTime().patched(current.getThread(), generateSleigh(rv));
- traceManager.activateTime(time);
- }
- protected String generateSleigh(RegisterValue rv) {
- return String.format("%s=0x%s", rv.getRegister(), rv.getUnsignedValue().toString(16));
+ CompletableFuture future = editor.setRegister(rv);
+ future.exceptionally(ex -> {
+ ex = AsyncUtils.unwrapThrowable(ex);
+ if (ex instanceof DebuggerModelAccessException) {
+ Msg.error(this, "Could not write target register", ex);
+ plugin.getTool()
+ .setStatusInfo("Could not write target register: " + ex.getMessage());
+ }
+ else {
+ Msg.showError(this, getComponent(), "Edit Register",
+ "Could not write target register", ex);
+ }
+ return null;
+ });
+ return;
}
private RegisterValue combineWithTraceBaseRegisterValue(RegisterValue rv) {
TraceMemoryRegisterSpace regs = getRegisterMemorySpace(false);
- long snap = current.getSnap();
+ long snap = current.getViewSnap();
return TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, snap, regs, true);
}
@@ -847,7 +830,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
try (UndoableTransaction tid =
UndoableTransaction.start(current.getTrace(), "Edit Register Type", false)) {
TraceCodeRegisterSpace space = getRegisterMemorySpace(true).getCodeSpace(true);
- long snap = current.getSnap();
+ long snap = current.getViewSnap();
space.definedUnits().clear(Range.closed(snap, snap), register, TaskMonitor.DUMMY);
if (dataType != null) {
space.definedData().create(Range.atLeast(snap), register, dataType);
@@ -864,7 +847,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (space == null) {
return null;
}
- long snap = current.getSnap();
+ long snap = current.getViewSnap();
return space.definedData().getForRegister(snap, register);
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java
index d1c8f4e5df..1a5c098afc 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java
@@ -253,6 +253,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager; // For goto time (emu mods)
@AutoServiceConsumed
+ protected DebuggerStateEditingService editingService;
+ @AutoServiceConsumed
private DebuggerStaticMappingService mappingService; // For listing action
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java
index ca346ac0d3..e55da5b827 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java
@@ -24,6 +24,8 @@ import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.DataTypeManagerService;
+import ghidra.app.services.DebuggerStateEditingService;
+import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.docking.settings.SettingsImpl;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
@@ -32,14 +34,12 @@ import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Language;
-import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.ByteMemBufferImpl;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
-import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.*;
public class WatchRow {
@@ -142,7 +142,7 @@ public class WatchRow {
MemBuffer buffer = new ByteMemBufferImpl(address, value, language.isBigEndian());
return dataType.getRepresentation(buffer, SettingsImpl.NO_SETTINGS, value.length);
}
-
+
protected Object parseAsDataTypeObj() {
if (dataType == null || value == null) {
return null;
@@ -372,9 +372,20 @@ public class WatchRow {
public Object getValueObj() {
return valueObj;
}
-
+
public boolean isValueEditable() {
- return address != null && provider.isEditsEnabled();
+ if (!provider.isEditsEnabled()) {
+ return false;
+ }
+ if (address == null) {
+ return false;
+ }
+ DebuggerStateEditingService editingService = provider.editingService;
+ if (editingService == null) {
+ return false;
+ }
+ StateEditor editor = editingService.createStateEditor(coordinates);
+ return editor.isVariableEditable(address, getValueLength());
}
public void setRawValueString(String valueString) {
@@ -415,55 +426,16 @@ public class WatchRow {
if (bytes.length != value.length) {
throw new IllegalArgumentException("Byte array values must match length of variable");
}
-
- // Allow writes to unmappable registers to fall through to trace
- // However, attempts to write "weird" register addresses is forbidden
- if (coordinates.isAliveAndPresent() && coordinates.getRecorder()
- .isVariableOnTarget(coordinates.getThread(), address, bytes.length)) {
- coordinates.getRecorder()
- .writeVariable(coordinates.getThread(), coordinates.getFrame(), address, bytes)
- .exceptionally(ex -> {
- Msg.showError(this, null, "Write Failed",
- "Could not modify watch value (on target)", ex);
- return null;
- });
- // NB: if successful, recorder will write to trace
- return;
+ DebuggerStateEditingService editingService = provider.editingService;
+ if (editingService == null) {
+ throw new AssertionError("No editing service");
}
-
- /*try (UndoableTransaction tid =
- UndoableTransaction.start(trace, "Write watch at " + address, true)) {
- final TraceMemorySpace space;
- if (address.isRegisterAddress()) {
- space = trace.getMemoryManager()
- .getMemoryRegisterSpace(coordinates.getThread(), coordinates.getFrame(),
- true);
- }
- else {
- space = trace.getMemoryManager().getMemorySpace(address.getAddressSpace(), true);
- }
- space.putBytes(coordinates.getViewSnap(), address, ByteBuffer.wrap(bytes));
- }*/
- TraceSchedule time =
- coordinates.getTime().patched(coordinates.getThread(), generateSleigh(bytes));
- provider.goToTime(time);
- }
-
- protected String generateSleigh(byte[] bytes) {
- BigInteger value = Utils.bytesToBigInteger(bytes, bytes.length,
- trace.getBaseLanguage().isBigEndian(), false);
- if (address.isMemoryAddress()) {
- AddressSpace space = address.getAddressSpace();
- return String.format("*[%s]:%d 0x%s:%d=0x%s",
- space.getName(), bytes.length,
- address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
- value.toString(16));
- }
- Register register = trace.getBaseLanguage().getRegister(address, bytes.length);
- if (register == null) {
- throw new AssertionError("Can only modify memory or register");
- }
- return String.format("%s=0x%s", register, value.toString(16));
+ StateEditor editor = editingService.createStateEditor(coordinates);
+ editor.setVariable(address, bytes).exceptionally(ex -> {
+ Msg.showError(this, null, "Write Failed",
+ "Could not modify watch value (on target)", ex);
+ return null;
+ });
}
public int getValueLength() {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java
new file mode 100644
index 0000000000..aad9720ad1
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java
@@ -0,0 +1,483 @@
+/* ###
+ * 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.editing;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.concurrent.*;
+
+import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.app.plugin.core.debug.*;
+import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
+import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
+import ghidra.app.services.*;
+import ghidra.async.AsyncUtils;
+import ghidra.framework.plugintool.*;
+import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
+import ghidra.framework.plugintool.util.PluginStatus;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressRange;
+import ghidra.program.model.lang.Register;
+import ghidra.program.model.mem.*;
+import ghidra.trace.model.Trace;
+import ghidra.trace.model.Trace.TraceProgramViewListener;
+import ghidra.trace.model.memory.TraceMemoryOperations;
+import ghidra.trace.model.program.TraceProgramView;
+import ghidra.trace.model.program.TraceProgramViewMemory;
+import ghidra.trace.model.thread.TraceThread;
+import ghidra.trace.model.time.schedule.PatchStep;
+import ghidra.trace.model.time.schedule.TraceSchedule;
+import ghidra.trace.util.TraceRegisterUtils;
+import ghidra.util.database.UndoableTransaction;
+import ghidra.util.datastruct.ListenerSet;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+@PluginInfo(
+ shortDescription = "Debugger machine-state editing service plugin",
+ description = "Centralizes machine-state editing across the tool",
+ category = PluginCategoryNames.DEBUGGER,
+ packageName = DebuggerPluginPackage.NAME,
+ status = PluginStatus.RELEASED,
+ eventsConsumed = {
+ TraceOpenedPluginEvent.class,
+ TraceClosedPluginEvent.class,
+ },
+ servicesRequired = {
+ DebuggerTraceManagerService.class,
+ DebuggerEmulationService.class,
+ },
+ servicesProvided = {
+ DebuggerStateEditingService.class,
+ })
+public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
+ implements DebuggerStateEditingService {
+
+ protected abstract class AbstractStateEditor implements StateEditor {
+ @Override
+ public boolean isVariableEditable(Address address, int length) {
+ DebuggerCoordinates coordinates = getCoordinates();
+ Trace trace = coordinates.getTrace();
+
+ switch (getCurrentMode(trace)) {
+ case READ_ONLY:
+ return false;
+ case WRITE_TARGET:
+ return isTargetVariableEditable(coordinates, address, length);
+ case WRITE_TRACE:
+ return isTraceVariableEditable(coordinates, address, length);
+ case WRITE_EMULATOR:
+ return isEmulatorVariableEditable(coordinates, address, length);
+ }
+ throw new AssertionError();
+ }
+
+ protected boolean isTargetVariableEditable(DebuggerCoordinates coordinates, Address address,
+ int length) {
+ if (!coordinates.isAliveAndPresent()) {
+ return false;
+ }
+ TraceRecorder recorder = coordinates.getRecorder();
+ return recorder.isVariableOnTarget(coordinates.getThread(), address, length);
+ }
+
+ protected boolean isTraceVariableEditable(DebuggerCoordinates coordinates, Address address,
+ int length) {
+ return address.isMemoryAddress() || coordinates.getThread() != null;
+ }
+
+ protected boolean isEmulatorVariableEditable(DebuggerCoordinates coordinates,
+ Address address, int length) {
+ if (!isTraceVariableEditable(coordinates, address, length)) {
+ return false;
+ }
+ // TODO: Limitation from using Sleigh for patching
+ Register ctxReg = coordinates.getTrace().getBaseLanguage().getContextBaseRegister();
+ if (ctxReg == Register.NO_CONTEXT) {
+ return true;
+ }
+ AddressRange ctxRange = TraceRegisterUtils.rangeForRegister(ctxReg);
+ if (ctxRange.contains(address)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public CompletableFuture setVariable(Address address, byte[] data) {
+ DebuggerCoordinates coordinates = getCoordinates();
+ Trace trace = coordinates.getTrace();
+
+ switch (getCurrentMode(trace)) {
+ case READ_ONLY:
+ return CompletableFuture
+ .failedFuture(new MemoryAccessException("Read-only mode"));
+ case WRITE_TARGET:
+ return writeTargetVariable(coordinates, address, data);
+ case WRITE_TRACE:
+ return writeTraceVariable(coordinates, address, data);
+ case WRITE_EMULATOR:
+ return writeEmulatorVariable(coordinates, address, data);
+ }
+ throw new AssertionError();
+ }
+
+ protected CompletableFuture writeTargetVariable(DebuggerCoordinates coordinates,
+ Address address, byte[] data) {
+ TraceRecorder recorder = coordinates.getRecorder();
+ if (recorder == null) {
+ return CompletableFuture
+ .failedFuture(new MemoryAccessException("Trace has no live target"));
+ }
+ if (!coordinates.isPresent()) {
+ return CompletableFuture
+ .failedFuture(new MemoryAccessException("View is not the present"));
+ }
+ return recorder.writeVariable(coordinates.getThread(), coordinates.getFrame(), address,
+ data);
+ }
+
+ protected CompletableFuture writeTraceVariable(DebuggerCoordinates coordinates,
+ Address address, byte[] data) {
+ Trace trace = coordinates.getTrace();
+ long snap = coordinates.getViewSnap();
+ TraceMemoryOperations memOrRegs;
+ try (UndoableTransaction txid =
+ UndoableTransaction.start(trace, "Edit Variable", true)) {
+ if (address.isRegisterAddress()) {
+ TraceThread thread = coordinates.getThread();
+ if (thread == null) {
+ throw new IllegalArgumentException("Register edits require a thread.");
+ }
+ memOrRegs = trace.getMemoryManager()
+ .getMemoryRegisterSpace(thread, coordinates.getFrame(),
+ true);
+ }
+ else {
+ memOrRegs = trace.getMemoryManager();
+ }
+ if (memOrRegs.putBytes(snap, address, ByteBuffer.wrap(data)) != data.length) {
+ return CompletableFuture.failedFuture(new MemoryAccessException());
+ }
+ }
+ return AsyncUtils.NIL;
+ }
+
+ protected CompletableFuture writeEmulatorVariable(DebuggerCoordinates coordinates,
+ Address address, byte[] data) {
+ TraceThread thread = coordinates.getThread();
+ if (thread == null) {
+ // TODO: Well, technically, only for register edits
+ throw new IllegalArgumentException("Emulator edits require a thread.");
+ }
+ TraceSchedule time = coordinates.getTime()
+ .patched(thread, PatchStep.generateSleigh(
+ coordinates.getTrace().getBaseLanguage(), address, data));
+
+ DebuggerCoordinates withTime = coordinates.withTime(time);
+ Long found = traceManager.findSnapshot(withTime);
+ // Materialize it on the same thread (even if swing)
+ // It shouldn't take long, since we're only appending one step.
+ if (found == null) {
+ // TODO: Could still do it async on another thread, no?
+ // Not sure it buys anything, since program view will call .get on swing thread
+ try {
+ emulationSerivce.emulate(coordinates.getTrace(), time, TaskMonitor.DUMMY);
+ }
+ catch (CancelledException e) {
+ throw new AssertionError(e);
+ }
+ }
+ return traceManager.activateAndNotify(withTime, false);
+ }
+ }
+
+ protected class DefaultStateEditor extends AbstractStateEditor {
+ private final DebuggerCoordinates coordinates;
+
+ public DefaultStateEditor(DebuggerCoordinates coordinates) {
+ this.coordinates = Objects.requireNonNull(coordinates);
+ }
+
+ @Override
+ public DebuggerStateEditingService getService() {
+ return DebuggerStateEditingServicePlugin.this;
+ }
+
+ @Override
+ public DebuggerCoordinates getCoordinates() {
+ return this.coordinates;
+ }
+ }
+
+ protected class FollowsManagerStateEditor extends AbstractStateEditor {
+ private final Trace trace;
+
+ public FollowsManagerStateEditor(Trace trace) {
+ this.trace = trace;
+ }
+
+ @Override
+ public DebuggerStateEditingService getService() {
+ return DebuggerStateEditingServicePlugin.this;
+ }
+
+ @Override
+ public DebuggerCoordinates getCoordinates() {
+ DebuggerCoordinates current = traceManager.getCurrentFor(trace);
+ if (current != null) {
+ return current;
+ }
+ DebuggerCoordinates resolved =
+ traceManager.resolveCoordinates(DebuggerCoordinates.trace(trace));
+ if (resolved != null) {
+ return resolved;
+ }
+ throw new IllegalStateException(
+ "Trace " + trace + " is not opened in the trace manager.");
+ }
+ }
+
+ public class FollowsViewStateEditor extends AbstractStateEditor
+ implements StateEditingMemoryHandler {
+ private final TraceProgramView view;
+
+ public FollowsViewStateEditor(TraceProgramView view) {
+ this.view = view;
+ }
+
+ @Override
+ public DebuggerStateEditingService getService() {
+ return DebuggerStateEditingServicePlugin.this;
+ }
+
+ @Override
+ public DebuggerCoordinates getCoordinates() {
+ return traceManager.resolveCoordinates(DebuggerCoordinates.view(view));
+ }
+
+ @Override
+ public void clearCache() {
+ // Nothing to do
+ }
+
+ @Override
+ public byte getByte(Address addr) throws MemoryAccessException {
+ ByteBuffer buf = ByteBuffer.allocate(1);
+ view.getTrace().getMemoryManager().getViewBytes(view.getSnap(), addr, buf);
+ return buf.get(0);
+ }
+
+ @Override
+ public int getBytes(Address address, byte[] buffer, int startIndex, int size)
+ throws MemoryAccessException {
+ return view.getTrace()
+ .getMemoryManager()
+ .getViewBytes(view.getSnap(), address,
+ ByteBuffer.wrap(buffer, startIndex, size));
+ }
+
+ @Override
+ public void putByte(Address address, byte value) throws MemoryAccessException {
+ try {
+ setVariable(address, new byte[] { value }).get(1, TimeUnit.SECONDS);
+ }
+ catch (ExecutionException e) {
+ throw new MemoryAccessException("Failed to write " + address + ": " + e.getCause());
+ }
+ catch (TimeoutException | InterruptedException e) {
+ throw new MemoryAccessException("Failed to write " + address + ": " + e);
+ }
+ }
+
+ @Override
+ public int putBytes(Address address, byte[] source, int startIndex, int size)
+ throws MemoryAccessException {
+ try {
+ setVariable(address, Arrays.copyOfRange(source, startIndex, startIndex + size))
+ .get(1, TimeUnit.SECONDS);
+ }
+ catch (ExecutionException e) {
+ throw new MemoryAccessException("Failed to write " + address + ": " + e.getCause());
+ }
+ catch (TimeoutException | InterruptedException e) {
+ throw new MemoryAccessException("Failed to write " + address + ": " + e);
+ }
+ return size;
+ }
+
+ @Override
+ public void addLiveMemoryListener(LiveMemoryListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeLiveMemoryListener(LiveMemoryListener listener) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected class ListenerForEditorInstallation implements TraceProgramViewListener {
+ @Override
+ public void viewCreated(TraceProgramView view) {
+ installMemoryEditor(view);
+ }
+ }
+
+ //@AutoServiceConsumed // via method
+ private DebuggerTraceManagerService traceManager;
+ @AutoServiceConsumed
+ private DebuggerEmulationService emulationSerivce;
+ @AutoServiceConsumed
+ private DebuggerModelService modelService;
+
+ protected final ListenerForEditorInstallation listenerForEditorInstallation =
+ new ListenerForEditorInstallation();
+
+ public DebuggerStateEditingServicePlugin(PluginTool tool) {
+ super(tool);
+ }
+
+ private static final StateEditingMode DEFAULT_MODE = StateEditingMode.WRITE_TARGET;
+
+ private final Map currentModes = new HashMap<>();
+
+ private final ListenerSet listeners =
+ new ListenerSet<>(StateEditingModeChangeListener.class);
+
+ @Override
+ public StateEditingMode getCurrentMode(Trace trace) {
+ synchronized (currentModes) {
+ return currentModes.getOrDefault(Objects.requireNonNull(trace), DEFAULT_MODE);
+ }
+ }
+
+ @Override
+ public void setCurrentMode(Trace trace, StateEditingMode mode) {
+ boolean fire = false;
+ synchronized (currentModes) {
+ StateEditingMode old =
+ currentModes.getOrDefault(Objects.requireNonNull(trace), DEFAULT_MODE);
+ if (mode != old) {
+ currentModes.put(trace, mode);
+ fire = true;
+ }
+ }
+ if (fire) {
+ listeners.fire.modeChanged(trace, mode);
+ tool.contextChanged(null);
+ }
+ }
+
+ @Override
+ public void addModeChangeListener(StateEditingModeChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeModeChangeListener(StateEditingModeChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public StateEditor createStateEditor(DebuggerCoordinates coordinates) {
+ return new DefaultStateEditor(coordinates);
+ }
+
+ @Override
+ public StateEditor createStateEditor(Trace trace) {
+ return new FollowsManagerStateEditor(trace);
+ }
+
+ @Override
+ public StateEditingMemoryHandler createStateEditor(TraceProgramView view) {
+ return new FollowsViewStateEditor(view);
+ }
+
+ protected void installMemoryEditor(TraceProgramView view) {
+ TraceProgramViewMemory memory = view.getMemory();
+ if (memory.getLiveMemoryHandler() != null) {
+ return;
+ }
+ memory.setLiveMemoryHandler(createStateEditor(view));
+ }
+
+ protected void uninstallMemoryEditor(TraceProgramView view) {
+ TraceProgramViewMemory memory = view.getMemory();
+ LiveMemoryHandler handler = memory.getLiveMemoryHandler();
+ if (!(handler instanceof StateEditingMemoryHandler)) {
+ return;
+ }
+ StateEditingMemoryHandler editor = (StateEditingMemoryHandler) handler;
+ if (editor.getService() != this) {
+ return;
+ }
+ memory.setLiveMemoryHandler(null);
+ }
+
+ protected void installAllMemoryEditors(Trace trace) {
+ trace.addProgramViewListener(listenerForEditorInstallation);
+ for (TraceProgramView view : trace.getAllProgramViews()) {
+ installMemoryEditor(view);
+ }
+ }
+
+ protected void installAllMemoryEditors() {
+ if (traceManager == null) {
+ return;
+ }
+
+ for (Trace trace : traceManager.getOpenTraces()) {
+ installAllMemoryEditors(trace);
+ }
+ }
+
+ protected void uninstallAllMemoryEditors(Trace trace) {
+ trace.removeProgramViewListener(listenerForEditorInstallation);
+ for (TraceProgramView view : trace.getAllProgramViews()) {
+ uninstallMemoryEditor(view);
+ }
+ }
+
+ protected void uninstallAllMemoryEditors() {
+ if (traceManager == null) {
+ return;
+ }
+ for (Trace trace : traceManager.getOpenTraces()) {
+ uninstallAllMemoryEditors(trace);
+ }
+ }
+
+ @Override
+ public void processEvent(PluginEvent event) {
+ super.processEvent(event);
+ if (event instanceof TraceOpenedPluginEvent) {
+ TraceOpenedPluginEvent ev = (TraceOpenedPluginEvent) event;
+ installAllMemoryEditors(ev.getTrace());
+ }
+ else if (event instanceof TraceClosedPluginEvent) {
+ TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
+ uninstallAllMemoryEditors(ev.getTrace());
+ }
+ }
+
+ @AutoServiceConsumed
+ private void setTraceManager(DebuggerTraceManagerService traceManager) {
+ uninstallAllMemoryEditors();
+ this.traceManager = traceManager;
+ installAllMemoryEditors();
+ }
+}
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 f6473da40f..0befa08c22 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
@@ -59,7 +59,7 @@ import ghidra.util.task.TaskMonitor;
description = "Manages and cache trace emulation states",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
- status = PluginStatus.UNSTABLE,
+ status = PluginStatus.RELEASED,
eventsConsumed = {
TraceClosedPluginEvent.class,
ProgramActivatedPluginEvent.class,
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java
index dc016500da..ba0de5f927 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerConnectDialog.java
@@ -256,7 +256,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
result.completeAsync(() -> m);
result = null;
}
- }, SwingExecutorService.INSTANCE).exceptionally(e -> {
+ }, SwingExecutorService.LATER).exceptionally(e -> {
e = AsyncUtils.unwrapThrowable(e);
if (!(e instanceof CancellationException)) {
Msg.showError(this, getComponent(), "Could not connect", e);
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java
index 3b2c33b6ff..6aeef5da0d 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java
@@ -280,7 +280,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
"The given launcher path is not a TargetLauncher, according to its schema");
}
return new ValueExpecter(m, launcherPath);
- }, SwingExecutorService.INSTANCE).thenCompose(l -> {
+ }, SwingExecutorService.LATER).thenCompose(l -> {
monitor.incrementProgress(1);
monitor.setMessage("Launching");
TargetLauncher launcher = (TargetLauncher) l;
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
index a1d0b8baf3..4366d1a03f 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java
@@ -32,9 +32,8 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.*;
+import ghidra.async.*;
import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
-import ghidra.async.AsyncReference;
-import ghidra.async.AsyncUtils;
import ghidra.dbg.target.*;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.NotConnectedException;
@@ -60,16 +59,25 @@ import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.exception.*;
import ghidra.util.task.*;
-@PluginInfo(shortDescription = "Debugger Trace View Management Plugin", description = "Manages UI Components, Wrappers, Focus, etc.", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.RELEASED, eventsProduced = {
- TraceActivatedPluginEvent.class,
-}, eventsConsumed = {
- TraceActivatedPluginEvent.class,
- TraceClosedPluginEvent.class,
- ModelObjectFocusedPluginEvent.class,
- TraceRecorderAdvancedPluginEvent.class,
-}, servicesRequired = {}, servicesProvided = {
- DebuggerTraceManagerService.class,
-})
+@PluginInfo(
+ shortDescription = "Debugger Trace View Management Plugin",
+ description = "Manages UI Components, Wrappers, Focus, etc.",
+ category = PluginCategoryNames.DEBUGGER,
+ packageName = DebuggerPluginPackage.NAME,
+ status = PluginStatus.RELEASED,
+ eventsProduced = {
+ TraceActivatedPluginEvent.class,
+ },
+ eventsConsumed = {
+ TraceActivatedPluginEvent.class,
+ TraceClosedPluginEvent.class,
+ ModelObjectFocusedPluginEvent.class,
+ TraceRecorderAdvancedPluginEvent.class,
+ },
+ servicesRequired = {},
+ servicesProvided = {
+ DebuggerTraceManagerService.class,
+ })
public class DebuggerTraceManagerServicePlugin extends Plugin
implements DebuggerTraceManagerService {
private static final AutoConfigState.ClassHandler CONFIG_STATE_HANDLER =
@@ -623,6 +631,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current;
}
+ @Override
+ public DebuggerCoordinates getCurrentFor(Trace trace) {
+ return lastCoordsByTrace.get(trace);
+ }
+
@Override
public Trace getCurrentTrace() {
return current.getTrace();
@@ -654,17 +667,25 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current.getFrame();
}
- @Override
- public CompletableFuture materialize(DebuggerCoordinates coordinates) {
+ public Long findSnapshot(DebuggerCoordinates coordinates) {
if (coordinates.getTime().isSnapOnly()) {
- return CompletableFuture.completedFuture(coordinates.getSnap());
+ return coordinates.getSnap();
}
Collection extends TraceSnapshot> suitable = coordinates.getTrace()
.getTimeManager()
.getSnapshotsWithSchedule(coordinates.getTime());
if (!suitable.isEmpty()) {
TraceSnapshot found = suitable.iterator().next();
- return CompletableFuture.completedFuture(found.getKey());
+ return found.getKey();
+ }
+ return null;
+ }
+
+ @Override
+ public CompletableFuture materialize(DebuggerCoordinates coordinates) {
+ Long found = findSnapshot(coordinates);
+ if (found != null) {
+ return CompletableFuture.completedFuture(found);
}
if (emulationService == null) {
throw new IllegalStateException(
@@ -674,19 +695,19 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
}
- protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
+ protected CompletableFuture prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
if (varView == null) { // Should only happen with NOWHERE
fireLocationEvent(coordinates);
- return;
+ return AsyncUtils.NIL;
}
- materialize(coordinates).thenAcceptAsync(snap -> {
+ return materialize(coordinates).thenAcceptAsync(snap -> {
if (!coordinates.equals(current)) {
return; // We navigated elsewhere before emulation completed
}
varView.setSnap(snap);
fireLocationEvent(coordinates);
- }, AsyncUtils.SWING_EXECUTOR);
+ }, SwingExecutorService.MAYBE_NOW);
}
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
@@ -977,14 +998,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return thread;
}
}
- //if (!Objects.equals(prev.getTrace(), resolved.getTrace())) {
return recorder.getTarget();
- //}
- //return null;
}
@Override
- public void activate(DebuggerCoordinates coordinates) {
+ public CompletableFuture activateAndNotify(DebuggerCoordinates coordinates,
+ boolean syncTargetFocus) {
DebuggerCoordinates prev;
DebuggerCoordinates resolved;
synchronized (listenersByTrace) {
@@ -992,34 +1011,34 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
resolved = doSetCurrent(coordinates);
}
if (resolved == null) {
- return;
+ return AsyncUtils.NIL;
+ }
+ CompletableFuture future = prepareViewAndFireEvent(resolved);
+ if (!syncTargetFocus) {
+ return future;
}
- prepareViewAndFireEvent(resolved);
if (!synchronizeFocus.get()) {
- return;
+ return future;
}
TraceRecorder recorder = resolved.getRecorder();
if (recorder == null) {
- return;
+ return future;
}
TargetObject focus = translateToFocus(prev, resolved);
if (focus == null || !focus.isValid()) {
- return;
+ return future;
}
recorder.requestFocus(focus);
+ return future;
+ }
+
+ @Override
+ public void activate(DebuggerCoordinates coordinates) {
+ activateAndNotify(coordinates, true); // Drop future on floor
}
public void activateNoFocusChange(DebuggerCoordinates coordinates) {
- //DebuggerCoordinates prev;
- DebuggerCoordinates resolved;
- synchronized (listenersByTrace) {
- //prev = current;
- resolved = doSetCurrent(coordinates);
- }
- if (resolved == null) {
- return;
- }
- prepareViewAndFireEvent(resolved);
+ activateAndNotify(coordinates, false); // Drop future on floor
}
@Override
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassembleAtPcDebuggerBot.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassembleAtPcDebuggerBot.java
index f3597da30a..c4e1b0c782 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassembleAtPcDebuggerBot.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/workflow/DisassembleAtPcDebuggerBot.java
@@ -295,22 +295,29 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
synchronized (injects) {
- if (codeManager.definedUnits().containsAddress(ks, start)) {
+ try {
+ if (codeManager.definedUnits().containsAddress(ks, start)) {
+ return true;
+ }
+ for (DisassemblyInject i : injects) {
+ i.pre(plugin.getTool(), this, view, thread,
+ new AddressSet(start, start),
+ disassemblable);
+ }
+ boolean result = super.applyTo(obj, monitor);
+ if (!result) {
+ Msg.error(this, "Auto-disassembly error: " + getStatusMsg());
+ return true; // No pop-up errors
+ }
+ for (DisassemblyInject i : injects) {
+ i.post(plugin.getTool(), view, getDisassembledAddressSet());
+ }
return true;
}
- for (DisassemblyInject i : injects) {
- i.pre(plugin.getTool(), this, view, thread,
- new AddressSet(start, start),
- disassemblable);
+ catch (Throwable e) {
+ Msg.error(this, "Auto-disassembly error: " + e);
+ return true; // No pop-up errors
}
- boolean result = super.applyTo(obj, monitor);
- if (!result) {
- return false;
- }
- for (DisassemblyInject i : injects) {
- i.post(plugin.getTool(), view, getDisassembledAddressSet());
- }
- return result;
}
}
};
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java
index 89fe66231a..f0894526ac 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerLogicalBreakpointService.java
@@ -31,9 +31,8 @@ import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.program.TraceProgramView;
@ServiceInfo( //
- defaultProvider = DebuggerLogicalBreakpointServicePlugin.class, //
- description = "Aggregate breakpoints for programs and live traces" //
-)
+ defaultProvider = DebuggerLogicalBreakpointServicePlugin.class,
+ description = "Aggregate breakpoints for programs and live traces")
public interface DebuggerLogicalBreakpointService {
/**
* Get all logical breakpoints known to the tool.
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java
new file mode 100644
index 0000000000..8eec698903
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java
@@ -0,0 +1,121 @@
+/* ###
+ * 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.services;
+
+import java.util.concurrent.CompletableFuture;
+
+import javax.swing.Icon;
+
+import ghidra.app.plugin.core.debug.DebuggerCoordinates;
+import ghidra.app.plugin.core.debug.gui.DebuggerResources;
+import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
+import ghidra.framework.plugintool.ServiceInfo;
+import ghidra.pcode.utils.Utils;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.lang.Register;
+import ghidra.program.model.lang.RegisterValue;
+import ghidra.program.model.mem.LiveMemoryHandler;
+import ghidra.trace.model.Trace;
+import ghidra.trace.model.program.TraceProgramView;
+
+@ServiceInfo(
+ defaultProvider = DebuggerStateEditingServicePlugin.class,
+ description = "Centralized service for modifying machine states")
+public interface DebuggerStateEditingService {
+ enum StateEditingMode {
+ READ_ONLY(DebuggerResources.NAME_EDIT_MODE_READ_ONLY, //
+ DebuggerResources.ICON_EDIT_MODE_READ_ONLY) {
+ @Override
+ public boolean canEdit(DebuggerCoordinates coordinates) {
+ return false;
+ }
+ },
+ WRITE_TARGET(DebuggerResources.NAME_EDIT_MODE_WRITE_TARGET, //
+ DebuggerResources.ICON_EDIT_MODE_WRITE_TARGET) {
+ @Override
+ public boolean canEdit(DebuggerCoordinates coordinates) {
+ return coordinates.isAliveAndPresent();
+ }
+ },
+ WRITE_TRACE(DebuggerResources.NAME_EDIT_MODE_WRITE_TRACE, //
+ DebuggerResources.ICON_EDIT_MODE_WRITE_TRACE) {
+ @Override
+ public boolean canEdit(DebuggerCoordinates coordinates) {
+ return coordinates.getTrace() != null;
+ }
+ },
+ WRITE_EMULATOR(DebuggerResources.NAME_EDIT_MODE_WRITE_EMULATOR, //
+ DebuggerResources.ICON_EDIT_MODE_WRITE_EMULATOR) {
+ @Override
+ public boolean canEdit(DebuggerCoordinates coordinates) {
+ return coordinates.getTrace() != null;
+ }
+ };
+
+ public final String name;
+ public final Icon icon;
+
+ private StateEditingMode(String name, Icon icon) {
+ this.name = name;
+ this.icon = icon;
+ }
+
+ public abstract boolean canEdit(DebuggerCoordinates coordinates);
+ }
+
+ interface StateEditor {
+ DebuggerStateEditingService getService();
+
+ DebuggerCoordinates getCoordinates();
+
+ boolean isVariableEditable(Address address, int length);
+
+ default boolean isRegisterEditable(Register register) {
+ return isVariableEditable(register.getAddress(), register.getNumBytes());
+ }
+
+ CompletableFuture setVariable(Address address, byte[] data);
+
+ default CompletableFuture setRegister(RegisterValue value) {
+ Register register = value.getRegister();
+ boolean isBigEndian = getCoordinates().getTrace().getBaseLanguage().isBigEndian();
+ byte[] bytes = Utils.bigIntegerToBytes(value.getUnsignedValue(), register.getNumBytes(),
+ isBigEndian);
+ return setVariable(register.getAddress(), bytes);
+ }
+ }
+
+ interface StateEditingMemoryHandler extends StateEditor, LiveMemoryHandler {
+ }
+
+ interface StateEditingModeChangeListener {
+ void modeChanged(Trace trace, StateEditingMode mode);
+ }
+
+ StateEditingMode getCurrentMode(Trace trace);
+
+ void setCurrentMode(Trace trace, StateEditingMode mode);
+
+ void addModeChangeListener(StateEditingModeChangeListener listener);
+
+ void removeModeChangeListener(StateEditingModeChangeListener listener);
+
+ StateEditor createStateEditor(DebuggerCoordinates coordinates);
+
+ StateEditor createStateEditor(Trace trace);
+
+ StateEditingMemoryHandler createStateEditor(TraceProgramView view);
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java
index 6f7b3f73f4..a6e4f6ffeb 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java
@@ -73,6 +73,14 @@ public interface DebuggerTraceManagerService {
*/
DebuggerCoordinates getCurrent();
+ /**
+ * Get the current coordinates for a given trace
+ *
+ * @param trace the trace
+ * @return the current coordinates for the trace
+ */
+ DebuggerCoordinates getCurrentFor(Trace trace);
+
/**
* Get the active trace
*
@@ -212,13 +220,28 @@ public interface DebuggerTraceManagerService {
void closeDeadTraces();
/**
- * Activate the given coordinates
+ * Activate the given coordinates with future notification
*
*
* This operation may be completed asynchronously, esp., if emulation is required to materialize
- * the coordinates. The coordinates are "resolved" as a means of filling in missing parts. For
- * example, if the thread is not specified, the manager may activate the last-active thread for
- * the desired trace.
+ * the coordinates. The returned future is completed when the coordinates are actually
+ * materialized and active. The coordinates are "resolved" as a means of filling in missing
+ * parts. For example, if the thread is not specified, the manager may activate the last-active
+ * thread for the desired trace.
+ *
+ * @param coordinates the desired coordinates
+ * @param syncTargetFocus true synchronize the current target to the same coordinates
+ * @return a future which completes when emulation and navigation is complete
+ */
+ CompletableFuture activateAndNotify(DebuggerCoordinates coordinates,
+ boolean syncTargetFocus);
+
+ /**
+ * Activate the given coordinates, synchronizing the current target, if possible
+ *
+ *
+ * If asynchronous notification is needed, use
+ * {@link #activateAndNotify(DebuggerCoordinates, boolean)}.
*
* @param coordinates the desired coordinates
*/
@@ -383,6 +406,18 @@ public interface DebuggerTraceManagerService {
*/
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coordinates);
+ /**
+ * If the given coordinates are already materialized, get the snapshot
+ *
+ *
+ * If the coordinates do not include a schedule, this simply returns the coordinates' snapshot.
+ * Otherwise, it searches for the first snapshot whose schedule is the coordinates' schedule.
+ *
+ * @param coordinates the coordinates
+ * @return the materialized snapshot key, or null if not materialized.
+ */
+ Long findSnapshot(DebuggerCoordinates coordinates);
+
/**
* Materialize the given coordinates to a snapshot in the same trace
*
diff --git a/Ghidra/Debug/Debugger/src/main/resources/images/write-disabled.png b/Ghidra/Debug/Debugger/src/main/resources/images/write-disabled.png
new file mode 100644
index 0000000000..483d165a27
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/resources/images/write-disabled.png differ
diff --git a/Ghidra/Debug/Debugger/src/main/resources/images/write-emulator.png b/Ghidra/Debug/Debugger/src/main/resources/images/write-emulator.png
new file mode 100644
index 0000000000..72600b472c
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/resources/images/write-emulator.png differ
diff --git a/Ghidra/Debug/Debugger/src/main/resources/images/write-target.png b/Ghidra/Debug/Debugger/src/main/resources/images/write-target.png
new file mode 100644
index 0000000000..c8f0e047ec
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/resources/images/write-target.png differ
diff --git a/Ghidra/Debug/Debugger/src/main/resources/images/write-trace.png b/Ghidra/Debug/Debugger/src/main/resources/images/write-trace.png
new file mode 100644
index 0000000000..c0b120cd82
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/resources/images/write-trace.png differ
diff --git a/Ghidra/Debug/Debugger/src/main/svg/pencil.png b/Ghidra/Debug/Debugger/src/main/svg/pencil.png
new file mode 100644
index 0000000000..cd17091c3a
Binary files /dev/null and b/Ghidra/Debug/Debugger/src/main/svg/pencil.png differ
diff --git a/Ghidra/Debug/Debugger/src/main/svg/write-disabled.svg b/Ghidra/Debug/Debugger/src/main/svg/write-disabled.svg
new file mode 100644
index 0000000000..2af2786bae
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/svg/write-disabled.svg
@@ -0,0 +1,49 @@
+
+
diff --git a/Ghidra/Debug/Debugger/src/main/svg/write-emulator.svg b/Ghidra/Debug/Debugger/src/main/svg/write-emulator.svg
new file mode 100644
index 0000000000..7d4627d504
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/svg/write-emulator.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/Ghidra/Debug/Debugger/src/main/svg/write-target.svg b/Ghidra/Debug/Debugger/src/main/svg/write-target.svg
new file mode 100644
index 0000000000..c559e002f5
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/svg/write-target.svg
@@ -0,0 +1,65 @@
+
+
diff --git a/Ghidra/Debug/Debugger/src/main/svg/write-trace.svg b/Ghidra/Debug/Debugger/src/main/svg/write-trace.svg
new file mode 100644
index 0000000000..6edb3492dd
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/svg/write-trace.svg
@@ -0,0 +1,48 @@
+
+
diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperPluginScreenShots.java
index e039d7f833..36da1cfdf6 100644
--- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperPluginScreenShots.java
+++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperPluginScreenShots.java
@@ -69,8 +69,8 @@ public class DebuggerPcodeStepperPluginScreenShots extends GhidraScreenShotGener
PcodeExecutor exe =
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
- exe.executeLine("RIP = 0x00400000");
- exe.executeLine("RSP = 0x0010fff8");
+ exe.executeSleighLine("RIP = 0x00400000");
+ exe.executeSleighLine("RSP = 0x0010fff8");
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(snap0));
asm.assemble(tb.addr(0x00400000), "SUB RSP,0x40");
diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPluginScreenShots.java
index 0f6855f156..edca2e4539 100644
--- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPluginScreenShots.java
+++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPluginScreenShots.java
@@ -63,14 +63,14 @@ public class DebuggerWatchesPluginScreenShots extends GhidraScreenShotGenerator
PcodeExecutor executor0 =
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
- executor0.executeLine("RSP = 0x7ffefff8");
- executor0.executeLine("*:4 (RSP+8) = 0x4030201");
+ executor0.executeSleighLine("RSP = 0x7ffefff8");
+ executor0.executeSleighLine("*:4 (RSP+8) = 0x4030201");
PcodeExecutor executor1 =
TraceSleighUtils.buildByteExecutor(tb.trace, snap1, thread, 0);
- executor1.executeLine("RSP = 0x7ffefff8");
- executor1.executeLine("*:4 (RSP+8) = 0x1020304");
- executor1.executeLine("*:4 0x7fff0004:8 = 0x4A9A70C8");
+ executor1.executeSleighLine("RSP = 0x7ffefff8");
+ executor1.executeSleighLine("*:4 (RSP+8) = 0x1020304");
+ executor1.executeSleighLine("*:4 0x7fff0004:8 = 0x4A9A70C8");
}
watchesProvider.addWatch("RSP");
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java
index 8b7fbb6de0..9d542083de 100644
--- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java
@@ -583,6 +583,38 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
modelService.addModel(mb.testModel);
}
+ protected TraceRecorder recordAndWaitSync() throws Exception {
+ createTestModel();
+ mb.createTestProcessesAndThreads();
+ mb.createTestThreadRegisterBanks();
+ // NOTE: Test mapper uses TOYBE64
+ mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
+ Register::isBaseRegister);
+ mb.testProcess1.addRegion(".text", mb.rng(0x00400000, 0x00401000), "rx");
+ mb.testProcess1.addRegion(".data", mb.rng(0x00600000, 0x00601000), "rw");
+
+ TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
+ createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
+
+ waitFor(() -> {
+ TraceThread thread = recorder.getTraceThread(mb.testThread1);
+ if (thread == null) {
+ return false;
+ }
+ /*
+ DebuggerRegisterMapper mapper = recorder.getRegisterMapper(thread);
+ if (mapper == null) {
+ return false;
+ }
+ if (!mapper.getRegistersOnTarget().containsAll(baseRegs)) {
+ return false;
+ }
+ */
+ return true;
+ });
+ return recorder;
+ }
+
protected void nop() {
}
diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/editing/DebuggerStateEditingPluginIntegrationTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/editing/DebuggerStateEditingPluginIntegrationTest.java
new file mode 100644
index 0000000000..9587ca6d52
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/editing/DebuggerStateEditingPluginIntegrationTest.java
@@ -0,0 +1,231 @@
+/* ###
+ * 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.gui.editing;
+
+import static org.junit.Assert.*;
+
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.util.Set;
+
+import org.junit.Test;
+
+import com.google.common.collect.Range;
+
+import docking.ActionContext;
+import docking.action.DockingActionIf;
+import docking.dnd.GClipboard;
+import docking.widgets.OptionDialog;
+import ghidra.app.plugin.core.assembler.AssemblerPlugin;
+import ghidra.app.plugin.core.assembler.AssemblerPluginTestHelper;
+import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
+import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
+import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
+import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
+import ghidra.app.services.DebuggerStateEditingService;
+import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
+import ghidra.program.model.data.ShortDataType;
+import ghidra.program.model.listing.Instruction;
+import ghidra.program.util.ProgramLocation;
+import ghidra.trace.database.DBTraceUtils;
+import ghidra.trace.model.memory.TraceMemoryFlag;
+import ghidra.trace.model.program.TraceVariableSnapProgramView;
+import ghidra.util.database.UndoableTransaction;
+
+/**
+ * Tests for editing machine state that don't naturally fit elsewhere.
+ *
+ *