GP-1584: Unify state-editing story across Debugger UI.
|
@ -149,6 +149,10 @@
|
|||
sortgroup="n"
|
||||
target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerStateEditingPlugin" text="Editing Machine State"
|
||||
sortgroup="n1"
|
||||
target="help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerMemviewPlugin" text="Memview Plot"
|
||||
sortgroup="o"
|
||||
target="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html" />
|
||||
|
|
|
@ -23,17 +23,17 @@
|
|||
</TABLE>
|
||||
|
||||
<P><A name="Toggle_Header"></A>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.</P>
|
||||
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.</P>
|
||||
|
||||
<P>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.</P>
|
||||
|
||||
<P>The dynamic listing supports editing memory via the <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">Machine State Editing
|
||||
Plugin and Service</A>. Such edits are performed as usual: Via the <A href=
|
||||
"help/topics/AssemblerPlugin/Assembler.htm">Patch</A> actions, or by pasting byte strings.
|
||||
These edits may be directed toward a live target, the trace, or the emulator.</P>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<P>The listing provides a variety of actions, some for managing and configuring listings, and
|
||||
|
@ -67,7 +73,7 @@
|
|||
|
||||
<P>This action is always available in the <SPAN class="menu">Window → Debugger</SPAN>
|
||||
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.</P>
|
||||
listing. It is equivalent to cloning the primary dynamic listing.</P>
|
||||
|
||||
<H3><A name="export_view"></A>Export Trace View</H3>
|
||||
|
||||
|
@ -78,11 +84,11 @@
|
|||
|
||||
<H3><A name="follows_thread"></A>Follows Selected Thread</H3>
|
||||
|
||||
<P>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."</P>
|
||||
<P>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."</P>
|
||||
|
||||
<H3><A name="track_location"></A>Track Location</H3>
|
||||
|
||||
|
|
|
@ -23,18 +23,17 @@
|
|||
</TABLE>
|
||||
|
||||
<P><A name="Toggle_Header"></A>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.</P>
|
||||
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.</P>
|
||||
|
||||
<P>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.</P>
|
||||
|
||||
<P>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.</P>
|
||||
<P>The dynamic listing supports editing memory via the <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">Machine State Editing
|
||||
Plugin and Service</A>. 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. <B>NOTE:</B> 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.</P>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
|
@ -60,24 +61,24 @@
|
|||
|
||||
<P>This action is always available in the <SPAN class="menu">Window → Debugger</SPAN>
|
||||
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.</P>
|
||||
It is equivalent to cloning the primary memory window.</P>
|
||||
|
||||
<H3><A name="follows_thread"></A>Follows Selected Thread</H3>
|
||||
|
||||
<P>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."</P>
|
||||
<P>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."</P>
|
||||
|
||||
<H3><A name="track_location"></A>Track Location</H3>
|
||||
|
||||
<P>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:</P>
|
||||
address computed from the trace's or target's machine state. <B>NOTE:</B> 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:</P>
|
||||
|
||||
<UL>
|
||||
<LI>Do Not Track - disables automatic navigation.</LI>
|
||||
|
@ -143,8 +144,10 @@
|
|||
|
||||
<H3><A name="Enable_Disable_Byteviewer_Editing"></A>Toggle Editing</H3>
|
||||
|
||||
<P>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.</P>
|
||||
<P>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. <B>NOTE:</B> This toggle also disables
|
||||
automatic navigation in order to prevent the cursor from being moved unexpectedly while typing
|
||||
edits.</P>
|
||||
|
||||
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
||||
|
||||
|
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
@ -46,9 +46,10 @@
|
|||
<LI>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 <B>Enable Edits</B> toggle is
|
||||
on, and the register is not part of <CODE>contextreg</CODE>. 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 <FONT color=
|
||||
on, and the <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">editing service</A>
|
||||
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 <FONT color=
|
||||
"red">red</FONT>.</LI>
|
||||
|
||||
<LI>Type - the type of the register as marked up in the trace. There is generally no default
|
||||
|
@ -92,21 +93,17 @@
|
|||
|
||||
<H3><A name="enable_edits"></A>Enable Edits</H3>
|
||||
|
||||
<P>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.</P>
|
||||
<P>This toggle is a write protector for machine state. To modify register values, this toggle
|
||||
must be enabled. Edits are directed according the to <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
|
||||
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
|
||||
column cannot be edited, yet.</P>
|
||||
|
||||
<H3><A name="snapshot_window"></A>Snapshot Window</H3>
|
||||
|
||||
<P>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.</P>
|
||||
clone of this window. The clone will no longer follow the current thread, but it will follow
|
||||
the current time.</P>
|
||||
|
||||
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
||||
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="generator" content=
|
||||
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||
|
||||
<TITLE>Debugger: Editing Machine State</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="plugin"></A>Debugger: Editing Machine State</H1>
|
||||
|
||||
<P>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.</P>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<P>The plugin provides a single action:</P>
|
||||
|
||||
<H3><A name="edit_mode"></A>Edit Mode</H3>
|
||||
|
||||
<P>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:</P>
|
||||
|
||||
<UL>
|
||||
<LI><IMG alt="" src="images/write-disabled.png"> Read-Only - Rejects all edits.</LI>
|
||||
|
||||
<LI><IMG alt="" src="images/write-target.png"> 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.</LI>
|
||||
|
||||
<LI><IMG alt="" src="images/write-trace.png"> Write Trace - Directs all edits to the trace.
|
||||
Edits are generally always accepted, and they are applied directly to the trace.</LI>
|
||||
|
||||
<LI><IMG alt="" src="images/write-emulator.png"> 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 <A href=
|
||||
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
|
||||
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.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Recommendations</H2>
|
||||
|
||||
<P>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.</P>
|
||||
|
||||
<P>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 <A href=
|
||||
"help/topics/DebuggerTraceViewDiffPlugin/DebuggerTraceViewDiffPlugin.html">compared</A>.</P>
|
||||
|
||||
<P>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: <A href=
|
||||
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Dynamic Listing</A>, <A href=
|
||||
"help/topics/DebuggerMemoryBytesPlugin/DebuggerMemoryBytesPlugin.html">Memory (Dynamic
|
||||
Bytes)</A>, <A href=
|
||||
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A>, and <A href=
|
||||
"help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html">Watches</A>.</P>
|
||||
</BODY>
|
||||
</HTML>
|
After Width: | Height: | Size: 397 B |
After Width: | Height: | Size: 559 B |
After Width: | Height: | Size: 414 B |
After Width: | Height: | Size: 699 B |
|
@ -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.</LI>
|
||||
allow other threads to be stepped, too. See the <A href=
|
||||
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
|
||||
action.</LI>
|
||||
|
||||
<LI>Description - a user-modifiable description of the snapshot or event. This defaults to
|
||||
the debugger's description of the event.</LI>
|
||||
|
|
|
@ -25,27 +25,33 @@
|
|||
<P>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.</P>
|
||||
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.</P>
|
||||
|
||||
<H2>Examples</H2>
|
||||
|
||||
<P>For those less familiar with Sleigh, here are some example expressions:</P>
|
||||
|
||||
<UL>
|
||||
<LI><CODE>*:4 (RSP+8)</CODE>: Display 4 bytes of [ram] starting 8 bytes after the offset
|
||||
given by register RSP.</LI>
|
||||
<LI><CODE>RSP</CODE>: Display the value of register RSP.</LI>
|
||||
|
||||
<LI><CODE>*:4 0x7fff0004:8</CODE>: 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.</LI>
|
||||
<LI><CODE>*:4 0x7fff0004:8</CODE>: Display 4 bytes starting at ram:7fff0004, e.g., to read
|
||||
the <CODE>int</CODE> 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.</LI>
|
||||
|
||||
<LI><CODE>*:8 RSP</CODE>: Display 8 bytes of [ram] starting at the offset given by register
|
||||
RSP.</LI>
|
||||
RSP, e.g., to read a <CODE>long</CODE> on the stack.</LI>
|
||||
|
||||
<LI><CODE>RSP</CODE>: Display the value of register RSP.</LI>
|
||||
<LI><CODE>*:4 (RSP+8)</CODE>: Display 4 bytes of [ram] starting 8 bytes after the offset
|
||||
given by register RSP, e.g., to read a <CODE>int</CODE> on the stack.</LI>
|
||||
|
||||
<LI><CODE>*:4 ((*:8 RSP)+4)</CODE>: 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 <CODE>int</CODE> of an array whose base pointer is on the
|
||||
stack.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Table Columns</H2>
|
||||
|
@ -62,9 +68,11 @@
|
|||
|
||||
<LI>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 <B>Enable Edits</B> 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 <FONT color="red">red</FONT>.</LI>
|
||||
is on, and the <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">editing service</A>
|
||||
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 <FONT color="red">red</FONT>.</LI>
|
||||
|
||||
<LI>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 @@
|
|||
|
||||
<H3><A name="enable_edits"></A>Enable Edits</H3>
|
||||
|
||||
<P>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.</P>
|
||||
<P>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 <A href=
|
||||
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
|
||||
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
|
||||
column cannot be edited, yet.</P>
|
||||
|
||||
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<StateEditingMode> builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new MultiStateActionBuilder<StateEditingMode>(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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<StateEditingMode> 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<StateEditingMode> 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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -897,7 +897,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
|||
return;
|
||||
}
|
||||
doLoadPcodeFrameFromEmulator(emulationService.getCachedEmulator(trace, time));
|
||||
}, SwingExecutorService.INSTANCE);
|
||||
}, SwingExecutorService.LATER);
|
||||
}
|
||||
|
||||
protected void doLoadPcodeFrameFromEmulator(DebuggerTracePcodeEmulator emu) {
|
||||
|
|
|
@ -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<Void> 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<Void> 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<Void> 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<Void> 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<Void> 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<Void> 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<Trace, StateEditingMode> currentModes = new HashMap<>();
|
||||
|
||||
private final ListenerSet<StateEditingModeChangeListener> 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();
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<DebuggerTraceManagerServicePlugin> 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<Long> 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<Long> 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<Void> 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<Void> 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<Void> 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<Void> setVariable(Address address, byte[] data);
|
||||
|
||||
default CompletableFuture<Void> 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);
|
||||
}
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
||||
boolean syncTargetFocus);
|
||||
|
||||
/**
|
||||
* Activate the given coordinates, synchronizing the current target, if possible
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
|
|
After Width: | Height: | Size: 397 B |
After Width: | Height: | Size: 559 B |
BIN
Ghidra/Debug/Debugger/src/main/resources/images/write-target.png
Normal file
After Width: | Height: | Size: 414 B |
BIN
Ghidra/Debug/Debugger/src/main/resources/images/write-trace.png
Normal file
After Width: | Height: | Size: 699 B |
BIN
Ghidra/Debug/Debugger/src/main/svg/pencil.png
Normal file
After Width: | Height: | Size: 217 B |
49
Ghidra/Debug/Debugger/src/main/svg/write-disabled.svg
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 4.2333331 4.2333331"
|
||||
height="15.999999"
|
||||
width="15.999999">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(-140.75833,-215.50833)"
|
||||
id="layer1">
|
||||
<image
|
||||
width="4.2333331"
|
||||
height="4.2333331"
|
||||
preserveAspectRatio="none"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAANJJREFU
|
||||
OI2dUbsRgzAMfcqlYYSMYUpGoMwKpPMaWcGdXWYFRqAUY2QEypciMQcHAl9USb73kyxT61FSVR+Y
|
||||
+6n1kvtrKZldN8+SArPIpcgegKS0+34qUPWBqgqq2xUpSuCwjJ/WN2jeA723D6lPBX4CsQMeCXjd
|
||||
HUMIGG6NwDlHqwCQ6kh1BPCdScYYCYBT6+0VRASqOs+x28cdfmPeXeoR+NpvMIdHzETLfZNARDYu
|
||||
R+4rgT2yRVqWuUImi4gFWSewwGcpZoGSuKZAXdcYx/EvgQ9cP3cPT6z/fgAAAABJRU5ErkJggg==
|
||||
"
|
||||
id="image4638"
|
||||
x="140.75833"
|
||||
y="215.50833" />
|
||||
<path
|
||||
id="path4643"
|
||||
d="m 141.16138,215.91138 3.42712,3.42722"
|
||||
style="fill:#ff0000;stroke:#ff0000;stroke-width:0.39167586;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
56
Ghidra/Debug/Debugger/src/main/svg/write-emulator.svg
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 4.2333331 4.2333331"
|
||||
height="15.999999"
|
||||
width="15.999999">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(-140.75833,-215.50833)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458333,140.75833,215.50833)"
|
||||
id="layer1-5">
|
||||
<path
|
||||
id="path5852"
|
||||
d="M 4.0000781,6.0000002 V 7.1308596 A 4,4 0 0 0 2.1485156,8.199219 L 1.17,7.6347658 0.17,9.3652346 1.1485156,9.9296877 A 4,4 0 0 0 1.0000781,11 4,4 0 0 0 1.1465625,12.070313 L 0.17,12.634766 l 1,1.730468 0.9765625,-0.564453 a 4,4 0 0 0 1.8535156,1.066407 V 16 H 6.000078 v -1.130859 a 4,4 0 0 0 1.851562,-1.06836 l 0.978516,0.564453 1,-1.730468 L 8.85164,12.070313 A 4,4 0 0 0 9.000078,11 4,4 0 0 0 8.853594,9.9296877 l 0.976562,-0.5644531 -1,-1.7304688 L 7.853594,8.199219 A 4,4 0 0 0 6.000078,7.1328127 V 6.0000002 Z M 5.000078,10 a 1,1 0 0 1 1,1 1,1 0 0 1 -1,1 1,1 0 0 1 -0.9999999,-1 1,1 0 0 1 0.9999999,-1 z"
|
||||
style="opacity:1;fill:#2c5aa0;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path898"
|
||||
d="M 1.5449219,0 A 4,4 0 0 0 1.1328125,1 H 0 v 2 h 1.1308594 a 4,4 0 0 0 1.0683594,1.8515625 l -0.5644532,0.9785156 1.7304688,1 0.5644531,-0.9785156 A 4,4 0 0 0 5,6 4,4 0 0 0 6.0703125,5.8535156 l 0.5644531,0.9765625 1.7304688,-1 L 7.8007812,4.8535156 A 4,4 0 0 0 8.8671875,3 H 10 V 1 H 8.8691406 A 4,4 0 0 0 8.4550781,0 Z M 5,1 A 1,1 0 0 1 6,2 1,1 0 0 1 5,3 1,1 0 0 1 4,2 1,1 0 0 1 5,1 Z"
|
||||
style="opacity:1;fill:#666666;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path906"
|
||||
d="m 12.365234,6.1699219 -1.730468,1 0.564453,0.9765625 A 4,4 0 0 0 10.132812,10 H 9 v 2 h 1.130859 a 4,4 0 0 0 1.06836,1.851562 l -0.564453,0.978516 1.730468,1 0.564454,-0.978516 A 4,4 0 0 0 14,15 4,4 0 0 0 15.070312,14.853516 L 15.634766,15.830078 16,15.619141 V 6.3808594 L 15.634766,6.1699219 15.070312,7.1484375 A 4,4 0 0 0 14,7 4,4 0 0 0 12.929688,7.1464844 Z M 14,10 a 1,1 0 0 1 1,1 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 z"
|
||||
style="opacity:1;fill:#666666;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
<image
|
||||
width="4.2333331"
|
||||
height="4.2333331"
|
||||
preserveAspectRatio="none"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEUxMTEAAACcYwCcnJzOzs73 Smv/Y2P/zjH///8dghBMAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsT AQCanBgAAAAHdElNRQfmAxgRMR1me/WzAAAAN0lEQVQI12NgAAFBATDFwJiWCGWIQRkihVCGeCFE DaN7EZRRLghVUq6IqoRBsAjGgCphgPIJAwBLAAY82pfN3gAAAABJRU5ErkJggg== "
|
||||
id="image4544"
|
||||
x="140.75833"
|
||||
y="215.50833" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
65
Ghidra/Debug/Debugger/src/main/svg/write-target.svg
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 4.2333331 4.2333331"
|
||||
height="15.999999"
|
||||
width="15.999999">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(-140.75833,-215.50833)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="matrix(0.26458333,0,0,0.26458333,140.75833,215.50833)"
|
||||
id="g4579">
|
||||
<g
|
||||
transform="matrix(1.5,0,0,1.5,-4,-3.9999972)"
|
||||
id="g3700"
|
||||
style="fill:#000000">
|
||||
<circle
|
||||
r="4"
|
||||
cy="7.9999981"
|
||||
cx="8"
|
||||
id="circle3698"
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
style="fill:#ff2a2a"
|
||||
id="layer1-6">
|
||||
<circle
|
||||
style="opacity:1;fill:#ff2a2a;fill-opacity:1;stroke:none;stroke-width:1.14285719;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path821"
|
||||
cx="8"
|
||||
cy="7.9999981"
|
||||
r="4" />
|
||||
</g>
|
||||
</g>
|
||||
<image
|
||||
width="4.2333331"
|
||||
height="4.2333331"
|
||||
preserveAspectRatio="none"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEUxMTEAAACcYwCcnJzOzs73 Smv/Y2P/zjH///8dghBMAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsT AQCanBgAAAAHdElNRQfmAxgRMR1me/WzAAAAN0lEQVQI12NgAAFBATDFwJiWCGWIQRkihVCGeCFE DaN7EZRRLghVUq6IqoRBsAjGgCphgPIJAwBLAAY82pfN3gAAAABJRU5ErkJggg== "
|
||||
id="image4544"
|
||||
x="140.75833"
|
||||
y="215.50833" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
48
Ghidra/Debug/Debugger/src/main/svg/write-trace.svg
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 4.2333331 4.2333331"
|
||||
height="15.999999"
|
||||
width="15.999999">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(-140.75833,-215.50833)"
|
||||
id="layer1">
|
||||
<image
|
||||
width="4.2333331"
|
||||
height="4.2333331"
|
||||
preserveAspectRatio="none"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI WXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH1gEEDDonMwh5AwAAADV0RVh0Q29tbWVudAAoYykgMjAw NCBKYWt1YiBTdGVpbmVyCgpDcmVhdGVkIHdpdGggVGhlIEdJTVCQ2YtvAAACg0lEQVQ4y52T30tT YRjHP+ec6ZxnxjbEmw3NYrpjWmpqGSpECf2gCNb02qsu6loEqTtB/AdCCIzubAai+CvNQL0QkpLQ nbOoC/XCaTGlneN25qZduEnaTfXcvO8Lz/Pheb/f5xE4Fb19PR1AAKgDXEAUWAKCXZ3dA6fzhd8K LwL9NdW1VxVFQc6Xj5OMPQNVVfm0/HEReNTV2f35BCBTPB3wtxXZC+zoRgwzmSB9kEYSJay5edjl AvSYTvDN622gNQsRM6D+gL+tSJAgpK2wvrGGmTQBMJMm6xtrhLQVBAkC/rYioD/bgdTb19NRU137 uLikmK/fvrC/v08opBLWwnjLvExOTBGN7uByOYnpMdxuDwKCp7KqYn1menZZBAKKoqAbMQA0Lcz8 3AKGbhz9XzeYn1tA08IA6EYMRVHICI0FqJPzZbZ+/MThdBCPx1EUH/cf3AM4PuPxOA6nAzOZwHHG ScYlLBmrSB+ksVgs3Ll7+4RNVquVQPvD43f6IJ29urIiRgEkUSKVSjE+NkFwcAjTzIhomgQHhxgf myCVSiGJUhYQzQKWjD0Da24euzu72Gw2VFVjZHgUgJHhUVRVw2azsbuzizU3D2PPIDNciEBQVVXs cgEAPl85zS1NyPajQZLtMs0tTfh85QDY5QJUVQUIAkgz07PLlVUVt7znvR6H00FMjyEIAg1X6hFF kdJzpUQ2I6yvbdDYeI1kIsm79zOLXZ3dTwAkgJutNz6E1FV/he+C7HZ7KCwsBODw8JAcSw5l3nLq 6xpIJpK8fDUQjUS2np0tLbGGVtXNf9qFqbeTX7e3v0+ZpnkdmANeCP+zjYF2/2XgOTD6B+BvI9Du fwpc+gUuhglRSTLyIQAAAABJRU5ErkJggg== "
|
||||
id="image4608"
|
||||
x="140.75833"
|
||||
y="215.50833" />
|
||||
<image
|
||||
width="4.2333331"
|
||||
height="4.2333331"
|
||||
preserveAspectRatio="none"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAG1BMVEUxMTEAAACcYwCcnJzOzs73 Smv/Y2P/zjH///8dghBMAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsT AQCanBgAAAAHdElNRQfmAxgRMR1me/WzAAAAN0lEQVQI12NgAAFBATDFwJiWCGWIQRkihVCGeCFE DaN7EZRRLghVUq6IqoRBsAjGgCphgPIJAwBLAAY82pfN3gAAAABJRU5ErkJggg== "
|
||||
id="image4544"
|
||||
x="140.75833"
|
||||
y="215.50833" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -69,8 +69,8 @@ public class DebuggerPcodeStepperPluginScreenShots extends GhidraScreenShotGener
|
|||
|
||||
PcodeExecutor<byte[]> 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");
|
||||
|
|
|
@ -63,14 +63,14 @@ public class DebuggerWatchesPluginScreenShots extends GhidraScreenShotGenerator
|
|||
|
||||
PcodeExecutor<byte[]> 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<byte[]> 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");
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* In these and other machine-state-editing integration tests, we use
|
||||
* {@link StateEditingMode#WRITE_EMULATOR} as a stand-in for any mode. We also use
|
||||
* {@link StateEditingMode#READ_ONLY} just to verify the mode is heeded. Other modes may be tested
|
||||
* if bugs crop up in various combinations.
|
||||
*/
|
||||
public class DebuggerStateEditingPluginIntegrationTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
@Test
|
||||
public void testPatchInstructionActionInDynamicListingEmu() throws Throwable {
|
||||
DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
|
||||
DebuggerStateEditingPlugin editingPlugin =
|
||||
addPlugin(tool, DebuggerStateEditingPlugin.class);
|
||||
DebuggerStateEditingService editingService =
|
||||
tool.getService(DebuggerStateEditingService.class);
|
||||
|
||||
assertFalse(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceVariableSnapProgramView view = tb.trace.getProgramView();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
}
|
||||
|
||||
CodeViewerProvider listingProvider = listingPlugin.getProvider();
|
||||
AssemblerPluginTestHelper helper =
|
||||
new AssemblerPluginTestHelper(assemblerPlugin, listingProvider, view);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
|
||||
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
|
||||
assertFalse(
|
||||
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
|
||||
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
|
||||
|
||||
assertTrue(
|
||||
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
Instruction ins = helper.patchInstructionAt(tb.addr(0x00400123), "", "imm r0,#1234");
|
||||
assertEquals(2, ins.getLength());
|
||||
|
||||
long snap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
byte[] bytes = new byte[2];
|
||||
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
|
||||
assertArrayEquals(tb.arr(0x40, 1234), bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatchDataActionInDynamicListingEmu() throws Throwable {
|
||||
DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
AssemblerPlugin assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
|
||||
DebuggerStateEditingPlugin editingPlugin =
|
||||
addPlugin(tool, DebuggerStateEditingPlugin.class);
|
||||
DebuggerStateEditingService editingService =
|
||||
tool.getService(DebuggerStateEditingService.class);
|
||||
|
||||
assertFalse(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceVariableSnapProgramView view = tb.trace.getProgramView();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
tb.trace.getCodeManager()
|
||||
.definedData()
|
||||
.create(Range.atLeast(0L), tb.addr(0x00400123), ShortDataType.dataType);
|
||||
}
|
||||
|
||||
CodeViewerProvider listingProvider = listingPlugin.getProvider();
|
||||
AssemblerPluginTestHelper helper =
|
||||
new AssemblerPluginTestHelper(assemblerPlugin, listingProvider, view);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
|
||||
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
|
||||
assertFalse(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
|
||||
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
|
||||
|
||||
goTo(listingProvider.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
|
||||
assertTrue(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
|
||||
|
||||
/**
|
||||
* TODO: There's a bug in the trace forking: Data units are not replaced when bytes changed.
|
||||
* Thus, we'll make no assertions about the data unit.
|
||||
*/
|
||||
/*Data data =*/ helper.patchDataAt(tb.addr(0x00400123), "0h", "5h");
|
||||
// assertEquals(2, data.getLength());
|
||||
|
||||
long snap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
byte[] bytes = new byte[2];
|
||||
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
|
||||
assertArrayEquals(tb.arr(0, 5), bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasteActionInDynamicListingEmu() throws Throwable {
|
||||
DebuggerListingPlugin listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
DebuggerStateEditingPlugin editingPlugin =
|
||||
addPlugin(tool, DebuggerStateEditingPlugin.class);
|
||||
addPlugin(tool, ClipboardPlugin.class);
|
||||
DebuggerStateEditingService editingService =
|
||||
tool.getService(DebuggerStateEditingService.class);
|
||||
|
||||
CodeViewerProvider listingProvider = listingPlugin.getProvider();
|
||||
DockingActionIf pasteAction = getLocalAction(listingProvider, "Paste");
|
||||
|
||||
assertFalse(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
TraceVariableSnapProgramView view = tb.trace.getProgramView();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
tb.trace.getMemoryManager()
|
||||
.createRegion("Memory[bin:.text]", 0, tb.range(0x00400000, 0x00401000),
|
||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||
}
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
ActionContext ctx;
|
||||
|
||||
assertTrue(editingPlugin.actionEditMode.isEnabled());
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
|
||||
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
|
||||
ctx = listingProvider.getActionContext(null);
|
||||
assertTrue(pasteAction.isAddToPopup(ctx));
|
||||
assertFalse(pasteAction.isEnabledForContext(ctx));
|
||||
|
||||
runSwing(() -> editingPlugin.actionEditMode
|
||||
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
|
||||
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
|
||||
|
||||
goTo(listingPlugin.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
|
||||
ctx = listingProvider.getActionContext(null);
|
||||
assertTrue(pasteAction.isAddToPopup(ctx));
|
||||
assertFalse(pasteAction.isEnabledForContext(ctx));
|
||||
|
||||
Clipboard clipboard = GClipboard.getSystemClipboard();
|
||||
clipboard.setContents(new StringSelection("12 34 56 78"), null);
|
||||
ctx = listingProvider.getActionContext(null);
|
||||
assertTrue(pasteAction.isAddToPopup(ctx));
|
||||
assertTrue(pasteAction.isEnabledForContext(ctx));
|
||||
|
||||
performAction(pasteAction, listingProvider, false);
|
||||
OptionDialog confirm = waitForDialogComponent(OptionDialog.class);
|
||||
pressButtonByText(confirm, "Yes");
|
||||
|
||||
byte[] bytes = new byte[4];
|
||||
waitForPass(noExc(() -> {
|
||||
long snap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
view.getMemory().getBytes(tb.addr(0x00400123), bytes);
|
||||
assertArrayEquals(tb.arr(0x12, 0x34, 0x56, 0x78), bytes);
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -236,7 +236,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
createAndOpenTrace();
|
||||
TraceThread thread1;
|
||||
TraceThread thread2;
|
||||
DebuggerListingProvider extraProvider = SwingExecutorService.INSTANCE
|
||||
DebuggerListingProvider extraProvider = SwingExecutorService.LATER
|
||||
.submit(() -> listingPlugin.createListingIfMissing(trackPc, true))
|
||||
.get();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
|
|
|
@ -21,31 +21,38 @@ import static org.junit.Assert.*;
|
|||
import java.awt.*;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.junit.*;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.dnd.GClipboard;
|
||||
import docking.menu.ActionState;
|
||||
import docking.menu.MultiStateDockingAction;
|
||||
import docking.widgets.EventTrigger;
|
||||
import docking.widgets.OptionDialog;
|
||||
import generic.test.category.NightlyCategory;
|
||||
import ghidra.GhidraOptions;
|
||||
import ghidra.app.plugin.core.byteviewer.ByteViewerComponent;
|
||||
import ghidra.app.plugin.core.byteviewer.ByteViewerPanel;
|
||||
import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
|
||||
import ghidra.app.plugin.core.debug.gui.action.*;
|
||||
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
|
||||
import ghidra.app.services.DebuggerStateEditingService;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.async.SwingExecutorService;
|
||||
import ghidra.program.model.address.*;
|
||||
|
@ -70,11 +77,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
protected DebuggerMemoryBytesPlugin memBytesPlugin;
|
||||
protected DebuggerMemoryBytesProvider memBytesProvider;
|
||||
|
||||
protected DebuggerStateEditingService editingService;
|
||||
|
||||
@Before
|
||||
public void setUpMemoryBytesProviderTest() throws Exception {
|
||||
memBytesPlugin = addPlugin(tool, DebuggerMemoryBytesPlugin.class);
|
||||
memBytesProvider = waitForComponentProvider(DebuggerMemoryBytesProvider.class);
|
||||
memBytesProvider.setVisible(true);
|
||||
|
||||
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
|
||||
}
|
||||
|
||||
protected void goToDyn(Address address) {
|
||||
|
@ -236,7 +247,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
createAndOpenTrace();
|
||||
TraceThread thread1;
|
||||
TraceThread thread2;
|
||||
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.INSTANCE
|
||||
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.LATER
|
||||
.submit(() -> memBytesPlugin.createViewerIfMissing(trackPc, true))
|
||||
.get();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
|
@ -1068,7 +1079,6 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("TODO")
|
||||
public void testEditLiveBytesWritesTarget() throws Exception {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
@ -1077,19 +1087,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
createTargetTraceMapper(mb.testProcess1));
|
||||
Trace trace = recorder.getTrace();
|
||||
|
||||
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
|
||||
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
|
||||
|
||||
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
|
||||
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
|
||||
|
||||
goToDyn(addr(trace, 0x55550800));
|
||||
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
|
||||
performAction(actionEdit);
|
||||
|
||||
Robot robot = new Robot();
|
||||
robot.keyPress(KeyEvent.VK_4);
|
||||
robot.keyRelease(KeyEvent.VK_4);
|
||||
robot.keyPress(KeyEvent.VK_2);
|
||||
robot.keyRelease(KeyEvent.VK_2);
|
||||
|
||||
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
|
||||
performAction(actionEdit);
|
||||
|
||||
byte[] data = new byte[4];
|
||||
|
@ -1100,8 +1106,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("TODO")
|
||||
public void testEditPastBytesWritesNotTarget() throws Exception {
|
||||
public void testEditTraceBytesWritesNotTarget() throws Exception {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
||||
|
@ -1109,31 +1114,22 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
createTargetTraceMapper(mb.testProcess1));
|
||||
Trace trace = recorder.getTrace();
|
||||
|
||||
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TRACE);
|
||||
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
|
||||
|
||||
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
|
||||
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
|
||||
|
||||
TraceSnapshot present = recorder.forceSnapshot();
|
||||
|
||||
goToDyn(addr(trace, 0x55550800));
|
||||
long snap = present.getKey() - 1;
|
||||
traceManager.activateSnap(snap);
|
||||
waitForSwing();
|
||||
|
||||
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
|
||||
performAction(actionEdit);
|
||||
|
||||
Robot robot = new Robot();
|
||||
robot.keyPress(KeyEvent.VK_4);
|
||||
robot.keyRelease(KeyEvent.VK_4);
|
||||
robot.keyPress(KeyEvent.VK_2);
|
||||
robot.keyRelease(KeyEvent.VK_2);
|
||||
|
||||
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
|
||||
performAction(actionEdit);
|
||||
|
||||
byte[] data = new byte[4];
|
||||
AddressSpace space = trace.getBaseAddressFactory().getDefaultAddressSpace();
|
||||
trace.getMemoryManager()
|
||||
.getBytes(snap, space.getAddress(0x55550800), ByteBuffer.wrap(data));
|
||||
.getBytes(traceManager.getCurrentSnap(), space.getAddress(0x55550800),
|
||||
ByteBuffer.wrap(data));
|
||||
assertArrayEquals(mb.arr(0x42, 0, 0, 0), data);
|
||||
// Verify the target was not touched
|
||||
Arrays.fill(data, (byte) 0); // test model uses semisparse array
|
||||
|
@ -1144,8 +1140,10 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("TODO")
|
||||
public void testPasteLiveBytesWritesTarget() throws Exception {
|
||||
addPlugin(tool, ClipboardPlugin.class);
|
||||
ActionContext ctx;
|
||||
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
||||
|
@ -1153,6 +1151,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
createTargetTraceMapper(mb.testProcess1));
|
||||
Trace trace = recorder.getTrace();
|
||||
|
||||
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
|
||||
|
||||
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
|
||||
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
|
||||
|
||||
|
@ -1160,10 +1160,19 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
|
||||
performAction(actionEdit);
|
||||
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
Clipboard clipboard = GClipboard.getSystemClipboard();
|
||||
clipboard.setContents(new StringSelection("42 53 64 75"), null);
|
||||
DockingActionIf actionPaste = getAction(memBytesPlugin, "Paste");
|
||||
performAction(actionPaste);
|
||||
|
||||
DockingActionIf actionPaste =
|
||||
Objects.requireNonNull(getLocalAction(memBytesProvider, "Paste"));
|
||||
|
||||
ctx = waitForValue(() -> memBytesProvider.getActionContext(null));
|
||||
assertTrue(actionPaste.isAddToPopup(ctx));
|
||||
assertTrue(actionPaste.isEnabledForContext(ctx));
|
||||
|
||||
performAction(actionPaste, memBytesProvider, false);
|
||||
OptionDialog confirm = waitForDialogComponent(OptionDialog.class);
|
||||
pressButtonByText(confirm, "Yes");
|
||||
|
||||
performAction(actionEdit);
|
||||
byte[] data = new byte[4];
|
||||
|
|
|
@ -75,7 +75,7 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
|
|||
thread = tb.getOrAddThread("1", 0);
|
||||
|
||||
PcodeExecutor<byte[]> init = TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0);
|
||||
init.executeLine("pc = 0x00400000");
|
||||
init.executeSleighLine("pc = 0x00400000");
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||
iit = asm.assemble(start,
|
||||
|
|
|
@ -33,15 +33,17 @@ import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec;
|
|||
import ghidra.app.plugin.core.debug.gui.action.NoneLocationTrackingSpec;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterTableColumns;
|
||||
import ghidra.app.services.ActionSource;
|
||||
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
|
||||
import ghidra.app.services.DebuggerStateEditingService;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.async.AsyncTestUtils;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.util.CodeUnitInsertionException;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.listing.*;
|
||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||
|
@ -56,6 +58,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
protected DebuggerRegistersPlugin registersPlugin;
|
||||
protected DebuggerRegistersProvider registersProvider;
|
||||
protected DebuggerListingPlugin listingPlugin;
|
||||
protected DebuggerStateEditingService editingService;
|
||||
|
||||
protected Register r0;
|
||||
protected Register pc;
|
||||
|
@ -76,6 +79,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
registersPlugin = addPlugin(tool, DebuggerRegistersPlugin.class);
|
||||
registersProvider = waitForComponentProvider(DebuggerRegistersProvider.class);
|
||||
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
|
||||
|
||||
createTrace();
|
||||
r0 = tb.language.getRegister("r0");
|
||||
|
@ -138,36 +142,6 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
}
|
||||
}
|
||||
|
||||
protected TraceRecorder recordAndWaitSync() throws Exception {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
mb.createTestThreadRegisterBanks();
|
||||
// NOTE: Test mapper uses TOYBE64
|
||||
mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(),
|
||||
Register::isBaseRegister);
|
||||
|
||||
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 RegisterRow findRegisterRow(Register reg) {
|
||||
RegisterRow row = getRegisterRow(reg);
|
||||
if (row == null) {
|
||||
|
@ -388,13 +362,15 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
// TODO: Make contextreg modifiable by Registers window
|
||||
|
||||
@Test
|
||||
public void testDeadModifyValueEmulates() throws Exception {
|
||||
public void testModifyValueEmu() throws Exception {
|
||||
traceManager.openTrace(tb.trace);
|
||||
|
||||
TraceThread thread = addThread();
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
assertTrue(registersProvider.actionEnableEdits.isEnabled());
|
||||
performAction(registersProvider.actionEnableEdits);
|
||||
|
||||
|
@ -410,56 +386,13 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||
waitForSwing();
|
||||
waitForPass(() -> {
|
||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||
assertEquals(BigInteger.valueOf(0x1234),
|
||||
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
||||
assertEquals(BigInteger.valueOf(0x1234), row.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiveModifyValueAffectsTarget() throws Exception {
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(recorder.getTrace());
|
||||
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(registersProvider.actionEnableEdits.isEnabled());
|
||||
performAction(registersProvider.actionEnableEdits);
|
||||
|
||||
RegisterRow row = findRegisterRow(r0);
|
||||
assertTrue(row.isValueEditable());
|
||||
|
||||
setRowText(row, "0102030405060708");
|
||||
waitForSwing();
|
||||
|
||||
assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, mb.testBank1.regVals.get("r0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiveModifySubValueAffectsTarget() throws Throwable {
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
Trace trace = recorder.getTrace();
|
||||
traceManager.openTrace(trace);
|
||||
TraceThread thread = recorder.getTraceThread(mb.testThread1);
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(registersProvider.actionEnableEdits.isEnabled());
|
||||
performAction(registersProvider.actionEnableEdits);
|
||||
|
||||
mb.testBank1.writeRegistersNamed(Map.of("r0", new byte[] { 0 }));
|
||||
waitOn(mb.testModel.flushEvents());
|
||||
waitForDomainObject(trace);
|
||||
|
||||
RegisterRow rowL = findRegisterRow(r0l);
|
||||
waitForPass(() -> assertTrue(rowL.isValueEditable()));
|
||||
|
||||
setRowText(rowL, "05060708");
|
||||
waitForSwing();
|
||||
|
||||
assertArrayEquals(new byte[] { 0, 0, 0, 0, 5, 6, 7, 8 }, mb.testBank1.regVals.get("r0"));
|
||||
}
|
||||
|
||||
// NOTE: Value modification only allowed on live target
|
||||
|
||||
@Test
|
||||
|
|
|
@ -33,9 +33,10 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
|||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
||||
import ghidra.app.plugin.core.debug.gui.register.*;
|
||||
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
|
||||
import ghidra.app.services.ActionSource;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||
import ghidra.async.AsyncTestUtils;
|
||||
import ghidra.dbg.model.TestTargetRegisterBankInThread;
|
||||
import ghidra.program.model.address.*;
|
||||
|
@ -44,6 +45,7 @@ import ghidra.program.model.lang.Register;
|
|||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.model.DefaultTraceLocation;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.*;
|
||||
|
@ -69,6 +71,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
protected DebuggerListingProvider listingProvider;
|
||||
protected DebuggerStaticMappingServicePlugin mappingService;
|
||||
protected CodeViewerProvider codeViewerProvider;
|
||||
protected DebuggerStateEditingService editingService;
|
||||
|
||||
protected Register r0;
|
||||
protected Register r1;
|
||||
|
@ -88,6 +91,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
|
||||
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
|
||||
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
|
||||
|
||||
createTrace();
|
||||
r0 = tb.language.getRegister("r0");
|
||||
|
@ -259,7 +263,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
assertNoErr(row);
|
||||
}
|
||||
|
||||
protected void runTestDeadIsEditable(String expression, boolean expectWritable) {
|
||||
protected void runTestIsEditableEmu(String expression, boolean expectWritable) {
|
||||
setRegisterValues(thread);
|
||||
|
||||
performAction(watchesProvider.actionAdd);
|
||||
|
@ -269,6 +273,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
assertFalse(row.isValueEditable());
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activateThread(thread);
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
waitForSwing();
|
||||
|
||||
assertNoErr(row);
|
||||
|
@ -279,21 +284,21 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDeadIsRegisterEditable() {
|
||||
runTestDeadIsEditable("r0", true);
|
||||
public void testIsRegisterEditableEmu() {
|
||||
runTestIsEditableEmu("r0", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeadIsUniqueEditable() {
|
||||
runTestDeadIsEditable("r0 + 8", false);
|
||||
public void testIsUniqueEditableEmu() {
|
||||
runTestIsEditableEmu("r0 + 8", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeadIsMemoryEditable() {
|
||||
runTestDeadIsEditable("*:8 r0", true);
|
||||
public void testIsMemoryEditableEmu() {
|
||||
runTestIsEditableEmu("*:8 r0", true);
|
||||
}
|
||||
|
||||
protected WatchRow prepareTestDeadEdit(String expression) {
|
||||
protected WatchRow prepareTestEditEmu(String expression) {
|
||||
setRegisterValues(thread);
|
||||
|
||||
performAction(watchesProvider.actionAdd);
|
||||
|
@ -302,20 +307,23 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activateThread(thread);
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
performAction(watchesProvider.actionEnableEdits);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeadEditRegister() {
|
||||
WatchRow row = prepareTestDeadEdit("r0");
|
||||
public void testEditRegisterEmu() {
|
||||
WatchRow row = prepareTestEditEmu("r0");
|
||||
TraceMemoryRegisterSpace regVals =
|
||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||
|
||||
row.setRawValueString("0x1234");
|
||||
waitForPass(() -> {
|
||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||
assertEquals(BigInteger.valueOf(0x1234),
|
||||
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
||||
assertEquals("0x1234", row.getRawValueString());
|
||||
|
@ -324,6 +332,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
row.setRawValueString("1234"); // Decimal this time
|
||||
waitForPass(() -> {
|
||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||
assertEquals(BigInteger.valueOf(1234),
|
||||
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
||||
assertEquals("0x4d2", row.getRawValueString());
|
||||
|
@ -331,14 +340,15 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testDeadEditMemory() {
|
||||
WatchRow row = prepareTestDeadEdit("*:8 r0");
|
||||
public void testEditMemoryEmu() {
|
||||
WatchRow row = prepareTestEditEmu("*:8 r0");
|
||||
TraceMemoryOperations mem = tb.trace.getMemoryManager();
|
||||
ByteBuffer buf = ByteBuffer.allocate(8);
|
||||
|
||||
row.setRawValueString("0x1234");
|
||||
waitForPass(() -> {
|
||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||
buf.clear();
|
||||
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
|
||||
buf.flip();
|
||||
|
@ -348,6 +358,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
row.setRawValueString("{ 12 34 56 78 9a bc de f0 }");
|
||||
waitForPass(() -> {
|
||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||
buf.clear();
|
||||
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
|
||||
buf.flip();
|
||||
|
@ -355,7 +366,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
});
|
||||
}
|
||||
|
||||
protected WatchRow prepareTestLiveEdit(String expression) throws Exception {
|
||||
protected WatchRow prepareTestEditTarget(String expression) throws Exception {
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
bank = mb.testThread1.addRegisterBank();
|
||||
|
@ -372,6 +383,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
|
||||
traceManager.openTrace(trace);
|
||||
traceManager.activateThread(thread);
|
||||
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
|
||||
waitForSwing();
|
||||
|
||||
performAction(watchesProvider.actionAdd);
|
||||
|
@ -383,44 +395,35 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testLiveEditRegister() throws Throwable {
|
||||
WatchRow row = prepareTestLiveEdit("r0");
|
||||
public void testEditRegisterTarget() throws Throwable {
|
||||
WatchRow row = prepareTestEditTarget("r0");
|
||||
|
||||
row.setRawValueString("0x1234");
|
||||
retryVoid(() -> {
|
||||
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0"));
|
||||
assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0"));
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiveEditMemory() throws Throwable {
|
||||
WatchRow row = prepareTestLiveEdit("*:8 r0");
|
||||
public void testEditMemoryTarget() throws Throwable {
|
||||
WatchRow row = prepareTestEditTarget("*:8 r0");
|
||||
|
||||
row.setRawValueString("0x1234");
|
||||
retryVoid(() -> {
|
||||
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34),
|
||||
waitOn(mb.testProcess1.memory.readMemory(tb.addr(0x00400000), 8)));
|
||||
waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 8)));
|
||||
}, List.of(AssertionError.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLiveEditNonMappableRegister() throws Throwable {
|
||||
WatchRow row = prepareTestLiveEdit("r1");
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testEditNonMappableRegisterTarget() throws Throwable {
|
||||
WatchRow row = prepareTestEditTarget("r1");
|
||||
TraceThread thread = recorder.getTraceThread(mb.testThread1);
|
||||
// Sanity check
|
||||
assertFalse(recorder.isRegisterOnTarget(thread, r1));
|
||||
|
||||
assertFalse(row.isValueEditable());
|
||||
row.setRawValueString("0x1234");
|
||||
waitForPass(() -> {
|
||||
TraceMemoryRegisterSpace regs =
|
||||
recorder.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||
assertNotNull(regs);
|
||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||
assertEquals(BigInteger.valueOf(0x1234),
|
||||
regs.getValue(viewSnap, r1).getUnsignedValue());
|
||||
});
|
||||
|
||||
assertFalse(bank.regVals.containsKey("r1"));
|
||||
}
|
||||
|
||||
protected void setupUnmappedDataSection() throws Throwable {
|
||||
|
|
|
@ -0,0 +1,443 @@
|
|||
/* ###
|
||||
* 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 static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.app.plugin.assembler.Assembler;
|
||||
import ghidra.app.plugin.assembler.Assemblers;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.services.DebuggerStateEditingService;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.async.AsyncTestUtils;
|
||||
import ghidra.dbg.target.TargetRegisterBank;
|
||||
import ghidra.pcode.exec.AsyncPcodeExecutor;
|
||||
import ghidra.pcode.exec.TracePcodeUtils;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryRegisterSpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebuggerGUITest
|
||||
implements AsyncTestUtils {
|
||||
private DebuggerStateEditingService editingService;
|
||||
|
||||
private Register r0;
|
||||
private Register r0h;
|
||||
private RegisterValue rv1234;
|
||||
private RegisterValue rv5678;
|
||||
private RegisterValue rvHigh1234;
|
||||
|
||||
@Before
|
||||
public void setUpEditorTest() throws Exception {
|
||||
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
|
||||
Language toy = getToyBE64Language();
|
||||
r0 = toy.getRegister("r0");
|
||||
r0h = toy.getRegister("r0h");
|
||||
rv1234 = new RegisterValue(r0, BigInteger.valueOf(1234));
|
||||
rv5678 = new RegisterValue(r0, BigInteger.valueOf(5678));
|
||||
rvHigh1234 = new RegisterValue(r0h, BigInteger.valueOf(1234));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteEmuMemoryNoThreadErr() throws Throwable {
|
||||
/**
|
||||
* TODO: It'd be nice if this worked, since memory edits don't really require a thread
|
||||
* context. That would require some changes in the TraceSchedule and its execution. IINM,
|
||||
* each step currently requires a thread. We'd have to relax that for patch steps, and it'd
|
||||
* only work if they don't refer to any register.
|
||||
*/
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteEmuRegisterNoThreadErr() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteEmuMemory() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
// NB. TraceManager should automatically activate the first thread
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
}
|
||||
waitForSwing();
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
|
||||
waitForSwing();
|
||||
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
long snap = current.getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(4);
|
||||
tb.trace.getMemoryManager().getBytes(snap, tb.addr(0x00400000), buf);
|
||||
assertArrayEquals(tb.arr(1, 2, 3, 4), buf.array());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteEmuRegister() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
TraceThread thread;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
// NB. TraceManager should automatically activate the first thread
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
}
|
||||
waitForSwing();
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
waitForSwing();
|
||||
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
long snap = current.getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
|
||||
RegisterValue value =
|
||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0);
|
||||
assertEquals(rv1234, value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteEmuMemoryAfterStep() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
// NB. TraceManager should automatically activate the first thread
|
||||
TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
AsyncPcodeExecutor<byte[]> executor =
|
||||
TracePcodeUtils.executorForCoordinates(
|
||||
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
||||
executor.executeSleighLine("pc = 0x00400000");
|
||||
}
|
||||
waitForSwing();
|
||||
|
||||
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
|
||||
traceManager.activateTime(step1);
|
||||
waitForPass(() -> assertEquals(step1, traceManager.getCurrent().getTime()));
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setVariable(tb.addr(0x00600000), tb.arr(1, 2, 3, 4)));
|
||||
waitForSwing();
|
||||
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
assertEquals(0, current.getSnap().longValue()); // Chain edits, don't source from scratch
|
||||
long snap = current.getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(4);
|
||||
tb.trace.getMemoryManager().getBytes(snap, tb.addr(0x00600000), buf);
|
||||
assertArrayEquals(tb.arr(1, 2, 3, 4), buf.array());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteEmuRegisterAfterStep() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
TraceThread thread;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
// NB. TraceManager should automatically activate the first thread
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
AsyncPcodeExecutor<byte[]> executor =
|
||||
TracePcodeUtils.executorForCoordinates(
|
||||
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0));
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
||||
executor.executeSleighLine("pc = 0x00400000");
|
||||
}
|
||||
waitForSwing();
|
||||
|
||||
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
|
||||
traceManager.activateTime(step1);
|
||||
waitForPass(() -> assertEquals(step1, traceManager.getCurrent().getTime()));
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
waitForSwing();
|
||||
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
assertEquals(0, current.getSnap().longValue()); // Chain edits, don't source from scratch
|
||||
long snap = current.getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
|
||||
RegisterValue value =
|
||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0);
|
||||
assertEquals(rv1234, value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteEmuMemoryTwice() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
// NB. TraceManager should automatically activate the first thread
|
||||
tb.getOrAddThread("Threads[0]", 0);
|
||||
}
|
||||
waitForSwing();
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
|
||||
waitOn(editor.setVariable(tb.addr(0x00400002), tb.arr(5, 6, 7, 8)));
|
||||
waitForSwing();
|
||||
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
long snap = current.getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
assertEquals(1, current.getTime().patchCount()); // Check coalesced
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(6);
|
||||
tb.trace.getMemoryManager().getBytes(snap, tb.addr(0x00400000), buf);
|
||||
assertArrayEquals(tb.arr(1, 2, 5, 6, 7, 8), buf.array());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteEmuRegisterTwice() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||
|
||||
TraceThread thread;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
// NB. TraceManager should automatically activate the first thread
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
}
|
||||
waitForSwing();
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
waitOn(editor.setRegister(rv5678));
|
||||
waitForSwing();
|
||||
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
long snap = current.getViewSnap();
|
||||
assertTrue(DBTraceUtils.isScratch(snap));
|
||||
assertEquals(1, current.getTime().patchCount()); // Check coalesced
|
||||
|
||||
RegisterValue value =
|
||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0);
|
||||
assertEquals(rv5678, value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteTraceMemory() throws Throwable {
|
||||
// NB. Definitely no thread required
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
// NB. Editor creates its own transaction
|
||||
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
|
||||
waitForSwing();
|
||||
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
long snap = current.getViewSnap();
|
||||
assertEquals(0, snap);
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(4);
|
||||
tb.trace.getMemoryManager().getBytes(snap, tb.addr(0x00400000), buf);
|
||||
assertArrayEquals(tb.arr(1, 2, 3, 4), buf.array());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteTraceRegisterNoThreadErr() throws Throwable {
|
||||
// NB. Definitely no thread required
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
// NB. Editor creates its own transaction
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteTraceRegister() throws Throwable {
|
||||
// NB. Definitely no thread required
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
|
||||
|
||||
TraceThread thread;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
// NB. TraceManager should automatically activate the first thread
|
||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||
}
|
||||
waitForSwing();
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
// NB. Editor creates its own transaction
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
waitForSwing();
|
||||
|
||||
DebuggerCoordinates current = traceManager.getCurrent();
|
||||
long snap = current.getViewSnap();
|
||||
assertEquals(0, snap);
|
||||
|
||||
RegisterValue value =
|
||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0);
|
||||
assertEquals(rv1234, value);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteTargetMemory() throws Throwable {
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(recorder.getTrace());
|
||||
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
|
||||
waitForSwing();
|
||||
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
|
||||
|
||||
assertArrayEquals(mb.arr(1, 2, 3, 4),
|
||||
waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 4)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteTargetRegister() throws Throwable {
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
TargetRegisterBank bank =
|
||||
(TargetRegisterBank) mb.testThread1.getCachedAttribute("RegisterBank");
|
||||
traceManager.openTrace(recorder.getTrace());
|
||||
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
|
||||
waitForSwing();
|
||||
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
|
||||
assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 4, 0xd2), waitOn(bank.readRegister("r0")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteTargetSubRegister() throws Throwable {
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
TargetRegisterBank bank =
|
||||
(TargetRegisterBank) mb.testThread1.getCachedAttribute("RegisterBank");
|
||||
traceManager.openTrace(recorder.getTrace());
|
||||
TraceThread thread = recorder.getTraceThread(mb.testThread1);
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
waitForPass(() -> {
|
||||
DBTraceMemoryRegisterSpace regs = tb.trace.getMemoryManager()
|
||||
.getMemoryRegisterSpace(thread, false);
|
||||
assertNotNull(regs);
|
||||
RegisterValue value = regs.getValue(traceManager.getCurrentSnap(), r0);
|
||||
assertEquals(rv1234, value);
|
||||
});
|
||||
waitOn(editor.setRegister(rvHigh1234));
|
||||
|
||||
assertArrayEquals(mb.arr(0, 0, 4, 0xd2, 0, 0, 4, 0xd2), waitOn(bank.readRegister("r0")));
|
||||
}
|
||||
|
||||
@Test(expected = MemoryAccessException.class)
|
||||
public void testWriteTargetMemoryNotPresentErr() throws Throwable {
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(recorder.getTrace());
|
||||
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
|
||||
waitForSwing();
|
||||
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
|
||||
|
||||
traceManager.activateSnap(traceManager.getCurrentSnap() - 1);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
|
||||
}
|
||||
|
||||
@Test(expected = MemoryAccessException.class)
|
||||
public void testWriteTargetRegisterNotPresentErr() throws Throwable {
|
||||
TraceRecorder recorder = recordAndWaitSync();
|
||||
traceManager.openTrace(recorder.getTrace());
|
||||
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
|
||||
waitForSwing();
|
||||
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
|
||||
|
||||
traceManager.activateSnap(traceManager.getCurrentSnap() - 1);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
}
|
||||
|
||||
@Test(expected = MemoryAccessException.class)
|
||||
public void testWriteTargetMemoryNotAliveErr() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
|
||||
}
|
||||
|
||||
@Test(expected = MemoryAccessException.class)
|
||||
public void testWriteTargetRegisterNotAliveErr() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
}
|
||||
|
||||
@Test(expected = MemoryAccessException.class)
|
||||
public void testWriteReadOnlyMemoryErr() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4)));
|
||||
}
|
||||
|
||||
@Test(expected = MemoryAccessException.class)
|
||||
public void testWriteReadOnlyRegisterErr() throws Throwable {
|
||||
createAndOpenTrace();
|
||||
editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY);
|
||||
|
||||
StateEditor editor = editingService.createStateEditor(tb.trace);
|
||||
waitOn(editor.setRegister(rv1234));
|
||||
}
|
||||
}
|