GP-1584: Unify state-editing story across Debugger UI.
@ -105,6 +105,11 @@ src/main/help/help/topics/DebuggerRegistersPlugin/images/DebuggerRegistersPlugin
|
||||
src/main/help/help/topics/DebuggerRegistersPlugin/images/select-registers.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStackPlugin/images/DebuggerStackPlugin.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-disabled.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-emulator.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-target.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStateEditingPlugin/images/write-trace.png||Tango Icons - Public Domain||||END|
|
||||
src/main/help/help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerTargetsPlugin/DebuggerTargetsPlugin.html||GHIDRA||||END|
|
||||
@ -192,6 +197,10 @@ src/main/resources/images/table_relationship.png||FAMFAMFAM Icons - CC 2.5||||EN
|
||||
src/main/resources/images/text-xml.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
src/main/resources/images/thread.png||GHIDRA||||END|
|
||||
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||
src/main/resources/images/write-disabled.png||GHIDRA||||END|
|
||||
src/main/resources/images/write-emulator.png||GHIDRA||||END|
|
||||
src/main/resources/images/write-target.png||GHIDRA||||END|
|
||||
src/main/resources/images/write-trace.png||Tango Icons - Public Domain||||END|
|
||||
src/main/svg/attach.svg||GHIDRA||||END|
|
||||
src/main/svg/blank.svg||GHIDRA||||END|
|
||||
src/main/svg/breakpoint-clear.svg||GHIDRA||||END|
|
||||
@ -217,6 +226,7 @@ src/main/svg/kill.svg||GHIDRA||||END|
|
||||
src/main/svg/launch.svg||GHIDRA||||END|
|
||||
src/main/svg/memory.svg||GHIDRA||||END|
|
||||
src/main/svg/object-terminated.svg||GHIDRA||||END|
|
||||
src/main/svg/pencil.png||GHIDRA||||END|
|
||||
src/main/svg/process.svg||GHIDRA||||END|
|
||||
src/main/svg/recording.svg||GHIDRA||||END|
|
||||
src/main/svg/register-marker.svg||GHIDRA||||END|
|
||||
@ -230,3 +240,7 @@ src/main/svg/stepout.svg||GHIDRA||||END|
|
||||
src/main/svg/stepover.svg||GHIDRA||||END|
|
||||
src/main/svg/stop.svg||GHIDRA||||END|
|
||||
src/main/svg/thread.svg||GHIDRA||||END|
|
||||
src/main/svg/write-disabled.svg||GHIDRA||||END|
|
||||
src/main/svg/write-emulator.svg||GHIDRA||||END|
|
||||
src/main/svg/write-target.svg||GHIDRA||||END|
|
||||
src/main/svg/write-trace.svg||Tango Icons - Public Domain||||END|
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -409,7 +409,7 @@ public interface AsyncUtils<T> {
|
||||
Cleaner CLEANER = Cleaner.create();
|
||||
|
||||
ExecutorService FRAMEWORK_EXECUTOR = Executors.newWorkStealingPool();
|
||||
ExecutorService SWING_EXECUTOR = SwingExecutorService.INSTANCE;
|
||||
ExecutorService SWING_EXECUTOR = SwingExecutorService.LATER;
|
||||
|
||||
CompletableFuture<Void> NIL = CompletableFuture.completedFuture(null);
|
||||
|
||||
|
@ -22,14 +22,30 @@ import javax.swing.SwingUtilities;
|
||||
|
||||
import ghidra.async.seq.AsyncSequenceActionRuns;
|
||||
import ghidra.async.seq.AsyncSequenceWithoutTemp;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
/**
|
||||
* A wrapper for {@link SwingUtilities#invokeLater(Runnable)} that implements
|
||||
* {@link ExecutorService}. This makes it a suitable first parameter to
|
||||
* {@link AsyncSequenceWithoutTemp#then(ExecutorService, AsyncSequenceActionRuns)} and similar.
|
||||
*/
|
||||
public class SwingExecutorService extends AbstractExecutorService {
|
||||
public static final SwingExecutorService INSTANCE = new SwingExecutorService();
|
||||
public abstract class SwingExecutorService extends AbstractExecutorService {
|
||||
public static final SwingExecutorService LATER = new SwingExecutorService() {
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
SwingUtilities.invokeLater(command);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps {@link Swing#runIfSwingOrRunLater(Runnable)} instead
|
||||
*/
|
||||
public static final SwingExecutorService MAYBE_NOW = new SwingExecutorService() {
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
Swing.runIfSwingOrRunLater(command);
|
||||
}
|
||||
};
|
||||
|
||||
private SwingExecutorService() {
|
||||
}
|
||||
@ -59,8 +75,4 @@ public class SwingExecutorService extends AbstractExecutorService {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
SwingUtilities.invokeLater(command);
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,6 @@ package ghidra.async;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import ghidra.async.AsyncUtils.TemperamentalRunnable;
|
||||
import ghidra.async.AsyncUtils.TemperamentalSupplier;
|
||||
import ghidra.util.Msg;
|
||||
@ -30,7 +28,7 @@ public interface AsyncTestUtils {
|
||||
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
||||
static final long RETRY_INTERVAL_MS = 100;
|
||||
|
||||
default <T> T waitOnNoValidate(CompletableFuture<T> future) {
|
||||
default <T> T waitOnNoValidate(CompletableFuture<T> future) throws Throwable {
|
||||
// Do this instead of plain ol' .get(time), to ease debugging
|
||||
// When suspended in .get(time), you can't introspect much, otherwise
|
||||
long started = System.currentTimeMillis();
|
||||
@ -40,15 +38,11 @@ public interface AsyncTestUtils {
|
||||
}
|
||||
catch (TimeoutException e) {
|
||||
if (Long.compareUnsigned(System.currentTimeMillis() - started, TIMEOUT_MS) >= 0) {
|
||||
throw new RuntimeException(AsyncUtils.unwrapThrowable(e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Throwable unwrapped = AsyncUtils.unwrapThrowable(e);
|
||||
if (unwrapped instanceof RuntimeException) {
|
||||
throw (RuntimeException) unwrapped;
|
||||
}
|
||||
return ExceptionUtils.rethrow(e);
|
||||
throw AsyncUtils.unwrapThrowable(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,10 +54,12 @@ import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.util.TraceChangeManager;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
@ -135,6 +137,8 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
||||
protected DBTraceVariableSnapProgramView programView;
|
||||
protected Map<DBTraceVariableSnapProgramView, Void> programViews = new WeakHashMap<>();
|
||||
protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashMap<>();
|
||||
protected ListenerSet<TraceProgramViewListener> viewListeners =
|
||||
new ListenerSet<>(TraceProgramViewListener.class);
|
||||
|
||||
public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer)
|
||||
throws IOException, LanguageNotFoundException {
|
||||
@ -562,29 +566,34 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
||||
// NOTE: addListener synchronizes on this and might generate callbacks immediately
|
||||
public synchronized DBTraceProgramView getFixedProgramView(long snap) {
|
||||
// NOTE: The new viewport will need to read from the time manager during init
|
||||
DBTraceProgramView view;
|
||||
try (LockHold hold = lockRead()) {
|
||||
synchronized (fixedProgramViews) {
|
||||
DBTraceProgramView view = fixedProgramViews.computeIfAbsent(snap, t -> {
|
||||
Msg.debug(this, "Creating fixed view at snap=" + snap);
|
||||
return new DBTraceProgramView(this, snap, baseCompilerSpec);
|
||||
});
|
||||
return view;
|
||||
view = fixedProgramViews.get(snap);
|
||||
if (view != null) {
|
||||
return view;
|
||||
}
|
||||
Msg.debug(this, "Creating fixed view at snap=" + snap);
|
||||
view = new DBTraceProgramView(this, snap, baseCompilerSpec);
|
||||
}
|
||||
}
|
||||
viewListeners.fire.viewCreated(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
// NOTE: Ditto getFixedProgramView
|
||||
public synchronized DBTraceVariableSnapProgramView createProgramView(long snap) {
|
||||
// NOTE: The new viewport will need to read from the time manager during init
|
||||
DBTraceVariableSnapProgramView view;
|
||||
try (LockHold hold = lockRead()) {
|
||||
synchronized (programViews) {
|
||||
DBTraceVariableSnapProgramView view =
|
||||
new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
|
||||
view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
|
||||
programViews.put(view, null);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
viewListeners.fire.viewCreated(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -721,6 +730,18 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
||||
return getOptions(TRACE_INFO).getDate(DATE_CREATED, new Date(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TraceProgramView> getAllProgramViews() {
|
||||
Collection<TraceProgramView> all = new ArrayList<>();
|
||||
synchronized (programViews) {
|
||||
all.addAll(programViews.keySet());
|
||||
}
|
||||
synchronized (fixedProgramViews) {
|
||||
all.addAll(fixedProgramViews.values());
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
protected void allViews(Consumer<DBTraceProgramView> action) {
|
||||
Collection<DBTraceProgramView> all = new ArrayList<>();
|
||||
synchronized (programViews) {
|
||||
@ -734,6 +755,16 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addProgramViewListener(TraceProgramViewListener listener) {
|
||||
viewListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProgramViewListener(TraceProgramViewListener listener) {
|
||||
viewListeners.remove(listener);
|
||||
}
|
||||
|
||||
public void updateViewsAddRegionBlock(TraceMemoryRegion region) {
|
||||
allViews(v -> v.updateMemoryAddRegionBlock(region));
|
||||
}
|
||||
|
@ -27,7 +27,8 @@ import ghidra.framework.store.LockException;
|
||||
import ghidra.program.database.mem.*;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.*;
|
||||
import ghidra.trace.database.memory.*;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryManager;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
@ -47,6 +48,8 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||
protected boolean forceFullView = false;
|
||||
protected long snap;
|
||||
|
||||
protected LiveMemoryHandler memoryWriteRedirect;
|
||||
|
||||
public AbstractDBTraceProgramViewMemory(DBTraceProgramView program) {
|
||||
this.program = program;
|
||||
this.memoryManager = program.trace.getMemoryManager();
|
||||
@ -155,7 +158,7 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||
|
||||
@Override
|
||||
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
|
||||
throw new UnsupportedOperationException();
|
||||
this.memoryWriteRedirect = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -329,6 +332,10 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||
|
||||
@Override
|
||||
public void setByte(Address addr, byte value) throws MemoryAccessException {
|
||||
if (memoryWriteRedirect != null) {
|
||||
memoryWriteRedirect.putByte(addr, value);
|
||||
return;
|
||||
}
|
||||
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
|
||||
if (space.putBytes(snap, addr, ByteBuffer.wrap(new byte[] { value })) != 1) {
|
||||
throw new MemoryAccessException();
|
||||
@ -338,6 +345,10 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||
@Override
|
||||
public void setBytes(Address addr, byte[] source, int sIndex, int size)
|
||||
throws MemoryAccessException {
|
||||
if (memoryWriteRedirect != null) {
|
||||
memoryWriteRedirect.putBytes(addr, source, sIndex, size);
|
||||
return;
|
||||
}
|
||||
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
|
||||
if (space.putBytes(snap, addr, ByteBuffer.wrap(source, sIndex, size)) != size) {
|
||||
throw new MemoryAccessException();
|
||||
|
@ -369,6 +369,10 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||
public static final TraceSnapshotChangeType<Void> DELETED = new TraceSnapshotChangeType<>();
|
||||
}
|
||||
|
||||
public interface TraceProgramViewListener {
|
||||
void viewCreated(TraceProgramView view);
|
||||
}
|
||||
|
||||
Language getBaseLanguage();
|
||||
|
||||
CompilerSpec getBaseCompilerSpec();
|
||||
@ -412,6 +416,13 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||
|
||||
TraceVariableSnapProgramView createProgramView(long snap);
|
||||
|
||||
/**
|
||||
* Collect all program views, fixed or variable, of this trace.
|
||||
*
|
||||
* @return the current set of program views
|
||||
*/
|
||||
Collection<TraceProgramView> getAllProgramViews();
|
||||
|
||||
/**
|
||||
* Get the "canonical" program view for this trace
|
||||
*
|
||||
@ -423,6 +434,10 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||
*/
|
||||
TraceVariableSnapProgramView getProgramView();
|
||||
|
||||
void addProgramViewListener(TraceProgramViewListener listener);
|
||||
|
||||
void removeProgramViewListener(TraceProgramViewListener listener);
|
||||
|
||||
LockHold lockRead();
|
||||
|
||||
LockHold lockWrite();
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.trace.model.program;
|
||||
|
||||
import ghidra.program.model.mem.LiveMemoryHandler;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
|
||||
public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
|
||||
@ -24,4 +25,13 @@ public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
|
||||
void setForceFullView(boolean forceFullView);
|
||||
|
||||
boolean isForceFullView();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* For trace views, this only redirects memory writes.
|
||||
*/
|
||||
@Override
|
||||
void setLiveMemoryHandler(LiveMemoryHandler handler);
|
||||
}
|
||||
|
@ -15,21 +15,133 @@
|
||||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.generic.util.datastruct.SemisparseByteArray;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeProgram;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class PatchStep implements Step {
|
||||
protected final long threadKey;
|
||||
protected final String sleigh;
|
||||
protected final int hashCode;
|
||||
protected String sleigh;
|
||||
protected int hashCode;
|
||||
|
||||
public static String generateSleigh(Language language, Address address, byte[] data,
|
||||
int length) {
|
||||
BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false);
|
||||
if (address.isMemoryAddress()) {
|
||||
AddressSpace space = address.getAddressSpace();
|
||||
if (language.getDefaultSpace() == space) {
|
||||
return String.format("*:%d 0x%s:%d=0x%s",
|
||||
length,
|
||||
address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
|
||||
value.toString(16));
|
||||
}
|
||||
return String.format("*[%s]:%d 0x%s:%d=0x%s",
|
||||
space.getName(), length,
|
||||
address.getOffsetAsBigInteger().toString(16), space.getPointerSize(),
|
||||
value.toString(16));
|
||||
}
|
||||
Register register = language.getRegister(address, length);
|
||||
if (register == null) {
|
||||
throw new AssertionError("Can only modify memory or register");
|
||||
}
|
||||
return String.format("%s=0x%s", register, value.toString(16));
|
||||
}
|
||||
|
||||
public static String generateSleigh(Language language, Address address, byte[] data) {
|
||||
return generateSleigh(language, address, data, data.length);
|
||||
}
|
||||
|
||||
protected static List<String> generateSleigh(Language language,
|
||||
Map<AddressSpace, SemisparseByteArray> patches) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Entry<AddressSpace, SemisparseByteArray> entry : patches.entrySet()) {
|
||||
generateSleigh(result, language, entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static void generateSleigh(List<String> result, Language language, AddressSpace space,
|
||||
SemisparseByteArray array) {
|
||||
if (space.isRegisterSpace()) {
|
||||
generateRegisterSleigh(result, language, space, array);
|
||||
}
|
||||
else {
|
||||
generateMemorySleigh(result, language, space, array);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void generateMemorySleigh(List<String> result, Language language,
|
||||
AddressSpace space, SemisparseByteArray array) {
|
||||
byte[] data = new byte[8];
|
||||
for (Range<UnsignedLong> range : array.getInitialized(0, -1).asRanges()) {
|
||||
assert range.lowerBoundType() == BoundType.CLOSED;
|
||||
Address start = space.getAddress(range.lowerEndpoint().longValue());
|
||||
Address end = space.getAddress(range.upperEndpoint().longValue() -
|
||||
(range.upperBoundType() == BoundType.OPEN ? 1 : 0));
|
||||
for (AddressRange chunk : new AddressRangeChunker(start, end, data.length)) {
|
||||
Address min = chunk.getMinAddress();
|
||||
int length = (int) chunk.getLength();
|
||||
array.getData(min.getOffset(), data, 0, length);
|
||||
result.add(generateSleigh(language, min, data, length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static Range<UnsignedLong> rangeOfRegister(Register r) {
|
||||
long lower = r.getAddress().getOffset();
|
||||
long upper = lower + r.getNumBytes();
|
||||
return Range.closedOpen(UnsignedLong.fromLongBits(lower), UnsignedLong.fromLongBits(upper));
|
||||
}
|
||||
|
||||
protected static boolean isContained(Register r, RangeSet<UnsignedLong> remains) {
|
||||
return remains.encloses(rangeOfRegister(r));
|
||||
}
|
||||
|
||||
protected static void generateRegisterSleigh(List<String> result, Language language,
|
||||
AddressSpace space, SemisparseByteArray array) {
|
||||
byte[] data = new byte[8];
|
||||
RangeSet<UnsignedLong> remains = TreeRangeSet.create(array.getInitialized(0, -1));
|
||||
while (!remains.isEmpty()) {
|
||||
Range<UnsignedLong> span = remains.span();
|
||||
assert span.lowerBoundType() == BoundType.CLOSED;
|
||||
Address min = space.getAddress(span.lowerEndpoint().longValue());
|
||||
Register register = Stream.of(language.getRegisters(min))
|
||||
.filter(r -> r.getAddress().equals(min))
|
||||
.filter(r -> r.getNumBytes() <= data.length)
|
||||
.filter(r -> isContained(r, remains))
|
||||
.sorted(Comparator.comparing(r -> -r.getNumBytes()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (register == null) {
|
||||
throw new IllegalArgumentException("Could not find a register for " + min);
|
||||
}
|
||||
int length = register.getNumBytes();
|
||||
array.getData(min.getOffset(), data, 0, length);
|
||||
BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false);
|
||||
result.add(String.format("%s=0x%s", register, value.toString(16)));
|
||||
remains.remove(rangeOfRegister(register));
|
||||
}
|
||||
}
|
||||
|
||||
public static PatchStep parse(long threadKey, String stepSpec) {
|
||||
// TODO: Can I parse and validate the sleigh here?
|
||||
@ -45,6 +157,11 @@ public class PatchStep implements Step {
|
||||
this.hashCode = Objects.hash(threadKey, sleigh); // TODO: May become mutable
|
||||
}
|
||||
|
||||
private void setSleigh(String sleigh) {
|
||||
this.sleigh = sleigh;
|
||||
this.hashCode = Objects.hash(threadKey, sleigh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
@ -162,4 +279,120 @@ public class PatchStep implements Step {
|
||||
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";"));
|
||||
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long coalescePatches(Language language, List<Step> steps) {
|
||||
long threadKey = -1;
|
||||
int toRemove = 0;
|
||||
Map<AddressSpace, SemisparseByteArray> patches = new TreeMap<>();
|
||||
for (int i = steps.size() - 1; i >= 0; i--) {
|
||||
Step step = steps.get(i);
|
||||
long stk = step.getThreadKey();
|
||||
if (threadKey == -1) {
|
||||
threadKey = stk;
|
||||
}
|
||||
else if (stk != -1 && stk != threadKey) {
|
||||
break;
|
||||
}
|
||||
if (!(step instanceof PatchStep)) {
|
||||
break;
|
||||
}
|
||||
PatchStep ps = (PatchStep) step;
|
||||
Map<AddressSpace, SemisparseByteArray> subs = ps.getPatches(language);
|
||||
if (subs == null) {
|
||||
break;
|
||||
}
|
||||
mergePatches(subs, patches);
|
||||
patches = subs;
|
||||
toRemove++;
|
||||
}
|
||||
List<String> sleighPatches = generateSleigh(language, patches);
|
||||
assert sleighPatches.size() <= toRemove;
|
||||
for (String sleighPatch : sleighPatches) {
|
||||
PatchStep ps = (PatchStep) steps.get(steps.size() - toRemove);
|
||||
ps.setSleigh(sleighPatch);
|
||||
toRemove--;
|
||||
}
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
protected void mergePatches(Map<AddressSpace, SemisparseByteArray> into,
|
||||
Map<AddressSpace, SemisparseByteArray> from) {
|
||||
for (Entry<AddressSpace, SemisparseByteArray> entry : from.entrySet()) {
|
||||
if (!into.containsKey(entry.getKey())) {
|
||||
into.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
else {
|
||||
into.get(entry.getKey()).putAll(entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<AddressSpace, SemisparseByteArray> getPatches(Language language) {
|
||||
PcodeProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language,
|
||||
"schedule", List.of(sleigh + ";"), SleighUseropLibrary.nil());
|
||||
// SemisparseArray is a bit overkill, no?
|
||||
Map<AddressSpace, SemisparseByteArray> result = new TreeMap<>();
|
||||
for (PcodeOp op : prog.getCode()) {
|
||||
// Only accept patches in form [mem/reg] = [constant]
|
||||
switch (op.getOpcode()) {
|
||||
case PcodeOp.COPY:
|
||||
if (!getPatchCopyOp(language, result, op)) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case PcodeOp.STORE:
|
||||
if (!getPatchStoreOp(language, result, op)) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected boolean getPatchCopyOp(Language language,
|
||||
Map<AddressSpace, SemisparseByteArray> result, PcodeOp op) {
|
||||
Varnode output = op.getOutput();
|
||||
if (!output.isAddress() && !output.isRegister()) {
|
||||
return false;
|
||||
}
|
||||
Varnode input = op.getInput(0);
|
||||
if (!input.isConstant()) {
|
||||
return false;
|
||||
}
|
||||
Address address = output.getAddress();
|
||||
SemisparseByteArray array = result.computeIfAbsent(address.getAddressSpace(),
|
||||
as -> new SemisparseByteArray());
|
||||
array.putData(address.getOffset(),
|
||||
Utils.longToBytes(input.getOffset(), input.getSize(),
|
||||
language.isBigEndian()));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean getPatchStoreOp(Language language,
|
||||
Map<AddressSpace, SemisparseByteArray> result,
|
||||
PcodeOp op) {
|
||||
Varnode vnSpace = op.getInput(0);
|
||||
if (!vnSpace.isConstant()) {
|
||||
return false;
|
||||
}
|
||||
AddressSpace space =
|
||||
language.getAddressFactory().getAddressSpace((int) vnSpace.getOffset());
|
||||
Varnode vnOffset = op.getInput(1);
|
||||
if (!vnOffset.isConstant()) {
|
||||
return false;
|
||||
}
|
||||
Varnode vnValue = op.getInput(2);
|
||||
if (!vnValue.isConstant()) {
|
||||
return false;
|
||||
}
|
||||
SemisparseByteArray array = result.computeIfAbsent(space, as -> new SemisparseByteArray());
|
||||
array.putData(vnOffset.getOffset(), Utils.longToBytes(vnValue.getOffset(),
|
||||
vnValue.getSize(), language.isBigEndian()));
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
@ -122,7 +123,7 @@ public class Sequence implements Comparable<Sequence> {
|
||||
return;
|
||||
}
|
||||
if (steps.isEmpty()) {
|
||||
steps.add(step);
|
||||
steps.add(step.clone());
|
||||
return;
|
||||
}
|
||||
Step last = steps.get(steps.size() - 1);
|
||||
@ -157,6 +158,17 @@ public class Sequence implements Comparable<Sequence> {
|
||||
steps.addAll(clone.subList(2, size));
|
||||
}
|
||||
|
||||
public void coalescePatches(Language language) {
|
||||
if (steps.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Step last = steps.get(steps.size() - 1);
|
||||
long toRemove = last.coalescePatches(language, steps);
|
||||
for (; toRemove > 0; toRemove--) {
|
||||
steps.remove(steps.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind this sequence the given step count
|
||||
*
|
||||
|
@ -15,10 +15,12 @@
|
||||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.pcode.emu.PcodeMachine;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
@ -171,4 +173,6 @@ public interface Step extends Comparable<Step> {
|
||||
|
||||
<T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
|
||||
TaskMonitor monitor) throws CancelledException;
|
||||
|
||||
long coalescePatches(Language language, List<Step> steps);
|
||||
}
|
||||
|
@ -15,9 +15,11 @@
|
||||
*/
|
||||
package ghidra.trace.model.time.schedule;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@ -194,4 +196,9 @@ public class TickStep implements Step {
|
||||
stepAction.accept(emuThread);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long coalescePatches(Language language, List<Step> steps) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -487,6 +487,10 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||
}
|
||||
|
||||
private long keyOf(TraceThread thread) {
|
||||
return thread == null ? -1 : thread.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the equivalent of executing this schedule then performing a given patch
|
||||
*
|
||||
@ -497,10 +501,12 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||
if (!this.pSteps.isNop()) {
|
||||
Sequence pTicks = this.pSteps.clone();
|
||||
pTicks.advance(new PatchStep(thread.getKey(), sleigh));
|
||||
pTicks.coalescePatches(thread.getTrace().getBaseLanguage());
|
||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||
}
|
||||
Sequence ticks = this.steps.clone();
|
||||
ticks.advance(new PatchStep(thread.getKey(), sleigh));
|
||||
ticks.advance(new PatchStep(keyOf(thread), sleigh));
|
||||
ticks.coalescePatches(thread.getTrace().getBaseLanguage());
|
||||
return new TraceSchedule(snap, ticks, new Sequence());
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +334,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
public PcodeFrame getFrame() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Instruction getInstruction() {
|
||||
return null;
|
||||
@ -564,4 +564,27 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
time.finish(tb.trace, TraceSchedule.parse("1:4;t0-4"), machine, TaskMonitor.DUMMY);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCoalescePatches() throws Exception {
|
||||
// TODO: Should parse require coalescing? Can't without passing a language...
|
||||
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("test", "Toy:BE:64:default")) {
|
||||
TraceThread thread;
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
thread = tb.trace.getThreadManager().createThread("Threads[0]", 0);
|
||||
}
|
||||
TraceSchedule time = TraceSchedule.parse("0");
|
||||
time = time.patched(thread, "r0l=1");
|
||||
assertEquals("0:t0-{r0l=0x1}", time.toString());
|
||||
time = time.patched(thread, "r0h=2");
|
||||
assertEquals("0:t0-{r0=0x200000001}", time.toString());
|
||||
time = time.patched(thread, "r1l=3").patched(thread, "*[ram]:4 0xcafe:8=0xdeadbeef");
|
||||
assertEquals("0:t0-{*:4 0xcafe:8=0xdeadbeef};t0-{r0=0x200000001};t0-{r1l=0x3}",
|
||||
time.toString());
|
||||
|
||||
time = time.patched(thread, "*:8 0xcb00:8 = 0x1122334455667788");
|
||||
assertEquals("0:t0-{*:8 0xcafe:8=0xdead112233445566};t0-{*:2 0xcb06:8=0x7788};" +
|
||||
"t0-{r0=0x200000001};t0-{r1l=0x3}", time.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ import java.util.Map;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.util.MathUtilities;
|
||||
|
||||
/**
|
||||
* A sparse byte array characterized by contiguous dense regions
|
||||
*
|
||||
@ -251,6 +253,32 @@ public class SemisparseByteArray {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the contents on another semisparse array into this one
|
||||
*
|
||||
* @param from the source array
|
||||
*/
|
||||
public synchronized void putAll(SemisparseByteArray from) {
|
||||
byte[] temp = new byte[4096];
|
||||
for (Range<UnsignedLong> range : from.defined.asRanges()) {
|
||||
long length;
|
||||
long lower = range.lowerEndpoint().longValue();
|
||||
if (range.upperBoundType() == BoundType.CLOSED) {
|
||||
assert range.upperEndpoint() == UnsignedLong.MAX_VALUE;
|
||||
length = -lower;
|
||||
}
|
||||
else {
|
||||
length = range.upperEndpoint().longValue() - lower;
|
||||
}
|
||||
for (long i = 0; Long.compareUnsigned(i, length) < 0;) {
|
||||
int l = MathUtilities.unsignedMin(temp.length, length - i);
|
||||
from.getData(lower + i, temp, 0, l);
|
||||
this.putData(lower + i, temp, 0, l);
|
||||
i += l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many contiguous bytes are available starting at the given address
|
||||
*
|
||||
|
@ -72,7 +72,7 @@ public class PcodeExecutor<T> {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void executeLine(String line) {
|
||||
public void executeSleighLine(String line) {
|
||||
PcodeProgram program = SleighProgramCompiler.compileProgram(language,
|
||||
"line", List.of(line + ";"), SleighUseropLibrary.NIL);
|
||||
execute(program, SleighUseropLibrary.nil());
|
||||
|
@ -116,6 +116,10 @@ public class PcodeProgram {
|
||||
return language;
|
||||
}
|
||||
|
||||
public List<PcodeOp> getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public <T> void execute(PcodeExecutor<T> executor, SleighUseropLibrary<T> library) {
|
||||
executor.execute(this, library);
|
||||
}
|
||||
|
@ -15,8 +15,7 @@
|
||||
*/
|
||||
package ghidra.generic.util.datastruct;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
@ -26,8 +25,6 @@ import org.junit.Test;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.generic.util.datastruct.SemisparseByteArray;
|
||||
|
||||
public class SemisparseByteArrayTest {
|
||||
private static final String HELLO_WORLD = "Hello, World!";
|
||||
protected static final byte[] HW = HELLO_WORLD.getBytes();
|
||||
@ -117,4 +114,37 @@ public class SemisparseByteArrayTest {
|
||||
assertEquals(0, read[1 + chunk.length + i]); // Test length param. Should not see HW.
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutAll() {
|
||||
SemisparseByteArray first = new SemisparseByteArray();
|
||||
SemisparseByteArray second = new SemisparseByteArray();
|
||||
|
||||
second.putData(0, new byte[] { 1, 2, 3, 4 });
|
||||
second.putData(-HW.length, HW);
|
||||
|
||||
first.putData(2, new byte[] { 1, 2, 3, 4 });
|
||||
first.putData(-HW.length - 1, new byte[] { 10, 11 });
|
||||
|
||||
first.putAll(second);
|
||||
|
||||
RangeSet<UnsignedLong> expectedInit = TreeRangeSet.create();
|
||||
expectedInit.add(Range.closedOpen(
|
||||
UnsignedLong.fromLongBits(0), UnsignedLong.fromLongBits(6)));
|
||||
expectedInit.add(Range.closed(
|
||||
UnsignedLong.fromLongBits(-HW.length - 1), UnsignedLong.fromLongBits(-1)));
|
||||
assertEquals(expectedInit, first.getInitialized(0, -1));
|
||||
|
||||
byte[] read = new byte[6];
|
||||
first.getData(0, read);
|
||||
assertArrayEquals(new byte[] { 1, 2, 3, 4, 3, 4 }, read);
|
||||
|
||||
read = new byte[HW.length];
|
||||
first.getData(-HW.length, read);
|
||||
assertArrayEquals(HW, read);
|
||||
|
||||
read = new byte[2];
|
||||
first.getData(-HW.length - 1, read);
|
||||
assertArrayEquals(new byte[] { 10, 'H' }, read);
|
||||
}
|
||||
}
|
||||
|
@ -133,8 +133,13 @@ public class VerticalPixelAddressMapImpl implements VerticalPixelAddressMap {
|
||||
return new AddressSet();
|
||||
}
|
||||
if (viewedAddresses == null) {
|
||||
Address start = getStartAddress();
|
||||
Address end = getEndAddress();
|
||||
if (start == null || end == null) {
|
||||
return new AddressSet();
|
||||
}
|
||||
viewedAddresses =
|
||||
map.getOriginalAddressSet().intersectRange(getStartAddress(), getEndAddress());
|
||||
map.getOriginalAddressSet().intersectRange(start, end);
|
||||
}
|
||||
return viewedAddresses;
|
||||
}
|
||||
|
@ -15,19 +15,12 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.assembler;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.JTextField;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.app.plugin.assembler.Assembler;
|
||||
import ghidra.app.plugin.assembler.Assemblers;
|
||||
import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyCompletion;
|
||||
import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyInstruction;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
||||
@ -41,7 +34,6 @@ import ghidra.program.model.data.ShortDataType;
|
||||
import ghidra.program.model.data.TerminatedStringDataType;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@ -55,8 +47,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private CodeViewerProvider codeViewer;
|
||||
|
||||
private AssemblyDualTextField instructionInput;
|
||||
private JTextField dataInput;
|
||||
private AssemblerPluginTestHelper helper;
|
||||
|
||||
private ProgramDB program;
|
||||
private AddressSpace space;
|
||||
@ -73,24 +64,20 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
|
||||
|
||||
codeViewer = waitForComponentProvider(CodeViewerProvider.class);
|
||||
|
||||
instructionInput = assemblerPlugin.patchInstructionAction.input;
|
||||
dataInput = assemblerPlugin.patchDataAction.input;
|
||||
|
||||
program = createDefaultProgram(getName(), "Toy:BE:64:default", this);
|
||||
|
||||
space = program.getAddressFactory().getDefaultAddressSpace();
|
||||
memory = program.getMemory();
|
||||
listing = program.getListing();
|
||||
|
||||
helper = new AssemblerPluginTestHelper(assemblerPlugin, codeViewer, program);
|
||||
|
||||
try (ProgramTransaction trans = ProgramTransaction.open(program, "Setup")) {
|
||||
memory.createInitializedBlock(".text", space.getAddress(0x00400000), 0x1000, (byte) 0,
|
||||
TaskMonitor.DUMMY, false);
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
// Snuff the assembler's warning prompt
|
||||
assemblerPlugin.patchInstructionAction.shownWarning.put(program.getLanguage(), true);
|
||||
|
||||
env.showTool();
|
||||
programManager.openProgram(program);
|
||||
}
|
||||
@ -100,45 +87,10 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
protected void assertDualFields() {
|
||||
assertFalse(instructionInput.getAssemblyField().isVisible());
|
||||
assertTrue(instructionInput.getMnemonicField().isVisible());
|
||||
assertTrue(instructionInput.getOperandsField().isVisible());
|
||||
}
|
||||
|
||||
protected List<AssemblyCompletion> inputAndGetCompletions(String text) {
|
||||
return runSwing(() -> {
|
||||
instructionInput.setText(text);
|
||||
instructionInput.auto.startCompletion(instructionInput.getOperandsField());
|
||||
instructionInput.auto.flushUpdates();
|
||||
return instructionInput.auto.getSuggestions();
|
||||
});
|
||||
}
|
||||
|
||||
private void goTo(Address address) {
|
||||
runSwing(() -> codeViewer.goTo(program, new ProgramLocation(program, address)));
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionPatchInstructionNoExisting() throws Exception {
|
||||
Address address = space.getAddress(0x00400000);
|
||||
goTo(address);
|
||||
|
||||
performAction(assemblerPlugin.patchInstructionAction, codeViewer, true);
|
||||
assertDualFields();
|
||||
assertEquals("", instructionInput.getText());
|
||||
assertEquals(address, assemblerPlugin.patchInstructionAction.getAddress());
|
||||
|
||||
List<AssemblyCompletion> completions = inputAndGetCompletions("imm r0, #1234");
|
||||
AssemblyCompletion first = completions.get(0);
|
||||
assertTrue(first instanceof AssemblyInstruction);
|
||||
AssemblyInstruction ai = (AssemblyInstruction) first;
|
||||
|
||||
runSwing(() -> assemblerPlugin.patchInstructionAction.accept(ai));
|
||||
waitForProgram(program);
|
||||
|
||||
Instruction ins = Objects.requireNonNull(listing.getInstructionAt(address));
|
||||
Instruction ins = helper.patchInstructionAt(address, "", "imm r0, #1234");
|
||||
assertEquals("imm r0,#0x4d2", ins.toString());
|
||||
}
|
||||
|
||||
@ -151,45 +103,13 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
goTo(address);
|
||||
|
||||
performAction(assemblerPlugin.patchInstructionAction, codeViewer, true);
|
||||
assertDualFields();
|
||||
assertEquals("imm r0,#0x4d2", instructionInput.getText());
|
||||
assertEquals(address, assemblerPlugin.patchInstructionAction.getAddress());
|
||||
|
||||
List<AssemblyCompletion> completions = inputAndGetCompletions("imm r0, #123");
|
||||
AssemblyCompletion first = completions.get(0);
|
||||
assertTrue(first instanceof AssemblyInstruction);
|
||||
AssemblyInstruction ai = (AssemblyInstruction) first;
|
||||
|
||||
runSwing(() -> assemblerPlugin.patchInstructionAction.accept(ai));
|
||||
waitForProgram(program);
|
||||
|
||||
Instruction ins = Objects.requireNonNull(listing.getInstructionAt(address));
|
||||
Instruction ins = helper.patchInstructionAt(address, "imm r0,#0x4d2", "imm r0, #123");
|
||||
assertEquals("imm r0,#0x7b", ins.toString());
|
||||
}
|
||||
|
||||
// TODO: Test disabled on uninitialized memory
|
||||
// TODO: Test disabled on read-only listings
|
||||
|
||||
protected Data doPatchAt(Address address, String expText, String newText) {
|
||||
goTo(address);
|
||||
|
||||
performAction(assemblerPlugin.patchDataAction, codeViewer, true);
|
||||
assertTrue(dataInput.isVisible());
|
||||
assertEquals(expText, dataInput.getText());
|
||||
assertEquals(address, assemblerPlugin.patchDataAction.getAddress());
|
||||
|
||||
runSwing(() -> {
|
||||
dataInput.setText(newText);
|
||||
assemblerPlugin.patchDataAction.accept();
|
||||
});
|
||||
waitForProgram(program);
|
||||
|
||||
return Objects.requireNonNull(listing.getDataAt(address));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionPatchDataShortHexValid() throws Exception {
|
||||
Address address = space.getAddress(0x00400000);
|
||||
@ -198,7 +118,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
Data data = doPatchAt(address, "0h", "1234h");
|
||||
Data data = helper.patchDataAt(address, "0h", "1234h");
|
||||
assertEquals("1234h", data.getDefaultValueRepresentation());
|
||||
}
|
||||
|
||||
@ -211,7 +131,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
Data data = doPatchAt(address, "0", "1234");
|
||||
Data data = helper.patchDataAt(address, "0", "1234");
|
||||
assertEquals("1234", data.getDefaultValueRepresentation());
|
||||
}
|
||||
|
||||
@ -224,7 +144,8 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello, Patch!\"");
|
||||
Data data = helper.patchDataAt(address, "\"Hello, World!\"",
|
||||
"\"Hello, Patch!\"");
|
||||
assertEquals("\"Hello, Patch!\"", data.getDefaultValueRepresentation());
|
||||
}
|
||||
|
||||
@ -237,7 +158,8 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello!\"");
|
||||
Data data =
|
||||
helper.patchDataAt(address, "\"Hello, World!\"", "\"Hello!\"");
|
||||
assertEquals("\"Hello!\"", data.getDefaultValueRepresentation());
|
||||
assertEquals(7, data.getLength());
|
||||
}
|
||||
@ -251,7 +173,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
trans.commit();
|
||||
}
|
||||
|
||||
Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello to you, too!\"");
|
||||
Data data = helper.patchDataAt(address, "\"Hello, World!\"", "\"Hello to you, too!\"");
|
||||
assertEquals("\"Hello to you, too!\"", data.getDefaultValueRepresentation());
|
||||
assertEquals(19, data.getLength());
|
||||
}
|
||||
|
@ -0,0 +1,131 @@
|
||||
/* ###
|
||||
* 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.assembler;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
import generic.test.AbstractGTest;
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyCompletion;
|
||||
import ghidra.app.plugin.core.assembler.AssemblyDualTextField.AssemblyInstruction;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
public class AssemblerPluginTestHelper {
|
||||
public final AssemblerPlugin assemblerPlugin;
|
||||
private final CodeViewerProvider provider;
|
||||
private final Program program;
|
||||
|
||||
public final PatchInstructionAction patchInstructionAction;
|
||||
public final PatchDataAction patchDataAction;
|
||||
public final AssemblyDualTextField instructionInput;
|
||||
public final JTextField dataInput;
|
||||
|
||||
private final Listing listing;
|
||||
|
||||
public AssemblerPluginTestHelper(AssemblerPlugin assemblerPlugin, CodeViewerProvider provider,
|
||||
Program program) {
|
||||
this.assemblerPlugin = assemblerPlugin;
|
||||
this.provider = provider;
|
||||
this.program = program;
|
||||
|
||||
this.patchInstructionAction = assemblerPlugin.patchInstructionAction;
|
||||
this.patchDataAction = assemblerPlugin.patchDataAction;
|
||||
this.instructionInput = assemblerPlugin.patchInstructionAction.input;
|
||||
this.dataInput = assemblerPlugin.patchDataAction.input;
|
||||
this.listing = program.getListing();
|
||||
|
||||
// Snuff the assembler's warning prompt
|
||||
patchInstructionAction.shownWarning.put(program.getLanguage(), true);
|
||||
}
|
||||
|
||||
public void assertDualFields() {
|
||||
assertFalse(instructionInput.getAssemblyField().isVisible());
|
||||
assertTrue(instructionInput.getMnemonicField().isVisible());
|
||||
assertTrue(instructionInput.getOperandsField().isVisible());
|
||||
}
|
||||
|
||||
public List<AssemblyCompletion> inputAndGetCompletions(String text) {
|
||||
return AbstractGenericTest.runSwing(() -> {
|
||||
instructionInput.setText(text);
|
||||
instructionInput.auto.startCompletion(instructionInput.getOperandsField());
|
||||
instructionInput.auto.flushUpdates();
|
||||
return instructionInput.auto.getSuggestions();
|
||||
});
|
||||
}
|
||||
|
||||
public void goTo(Address address) {
|
||||
ListingPanel listingPanel = provider.getListingPanel();
|
||||
ProgramLocation location = new ProgramLocation(program, address);
|
||||
AbstractGTest.waitForCondition(() -> {
|
||||
AbstractGenericTest.runSwing(() -> listingPanel.goTo(location));
|
||||
ProgramLocation confirm = listingPanel.getCursorLocation();
|
||||
if (confirm == null) {
|
||||
return false;
|
||||
}
|
||||
if (!address.equals(confirm.getAddress())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public Instruction patchInstructionAt(Address address, String expText, String newText) {
|
||||
goTo(address);
|
||||
|
||||
AbstractDockingTest.performAction(assemblerPlugin.patchInstructionAction, provider, true);
|
||||
assertDualFields();
|
||||
assertEquals(expText, instructionInput.getText());
|
||||
assertEquals(address, assemblerPlugin.patchInstructionAction.getAddress());
|
||||
|
||||
List<AssemblyCompletion> completions = inputAndGetCompletions(newText);
|
||||
AssemblyCompletion first = completions.get(0);
|
||||
assertTrue(first instanceof AssemblyInstruction);
|
||||
AssemblyInstruction ai = (AssemblyInstruction) first;
|
||||
|
||||
AbstractGenericTest.runSwing(() -> assemblerPlugin.patchInstructionAction.accept(ai));
|
||||
AbstractGhidraHeadedIntegrationTest.waitForProgram(program);
|
||||
|
||||
return Objects.requireNonNull(listing.getInstructionAt(address));
|
||||
}
|
||||
|
||||
public Data patchDataAt(Address address, String expText, String newText) {
|
||||
goTo(address);
|
||||
|
||||
AbstractDockingTest.performAction(assemblerPlugin.patchDataAction, provider, true);
|
||||
assertTrue(dataInput.isVisible());
|
||||
assertEquals(expText, dataInput.getText());
|
||||
assertEquals(address, assemblerPlugin.patchDataAction.getAddress());
|
||||
|
||||
AbstractGenericTest.runSwing(() -> {
|
||||
dataInput.setText(newText);
|
||||
assemblerPlugin.patchDataAction.accept();
|
||||
});
|
||||
AbstractGhidraHeadedIntegrationTest.waitForProgram(program);
|
||||
|
||||
return Objects.requireNonNull(listing.getDataAt(address));
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import java.util.List;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import docking.action.ToggleDockingAction;
|
||||
import ghidra.GhidraOptions;
|
||||
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
|
||||
import ghidra.app.plugin.core.format.*;
|
||||
@ -88,7 +89,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||
|
||||
protected Map<String, ByteViewerComponent> viewMap = new HashMap<>();
|
||||
|
||||
protected ToggleEditAction editModeAction;
|
||||
protected ToggleDockingAction editModeAction;
|
||||
protected OptionsAction setOptionsAction;
|
||||
|
||||
protected ProgramByteBlockSet blockSet;
|
||||
|