Merge remote-tracking branch 'origin/GP-1584_Dan_emuStateEdit--SQUASHED'
Conflicts: Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerMemoryBytesPlugin/images/DebuggerMemoryBytesPlugin.png
|
@ -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/DebuggerRegistersPlugin/images/select-registers.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html||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/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/DebuggerStaticMappingPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END|
|
src/main/help/help/topics/DebuggerStaticMappingPlugin/images/DebuggerStaticMappingPlugin.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerTargetsPlugin/DebuggerTargetsPlugin.html||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/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/thread.png||GHIDRA||||END|
|
||||||
src/main/resources/images/time.png||FAMFAMFAM Icons - CC 2.5||||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/attach.svg||GHIDRA||||END|
|
||||||
src/main/svg/blank.svg||GHIDRA||||END|
|
src/main/svg/blank.svg||GHIDRA||||END|
|
||||||
src/main/svg/breakpoint-clear.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/launch.svg||GHIDRA||||END|
|
||||||
src/main/svg/memory.svg||GHIDRA||||END|
|
src/main/svg/memory.svg||GHIDRA||||END|
|
||||||
src/main/svg/object-terminated.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/process.svg||GHIDRA||||END|
|
||||||
src/main/svg/recording.svg||GHIDRA||||END|
|
src/main/svg/recording.svg||GHIDRA||||END|
|
||||||
src/main/svg/register-marker.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/stepover.svg||GHIDRA||||END|
|
||||||
src/main/svg/stop.svg||GHIDRA||||END|
|
src/main/svg/stop.svg||GHIDRA||||END|
|
||||||
src/main/svg/thread.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"
|
sortgroup="n"
|
||||||
target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" />
|
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"
|
<tocdef id="DebuggerMemviewPlugin" text="Memview Plot"
|
||||||
sortgroup="o"
|
sortgroup="o"
|
||||||
target="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html" />
|
target="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html" />
|
||||||
|
|
|
@ -23,17 +23,17 @@
|
||||||
</TABLE>
|
</TABLE>
|
||||||
|
|
||||||
<P><A name="Toggle_Header"></A>The dynamic listing is analogous to Ghidra's listing for static
|
<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
|
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
|
"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
|
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
|
"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
|
listing can be "snapshotted," i.e., cloned. This is where dynamic listings differ from static
|
||||||
static listings. Static snapshots remain in place; they do not automatically navigate. Dynamic
|
listings. Static clones remain in place; they do not automatically navigate. Dynamic clones can
|
||||||
snapshots can still be configured to navigate, following the rest of the tool. A common use is
|
still be configured to navigate, following the rest of the tool. A common use is to configure a
|
||||||
to configure a "snapshot" to follow the stack pointer. Still, you can disable a listing's
|
clone to follow the stack pointer. Still, you can disable a listing's automatic navigation, so
|
||||||
automatic navigation, so it behaves like a true snapshot. A current limitation is that you
|
it behaves like a true clone. A current limitation is that you cannot use clones to display
|
||||||
cannot use snapshots to display different points in time for the same trace.</P>
|
different points in time for the same trace.</P>
|
||||||
|
|
||||||
<P>Where applicable, the static listing's location is synchronized to the dynamic listing, and
|
<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
|
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,
|
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>
|
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>
|
<H2>Actions</H2>
|
||||||
|
|
||||||
<P>The listing provides a variety of actions, some for managing and configuring listings, and
|
<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>
|
<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
|
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>
|
<H3><A name="export_view"></A>Export Trace View</H3>
|
||||||
|
|
||||||
|
@ -78,11 +84,11 @@
|
||||||
|
|
||||||
<H3><A name="follows_thread"></A>Follows Selected Thread</H3>
|
<H3><A name="follows_thread"></A>Follows Selected Thread</H3>
|
||||||
|
|
||||||
<P>This action is only available on snapshot dynamic listings. The primary listing always
|
<P>This action is only available on cloned dynamic listings. The primary listing always follows
|
||||||
follows the tool's current thread. Disabling this toggle causes the snapshot to remain on its
|
the tool's current thread. Disabling this toggle causes the clone to remain on its own current
|
||||||
own current thread rather than following the tool's. The current thread is used when computing
|
thread rather than following the tool's. The current thread is used when computing a location
|
||||||
a location to navigate to automatically. It is only applicable when "Track Location" is set to
|
to navigate to automatically. It is only applicable when "Track Location" is set to something
|
||||||
something other than "Do Not Track."</P>
|
other than "Do Not Track."</P>
|
||||||
|
|
||||||
<H3><A name="track_location"></A>Track Location</H3>
|
<H3><A name="track_location"></A>Track Location</H3>
|
||||||
|
|
||||||
|
|
|
@ -23,18 +23,17 @@
|
||||||
</TABLE>
|
</TABLE>
|
||||||
|
|
||||||
<P><A name="Toggle_Header"></A>The memory, or dynamic bytes, window is analogous to Ghidra's
|
<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
|
bytes window for static analysis, but in the dynamic context. It displays memory contents from
|
||||||
contents from a target. More precisely, it displays recorded memory contents in a trace. In
|
a target. More precisely, it displays recorded memory contents in a trace. In most use cases,
|
||||||
most use cases, that trace is "at the present," meaning it is the most recent memory from a
|
that trace is "at the present," meaning it is the most recent memory from a live target.
|
||||||
live target. Multiple memory windows can be displayed simultaneously, using the same pattern as
|
Multiple memory windows can be displayed simultaneously, using the same pattern as many other
|
||||||
many other Ghidra windows. The "primary" window is always displayed and generally tracks with
|
Ghidra windows. The "primary" window is always displayed and generally tracks with the rest of
|
||||||
the rest of the tool. Any window can be "snapshotted," i.e., duplicated. This is where memory
|
the tool. Any window can be "snapshotted," i.e., cloned. This is where memory windows differ
|
||||||
windows differ from static bytes windows. Static snapshots remain in place; they do not
|
from static bytes windows. Static clones remain in place; they do not automatically navigate.
|
||||||
automatically navigate. Dynamic snapshots can still be configured to navigate, following the
|
Dynamic clones can still be configured to navigate, following the rest of the tool. A common
|
||||||
rest of the tool. A common use is to configure a "snapshot" to follow the stack pointer. Still,
|
use is to configure a clone to follow the stack pointer. Still, you can disable a window's
|
||||||
you can disable a window's automatic navigation, so it behaves like a true snapshot. A current
|
automatic navigation, so it behaves like a true clone. A current limitation is that you cannot
|
||||||
limitation is that you cannot use snapshots to display different points in time for the same
|
use clones to display different points in time for the same trace.</P>
|
||||||
trace.</P>
|
|
||||||
|
|
||||||
<P>Because not all memory is recorded, some background coloring is used to indicate the state
|
<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
|
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,
|
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>
|
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
|
<P>The dynamic listing supports editing memory via the <A href=
|
||||||
is simply disabled. In general, modifications to bytes when the window is "at the present" are
|
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">Machine State Editing
|
||||||
directed to the target. Otherwise, they simply modify the historical or emulated values stored
|
Plugin and Service</A>. Such edits are performed as usual: Toggling edits and typing into the
|
||||||
in the trace. A modification at a given time remains in effect, but stale, for all future times
|
editor, or by pasting byte strings. These edits may be directed toward a live target, the
|
||||||
up to but excluding the time of the next recorded value.</P>
|
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>
|
<H2>Actions</H2>
|
||||||
|
|
||||||
|
@ -60,24 +61,24 @@
|
||||||
|
|
||||||
<P>This action is always available in the <SPAN class="menu">Window → Debugger</SPAN>
|
<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.
|
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>
|
<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
|
<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 snapshot to remain on its own
|
the tool's current thread. Disabling this toggle causes the clone to remain on its own current
|
||||||
current thread rather than following the tool's. The current thread is used when computing a
|
thread rather than following the tool's. The current thread is used when computing a location
|
||||||
location to navigate to automatically. It is only applicable when "Track Location" is set to
|
to navigate to automatically. It is only applicable when "Track Location" is set to something
|
||||||
something other than "Do Not Track."</P>
|
other than "Do Not Track."</P>
|
||||||
|
|
||||||
<H3><A name="track_location"></A>Track Location</H3>
|
<H3><A name="track_location"></A>Track Location</H3>
|
||||||
|
|
||||||
<P>This action is always available on all memory windows. It configures automatic navigation
|
<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
|
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
|
address computed from the trace's or target's machine state. <B>NOTE:</B> This feature is
|
||||||
green. The computed address is affected by the tool's current "coordinates," that is the
|
disabled when the edit toggle is on. The address is also highlighted in green. The computed
|
||||||
selected thread, frame, and point in time. The options are pluggable, but currently consist
|
address is affected by the tool's current "coordinates," that is the selected thread, frame,
|
||||||
of:</P>
|
and point in time. The options are pluggable, but currently consist of:</P>
|
||||||
|
|
||||||
<UL>
|
<UL>
|
||||||
<LI>Do Not Track - disables automatic navigation.</LI>
|
<LI>Do Not Track - disables automatic navigation.</LI>
|
||||||
|
@ -143,8 +144,10 @@
|
||||||
|
|
||||||
<H3><A name="Enable_Disable_Byteviewer_Editing"></A>Toggle Editing</H3>
|
<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
|
<P>This action does the same as it does for the static context. Edits may be rejected if the
|
||||||
disabled, as it is a work in progress.</P>
|
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>
|
<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
|
<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
|
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
|
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
|
on, and the <A href=
|
||||||
if the trace is live and "at the present." Otherwise, the change is materialized via
|
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">editing service</A>
|
||||||
emulation. Values changed by the last event are displayed in <FONT color=
|
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>
|
"red">red</FONT>.</LI>
|
||||||
|
|
||||||
<LI>Type - the type of the register as marked up in the trace. There is generally no default
|
<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>
|
<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
|
<P>This toggle is a write protector for machine state. To modify register values, this toggle
|
||||||
toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
|
must be enabled. Edits are directed according the to <A href=
|
||||||
the value to be modified on the target. Editing emulated values is permitted, but ity has no
|
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
|
||||||
effect on the target. Editing historical values is not permitted. All edits to non-live trace
|
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
|
||||||
values are performed in emulation. Specifically, it appends a patch command to the current
|
column cannot be edited, yet.</P>
|
||||||
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>
|
|
||||||
|
|
||||||
<H3><A name="snapshot_window"></A>Snapshot Window</H3>
|
<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
|
<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
|
clone of this window. The clone will no longer follow the current thread, but it will follow
|
||||||
current time.</P>
|
the current time.</P>
|
||||||
|
|
||||||
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
<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
|
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
|
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
|
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
|
<LI>Description - a user-modifiable description of the snapshot or event. This defaults to
|
||||||
the debugger's description of the event.</LI>
|
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
|
<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
|
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
|
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
|
live and at the present, then the necessary target state is retrieved and recorded. The watch
|
||||||
be assigned a data type so that the raw data is rendered in a meaningful way. When applicable,
|
can be assigned a data type so that the raw data is rendered in a meaningful way. When
|
||||||
that data type can optionally be applied to the trace database. Some metadata about the watch
|
applicable, that data type can optionally be applied to the trace database. Some metadata about
|
||||||
is also given, e.g., the address of the value.</P>
|
the watch is also given, e.g., the address of the value.</P>
|
||||||
|
|
||||||
<H2>Examples</H2>
|
<H2>Examples</H2>
|
||||||
|
|
||||||
<P>For those less familiar with Sleigh, here are some example expressions:</P>
|
<P>For those less familiar with Sleigh, here are some example expressions:</P>
|
||||||
|
|
||||||
<UL>
|
<UL>
|
||||||
<LI><CODE>*:4 (RSP+8)</CODE>: Display 4 bytes of [ram] starting 8 bytes after the offset
|
<LI><CODE>RSP</CODE>: Display the value of register RSP.</LI>
|
||||||
given by register RSP.</LI>
|
|
||||||
|
|
||||||
<LI><CODE>*:4 0x7fff0004:8</CODE>: Display 4 bytes starting at ram:7fff0004. The extraneous,
|
<LI><CODE>*:4 0x7fff0004:8</CODE>: Display 4 bytes starting at ram:7fff0004, e.g., to read
|
||||||
but required, size specifier on constant derefs is a known issue. Just use the target's
|
the <CODE>int</CODE> at address 7fff0004. The extraneous but required size specifier on the
|
||||||
pointer size in bytes.</LI>
|
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
|
<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>
|
</UL>
|
||||||
|
|
||||||
<H2>Table Columns</H2>
|
<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
|
<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 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,
|
is on, and the <A href=
|
||||||
the change is materialized via emulation. If the value has changed since the last navigation
|
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">editing service</A>
|
||||||
event, this cell is rendered in <FONT color="red">red</FONT>.</LI>
|
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
|
<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
|
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>
|
<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
|
<P>This toggle is a write protector for machine state. To modify a watch's value, this toggle
|
||||||
toggle must be enabled. Editing a value when the trace is live and "at the present" will cause
|
must be enabled. Edits are directed according the to <A href=
|
||||||
the value to be modified on the target. Editing emulated values is permitted, but it has no
|
"help/topics/DebuggerStateEditingPlugin/DebuggerStateEditingPlugin.html">State Editing Plugin
|
||||||
effect on the target. Editing historical values is not permitted. All edits to non-live trace
|
and Service</A>. <B>Note:</B> Only the raw "Value" column can be edited directly. The "Repr"
|
||||||
values are performed in emulation. Specifically, it appends a patch command to the current
|
column cannot be edited, yet.</P>
|
||||||
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>
|
|
||||||
|
|
||||||
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
<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.options.SaveState;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.trace.database.DBTraceContentHandler;
|
import ghidra.trace.database.DBTraceContentHandler;
|
||||||
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
|
@ -81,9 +82,28 @@ public class DebuggerCoordinates {
|
||||||
null, null, null);
|
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) {
|
public static DebuggerCoordinates view(TraceProgramView view) {
|
||||||
return all(view == null ? null : view.getTrace(), null, null, view,
|
if (view == null) {
|
||||||
view == null ? null : TraceSchedule.snap(view.getSnap()), 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) {
|
public static DebuggerCoordinates snap(long snap) {
|
||||||
|
|
|
@ -22,12 +22,15 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
import docking.action.ToggleDockingAction;
|
import docking.action.ToggleDockingAction;
|
||||||
import docking.action.builder.*;
|
import docking.action.builder.*;
|
||||||
|
import docking.menu.ActionState;
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
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.time.DebuggerTimePlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
|
import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin;
|
||||||
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
|
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.DebuggerTraceManagerService.BooleanChangeAdapter;
|
||||||
import ghidra.app.services.MarkerService;
|
import ghidra.app.services.MarkerService;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
|
@ -162,7 +166,7 @@ public interface DebuggerResources {
|
||||||
// TODO: Draw an icon?
|
// TODO: Draw an icon?
|
||||||
ImageIcon ICON_CAPTURE_SYMBOLS = ResourceManager.loadImage("images/closedFolderLabels.png");
|
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_ERROR = ResourceManager.loadImage("images/dialog-warning_red.png");
|
||||||
ImageIcon ICON_LOG_WARN = ResourceManager.loadImage("images/dialog-warning.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_PREV = ResourceManager.loadImage("images/up.png");
|
||||||
ImageIcon ICON_DIFF_NEXT = ResourceManager.loadImage("images/down.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");
|
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
|
||||||
|
|
||||||
String HELP_ANCHOR_PLUGIN = "plugin";
|
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 {
|
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|
|
@ -192,7 +192,9 @@ public class DebuggerTrackLocationTrait {
|
||||||
|
|
||||||
public void setSpec(LocationTrackingSpec spec) {
|
public void setSpec(LocationTrackingSpec spec) {
|
||||||
// TODO: What if action == null?
|
// TODO: What if action == null?
|
||||||
action.setCurrentActionStateByUserData(spec);
|
if (action != null) {
|
||||||
|
action.setCurrentActionStateByUserData(spec);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocationTrackingSpec getSpec() {
|
public LocationTrackingSpec getSpec() {
|
||||||
|
|
|
@ -24,22 +24,21 @@ import ghidra.app.services.DebuggerModelService;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
|
||||||
@PluginInfo( //
|
@PluginInfo(
|
||||||
shortDescription = "Debugger breakpoints manager", //
|
shortDescription = "Debugger breakpoints manager",
|
||||||
description = "GUI to manage breakpoints", //
|
description = "GUI to manage breakpoints",
|
||||||
category = PluginCategoryNames.DEBUGGER, //
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME, //
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.RELEASED, //
|
status = PluginStatus.RELEASED,
|
||||||
servicesRequired = { //
|
servicesRequired = {
|
||||||
DebuggerLogicalBreakpointService.class, //
|
DebuggerLogicalBreakpointService.class,
|
||||||
DebuggerModelService.class, //
|
DebuggerModelService.class,
|
||||||
},
|
},
|
||||||
eventsConsumed = {
|
eventsConsumed = {
|
||||||
TraceOpenedPluginEvent.class, //
|
TraceOpenedPluginEvent.class,
|
||||||
TraceClosedPluginEvent.class, //
|
TraceClosedPluginEvent.class,
|
||||||
TraceActivatedPluginEvent.class, //
|
TraceActivatedPluginEvent.class,
|
||||||
} //
|
})
|
||||||
)
|
|
||||||
public class DebuggerBreakpointsPlugin extends AbstractDebuggerPlugin {
|
public class DebuggerBreakpointsPlugin extends AbstractDebuggerPlugin {
|
||||||
protected DebuggerBreakpointsProvider provider;
|
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 static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_TRACKING_MARKERS;
|
||||||
|
|
||||||
import java.awt.Color;
|
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.lang.invoke.MethodHandles;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -41,7 +38,6 @@ import docking.menu.MultiStateDockingAction;
|
||||||
import docking.widgets.EventTrigger;
|
import docking.widgets.EventTrigger;
|
||||||
import docking.widgets.fieldpanel.support.ViewerPosition;
|
import docking.widgets.fieldpanel.support.ViewerPosition;
|
||||||
import ghidra.app.nav.ListingPanelContainer;
|
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.CodeViewerProvider;
|
||||||
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
|
import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
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.plugin.core.marker.MarkerOverviewProvider;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
|
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
|
||||||
|
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||||
import ghidra.app.util.viewer.format.FormatManager;
|
import ghidra.app.util.viewer.format.FormatManager;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
|
@ -223,6 +220,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerConsoleService consoleService;
|
private DebuggerConsoleService consoleService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
private DebuggerStateEditingService editingService;
|
||||||
|
@AutoServiceConsumed
|
||||||
private ProgramManager programManager;
|
private ProgramManager programManager;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private FileImporterService importerService;
|
private FileImporterService importerService;
|
||||||
|
@ -351,7 +350,15 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReadOnly() {
|
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
|
@Override
|
||||||
|
@ -429,28 +436,6 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
super.addToTool();
|
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() {
|
protected void updateMarkerServiceColorModel() {
|
||||||
colorModel.removeModel(markerServiceColorModel);
|
colorModel.removeModel(markerServiceColorModel);
|
||||||
if (markerService != null) {
|
if (markerService != null) {
|
||||||
|
@ -825,7 +810,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
||||||
if (loc == null) { // Redundant?
|
if (loc == null) { // Redundant?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mappingService == null) {
|
if (mappingService == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ProgramLocation mapped = mappingService.getStaticLocationFromDynamic(loc);
|
ProgramLocation mapped = mappingService.getStaticLocationFromDynamic(loc);
|
||||||
|
|
|
@ -23,7 +23,8 @@ import java.util.*;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.*;
|
import docking.action.DockingAction;
|
||||||
|
import docking.action.MenuData;
|
||||||
import docking.menu.MultiStateDockingAction;
|
import docking.menu.MultiStateDockingAction;
|
||||||
import docking.widgets.fieldpanel.support.ViewerPosition;
|
import docking.widgets.fieldpanel.support.ViewerPosition;
|
||||||
import ghidra.app.plugin.core.byteviewer.*;
|
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.Address;
|
||||||
import ghidra.program.model.address.AddressSetView;
|
import ghidra.program.model.address.AddressSetView;
|
||||||
import ghidra.program.model.listing.Program;
|
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.ProgramLocation;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
|
@ -187,6 +190,25 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
|
||||||
setHelpLocation(DebuggerResources.HELP_PROVIDER_MEMORY_BYTES);
|
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
|
* TODO: I'd rather this not be here
|
||||||
*/
|
*/
|
||||||
|
@ -209,6 +231,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
|
||||||
@Override
|
@Override
|
||||||
protected ByteViewerPanel newByteViewerPanel() {
|
protected ByteViewerPanel newByteViewerPanel() {
|
||||||
initTraits();
|
initTraits();
|
||||||
|
// For highlighting, e.g., state, pc
|
||||||
return new DebuggerMemoryBytesPanel(this);
|
return new DebuggerMemoryBytesPanel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,6 +381,9 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doGoToTracked() {
|
protected void doGoToTracked() {
|
||||||
|
if (editModeAction.isSelected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ProgramLocation loc = trackingTrait.getTrackedLocation();
|
ProgramLocation loc = trackingTrait.getTrackedLocation();
|
||||||
if (loc == null) {
|
if (loc == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -446,26 +472,4 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
|
||||||
newProvider.panel.setViewerPosition(vp);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
doLoadPcodeFrameFromEmulator(emulationService.getCachedEmulator(trace, time));
|
doLoadPcodeFrameFromEmulator(emulationService.getCachedEmulator(trace, time));
|
||||||
}, SwingExecutorService.INSTANCE);
|
}, SwingExecutorService.LATER);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doLoadPcodeFrameFromEmulator(DebuggerTracePcodeEmulator emu) {
|
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.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
|
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
|
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
|
||||||
import ghidra.async.AsyncLazyValue;
|
import ghidra.async.AsyncLazyValue;
|
||||||
import ghidra.async.AsyncUtils;
|
import ghidra.async.AsyncUtils;
|
||||||
import ghidra.base.widgets.table.DataTypeTableCellEditor;
|
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.memory.TraceMemoryState;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
|
||||||
import ghidra.trace.util.*;
|
import ghidra.trace.util.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
|
@ -388,6 +388,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerListingService listingService;
|
private DebuggerListingService listingService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
private DebuggerStateEditingService editingService;
|
||||||
|
@AutoServiceConsumed
|
||||||
private MarkerService markerService; // TODO: Mark address types (separate plugin?)
|
private MarkerService markerService; // TODO: Mark address types (separate plugin?)
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
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) {
|
boolean canWriteRegister(Register register) {
|
||||||
if (!isEditsEnabled()) {
|
if (!isEditsEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (register.isProcessorContext()) {
|
if (editingService == null) {
|
||||||
return false; // TODO: Limitation from using Sleigh for patching
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean canWriteTargetRegister(Register register) {
|
|
||||||
if (!isEditsEnabled()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!canWriteTarget()) {
|
StateEditor editor = editingService.createStateEditor(current);
|
||||||
return false;
|
return editor.isRegisterEditable(register);
|
||||||
}
|
|
||||||
return current.getRecorder().isRegisterOnTarget(current.getThread(), register);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BigInteger getRegisterValue(Register register) {
|
BigInteger getRegisterValue(Register register) {
|
||||||
|
@ -804,37 +784,40 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeRegisterValue(RegisterValue rv) {
|
void writeRegisterValue(RegisterValue rv) {
|
||||||
if (canWriteTargetRegister(rv.getRegister())) {
|
if (editingService == null) {
|
||||||
rv = combineWithTraceBaseRegisterValue(rv);
|
Msg.showError(this, getComponent(), "Edit Register", "No editing service.");
|
||||||
CompletableFuture<Void> future = current.getRecorder()
|
return;
|
||||||
.writeThreadRegisters(current.getThread(), current.getFrame(),
|
}
|
||||||
Map.of(rv.getRegister(), rv));
|
StateEditor editor = editingService.createStateEditor(current);
|
||||||
future.exceptionally(ex -> {
|
if (!editor.isRegisterEditable(rv.getRegister())) {
|
||||||
ex = AsyncUtils.unwrapThrowable(ex);
|
rv = combineWithTraceBaseRegisterValue(rv);
|
||||||
if (ex instanceof DebuggerModelAccessException) {
|
}
|
||||||
Msg.error(this, "Could not write target register", ex);
|
if (!editor.isRegisterEditable(rv.getRegister())) {
|
||||||
plugin.getTool()
|
Msg.showError(this, getComponent(), "Edit Register",
|
||||||
.setStatusInfo("Could not write target register: " + ex.getMessage());
|
"Neither the register nor its base can be edited.");
|
||||||
}
|
|
||||||
else {
|
|
||||||
Msg.showError(this, getComponent(), "Edit Register",
|
|
||||||
"Could not write target register", ex);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TraceSchedule time = current.getTime().patched(current.getThread(), generateSleigh(rv));
|
|
||||||
traceManager.activateTime(time);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String generateSleigh(RegisterValue rv) {
|
CompletableFuture<Void> future = editor.setRegister(rv);
|
||||||
return String.format("%s=0x%s", rv.getRegister(), rv.getUnsignedValue().toString(16));
|
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) {
|
private RegisterValue combineWithTraceBaseRegisterValue(RegisterValue rv) {
|
||||||
TraceMemoryRegisterSpace regs = getRegisterMemorySpace(false);
|
TraceMemoryRegisterSpace regs = getRegisterMemorySpace(false);
|
||||||
long snap = current.getSnap();
|
long snap = current.getViewSnap();
|
||||||
return TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, snap, regs, true);
|
return TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, snap, regs, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -847,7 +830,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
try (UndoableTransaction tid =
|
try (UndoableTransaction tid =
|
||||||
UndoableTransaction.start(current.getTrace(), "Edit Register Type", false)) {
|
UndoableTransaction.start(current.getTrace(), "Edit Register Type", false)) {
|
||||||
TraceCodeRegisterSpace space = getRegisterMemorySpace(true).getCodeSpace(true);
|
TraceCodeRegisterSpace space = getRegisterMemorySpace(true).getCodeSpace(true);
|
||||||
long snap = current.getSnap();
|
long snap = current.getViewSnap();
|
||||||
space.definedUnits().clear(Range.closed(snap, snap), register, TaskMonitor.DUMMY);
|
space.definedUnits().clear(Range.closed(snap, snap), register, TaskMonitor.DUMMY);
|
||||||
if (dataType != null) {
|
if (dataType != null) {
|
||||||
space.definedData().create(Range.atLeast(snap), register, dataType);
|
space.definedData().create(Range.atLeast(snap), register, dataType);
|
||||||
|
@ -864,7 +847,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
if (space == null) {
|
if (space == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
long snap = current.getSnap();
|
long snap = current.getViewSnap();
|
||||||
return space.definedData().getForRegister(snap, register);
|
return space.definedData().getForRegister(snap, register);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -253,6 +253,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerTraceManagerService traceManager; // For goto time (emu mods)
|
private DebuggerTraceManagerService traceManager; // For goto time (emu mods)
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
protected DebuggerStateEditingService editingService;
|
||||||
|
@AutoServiceConsumed
|
||||||
private DebuggerStaticMappingService mappingService; // For listing action
|
private DebuggerStaticMappingService mappingService; // For listing action
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
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.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||||
import ghidra.app.services.DataTypeManagerService;
|
import ghidra.app.services.DataTypeManagerService;
|
||||||
|
import ghidra.app.services.DebuggerStateEditingService;
|
||||||
|
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
|
||||||
import ghidra.docking.settings.SettingsImpl;
|
import ghidra.docking.settings.SettingsImpl;
|
||||||
import ghidra.pcode.exec.*;
|
import ghidra.pcode.exec.*;
|
||||||
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
|
import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState;
|
||||||
|
@ -32,14 +34,12 @@ import ghidra.pcode.utils.Utils;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.data.DataType;
|
import ghidra.program.model.data.DataType;
|
||||||
import ghidra.program.model.lang.Language;
|
import ghidra.program.model.lang.Language;
|
||||||
import ghidra.program.model.lang.Register;
|
|
||||||
import ghidra.program.model.mem.ByteMemBufferImpl;
|
import ghidra.program.model.mem.ByteMemBufferImpl;
|
||||||
import ghidra.program.model.mem.MemBuffer;
|
import ghidra.program.model.mem.MemBuffer;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||||
import ghidra.trace.model.memory.TraceMemoryState;
|
import ghidra.trace.model.memory.TraceMemoryState;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
|
|
||||||
public class WatchRow {
|
public class WatchRow {
|
||||||
|
@ -142,7 +142,7 @@ public class WatchRow {
|
||||||
MemBuffer buffer = new ByteMemBufferImpl(address, value, language.isBigEndian());
|
MemBuffer buffer = new ByteMemBufferImpl(address, value, language.isBigEndian());
|
||||||
return dataType.getRepresentation(buffer, SettingsImpl.NO_SETTINGS, value.length);
|
return dataType.getRepresentation(buffer, SettingsImpl.NO_SETTINGS, value.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Object parseAsDataTypeObj() {
|
protected Object parseAsDataTypeObj() {
|
||||||
if (dataType == null || value == null) {
|
if (dataType == null || value == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -372,9 +372,20 @@ public class WatchRow {
|
||||||
public Object getValueObj() {
|
public Object getValueObj() {
|
||||||
return valueObj;
|
return valueObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValueEditable() {
|
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) {
|
public void setRawValueString(String valueString) {
|
||||||
|
@ -415,55 +426,16 @@ public class WatchRow {
|
||||||
if (bytes.length != value.length) {
|
if (bytes.length != value.length) {
|
||||||
throw new IllegalArgumentException("Byte array values must match length of variable");
|
throw new IllegalArgumentException("Byte array values must match length of variable");
|
||||||
}
|
}
|
||||||
|
DebuggerStateEditingService editingService = provider.editingService;
|
||||||
// Allow writes to unmappable registers to fall through to trace
|
if (editingService == null) {
|
||||||
// However, attempts to write "weird" register addresses is forbidden
|
throw new AssertionError("No editing service");
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
StateEditor editor = editingService.createStateEditor(coordinates);
|
||||||
/*try (UndoableTransaction tid =
|
editor.setVariable(address, bytes).exceptionally(ex -> {
|
||||||
UndoableTransaction.start(trace, "Write watch at " + address, true)) {
|
Msg.showError(this, null, "Write Failed",
|
||||||
final TraceMemorySpace space;
|
"Could not modify watch value (on target)", ex);
|
||||||
if (address.isRegisterAddress()) {
|
return null;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getValueLength() {
|
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",
|
description = "Manages and cache trace emulation states",
|
||||||
category = PluginCategoryNames.DEBUGGER,
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME,
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.UNSTABLE,
|
status = PluginStatus.RELEASED,
|
||||||
eventsConsumed = {
|
eventsConsumed = {
|
||||||
TraceClosedPluginEvent.class,
|
TraceClosedPluginEvent.class,
|
||||||
ProgramActivatedPluginEvent.class,
|
ProgramActivatedPluginEvent.class,
|
||||||
|
|
|
@ -256,7 +256,7 @@ public class DebuggerConnectDialog extends DialogComponentProvider
|
||||||
result.completeAsync(() -> m);
|
result.completeAsync(() -> m);
|
||||||
result = null;
|
result = null;
|
||||||
}
|
}
|
||||||
}, SwingExecutorService.INSTANCE).exceptionally(e -> {
|
}, SwingExecutorService.LATER).exceptionally(e -> {
|
||||||
e = AsyncUtils.unwrapThrowable(e);
|
e = AsyncUtils.unwrapThrowable(e);
|
||||||
if (!(e instanceof CancellationException)) {
|
if (!(e instanceof CancellationException)) {
|
||||||
Msg.showError(this, getComponent(), "Could not connect", e);
|
Msg.showError(this, getComponent(), "Could not connect", e);
|
||||||
|
|
|
@ -283,7 +283,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
"The given launcher path is not a TargetLauncher, according to its schema");
|
"The given launcher path is not a TargetLauncher, according to its schema");
|
||||||
}
|
}
|
||||||
return new ValueExpecter(m, launcherPath);
|
return new ValueExpecter(m, launcherPath);
|
||||||
}, SwingExecutorService.INSTANCE).thenCompose(l -> {
|
}, SwingExecutorService.LATER).thenCompose(l -> {
|
||||||
monitor.incrementProgress(1);
|
monitor.incrementProgress(1);
|
||||||
monitor.setMessage("Launching");
|
monitor.setMessage("Launching");
|
||||||
TargetLauncher launcher = (TargetLauncher) l;
|
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.event.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
|
import ghidra.async.*;
|
||||||
import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
|
import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
|
||||||
import ghidra.async.AsyncReference;
|
|
||||||
import ghidra.async.AsyncUtils;
|
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.framework.client.ClientUtil;
|
import ghidra.framework.client.ClientUtil;
|
||||||
import ghidra.framework.client.NotConnectedException;
|
import ghidra.framework.client.NotConnectedException;
|
||||||
|
@ -60,16 +59,25 @@ import ghidra.util.datastruct.CollectionChangeListener;
|
||||||
import ghidra.util.exception.*;
|
import ghidra.util.exception.*;
|
||||||
import ghidra.util.task.*;
|
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 = {
|
@PluginInfo(
|
||||||
TraceActivatedPluginEvent.class,
|
shortDescription = "Debugger Trace View Management Plugin",
|
||||||
}, eventsConsumed = {
|
description = "Manages UI Components, Wrappers, Focus, etc.",
|
||||||
TraceActivatedPluginEvent.class,
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
TraceClosedPluginEvent.class,
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
ModelObjectFocusedPluginEvent.class,
|
status = PluginStatus.RELEASED,
|
||||||
TraceRecorderAdvancedPluginEvent.class,
|
eventsProduced = {
|
||||||
}, servicesRequired = {}, servicesProvided = {
|
TraceActivatedPluginEvent.class,
|
||||||
DebuggerTraceManagerService.class,
|
},
|
||||||
})
|
eventsConsumed = {
|
||||||
|
TraceActivatedPluginEvent.class,
|
||||||
|
TraceClosedPluginEvent.class,
|
||||||
|
ModelObjectFocusedPluginEvent.class,
|
||||||
|
TraceRecorderAdvancedPluginEvent.class,
|
||||||
|
},
|
||||||
|
servicesRequired = {},
|
||||||
|
servicesProvided = {
|
||||||
|
DebuggerTraceManagerService.class,
|
||||||
|
})
|
||||||
public class DebuggerTraceManagerServicePlugin extends Plugin
|
public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
implements DebuggerTraceManagerService {
|
implements DebuggerTraceManagerService {
|
||||||
private static final AutoConfigState.ClassHandler<DebuggerTraceManagerServicePlugin> CONFIG_STATE_HANDLER =
|
private static final AutoConfigState.ClassHandler<DebuggerTraceManagerServicePlugin> CONFIG_STATE_HANDLER =
|
||||||
|
@ -623,6 +631,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DebuggerCoordinates getCurrentFor(Trace trace) {
|
||||||
|
return lastCoordsByTrace.get(trace);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Trace getCurrentTrace() {
|
public Trace getCurrentTrace() {
|
||||||
return current.getTrace();
|
return current.getTrace();
|
||||||
|
@ -654,17 +667,25 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return current.getFrame();
|
return current.getFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Long findSnapshot(DebuggerCoordinates coordinates) {
|
||||||
public CompletableFuture<Long> materialize(DebuggerCoordinates coordinates) {
|
|
||||||
if (coordinates.getTime().isSnapOnly()) {
|
if (coordinates.getTime().isSnapOnly()) {
|
||||||
return CompletableFuture.completedFuture(coordinates.getSnap());
|
return coordinates.getSnap();
|
||||||
}
|
}
|
||||||
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
|
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
|
||||||
.getTimeManager()
|
.getTimeManager()
|
||||||
.getSnapshotsWithSchedule(coordinates.getTime());
|
.getSnapshotsWithSchedule(coordinates.getTime());
|
||||||
if (!suitable.isEmpty()) {
|
if (!suitable.isEmpty()) {
|
||||||
TraceSnapshot found = suitable.iterator().next();
|
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) {
|
if (emulationService == null) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
@ -674,19 +695,19 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
|
return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
|
protected CompletableFuture<Void> prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
|
||||||
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
|
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
|
||||||
if (varView == null) { // Should only happen with NOWHERE
|
if (varView == null) { // Should only happen with NOWHERE
|
||||||
fireLocationEvent(coordinates);
|
fireLocationEvent(coordinates);
|
||||||
return;
|
return AsyncUtils.NIL;
|
||||||
}
|
}
|
||||||
materialize(coordinates).thenAcceptAsync(snap -> {
|
return materialize(coordinates).thenAcceptAsync(snap -> {
|
||||||
if (!coordinates.equals(current)) {
|
if (!coordinates.equals(current)) {
|
||||||
return; // We navigated elsewhere before emulation completed
|
return; // We navigated elsewhere before emulation completed
|
||||||
}
|
}
|
||||||
varView.setSnap(snap);
|
varView.setSnap(snap);
|
||||||
fireLocationEvent(coordinates);
|
fireLocationEvent(coordinates);
|
||||||
}, AsyncUtils.SWING_EXECUTOR);
|
}, SwingExecutorService.MAYBE_NOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
|
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
|
||||||
|
@ -977,14 +998,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if (!Objects.equals(prev.getTrace(), resolved.getTrace())) {
|
|
||||||
return recorder.getTarget();
|
return recorder.getTarget();
|
||||||
//}
|
|
||||||
//return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activate(DebuggerCoordinates coordinates) {
|
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
||||||
|
boolean syncTargetFocus) {
|
||||||
DebuggerCoordinates prev;
|
DebuggerCoordinates prev;
|
||||||
DebuggerCoordinates resolved;
|
DebuggerCoordinates resolved;
|
||||||
synchronized (listenersByTrace) {
|
synchronized (listenersByTrace) {
|
||||||
|
@ -992,34 +1011,34 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
resolved = doSetCurrent(coordinates);
|
resolved = doSetCurrent(coordinates);
|
||||||
}
|
}
|
||||||
if (resolved == null) {
|
if (resolved == null) {
|
||||||
return;
|
return AsyncUtils.NIL;
|
||||||
|
}
|
||||||
|
CompletableFuture<Void> future = prepareViewAndFireEvent(resolved);
|
||||||
|
if (!syncTargetFocus) {
|
||||||
|
return future;
|
||||||
}
|
}
|
||||||
prepareViewAndFireEvent(resolved);
|
|
||||||
if (!synchronizeFocus.get()) {
|
if (!synchronizeFocus.get()) {
|
||||||
return;
|
return future;
|
||||||
}
|
}
|
||||||
TraceRecorder recorder = resolved.getRecorder();
|
TraceRecorder recorder = resolved.getRecorder();
|
||||||
if (recorder == null) {
|
if (recorder == null) {
|
||||||
return;
|
return future;
|
||||||
}
|
}
|
||||||
TargetObject focus = translateToFocus(prev, resolved);
|
TargetObject focus = translateToFocus(prev, resolved);
|
||||||
if (focus == null || !focus.isValid()) {
|
if (focus == null || !focus.isValid()) {
|
||||||
return;
|
return future;
|
||||||
}
|
}
|
||||||
recorder.requestFocus(focus);
|
recorder.requestFocus(focus);
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void activate(DebuggerCoordinates coordinates) {
|
||||||
|
activateAndNotify(coordinates, true); // Drop future on floor
|
||||||
}
|
}
|
||||||
|
|
||||||
public void activateNoFocusChange(DebuggerCoordinates coordinates) {
|
public void activateNoFocusChange(DebuggerCoordinates coordinates) {
|
||||||
//DebuggerCoordinates prev;
|
activateAndNotify(coordinates, false); // Drop future on floor
|
||||||
DebuggerCoordinates resolved;
|
|
||||||
synchronized (listenersByTrace) {
|
|
||||||
//prev = current;
|
|
||||||
resolved = doSetCurrent(coordinates);
|
|
||||||
}
|
|
||||||
if (resolved == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prepareViewAndFireEvent(resolved);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -295,22 +295,29 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
|
||||||
@Override
|
@Override
|
||||||
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
|
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
|
||||||
synchronized (injects) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
for (DisassemblyInject i : injects) {
|
catch (Throwable e) {
|
||||||
i.pre(plugin.getTool(), this, view, thread,
|
Msg.error(this, "Auto-disassembly error: " + e);
|
||||||
new AddressSet(start, start),
|
return true; // No pop-up errors
|
||||||
disassemblable);
|
|
||||||
}
|
}
|
||||||
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;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
|
|
||||||
@ServiceInfo( //
|
@ServiceInfo( //
|
||||||
defaultProvider = DebuggerLogicalBreakpointServicePlugin.class, //
|
defaultProvider = DebuggerLogicalBreakpointServicePlugin.class,
|
||||||
description = "Aggregate breakpoints for programs and live traces" //
|
description = "Aggregate breakpoints for programs and live traces")
|
||||||
)
|
|
||||||
public interface DebuggerLogicalBreakpointService {
|
public interface DebuggerLogicalBreakpointService {
|
||||||
/**
|
/**
|
||||||
* Get all logical breakpoints known to the tool.
|
* 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();
|
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
|
* Get the active trace
|
||||||
*
|
*
|
||||||
|
@ -212,13 +220,28 @@ public interface DebuggerTraceManagerService {
|
||||||
void closeDeadTraces();
|
void closeDeadTraces();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the given coordinates
|
* Activate the given coordinates with future notification
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This operation may be completed asynchronously, esp., if emulation is required to materialize
|
* 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
|
* the coordinates. The returned future is completed when the coordinates are actually
|
||||||
* example, if the thread is not specified, the manager may activate the last-active thread for
|
* materialized and active. The coordinates are "resolved" as a means of filling in missing
|
||||||
* the desired trace.
|
* 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
|
* @param coordinates the desired coordinates
|
||||||
*/
|
*/
|
||||||
|
@ -383,6 +406,18 @@ public interface DebuggerTraceManagerService {
|
||||||
*/
|
*/
|
||||||
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coordinates);
|
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
|
* 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 =
|
PcodeExecutor<byte[]> exe =
|
||||||
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
|
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
|
||||||
exe.executeLine("RIP = 0x00400000");
|
exe.executeSleighLine("RIP = 0x00400000");
|
||||||
exe.executeLine("RSP = 0x0010fff8");
|
exe.executeSleighLine("RSP = 0x0010fff8");
|
||||||
|
|
||||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(snap0));
|
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(snap0));
|
||||||
asm.assemble(tb.addr(0x00400000), "SUB RSP,0x40");
|
asm.assemble(tb.addr(0x00400000), "SUB RSP,0x40");
|
||||||
|
|
|
@ -63,14 +63,14 @@ public class DebuggerWatchesPluginScreenShots extends GhidraScreenShotGenerator
|
||||||
|
|
||||||
PcodeExecutor<byte[]> executor0 =
|
PcodeExecutor<byte[]> executor0 =
|
||||||
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
|
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
|
||||||
executor0.executeLine("RSP = 0x7ffefff8");
|
executor0.executeSleighLine("RSP = 0x7ffefff8");
|
||||||
executor0.executeLine("*:4 (RSP+8) = 0x4030201");
|
executor0.executeSleighLine("*:4 (RSP+8) = 0x4030201");
|
||||||
|
|
||||||
PcodeExecutor<byte[]> executor1 =
|
PcodeExecutor<byte[]> executor1 =
|
||||||
TraceSleighUtils.buildByteExecutor(tb.trace, snap1, thread, 0);
|
TraceSleighUtils.buildByteExecutor(tb.trace, snap1, thread, 0);
|
||||||
executor1.executeLine("RSP = 0x7ffefff8");
|
executor1.executeSleighLine("RSP = 0x7ffefff8");
|
||||||
executor1.executeLine("*:4 (RSP+8) = 0x1020304");
|
executor1.executeSleighLine("*:4 (RSP+8) = 0x1020304");
|
||||||
executor1.executeLine("*:4 0x7fff0004:8 = 0x4A9A70C8");
|
executor1.executeSleighLine("*:4 0x7fff0004:8 = 0x4A9A70C8");
|
||||||
}
|
}
|
||||||
|
|
||||||
watchesProvider.addWatch("RSP");
|
watchesProvider.addWatch("RSP");
|
||||||
|
|
|
@ -583,6 +583,38 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
||||||
modelService.addModel(mb.testModel);
|
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() {
|
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();
|
createAndOpenTrace();
|
||||||
TraceThread thread1;
|
TraceThread thread1;
|
||||||
TraceThread thread2;
|
TraceThread thread2;
|
||||||
DebuggerListingProvider extraProvider = SwingExecutorService.INSTANCE
|
DebuggerListingProvider extraProvider = SwingExecutorService.LATER
|
||||||
.submit(() -> listingPlugin.createListingIfMissing(trackPc, true))
|
.submit(() -> listingPlugin.createListingIfMissing(trackPc, true))
|
||||||
.get();
|
.get();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
|
|
@ -21,31 +21,38 @@ import static org.junit.Assert.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.datatransfer.Clipboard;
|
import java.awt.datatransfer.Clipboard;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
import org.junit.experimental.categories.Category;
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
|
import docking.dnd.GClipboard;
|
||||||
import docking.menu.ActionState;
|
import docking.menu.ActionState;
|
||||||
import docking.menu.MultiStateDockingAction;
|
import docking.menu.MultiStateDockingAction;
|
||||||
import docking.widgets.EventTrigger;
|
import docking.widgets.EventTrigger;
|
||||||
|
import docking.widgets.OptionDialog;
|
||||||
import generic.test.category.NightlyCategory;
|
import generic.test.category.NightlyCategory;
|
||||||
import ghidra.GhidraOptions;
|
import ghidra.GhidraOptions;
|
||||||
import ghidra.app.plugin.core.byteviewer.ByteViewerComponent;
|
import ghidra.app.plugin.core.byteviewer.ByteViewerComponent;
|
||||||
import ghidra.app.plugin.core.byteviewer.ByteViewerPanel;
|
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.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
|
import ghidra.app.plugin.core.debug.gui.action.DebuggerGoToDialog;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
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.app.services.TraceRecorder;
|
||||||
import ghidra.async.SwingExecutorService;
|
import ghidra.async.SwingExecutorService;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
|
@ -70,11 +77,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
protected DebuggerMemoryBytesPlugin memBytesPlugin;
|
protected DebuggerMemoryBytesPlugin memBytesPlugin;
|
||||||
protected DebuggerMemoryBytesProvider memBytesProvider;
|
protected DebuggerMemoryBytesProvider memBytesProvider;
|
||||||
|
|
||||||
|
protected DebuggerStateEditingService editingService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUpMemoryBytesProviderTest() throws Exception {
|
public void setUpMemoryBytesProviderTest() throws Exception {
|
||||||
memBytesPlugin = addPlugin(tool, DebuggerMemoryBytesPlugin.class);
|
memBytesPlugin = addPlugin(tool, DebuggerMemoryBytesPlugin.class);
|
||||||
memBytesProvider = waitForComponentProvider(DebuggerMemoryBytesProvider.class);
|
memBytesProvider = waitForComponentProvider(DebuggerMemoryBytesProvider.class);
|
||||||
memBytesProvider.setVisible(true);
|
memBytesProvider.setVisible(true);
|
||||||
|
|
||||||
|
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void goToDyn(Address address) {
|
protected void goToDyn(Address address) {
|
||||||
|
@ -236,7 +247,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
TraceThread thread1;
|
TraceThread thread1;
|
||||||
TraceThread thread2;
|
TraceThread thread2;
|
||||||
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.INSTANCE
|
DebuggerMemoryBytesProvider extraProvider = SwingExecutorService.LATER
|
||||||
.submit(() -> memBytesPlugin.createViewerIfMissing(trackPc, true))
|
.submit(() -> memBytesPlugin.createViewerIfMissing(trackPc, true))
|
||||||
.get();
|
.get();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
@ -1068,7 +1079,6 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("TODO")
|
|
||||||
public void testEditLiveBytesWritesTarget() throws Exception {
|
public void testEditLiveBytesWritesTarget() throws Exception {
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
@ -1077,19 +1087,15 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
createTargetTraceMapper(mb.testProcess1));
|
createTargetTraceMapper(mb.testProcess1));
|
||||||
Trace trace = recorder.getTrace();
|
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");
|
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
|
||||||
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
|
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
|
||||||
|
|
||||||
goToDyn(addr(trace, 0x55550800));
|
goToDyn(addr(trace, 0x55550800));
|
||||||
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
|
|
||||||
performAction(actionEdit);
|
performAction(actionEdit);
|
||||||
|
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
|
||||||
Robot robot = new Robot();
|
|
||||||
robot.keyPress(KeyEvent.VK_4);
|
|
||||||
robot.keyRelease(KeyEvent.VK_4);
|
|
||||||
robot.keyPress(KeyEvent.VK_2);
|
|
||||||
robot.keyRelease(KeyEvent.VK_2);
|
|
||||||
|
|
||||||
performAction(actionEdit);
|
performAction(actionEdit);
|
||||||
|
|
||||||
byte[] data = new byte[4];
|
byte[] data = new byte[4];
|
||||||
|
@ -1100,8 +1106,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("TODO")
|
public void testEditTraceBytesWritesNotTarget() throws Exception {
|
||||||
public void testEditPastBytesWritesNotTarget() throws Exception {
|
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
|
||||||
|
@ -1109,31 +1114,22 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
createTargetTraceMapper(mb.testProcess1));
|
createTargetTraceMapper(mb.testProcess1));
|
||||||
Trace trace = recorder.getTrace();
|
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");
|
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
|
||||||
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
|
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
|
||||||
|
|
||||||
TraceSnapshot present = recorder.forceSnapshot();
|
|
||||||
|
|
||||||
goToDyn(addr(trace, 0x55550800));
|
goToDyn(addr(trace, 0x55550800));
|
||||||
long snap = present.getKey() - 1;
|
|
||||||
traceManager.activateSnap(snap);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
|
|
||||||
performAction(actionEdit);
|
performAction(actionEdit);
|
||||||
|
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
|
||||||
Robot robot = new Robot();
|
|
||||||
robot.keyPress(KeyEvent.VK_4);
|
|
||||||
robot.keyRelease(KeyEvent.VK_4);
|
|
||||||
robot.keyPress(KeyEvent.VK_2);
|
|
||||||
robot.keyRelease(KeyEvent.VK_2);
|
|
||||||
|
|
||||||
performAction(actionEdit);
|
performAction(actionEdit);
|
||||||
|
|
||||||
byte[] data = new byte[4];
|
byte[] data = new byte[4];
|
||||||
AddressSpace space = trace.getBaseAddressFactory().getDefaultAddressSpace();
|
AddressSpace space = trace.getBaseAddressFactory().getDefaultAddressSpace();
|
||||||
trace.getMemoryManager()
|
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);
|
assertArrayEquals(mb.arr(0x42, 0, 0, 0), data);
|
||||||
// Verify the target was not touched
|
// Verify the target was not touched
|
||||||
Arrays.fill(data, (byte) 0); // test model uses semisparse array
|
Arrays.fill(data, (byte) 0); // test model uses semisparse array
|
||||||
|
@ -1144,8 +1140,10 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("TODO")
|
|
||||||
public void testPasteLiveBytesWritesTarget() throws Exception {
|
public void testPasteLiveBytesWritesTarget() throws Exception {
|
||||||
|
addPlugin(tool, ClipboardPlugin.class);
|
||||||
|
ActionContext ctx;
|
||||||
|
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
|
||||||
|
@ -1153,6 +1151,8 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
createTargetTraceMapper(mb.testProcess1));
|
createTargetTraceMapper(mb.testProcess1));
|
||||||
Trace trace = recorder.getTrace();
|
Trace trace = recorder.getTrace();
|
||||||
|
|
||||||
|
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
|
||||||
|
|
||||||
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
|
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
|
||||||
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
|
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
|
||||||
|
|
||||||
|
@ -1160,10 +1160,19 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
||||||
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
|
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
|
||||||
performAction(actionEdit);
|
performAction(actionEdit);
|
||||||
|
|
||||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
Clipboard clipboard = GClipboard.getSystemClipboard();
|
||||||
clipboard.setContents(new StringSelection("42 53 64 75"), null);
|
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);
|
performAction(actionEdit);
|
||||||
byte[] data = new byte[4];
|
byte[] data = new byte[4];
|
||||||
|
|
|
@ -75,7 +75,7 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg
|
||||||
thread = tb.getOrAddThread("1", 0);
|
thread = tb.getOrAddThread("1", 0);
|
||||||
|
|
||||||
PcodeExecutor<byte[]> init = TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 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));
|
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||||
iit = asm.assemble(start,
|
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.action.NoneLocationTrackingSpec;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterTableColumns;
|
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.app.services.TraceRecorder;
|
||||||
import ghidra.async.AsyncTestUtils;
|
import ghidra.async.AsyncTestUtils;
|
||||||
import ghidra.program.model.data.*;
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
import ghidra.program.model.util.CodeUnitInsertionException;
|
import ghidra.program.model.util.CodeUnitInsertionException;
|
||||||
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
|
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
|
||||||
import ghidra.trace.model.Trace;
|
|
||||||
import ghidra.trace.model.listing.*;
|
import ghidra.trace.model.listing.*;
|
||||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||||
|
@ -56,6 +58,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
||||||
protected DebuggerRegistersPlugin registersPlugin;
|
protected DebuggerRegistersPlugin registersPlugin;
|
||||||
protected DebuggerRegistersProvider registersProvider;
|
protected DebuggerRegistersProvider registersProvider;
|
||||||
protected DebuggerListingPlugin listingPlugin;
|
protected DebuggerListingPlugin listingPlugin;
|
||||||
|
protected DebuggerStateEditingService editingService;
|
||||||
|
|
||||||
protected Register r0;
|
protected Register r0;
|
||||||
protected Register pc;
|
protected Register pc;
|
||||||
|
@ -76,6 +79,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
||||||
registersPlugin = addPlugin(tool, DebuggerRegistersPlugin.class);
|
registersPlugin = addPlugin(tool, DebuggerRegistersPlugin.class);
|
||||||
registersProvider = waitForComponentProvider(DebuggerRegistersProvider.class);
|
registersProvider = waitForComponentProvider(DebuggerRegistersProvider.class);
|
||||||
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
|
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
|
||||||
|
|
||||||
createTrace();
|
createTrace();
|
||||||
r0 = tb.language.getRegister("r0");
|
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) {
|
protected RegisterRow findRegisterRow(Register reg) {
|
||||||
RegisterRow row = getRegisterRow(reg);
|
RegisterRow row = getRegisterRow(reg);
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
|
@ -388,13 +362,15 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
||||||
// TODO: Make contextreg modifiable by Registers window
|
// TODO: Make contextreg modifiable by Registers window
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeadModifyValueEmulates() throws Exception {
|
public void testModifyValueEmu() throws Exception {
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
|
|
||||||
TraceThread thread = addThread();
|
TraceThread thread = addThread();
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||||
|
|
||||||
assertTrue(registersProvider.actionEnableEdits.isEnabled());
|
assertTrue(registersProvider.actionEnableEdits.isEnabled());
|
||||||
performAction(registersProvider.actionEnableEdits);
|
performAction(registersProvider.actionEnableEdits);
|
||||||
|
|
||||||
|
@ -410,56 +386,13 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
|
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||||
assertEquals(BigInteger.valueOf(0x1234),
|
assertEquals(BigInteger.valueOf(0x1234),
|
||||||
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
||||||
assertEquals(BigInteger.valueOf(0x1234), row.getValue());
|
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
|
// NOTE: Value modification only allowed on live target
|
||||||
|
|
||||||
@Test
|
@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.DebuggerListingPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
|
||||||
import ghidra.app.plugin.core.debug.gui.register.*;
|
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.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
|
||||||
import ghidra.app.services.ActionSource;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.services.TraceRecorder;
|
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
|
||||||
import ghidra.async.AsyncTestUtils;
|
import ghidra.async.AsyncTestUtils;
|
||||||
import ghidra.dbg.model.TestTargetRegisterBankInThread;
|
import ghidra.dbg.model.TestTargetRegisterBankInThread;
|
||||||
import ghidra.program.model.address.*;
|
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.lang.RegisterValue;
|
||||||
import ghidra.program.model.mem.Memory;
|
import ghidra.program.model.mem.Memory;
|
||||||
import ghidra.program.util.ProgramLocation;
|
import ghidra.program.util.ProgramLocation;
|
||||||
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.model.DefaultTraceLocation;
|
import ghidra.trace.model.DefaultTraceLocation;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.memory.*;
|
import ghidra.trace.model.memory.*;
|
||||||
|
@ -69,6 +71,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
protected DebuggerListingProvider listingProvider;
|
protected DebuggerListingProvider listingProvider;
|
||||||
protected DebuggerStaticMappingServicePlugin mappingService;
|
protected DebuggerStaticMappingServicePlugin mappingService;
|
||||||
protected CodeViewerProvider codeViewerProvider;
|
protected CodeViewerProvider codeViewerProvider;
|
||||||
|
protected DebuggerStateEditingService editingService;
|
||||||
|
|
||||||
protected Register r0;
|
protected Register r0;
|
||||||
protected Register r1;
|
protected Register r1;
|
||||||
|
@ -88,6 +91,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
|
||||||
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
|
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
|
||||||
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
|
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
|
||||||
|
editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class);
|
||||||
|
|
||||||
createTrace();
|
createTrace();
|
||||||
r0 = tb.language.getRegister("r0");
|
r0 = tb.language.getRegister("r0");
|
||||||
|
@ -259,7 +263,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertNoErr(row);
|
assertNoErr(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void runTestDeadIsEditable(String expression, boolean expectWritable) {
|
protected void runTestIsEditableEmu(String expression, boolean expectWritable) {
|
||||||
setRegisterValues(thread);
|
setRegisterValues(thread);
|
||||||
|
|
||||||
performAction(watchesProvider.actionAdd);
|
performAction(watchesProvider.actionAdd);
|
||||||
|
@ -269,6 +273,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
assertFalse(row.isValueEditable());
|
assertFalse(row.isValueEditable());
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertNoErr(row);
|
assertNoErr(row);
|
||||||
|
@ -279,21 +284,21 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeadIsRegisterEditable() {
|
public void testIsRegisterEditableEmu() {
|
||||||
runTestDeadIsEditable("r0", true);
|
runTestIsEditableEmu("r0", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeadIsUniqueEditable() {
|
public void testIsUniqueEditableEmu() {
|
||||||
runTestDeadIsEditable("r0 + 8", false);
|
runTestIsEditableEmu("r0 + 8", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeadIsMemoryEditable() {
|
public void testIsMemoryEditableEmu() {
|
||||||
runTestDeadIsEditable("*:8 r0", true);
|
runTestIsEditableEmu("*:8 r0", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WatchRow prepareTestDeadEdit(String expression) {
|
protected WatchRow prepareTestEditEmu(String expression) {
|
||||||
setRegisterValues(thread);
|
setRegisterValues(thread);
|
||||||
|
|
||||||
performAction(watchesProvider.actionAdd);
|
performAction(watchesProvider.actionAdd);
|
||||||
|
@ -302,20 +307,23 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||||
|
|
||||||
performAction(watchesProvider.actionEnableEdits);
|
performAction(watchesProvider.actionEnableEdits);
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeadEditRegister() {
|
public void testEditRegisterEmu() {
|
||||||
WatchRow row = prepareTestDeadEdit("r0");
|
WatchRow row = prepareTestEditEmu("r0");
|
||||||
TraceMemoryRegisterSpace regVals =
|
TraceMemoryRegisterSpace regVals =
|
||||||
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||||
|
|
||||||
row.setRawValueString("0x1234");
|
row.setRawValueString("0x1234");
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
|
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||||
assertEquals(BigInteger.valueOf(0x1234),
|
assertEquals(BigInteger.valueOf(0x1234),
|
||||||
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
||||||
assertEquals("0x1234", row.getRawValueString());
|
assertEquals("0x1234", row.getRawValueString());
|
||||||
|
@ -324,6 +332,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
row.setRawValueString("1234"); // Decimal this time
|
row.setRawValueString("1234"); // Decimal this time
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
|
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||||
assertEquals(BigInteger.valueOf(1234),
|
assertEquals(BigInteger.valueOf(1234),
|
||||||
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
||||||
assertEquals("0x4d2", row.getRawValueString());
|
assertEquals("0x4d2", row.getRawValueString());
|
||||||
|
@ -331,14 +340,15 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeadEditMemory() {
|
public void testEditMemoryEmu() {
|
||||||
WatchRow row = prepareTestDeadEdit("*:8 r0");
|
WatchRow row = prepareTestEditEmu("*:8 r0");
|
||||||
TraceMemoryOperations mem = tb.trace.getMemoryManager();
|
TraceMemoryOperations mem = tb.trace.getMemoryManager();
|
||||||
ByteBuffer buf = ByteBuffer.allocate(8);
|
ByteBuffer buf = ByteBuffer.allocate(8);
|
||||||
|
|
||||||
row.setRawValueString("0x1234");
|
row.setRawValueString("0x1234");
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
|
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||||
buf.clear();
|
buf.clear();
|
||||||
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
|
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
|
||||||
buf.flip();
|
buf.flip();
|
||||||
|
@ -348,6 +358,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
row.setRawValueString("{ 12 34 56 78 9a bc de f0 }");
|
row.setRawValueString("{ 12 34 56 78 9a bc de f0 }");
|
||||||
waitForPass(() -> {
|
waitForPass(() -> {
|
||||||
long viewSnap = traceManager.getCurrent().getViewSnap();
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
|
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||||
buf.clear();
|
buf.clear();
|
||||||
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
|
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
|
||||||
buf.flip();
|
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();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
bank = mb.testThread1.addRegisterBank();
|
bank = mb.testThread1.addRegisterBank();
|
||||||
|
@ -372,6 +383,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
traceManager.openTrace(trace);
|
traceManager.openTrace(trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
|
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
performAction(watchesProvider.actionAdd);
|
performAction(watchesProvider.actionAdd);
|
||||||
|
@ -383,44 +395,35 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLiveEditRegister() throws Throwable {
|
public void testEditRegisterTarget() throws Throwable {
|
||||||
WatchRow row = prepareTestLiveEdit("r0");
|
WatchRow row = prepareTestEditTarget("r0");
|
||||||
|
|
||||||
row.setRawValueString("0x1234");
|
row.setRawValueString("0x1234");
|
||||||
retryVoid(() -> {
|
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));
|
}, List.of(AssertionError.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLiveEditMemory() throws Throwable {
|
public void testEditMemoryTarget() throws Throwable {
|
||||||
WatchRow row = prepareTestLiveEdit("*:8 r0");
|
WatchRow row = prepareTestEditTarget("*:8 r0");
|
||||||
|
|
||||||
row.setRawValueString("0x1234");
|
row.setRawValueString("0x1234");
|
||||||
retryVoid(() -> {
|
retryVoid(() -> {
|
||||||
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34),
|
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));
|
}, List.of(AssertionError.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testLiveEditNonMappableRegister() throws Throwable {
|
public void testEditNonMappableRegisterTarget() throws Throwable {
|
||||||
WatchRow row = prepareTestLiveEdit("r1");
|
WatchRow row = prepareTestEditTarget("r1");
|
||||||
TraceThread thread = recorder.getTraceThread(mb.testThread1);
|
TraceThread thread = recorder.getTraceThread(mb.testThread1);
|
||||||
// Sanity check
|
// Sanity check
|
||||||
assertFalse(recorder.isRegisterOnTarget(thread, r1));
|
assertFalse(recorder.isRegisterOnTarget(thread, r1));
|
||||||
|
|
||||||
|
assertFalse(row.isValueEditable());
|
||||||
row.setRawValueString("0x1234");
|
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 {
|
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();
|
Cleaner CLEANER = Cleaner.create();
|
||||||
|
|
||||||
ExecutorService FRAMEWORK_EXECUTOR = Executors.newWorkStealingPool();
|
ExecutorService FRAMEWORK_EXECUTOR = Executors.newWorkStealingPool();
|
||||||
ExecutorService SWING_EXECUTOR = SwingExecutorService.INSTANCE;
|
ExecutorService SWING_EXECUTOR = SwingExecutorService.LATER;
|
||||||
|
|
||||||
CompletableFuture<Void> NIL = CompletableFuture.completedFuture(null);
|
CompletableFuture<Void> NIL = CompletableFuture.completedFuture(null);
|
||||||
|
|
||||||
|
|
|
@ -22,14 +22,30 @@ import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
import ghidra.async.seq.AsyncSequenceActionRuns;
|
import ghidra.async.seq.AsyncSequenceActionRuns;
|
||||||
import ghidra.async.seq.AsyncSequenceWithoutTemp;
|
import ghidra.async.seq.AsyncSequenceWithoutTemp;
|
||||||
|
import ghidra.util.Swing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper for {@link SwingUtilities#invokeLater(Runnable)} that implements
|
* A wrapper for {@link SwingUtilities#invokeLater(Runnable)} that implements
|
||||||
* {@link ExecutorService}. This makes it a suitable first parameter to
|
* {@link ExecutorService}. This makes it a suitable first parameter to
|
||||||
* {@link AsyncSequenceWithoutTemp#then(ExecutorService, AsyncSequenceActionRuns)} and similar.
|
* {@link AsyncSequenceWithoutTemp#then(ExecutorService, AsyncSequenceActionRuns)} and similar.
|
||||||
*/
|
*/
|
||||||
public class SwingExecutorService extends AbstractExecutorService {
|
public abstract class SwingExecutorService extends AbstractExecutorService {
|
||||||
public static final SwingExecutorService INSTANCE = new SwingExecutorService();
|
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() {
|
private SwingExecutorService() {
|
||||||
}
|
}
|
||||||
|
@ -59,8 +75,4 @@ public class SwingExecutorService extends AbstractExecutorService {
|
||||||
throw new UnsupportedOperationException();
|
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.Collection;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
|
||||||
|
|
||||||
import ghidra.async.AsyncUtils.TemperamentalRunnable;
|
import ghidra.async.AsyncUtils.TemperamentalRunnable;
|
||||||
import ghidra.async.AsyncUtils.TemperamentalSupplier;
|
import ghidra.async.AsyncUtils.TemperamentalSupplier;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
@ -30,7 +28,7 @@ public interface AsyncTestUtils {
|
||||||
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
SystemUtilities.isInTestingBatchMode() ? 5000 : Long.MAX_VALUE;
|
||||||
static final long RETRY_INTERVAL_MS = 100;
|
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
|
// Do this instead of plain ol' .get(time), to ease debugging
|
||||||
// When suspended in .get(time), you can't introspect much, otherwise
|
// When suspended in .get(time), you can't introspect much, otherwise
|
||||||
long started = System.currentTimeMillis();
|
long started = System.currentTimeMillis();
|
||||||
|
@ -40,15 +38,11 @@ public interface AsyncTestUtils {
|
||||||
}
|
}
|
||||||
catch (TimeoutException e) {
|
catch (TimeoutException e) {
|
||||||
if (Long.compareUnsigned(System.currentTimeMillis() - started, TIMEOUT_MS) >= 0) {
|
if (Long.compareUnsigned(System.currentTimeMillis() - started, TIMEOUT_MS) >= 0) {
|
||||||
throw new RuntimeException(AsyncUtils.unwrapThrowable(e));
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Throwable unwrapped = AsyncUtils.unwrapThrowable(e);
|
throw AsyncUtils.unwrapThrowable(e);
|
||||||
if (unwrapped instanceof RuntimeException) {
|
|
||||||
throw (RuntimeException) unwrapped;
|
|
||||||
}
|
|
||||||
return ExceptionUtils.rethrow(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,10 +54,12 @@ import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||||
import ghidra.trace.database.time.DBTraceTimeManager;
|
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.util.TraceChangeManager;
|
import ghidra.trace.util.TraceChangeManager;
|
||||||
import ghidra.trace.util.TraceChangeRecord;
|
import ghidra.trace.util.TraceChangeRecord;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.database.*;
|
import ghidra.util.database.*;
|
||||||
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.datastruct.WeakValueHashMap;
|
import ghidra.util.datastruct.WeakValueHashMap;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.VersionException;
|
import ghidra.util.exception.VersionException;
|
||||||
|
@ -135,6 +137,8 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
||||||
protected DBTraceVariableSnapProgramView programView;
|
protected DBTraceVariableSnapProgramView programView;
|
||||||
protected Map<DBTraceVariableSnapProgramView, Void> programViews = new WeakHashMap<>();
|
protected Map<DBTraceVariableSnapProgramView, Void> programViews = new WeakHashMap<>();
|
||||||
protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashMap<>();
|
protected Map<Long, DBTraceProgramView> fixedProgramViews = new WeakValueHashMap<>();
|
||||||
|
protected ListenerSet<TraceProgramViewListener> viewListeners =
|
||||||
|
new ListenerSet<>(TraceProgramViewListener.class);
|
||||||
|
|
||||||
public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer)
|
public DBTrace(String name, CompilerSpec baseCompilerSpec, Object consumer)
|
||||||
throws IOException, LanguageNotFoundException {
|
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
|
// NOTE: addListener synchronizes on this and might generate callbacks immediately
|
||||||
public synchronized DBTraceProgramView getFixedProgramView(long snap) {
|
public synchronized DBTraceProgramView getFixedProgramView(long snap) {
|
||||||
// NOTE: The new viewport will need to read from the time manager during init
|
// NOTE: The new viewport will need to read from the time manager during init
|
||||||
|
DBTraceProgramView view;
|
||||||
try (LockHold hold = lockRead()) {
|
try (LockHold hold = lockRead()) {
|
||||||
synchronized (fixedProgramViews) {
|
synchronized (fixedProgramViews) {
|
||||||
DBTraceProgramView view = fixedProgramViews.computeIfAbsent(snap, t -> {
|
view = fixedProgramViews.get(snap);
|
||||||
Msg.debug(this, "Creating fixed view at snap=" + snap);
|
if (view != null) {
|
||||||
return new DBTraceProgramView(this, snap, baseCompilerSpec);
|
return view;
|
||||||
});
|
}
|
||||||
return view;
|
Msg.debug(this, "Creating fixed view at snap=" + snap);
|
||||||
|
view = new DBTraceProgramView(this, snap, baseCompilerSpec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewListeners.fire.viewCreated(view);
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
// NOTE: Ditto getFixedProgramView
|
// NOTE: Ditto getFixedProgramView
|
||||||
public synchronized DBTraceVariableSnapProgramView createProgramView(long snap) {
|
public synchronized DBTraceVariableSnapProgramView createProgramView(long snap) {
|
||||||
// NOTE: The new viewport will need to read from the time manager during init
|
// NOTE: The new viewport will need to read from the time manager during init
|
||||||
|
DBTraceVariableSnapProgramView view;
|
||||||
try (LockHold hold = lockRead()) {
|
try (LockHold hold = lockRead()) {
|
||||||
synchronized (programViews) {
|
synchronized (programViews) {
|
||||||
DBTraceVariableSnapProgramView view =
|
view = new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
|
||||||
new DBTraceVariableSnapProgramView(this, snap, baseCompilerSpec);
|
|
||||||
programViews.put(view, null);
|
programViews.put(view, null);
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewListeners.fire.viewCreated(view);
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -721,6 +730,18 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
|
||||||
return getOptions(TRACE_INFO).getDate(DATE_CREATED, new Date(0));
|
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) {
|
protected void allViews(Consumer<DBTraceProgramView> action) {
|
||||||
Collection<DBTraceProgramView> all = new ArrayList<>();
|
Collection<DBTraceProgramView> all = new ArrayList<>();
|
||||||
synchronized (programViews) {
|
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) {
|
public void updateViewsAddRegionBlock(TraceMemoryRegion region) {
|
||||||
allViews(v -> v.updateMemoryAddRegionBlock(region));
|
allViews(v -> v.updateMemoryAddRegionBlock(region));
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ import ghidra.framework.store.LockException;
|
||||||
import ghidra.program.database.mem.*;
|
import ghidra.program.database.mem.*;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.mem.*;
|
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.Trace;
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
|
@ -47,6 +48,8 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||||
protected boolean forceFullView = false;
|
protected boolean forceFullView = false;
|
||||||
protected long snap;
|
protected long snap;
|
||||||
|
|
||||||
|
protected LiveMemoryHandler memoryWriteRedirect;
|
||||||
|
|
||||||
public AbstractDBTraceProgramViewMemory(DBTraceProgramView program) {
|
public AbstractDBTraceProgramViewMemory(DBTraceProgramView program) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
this.memoryManager = program.trace.getMemoryManager();
|
this.memoryManager = program.trace.getMemoryManager();
|
||||||
|
@ -155,7 +158,7 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
|
public void setLiveMemoryHandler(LiveMemoryHandler handler) {
|
||||||
throw new UnsupportedOperationException();
|
this.memoryWriteRedirect = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -329,6 +332,10 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setByte(Address addr, byte value) throws MemoryAccessException {
|
public void setByte(Address addr, byte value) throws MemoryAccessException {
|
||||||
|
if (memoryWriteRedirect != null) {
|
||||||
|
memoryWriteRedirect.putByte(addr, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
|
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
|
||||||
if (space.putBytes(snap, addr, ByteBuffer.wrap(new byte[] { value })) != 1) {
|
if (space.putBytes(snap, addr, ByteBuffer.wrap(new byte[] { value })) != 1) {
|
||||||
throw new MemoryAccessException();
|
throw new MemoryAccessException();
|
||||||
|
@ -338,6 +345,10 @@ public abstract class AbstractDBTraceProgramViewMemory
|
||||||
@Override
|
@Override
|
||||||
public void setBytes(Address addr, byte[] source, int sIndex, int size)
|
public void setBytes(Address addr, byte[] source, int sIndex, int size)
|
||||||
throws MemoryAccessException {
|
throws MemoryAccessException {
|
||||||
|
if (memoryWriteRedirect != null) {
|
||||||
|
memoryWriteRedirect.putBytes(addr, source, sIndex, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
|
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
|
||||||
if (space.putBytes(snap, addr, ByteBuffer.wrap(source, sIndex, size)) != size) {
|
if (space.putBytes(snap, addr, ByteBuffer.wrap(source, sIndex, size)) != size) {
|
||||||
throw new MemoryAccessException();
|
throw new MemoryAccessException();
|
||||||
|
|
|
@ -369,6 +369,10 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||||
public static final TraceSnapshotChangeType<Void> DELETED = new TraceSnapshotChangeType<>();
|
public static final TraceSnapshotChangeType<Void> DELETED = new TraceSnapshotChangeType<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface TraceProgramViewListener {
|
||||||
|
void viewCreated(TraceProgramView view);
|
||||||
|
}
|
||||||
|
|
||||||
Language getBaseLanguage();
|
Language getBaseLanguage();
|
||||||
|
|
||||||
CompilerSpec getBaseCompilerSpec();
|
CompilerSpec getBaseCompilerSpec();
|
||||||
|
@ -412,6 +416,13 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||||
|
|
||||||
TraceVariableSnapProgramView createProgramView(long snap);
|
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
|
* Get the "canonical" program view for this trace
|
||||||
*
|
*
|
||||||
|
@ -423,6 +434,10 @@ public interface Trace extends DataTypeManagerDomainObject {
|
||||||
*/
|
*/
|
||||||
TraceVariableSnapProgramView getProgramView();
|
TraceVariableSnapProgramView getProgramView();
|
||||||
|
|
||||||
|
void addProgramViewListener(TraceProgramViewListener listener);
|
||||||
|
|
||||||
|
void removeProgramViewListener(TraceProgramViewListener listener);
|
||||||
|
|
||||||
LockHold lockRead();
|
LockHold lockRead();
|
||||||
|
|
||||||
LockHold lockWrite();
|
LockHold lockWrite();
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.model.program;
|
package ghidra.trace.model.program;
|
||||||
|
|
||||||
|
import ghidra.program.model.mem.LiveMemoryHandler;
|
||||||
import ghidra.program.model.mem.Memory;
|
import ghidra.program.model.mem.Memory;
|
||||||
|
|
||||||
public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
|
public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
|
||||||
|
@ -24,4 +25,13 @@ public interface TraceProgramViewMemory extends Memory, SnapSpecificTraceView {
|
||||||
void setForceFullView(boolean forceFullView);
|
void setForceFullView(boolean forceFullView);
|
||||||
|
|
||||||
boolean isForceFullView();
|
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;
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
import java.util.List;
|
import java.math.BigInteger;
|
||||||
import java.util.Objects;
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.help.UnsupportedOperationException;
|
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.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.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class PatchStep implements Step {
|
public class PatchStep implements Step {
|
||||||
protected final long threadKey;
|
protected final long threadKey;
|
||||||
protected final String sleigh;
|
protected String sleigh;
|
||||||
protected final int hashCode;
|
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) {
|
public static PatchStep parse(long threadKey, String stepSpec) {
|
||||||
// TODO: Can I parse and validate the sleigh here?
|
// 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
|
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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return hashCode;
|
return hashCode;
|
||||||
|
@ -162,4 +279,120 @@ public class PatchStep implements Step {
|
||||||
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";"));
|
PcodeProgram prog = emuThread.getMachine().compileSleigh("schedule", List.of(sleigh + ";"));
|
||||||
emuThread.getExecutor().execute(prog, emuThread.getUseropLibrary());
|
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.PcodeMachine;
|
||||||
import ghidra.pcode.emu.PcodeThread;
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.program.model.lang.Language;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.thread.TraceThreadManager;
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
|
@ -122,7 +123,7 @@ public class Sequence implements Comparable<Sequence> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (steps.isEmpty()) {
|
if (steps.isEmpty()) {
|
||||||
steps.add(step);
|
steps.add(step.clone());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Step last = steps.get(steps.size() - 1);
|
Step last = steps.get(steps.size() - 1);
|
||||||
|
@ -157,6 +158,17 @@ public class Sequence implements Comparable<Sequence> {
|
||||||
steps.addAll(clone.subList(2, size));
|
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
|
* Rewind this sequence the given step count
|
||||||
*
|
*
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.model.time.schedule;
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import ghidra.pcode.emu.PcodeMachine;
|
import ghidra.pcode.emu.PcodeMachine;
|
||||||
import ghidra.pcode.emu.PcodeThread;
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.program.model.lang.Language;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.thread.TraceThreadManager;
|
import ghidra.trace.model.thread.TraceThreadManager;
|
||||||
import ghidra.util.exception.CancelledException;
|
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,
|
<T> void execute(PcodeThread<T> emuThread, Consumer<PcodeThread<T>> stepAction,
|
||||||
TaskMonitor monitor) throws CancelledException;
|
TaskMonitor monitor) throws CancelledException;
|
||||||
|
|
||||||
|
long coalescePatches(Language language, List<Step> steps);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.model.time.schedule;
|
package ghidra.trace.model.time.schedule;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import ghidra.pcode.emu.PcodeThread;
|
import ghidra.pcode.emu.PcodeThread;
|
||||||
|
import ghidra.program.model.lang.Language;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
@ -194,4 +196,9 @@ public class TickStep implements Step {
|
||||||
stepAction.accept(emuThread);
|
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);
|
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
|
* 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()) {
|
if (!this.pSteps.isNop()) {
|
||||||
Sequence pTicks = this.pSteps.clone();
|
Sequence pTicks = this.pSteps.clone();
|
||||||
pTicks.advance(new PatchStep(thread.getKey(), sleigh));
|
pTicks.advance(new PatchStep(thread.getKey(), sleigh));
|
||||||
|
pTicks.coalescePatches(thread.getTrace().getBaseLanguage());
|
||||||
return new TraceSchedule(snap, steps.clone(), pTicks);
|
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||||
}
|
}
|
||||||
Sequence ticks = this.steps.clone();
|
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());
|
return new TraceSchedule(snap, ticks, new Sequence());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,7 +334,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
public PcodeFrame getFrame() {
|
public PcodeFrame getFrame() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Instruction getInstruction() {
|
public Instruction getInstruction() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -564,4 +564,27 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
time.finish(tb.trace, TraceSchedule.parse("1:4;t0-4"), machine, TaskMonitor.DUMMY);
|
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.collect.*;
|
||||||
import com.google.common.primitives.UnsignedLong;
|
import com.google.common.primitives.UnsignedLong;
|
||||||
|
|
||||||
|
import ghidra.util.MathUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A sparse byte array characterized by contiguous dense regions
|
* 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
|
* Check how many contiguous bytes are available starting at the given address
|
||||||
*
|
*
|
||||||
|
|
|
@ -72,7 +72,7 @@ public class PcodeExecutor<T> {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void executeLine(String line) {
|
public void executeSleighLine(String line) {
|
||||||
PcodeProgram program = SleighProgramCompiler.compileProgram(language,
|
PcodeProgram program = SleighProgramCompiler.compileProgram(language,
|
||||||
"line", List.of(line + ";"), SleighUseropLibrary.NIL);
|
"line", List.of(line + ";"), SleighUseropLibrary.NIL);
|
||||||
execute(program, SleighUseropLibrary.nil());
|
execute(program, SleighUseropLibrary.nil());
|
||||||
|
|
|
@ -116,6 +116,10 @@ public class PcodeProgram {
|
||||||
return language;
|
return language;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PcodeOp> getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
public <T> void execute(PcodeExecutor<T> executor, SleighUseropLibrary<T> library) {
|
public <T> void execute(PcodeExecutor<T> executor, SleighUseropLibrary<T> library) {
|
||||||
executor.execute(this, library);
|
executor.execute(this, library);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.generic.util.datastruct;
|
package ghidra.generic.util.datastruct;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -26,8 +25,6 @@ import org.junit.Test;
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.*;
|
||||||
import com.google.common.primitives.UnsignedLong;
|
import com.google.common.primitives.UnsignedLong;
|
||||||
|
|
||||||
import ghidra.generic.util.datastruct.SemisparseByteArray;
|
|
||||||
|
|
||||||
public class SemisparseByteArrayTest {
|
public class SemisparseByteArrayTest {
|
||||||
private static final String HELLO_WORLD = "Hello, World!";
|
private static final String HELLO_WORLD = "Hello, World!";
|
||||||
protected static final byte[] HW = HELLO_WORLD.getBytes();
|
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.
|
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();
|
return new AddressSet();
|
||||||
}
|
}
|
||||||
if (viewedAddresses == null) {
|
if (viewedAddresses == null) {
|
||||||
|
Address start = getStartAddress();
|
||||||
|
Address end = getEndAddress();
|
||||||
|
if (start == null || end == null) {
|
||||||
|
return new AddressSet();
|
||||||
|
}
|
||||||
viewedAddresses =
|
viewedAddresses =
|
||||||
map.getOriginalAddressSet().intersectRange(getStartAddress(), getEndAddress());
|
map.getOriginalAddressSet().intersectRange(start, end);
|
||||||
}
|
}
|
||||||
return viewedAddresses;
|
return viewedAddresses;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,19 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.assembler;
|
package ghidra.app.plugin.core.assembler;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
import ghidra.app.plugin.assembler.Assembler;
|
import ghidra.app.plugin.assembler.Assembler;
|
||||||
import ghidra.app.plugin.assembler.Assemblers;
|
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.CodeBrowserPlugin;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||||
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
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.data.TerminatedStringDataType;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.mem.Memory;
|
import ghidra.program.model.mem.Memory;
|
||||||
import ghidra.program.util.ProgramLocation;
|
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
@ -55,8 +47,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
private CodeViewerProvider codeViewer;
|
private CodeViewerProvider codeViewer;
|
||||||
|
|
||||||
private AssemblyDualTextField instructionInput;
|
private AssemblerPluginTestHelper helper;
|
||||||
private JTextField dataInput;
|
|
||||||
|
|
||||||
private ProgramDB program;
|
private ProgramDB program;
|
||||||
private AddressSpace space;
|
private AddressSpace space;
|
||||||
|
@ -73,24 +64,20 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
|
assemblerPlugin = addPlugin(tool, AssemblerPlugin.class);
|
||||||
|
|
||||||
codeViewer = waitForComponentProvider(CodeViewerProvider.class);
|
codeViewer = waitForComponentProvider(CodeViewerProvider.class);
|
||||||
|
|
||||||
instructionInput = assemblerPlugin.patchInstructionAction.input;
|
|
||||||
dataInput = assemblerPlugin.patchDataAction.input;
|
|
||||||
|
|
||||||
program = createDefaultProgram(getName(), "Toy:BE:64:default", this);
|
program = createDefaultProgram(getName(), "Toy:BE:64:default", this);
|
||||||
|
|
||||||
space = program.getAddressFactory().getDefaultAddressSpace();
|
space = program.getAddressFactory().getDefaultAddressSpace();
|
||||||
memory = program.getMemory();
|
memory = program.getMemory();
|
||||||
listing = program.getListing();
|
listing = program.getListing();
|
||||||
|
|
||||||
|
helper = new AssemblerPluginTestHelper(assemblerPlugin, codeViewer, program);
|
||||||
|
|
||||||
try (ProgramTransaction trans = ProgramTransaction.open(program, "Setup")) {
|
try (ProgramTransaction trans = ProgramTransaction.open(program, "Setup")) {
|
||||||
memory.createInitializedBlock(".text", space.getAddress(0x00400000), 0x1000, (byte) 0,
|
memory.createInitializedBlock(".text", space.getAddress(0x00400000), 0x1000, (byte) 0,
|
||||||
TaskMonitor.DUMMY, false);
|
TaskMonitor.DUMMY, false);
|
||||||
trans.commit();
|
trans.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snuff the assembler's warning prompt
|
|
||||||
assemblerPlugin.patchInstructionAction.shownWarning.put(program.getLanguage(), true);
|
|
||||||
|
|
||||||
env.showTool();
|
env.showTool();
|
||||||
programManager.openProgram(program);
|
programManager.openProgram(program);
|
||||||
}
|
}
|
||||||
|
@ -100,45 +87,10 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
env.dispose();
|
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
|
@Test
|
||||||
public void testActionPatchInstructionNoExisting() throws Exception {
|
public void testActionPatchInstructionNoExisting() throws Exception {
|
||||||
Address address = space.getAddress(0x00400000);
|
Address address = space.getAddress(0x00400000);
|
||||||
goTo(address);
|
Instruction ins = helper.patchInstructionAt(address, "", "imm r0, #1234");
|
||||||
|
|
||||||
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));
|
|
||||||
assertEquals("imm r0,#0x4d2", ins.toString());
|
assertEquals("imm r0,#0x4d2", ins.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,45 +103,13 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
trans.commit();
|
trans.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
goTo(address);
|
Instruction ins = helper.patchInstructionAt(address, "imm r0,#0x4d2", "imm r0, #123");
|
||||||
|
|
||||||
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));
|
|
||||||
assertEquals("imm r0,#0x7b", ins.toString());
|
assertEquals("imm r0,#0x7b", ins.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test disabled on uninitialized memory
|
// TODO: Test disabled on uninitialized memory
|
||||||
// TODO: Test disabled on read-only listings
|
// 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
|
@Test
|
||||||
public void testActionPatchDataShortHexValid() throws Exception {
|
public void testActionPatchDataShortHexValid() throws Exception {
|
||||||
Address address = space.getAddress(0x00400000);
|
Address address = space.getAddress(0x00400000);
|
||||||
|
@ -198,7 +118,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
trans.commit();
|
trans.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Data data = doPatchAt(address, "0h", "1234h");
|
Data data = helper.patchDataAt(address, "0h", "1234h");
|
||||||
assertEquals("1234h", data.getDefaultValueRepresentation());
|
assertEquals("1234h", data.getDefaultValueRepresentation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +131,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
trans.commit();
|
trans.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Data data = doPatchAt(address, "0", "1234");
|
Data data = helper.patchDataAt(address, "0", "1234");
|
||||||
assertEquals("1234", data.getDefaultValueRepresentation());
|
assertEquals("1234", data.getDefaultValueRepresentation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +144,8 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
trans.commit();
|
trans.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello, Patch!\"");
|
Data data = helper.patchDataAt(address, "\"Hello, World!\"",
|
||||||
|
"\"Hello, Patch!\"");
|
||||||
assertEquals("\"Hello, Patch!\"", data.getDefaultValueRepresentation());
|
assertEquals("\"Hello, Patch!\"", data.getDefaultValueRepresentation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +158,8 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
trans.commit();
|
trans.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
Data data = doPatchAt(address, "\"Hello, World!\"", "\"Hello!\"");
|
Data data =
|
||||||
|
helper.patchDataAt(address, "\"Hello, World!\"", "\"Hello!\"");
|
||||||
assertEquals("\"Hello!\"", data.getDefaultValueRepresentation());
|
assertEquals("\"Hello!\"", data.getDefaultValueRepresentation());
|
||||||
assertEquals(7, data.getLength());
|
assertEquals(7, data.getLength());
|
||||||
}
|
}
|
||||||
|
@ -251,7 +173,7 @@ public class AssemblerPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
trans.commit();
|
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("\"Hello to you, too!\"", data.getDefaultValueRepresentation());
|
||||||
assertEquals(19, data.getLength());
|
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 javax.swing.JComponent;
|
||||||
|
|
||||||
|
import docking.action.ToggleDockingAction;
|
||||||
import ghidra.GhidraOptions;
|
import ghidra.GhidraOptions;
|
||||||
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
|
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
|
||||||
import ghidra.app.plugin.core.format.*;
|
import ghidra.app.plugin.core.format.*;
|
||||||
|
@ -88,7 +89,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
|
||||||
|
|
||||||
protected Map<String, ByteViewerComponent> viewMap = new HashMap<>();
|
protected Map<String, ByteViewerComponent> viewMap = new HashMap<>();
|
||||||
|
|
||||||
protected ToggleEditAction editModeAction;
|
protected ToggleDockingAction editModeAction;
|
||||||
protected OptionsAction setOptionsAction;
|
protected OptionsAction setOptionsAction;
|
||||||
|
|
||||||
protected ProgramByteBlockSet blockSet;
|
protected ProgramByteBlockSet blockSet;
|
||||||
|
|