Merge remote-tracking branch 'origin/GP-2676_Dan_emuBreakpointEdit-REBASED-1--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-01-19 06:08:10 -05:00
commit c8643f524f
116 changed files with 5676 additions and 1993 deletions

View file

@ -65,6 +65,7 @@ color.debugger.plugin.resources.breakpoint.marker.disabled.ineffective = color.d
icon.debugger.object.populated = object-populated.png
icon.debugger.object.unpopulated = object-unpopulated.png
font.debugger.sleigh = font.monospaced
font.debugger.object.tree.renderer = Tahoma-plain-11
icon.debugger.display.graph = breakpoint-enable.png // TODO this icon was missing 'breakpoints.png'
@ -168,10 +169,11 @@ icon.debugger.diff = table_relationship.png
icon.debugger.diff.previous = up.png
icon.debugger.diff.next = down.png
icon.debugger.edit.mode.read.only = write-disabled.png
icon.debugger.edit.mode.write.target = write-target.png
icon.debugger.edit.mode.write.trace = write-trace.png
icon.debugger.edit.mode.write.emulator = write-emulator.png
icon.debugger.edit.mode.ro.target = record.png
icon.debugger.edit.mode.rw.target = write-target.png
icon.debugger.edit.mode.ro.trace = video-x-generic16.png
icon.debugger.edit.mode.rw.trace = write-trace.png
icon.debugger.edit.mode.rw.emulator = write-emulator.png
icon.debugger.marker.register = register-marker.png
icon.debugger.marker.event = icon.debugger.marker.register

View file

@ -62,8 +62,8 @@
breakpoints on arbitrary expressions, use the <A href=
"help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html#set_breakpoint">Set
Breakpoint</A> action of the Objects window. <B>NOTE:</B> These actions may also appear in
other address-based contexts, e.g., the decompiler listing; however, those contexts often lack
any indication of breakpoint presence or state.</P>
other address-based contexts, but not all of those contexts include visual breakpoint
indicators.</P>
<H3><A name="toggle_breakpoint"></A><IMG alt="" src="images/breakpoint-mixed.png"> Toggle
Breakpoint (K)</H3>

View file

@ -22,126 +22,145 @@
</TBODY>
</TABLE>
<P>The breakpoints window tabulates and manipulates breakpoints among all live and traced
targets. Only address-based breakpoints are tabulated. For other traps, e.g., "break on
<P>The breakpoints window tabulates and manipulates breakpoints among all traces, including
live targets. Only address-based breakpoints are tabulated. For other traps, e.g., "break on
exception," see the <A href=
"help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html">Objects Window</A>. Breakpoints
can also be manipulated from address-based views, especially the disassembly listings. See <A
href=
"help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html">Breakpoints in
the Listings</A>. Display of breakpoints in other views, e.g., the decompiler, is not yet
implemented.</P>
the Listings</A>.</P>
<P>Individual breakpoint locations from among the targets are consolidated into logical
<P>Individual breakpoint locations from among the traces are consolidated into logical
breakpoints, based on their addresses in the static listing. The static locations are typically
stored as bookmarks in their respective Ghidra programs, comprising the current breakpoint set.
See the <A href=
"help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static Mappings</A>
window for the finer details of address mapping. A breakpoint which cannot be mapped to a
static address becomes its own logical breakpoint at its dynamic address. The top table of the
provider displays logical breakpoints; the bottom table displays breakpoint locations.
<B>NOTE:</B> The breakpoints window cannot display or manipulate breakpoints from a target
until that target is recorded into a trace. Furthermore, dead traces are not included. Nor are
breakpoints from the past included, even when viewing past machine state.</P>
provider displays logical breakpoints; the bottom table displays individual trace breakpoint
locations. <B>NOTE:</B> The breakpoints window cannot display or manipulate breakpoints from a
live target until that target is recorded into a trace. Only those breakpoints visible at the
current snapshot of each trace are included. For live targets, this is typically the latest
snapshot, i.e., the present.</P>
<P>Depending on what is supported by the connected debugger, breakpoints can trap a target when
an address or range is executed, read, or written; using software or hardware mechanisms. In
case of "read" or "write," debuggers may differ in terminology, e.g., GDB might call them
"watchpoints," but Ghidra still calls these "breakpoints." Some debuggers allow the user to
specify a breakpoint location other than by address, but ultimately each specification is
realized by 0 or more addressable locations. To accommodate this, the <A href=
the case of "read" or "write" breakpoints, debuggers may differ in terminology. For example,
GDB might call them "watchpoints," but Ghidra still calls these "breakpoints." Some debuggers
allow the user to specify a breakpoint location other than by address, but ultimately each
specification is realized by 0 or more addressable locations. To accommodate this, the <A href=
"help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html">Objects</A> window will
typically display a list of specifications, each listing its locations as children. However,
the grouping of breakpoint locations into logical breakpoints by this manager is done
<EM>without respect</EM> to the specifications. Often the specification is at a higher stratum
than Ghidra natively understands, e.g., the source filename and line number, and so such
specifications are not relevant. Note, however, that the model might not permit locations to be
manipulated independently of their specification, which may limit how the manager can operate
on each breakpoint location.</P>
the grouping of breakpoint locations into logical breakpoints by Ghidra's breakpoint manager is
done <EM>without respect to</EM> the debugger's specifications. A specification may be at a
higher stratum than Ghidra natively understands, e.g., the source filename and line number, and
so such specifications are not relevant. Also note that the debugger might not permit locations
to be manipulated independently of their specifications. This may limit how Ghidra can operate,
since in that case, it must configure the specification, which may affect more locations than
intended.</P>
<P>When the <A href="help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">control
mode</A> is set to "trace" or "emulator," it is possible to rewind the trace to past snapshots
and examine old breakpoints. You may also emulate from those snapshots, even if the target is
no longer alive. By default, those historical breakpoints are disabled in the integrated
emulator, but they can be toggled in the usual ways. In addition, the locations can be
manipulated independently, since the emulator has its own breakpoint set. Emulated breakpoints
can be configured with conditions expressed in Sleigh using the <A href="#set_condition">Set
Condition</A> action, or configured to replace the instruction's semantics altogether using the
<A href="#set_injection">Set Injection</A> action.</P>
<P>Because of the logical grouping of breakpoints, it is possible for a breakpoint to be in a
mixed or inconsistent state. This happens quite commonly, e.g., when a breakpoint is placed in
a Ghidra program before that program is mapped in any traced target. Once mapped in, the
location of that breakpoint in the trace is computed and noted as missing. A logical breakpoint
without any location in a trace (i.e., on an actual target) is called <EM>ineffective</EM> and
is drawn in grey, e.g.: <IMG alt="" src="images/breakpoint-enable-ineff.png">. An enabled
logical breakpoint having a disabled location is called <EM>inconsistent</EM> and its icon will
include an exclamation mark: <IMG alt="" src="images/breakpoint-overlay-inconsistent.png">. A
disabled logical breakpoint having an enabled location is similarly inconsistent. Toggling an
ineffective or inconsistent logical breakpoint enables and/or places all its locations, aiming
for a consistent enabled state. Toggling it again disables all locations.</P>
a Ghidra program before that program is mapped to any trace. Once mapped, the location of that
breakpoint in the trace is computed and noted as missing. A logical breakpoint without any
location in a trace is called <EM>ineffective</EM> and is drawn in grey, e.g.: <IMG alt="" src=
"images/breakpoint-enable-ineff.png">. An enabled logical breakpoint having a disabled location
is called <EM>inconsistent</EM> and its icon will include an exclamation mark: <IMG alt="" src=
"images/breakpoint-overlay-inconsistent.png">. A disabled logical breakpoint having an enabled
location is similarly inconsistent. Toggling an ineffective or inconsistent logical breakpoint
enables and/or places all its mapped locations, aiming for a consistent enabled state. Toggling
it again disables all locations.</P>
<H2>Tables and Columns</H2>
<P>The top table, which lists logical breakpoints, has the following columns:</P>
<UL>
<LI>State - displays an icon indicating the state of the breakpoint. If rendered in grey, the
breakpoint has no locations, i.e., it is ineffective. If rendered with an exclamation mark
overlay, the breakpoint is inconsistent. Clicking the icon toggles the breakpoint.</LI>
<LI><B>State:</B> displays an icon indicating the state of the breakpoint. If rendered in
grey, the breakpoint has no locations, i.e., it is ineffective. If rendered with an
exclamation mark overlay, the breakpoint is inconsistent. Clicking the icon toggles the
breakpoint.</LI>
<UL style="list-style-type: none">
<LI><IMG alt="" src="images/breakpoint-enable.png"> Enabled: The logical breakpoint,
including all its locations, is enabled.</LI>
<LI><IMG alt="" src="images/breakpoint-enable.png"> <B>Enabled:</B> The logical
breakpoint, including all its locations, is enabled.</LI>
<LI><IMG alt="" src="images/breakpoint-disable.png"> Disabled: The logical breakpoint,
including all its locations, is disabled.</LI>
<LI><IMG alt="" src="images/breakpoint-disable.png"> <B>Disabled:</B> The logical
breakpoint, including all its locations, is disabled.</LI>
<LI><IMG alt="" src="images/breakpoint-mixed.png"> Mixed: (Listing only) Two logical
breakpoints at the same address have different states.</LI>
<LI><IMG alt="" src="images/breakpoint-mixed.png"> <B>Mixed:</B> (Listing only) Two
logical breakpoints at the same address have different states.</LI>
</UL>
<LI>Name - gives the user-defined name of the breakpoint. This cell is only populated and
modifiable when the breakpoint is bookmarked in a program, since the name is associated with
the static location.</LI>
<LI><B>Name:</B> gives the user-defined name of the breakpoint. This cell is only populated
and modifiable when the breakpoint is bookmarked in a program, since the name is associated
with the static location.</LI>
<LI>Address - gives the address of the breakpoint. This is typically the static address. If
the breakpoint cannot be mapped to a static address, this is its dynamic address.</LI>
<LI><B>Address:</B> gives the address of the breakpoint. This is typically the static
address. If the breakpoint cannot be mapped to a static address, this is its dynamic
address.</LI>
<LI>Image - gives the name of the static image, i.e., Ghidra program. If the breakpoint
<LI><B>Image:</B> gives the name of the static image, i.e., Ghidra program. If the breakpoint
cannot be mapped to a static location, this is blank.</LI>
<LI>Length - usually 1. For access breakpoints, this is the length of the address range (in
bytes).</LI>
<LI><B>Length:</B> usually 1. For access breakpoints, this is the length in bytes of the
address range.</LI>
<LI>Kinds - indicates the kind(s) of breakpoint: SW_EXECUTE, HW_EXECUTE, READ, and/or
<LI><B>Kinds:</B> indicates the kind(s) of breakpoint: SW_EXECUTE, HW_EXECUTE, READ, and/or
WRITE.</LI>
<LI>Locations - counts the number of locations included in this logical breakpoint, applying
the trace filter if active. Note that a logical breakpoint with 0 locations is
<LI><B>Locations:</B> counts the number of locations included in this logical breakpoint,
applying the trace filter if active. Note that a logical breakpoint with 0 locations is
ineffective.</LI>
<LI><B>Sleigh:</B> indicates whether or not the breakpoint has a customized Sleigh
configuration. This is only relevant for emulation.</LI>
</UL>
<P>The bottom table, which lists breakpoint locations, has the following columns:</P>
<UL>
<LI>State - displays an icon indicating the state of the location. If rendered with an
<LI><B>State:</B> displays an icon indicating the state of the location. If rendered with an
exclamation mark overlay, the location does not agree with its logical breakpoint, or it
cannot be bookmarked. Clicking the icon toggles the location.</LI>
<UL style="list-style-type: none">
<LI><IMG alt="" src="images/breakpoint-enable.png"> Enabled: The location is
<LI><IMG alt="" src="images/breakpoint-enable.png"> <B>Enabled:</B> The location is
enabled.</LI>
<LI><IMG alt="" src="images/breakpoint-disable.png"> Disabled: The location is
<LI><IMG alt="" src="images/breakpoint-disable.png"> <B>Disabled:</B> The location is
disabled.</LI>
<LI><IMG alt="" src="images/breakpoint-mixed.png"> Mixed: (Listing only) Two locations at
the same address have different states.</LI>
<LI><IMG alt="" src="images/breakpoint-mixed.png"> <B>Mixed:</B> (Listing only) Two
locations at the same address have different states.</LI>
</UL>
<LI>Name - displays the name given to the location by the connected debugger. This field is
user modifiable.</LI>
<LI>Address - gives the dynamic address of this location.</LI>
<LI>Trace - gives the name of the location's trace.</LI>
<LI>Threads - (hidden by default) if the breakpoint applies to a limited set of threads,
gives the list of threads.</LI>
<LI>Comment - gives a user comment &mdash; the specification's expression by default. This
<LI><B>Name:</B> displays the name given to the location by the connected debugger. This
field is user modifiable.</LI>
<LI><B>Address:</B> gives the dynamic address of this location.</LI>
<LI><B>Trace:</B> gives the name of the location's trace.</LI>
<LI><B>Threads:</B> (hidden by default) if the breakpoint applies to a limited set of
threads, gives the list of threads.</LI>
<LI><B>Comment:</B> gives a user comment &mdash; the specification's expression by default.
This field is user modifiable.</LI>
<LI><B>Sleigh:</B> (hidden by default) indicates whether or not the location has a customized
Sleigh configuration. This is only relevant for emulation.</LI>
</UL>
<H2>Breakpoint Actions</H2>
@ -194,6 +213,124 @@
<P>This action is always available. <FONT color="red">Use with caution!</FONT> It deletes every
breakpoint.</P>
<H3><A name="set_condition"></A>Set Condition (Emulator)</H3>
<P>This action is available when all selected locations are emulated execution breakpoints.
(Conditional access breakpoints are not yet implemented.) It sets the condition using a Sleigh
expression that when true will trap the emulator. When false, the emulator continues past the
breakpoint without interruption. Sleigh operates similarly to C: zero is considered false,
while everything else is considered true. The dialog provides syntax checking, but does not
verify the semantics. If the breakpoint condition is not semantically correct, then the
breakpoint behaves as if unconditional; it will interrupt the emulator then indicate the
semantic error. To trap unconditionally (the default) use <CODE>1:1</CODE>. Otherwise, use a
boolean Sleigh expression, such as <CODE>RAX &gt;= 0x1000</CODE>. Sleigh conditions are rather
expressive. For example, on an x86 target, you might place a breakpoint at the entry of a
function and set the condition to <CODE>(*:8 RSP) &amp; 0xfff00000 == 0x00400000</CODE>. This
will break on calls to the function from any address matching <CODE>0x004?????</CODE>.</P>
<H3><A name="set_injection"></A>Set Injection (Emulator)</H3>
<P>This action is available when all selected locations are emulated execution breakpoints.
(Injections on access breakpoints are not yet implemented.) It replaces the instruction's usual
Sleigh semantics with those entered into the dialog. The Sleigh syntax is the same as used in
the processor language's Sleigh specification (<CODE>.slaspec</CODE> and <CODE>.sinc</CODE>
files). The dialog provides syntax checking, but does not verify the semantics. If the
injection is not semantically correct, then the breakpoint behaves as if unconditional; it will
interrupt the emulator then indicate the semantic error. <B>NOTE:</B> The semantics at the
breakpoint address are <EM>completely</EM> replaced, ignoring the original instruction
entirely. This includes its control flow behavior, even fall through. The replacement semantics
<EM>must</EM> provide control flow behavior, or else the emulator's program counter will not
advance, and the same injection will be executed repeatedly. Here are three ways to provide
control flow:</P>
<UL>
<LI>Call the <CODE>emu_exec_decoded()</CODE> userop: This is probably the most common method,
and is usually the last Sleigh statement in the injected semantics. This userop, which is
defined by the emulator, instructs it to decode and execute the instruction at the program
counter, effectively incorporating the original instruction's semantics. If this is not the
last statement of the injection, please consider: If the instruction transfers control, the
remainder of the injection is <EM>not</EM> executed. If the instruction falls through, the
remainder of the injection <EM>is</EM> executed.</LI>
<LI>Call the <CODE>emu_skip_decoded()</CODE> userop: This is a less common method, and is
usually the last Sleigh statement in the injected semantics. This userop, which is defined by
the emulator, instructs it to decode but skip the instruction at the program counter.
(Decoding is necessary to determine the instruction's length.) Use this when the intent is to
replace the original instruction's semantics. This will ensure the program counter advances
without actually executing the original instruction. No matter the original instruction's
control flow, this imposes fall through. It may be used to skip over function calls or
jumps.</LI>
<LI>Use a control transfer statement: This is probably the second most common method, and
includes the Sleigh keywords <CODE>call</CODE>, <CODE>goto</CODE>, and <CODE>return</CODE>.
Note that just as in processor specifications, a control transfer statement will be the last
statement executed for the injection. It immediately sets the program counter, skipping the
remainder of the injection.</LI>
</UL>
<P>Here are a few examples:</P>
<UL>
<LI>
An unconditional breakpoint. This is the default injection. The first statement calls the
<CODE>emu_swi()</CODE> "emulator software interrupt" userop, which is defined by the
emulator. The second statement incorporates the semantics of the original instruction:
<PRE>
emu_swi();
emu_exec_decoded();
</PRE>
</LI>
<LI>
A conditional breakpoint for <CODE>RAX &gt;= 0x1000</CODE>. This is a simple extension of
the unconditional breakpoint. The <CODE>emu_swi()</CODE> call is skipped if the inverse of
the condition is true:
<PRE>
if RAX &lt; 0x1000 goto &lt;L1&gt;;
emu_swi();
&lt;L1&gt;
emu_exec_decoded();
</PRE>
</LI>
<LI>
Stub a function, returning 0. This depends on the architecture and calling convention. Take
the AMD64 System V calling convention for example &mdash;
<CODE>x86:LE:64:default:gcc</CODE>. The injection, located at the function's entry or in
the program linkage table, would place the return value into the expected storage location
then replicate the behavior of <CODE>RET</CODE>:
<PRE>
RAX = 0;
RIP = *:8 RSP;
RSP = RSP + 8;
return [RIP];
</PRE>
</LI>
<LI>
Force a <CODE>JZ</CODE> to be taken, without modifying the image. This depends on the
architecture. Take x86 for example. The injection, located on the conditional jump, would
simply set the flag accordingly then execute the original instruction:
<PRE>
ZF = 1;
emu_exec_decoded();
</PRE>
Alternatively, suppose the example instruction is <CODE>JZ 0x00401234</CODE>. Then, the
injection can jump straight to the target:
<PRE>
goto [0x00401234];
</PRE>
</LI>
<LI>
Force a <CODE>JZ</CODE> to fall through, without modifying the image. The injection,
located on the conditional jump, would simply skip the instruction:
<PRE>
emu_skip_decoded();
</PRE>
</LI>
</UL>
<H2>Filter Actions</H2>
<P>For organizing breakpoints the manager provides the following actions:</P>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

View file

@ -16,50 +16,60 @@
<P>This plugin presents actions for controlling targets and modifying machine state. It
provides a drop-down action in the main toolbar for choosing what to control for the current
target: The live target, the integrated emulator, or the recorded trace. Only those control
actions suitable for the selection are displayed. Machine-state edits throughout the UI are
directed accordingly.</P>
actions suitable for the selection are displayed. Machine-state edits and breakpoint commands
throughout the UI are directed accordingly.</P>
<H2>Actions</H2>
<P>The plugin provides several actions, but only certain ones are displayed, depending on the
current mode.</P>
<H3><A name="edit_mode"></A>Edit Mode</H3>
<H3><A name="control_mode"></A>Control Mode</H3>
<P>This action changes the mode for the active trace and, if applicable, its associated live
target. It is always displayed, but only available when a trace is active. The possible modes
are:</P>
<UL>
<LI><IMG alt="" src="images/write-disabled.png"> Control Target w/Edits Disabled - This
presents actions for controlling the live target but rejects all machine-state edits.</LI>
<LI><IMG alt="" src="images/record.png"> <B>Control Target w/Edits Disabled:</B> The default,
this presents actions for controlling the live target but rejects all machine-state edits.
Breakpoint commands are directed to the live target. When active, the UI automatically
follows the latest snapshot recorded. If the target is terminated, the mode is automatically
switched to <B>Control Trace</B>.</LI>
<LI><IMG alt="" src="images/write-target.png"> Control Target - The default, this presents
actions for controlling the live target and directs edits to the live target. To accept
edits, the UI must be "live and at the present." If the trace has no associated target, i.e.,
it is dead; or if the current view is in the past or includes any steps of emulation, i.e.,
it is not at the present; then edits are rejected.</LI>
<LI><IMG alt="" src="images/write-target.png"> <B>Control Target:</B> This presents actions
for controlling the live target and directs edits to the live target. Breakpoint commands are
directed to the live target. To accept edits, the UI must be "live and at the present." If
the trace has no associated target, i.e., it is dead; or if the current view is in the past
or includes any steps of emulation, i.e., it is not at the present; then edits are rejected.
When active, the UI automatically follows the latest snapshot recorded. If the target is
terminated, the mode is automatically switched to <B>Control Emulator</B>.</LI>
<LI><IMG alt="" src="images/write-trace.png"> Control Trace - This presents actions for
<LI><IMG alt="" src="images/video-x-generic16.png"> <B>Control Trace w/Edits Disabled:</B>
This presents actions for navigating trace snapshots but rejects all machine-state edits.
Breakpoint commands are directed to the emulator.</LI>
<LI><IMG alt="" src="images/write-trace.png"> <B>Control Trace:</B> This presents actions for
navigating trace snapshots. It directs all edits to the trace database. Edits are generally
always accepted, and they are applied directly to the trace.</LI>
always accepted, and they are applied directly to the trace. Breakpoint commands are directed
to the emulator.</LI>
<LI><IMG alt="" src="images/write-emulator.png"> Control Emulator - This presents actions for
controlling the integrated emulator. This can be used for interpolating and extrapolating
execution from the current snapshot, without affecting the live target. It directs edits to
the integrated emulator by generating patch steps and appending them to the emulation
schedule. See the <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
<LI><IMG alt="" src="images/write-emulator.png"> <B>Control Emulator:</B> This presents
actions for controlling the integrated emulator. Breakpoint commands are directed to the
emulator. This can be used for interpolating and extrapolating execution from the current
snapshot, without affecting the live target. It directs edits to the integrated emulator by
generating patch steps and appending them to the emulation schedule. See the <A href=
"help/topics/DebuggerTimePlugin/DebuggerTimePlugin.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>Target Control Actions</H2>
<P>These actions are visible when the "Control Target" or "Control Target w/Edits Disabled"
mode is selected. They are available only when the current trace has an associated live target.
Commands are directed to the focused object or a suitable substitute.</P>
<P>These actions are visible when a <B>Control Target</B> mode is selected. They are available
only when the current trace has an associated live target. Commands are directed to the focused
object or a suitable substitute.</P>
<H3><A name="target_resume"></A><IMG alt="" src="images/resume.png"> Resume</H3>
@ -111,28 +121,28 @@
<H2>Trace Navigation Actions</H2>
<P>These actions are visible when the "Control Trace" mode is selected. They are available when
there is an active trace.</P>
<P>These actions are visible when a <B>Control Trace</B> mode is selected. They are available
when there is an active trace.</P>
<H3><A name="trace_snap_backward"></A><IMG alt="" src="images/2leftarrow.png" width="16">
Snapshot Backward</H3>
<P>This navigates the trace backward one snapshot. All windows displaying machine state will
show that recorded in the current snapshot. This is available only when there exists a snapshot
<P>This activates the previous snapshot. All windows displaying machine state will show that
recorded in the activated snapshot. This is available only when there exists a snapshot
previous to the current.</P>
<H3><A name="trace_snap_forward"></A><IMG alt="" src="images/2rightarrow.png" width="16">
Snapshot Forward</H3>
<P>This nativates the trace forward one snapshot. All windows displaying machine state will
show that recorded in the current snapshot. This is available only when there exists a snapshot
after the current.</P>
<P>This activates the next snapshot. All windows displaying machine state will show that
recorded in the activated snapshot. This is available only when there exists a snapshot after
the current.</P>
<H2>Emulation Actions</H2>
<H2><A name="emu_actions"></A>Emulation Actions</H2>
<P>These actions are visible when the "Control Emulator" mode is selected. They are available
when there is an active trace. Commands are directed to the integrated emulator for the current
trace.</P>
<P>These actions are visible when the <B>Control Emulator</B> mode is selected. They are
available when there is an active trace. Commands are directed to the integrated emulator for
the current trace.</P>
<H3><A name="emu_resume"></A><IMG alt="" src="images/resume.png"> Resume</H3>
@ -141,15 +151,15 @@
Only one emulator can be run from the GUI at a time. If the current snapshot represents the
live target, the emulator may read additional machine state from the live target. For
non-contrived programs, the emulator will likely be interrupted, since some instructions and
all system calls are not yet supported. It could also start executing from unmapped memory or
enter a infinite loop. If it seems to carry on too long, interrupt it and examine.</P>
system calls are not yet supported. It could also start executing from unmapped memory or enter
an infinite loop. If it seems to carry on too long, interrupt it and examine.</P>
<H3><A name="emu_interrupt"></A><IMG alt="" src="images/interrupt.png"> Interrupt</H3>
<P>Interrupt the currently-running emulator. This is available when any integrated emulator is
running. In most cases, this is the emulator for the current trace, but it may not be.
Canceling the dialog for an emulation task will also interrupt the emulator. Upon interruption,
the emulation schedule is recorded and the snapshot displayed in the GUI.</P>
the emulation schedule is noted and the snapshot displayed in the GUI.</P>
<H3><A name="emu_step_back"></A><IMG alt="" src="images/stepback.png"> Step Back</H3>
@ -164,8 +174,8 @@
<P>Steps the emulator to the next instruction, by flow. This is available when there is an
active thread. At worst, this operates by repeating the current emulation schedule with one
more step of the current thread. In most cases, this can use the cached emulator for the
current snapshot and advanced it a single step. Note that "Step Over" is not currently
supported by the emulator.</P>
current snapshot and advance it a single step. Note that "Step Over" is not currently supported
by the emulator.</P>
<H3><A name="emu_skip_over"></A><IMG alt="" src="images/skipover.png"> Skip Over</H3>
@ -179,20 +189,21 @@
<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. Be wary
when switching from emulation to trace mode, since you could accidentally edit scratch space in
the trace. It is allowed but can produce non-intuitive and erroneous results, since the
emulator caches its snapshots in scratch space.</P>
<P>The default mode is <B>Control Target w/Edits Disabled</B>, because in most cases, this is
the desired behavior. When the target dies, the mode becomes <B>Control Trace w/Edits
Disabled</B>. For the most part, modifying the recorded trace itself is discouraged. There are
few reasons to edit a trace, perhaps including 1) Hand-generating an experimental trace; 2)
Generating a trace from a script, e.g., importing an event log, recording an emulated target;
3) Patching in state missed by the original recording. Be wary of <B>Control Trace</B> mode,
especially after emulating, since you could accidentally edit scratch space in the trace. It is
allowed but can produce non-intuitive and erroneous results, since the emulator caches its
snapshots in scratch space.</P>
<P>To prevent accidental edits to a live target, use the Control Target w/Edits Disabled mode.
This is only effective against edits from the UI, and only when all plugins and scripts use the
state editing service. This cannot prevent a component from accessing Ghidra's Target API to
modify a target. Nor can it prevent edits via the connected debugger's command-line
interpreter. The following components all use the service: <A href=
<P>To modify state in a live target, use the <B>Control Target</B> mode. <B>NOTE:</B> Some UI
components will also require you to toggle a write protector. This also affects scripts that
use the state editing service. However, disabling edits cannot prevent a script from directly
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=

View file

@ -5,7 +5,7 @@
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Debugger: Model Service</TITLE>
<TITLE>Debugger: Emulation Service</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
@ -26,20 +26,21 @@
<P>This action is available whenever a program is active. It will create a new trace suitable
for "pure emulation" of the current program starting at the current address. More precisely, it
will open a new blank trace, initializes it with the current program's memory map, allocates a
stack, and creates a thread whose program counter is initialized to the current address and
whose registers are initialized to the register context at the current address. Optionally, any
other initialization can be done by manually modifying the trace in the UI, or using a script.
The trace and/or p-code stepping actions can then be used to emulate.</P>
will open a new blank trace, initialize it with the current program's memory map, allocate a
stack, and create a thread whose program counter is initialized to the current address and
whose registers are initialized to the register context at that address. Optionally, any other
initialization can be done by manually modifying the trace in the UI, or using a script. The <A
href="help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html#emu_actions">emulator
controls</A> can then be used.</P>
<H3><A name="add_emulated_thread"></A> Add Emulated Thread</H3>
<P>This action is available whenever a "pure emulation" trace is active. It spawns a new thread
in the current trace suitable for emulating starting at the current address. More precisely, it
allocates another stack and creates a new thread whose program counter is initialized to the
current address and whose registers are initialized to the register context at the current
address. Optionally, other registers can be initialized via the UI or a script. The new thread
is activated so that stepping actions will affect it by default.</P>
in the current trace suitable for emulation starting at the current address. More precisely, it
will allocate another stack and create a new thread whose program counter is initialized to the
current address and whose registers are initialized to the register context at that address.
Optionally, other registers can be initialized via the UI or a script. The new thread is
activated so that control actions will affect it by default.</P>
<H3><A name="configure_emulator"></A> Configure Emulator</H3>

View file

@ -103,6 +103,13 @@
is a suitable mapping when the current program is loaded in the trace <EM>without
relocation</EM>. It is also a fallback worth trying in the absence of a module list.</P>
<H3><A name="map_manually"></A>Map Manually</H3>
<P>This action is always available. It simply displays the <A href=
"help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static Mappings</A>
window. From there, it is possible to construct the map from trace memory to static images
entirely by hand.</P>
<H3><A name="map_modules"></A>Map Modules</H3>
<P>This action is available from the modules' or sections' pop-up menu. It searches the tool's

View file

@ -82,103 +82,11 @@
<LI>Comment - a user-modifiable comment about the thread.</LI>
<LI>Plot - a graphical representation of the thread's lifespan. Unlike other column headers,
clicking and dragging in this one will navigate through time. To rearrange this column, hold
SHIFT while dragging.</LI>
clicking and dragging in this one will navigate trace snapshots. To rearrange this column,
hold SHIFT while dragging.</LI>
</UL>
<H2>Navigating Time</H2>
<P>The user can navigate through time within the current trace by using the caret in the plot
column header. There are also actions for "stepping the trace" forward and backward. See the <A
href="help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html">Time</A> window for a way to
display and navigate to specific events in the trace's timeline. Note that stepping away from
the present will prevent most windows from interacting with the live target. While some
components and scripts may interact with the target and record things "into the present," such
updates may not appear on screen until the user steps back to the present. Stepping the trace
does not affect the target, except that viewing the present requires querying the live target,
which can perturb its state.</P>
<H3><A name="step_trace_snap_backward"></A><IMG alt="" src="images/arrow_up.png">Step Trace
Snap Backward</H3>
<P>This action is available when there exists a snapshot previous to the current. It steps the
trace backward to the previous snapshot, causing most windows to display the recorded data from
the new point in time.</P>
<H3><A name="step_trace_snap_forward"></A><IMG alt="" src="images/arrow_down.png">Step Trace
Snap Forward</H3>
<P>This action is available when there exists a snapshot ahead of the current. It steps the
trace forward to the next snapshot, causing most windows to display the recorded data from the
new point in time.</P>
<H3><A name="emu_trace_tick_backward"></A><IMG alt="" src="images/stepback.png">Emulate Trace
Tick Backward</H3>
<P>This action is available when the current point in time includes emulated steps. It steps
the trace backward to the previous tick.</P>
<H3><A name="emu_trace_tick_forward"></A><IMG alt="" src="images/stepinto.png">Emulate Trace
Tick Forward</H3>
<P>This action is available when a thread is selected. It steps the current thread forward to
the next tick, using emulation. Note that emulation does not affect the target. Furthermore,
emulation may halt early if it encounters certain instructions or causes an exception.</P>
<H3><A name="emu_trace_skip_tick_forward"></A><IMG alt="" src="images/skipover.png">Emulate
Trace Skip Tick Forward</H3>
<P>This action is available when a thread is selected. It steps the current thread forward by
skipping the next instruction, using emulation. Note that emulation does not affect the target.
Furthermore, emulation may halt early if it encounters certain instructions or causes an
exception. This action may be used skip subroutines; <B>however</B>, the stack may require
additional patching, e.g., to clean up stack parameters, depending on the calling convention.
This action <EM>does not</EM> perform those patches automatically. It only advances the program
counter. You may use <A href="#goto_time">Go To Time</A> to append the require stack patch,
e.g., <CODE>t0-{RSP=RSP+8}</CODE>.</P>
<H3><A name="seek_trace_present"></A><IMG alt="" src="images/seek-present.png">Seek Trace to
Present</H3>
<P>This toggle is always available and is enabled by default. When first enabled, if the
current trace is live, it immediately steps the trace to the present. Furthermore, as long as
it is enabled and the current trace is live, whenever the recorder steps forward, the tool
steps with it. In most cases, this option should remain on, otherwise stepping the target will
not cause any windows to update. Toggling it off then on is a quick way to return to the
present after browsing the past.</P>
<H3><A name="goto_time"></A>Go To Time</H3>
<P>This action is available when a trace is active. It prompts for a <B>Time Schedule</B>
expression. This is the same form as the expression in the title bar of the threads window. In
many cases, it is simply the snapshot number, e.g., <CODE>3</CODE>, which will go to the
snapshot with key 3. It may optionally include an emulation schedule, for example,
<CODE>3:10</CODE> will use snapshot 3 for an emulator's initial state and step 10 machine
instructions on snapshot 3's event thread. If the snapshot does not give an event thread, then
the thread must be specified in the expression, e.g., <CODE>3:t1-10</CODE>. That expression
will start at snapshot 3, get the thread with key 1, and step it 10 machine instructions. The
stepping commands can be repeated any number of times, separated by semicolons, to step threads
in a specified sequence, e.g., <CODE>3:t1-10;t2-5</CODE> will do the same as before, then get
thread 2 and step it 5 times.</P>
<P>The emulator's state can also be modified by the schedule. Instead of specifying a number of
steps, write a <B>Sleigh</B> statement, e.g., <CODE>3:t1-{r0=0x1234};10</CODE>. This will start
at snapshot 3, patch thread 1's r0 to 0x1234, then step 10 instructions. Like stepping
commands, the thread may be omitted for Sleigh commands. Each command without a thread
specified implicitly uses the one from the previous command, or in the case of the first
command, the event thread. Only one Sleigh statement is permitted per command.</P>
<P>A second command sequence may be appended, following a dot, to command the emulator at the
level of p-code operations as well. This is particularly useful when debugging a processor
specification. See also the <A href=
"help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html">P-code Stepper</A>
window. For example, <CODE>3:2.10</CODE> will start at snapshot 3 and step the event thread 2
machine instructions, followed by 10 p-code operations. The same thread-by-thread sequencing
and state patching commands are allowed in the p-code command sequence. The <EM>entire</EM>
instruction sequence precedes the entire p-code sequence, i.e., only a single dot is allowed.
Once the expression enters p-code mode, it cannot re-enter instruction mode.</P>
<H2>Other Actions</H2>
<H2>Actions</H2>
<H3><A name="sync_focus"></A>Synchronize Trace and Target Focus</H3>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

View file

@ -48,8 +48,7 @@
snapshot. This always applies to "scratch" snapshots produced by emulation, but may also
apply if the stepping schedule between recorded events is somehow known. Typically, it is
just the number of steps of the source snapshot's event thread; however, the notation does
allow other threads to be stepped, too. See the <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
allow other threads to be stepped, too. See the <A href="#goto_time">Go To Time</A>
action.</LI>
<LI>Description - a user-modifiable description of the snapshot or event. This defaults to
@ -65,6 +64,38 @@
snapshot. This is a shortcut to modifying the description in the time table, but can be
accessed outside of the time window.</P>
<H3><A name="goto_time"></A>Go To Time</H3>
<P>This action is available when a trace is active. It prompts for a <B>Time Schedule</B>
expression. This is the same form as the expression in the sub-title of the <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html">Threads</A> window. In many
cases, it is simply the snapshot number, e.g., <CODE>3</CODE>, which will go to the snapshot
with key 3. It may optionally include an emulation schedule, for example, <CODE>3:10</CODE>
will use snapshot 3 for an emulator's initial state and step 10 machine instructions on
snapshot 3's event thread. If the snapshot does not give an event thread, then the thread must
be specified in the expression, e.g., <CODE>3:t1-10</CODE>. That expression will start at
snapshot 3, get the thread with key 1, and step it 10 machine instructions. The stepping
commands can be repeated any number of times, separated by semicolons, to step threads in a
specified sequence, e.g., <CODE>3:t1-10;t2-5</CODE> will do the same as before, then get thread
2 and step it 5 times.</P>
<P>The emulator's state can also be modified by the schedule. Instead of specifying a number of
steps, write a <B>Sleigh</B> statement, e.g., <CODE>3:t1-{r0=0x1234};10</CODE>. This will start
at snapshot 3, patch thread 1's r0 to 0x1234, then step 10 instructions. Like stepping
commands, the thread may be omitted for Sleigh commands. Each command without a thread
specified implicitly uses the one from the previous command, or in the case of the first
command, the event thread. Only one Sleigh statement is permitted per command.</P>
<P>A second command sequence may be appended, following a dot, to command the emulator at the
level of p-code operations as well. This is particularly useful when debugging a processor
specification. See also the <A href=
"help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html">P-code Stepper</A>
window. For example, <CODE>3:2.10</CODE> will start at snapshot 3 and step the event thread 2
machine instructions then 10 p-code operations. The same thread-by-thread sequencing and state
patching commands are allowed in the p-code command sequence. The <EM>entire</EM> instruction
sequence precedes the entire p-code sequence, i.e., only a single dot is allowed. Once the
expression enters p-code mode, it cannot re-enter instruction mode.</P>
<H3><A name="hide_scratch"></A>Hide Scratch</H3>
<P>This toggle action is always available in the drop-down actions of the Time window. It is

View file

@ -111,8 +111,8 @@
Remember, the variable's storage should encode its value.</P>
<P>Optionally, the specified time may also include emulation. See the <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A> action
for the syntax of the <B>Time Schedule</B> expression. For simple schedules, the step buttons
"help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html#goto_time">Go To Time</A> action for
the syntax of the <B>Time Schedule</B> expression. For simple schedules, the step buttons
provide convenient forward and backward changes to the emulation schedule. Perhaps the most
common use of this is to see what changes from executing an isolated block of code. Ideally,
the baseline is a relatively complete capture or represents the present in a live session, so
@ -123,10 +123,10 @@
interesting block of code.</LI>
<LI>Keeping the target alive, use the <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#emu_trace_tick_forward">Emulate
Forward</A> and/or <A href=
"help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html#goto_time">Go To Time</A>
actions to reach the end of the interesting block.</LI>
"help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html#emu_actions">Emulator
Control</A> and/or <A href=
"help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html#goto_time">Go To Time</A> actions to
reach the end of the interesting block.</LI>
<LI>Use this <B>Compare</B> action and select the baseline snapshot.</LI>
</OL>

View file

@ -16,19 +16,27 @@
package ghidra.app.plugin.core.debug.event;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.framework.plugintool.PluginEvent;
public class TraceActivatedPluginEvent extends PluginEvent {
static final String NAME = "Trace Location";
private final DebuggerCoordinates coordinates;
private final ActivationCause cause;
public TraceActivatedPluginEvent(String source, DebuggerCoordinates coordinates) {
public TraceActivatedPluginEvent(String source, DebuggerCoordinates coordinates, ActivationCause cause) {
super(source, NAME);
this.coordinates = coordinates;
this.cause = cause;
}
public DebuggerCoordinates getActiveCoordinates() {
return coordinates;
}
public ActivationCause getCause() {
return cause;
}
}

View file

@ -0,0 +1,34 @@
/* ###
* 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.event;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginEvent;
public class TraceInactiveCoordinatesPluginEvent extends PluginEvent {
static final String NAME = "Trace Inactive Location";
private final DebuggerCoordinates coordinates;
public TraceInactiveCoordinatesPluginEvent(String source, DebuggerCoordinates coordinates) {
super(source, NAME);
this.coordinates = coordinates;
}
public DebuggerCoordinates getCoordinates() {
return coordinates;
}
}

View file

@ -174,17 +174,6 @@ public interface DebuggerResources {
Icon ICON_DIFF_PREV = new GIcon("icon.debugger.diff.previous");
Icon ICON_DIFF_NEXT = new GIcon("icon.debugger.diff.next");
Icon ICON_EDIT_MODE_READ_ONLY = new GIcon("icon.debugger.edit.mode.read.only");
Icon ICON_EDIT_MODE_WRITE_TARGET = new GIcon("icon.debugger.edit.mode.write.target");
Icon ICON_EDIT_MODE_WRITE_TRACE = new GIcon("icon.debugger.edit.mode.write.trace");
Icon ICON_EDIT_MODE_WRITE_EMULATOR =
new GIcon("icon.debugger.edit.mode.write.emulator");
String NAME_EDIT_MODE_READ_ONLY = "Control Target w/ Edits Disabled";
String NAME_EDIT_MODE_WRITE_TARGET = "Control Target";
String NAME_EDIT_MODE_WRITE_TRACE = "Control Trace";
String NAME_EDIT_MODE_WRITE_EMULATOR = "Control Emulator";
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
String HELP_ANCHOR_PLUGIN = "plugin";
@ -419,7 +408,7 @@ public interface DebuggerResources {
public AbstractConnectAction(Plugin owner) {
super(NAME, owner.getName());
setDescription("Create a new connection to an debugging agent");
setDescription("Create a new connection to a debugging agent");
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
}
}
@ -1660,23 +1649,6 @@ public interface DebuggerResources {
}
}
interface SeekTracePresentAction {
String NAME = "Seek Trace Present";
String DESCRIPTION = "Track the tool to the latest snap";
Icon ICON = ICON_SEEK_PRESENT;
String GROUP = "zz";
String HELP_ANCHOR = "seek_trace_present";
static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarIcon(ICON)
.toolBarGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
// TODO: Perhaps to reduce overloading of "snapshot" we should use "event" instead?
interface RenameSnapshotAction {
String NAME = "Rename Current Snapshot";
@ -1712,22 +1684,6 @@ public interface DebuggerResources {
}
}
interface GoToTimeAction {
String NAME = "Go To Time";
String DESCRIPTION = "Go to a specific time, optionally using emulation";
Icon ICON = ICON_TIME;
String HELP_ANCHOR = "goto_time";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName).description(DESCRIPTION)
.menuPath(NAME)
.menuIcon(ICON)
.keyBinding("CTRL SHIFT T")
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface SaveByDefaultAction {
String NAME = "Save Traces By Default";
String DESCRIPTION = "Automatically save traces to the project";

View file

@ -0,0 +1,140 @@
/* ###
* 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.breakpoint;
import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.text.*;
import docking.DialogComponentProvider;
import generic.theme.GColor;
import generic.theme.Gui;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.SleighUtils.SleighParseError;
import ghidra.pcode.exec.SleighUtils.SleighParseErrorEntry;
import ghidra.util.*;
public abstract class AbstractDebuggerSleighInputDialog extends DialogComponentProvider {
protected static final Color COLOR_ERROR = new GColor("color.fg.error");
protected static final AttributeSet RED_UNDERLINE;
static {
MutableAttributeSet attributes = new SimpleAttributeSet();
StyleConstants.setForeground(attributes, COLOR_ERROR);
StyleConstants.setUnderline(attributes, true);
RED_UNDERLINE = attributes;
}
protected final JPanel panel;
protected final JLabel label;
protected final StyledDocument docInput;
protected final JTextPane textInput;
private JScrollPane spInput;
protected boolean isValid = false;
static class SleighTextPane extends JTextPane {
public SleighTextPane(StyledDocument document) {
super(document);
Gui.registerFont(this, "font.debugger.sleigh");
}
}
protected AbstractDebuggerSleighInputDialog(String title, String prompt) {
super(title, true, true, true, false);
panel = new JPanel(new BorderLayout());
panel.setBorder(new EmptyBorder(16, 16, 16, 16));
label = new JLabel(prompt);
label.getMaximumSize().width = 400;
panel.add(label, BorderLayout.NORTH);
docInput = new DefaultStyledDocument();
textInput = new SleighTextPane(docInput);
spInput = new JScrollPane(textInput);
spInput.getMaximumSize().height = 300;
panel.add(spInput);
addWorkPanel(panel);
addOKButton();
addCancelButton();
}
public String prompt(PluginTool tool, String defaultInput) {
setStatusText("");
textInput.setText(defaultInput);
validateAndMarkup();
Swing.runLater(() -> repack());
tool.showDialog(this);
if (isValid) {
return getInput();
}
return null;
}
public String getInput() {
return textInput.getText();
}
protected abstract void validate();
protected void clearAttributes() {
docInput.setCharacterAttributes(0, docInput.getLength() + 1, SimpleAttributeSet.EMPTY,
true);
}
protected void addErrorAttribute(int start, int stop) {
int length = stop - start + 1;
docInput.setCharacterAttributes(start, length, RED_UNDERLINE, true);
}
protected void validateAndMarkup() {
isValid = false;
clearAttributes();
try {
validate();
isValid = true;
}
catch (SleighParseError e) {
setStatusText("<html><pre>" + HTMLUtilities.escapeHTML(e.getMessage()) + "</pre>",
MessageType.ERROR, false);
Swing.runLater(() -> {
if (spInput.getPreferredSize().height > spInput.getSize().height) {
repack();
}
});
for (SleighParseErrorEntry error : e.getErrors()) {
addErrorAttribute(error.start(), error.stop());
}
}
}
@Override
protected void okCallback() {
validateAndMarkup();
if (isValid) {
close();
}
}
@Override
protected void cancelCallback() {
isValid = false;
close();
}
}

View file

@ -20,7 +20,7 @@ import java.util.stream.Collectors;
import ghidra.app.services.LogicalBreakpoint;
import ghidra.app.services.LogicalBreakpoint.State;
import ghidra.app.services.TraceRecorder;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.thread.TraceThread;
@ -40,8 +40,8 @@ public class BreakpointLocationRow {
}
public boolean isEnabled() {
TraceRecorder recorder = provider.modelService.getRecorder(loc.getTrace());
return recorder != null && loc.isEnabled(recorder.getSnap());
long snap = provider.traceManager.getCurrentFor(loc.getTrace()).getSnap();
return loc.isEnabled(snap);
}
public State getState() {
@ -106,6 +106,10 @@ public class BreakpointLocationRow {
}
}
public boolean hasSleigh() {
return !SleighUtils.UNCONDITIONAL_BREAK.equals(loc.getEmuSleigh());
}
public TraceBreakpoint getTraceBreakpoint() {
return loc;
}

View file

@ -51,7 +51,8 @@ import ghidra.framework.options.annotation.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
@ -576,17 +577,11 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
return false;
}
ProgramLocation loc = getSingleLocationFromContext(context);
if (!(loc.getProgram() instanceof TraceProgramView)) {
if (!(loc.getProgram() instanceof TraceProgramView view)) {
return true;
}
TraceRecorder recorder = getRecorderFromContext(context);
if (recorder == null) {
return false;
}
if (!recorder.getSupportedBreakpointKinds().containsAll(kinds)) {
return false;
}
return true;
Set<TraceBreakpointKind> supported = getSupportedKindsFromTrace(view.getTrace());
return supported.containsAll(kinds);
}
}
@ -720,6 +715,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
@AutoServiceConsumed
private DebuggerStateEditingService editingService;
// @AutoServiceConsumed via method
DecompilerMarginService decompilerMarginService;
@SuppressWarnings("unused")
@ -830,66 +827,67 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin
}
}
protected TraceRecorder getRecorderFromContext(ActionContext context) {
if (modelService == null) {
return null;
}
Trace trace = getTraceFromContext(context);
return modelService.getRecorder(trace);
}
protected Set<TraceRecorder> getRecordersFromContext(ActionContext context) {
TraceRecorder single = getRecorderFromContext(context);
protected Set<Trace> getTracesFromContext(ActionContext context) {
Trace single = getTraceFromContext(context);
if (single != null) {
return Set.of(single);
}
if (mappingService == null || modelService == null) {
if (mappingService == null) {
return Set.of();
}
ProgramLocation loc = getSingleLocationFromContext(context);
if (loc == null || loc.getProgram() instanceof TraceProgramView) {
assert !(loc.getProgram() instanceof TraceProgramView);
if (loc == null) {
return Set.of();
}
Set<TraceLocation> mappedLocs = mappingService.getOpenMappedLocations(loc);
if (mappedLocs == null || mappedLocs.isEmpty()) {
return Set.of();
}
Set<TraceRecorder> result = new HashSet<>();
Set<Trace> result = new HashSet<>();
for (TraceLocation tloc : mappedLocs) {
TraceRecorder rec = modelService.getRecorder(tloc.getTrace());
if (rec != null) {
result.add(rec);
}
result.add(tloc.getTrace());
}
return result;
}
protected boolean contextHasRecorder(ActionContext ctx) {
return getRecorderFromContext(ctx) != null;
}
protected boolean contextCanManipulateBreakpoints(ActionContext ctx) {
if (breakpointService == null) {
return false;
}
if (!contextHasLocation(ctx)) {
return false;
}
// Programs, or live traces, but not dead traces
if (contextHasTrace(ctx) && !contextHasRecorder(ctx)) {
if (breakpointService == null || !contextHasLocation(ctx)) {
return false;
}
return true;
}
protected Set<TraceBreakpointKind> getSupportedKindsFromContext(ActionContext context) {
Set<TraceRecorder> recorders = getRecordersFromContext(context);
if (recorders.isEmpty()) {
protected Set<TraceBreakpointKind> getSupportedKindsFromTrace(Trace trace) {
StateEditingMode mode = editingService == null ? StateEditingMode.DEFAULT
: editingService.getCurrentMode(trace);
if (mode.useEmulatedBreakpoints()) {
return EnumSet.allOf(TraceBreakpointKind.class);
}
return recorders.stream()
.flatMap(rec -> rec.getSupportedBreakpointKinds().stream())
.collect(Collectors.toSet());
if (modelService == null) {
return Set.of();
}
TraceRecorder recorder = modelService.getRecorder(trace);
if (recorder == null) {
return Set.of();
}
return recorder.getSupportedBreakpointKinds();
}
protected Set<TraceBreakpointKind> getSupportedKindsFromContext(ActionContext context) {
Set<Trace> traces = getTracesFromContext(context);
if (traces.isEmpty()) {
return EnumSet.allOf(TraceBreakpointKind.class);
}
Set<TraceBreakpointKind> result = new HashSet<>();
for (Trace t : traces) {
result.addAll(getSupportedKindsFromTrace(t));
if (result.size() == TraceBreakpointKind.COUNT) {
// Short circuit if saturated
return result;
}
}
return result;
}
protected void doToggleBreakpointsAt(String title, ActionContext context) {

View file

@ -28,31 +28,36 @@ import javax.swing.table.TableColumnModel;
import docking.ActionContext;
import docking.WindowPosition;
import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditingModeChangeListener;
import ghidra.app.services.LogicalBreakpoint.State;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.util.MarkerLocation;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceBreakpointChangeType;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.util.*;
import ghidra.util.database.ObjectKey;
import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
implements LogicalBreakpointsChangeListener {
implements LogicalBreakpointsChangeListener, StateEditingModeChangeListener {
protected enum LogicalBreakpointTableColumns
implements EnumeratedTableColumn<LogicalBreakpointTableColumns, LogicalBreakpointRow> {
@ -63,7 +68,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
IMAGE("Image", String.class, LogicalBreakpointRow::getImageName, true),
LENGTH("Length", Long.class, LogicalBreakpointRow::getLength, true),
KINDS("Kinds", String.class, LogicalBreakpointRow::getKinds, true),
LOCATIONS("Locations", Integer.class, LogicalBreakpointRow::getLocationCount, true);
LOCATIONS("Locations", Integer.class, LogicalBreakpointRow::getLocationCount, true),
SLEIGH("Sleigh", Boolean.class, LogicalBreakpointRow::hasSleigh, true);
private final String header;
private final Class<?> cls;
@ -132,7 +138,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
public LogicalBreakpointTableModel(DebuggerBreakpointsProvider provider) {
super(provider.getTool(), "Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb,
lb -> new LogicalBreakpointRow(provider, lb));
lb -> new LogicalBreakpointRow(provider, lb),
LogicalBreakpointRow::getLogicalBreakpoint);
}
@Override
@ -149,7 +156,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
ADDRESS("Address", Address.class, BreakpointLocationRow::getAddress, true),
TRACE("Trace", String.class, BreakpointLocationRow::getTraceName, true),
THREADS("Threads", String.class, BreakpointLocationRow::getThreads, true),
COMMENT("Comment", String.class, BreakpointLocationRow::getComment, BreakpointLocationRow::setComment, true);
COMMENT("Comment", String.class, BreakpointLocationRow::getComment, BreakpointLocationRow::setComment, true),
SLEIGH("Sleigh", Boolean.class, BreakpointLocationRow::hasSleigh, true);
private final String header;
private final Function<BreakpointLocationRow, ?> getter;
@ -210,7 +218,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
public BreakpointLocationTableModel(DebuggerBreakpointsProvider provider) {
super(provider.getTool(), "Locations", BreakpointLocationTableColumns.class,
TraceBreakpoint::getObjectKey, loc -> new BreakpointLocationRow(provider, loc));
TraceBreakpoint::getObjectKey, loc -> new BreakpointLocationRow(provider, loc),
BreakpointLocationRow::getTraceBreakpoint);
}
@Override
@ -525,6 +534,38 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
}
interface SetEmulatedBreakpointConditionAction {
String NAME = "Set Condition (Emulator)";
String DESCRIPTION = "Set a Sleigh condition for this emulated breakpoint";
String GROUP = DebuggerResources.GROUP_BREAKPOINTS;
String HELP_ANCHOR = "set_condition";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.popupMenuPath(NAME)
.popupMenuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface SetEmulatedBreakpointInjectionAction {
String NAME = "Set Injection (Emulator)";
String DESCRIPTION = "Set a Sleigh injection for this emulated breakpoint";
String GROUP = DebuggerResources.GROUP_BREAKPOINTS;
String HELP_ANCHOR = "set_injection";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.popupMenuPath(NAME)
.popupMenuGroup(GROUP)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
class LocationsBySelectedBreakpointsTableFilter implements TableFilter<BreakpointLocationRow> {
@Override
public boolean acceptsRow(BreakpointLocationRow locationRow) {
@ -555,28 +596,11 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
}
protected class TrackRecordersListener implements CollectionChangeListener<TraceRecorder> {
@Override
public void elementAdded(TraceRecorder element) {
Swing.runIfSwingOrRunLater(() -> traceRecordingStarted(element));
}
@Override
public void elementRemoved(TraceRecorder element) {
Swing.runIfSwingOrRunLater(() -> traceRecordingStopped(element));
}
}
protected class ForBreakpointLocationsTraceListener extends TraceDomainObjectListener {
private final TraceRecorder recorder;
private final Trace trace;
public ForBreakpointLocationsTraceListener(TraceRecorder recorder) {
// TODO: What if recorder advances past a trace breakpoint?
// Tends never to happen during recording, since upper is unbounded
// (Same in LogicalBreak service)
this.recorder = recorder;
this.trace = recorder.getTrace();
public ForBreakpointLocationsTraceListener(Trace trace) {
this.trace = trace;
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
listenFor(TraceBreakpointChangeType.ADDED, this::locationAdded);
listenFor(TraceBreakpointChangeType.CHANGED, this::locationChanged);
@ -587,22 +611,23 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
private void objectRestored() {
reloadBreakpointLocations(recorder);
reloadBreakpointLocations(trace);
}
private boolean isLive(TraceBreakpoint location) {
return location.getLifespan().contains(recorder.getSnap());
private boolean isVisible(TraceBreakpoint location) {
long snap = traceManager.getCurrentFor(trace).getSnap();
return location.getLifespan().contains(snap);
}
private void locationAdded(TraceBreakpoint location) {
if (!isLive(location)) {
if (!isVisible(location)) {
return;
}
breakpointLocationAdded(location);
}
private void locationChanged(TraceBreakpoint location) {
if (!isLive(location)) {
if (!isVisible(location)) {
return;
}
breakpointLocationUpdated(location);
@ -610,8 +635,9 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
private void locationLifespanChanged(TraceBreakpoint location, Lifespan oldSpan,
Lifespan newSpan) {
boolean isLiveOld = oldSpan.contains(recorder.getSnap());
boolean isLiveNew = newSpan.contains(recorder.getSnap());
long snap = traceManager.getCurrentFor(trace).getSnap();
boolean isLiveOld = oldSpan.contains(snap);
boolean isLiveNew = newSpan.contains(snap);
if (isLiveOld == isLiveNew) {
return;
}
@ -624,7 +650,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
private void locationDeleted(TraceBreakpoint location) {
if (!isLive(location)) {
if (!isVisible(location)) {
return;
}
breakpointLocationRemoved(location);
@ -639,20 +665,19 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
// @AutoServiceConsumed via method
DebuggerLogicalBreakpointService breakpointService;
// @AutoServiceConsumed via method, package access for BreakpointLogicalRow
DebuggerModelService modelService;
@AutoServiceConsumed
private DebuggerListingService listingService;
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager;
DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerConsoleService consoleService;
// @AutoServiceConsumed via method
private DebuggerStateEditingService editingService;
@AutoServiceConsumed
private GoToService goToService;
@SuppressWarnings("unused")
private AutoService.Wiring autoServiceWiring;
private final TrackRecordersListener recorderListener = new TrackRecordersListener();
private final Map<Trace, ForBreakpointLocationsTraceListener> listenersByTrace =
new HashMap<>();
@ -686,6 +711,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
MakeBreakpointsEffectiveResolutionAction actionMakeBreakpointsEffectiveResolution;
ToggleDockingAction actionFilterByCurrentTrace;
ToggleDockingAction actionFilterLocationsByBreakpoints;
DockingAction actionSetCondition;
DockingAction actionSetInjection;
public DebuggerBreakpointsProvider(final DebuggerBreakpointsPlugin plugin) {
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_BREAKPOINTS, plugin.getName());
@ -747,17 +774,6 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
contextChanged();
}
@AutoServiceConsumed
private void setModelService(DebuggerModelService modelService) {
if (this.modelService != null) {
this.modelService.removeTraceRecordersChangedListener(recorderListener);
}
this.modelService = modelService;
if (this.modelService != null) {
this.modelService.addTraceRecordersChangedListener(recorderListener);
}
}
@AutoServiceConsumed
private void setConsoleService(DebuggerConsoleService consoleService) {
if (consoleService != null) {
@ -767,6 +783,25 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
}
@AutoServiceConsumed
private void setEditingService(DebuggerStateEditingService editingService) {
if (this.editingService != null) {
this.editingService.removeModeChangeListener(this);
}
this.editingService = editingService;
if (this.editingService != null) {
this.editingService.addModeChangeListener(this);
}
}
@Override
public void modeChanged(Trace trace, StateEditingMode mode) {
Swing.runIfSwingOrRunLater(() -> {
reloadBreakpointLocations(trace);
contextChanged();
});
}
protected void loadBreakpoints() {
Set<LogicalBreakpoint> all = breakpointService.getAllBreakpoints();
breakpointTableModel.addAllItems(all);
@ -824,22 +859,41 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
});
}
private void loadBreakpointLocations(TraceRecorder recorder) {
Trace trace = recorder.getTrace();
for (AddressRange range : trace.getBaseAddressFactory().getAddressSet()) {
locationTableModel.addAllItems(trace.getBreakpointManager()
.getBreakpointsIntersecting(Lifespan.at(recorder.getSnap()), range));
private void loadBreakpointLocations(Trace trace) {
StateEditingMode mode = editingService == null
? StateEditingMode.DEFAULT
: editingService.getCurrentMode(trace);
DebuggerCoordinates currentFor = traceManager.getCurrentFor(trace);
TraceRecorder recorder = currentFor.getRecorder();
if (!mode.useEmulatedBreakpoints() && recorder == null) {
return;
}
Lifespan span = Lifespan.at(currentFor.getSnap());
Collection<TraceBreakpoint> visible = new ArrayList<>();
for (AddressRange range : trace.getBaseAddressFactory().getAddressSet()) {
Collection<? extends TraceBreakpoint> breaks = trace.getBreakpointManager()
.getBreakpointsIntersecting(span, range);
if (mode.useEmulatedBreakpoints()) {
visible.addAll(breaks);
}
else {
for (TraceBreakpoint l : breaks) {
if (recorder.getTargetBreakpoint(l) != null) {
visible.add(l);
}
}
}
}
locationTableModel.addAllItems(visible);
}
private void unloadBreakpointLocations(Trace trace) {
locationTableModel.deleteWith(r -> r.getTraceBreakpoint().getTrace() == trace);
locationTableModel.deleteItemsWith(l -> l.getTrace() == trace);
}
private void reloadBreakpointLocations(TraceRecorder recorder) {
Trace trace = recorder.getTrace();
private void reloadBreakpointLocations(Trace trace) {
unloadBreakpointLocations(trace);
loadBreakpointLocations(recorder);
loadBreakpointLocations(trace);
}
private void breakpointLocationAdded(TraceBreakpoint location) {
@ -858,13 +912,13 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
locationTableModel.deleteItem(location);
}
private void doTrackTrace(Trace trace, TraceRecorder recorder) {
private void doTrackTrace(Trace trace) {
if (listenersByTrace.containsKey(trace)) {
Msg.warn(this, "Already tracking trace breakpoints");
return;
}
listenersByTrace.put(trace, new ForBreakpointLocationsTraceListener(recorder));
loadBreakpointLocations(recorder);
listenersByTrace.put(trace, new ForBreakpointLocationsTraceListener(trace));
loadBreakpointLocations(trace);
}
private void doUntrackTrace(Trace trace) {
@ -875,24 +929,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
}
private void traceRecordingStarted(TraceRecorder recorder) {
Trace trace = recorder.getTrace();
if (!traceManager.getOpenTraces().contains(trace)) {
return;
}
doTrackTrace(trace, recorder);
}
private void traceRecordingStopped(TraceRecorder recorder) {
doUntrackTrace(recorder.getTrace());
}
protected void traceOpened(Trace trace) {
TraceRecorder recorder = modelService.getRecorder(trace);
if (recorder == null) {
return;
}
doTrackTrace(trace, recorder);
doTrackTrace(trace);
}
protected void traceClosed(Trace trace) {
@ -1031,6 +1069,10 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
TableColumn locsCol =
bptColModel.getColumn(LogicalBreakpointTableColumns.LOCATIONS.ordinal());
locsCol.setPreferredWidth(20);
TableColumn bptSleighCol =
bptColModel.getColumn(LogicalBreakpointTableColumns.SLEIGH.ordinal());
bptSleighCol.setMaxWidth(24);
bptSleighCol.setMinWidth(24);
GTableColumnModel locColModel = (GTableColumnModel) locationTable.getColumnModel();
TableColumn locEnCol =
@ -1049,7 +1091,13 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
locAddrCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
TableColumn locThreadsCol =
locColModel.getColumn(BreakpointLocationTableColumns.THREADS.ordinal());
TableColumn locSleighCol =
locColModel.getColumn(BreakpointLocationTableColumns.SLEIGH.ordinal());
locSleighCol.setMaxWidth(24);
locSleighCol.setMinWidth(24);
locColModel.setVisible(locThreadsCol, false);
locColModel.setVisible(locSleighCol, false);
}
protected void navigateToSelectedBreakpoint() {
@ -1122,9 +1170,171 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
.onAction(this::toggledFilterLocationsByBreakpoints)
.buildAndInstallLocal(this);
actionSetCondition = SetEmulatedBreakpointConditionAction.builder(plugin)
.popupWhen(this::isPopupSetCondition)
.onAction(this::activatedSetCondition)
.buildAndInstall(tool);
actionSetInjection = SetEmulatedBreakpointInjectionAction.builder(plugin)
.popupWhen(this::isPopupSetInjection)
.onAction(this::activatedSetInjection)
.buildAndInstall(tool);
actionMakeBreakpointsEffectiveResolution = new MakeBreakpointsEffectiveResolutionAction();
}
private Collection<LogicalBreakpoint> getLogicalBreakpoints(ActionContext ctx) {
if (ctx instanceof DebuggerLogicalBreakpointsActionContext lbCtx) {
return lbCtx.getBreakpoints();
}
if (ctx instanceof ProgramLocationActionContext locCtx) {
return breakpointService.getBreakpointsAt(locCtx.getLocation());
}
if (ctx.getContextObject() instanceof MarkerLocation ml) {
return breakpointService
.getBreakpointsAt(new ProgramLocation(ml.getProgram(), ml.getAddr()));
}
return null;
}
private boolean isAllInvolvedTracesUsingEmulatedBreakpoints(ActionContext ctx) {
if (editingService == null) {
return false;
}
Set<Trace> traces = new HashSet<>();
Collection<LogicalBreakpoint> breakpoints = getLogicalBreakpoints(ctx);
if (breakpoints != null) {
if (breakpoints.isEmpty()) {
return false;
}
for (LogicalBreakpoint lb : breakpoints) {
traces.addAll(lb.getParticipatingTraces());
}
}
else if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx) {
Collection<TraceBreakpoint> locations = locCtx.getLocations();
if (locations.isEmpty()) {
return false;
}
for (TraceBreakpoint tb : locations) {
traces.add(tb.getTrace());
}
}
else {
return false;
}
for (Trace trace : traces) {
if (!editingService.getCurrentMode(trace).useEmulatedBreakpoints()) {
return false;
}
}
return true;
}
private static final Set<TraceBreakpointKind> EXECUTE_KINDS =
Set.of(TraceBreakpointKind.SW_EXECUTE, TraceBreakpointKind.HW_EXECUTE);
private boolean isAllBreakpointsExecution(ActionContext ctx) {
// TODO GP-2988: Remove this. Implement injection on emu access breakpoints, too
Collection<LogicalBreakpoint> breakpoints = getLogicalBreakpoints(ctx);
if (breakpoints != null) {
for (LogicalBreakpoint lb : breakpoints) {
if (!EXECUTE_KINDS.containsAll(lb.getKinds())) {
return false;
}
}
return true;
}
else if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx) {
for (TraceBreakpoint tb : locCtx.getLocations()) {
if (!EXECUTE_KINDS.containsAll(tb.getKinds())) {
return false;
}
}
return true;
}
return false;
}
private boolean isPopupSetCondition(ActionContext ctx) {
return isAllInvolvedTracesUsingEmulatedBreakpoints(ctx) && isAllBreakpointsExecution(ctx);
}
private boolean isPopupSetInjection(ActionContext ctx) {
return isAllInvolvedTracesUsingEmulatedBreakpoints(ctx) && isAllBreakpointsExecution(ctx);
}
private String deriveCurrentSleigh(ActionContext ctx) {
String sleigh = null;
Collection<LogicalBreakpoint> breakpoints = getLogicalBreakpoints(ctx);
if (breakpoints != null) {
for (LogicalBreakpoint lb : breakpoints) {
String s = lb.getEmuSleigh();
if (sleigh != null && !sleigh.equals(s)) {
return null;
}
sleigh = s;
}
return sleigh;
}
else if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx) {
for (TraceBreakpoint tb : locCtx.getLocations()) {
String s = tb.getEmuSleigh();
if (sleigh != null && !sleigh.equals(s)) {
return null;
}
sleigh = s;
}
return sleigh;
}
return null;
}
private String deriveCurrentCondition(ActionContext ctx) {
String sleigh = deriveCurrentSleigh(ctx);
return sleigh == null ? null : SleighUtils.recoverConditionFromBreakpoint(sleigh);
}
private void injectSleigh(ActionContext ctx, String sleigh) {
Collection<LogicalBreakpoint> breakpoints = getLogicalBreakpoints(ctx);
if (breakpoints != null) {
for (LogicalBreakpoint lb : breakpoints) {
lb.setEmuSleigh(sleigh);
}
}
else if (ctx instanceof DebuggerBreakpointLocationsActionContext locCtx) {
for (TraceBreakpoint tb : locCtx.getLocations()) {
tb.setEmuSleigh(sleigh);
}
}
else {
throw new AssertionError();
}
}
private void activatedSetCondition(ActionContext ctx) {
String curCondition = deriveCurrentCondition(ctx);
if (curCondition == null) {
curCondition = SleighUtils.CONDITION_ALWAYS;
}
String condition = DebuggerSleighExpressionInputDialog.INSTANCE.prompt(tool, curCondition);
if (condition == null) {
return; // Cancelled
}
injectSleigh(ctx, SleighUtils.sleighForConditionalBreak(condition));
}
private void activatedSetInjection(ActionContext ctx) {
String curSleigh = deriveCurrentSleigh(ctx);
if (curSleigh == null) {
curSleigh = SleighUtils.UNCONDITIONAL_BREAK;
}
String sleigh = DebuggerSleighSemanticInputDialog.INSTANCE.prompt(tool, curSleigh);
if (sleigh == null) {
return; // Cancelled
}
injectSleigh(ctx, sleigh);
}
private void toggledFilterByCurrentTrace(ActionContext ignored) {
breakpointTableModel.fireTableDataChanged();
locationTableModel.fireTableDataChanged();

View file

@ -0,0 +1,47 @@
/* ###
* 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.breakpoint;
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.SetEmulatedBreakpointConditionAction;
import ghidra.framework.plugintool.util.PluginUtils;
import ghidra.pcode.exec.SleighUtils;
import ghidra.util.HelpLocation;
public class DebuggerSleighExpressionInputDialog extends AbstractDebuggerSleighInputDialog {
public static final DebuggerSleighExpressionInputDialog INSTANCE =
new DebuggerSleighExpressionInputDialog();
protected DebuggerSleighExpressionInputDialog() {
super("Breakpoint Sleigh Condition", """
<html>
<p>Enter Emulated Breakpoint Sleigh Condition, e.g:</p>
<ul>
<li><code>1:1</code> (Always)</li>
<li><code>0:1</code> (Never)</li>
<li><code>RAX == 7</code></li>
</ul>
<p>Press <b>F1</b> for help.</p>
""");
setHelpLocation(new HelpLocation(
PluginUtils.getPluginNameFromClass(DebuggerBreakpointsPlugin.class),
SetEmulatedBreakpointConditionAction.HELP_ANCHOR));
}
@Override
protected void validate() {
SleighUtils.parseSleighExpression(getInput());
}
}

View file

@ -0,0 +1,47 @@
/* ###
* 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.breakpoint;
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.SetEmulatedBreakpointInjectionAction;
import ghidra.framework.plugintool.util.PluginUtils;
import ghidra.pcode.exec.SleighUtils;
import ghidra.util.HelpLocation;
public class DebuggerSleighSemanticInputDialog extends AbstractDebuggerSleighInputDialog {
public static final DebuggerSleighSemanticInputDialog INSTANCE =
new DebuggerSleighSemanticInputDialog();
protected DebuggerSleighSemanticInputDialog() {
super("Breakpoint Sleigh Injection", """
<html>
<p>Enter Emulated Breakpoint Sleigh Injection, e.g., an unconditional break:</p>
<pre>
emu_swi();
emu_exec_decoded();
</pre>
<p>Press <b>F1</b> for help and more examples.</p>
<p><b>Be sure to include control flow, or the emulator may get stuck!</b></p>
""");
setHelpLocation(new HelpLocation(
PluginUtils.getPluginNameFromClass(DebuggerBreakpointsPlugin.class),
SetEmulatedBreakpointInjectionAction.HELP_ANCHOR));
}
@Override
protected void validate() {
SleighUtils.parseSleighSemantic(getInput());
}
}

View file

@ -22,6 +22,7 @@ import ghidra.app.services.LogicalBreakpoint.Mode;
import ghidra.app.services.LogicalBreakpoint.State;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
@ -139,6 +140,11 @@ public class LogicalBreakpointRow {
return lb.getTraceBreakpoints().size();
}
public boolean hasSleigh() {
String sleigh = lb.getEmuSleigh();
return sleigh != null && !SleighUtils.UNCONDITIONAL_BREAK.equals(sleigh);
}
/**
* Check if it has mapped locations, regardless of whether those locations are present
*

View file

@ -205,7 +205,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
LogTableColumns, ActionContext, LogRow, LogRow> {
public LogTableModel(PluginTool tool) {
super(tool, "Log", LogTableColumns.class, r -> r.getActionContext(), r -> r);
super(tool, "Log", LogTableColumns.class, r -> r.getActionContext(), r -> r, r -> r);
}
@Override

View file

@ -42,8 +42,8 @@ import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.CachedEmulator;
import ghidra.app.services.DebuggerEmulationService.EmulatorStateListener;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditingModeChangeListener;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.AsyncUtils;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.*;
@ -210,11 +210,11 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
String GROUP = DebuggerResources.GROUP_CONTROL;
}
interface EditModeAction {
String NAME = "Edit Mode";
String DESCRIPTION = "Choose what to edit in dynamic views";
interface ControlModeAction {
String NAME = "Control Mode";
String DESCRIPTION = "Choose what to control and edit in dynamic views";
String GROUP = DebuggerResources.GROUP_CONTROL;
String HELP_ANCHOR = "edit_mode";
String HELP_ANCHOR = "control_mode";
static MultiStateActionBuilder<StateEditingMode> builder(Plugin owner) {
String ownerName = owner.getName();
@ -606,12 +606,13 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
protected Set<DockingAction> getActionSet(StateEditingMode mode) {
switch (mode) {
case READ_ONLY:
case WRITE_TARGET:
case RO_TARGET:
case RW_TARGET:
return actionsTarget;
case WRITE_TRACE:
case RO_TRACE:
case RW_TRACE:
return actionsTrace;
case WRITE_EMULATOR:
case RW_EMULATOR:
return actionsEmulate;
default:
throw new AssertionError();
@ -639,7 +640,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
}
protected void createActions() {
actionEditMode = EditModeAction.builder(this)
actionEditMode = ControlModeAction.builder(this)
.enabled(false)
.enabledWhen(c -> current.getTrace() != null)
.onActionStateChanged(this::activateEditMode)
@ -867,7 +868,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
DebuggerCoordinates current = this.current;
emulationService.backgroundRun(current.getPlatform(), current.getTime(),
Scheduler.oneThread(current.getThread())).thenAcceptAsync(r -> {
traceManager.activate(current.time(r.schedule()));
traceManager.activate(current.time(r.schedule()), ActivationCause.USER);
}, AsyncUtils.SWING_EXECUTOR).exceptionally(ex -> {
Msg.showError(this, null, "Emulate", "Error emulating", ex);
return null;
@ -974,10 +975,10 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
// TODO: We're sort of piggy-backing our mode onto that of the editing service.
// Seems we should have our own?
if (editingService == null) {
return StateEditingMode.READ_ONLY;
return StateEditingMode.DEFAULT;
}
if (current.getTrace() == null) {
return StateEditingMode.READ_ONLY;
return StateEditingMode.DEFAULT;
}
return editingService.getCurrentMode(current.getTrace());
}

View file

@ -20,7 +20,7 @@ import java.util.*;
import javax.swing.JCheckBox;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.service.breakpoint.LogicalBreakpointInternal.ProgramBreakpoint;
import ghidra.app.plugin.core.debug.service.breakpoint.ProgramBreakpoint;
import ghidra.app.util.viewer.listingpanel.PropertyBasedBackgroundColorModel;
import ghidra.program.database.IntRangeMap;
import ghidra.program.model.address.*;
@ -233,6 +233,7 @@ public class DebuggerCopyPlan {
else {
pb.disable();
}
pb.setEmuSleigh(bpt.getEmuSleigh());
}
}
},

View file

@ -52,7 +52,6 @@ import ghidra.app.plugin.core.marker.MarkerMarginProvider;
import ghidra.app.plugin.core.marker.MarkerOverviewProvider;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerListingService.LocationTrackingSpecChangeListener;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.util.viewer.format.FormatManager;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.framework.model.DomainFile;

View file

@ -113,7 +113,7 @@ public class DebuggerLegacyRegionsPanel extends JPanel {
public RegionTableModel(PluginTool tool) {
super(tool, "Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
RegionRow::new);
RegionRow::new, RegionRow::getRegion);
}
}

View file

@ -204,10 +204,6 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
createActions();
}
protected static boolean isLegacy(Trace trace) {
return trace != null && trace.getObjectManager().getRootSchema() == null;
}
protected void buildMainPanel() {
panel = new DebuggerRegionsPanel(this);
mainPanel.add(panel);
@ -246,7 +242,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
@Override
public ActionContext getActionContext(MouseEvent event) {
final ActionContext context;
if (isLegacy(current.getTrace())) {
if (Trace.isLegacy(current.getTrace())) {
context = legacyPanel.getActionContext();
}
else {
@ -395,7 +391,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
}
public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
if (isLegacy(current.getTrace())) {
if (Trace.isLegacy(current.getTrace())) {
legacyPanel.setSelectedRegions(sel);
}
else {
@ -465,7 +461,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
current = coordinates;
if (isLegacy(coordinates.getTrace())) {
if (Trace.isLegacy(coordinates.getTrace())) {
panel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
legacyPanel.coordinatesActivated(coordinates);
if (ArrayUtils.indexOf(mainPanel.getComponents(), legacyPanel) == -1) {

View file

@ -146,7 +146,7 @@ public class DebuggerLegacyModulesPanel extends JPanel {
public ModuleTableModel(PluginTool tool) {
super(tool, "Modules", ModuleTableColumns.class, TraceModule::getObjectKey,
ModuleRow::new);
ModuleRow::new, ModuleRow::getModule);
}
@Override

View file

@ -130,7 +130,7 @@ public class DebuggerLegacySectionsPanel extends JPanel {
public SectionTableModel(PluginTool tool) {
super(tool, "Sections", SectionTableColumns.class, TraceSection::getObjectKey,
SectionRow::new);
SectionRow::new, SectionRow::getSection);
}
@Override

View file

@ -513,10 +513,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
}
protected static boolean isLegacy(Trace trace) {
return trace != null && trace.getObjectManager().getRootSchema() == null;
}
@Override
public ActionContext getActionContext(MouseEvent event) {
if (myActionContext == null) {
@ -1026,7 +1022,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
current = coordinates;
if (isLegacy(coordinates.getTrace())) {
if (Trace.isLegacy(coordinates.getTrace())) {
modulesPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
sectionsPanel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
legacyModulesPanel.coordinatesActivated(coordinates);
@ -1057,7 +1053,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
public void setSelectedModules(Set<TraceModule> sel) {
if (isLegacy(current.getTrace())) {
if (Trace.isLegacy(current.getTrace())) {
legacyModulesPanel.setSelectedModules(sel);
}
else {
@ -1066,7 +1062,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
public void setSelectedSections(Set<TraceSection> sel) {
if (isLegacy(current.getTrace())) {
if (Trace.isLegacy(current.getTrace())) {
legacySectionsPanel.setSelectedSections(sel);
}
else {

View file

@ -101,7 +101,8 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
public MappingTableModel(PluginTool tool) {
super(tool, "Mappings", StaticMappingTableColumns.class,
TraceStaticMapping::getObjectKey, StaticMappingRow::new);
TraceStaticMapping::getObjectKey, StaticMappingRow::new,
StaticMappingRow::getMapping);
}
}

View file

@ -53,6 +53,7 @@ import ghidra.app.plugin.core.debug.gui.objects.components.*;
import ghidra.app.plugin.core.debug.mapping.DebuggerMemoryMapper;
import ghidra.app.script.*;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.*;
import ghidra.dbg.*;
import ghidra.dbg.error.DebuggerMemoryAccessException;
@ -1489,7 +1490,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
//this.recorder = rec;
Trace trace = rec.getTrace();
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
traceManager.activate(traceManager.resolveTrace(trace), ActivationCause.START_RECORDING);
}
public void stopRecording(TargetObject targetObject) {

View file

@ -131,14 +131,10 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
return mainPanel;
}
protected static boolean isLegacy(Trace trace) {
return trace != null && trace.getObjectManager().getRootSchema() == null;
}
@Override
public ActionContext getActionContext(MouseEvent event) {
final ActionContext context;
if (isLegacy(current.getTrace())) {
if (Trace.isLegacy(current.getTrace())) {
context = legacyPanel.getActionContext();
}
else {
@ -167,7 +163,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
current = coordinates;
if (isLegacy(coordinates.getTrace())) {
if (Trace.isLegacy(coordinates.getTrace())) {
panel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
legacyPanel.coordinatesActivated(coordinates);
if (ArrayUtils.indexOf(mainPanel.getComponents(), legacyPanel) == -1) {

View file

@ -120,7 +120,8 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
public ThreadTableModel(DebuggerThreadsProvider provider) {
super(provider.getTool(), "Threads", ThreadTableColumns.class,
TraceThread::getObjectKey, t -> new ThreadRow(provider.modelService, t));
TraceThread::getObjectKey, t -> new ThreadRow(provider.modelService, t),
ThreadRow::getThread);
}
}

View file

@ -26,11 +26,11 @@ import org.apache.commons.lang3.ArrayUtils;
import docking.ActionContext;
import docking.WindowPosition;
import docking.action.*;
import docking.widgets.dialogs.InputDialog;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SynchronizeFocusAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.ToToggleSelectionListener;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
import ghidra.framework.model.DomainObject;
@ -42,8 +42,6 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.Msg;
public class DebuggerThreadsProvider extends ComponentProviderAdapter {
@ -108,8 +106,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
private final BooleanChangeAdapter activatePresentChangeListener =
this::changedAutoActivatePresent;
private final BooleanChangeAdapter synchronizeFocusChangeListener =
this::changedSynchronizeFocus;
@ -123,9 +119,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
DebuggerLegacyThreadsPanel legacyPanel;
DockingAction actionSaveTrace;
ToggleDockingAction actionSeekTracePresent;
ToggleDockingAction actionSyncFocus;
DockingAction actionGoToTime;
ActionContext myActionContext;
@ -155,17 +149,11 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
@AutoServiceConsumed
public void setTraceManager(DebuggerTraceManagerService traceManager) {
if (this.traceManager != null) {
this.traceManager
.removeAutoActivatePresentChangeListener(activatePresentChangeListener);
this.traceManager.removeSynchronizeFocusChangeListener(synchronizeFocusChangeListener);
}
this.traceManager = traceManager;
if (traceManager != null) {
traceManager.addAutoActivatePresentChangeListener(activatePresentChangeListener);
traceManager.addSynchronizeFocusChangeListener(synchronizeFocusChangeListener);
if (actionSeekTracePresent != null) {
actionSeekTracePresent.setSelected(traceManager.isAutoActivatePresent());
}
if (actionSyncFocus != null) {
actionSyncFocus.setSelected(traceManager.isSynchronizeFocus());
}
@ -178,10 +166,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
contextChanged();
}
private boolean isLegacy(Trace trace) {
return trace != null && trace.getObjectManager().getRootSchema() == null;
}
public void coordinatesActivated(DebuggerCoordinates coordinates) {
if (sameCoordinates(current, coordinates)) {
current = coordinates;
@ -191,7 +175,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
current = coordinates;
traceTabs.coordinatesActivated(coordinates);
if (isLegacy(coordinates.getTrace())) {
if (Trace.isLegacy(coordinates.getTrace())) {
panel.coordinatesActivated(DebuggerCoordinates.NOWHERE);
legacyPanel.coordinatesActivated(coordinates);
if (ArrayUtils.indexOf(mainPanel.getComponents(), legacyPanel) == -1) {
@ -252,43 +236,15 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
protected void createActions() {
actionSeekTracePresent = SeekTracePresentAction.builder(plugin)
.enabledWhen(this::isSeekTracePresentEnabled)
.onAction(this::toggledSeekTracePresent)
.selected(traceManager == null ? false : traceManager.isAutoActivatePresent())
.buildAndInstallLocal(this);
actionSyncFocus = SynchronizeFocusAction.builder(plugin)
.selected(traceManager != null && traceManager.isSynchronizeFocus())
.enabledWhen(c -> traceManager != null)
.onAction(c -> toggleSyncFocus(actionSyncFocus.isSelected()))
.buildAndInstallLocal(this);
actionGoToTime = GoToTimeAction.builder(plugin)
.enabledWhen(c -> current.getTrace() != null)
.onAction(c -> activatedGoToTime())
.buildAndInstallLocal(this);
traceManager.addSynchronizeFocusChangeListener(toToggleSelectionListener =
new ToToggleSelectionListener(actionSyncFocus));
}
private boolean isSeekTracePresentEnabled(ActionContext context) {
return traceManager != null;
}
private void toggledSeekTracePresent(ActionContext context) {
if (traceManager == null) {
return;
}
traceManager.setAutoActivatePresent(actionSeekTracePresent.isSelected());
}
private void changedAutoActivatePresent(boolean value) {
if (actionSeekTracePresent == null || actionSeekTracePresent.isSelected()) {
return;
}
actionSeekTracePresent.setSelected(value);
}
private void changedSynchronizeFocus(boolean value) {
if (actionSyncFocus == null || actionSyncFocus.isSelected()) {
return;
@ -303,22 +259,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
traceManager.setSynchronizeFocus(enabled);
}
private void activatedGoToTime() {
InputDialog dialog =
new InputDialog("Go To Time", "Schedule:", current.getTime().toString());
tool.showDialog(dialog);
if (dialog.isCanceled()) {
return;
}
try {
TraceSchedule time = TraceSchedule.parse(dialog.getValue());
traceManager.activateTime(time);
}
catch (IllegalArgumentException e) {
Msg.showError(this, getComponent(), "Go To Time", "Could not parse schedule");
}
}
@Override
public JComponent getComponent() {
return mainPanel;

View file

@ -21,11 +21,13 @@ import java.awt.event.MouseEvent;
import java.lang.invoke.MethodHandles;
import java.util.Objects;
import javax.swing.Icon;
import javax.swing.JComponent;
import docking.ActionContext;
import docking.action.DockingActionIf;
import docking.action.ToggleDockingAction;
import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.widgets.dialogs.InputDialog;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
@ -36,11 +38,32 @@ import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.AutoService.Wiring;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
public class DebuggerTimeProvider extends ComponentProviderAdapter {
private static final AutoConfigState.ClassHandler<DebuggerTimeProvider> CONFIG_STATE_HANDLER =
AutoConfigState.wireHandler(DebuggerTimeProvider.class, MethodHandles.lookup());
interface GoToTimeAction {
String NAME = "Go To Time";
String DESCRIPTION = "Go to a specific time, optionally using emulation";
String GROUP = GROUP_TRACE;
Icon ICON = ICON_TIME;
String HELP_ANCHOR = "goto_time";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName).description(DESCRIPTION)
.menuPath(DebuggerPluginPackage.NAME, NAME)
.menuGroup(GROUP)
.menuIcon(ICON)
.keyBinding("CTRL G")
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
if (!Objects.equals(a.getTrace(), b.getTrace())) {
return false;
@ -56,16 +79,17 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
@AutoServiceConsumed
protected DebuggerTraceManagerService viewManager;
protected DebuggerTraceManagerService traceManager;
@SuppressWarnings("unused")
private final Wiring autoServiceWiring;
/*testing*/ final DebuggerSnapshotTablePanel mainPanel;
private DebuggerSnapActionContext myActionContext;
DockingAction actionGoToTime;
ToggleDockingAction actionHideScratch;
private DebuggerSnapActionContext myActionContext;
@AutoConfigStateField
/*testing*/ boolean hideScratch = true;
@ -122,18 +146,38 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
return;
}
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
viewManager.activateSnap(snap);
traceManager.activateSnap(snap);
contextChanged();
});
}
protected void createActions() {
actionGoToTime = GoToTimeAction.builder(plugin)
.enabledWhen(c -> current.getTrace() != null)
.onAction(c -> activatedGoToTime())
.buildAndInstall(tool);
actionHideScratch = DebuggerResources.HideScratchSnapshotsAction.builder(plugin)
.selected(hideScratch)
.onAction(this::activatedHideScratch)
.buildAndInstallLocal(this);
}
private void activatedGoToTime() {
InputDialog dialog =
new InputDialog("Go To Time", "Schedule:", current.getTime().toString());
tool.showDialog(dialog);
if (dialog.isCanceled()) {
return;
}
try {
TraceSchedule time = TraceSchedule.parse(dialog.getValue());
traceManager.activateTime(time);
}
catch (IllegalArgumentException e) {
Msg.showError(this, getComponent(), "Go To Time", "Could not parse schedule");
}
}
private void activatedHideScratch(ActionContext ctx) {
hideScratch = !hideScratch;
mainPanel.setHideScratchSnapshots(hideScratch);

View file

@ -17,6 +17,36 @@ package ghidra.app.plugin.core.debug.service.breakpoint;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.program.model.address.*;
/**
* An invocation is planning an action on a breakpoint
*
* @see BreakpointActionSet
*/
public interface BreakpointActionItem {
/**
* Compute a range from an address and length
*
* @param address the min address
* @param length the length
* @return the range
*/
default AddressRange range(Address address, long length) {
try {
return new AddressRangeImpl(address, length);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
}
/**
* Perform the action
*
* @return the future for the action. Synchronous invocations can just return
* {@link AsyncUtils#NIL}.
*/
public CompletableFuture<Void> execute();
}

View file

@ -20,62 +20,122 @@ import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncFence;
import ghidra.dbg.target.*;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
/**
* A de-duplicated collection of target breakpoint actions necessary to implement a logical
* breakpoint action.
* A de-duplicated collection of breakpoint action items necessary to implement a logical breakpoint
* action.
*
* <p>
* This will de-duplicate action items, but it does not check them for sanity. For example, deleting
* a breakpoint then enabling it. Typically, all the items are the same type, so such sanity checks
* are not necessary.
*/
public class BreakpointActionSet extends LinkedHashSet<BreakpointActionItem> {
public EnableBreakpointActionItem planEnable(TargetBreakpointLocation loc) {
/**
* Add an item to enable a target breakpoint
*
* @param loc the target breakpoint
* @return the added item
*/
public EnableTargetBreakpointActionItem planEnableTarget(TargetBreakpointLocation loc) {
if (loc instanceof TargetTogglable) {
EnableBreakpointActionItem action =
new EnableBreakpointActionItem((TargetTogglable) loc);
EnableTargetBreakpointActionItem action =
new EnableTargetBreakpointActionItem((TargetTogglable) loc);
add(action);
return action;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (spec instanceof TargetTogglable) {
EnableBreakpointActionItem action = new EnableBreakpointActionItem(spec);
EnableTargetBreakpointActionItem action = new EnableTargetBreakpointActionItem(spec);
add(action);
return action;
}
return null;
}
public DisableBreakpointActionItem planDisable(TargetBreakpointLocation loc) {
/**
* Add an item to enable an emulated breakpoint
*
* @param bpt the trace breakpoint
* @return the added item
*/
public EnableEmuBreakpointActionItem planEnableEmu(TraceBreakpoint bpt) {
EnableEmuBreakpointActionItem action = new EnableEmuBreakpointActionItem(bpt);
add(action);
return action;
}
/**
* Add an item to disable a target breakpoint
*
* @param loc the target breakpoint
* @return the added item
*/
public DisableTargetBreakpointActionItem planDisableTarget(TargetBreakpointLocation loc) {
if (loc instanceof TargetTogglable) {
DisableBreakpointActionItem action =
new DisableBreakpointActionItem((TargetTogglable) loc);
DisableTargetBreakpointActionItem action =
new DisableTargetBreakpointActionItem((TargetTogglable) loc);
add(action);
return action;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (spec instanceof TargetTogglable) {
DisableBreakpointActionItem action = new DisableBreakpointActionItem(spec);
DisableTargetBreakpointActionItem action = new DisableTargetBreakpointActionItem(spec);
add(action);
return action;
}
return null;
}
public DeleteBreakpointActionItem planDelete(TargetBreakpointLocation loc) {
/**
* Add an item to disable an emulated breakpoint
*
* @param bpt the trace breakpoint
* @return the added item
*/
public DisableEmuBreakpointActionItem planDisableEmu(TraceBreakpoint bpt) {
DisableEmuBreakpointActionItem action = new DisableEmuBreakpointActionItem(bpt);
add(action);
return action;
}
/**
* Add an item to delete a target breakpoint
*
* @param loc the target breakpoint
* @return the added item
*/
public DeleteTargetBreakpointActionItem planDeleteTarget(TargetBreakpointLocation loc) {
if (loc instanceof TargetDeletable) {
DeleteBreakpointActionItem action =
new DeleteBreakpointActionItem((TargetDeletable) loc);
DeleteTargetBreakpointActionItem action =
new DeleteTargetBreakpointActionItem((TargetDeletable) loc);
add(action);
return action;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (spec instanceof TargetTogglable) {
DeleteBreakpointActionItem action =
new DeleteBreakpointActionItem((TargetDeletable) spec);
DeleteTargetBreakpointActionItem action =
new DeleteTargetBreakpointActionItem((TargetDeletable) spec);
add(action);
return action;
}
return null;
}
/**
* Add an item to delete an emulated breakpoint
*
* @param bpt the trace breakpoint
* @return the added item
*/
public DeleteEmuBreakpointActionItem planDeleteEmu(TraceBreakpoint bpt) {
DeleteEmuBreakpointActionItem action = new DeleteEmuBreakpointActionItem(bpt);
add(action);
return action;
}
/**
* Carry out the actions in the order they were added
*

View file

@ -27,11 +27,11 @@ import generic.CatenatedCollection;
import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.events.ProgramOpenedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceOpenedPluginEvent;
import ghidra.app.plugin.core.debug.service.breakpoint.LogicalBreakpointInternal.ProgramBreakpoint;
import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditingModeChangeListener;
import ghidra.app.services.LogicalBreakpoint.State;
import ghidra.async.SwingExecutorService;
import ghidra.dbg.target.TargetBreakpointLocation;
@ -41,8 +41,7 @@ import ghidra.framework.model.*;
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.address.*;
import ghidra.program.model.listing.*;
import ghidra.program.util.*;
import ghidra.trace.model.*;
@ -65,6 +64,8 @@ import ghidra.util.datastruct.ListenerSet;
ProgramOpenedPluginEvent.class,
ProgramClosedPluginEvent.class,
TraceOpenedPluginEvent.class,
TraceActivatedPluginEvent.class,
TraceInactiveCoordinatesPluginEvent.class,
TraceClosedPluginEvent.class,
},
servicesRequired = {
@ -186,6 +187,13 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
}
protected class TrackModesListener implements StateEditingModeChangeListener {
@Override
public void modeChanged(Trace trace, StateEditingMode mode) {
processChange(c -> evtModeChanged(c, trace), "modeChanged");
}
}
private class TraceBreakpointsListener extends TraceDomainObjectListener {
private final InfoPerTrace info;
private ChangeCollector c;
@ -218,29 +226,28 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
private void breakpointAdded(TraceBreakpoint tb) {
if (!tb.getLifespan().contains(info.recorder.getSnap())) {
// NOTE: User/script probably added historical breakpoint
if (!tb.getLifespan().contains(info.snap)) {
return;
}
try {
info.trackTraceBreakpoint(c.a, tb, false);
}
catch (TrackedTooSoonException e) {
Msg.info(this, "Ignoring " + tb +
" added until service has finished loading its trace");
Msg.info(this,
"Ignoring " + tb + " added until service has finished loading its trace");
}
}
private void breakpointChanged(TraceBreakpoint tb) {
if (!tb.getLifespan().contains(info.recorder.getSnap())) {
if (!tb.getLifespan().contains(info.snap)) {
return;
}
try {
info.trackTraceBreakpoint(c.a, tb, true);
}
catch (TrackedTooSoonException e) {
Msg.info(this, "Ignoring " + tb +
" changed until service has finished loading its trace");
Msg.info(this,
"Ignoring " + tb + " changed until service has finished loading its trace");
}
catch (NoSuchElementException e) {
// TODO: This catch clause should not be necessary.
@ -252,8 +259,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
private void breakpointLifespanChanged(TraceAddressSpace spaceIsNull,
TraceBreakpoint tb, Lifespan oldSpan, Lifespan newSpan) {
// NOTE: User/script probably modified historical breakpoint
boolean isInOld = oldSpan.contains(info.recorder.getSnap());
boolean isInNew = newSpan.contains(info.recorder.getSnap());
boolean isInOld = oldSpan.contains(info.snap);
boolean isInNew = newSpan.contains(info.snap);
if (isInOld == isInNew) {
return;
}
@ -272,11 +279,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
private void breakpointDeleted(TraceBreakpoint tb) {
if (!tb.getLifespan().contains(info.recorder.getSnap())) {
// NOTE: User/script probably removed historical breakpoint
// assert false;
return;
}
// Could check snap, but might as well just be sure it's gone
info.forgetTraceBreakpoint(c.r, tb);
}
}
@ -375,8 +378,6 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
Address address, TraceBreakpoint tb) {
Set<LogicalBreakpointInternal> set = breakpointsByAddress.get(address);
if (set == null) {
Msg.warn(this, "Breakpoint to remove is not present: " + tb + ", trace=" +
tb.getTrace());
return null;
}
for (LogicalBreakpointInternal lb : Set.copyOf(set)) {
@ -393,8 +394,6 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
return lb;
}
}
Msg.warn(this, "Breakpoint to remove is not present: " + tb + ", trace=" +
tb.getTrace());
return null;
}
@ -451,22 +450,41 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
protected class InfoPerTrace extends AbstractInfo {
final TraceRecorder recorder;
final Trace trace;
final TraceBreakpointsListener breakpointListener;
public InfoPerTrace(TraceRecorder recorder) {
this.recorder = recorder;
this.trace = recorder.getTrace();
TraceRecorder recorder;
long snap = -1;
public InfoPerTrace(Trace trace) {
this.trace = Objects.requireNonNull(trace);
this.breakpointListener = new TraceBreakpointsListener(this);
trace.addListener(breakpointListener);
}
protected void setRecorderAndSnap(TraceRecorder recorder, long snap, ChangeCollector c) {
if (this.recorder == recorder && this.snap == snap) {
return;
}
this.recorder = recorder;
this.snap = snap;
for (InfoPerProgram info : programInfos.values()) {
// This is heavy, but not sure a better method
// Could examine mapping service to narrow down relevant programs...
info.reloadBreakpoints(c);
}
reloadBreakpoints(c);
}
@Override
protected LogicalBreakpointInternal createLogicalBreakpoint(Address address, long length,
Collection<TraceBreakpointKind> kinds) {
return new LoneLogicalBreakpoint(recorder, address, length, kinds);
LoneLogicalBreakpoint lb =
new LoneLogicalBreakpoint(tool, trace, address, length, kinds);
lb.setRecorder(trace, recorder);
return lb;
}
@Override
@ -477,33 +495,45 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
protected void reloadBreakpoints(ChangeCollector c) {
forgetTraceInvalidBreakpoints(c.r);
trackTraceLiveBreakpoints(c.a);
trackTraceBreakpoints(c.a);
}
protected void forgetAllBreakpoints(RemoveCollector r) {
Collection<TraceBreakpoint> live = new ArrayList<>();
Collection<TraceBreakpoint> toForget = new ArrayList<>();
for (AddressRange range : trace.getBaseAddressFactory().getAddressSet()) {
live.addAll(trace
toForget.addAll(trace
.getBreakpointManager()
.getBreakpointsIntersecting(Lifespan.at(recorder.getSnap()), range));
.getBreakpointsIntersecting(Lifespan.ALL, range));
}
for (TraceBreakpoint tb : live) {
for (TraceBreakpoint tb : toForget) {
forgetTraceBreakpoint(r, tb);
}
}
protected void forgetTraceInvalidBreakpoints(RemoveCollector r) {
// Breakpoint can become invalid because it is itself invalid (deleted)
// Or because the mapping to static space has changed or become invalid
/**
* Breakpoint can become invalid because it is itself invalid (deleted), because the
* snap has changed to where it's no longer present, because the mapping to static space
* has changed or become invalid, or because it has no live breakpoint in target mode.
*/
StateEditingMode mode = getMode(trace);
for (Set<LogicalBreakpointInternal> set : List
.copyOf(breakpointsByAddress.values())) {
for (LogicalBreakpointInternal lb : Set.copyOf(set)) {
for (TraceBreakpoint tb : Set.copyOf(lb.getTraceBreakpoints(trace))) {
if (!mode.useEmulatedBreakpoints() &&
(recorder == null || recorder.getTargetBreakpoint(tb) == null)) {
forgetTraceBreakpoint(r, tb);
continue;
}
if (!trace.getBreakpointManager().getAllBreakpoints().contains(tb)) {
forgetTraceBreakpoint(r, tb);
continue;
}
if (!tb.getLifespan().contains(snap)) {
forgetTraceBreakpoint(r, tb);
continue;
}
ProgramLocation progLoc = computeStaticLocation(tb);
if (!Objects.equals(lb.getProgramLocation(), progLoc)) {
// NOTE: This can happen to Lone breakpoints.
@ -516,14 +546,28 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
}
protected void trackTraceLiveBreakpoints(AddCollector a) {
Collection<TraceBreakpoint> live = new ArrayList<>();
for (AddressRange range : trace.getBaseAddressFactory().getAddressSet()) {
live.addAll(trace
.getBreakpointManager()
.getBreakpointsIntersecting(Lifespan.at(recorder.getSnap()), range));
protected void trackTraceBreakpoints(AddCollector a) {
StateEditingMode mode = getMode(trace);
if (!mode.useEmulatedBreakpoints() && recorder == null) {
return;
}
trackTraceBreakpoints(a, live);
Collection<TraceBreakpoint> visible = new ArrayList<>();
for (AddressRange range : trace.getBaseAddressFactory().getAddressSet()) {
Collection<? extends TraceBreakpoint> breaks = trace
.getBreakpointManager()
.getBreakpointsIntersecting(Lifespan.at(snap), range);
if (mode.useEmulatedBreakpoints()) {
visible.addAll(breaks);
}
else {
for (TraceBreakpoint tb : breaks) {
if (recorder.getTargetBreakpoint(tb) != null) {
visible.add(tb);
}
}
}
}
trackTraceBreakpoints(a, visible);
}
protected void trackTraceBreakpoints(AddCollector a,
@ -549,8 +593,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
*/
return null;
}
return mappingService.getOpenMappedLocation(new DefaultTraceLocation(trace,
null, Lifespan.at(recorder.getSnap()), tb.getMinAddress()));
return mappingService.getOpenMappedLocation(
new DefaultTraceLocation(trace, null, Lifespan.at(snap), tb.getMinAddress()));
}
protected void trackTraceBreakpoint(AddCollector a, TraceBreakpoint tb, boolean forceUpdate)
@ -585,7 +629,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
// Must be shutting down
return null;
}
return mappingService.getOpenMappedLocation(trace, loc, recorder.getSnap());
return mappingService.getOpenMappedLocation(trace, loc, snap);
}
}
@ -606,7 +650,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
if (loc == null) {
continue;
}
lb.setTraceAddress(ti.recorder, loc.getAddress());
lb.setTraceAddress(ti.trace, loc.getAddress());
lb.setRecorder(ti.trace, ti.recorder);
ti.breakpointsByAddress.computeIfAbsent(loc.getAddress(), __ -> new HashSet<>())
.add(lb);
}
@ -616,7 +661,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
protected LogicalBreakpointInternal createLogicalBreakpoint(Address address, long length,
Collection<TraceBreakpointKind> kinds) {
MappedLogicalBreakpoint lb =
new MappedLogicalBreakpoint(program, address, length, kinds);
new MappedLogicalBreakpoint(tool, program, address, length, kinds);
mapTraceAddresses(lb);
return lb;
}
@ -763,6 +808,8 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
private DebuggerTraceManagerService traceManager;
// @AutoServiceConsumed via method
private DebuggerStaticMappingService mappingService;
// @AutoServiceConsumed via method
private DebuggerStateEditingService editingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@ -772,6 +819,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
private final TrackRecordersListener recorderListener = new TrackRecordersListener();
private final TrackMappingsListener mappingListener = new TrackMappingsListener();
private final TrackModesListener modeListener = new TrackModesListener();
private final Map<Trace, InfoPerTrace> traceInfos = new HashMap<>();
private final Map<Program, InfoPerProgram> programInfos = new HashMap<>();
@ -832,6 +880,13 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
}
protected void evtModeChanged(ChangeCollector c, Trace trace) {
InfoPerTrace info = traceInfos.get(trace);
if (info != null) {
info.reloadBreakpoints(c);
}
}
protected void removeLogicalBreakpointGlobally(LogicalBreakpoint lb) {
ProgramLocation pLoc = lb.getProgramLocation();
if (pLoc != null) {
@ -871,6 +926,17 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}
}
@AutoServiceConsumed
public void setEditingService(DebuggerStateEditingService editingService) {
if (this.editingService != null) {
this.editingService.removeModeChangeListener(modeListener);
}
this.editingService = editingService;
if (this.editingService != null) {
this.editingService.addModeChangeListener(modeListener);
}
}
private void programOpened(Program program) {
processChange(c -> evtProgramOpened(c, program), "programOpened");
}
@ -899,14 +965,13 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
// The mapping removals, if any, will clean up related traces
}
private void doTrackTrace(ChangeCollector c, Trace trace, TraceRecorder recorder) {
if (traceInfos.containsKey(trace)) {
Msg.warn(this, "Already tracking trace breakpoints");
return;
}
InfoPerTrace info = new InfoPerTrace(recorder);
private void doTrackTrace(ChangeCollector c, Trace trace, TraceRecorder recorder, long snap) {
InfoPerTrace info = traceInfos.get(trace);
if (info == null) {
info = new InfoPerTrace(trace);
traceInfos.put(trace, info);
info.reloadBreakpoints(c);
}
info.setRecorderAndSnap(recorder, snap, c);
}
private void doUntrackTrace(ChangeCollector c, Trace trace) {
@ -932,21 +997,33 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
if (!traceManager.getOpenTraces().contains(trace)) {
return;
}
doTrackTrace(c, trace, recorder);
long snap = traceManager.getCurrentFor(trace).getSnap();
doTrackTrace(c, trace, recorder, snap);
}
private void evtTraceRecordingStopped(ChangeCollector c, TraceRecorder recorder) {
doUntrackTrace(c, recorder.getTrace());
Trace trace = recorder.getTrace();
if (!traceManager.getOpenTraces().contains(trace)) {
return;
}
long snap = traceManager.getCurrentFor(trace).getSnap();
doTrackTrace(c, trace, null, snap);
}
private void traceOpened(Trace trace) {
processChange(c -> {
TraceRecorder recorder = modelService.getRecorder(trace);
if (recorder == null) {
long snap = traceManager.getCurrentFor(trace).getSnap();
doTrackTrace(c, trace, recorder, snap);
}, "traceOpened");
}
private void traceSnapChanged(DebuggerCoordinates coordinates) {
if (coordinates.getTrace() == null) {
return;
}
doTrackTrace(c, trace, recorder);
}, "traceOpened");
processChange(c -> doTrackTrace(c, coordinates.getTrace(), coordinates.getRecorder(),
coordinates.getSnap()), "coordinatesActivated");
}
private void traceClosed(Trace trace) {
@ -1074,14 +1151,16 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
* had to re-implement here, anyway. The actual logical breakpoint is created by event
* processors.
*/
MappedLogicalBreakpoint lb = new MappedLogicalBreakpoint(program, address, length, kinds);
MappedLogicalBreakpoint lb =
new MappedLogicalBreakpoint(tool, program, address, length, kinds);
synchronized (lock) {
for (InfoPerTrace ti : traceInfos.values()) {
TraceLocation loc = ti.toDynamicLocation(lb.getProgramLocation());
if (loc == null) {
continue;
}
lb.setTraceAddress(ti.recorder, loc.getAddress());
lb.setTraceAddress(ti.trace, loc.getAddress());
lb.setRecorder(ti.trace, ti.recorder);
}
}
return lb;
@ -1097,21 +1176,22 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
@Override
public CompletableFuture<Void> placeBreakpointAt(Trace trace, Address address, long length,
Collection<TraceBreakpointKind> kinds, String name) {
long snap = traceManager.getCurrentFor(trace).getSnap();
TraceRecorder recorder = modelService.getRecorder(trace);
if (recorder == null) {
throw new IllegalArgumentException("Given trace is not live");
}
ProgramLocation staticLocation = mappingService.getOpenMappedLocation(
new DefaultTraceLocation(trace, null, Lifespan.at(recorder.getSnap()), address));
new DefaultTraceLocation(trace, null, Lifespan.at(snap), address));
if (staticLocation == null) {
return new LoneLogicalBreakpoint(recorder, address, length, kinds)
.enableForTrace(trace);
LoneLogicalBreakpoint lb =
new LoneLogicalBreakpoint(tool, trace, address, length, kinds);
lb.setRecorder(trace, recorder);
return lb.enableForTrace(trace);
}
MappedLogicalBreakpoint lb = new MappedLogicalBreakpoint(staticLocation.getProgram(),
MappedLogicalBreakpoint lb = new MappedLogicalBreakpoint(tool, staticLocation.getProgram(),
staticLocation.getByteAddress(), length, kinds);
lb.setTraceAddress(recorder, address);
lb.setTraceAddress(trace, address);
lb.setRecorder(trace, recorder);
lb.enableForProgramWithName(name);
return lb.enableForTrace(trace);
}
@ -1126,7 +1206,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
protected CompletableFuture<Void> actOnAll(Collection<LogicalBreakpoint> col, Trace trace,
Consumer<LogicalBreakpoint> consumerForProgram,
BiConsumer<BreakpointActionSet, LogicalBreakpointInternal> consumerForTarget) {
BiConsumer<BreakpointActionSet, LogicalBreakpointInternal> consumerForTrace) {
BreakpointActionSet actions = new BreakpointActionSet();
for (LogicalBreakpoint lb : col) {
Set<Trace> participants = lb.getParticipatingTraces();
@ -1136,7 +1216,7 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
if (!(lb instanceof LogicalBreakpointInternal lbi)) {
continue;
}
consumerForTarget.accept(actions, lbi);
consumerForTrace.accept(actions, lbi);
}
return actions.execute();
}
@ -1174,8 +1254,48 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
}, (actions, lbi) -> lbi.planDelete(actions, trace));
}
private StateEditingMode getMode(Trace trace) {
return editingService == null
? StateEditingMode.DEFAULT
: editingService.getCurrentMode(trace);
}
private void planActOnLoc(BreakpointActionSet actions, TraceBreakpoint tb,
BiConsumer<BreakpointActionSet, TargetBreakpointLocation> targetLocConsumer,
BiConsumer<BreakpointActionSet, TraceBreakpoint> emuLocConsumer) {
StateEditingMode mode = getMode(tb.getTrace());
if (mode.useEmulatedBreakpoints()) {
planActOnLocEmu(actions, tb, emuLocConsumer);
}
else {
planActOnLocTarget(actions, tb, targetLocConsumer);
}
}
private void planActOnLocTarget(BreakpointActionSet actions, TraceBreakpoint tb,
BiConsumer<BreakpointActionSet, TargetBreakpointLocation> targetLocConsumer) {
TraceRecorder recorder = modelService.getRecorder(tb.getTrace());
if (recorder == null) {
return;
}
List<String> path = PathUtils.parse(tb.getPath());
TargetObject object = recorder.getTarget().getModel().getModelObject(path);
if (!(object instanceof TargetBreakpointLocation)) {
Msg.error(this, tb.getPath() + " is not a target breakpoint location");
return;
}
TargetBreakpointLocation loc = (TargetBreakpointLocation) object;
targetLocConsumer.accept(actions, loc);
}
private void planActOnLocEmu(BreakpointActionSet actions, TraceBreakpoint tb,
BiConsumer<BreakpointActionSet, TraceBreakpoint> emuLocConsumer) {
emuLocConsumer.accept(actions, tb);
}
protected CompletableFuture<Void> actOnLocs(Collection<TraceBreakpoint> col,
BiConsumer<BreakpointActionSet, TargetBreakpointLocation> locConsumer,
BiConsumer<BreakpointActionSet, TargetBreakpointLocation> targetLocConsumer,
BiConsumer<BreakpointActionSet, TraceBreakpoint> emuLocConsumer,
Consumer<LogicalBreakpoint> progConsumer) {
BreakpointActionSet actions = new BreakpointActionSet();
for (TraceBreakpoint tb : col) {
@ -1183,36 +1303,33 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
if (col.containsAll(lb.getTraceBreakpoints())) {
progConsumer.accept(lb);
}
TraceRecorder recorder = modelService.getRecorder(tb.getTrace());
if (recorder == null) {
continue;
}
List<String> path = PathUtils.parse(tb.getPath());
TargetObject object = recorder.getTarget().getModel().getModelObject(path);
if (!(object instanceof TargetBreakpointLocation)) {
Msg.error(this, tb.getPath() + " is not a target breakpoint location");
continue;
}
TargetBreakpointLocation loc = (TargetBreakpointLocation) object;
locConsumer.accept(actions, loc);
planActOnLoc(actions, tb, targetLocConsumer, emuLocConsumer);
}
return actions.execute();
}
@Override
public CompletableFuture<Void> enableLocs(Collection<TraceBreakpoint> col) {
return actOnLocs(col, BreakpointActionSet::planEnable, LogicalBreakpoint::enableForProgram);
return actOnLocs(col,
BreakpointActionSet::planEnableTarget,
BreakpointActionSet::planEnableEmu,
LogicalBreakpoint::enableForProgram);
}
@Override
public CompletableFuture<Void> disableLocs(Collection<TraceBreakpoint> col) {
return actOnLocs(col, BreakpointActionSet::planDisable,
return actOnLocs(col,
BreakpointActionSet::planDisableTarget,
BreakpointActionSet::planDisableEmu,
LogicalBreakpoint::disableForProgram);
}
@Override
public CompletableFuture<Void> deleteLocs(Collection<TraceBreakpoint> col) {
return actOnLocs(col, BreakpointActionSet::planDelete, lb -> {
return actOnLocs(col,
BreakpointActionSet::planDeleteTarget,
BreakpointActionSet::planDeleteEmu,
lb -> {
// Never delete bookmark when user requests deleting locations
});
}
@ -1266,21 +1383,23 @@ public class DebuggerLogicalBreakpointServicePlugin extends Plugin
@Override
public void processEvent(PluginEvent event) {
if (event instanceof ProgramOpenedPluginEvent) {
ProgramOpenedPluginEvent openedEvt = (ProgramOpenedPluginEvent) event;
programOpened(openedEvt.getProgram());
if (event instanceof ProgramOpenedPluginEvent evt) {
programOpened(evt.getProgram());
}
else if (event instanceof ProgramClosedPluginEvent) {
ProgramClosedPluginEvent closedEvt = (ProgramClosedPluginEvent) event;
programClosed(closedEvt.getProgram());
else if (event instanceof ProgramClosedPluginEvent evt) {
programClosed(evt.getProgram());
}
else if (event instanceof TraceOpenedPluginEvent) {
TraceOpenedPluginEvent openedEvt = (TraceOpenedPluginEvent) event;
traceOpened(openedEvt.getTrace());
else if (event instanceof TraceOpenedPluginEvent evt) {
traceOpened(evt.getTrace());
}
else if (event instanceof TraceClosedPluginEvent) {
TraceClosedPluginEvent closedEvt = (TraceClosedPluginEvent) event;
traceClosed(closedEvt.getTrace());
else if (event instanceof TraceActivatedPluginEvent evt) {
traceSnapChanged(evt.getActiveCoordinates());
}
else if (event instanceof TraceInactiveCoordinatesPluginEvent evt) {
traceSnapChanged(evt.getCoordinates());
}
else if (event instanceof TraceClosedPluginEvent evt) {
traceClosed(evt.getTrace());
}
}
}

View file

@ -0,0 +1,39 @@
/* ###
* 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.breakpoint;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.util.database.UndoableTransaction;
public record DeleteEmuBreakpointActionItem(TraceBreakpoint bpt) implements BreakpointActionItem {
@Override
public CompletableFuture<Void> execute() {
try (UndoableTransaction tid =
UndoableTransaction.start(bpt.getTrace(), "Delete Emulated Breakpoint")) {
String emuName = PlaceEmuBreakpointActionItem.createName(bpt.getMinAddress());
if (bpt.getPath().contains(emuName)) {
bpt.delete();
}
else {
bpt.setEmuEnabled(false);
}
}
return AsyncUtils.NIL;
}
}

View file

@ -15,35 +15,12 @@
*/
package ghidra.app.plugin.core.debug.service.breakpoint;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetDeletable;
public class DeleteBreakpointActionItem implements BreakpointActionItem {
private final TargetDeletable deletable;
public DeleteBreakpointActionItem(TargetDeletable deletable) {
this.deletable = deletable;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DeleteBreakpointActionItem)) {
return false;
}
DeleteBreakpointActionItem that = (DeleteBreakpointActionItem) obj;
if (this.deletable != that.deletable) {
return false;
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(getClass(), deletable);
}
public record DeleteTargetBreakpointActionItem(TargetDeletable deletable)
implements BreakpointActionItem {
@Override
public CompletableFuture<Void> execute() {
return deletable.delete();

View file

@ -0,0 +1,34 @@
/* ###
* 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.breakpoint;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.util.database.UndoableTransaction;
public record DisableEmuBreakpointActionItem(TraceBreakpoint bpt)
implements BreakpointActionItem {
@Override
public CompletableFuture<Void> execute() {
try (UndoableTransaction tid =
UndoableTransaction.start(bpt.getTrace(), "Disable Emulated Breakpoint")) {
bpt.setEmuEnabled(false);
}
return AsyncUtils.NIL;
}
}

View file

@ -15,35 +15,12 @@
*/
package ghidra.app.plugin.core.debug.service.breakpoint;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetTogglable;
public class DisableBreakpointActionItem implements BreakpointActionItem {
private final TargetTogglable togglable;
public DisableBreakpointActionItem(TargetTogglable togglable) {
this.togglable = togglable;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DisableBreakpointActionItem)) {
return false;
}
DisableBreakpointActionItem that = (DisableBreakpointActionItem) obj;
if (this.togglable != that.togglable) {
return false;
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(getClass(), togglable);
}
public record DisableTargetBreakpointActionItem(TargetTogglable togglable)
implements BreakpointActionItem {
@Override
public CompletableFuture<Void> execute() {
return togglable.disable();

View file

@ -0,0 +1,33 @@
/* ###
* 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.breakpoint;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.util.database.UndoableTransaction;
public record EnableEmuBreakpointActionItem(TraceBreakpoint bpt) implements BreakpointActionItem {
@Override
public CompletableFuture<Void> execute() {
try (UndoableTransaction tid =
UndoableTransaction.start(bpt.getTrace(), "Enable Emulated Breakpoint")) {
bpt.setEmuEnabled(true);
}
return AsyncUtils.NIL;
}
}

View file

@ -15,35 +15,12 @@
*/
package ghidra.app.plugin.core.debug.service.breakpoint;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetTogglable;
public class EnableBreakpointActionItem implements BreakpointActionItem {
private final TargetTogglable togglable;
public EnableBreakpointActionItem(TargetTogglable togglable) {
this.togglable = togglable;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof EnableBreakpointActionItem)) {
return false;
}
EnableBreakpointActionItem that = (EnableBreakpointActionItem) obj;
if (this.togglable != that.togglable) {
return false;
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(getClass(), togglable);
}
public record EnableTargetBreakpointActionItem(TargetTogglable togglable)
implements BreakpointActionItem {
@Override
public CompletableFuture<Void> execute() {
return togglable.enable();

View file

@ -15,427 +15,23 @@
*/
package ghidra.app.plugin.core.debug.service.breakpoint;
import java.util.*;
import com.google.common.collect.Collections2;
import ghidra.app.services.LogicalBreakpoint;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.program.model.address.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import utilities.util.IDHashed;
public interface LogicalBreakpointInternal extends LogicalBreakpoint {
public static class ProgramBreakpoint {
public static Set<TraceBreakpointKind> kindsFromBookmark(Bookmark mark) {
String[] parts = mark.getCategory().split(";");
Set<TraceBreakpointKind> result = TraceBreakpointKindSet.decode(parts[0], false);
if (result.isEmpty()) {
Msg.warn(TraceBreakpointKind.class,
"Decoded empty set of kinds from bookmark. Assuming SW_EXECUTE");
return Set.of(TraceBreakpointKind.SW_EXECUTE);
}
return result;
}
public static long lengthFromBookmark(Bookmark mark) {
String[] parts = mark.getCategory().split(";");
if (parts.length < 2) {
Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
"No length for bookmark breakpoint. Assuming 1.");
return 1;
}
try {
long length = Long.parseLong(parts[1]);
if (length <= 0) {
Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
"Non-positive length for bookmark breakpoint? Using 1.");
return 1;
}
return length;
}
catch (NumberFormatException e) {
Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
"Ill-formatted bookmark breakpoint length: " + e + ". Using 1.");
return 1;
}
}
private final Program program;
private final Address address;
private final ProgramLocation location;
private final long length;
private final Set<TraceBreakpointKind> kinds;
private Bookmark eBookmark; // when present
private Bookmark dBookmark; // when present
public ProgramBreakpoint(Program program, Address address, long length,
Set<TraceBreakpointKind> kinds) {
this.program = program;
this.address = address;
this.location = new ProgramLocation(program, address);
this.length = length;
this.kinds = kinds;
}
@Override
public String toString() {
// volatile reads
Bookmark eBookmark = this.eBookmark;
Bookmark dBookmark = this.dBookmark;
if (eBookmark != null) {
return String.format("<enabled %s(%s) at %s in %s>", eBookmark.getTypeString(),
eBookmark.getCategory(), eBookmark.getAddress(), program.getName());
}
else if (dBookmark != null) {
return String.format("<disabled %s(%s) at %s in %s>", dBookmark.getTypeString(),
dBookmark.getCategory(), dBookmark.getAddress(), program.getName());
}
else {
return String.format("<absent at %s in %s>", address, program.getName());
}
}
public ProgramLocation getLocation() {
return location;
}
public String getName() {
// TODO: Be prepared to use JSON or something, if more fields are needed
Bookmark bookmark = getBookmark();
if (bookmark == null) {
return "";
}
return bookmark.getComment();
}
public void setName(String name) {
Bookmark bookmark = getBookmark();
if (bookmark == null) {
throw new IllegalStateException("Must save breakpoint to program before naming it");
}
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Rename breakpoint")) {
bookmark.set(bookmark.getCategory(), name);
}
}
public ProgramMode computeMode() {
if (eBookmark != null) {
return ProgramMode.ENABLED;
}
if (dBookmark != null) {
return ProgramMode.DISABLED;
}
else {
return ProgramMode.MISSING;
}
}
public boolean isEmpty() {
return eBookmark == null && dBookmark == null;
}
public void deleteFromProgram() {
// volatile reads
Bookmark eBookmark = this.eBookmark;
Bookmark dBookmark = this.dBookmark;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Clear breakpoint")) {
BookmarkManager bookmarkManager = program.getBookmarkManager();
if (eBookmark != null) {
bookmarkManager.removeBookmark(eBookmark);
}
if (dBookmark != null) {
bookmarkManager.removeBookmark(dBookmark);
}
// (e,d)Bookmark Gets nulled on program change callback
// If null here, logical breakpoint manager will get confused
}
}
public boolean canMerge(Program candProgram, Bookmark candBookmark) {
if (program != candProgram) {
return false;
}
if (!address.equals(candBookmark.getAddress())) {
return false;
}
if (length != lengthFromBookmark(candBookmark)) {
return false;
}
if (!Objects.equals(kinds, kindsFromBookmark(candBookmark))) {
return false;
}
return true;
}
public Program getProgram() {
return program;
}
public boolean add(Bookmark bookmark) {
if (BREAKPOINT_ENABLED_BOOKMARK_TYPE.equals(bookmark.getTypeString())) {
if (eBookmark == bookmark) {
return false;
}
eBookmark = bookmark;
return true;
}
if (BREAKPOINT_DISABLED_BOOKMARK_TYPE.equals(bookmark.getTypeString())) {
if (dBookmark == bookmark) {
return false;
}
dBookmark = bookmark;
return true;
}
return false;
}
public boolean remove(Bookmark bookmark) {
if (eBookmark == bookmark) {
eBookmark = null;
return true;
}
if (dBookmark == bookmark) {
dBookmark = null;
return true;
}
return false;
}
public Bookmark getBookmark() {
Bookmark eBookmark = this.eBookmark;
if (eBookmark != null) {
return eBookmark;
}
return dBookmark;
}
protected String getComment() {
Bookmark bookmark = getBookmark();
return bookmark == null ? "" : bookmark.getComment();
}
public boolean isEnabled() {
return computeMode() == ProgramMode.ENABLED;
}
public boolean isDisabled() {
return computeMode() == ProgramMode.DISABLED;
}
public String computeCategory() {
return TraceBreakpointKindSet.encode(kinds) + ";" + Long.toUnsignedString(length);
}
public void toggleWithComment(boolean enabled, String comment) {
String addType =
enabled ? BREAKPOINT_ENABLED_BOOKMARK_TYPE : BREAKPOINT_DISABLED_BOOKMARK_TYPE;
String delType =
enabled ? BREAKPOINT_DISABLED_BOOKMARK_TYPE : BREAKPOINT_ENABLED_BOOKMARK_TYPE;
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Enable breakpoint")) {
BookmarkManager manager = program.getBookmarkManager();
String catStr = computeCategory();
manager.setBookmark(address, addType, catStr, comment);
manager.removeBookmarks(new AddressSet(address), delType, catStr,
TaskMonitor.DUMMY);
}
catch (CancelledException e) {
throw new AssertionError(e);
}
}
public void enable() {
if (isEnabled()) {
return;
}
toggleWithComment(true, getComment());
}
public void disable() {
if (isDisabled()) {
return;
}
toggleWithComment(false, getComment());
}
}
static class TraceBreakpointSet {
private final TraceRecorder recorder;
private final Trace trace;
private final Address address;
private Set<IDHashed<TraceBreakpoint>> breakpoints = new HashSet<>();
public TraceBreakpointSet(TraceRecorder recorder, Address address) {
this.recorder = recorder;
this.trace = recorder.getTrace();
this.address = address;
}
@Override
public String toString() {
return String.format("<at %s in %s: %s>", address, trace.getName(), breakpoints);
}
public Trace getTrace() {
return trace;
}
public Address getAddress() {
return address;
}
public Address computeTargetAddress() {
return recorder.getMemoryMapper().traceToTarget(address);
}
public TraceMode computeMode() {
TraceMode mode = TraceMode.NONE;
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
mode = mode.combine(computeMode(bpt.obj));
if (mode == TraceMode.MISSING) {
return mode;
}
}
return mode;
}
public TraceMode computeMode(TraceBreakpoint bpt) {
return TraceMode.fromBool(bpt.isEnabled(recorder.getSnap()));
}
public boolean isEmpty() {
return breakpoints.isEmpty();
}
public Collection<TraceBreakpoint> getBreakpoints() {
return Collections2.transform(breakpoints, e -> e.obj);
}
public boolean add(TraceBreakpoint bpt) {
return breakpoints.add(new IDHashed<>(bpt));
}
public boolean canMerge(TraceBreakpoint bpt) {
if (trace != bpt.getTrace()) {
return false;
}
if (!address.equals(bpt.getMinAddress())) {
return false;
}
return true;
}
public boolean remove(TraceBreakpoint bpt) {
return breakpoints.remove(new IDHashed<>(bpt));
}
/**
* Plan to enable a logical breakpoint within the trace.
*
* <p>
* This method prefers to use the existing breakpoint specifications which result in
* breakpoints at this address. In other words, it favors what the user has already done to
* effect a breakpoint at this logical breakpoint's address. If there is no such existing
* specification, then it attempts to place a new breakpoint via the target's breakpoint
* container, usually resulting in a new spec, which should effect exactly the one specified
* address.
*
* <p>
* This method must convert applicable addresses to the target space. If the address cannot
* be mapped, it's usually because this logical breakpoint does not apply to the given
* trace's target. E.g., the trace may not have a live target, or the logical breakpoint may
* be in a module not loaded by the trace.
*
* @param actions the action set to populate
* @param kind the kind of breakpoint
* @return a future which completes when the plan is ready
*/
public void planEnable(BreakpointActionSet actions, long length,
Collection<TraceBreakpointKind> kinds) {
if (breakpoints.isEmpty()) {
Set<TargetBreakpointKind> tKinds =
TraceRecorder.traceToTargetBreakpointKinds(kinds);
for (TargetBreakpointSpecContainer cont : recorder
.collectBreakpointContainers(null)) {
LinkedHashSet<TargetBreakpointKind> supKinds = new LinkedHashSet<>(tKinds);
supKinds.retainAll(cont.getSupportedBreakpointKinds());
actions.add(new PlaceBreakpointActionItem(cont, computeTargetAddress(), length,
supKinds));
}
return;
}
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
TargetBreakpointLocation loc = recorder.getTargetBreakpoint(bpt.obj);
if (loc == null) {
continue;
}
actions.planEnable(loc);
}
}
public void planDisable(BreakpointActionSet actions, long length,
Collection<TraceBreakpointKind> kinds) {
Set<TargetBreakpointKind> tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds);
Address targetAddr = computeTargetAddress();
for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) {
AddressRange range = loc.getRange();
if (!targetAddr.equals(range.getMinAddress())) {
continue;
}
if (length != range.getLength()) {
continue;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (!Objects.equals(spec.getKinds(), tKinds)) {
continue;
}
actions.planDisable(loc);
}
}
public void planDelete(BreakpointActionSet actions, long length,
Set<TraceBreakpointKind> kinds) {
Set<TargetBreakpointKind> tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds);
Address targetAddr = computeTargetAddress();
for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) {
AddressRange range = loc.getRange();
if (!targetAddr.equals(range.getMinAddress())) {
continue;
}
if (length != range.getLength()) {
continue;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (!Objects.equals(spec.getKinds(), tKinds)) {
continue;
}
actions.planDelete(loc);
}
}
}
/**
* Set the expected address for trace breakpoints in the given trace
*
* @param recorder the recorder for the given trace
* @param trace the trace
* @param address the address of this logical breakpoint in the given trace
*/
void setTraceAddress(TraceRecorder recorder, Address address);
void setTraceAddress(Trace trace, Address address);
void setRecorder(Trace trace, TraceRecorder recorder);
/**
* Remove the given trace from this set

View file

@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncUtils;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Bookmark;
import ghidra.program.model.listing.Program;
@ -35,13 +36,13 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal {
private final long length;
private final Set<TraceBreakpointKind> kinds;
public LoneLogicalBreakpoint(TraceRecorder recorder, Address address, long length,
public LoneLogicalBreakpoint(PluginTool tool, Trace trace, Address address, long length,
Collection<TraceBreakpointKind> kinds) {
this.breaks = new TraceBreakpointSet(recorder, address);
this.breaks = new TraceBreakpointSet(tool, trace, address);
this.length = length;
this.kinds = Set.copyOf(kinds);
this.justThisTrace = Set.of(recorder.getTrace());
this.justThisTrace = Set.of(trace);
}
@Override
@ -85,10 +86,25 @@ public class LoneLogicalBreakpoint implements LogicalBreakpointInternal {
}
@Override
public void setTraceAddress(TraceRecorder recorder, Address address) {
public String getEmuSleigh() {
return breaks.computeSleigh();
}
@Override
public void setEmuSleigh(String sleigh) {
breaks.setEmuSleigh(sleigh);
}
@Override
public void setTraceAddress(Trace trace, Address address) {
throw new AssertionError();
}
@Override
public void setRecorder(Trace trace, TraceRecorder recorder) {
breaks.setRecorder(recorder);
}
@Override
public void removeTrace(Trace trace) {
throw new AssertionError();

View file

@ -22,6 +22,8 @@ import ghidra.app.services.DebuggerModelService;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncUtils;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Bookmark;
import ghidra.program.model.listing.Program;
@ -32,6 +34,7 @@ import ghidra.trace.model.breakpoint.TraceBreakpointKind;
public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
private final PluginTool tool;
private final Set<TraceBreakpointKind> kinds;
private final long length;
@ -41,10 +44,13 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
// They may have different names and/or different conditions
private final Map<Trace, TraceBreakpointSet> traceBreaks = new HashMap<>();
protected MappedLogicalBreakpoint(Program program, Address progAddr, long length,
protected MappedLogicalBreakpoint(PluginTool tool, Program program, Address progAddr,
long length,
Collection<TraceBreakpointKind> kinds) {
this.length = length;
this.tool = tool;
this.kinds = Set.copyOf(kinds);
this.length = length;
this.progBreak = new ProgramBreakpoint(program, progAddr, length, this.kinds);
}
@ -83,26 +89,20 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
return recorder;
}
/**
* TODO: How best to allow the user to control behavior when a new static mapping is created
* that could generate new breakpoints in the target. Options:
*
* 1) A toggle during mapping creation for how to sync breakpoints.
*
* 2) A prompt when the mapping is created, asking the user to sync breakpoints.
*
* 3) A UI indicator (probably in the breakpoints provider) that some breakpoints are not in
* sync. Different actions for directions of sync.
*
* I'm thinking both 1 and 3. Option 2 is probably too abrasive, esp., if several mappings are
* created at once.
*/
@Override
public void enableForProgram() {
progBreak.enable();
}
/**
* Place the program's bookmark with a comment specifying a desired name
*
* <p>
* <b>WARNING:</b> Use only when this breakpoint was just placed, otherwise, this will reset
* other extrinsic properties, such as the sleigh injection.
*
* @param name the desired name
*/
public void enableForProgramWithName(String name) {
progBreak.toggleWithComment(true, name);
}
@ -194,21 +194,20 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
@Override
public String generateStatusEnable(Trace trace) {
// NB. It'll place a breakpoint if mappable but not present
if (trace == null) {
for (TraceBreakpointSet breaks : traceBreaks.values()) {
if (!breaks.isEmpty()) {
if (!traceBreaks.values().isEmpty()) {
return null;
}
}
return "A breakpoint is not mapped to any live trace. Cannot enable it on target. " +
return "A breakpoint is not mapped to any trace. Cannot enable it. " +
"Is there a target? Check your module map.";
}
TraceBreakpointSet breaks = traceBreaks.get(trace);
if (breaks != null && !breaks.isEmpty()) {
if (breaks != null) {
return null;
}
return "A breakpoint is not mapped to the trace, or the trace is not live. " +
"Cannot enable it on target. Is there a target? Check your module map.";
return "A breakpoint is not mapped to the trace. " +
"Cannot enable it. Is there a target? Check your module map.";
}
@Override
@ -296,9 +295,18 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
}
@Override
public void setTraceAddress(TraceRecorder recorder, Address address) {
public void setTraceAddress(Trace trace, Address address) {
synchronized (traceBreaks) {
traceBreaks.put(recorder.getTrace(), new TraceBreakpointSet(recorder, address));
TraceBreakpointSet newSet = new TraceBreakpointSet(tool, trace, address);
newSet.setEmuSleigh(progBreak.getEmuSleigh());
traceBreaks.put(trace, newSet);
}
}
@Override
public void setRecorder(Trace trace, TraceRecorder recorder) {
synchronized (traceBreaks) {
traceBreaks.get(trace).setRecorder(recorder);
}
}
@ -440,6 +448,39 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
return progMode.combineTrace(traceMode, Perspective.LOGICAL);
}
protected String computeTraceSleigh() {
String sleigh = null;
synchronized (traceBreaks) {
for (TraceBreakpointSet breaks : traceBreaks.values()) {
String s = breaks.computeSleigh();
if (sleigh != null && !sleigh.equals(s)) {
return null;
}
sleigh = s;
}
return sleigh;
}
}
@Override
public String getEmuSleigh() {
return progBreak.getEmuSleigh();
}
protected void setTraceBreakEmuSleigh(String sleigh) {
synchronized (traceBreaks) {
for (TraceBreakpointSet breaks : traceBreaks.values()) {
breaks.setEmuSleigh(sleigh);
}
}
}
@Override
public void setEmuSleigh(String sleigh) {
progBreak.setEmuSleigh(sleigh);
setTraceBreakEmuSleigh(sleigh);
}
@Override
public boolean canMerge(Program program, Bookmark bookmark) {
return progBreak.canMerge(program, bookmark);
@ -473,10 +514,21 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
@Override
public boolean trackBreakpoint(Bookmark bookmark) {
return progBreak.add(bookmark);
if (progBreak.add(bookmark)) {
String sleigh = progBreak.getEmuSleigh();
if (sleigh != null && !SleighUtils.UNCONDITIONAL_BREAK.equals(sleigh)) {
setEmuSleigh(sleigh);
}
return true;
}
return false;
}
protected void makeBookmarkConsistent() {
// NB. Apparently it is specified that the bookmark should be created automatically
/*if (progBreak.isEmpty()) {
return;
}*/
TraceMode traceMode = computeTraceMode();
if (traceMode == TraceMode.ENABLED) {
progBreak.enable();
@ -493,6 +545,15 @@ public class MappedLogicalBreakpoint implements LogicalBreakpointInternal {
breaks = traceBreaks.get(breakpoint.getTrace());
}
boolean result = breaks.add(breakpoint);
if (result) {
String traceSleigh = computeTraceSleigh();
if (traceSleigh != null && !SleighUtils.UNCONDITIONAL_BREAK.equals(traceSleigh)) {
String progSleigh = progBreak.getEmuSleigh();
if (progSleigh == null || SleighUtils.UNCONDITIONAL_BREAK.equals(progSleigh)) {
progBreak.setEmuSleigh(traceSleigh);
}
}
}
makeBookmarkConsistent();
return result;
}

View file

@ -1,73 +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.service.breakpoint;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.program.model.address.*;
public class PlaceBreakpointActionItem implements BreakpointActionItem {
private final TargetBreakpointSpecContainer container;
private final Address address;
private final long length;
private final Set<TargetBreakpointKind> kinds;
public PlaceBreakpointActionItem(TargetBreakpointSpecContainer container, Address address,
long length, Collection<TargetBreakpointKind> kinds) {
this.container = Objects.requireNonNull(container);
this.address = Objects.requireNonNull(address);
this.length = length;
this.kinds = Set.copyOf(kinds);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PlaceBreakpointActionItem)) {
return false;
}
PlaceBreakpointActionItem that = (PlaceBreakpointActionItem) obj;
if (this.container != that.container) {
return false;
}
if (!this.address.equals(that.address)) {
return false;
}
if (!Objects.equals(this.kinds, that.kinds)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(getClass(), container, address, kinds);
}
@Override
public CompletableFuture<Void> execute() {
AddressRange range;
try {
range = new AddressRangeImpl(address, length);
}
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
return container.placeBreakpoint(range, kinds);
}
}

View file

@ -0,0 +1,114 @@
/* ###
* 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.breakpoint;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.async.AsyncUtils;
import ghidra.dbg.target.TargetBreakpointSpec;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.dbg.util.PathMatcher;
import ghidra.program.model.address.*;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceObjectMemoryRegion;
import ghidra.trace.model.target.TraceObject;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.DuplicateNameException;
public record PlaceEmuBreakpointActionItem(Trace trace, long snap, Address address, long length,
Set<TraceBreakpointKind> kinds, String emuSleigh) implements BreakpointActionItem {
public static String createName(Address address) {
return "emu-" + address;
}
public PlaceEmuBreakpointActionItem(Trace trace, long snap, Address address, long length,
Set<TraceBreakpointKind> kinds, String emuSleigh) {
this.trace = trace;
this.snap = snap;
this.address = address;
this.length = length;
this.kinds = Set.copyOf(kinds);
this.emuSleigh = emuSleigh;
}
private TraceObjectMemoryRegion findRegion() {
TraceMemoryRegion region = trace.getMemoryManager().getRegionContaining(snap, address);
if (region != null) {
return (TraceObjectMemoryRegion) region;
}
AddressSpace space = address.getAddressSpace();
Collection<? extends TraceMemoryRegion> regionsInSpace = trace.getMemoryManager()
.getRegionsIntersecting(Lifespan.at(snap),
new AddressRangeImpl(space.getMinAddress(), space.getMaxAddress()));
if (!regionsInSpace.isEmpty()) {
return (TraceObjectMemoryRegion) regionsInSpace.iterator().next();
}
return null;
}
private TraceObject findBreakpointContainer() {
TraceObjectMemoryRegion region = findRegion();
if (region == null) {
throw new IllegalArgumentException("Address does not belong to a memory in the trace");
}
return region.getObject().querySuitableTargetInterface(TargetBreakpointSpecContainer.class);
}
private String computePath() {
String name = createName(address);
if (Trace.isLegacy(trace)) {
return "Breakpoints[" + name + "]";
}
TraceObject container = findBreakpointContainer();
if (container == null) {
throw new IllegalArgumentException(
"Address is not associated with a breakpoint container");
}
PathMatcher specMatcher =
container.getTargetSchema().searchFor(TargetBreakpointSpec.class, true);
if (specMatcher == null) {
throw new IllegalArgumentException("Cannot find path to breakpoint specifications");
}
List<String> relPath = specMatcher.applyKeys(name).getSingletonPath();
if (relPath == null) {
throw new IllegalArgumentException("Too many wildcards to breakpoint specification");
}
return container.getCanonicalPath().extend(relPath).toString();
}
@Override
public CompletableFuture<Void> execute() {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Place Emulated Breakpoint")) {
// Defaults with emuEnable=true
TraceBreakpoint bpt = trace.getBreakpointManager()
.addBreakpoint(computePath(), Lifespan.at(snap), range(address, length),
Set.of(), kinds, false, null);
bpt.setName(createName(address));
bpt.setEmuSleigh(emuSleigh);
return AsyncUtils.NIL;
}
catch (DuplicateNameException e) {
throw new AssertionError(e);
}
}
}

View file

@ -0,0 +1,41 @@
/* ###
* 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.breakpoint;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.program.model.address.*;
public record PlaceTargetBreakpointActionItem(TargetBreakpointSpecContainer container,
Address address, long length, Set<TargetBreakpointKind> kinds)
implements BreakpointActionItem {
public PlaceTargetBreakpointActionItem(TargetBreakpointSpecContainer container, Address address,
long length, Set<TargetBreakpointKind> kinds) {
this.container = container;
this.address = address;
this.length = length;
this.kinds = Set.copyOf(kinds);
}
@Override
public CompletableFuture<Void> execute() {
return container.placeBreakpoint(range(address, length), kinds);
}
}

View file

@ -0,0 +1,496 @@
/* ###
* 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.breakpoint;
import java.util.*;
import com.google.gson.*;
import ghidra.app.services.LogicalBreakpoint.ProgramMode;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* The static side of a mapped logical breakpoint
*
* <p>
* Programs don't have a built-in concept of breakpoints, so we store them as breakpoints with a
* specific type for each state. We also encode other intrinsic properties (length and kinds) to the
* category. Extrinsic properties (name and sleigh) are encoded in the comment. Because traces are
* fairly ephemeral, the program bookmarks are the primary means a user has to save and manage a
* breakpoint set.
*/
public class ProgramBreakpoint {
private static final Gson GSON = new GsonBuilder().create();
/**
* A class for (de)serializing breakoint properties in the bookmark's comments
*/
static class BreakpointProperties {
public String name;
public String sleigh;
public BreakpointProperties(String name, String sleigh) {
this.name = name;
this.sleigh = sleigh;
}
}
/**
* Get the kinds of a breakpoint from its bookmark
*
* @param mark the bookmark representing a breakpoint
* @return the kinds
*/
public static Set<TraceBreakpointKind> kindsFromBookmark(Bookmark mark) {
String[] parts = mark.getCategory().split(";");
Set<TraceBreakpointKind> result = TraceBreakpointKindSet.decode(parts[0], false);
if (result.isEmpty()) {
Msg.warn(TraceBreakpointKind.class,
"Decoded empty set of kinds from bookmark. Assuming SW_EXECUTE");
return Set.of(TraceBreakpointKind.SW_EXECUTE);
}
return result;
}
/**
* Get the length of a breakpoint from its bookmark
*
* @param mark the bookmark representing a breakpoint
* @return the length in bytes
*/
public static long lengthFromBookmark(Bookmark mark) {
String[] parts = mark.getCategory().split(";");
if (parts.length < 2) {
Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
"No length for bookmark breakpoint. Assuming 1.");
return 1;
}
try {
long length = Long.parseLong(parts[1]);
if (length <= 0) {
Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
"Non-positive length for bookmark breakpoint? Using 1.");
return 1;
}
return length;
}
catch (NumberFormatException e) {
Msg.warn(DebuggerLogicalBreakpointServicePlugin.class,
"Ill-formatted bookmark breakpoint length: " + e + ". Using 1.");
return 1;
}
}
private final Program program;
private final Address address;
private final ProgramLocation location;
private final long length;
private final Set<TraceBreakpointKind> kinds;
private Bookmark eBookmark; // when present
private Bookmark dBookmark; // when present
private String name;
private String sleigh;
/**
* Construct a program breakpoint
*
* @param program the program
* @param address the static address of the breakpoint (even if a bookmark is not present there)
* @param length the length of the breakpoint in bytes
* @param kinds the kinds of the breakpoint
*/
public ProgramBreakpoint(Program program, Address address, long length,
Set<TraceBreakpointKind> kinds) {
this.program = program;
this.address = address;
this.location = new ProgramLocation(program, address);
this.length = length;
this.kinds = kinds;
}
@Override
public String toString() {
// volatile reads
Bookmark eBookmark = this.eBookmark;
Bookmark dBookmark = this.dBookmark;
if (eBookmark != null) {
return String.format("<enabled %s(%s) at %s in %s>", eBookmark.getTypeString(),
eBookmark.getCategory(), eBookmark.getAddress(), program.getName());
}
else if (dBookmark != null) {
return String.format("<disabled %s(%s) at %s in %s>", dBookmark.getTypeString(),
dBookmark.getCategory(), dBookmark.getAddress(), program.getName());
}
else {
return String.format("<absent at %s in %s>", address, program.getName());
}
}
/**
* Get the breakpoint's static program location
*
* @return the location
*/
public ProgramLocation getLocation() {
return location;
}
private void syncProperties(Bookmark bookmark) {
if (bookmark == null) {
name = "";
sleigh = null;
return;
}
String comment = bookmark.getComment();
if (comment == null || !comment.startsWith("{")) {
// Backward compatibility.
name = comment;
sleigh = null;
return;
}
try {
BreakpointProperties props = GSON.fromJson(comment, BreakpointProperties.class);
name = props.name;
sleigh = props.sleigh;
return;
}
catch (JsonSyntaxException e) {
Msg.error(this, "Could not parse breakpoint bookmark properties", e);
name = "";
sleigh = null;
return;
}
}
private String computeComment() {
if ((name == null || "".equals(name)) && (sleigh == null || "".equals(sleigh))) {
return null;
}
return GSON.toJson(new BreakpointProperties(name, sleigh));
}
private void writeProperties(Bookmark bookmark) {
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Rename breakpoint")) {
bookmark.set(bookmark.getCategory(), computeComment());
}
catch (ConcurrentModificationException e) {
/**
* Can happen during breakpoint deletion. Doesn't seem like there's a good way to check.
* In any case, we need to keep processing events, so log and continue.
*/
Msg.error(this, "Could not update breakpoint properties: " + e);
}
}
/**
* Get the user-defined name of the breakpoint
*
* @return the name
*/
public String getName() {
return name;
}
/**
* Set the name of the breakpoint
*
* @param name the name
*/
public void setName(String name) {
Bookmark bookmark = getBookmark();
if (bookmark == null) {
throw new IllegalStateException("Must save breakpoint to program before naming it");
}
this.name = name;
writeProperties(bookmark);
}
/**
* Get the sleigh injection for this breakpoint
*
* @return the sleigh injection
*/
public String getEmuSleigh() {
return sleigh;
}
/***
* Set the sleigh injection for this breakpoint
*
* @param sleigh the sleigh injection
*/
public void setEmuSleigh(String sleigh) {
this.sleigh = sleigh;
Bookmark bookmark = getBookmark();
if (bookmark == null) {
return;
}
writeProperties(bookmark);
}
/**
* Compute the mode of this breakpoint
*
* <p>
* In order to ensure at least the saved state (enablement) can be rendered in the marker margin
* in the absence of the breakpoint marker plugin, we use one type of bookmark for disabled
* breakpoints, and another for enabled breakpoints. As the state is changing, it's possible for
* a brief moment that both bookmarks are present. We thus have a variable for each bookmark and
* prefer the "enabled" state. We can determine are state by examining which variable is
* non-null. If both are null, the breakpoint is not actually saved to the program, yet. We
* cannot return {@link ProgramMode#NONE}, because that would imply there is no static location.
*
* @return the state
*/
public ProgramMode computeMode() {
if (eBookmark != null) {
return ProgramMode.ENABLED;
}
if (dBookmark != null) {
return ProgramMode.DISABLED;
}
return ProgramMode.MISSING;
}
/**
* Check if either bookmark is present
*
* @return true if both are absent, false if either or both is present
*/
public boolean isEmpty() {
return eBookmark == null && dBookmark == null;
}
/**
* Remove the bookmark
*
* <p>
* Note this does not necessarily destroy the breakpoint, since it may still exist in one or
* more traces.
*/
public void deleteFromProgram() {
// volatile reads
Bookmark eBookmark = this.eBookmark;
Bookmark dBookmark = this.dBookmark;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Clear breakpoint")) {
BookmarkManager bookmarkManager = program.getBookmarkManager();
if (eBookmark != null) {
bookmarkManager.removeBookmark(eBookmark);
}
if (dBookmark != null) {
bookmarkManager.removeBookmark(dBookmark);
}
// (e,d)Bookmark Gets nulled on program change callback
// If null here, logical breakpoint manager will get confused
}
}
/**
* Check if the given bookmark can fill the static side of this breakpoint
*
* @param candProgram the program containing the bookmark
* @param candBookmark the bookmark
* @return true if the bookmark can represent this breakpoint, false otherwise
*/
public boolean canMerge(Program candProgram, Bookmark candBookmark) {
if (program != candProgram) {
return false;
}
if (!address.equals(candBookmark.getAddress())) {
return false;
}
if (length != lengthFromBookmark(candBookmark)) {
return false;
}
if (!Objects.equals(kinds, kindsFromBookmark(candBookmark))) {
return false;
}
return true;
}
/**
* Get the program where this breakpoint is located
*
* @return the program
*/
public Program getProgram() {
return program;
}
/**
* Fill the static side of this breakpoint with the given bookmark
*
* <p>
* The caller should first use {@link #canMerge(Program, Bookmark)} to ensure the bookmark can
* actually represent this breakpoint.
*
* @param bookmark the bookmark
* @return true if this changed the breakpoint state
*/
public boolean add(Bookmark bookmark) {
if (LogicalBreakpointInternal.BREAKPOINT_ENABLED_BOOKMARK_TYPE
.equals(bookmark.getTypeString())) {
if (eBookmark == bookmark) {
return false;
}
eBookmark = bookmark;
syncProperties(bookmark);
return true;
}
if (LogicalBreakpointInternal.BREAKPOINT_DISABLED_BOOKMARK_TYPE
.equals(bookmark.getTypeString())) {
if (dBookmark == bookmark) {
return false;
}
dBookmark = bookmark;
syncProperties(bookmark);
return true;
}
return false;
}
/**
* Remove a bookmark from the static side of this breakpoint
*
* @param bookmark the bookmark
* @return true if this changed the breakpoint state
*/
public boolean remove(Bookmark bookmark) {
if (eBookmark == bookmark) {
eBookmark = null;
return true;
}
if (dBookmark == bookmark) {
dBookmark = null;
return true;
}
return false;
}
/**
* Get the bookmark representing this breakpoint, if present
*
* @return the bookmark or null
*/
public Bookmark getBookmark() {
Bookmark eBookmark = this.eBookmark;
if (eBookmark != null) {
return eBookmark;
}
return dBookmark;
}
protected String getComment() {
Bookmark bookmark = getBookmark();
return bookmark == null ? computeComment() : bookmark.getComment();
}
/**
* Check if the bookmark represents an enabled breakpoint
*
* @return true if enabled, false if anything else
*/
public boolean isEnabled() {
return computeMode() == ProgramMode.ENABLED;
}
/**
* Check if the bookmark represents a disabled breakpoint
*
* @return true if disabled, false if anything else
*/
public boolean isDisabled() {
return computeMode() == ProgramMode.DISABLED;
}
/**
* Compute the category for a new bookmark representing this breakpoint
*
* @return the category
*/
public String computeCategory() {
return TraceBreakpointKindSet.encode(kinds) + ";" + Long.toUnsignedString(length);
}
/**
* Change the state of this breakpoint by manipulating bookmarks
*
* <p>
* If the breakpoint is already in the desired state, no change is made. Otherwise, this will
* delete the existing bookmark, if present, and create a new bookmark whose type indicates the
* desired state. Thus, some event processing may need to take place before this breakpoint's
* state is actually updated accordingly.
*
* @param enabled the desired state, true for {@link ProgramMode#ENABLED}, false for
* {@link ProgramMode#DISABLED}.
* @param comment the comment to give the breakpoint, almost always from {@link #getComment()}.
*/
public void toggleWithComment(boolean enabled, String comment) {
String addType =
enabled ? LogicalBreakpointInternal.BREAKPOINT_ENABLED_BOOKMARK_TYPE
: LogicalBreakpointInternal.BREAKPOINT_DISABLED_BOOKMARK_TYPE;
String delType =
enabled ? LogicalBreakpointInternal.BREAKPOINT_DISABLED_BOOKMARK_TYPE
: LogicalBreakpointInternal.BREAKPOINT_ENABLED_BOOKMARK_TYPE;
try (UndoableTransaction tid =
UndoableTransaction.start(program, "Enable breakpoint")) {
BookmarkManager manager = program.getBookmarkManager();
String catStr = computeCategory();
manager.setBookmark(address, addType, catStr, comment);
manager.removeBookmarks(new AddressSet(address), delType, catStr,
TaskMonitor.DUMMY);
}
catch (CancelledException e) {
throw new AssertionError(e);
}
}
/**
* Enable this breakpoint
*
* @see #toggleWithComment(boolean, String)
*/
public void enable() {
if (isEnabled()) {
return;
}
toggleWithComment(true, getComment());
}
/**
* Disable this breakpoint
*
* @see #toggleWithComment(boolean, String)
*/
public void disable() {
if (isDisabled()) {
return;
}
toggleWithComment(false, getComment());
}
}

View file

@ -0,0 +1,463 @@
/* ###
* 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.breakpoint;
import java.util.*;
import com.google.common.collect.Collections2;
import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.TraceMode;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.util.database.UndoableTransaction;
import utilities.util.IDHashed;
/**
* The trace side of a logical breakpoint
*
* <p>
* If the logical breakpoint is a mapped, it will have one of these sets for each trace where the
* breakpoint has (or could have) a location. For a lone logical breakpoint, it will have just one
* of these for the one trace where its located.
*/
class TraceBreakpointSet {
private final PluginTool tool;
private final Trace trace;
private final Address address;
private final Set<IDHashed<TraceBreakpoint>> breakpoints = new HashSet<>();
private TraceRecorder recorder;
private String emuSleigh;
/**
* Create a set of breakpoint locations for a given trace
*
* @param tool the plugin tool for the UI
* @param trace the trace whose locations this set collects
* @param address the dynamic address where the breakpoint is (or would be) located
*/
public TraceBreakpointSet(PluginTool tool, Trace trace, Address address) {
this.tool = Objects.requireNonNull(tool);
this.trace = Objects.requireNonNull(trace);
this.address = Objects.requireNonNull(address);
}
@Override
public String toString() {
return String.format("<at %s in %s: %s>", address, trace.getName(), breakpoints);
}
/**
* Set the recorder when the trace is associated to a live target
*
* @param recorder the recorder
*/
public void setRecorder(TraceRecorder recorder) {
this.recorder = recorder;
}
private StateEditingMode getStateEditingMode() {
DebuggerStateEditingService service = tool.getService(DebuggerStateEditingService.class);
return service == null ? StateEditingMode.DEFAULT : service.getCurrentMode(trace);
}
private long getSnap() {
/**
* TODO: Not exactly ideal.... It'd be nice to have it passed in, but that's infecting a lot
* of methods and putting a burden on the caller, when in most cases, it's going to be the
* "current snap" anyway.
*/
DebuggerTraceManagerService service = tool.getService(DebuggerTraceManagerService.class);
if (service == null) {
return trace.getProgramView().getViewport().getReversedSnaps().get(0);
}
return service.getCurrentFor(trace).getSnap();
}
/**
* Get the trace
*
* @return
*/
public Trace getTrace() {
return trace;
}
/**
* Get the dynamic address where the breakpoint is (or would be) located in this trace
*
* @return the dynamic address
*/
public Address getAddress() {
return address;
}
/**
* If there is a live target, get the dynamic address in the target's space
*
* @return the dynamic address on target
*/
public Address computeTargetAddress() {
if (recorder == null) {
throw new AssertionError();
}
return recorder.getMemoryMapper().traceToTarget(address);
}
/**
* Compute the mode (enablement) of this set
*
* <p>
* In most cases, there is 0 or 1 trace breakpoints that "fit" the logical breakpoint. The mode
* is derived from one of {@link TraceBreakpoint#isEnabled(long)} or
* {@link TraceBreakpoint#isEmuEnabled(long)}, depending on the UI's control mode for this
* trace.
*
* @return the mode
*/
public TraceMode computeMode() {
TraceMode mode = TraceMode.NONE;
if (getStateEditingMode().useEmulatedBreakpoints()) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
mode = mode.combine(computeEmuMode(bpt.obj));
if (mode == TraceMode.MISSING) {
return mode;
}
}
return mode;
}
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
mode = mode.combine(computeTargetMode(bpt.obj));
if (mode == TraceMode.MISSING) {
return mode;
}
}
return mode;
}
/**
* Compute the mode (enablement) of the given breakpoint
*
* <p>
* The mode is derived from one of {@link TraceBreakpoint#isEnabled(long)} or
* {@link TraceBreakpoint#isEmuEnabled(long)}, depending on the UI's control mode for this
* trace.
*
* @param bpt the breakpoint
* @return the mode
*/
public TraceMode computeMode(TraceBreakpoint bpt) {
return getStateEditingMode().useEmulatedBreakpoints()
? computeEmuMode(bpt)
: computeTargetMode(bpt);
}
/**
* Compute the mode of the given breakpoint for the target
*
* @param bpt the breakpoint
* @return the mode
*/
public TraceMode computeTargetMode(TraceBreakpoint bpt) {
return TraceMode.fromBool(bpt.isEnabled(getSnap()));
}
/**
* Compute the mode of the given breakpoint for the emulator
*
* @param bpt the breakpoint
* @return the mode
*/
public TraceMode computeEmuMode(TraceBreakpoint bpt) {
return TraceMode.fromBool(bpt.isEmuEnabled(getSnap()));
}
/**
* If all breakpoints agree on sleigh injection, get that injection
*
* @return the injection, or null if there's disagreement.
*/
public String computeSleigh() {
String sleigh = null;
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
String s = bpt.obj.getEmuSleigh();
if (sleigh != null && !sleigh.equals(s)) {
return null;
}
sleigh = s;
}
return sleigh;
}
/**
* Set the sleigh injection for all breakpoints in this set
*
* @param emuSleigh the sleigh injection
*/
public void setEmuSleigh(String emuSleigh) {
this.emuSleigh = emuSleigh;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Set breakpoint Sleigh")) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
bpt.obj.setEmuSleigh(emuSleigh);
}
}
}
/**
* Check if this set actually contains any trace breakpoints
*
* @return true if empty, false otherwise
*/
public boolean isEmpty() {
return breakpoints.isEmpty();
}
/**
* Get the breakpoints in this set
*
* @return the breakpoints
*/
public Collection<TraceBreakpoint> getBreakpoints() {
return Collections2.transform(breakpoints, e -> e.obj);
}
/**
* Add a breakpoint to this set
*
* <p>
* The caller should first call {@link #canMerge(TraceBreakpoint)} to check if the breakpoint
* "fits."
*
* @param bpt
* @return true if the set actually changed as a result
*/
public boolean add(TraceBreakpoint bpt) {
if (SleighUtils.UNCONDITIONAL_BREAK.equals(bpt.getEmuSleigh()) && emuSleigh != null) {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Set breakpoint Sleigh")) {
bpt.setEmuSleigh(emuSleigh);
}
}
return breakpoints.add(new IDHashed<>(bpt));
}
/**
* Check if the given trace breakpoint "fits" in this set
*
* <p>
* The breakpoint fits if it's dynamic location matches that expected in this set
*
* @param bpt the breakpoint
* @return true if it fits
*/
public boolean canMerge(TraceBreakpoint bpt) {
if (trace != bpt.getTrace()) {
return false;
}
if (!address.equals(bpt.getMinAddress())) {
return false;
}
return true;
}
/**
* Remove a breakpoint from this set
*
* @param bpt the breakpoint
* @return true if the set actually changes as a result
*/
public boolean remove(TraceBreakpoint bpt) {
return breakpoints.remove(new IDHashed<>(bpt));
}
/**
* Plan to enable the logical breakpoint within this trace
*
* <p>
* This method prefers to use the existing breakpoint specifications which result in breakpoints
* at this address. In other words, it favors what the user has already done to effect a
* breakpoint at this logical breakpoint's address. If there is no such existing specification,
* then it attempts to place a new breakpoint via the target's breakpoint container, usually
* resulting in a new spec, which should effect exactly the one specified address. If the
* control mode indicates emulated breakpoints, then this simply writes the breakpoint to the
* trace database.
*
* <p>
* This method may convert applicable addresses to the target space. If the address cannot be
* mapped, it's usually because this logical breakpoint does not apply to the given trace's
* target. E.g., the trace may not have a live target, or the logical breakpoint may be in a
* module not loaded by the trace.
*
* @param actions the action set to populate
* @param length the length in bytes of the breakpoint
* @param kinds the kinds of breakpoint
*/
public void planEnable(BreakpointActionSet actions, long length,
Collection<TraceBreakpointKind> kinds) {
long snap = getSnap();
if (breakpoints.isEmpty()) {
if (recorder == null || getStateEditingMode().useEmulatedBreakpoints()) {
planPlaceEmu(actions, snap, length, kinds);
}
else {
planPlaceTarget(actions, snap, length, kinds);
}
}
else {
if (recorder == null || getStateEditingMode().useEmulatedBreakpoints()) {
planEnableEmu(actions);
}
else {
planEnableTarget(actions);
}
}
}
private void planPlaceTarget(BreakpointActionSet actions, long snap, long length,
Collection<TraceBreakpointKind> kinds) {
if (snap != recorder.getSnap()) {
throw new AssertionError("Target breakpoints must be requested at present snap");
}
Set<TargetBreakpointKind> tKinds =
TraceRecorder.traceToTargetBreakpointKinds(kinds);
for (TargetBreakpointSpecContainer cont : recorder
.collectBreakpointContainers(null)) {
LinkedHashSet<TargetBreakpointKind> supKinds = new LinkedHashSet<>(tKinds);
supKinds.retainAll(cont.getSupportedBreakpointKinds());
actions.add(new PlaceTargetBreakpointActionItem(cont, computeTargetAddress(),
length, supKinds));
}
}
private void planPlaceEmu(BreakpointActionSet actions, long snap, long length,
Collection<TraceBreakpointKind> kinds) {
actions.add(
new PlaceEmuBreakpointActionItem(trace, snap, address, length, Set.copyOf(kinds),
emuSleigh));
}
private void planEnableTarget(BreakpointActionSet actions) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
TargetBreakpointLocation loc = recorder.getTargetBreakpoint(bpt.obj);
if (loc == null) {
continue;
}
actions.planEnableTarget(loc);
}
}
private void planEnableEmu(BreakpointActionSet actions) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
actions.planEnableEmu(bpt.obj);
}
}
/**
* Plan to disable the logical breakpoint in this trace
*
* @param actions the action set to populate
* @param length the length in bytes of the breakpoint
* @param kinds the kinds of breakpoint
*/
public void planDisable(BreakpointActionSet actions, long length,
Collection<TraceBreakpointKind> kinds) {
if (getStateEditingMode().useEmulatedBreakpoints()) {
planDisableEmu(actions);
}
else {
planDisableTarget(actions, length, kinds);
}
}
private void planDisableTarget(BreakpointActionSet actions, long length,
Collection<TraceBreakpointKind> kinds) {
Set<TargetBreakpointKind> tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds);
Address targetAddr = computeTargetAddress();
for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) {
AddressRange range = loc.getRange();
if (!targetAddr.equals(range.getMinAddress())) {
continue;
}
if (length != range.getLength()) {
continue;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (!Objects.equals(spec.getKinds(), tKinds)) {
continue;
}
actions.planDisableTarget(loc);
}
}
private void planDisableEmu(BreakpointActionSet actions) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
actions.planDisableEmu(bpt.obj);
}
}
/**
* Plan to delete the logical breakpoint in this trace
*
* @param actions the action set to populate
* @param length the length in bytes of the breakpoint
* @param kinds the kinds of breakpoint
*/
public void planDelete(BreakpointActionSet actions, long length,
Set<TraceBreakpointKind> kinds) {
if (getStateEditingMode().useEmulatedBreakpoints()) {
planDeleteEmu(actions);
}
else {
planDeleteTarget(actions, length, kinds);
}
}
private void planDeleteTarget(BreakpointActionSet actions, long length,
Set<TraceBreakpointKind> kinds) {
Set<TargetBreakpointKind> tKinds = TraceRecorder.traceToTargetBreakpointKinds(kinds);
Address targetAddr = computeTargetAddress();
for (TargetBreakpointLocation loc : recorder.collectBreakpoints(null)) {
AddressRange range = loc.getRange();
if (!targetAddr.equals(range.getMinAddress())) {
continue;
}
if (length != range.getLength()) {
continue;
}
TargetBreakpointSpec spec = loc.getSpecification();
if (!Objects.equals(spec.getKinds(), tKinds)) {
continue;
}
actions.planDeleteTarget(loc);
}
}
private void planDeleteEmu(BreakpointActionSet actions) {
for (IDHashed<TraceBreakpoint> bpt : breakpoints) {
actions.planDeleteEmu(bpt.obj);
}
}
}

View file

@ -21,32 +21,19 @@ 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.plugin.core.debug.event.*;
import ghidra.app.services.*;
import ghidra.async.AsyncUtils;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
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.Language;
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.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.program.*;
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.trace.model.program.TraceProgramView;
import ghidra.trace.model.program.TraceProgramViewMemory;
import ghidra.util.datastruct.ListenerSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@PluginInfo(
shortDescription = "Debugger machine-state editing service plugin",
@ -56,6 +43,7 @@ import ghidra.util.task.TaskMonitor;
status = PluginStatus.RELEASED,
eventsConsumed = {
TraceOpenedPluginEvent.class,
TraceActivatedPluginEvent.class,
TraceClosedPluginEvent.class,
},
servicesRequired = {
@ -73,158 +61,14 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
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.getPlatform(), coordinates.getThread(),
coordinates.getFrame(), 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 (coordinates.getThread() == null) {
// A limitation in TraceSchedule, which is used to manifest patches
return false;
}
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;
return getCurrentMode(trace).isVariableEditable(coordinates, address, length);
}
@Override
public CompletableFuture<Void> setVariable(Address address, byte[] data) {
DebuggerCoordinates coordinates = getCoordinates();
Trace trace = coordinates.getTrace();
StateEditingMode mode = getCurrentMode(trace);
switch (mode) {
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.getPlatform(), coordinates.getThread(),
coordinates.getFrame(), address, data);
}
protected CompletableFuture<Void> writeTraceVariable(DebuggerCoordinates coordinates,
Address guestAddress, byte[] data) {
Trace trace = coordinates.getTrace();
TracePlatform platform = coordinates.getPlatform();
long snap = coordinates.getViewSnap();
Address hostAddress = platform.mapGuestToHost(guestAddress);
if (hostAddress == null) {
throw new IllegalArgumentException(
"Guest address " + guestAddress + " is not mapped");
}
TraceMemoryOperations memOrRegs;
Address overlayAddress;
try (UndoableTransaction txid =
UndoableTransaction.start(trace, "Edit Variable")) {
if (hostAddress.isRegisterAddress()) {
TraceThread thread = coordinates.getThread();
if (thread == null) {
throw new IllegalArgumentException("Register edits require a thread.");
}
TraceMemorySpace regs = trace.getMemoryManager()
.getMemoryRegisterSpace(thread, coordinates.getFrame(),
true);
memOrRegs = regs;
overlayAddress = regs.getAddressSpace().getOverlayAddress(hostAddress);
}
else {
memOrRegs = trace.getMemoryManager();
overlayAddress = hostAddress;
}
if (memOrRegs.putBytes(snap, overlayAddress,
ByteBuffer.wrap(data)) != data.length) {
return CompletableFuture.failedFuture(new MemoryAccessException());
}
}
return AsyncUtils.NIL;
}
protected CompletableFuture<Void> writeEmulatorVariable(DebuggerCoordinates coordinates,
Address address, byte[] data) {
if (!(coordinates.getView() instanceof TraceVariableSnapProgramView)) {
throw new IllegalArgumentException("Cannot emulate using a Fixed Program View");
}
TraceThread thread = coordinates.getThread();
if (thread == null) {
// TODO: Well, technically, only for register edits
// It's a limitation in TraceSchedule. Every step requires a thread
throw new IllegalArgumentException("Emulator edits require a thread.");
}
Language language = coordinates.getPlatform().getLanguage();
TraceSchedule time = coordinates.getTime()
.patched(thread, language, PatchStep.generateSleigh(language, address, data));
DebuggerCoordinates withTime = coordinates.time(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.getPlatform(), time, TaskMonitor.DUMMY);
}
catch (CancelledException e) {
throw new AssertionError(e);
}
}
return traceManager.activateAndNotify(withTime, false);
return getCurrentMode(trace).setVariable(tool, coordinates, address, data);
}
}
@ -356,10 +200,6 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
//@AutoServiceConsumed // via method
private DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
private DebuggerEmulationService emulationSerivce;
@AutoServiceConsumed
private DebuggerModelService modelService;
protected final ListenerForEditorInstallation listenerForEditorInstallation =
new ListenerForEditorInstallation();
@ -368,8 +208,6 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
super(tool);
}
private static final StateEditingMode DEFAULT_MODE = StateEditingMode.WRITE_TARGET;
private final Map<Trace, StateEditingMode> currentModes = new HashMap<>();
private final ListenerSet<StateEditingModeChangeListener> listeners =
@ -378,23 +216,23 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
@Override
public StateEditingMode getCurrentMode(Trace trace) {
synchronized (currentModes) {
return currentModes.getOrDefault(Objects.requireNonNull(trace), DEFAULT_MODE);
return currentModes.getOrDefault(Objects.requireNonNull(trace),
StateEditingMode.DEFAULT);
}
}
@Override
public void setCurrentMode(Trace trace, StateEditingMode mode) {
boolean fire = false;
public void setCurrentMode(Trace trace, StateEditingMode newMode) {
StateEditingMode oldMode;
synchronized (currentModes) {
StateEditingMode old =
currentModes.getOrDefault(Objects.requireNonNull(trace), DEFAULT_MODE);
if (mode != old) {
currentModes.put(trace, mode);
fire = true;
oldMode =
currentModes.getOrDefault(Objects.requireNonNull(trace), StateEditingMode.DEFAULT);
if (newMode != oldMode) {
currentModes.put(trace, newMode);
}
}
if (fire) {
listeners.fire.modeChanged(trace, mode);
if (newMode != oldMode) {
listeners.fire.modeChanged(trace, newMode);
tool.contextChanged(null);
}
}
@ -424,6 +262,29 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
return new FollowsViewStateEditor(view);
}
protected void coordinatesActivated(DebuggerCoordinates coordinates, ActivationCause cause) {
if (cause != ActivationCause.USER) {
return;
}
Trace trace = coordinates.getTrace();
if (trace == null) {
return;
}
StateEditingMode oldMode;
StateEditingMode newMode;
synchronized (currentModes) {
oldMode = currentModes.getOrDefault(trace, StateEditingMode.DEFAULT);
newMode = oldMode.modeOnChange(coordinates);
if (newMode != oldMode) {
currentModes.put(trace, newMode);
}
}
if (newMode != oldMode) {
listeners.fire.modeChanged(trace, newMode);
tool.contextChanged(null);
}
}
protected void installMemoryEditor(TraceProgramView view) {
TraceProgramViewMemory memory = view.getMemory();
if (memory.getLiveMemoryHandler() != null) {
@ -481,13 +342,14 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
@Override
public void processEvent(PluginEvent event) {
super.processEvent(event);
if (event instanceof TraceOpenedPluginEvent) {
TraceOpenedPluginEvent ev = (TraceOpenedPluginEvent) event;
installAllMemoryEditors(ev.getTrace());
if (event instanceof TraceOpenedPluginEvent evt) {
installAllMemoryEditors(evt.getTrace());
}
else if (event instanceof TraceClosedPluginEvent) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
uninstallAllMemoryEditors(ev.getTrace());
else if (event instanceof TraceActivatedPluginEvent evt) {
coordinatesActivated(evt.getActiveCoordinates(), evt.getCause());
}
else if (event instanceof TraceClosedPluginEvent evt) {
uninstallAllMemoryEditors(evt.getTrace());
}
}

View file

@ -41,7 +41,8 @@ import ghidra.async.AsyncLazyMap;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.pcode.emu.PcodeMachine.AccessKind;
import ghidra.pcode.emu.PcodeMachine.*;
import ghidra.pcode.exec.InjectionErrorPcodeExecutionException;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
@ -191,7 +192,12 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
@Override
protected EmulationResult compute(TaskMonitor monitor) throws CancelledException {
return doRun(from, monitor, scheduler);
EmulationResult result = doRun(from, monitor, scheduler);
if (result.error() instanceof InjectionErrorPcodeExecutionException) {
Msg.showError(this, null, "Breakpoint Emulation Error",
"Compilation error in user-provided breakpoint Sleigh code.");
}
return result;
}
}
@ -260,6 +266,8 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
private DebuggerPlatformService platformService;
@AutoServiceConsumed
private DebuggerStaticMappingService staticMappings;
@AutoServiceConsumed
private DebuggerStateEditingService editingService;
@SuppressWarnings("unused")
private AutoService.Wiring autoServiceWiring;
@ -359,6 +367,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
trace = ProgramEmulationUtils.launchEmulationTrace(program, ctx.getAddress(), this);
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
if (editingService != null) {
editingService.setCurrentMode(trace, StateEditingMode.RW_EMULATOR);
}
}
catch (IOException e) {
Msg.showError(this, null, actionEmulateProgram.getDescription(),
@ -544,7 +555,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
for (AddressSpace as : trace.getBaseAddressFactory().getAddressSpaces()) {
for (TraceBreakpoint bpt : bm.getBreakpointsIntersecting(span,
new AddressRangeImpl(as.getMinAddress(), as.getMaxAddress()))) {
if (!bpt.isEnabled(snap)) {
if (!bpt.isEmuEnabled(snap)) {
continue;
}
Set<TraceBreakpointKind> kinds = bpt.getKinds();
@ -554,7 +565,14 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
boolean isRead = kinds.contains(TraceBreakpointKind.READ);
boolean isWrite = kinds.contains(TraceBreakpointKind.WRITE);
if (isExecute) {
emu.addBreakpoint(bpt.getMinAddress(), "1:1");
try {
emu.inject(bpt.getMinAddress(), bpt.getEmuSleigh());
}
catch (Exception e) { // This is a bit broad...
Msg.error(this,
"Error compiling breakpoint Sleigh at " + bpt.getMinAddress(), e);
emu.inject(bpt.getMinAddress(), "emu_injection_err();");
}
}
if (isRead && isWrite) {
emu.addAccessBreakpoint(bpt.getRange(), AccessKind.RW);
@ -592,6 +610,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
emu.clearAllInjects();
emu.clearAccessBreakpoints();
emu.setSuspended(false);
installBreakpoints(key.trace, key.time.getSnap(), be.ce.emulator());
monitor.initialize(time.totalTickCount() - prevKey.time.totalTickCount());
createRegisterSpaces(trace, time, monitor);
@ -603,6 +622,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
DebuggerPcodeMachine<?> emu = emulatorFactory.create(tool, platform, time.getSnap(),
modelService == null ? null : modelService.getRecorder(trace));
try (BusyEmu be = new BusyEmu(new CachedEmulator(key.trace, emu))) {
installBreakpoints(key.trace, key.time.getSnap(), be.ce.emulator());
monitor.initialize(time.totalTickCount());
createRegisterSpaces(trace, time, monitor);
monitor.setMessage("Emulating");
@ -627,7 +647,15 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
protected TraceSnapshot writeToScratch(CacheKey key, CachedEmulator ce) {
try (UndoableTransaction tid = UndoableTransaction.start(key.trace, "Emulate")) {
TraceSnapshot destSnap = findScratch(key.trace, key.time);
try {
ce.emulator().writeDown(key.platform, destSnap.getKey(), key.time.getSnap());
}
catch (Throwable e) {
Msg.showError(this, null, "Emulate",
"There was an issue writing the emulation result to trace trace. " +
"The displayed state may be inaccurate and/or incomplete.",
e);
}
return destSnap;
}
}
@ -643,10 +671,11 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
protected EmulationResult doRun(CacheKey key, TaskMonitor monitor, Scheduler scheduler)
throws CancelledException {
try (BusyEmu be = doEmulateFromCached(key, monitor)) {
installBreakpoints(key.trace, key.time.getSnap(), be.ce.emulator());
TraceThread eventThread = key.time.getEventThread(key.trace);
be.ce.emulator().setSoftwareInterruptMode(SwiMode.IGNORE_STEP);
RunResult result = scheduler.run(key.trace, eventThread, be.ce.emulator(), monitor);
key = new CacheKey(key.platform, key.time.advanced(result.schedule()));
Msg.info(this, "Stopped emulation at " + key.time);
TraceSnapshot destSnap = writeToScratch(key, be.ce);
cacheEmulator(key, be.ce);
return new RecordEmulationResult(key.time, destSnap.getKey(), result.error());

View file

@ -23,6 +23,9 @@ import java.util.stream.Stream;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.DebuggerEmulationService;
import ghidra.dbg.target.*;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.*;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
@ -34,8 +37,9 @@ import ghidra.trace.database.DBTrace;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.modules.TraceConflictedMappingException;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.thread.*;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.*;
import ghidra.util.database.UndoableTransaction;
@ -137,10 +141,11 @@ public enum ProgramEmulationUtils {
* A transaction must already be started on the destination trace.
*
* @param snapshot the destination snapshot, usually 0
* @param program the progam to load
* @param program the program to load
*/
public static void loadExecutable(TraceSnapshot snapshot, Program program) {
Trace trace = snapshot.getTrace();
PathPattern patRegion = computePatternRegion(trace);
Map<AddressSpace, Extrema> extremaBySpace = new HashMap<>();
try {
for (MemoryBlock block : program.getMemory().getBlocks()) {
@ -156,8 +161,9 @@ public enum ProgramEmulationUtils {
String modName = getModuleName(program);
// TODO: Do I populate modules, since the mapping will already be done?
String path = "Modules[" + modName + "].Sections[" + block.getName() + "-" +
block.getStart() + "]";
String path = PathUtils.toString(patRegion
.applyKeys(block.getStart() + "-" + modName + ":" + block.getName())
.getSingletonPath());
trace.getMemoryManager()
.createRegion(path, snapshot.getKey(), range, getRegionFlags(block));
}
@ -176,6 +182,28 @@ public enum ProgramEmulationUtils {
// N.B. Bytes will be loaded lazily
}
public static PathPattern computePattern(Trace trace, Class<? extends TargetObject> iface) {
TargetObjectSchema root = trace.getObjectManager().getRootSchema();
if (root == null) {
return new PathPattern(PathUtils.parse("Memory[]"));
}
PathMatcher matcher = root.searchFor(iface, true);
PathPattern pattern = matcher.getSingletonPattern();
if (pattern == null || pattern.countWildcards() != 1) {
throw new IllegalArgumentException(
"Cannot find unique " + iface.getSimpleName() + " container");
}
return pattern;
}
public static PathPattern computePatternRegion(Trace trace) {
return computePattern(trace, TargetMemoryRegion.class);
}
public static PathPattern computePatternThread(Trace trace) {
return computePattern(trace, TargetThread.class);
}
/**
* Spawn a new thread in the given trace at the given creation snap
*
@ -188,12 +216,16 @@ public enum ProgramEmulationUtils {
*/
public static TraceThread spawnThread(Trace trace, long snap) {
TraceThreadManager tm = trace.getThreadManager();
PathPattern patThread = computePatternThread(trace);
long next = tm.getAllThreads().size();
while (!tm.getThreadsByPath("Threads[" + next + "]").isEmpty()) {
String path;
while (!tm.getThreadsByPath(path =
PathUtils.toString(patThread.applyKeys(Long.toString(next)).getSingletonPath()))
.isEmpty()) {
next++;
}
try {
return tm.createThread("Threads[" + next + "]", "[" + next + "]", snap);
return tm.createThread(path, "[" + next + "]", snap);
}
catch (DuplicateNameException e) {
throw new AssertionError(e);
@ -214,6 +246,20 @@ public enum ProgramEmulationUtils {
public static void initializeRegisters(Trace trace, long snap, TraceThread thread,
Program program, Address tracePc, Address programPc, TraceMemoryRegion stack) {
TraceMemoryManager memory = trace.getMemoryManager();
if (thread instanceof TraceObjectThread ot) {
TraceObject object = ot.getObject();
PathPredicates regsMatcher = object.getRoot()
.getTargetSchema()
.searchForRegisterContainer(0, object.getCanonicalPath().getKeyList());
if (regsMatcher.isEmpty()) {
throw new IllegalArgumentException("Cannot create register container");
}
for (PathPattern regsPattern : regsMatcher.getPatterns()) {
trace.getObjectManager()
.createObject(TraceObjectKeyPath.of(regsPattern.getSingletonPath()));
break;
}
}
TraceMemorySpace regSpace = memory.getMemoryRegisterSpace(thread, true);
if (program != null) {
ProgramContext ctx = program.getProgramContext();
@ -269,11 +315,18 @@ public enum ProgramEmulationUtils {
TraceMemoryManager mm = trace.getMemoryManager();
AddressSetView left =
new DifferenceAddressSetView(except0, mm.getRegionsAddressSet(snap));
PathPattern patRegion = computePatternRegion(trace);
try {
for (AddressRange candidate : left) {
if (Long.compareUnsigned(candidate.getLength(), size) > 0) {
AddressRange alloc = new AddressRangeImpl(candidate.getMinAddress(), size);
return mm.createRegion(thread.getPath() + ".Stack", snap, alloc,
String threadName = PathUtils.isIndex(thread.getName())
? PathUtils.parseIndex(thread.getName())
: thread.getName();
String path = PathUtils.toString(
patRegion.applyKeys(alloc.getMinAddress() + "-stack " + threadName)
.getSingletonPath());
return mm.createRegion(path, snap, alloc,
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
}
}

View file

@ -40,6 +40,7 @@ import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOpinion;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.AsyncFence;
import ghidra.dbg.*;
import ghidra.dbg.target.*;
@ -459,7 +460,8 @@ public class DebuggerModelServicePlugin extends Plugin
if (traceManager != null) {
Trace trace = recorder.getTrace();
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
traceManager.activate(traceManager.resolveTrace(trace),
ActivationCause.ACTIVATE_DEFAULT);
}
return recorder;
}

View file

@ -32,6 +32,7 @@ import ghidra.app.plugin.core.debug.event.*;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditingModeChangeListener;
import ghidra.async.*;
import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
import ghidra.dbg.target.*;
@ -112,7 +113,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (supportsFocus(recorder)) {
// TODO: Same for stack frame? I can't imagine it's as common as this....
if (thread == recorder.getTraceThreadForSuccessor(recorder.getFocus())) {
activate(current.thread(thread));
activate(current.thread(thread), ActivationCause.SYNC_MODEL);
}
return;
}
@ -122,16 +123,18 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (current.getThread() != null) {
return;
}
activate(current.thread(thread));
activate(current.thread(thread), ActivationCause.ACTIVATE_DEFAULT);
}
private void threadDeleted(TraceThread thread) {
synchronized (listenersByTrace) {
DebuggerCoordinates last = lastCoordsByTrace.get(trace);
if (last != null && last.getThread() == thread) {
lastCoordsByTrace.remove(trace);
}
}
if (current.getThread() == thread) {
activate(current.thread(null));
activate(current.thread(null), ActivationCause.ACTIVATE_DEFAULT);
}
}
@ -146,7 +149,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (!object.isRoot()) {
return;
}
activate(current.object(object));
activate(current.object(object), ActivationCause.SYNC_MODEL);
}
}
@ -231,17 +234,32 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
}
class ForFollowPresentListener implements StateEditingModeChangeListener {
@Override
public void modeChanged(Trace trace, StateEditingMode mode) {
if (trace != current.getTrace() || !mode.followsPresent()) {
return;
}
TraceRecorder curRecorder = current.getRecorder();
if (curRecorder == null) {
return;
}
// TODO: Also re-sync focused thread/frame?
activateNoFocus(current.snap(curRecorder.getSnap()), ActivationCause.FOLLOW_PRESENT);
}
}
protected final Map<Trace, DebuggerCoordinates> lastCoordsByTrace = new WeakHashMap<>();
protected final Map<Trace, ListenerForTraceChanges> listenersByTrace = new WeakHashMap<>();
protected final Set<Trace> tracesView = Collections.unmodifiableSet(listenersByTrace.keySet());
private final ForRecordersListener forRecordersListener = new ForRecordersListener();
private final ForFollowPresentListener forFollowPresentListener =
new ForFollowPresentListener();
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected TargetObject curObj;
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> autoActivatePresent = new AsyncReference<>(true);
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> synchronizeFocus = new AsyncReference<>(true);
@ -254,6 +272,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
private DebuggerEmulationService emulationService;
@AutoServiceConsumed
private DebuggerPlatformService platformService;
// @AutoServiceConsumed via method
private DebuggerStateEditingService editingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@ -448,6 +468,17 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
}
@AutoServiceConsumed
private void setEditingService(DebuggerStateEditingService editingService) {
if (this.editingService != null) {
this.editingService.removeModeChangeListener(forFollowPresentListener);
}
this.editingService = editingService;
if (this.editingService != null) {
this.editingService.addModeChangeListener(forFollowPresentListener);
}
}
@Override
public Class<?>[] getSupportedDataTypes() {
return new Class<?>[] { Trace.class };
@ -574,20 +605,38 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return false;
}
}
activateNoFocus(getCurrentFor(trace).object(obj));
activateNoFocus(getCurrentFor(trace).object(obj), ActivationCause.SYNC_MODEL);
return true;
}
private boolean isFollowsPresent(Trace trace) {
StateEditingMode mode = editingService == null
? StateEditingMode.DEFAULT
: editingService.getCurrentMode(trace);
return mode.followsPresent();
}
protected void doTraceRecorderAdvanced(TraceRecorder recorder, long snap) {
if (!autoActivatePresent.get()) {
Trace trace = recorder.getTrace();
if (!isFollowsPresent(trace)) {
return;
}
if (recorder.getTrace() != current.getTrace()) {
// TODO: Could advance view, which might be desirable anyway
// Would also obviate checks in resolveCoordinates and updateCurrentRecorder
if (trace != current.getTrace()) {
/**
* The snap needs to match upon re-activating this trace, lest it look like the user
* intentionally navigated to the past, causing the mode to switch away from target.
*/
DebuggerCoordinates inactive = null;
synchronized (listenersByTrace) {
DebuggerCoordinates curForTrace = getCurrentFor(trace);
inactive = curForTrace.snap(snap);
lastCoordsByTrace.put(trace, inactive);
}
trace.getProgramView().setSnap(snap);
firePluginEvent(new TraceInactiveCoordinatesPluginEvent(getName(), inactive));
return;
}
activateSnap(snap);
activate(resolveSnap(snap), ActivationCause.FOLLOW_PRESENT);
}
protected TracePlatform getPlatformForMapper(Trace trace, TraceObject object,
@ -607,7 +656,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
lastCoordsByTrace.put(trace, adj);
if (trace == current.getTrace()) {
current = adj;
fireLocationEvent(adj);
fireLocationEvent(adj, ActivationCause.MAPPER_CHANGED);
}
}
}
@ -628,12 +677,10 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return;
}
DebuggerCoordinates toActivate = current.recorder(recorder);
if (autoActivatePresent.get()) {
activate(toActivate.snap(recorder.getSnap()));
}
else {
activate(toActivate);
if (isFollowsPresent(current.getTrace())) {
toActivate = toActivate.snap(recorder.getSnap());
}
activate(toActivate, ActivationCause.FOLLOW_PRESENT);
}
@Override
@ -740,10 +787,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return emulationService.backgroundEmulate(coordinates.getPlatform(), coordinates.getTime());
}
protected CompletableFuture<Void> prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
protected CompletableFuture<Void> prepareViewAndFireEvent(DebuggerCoordinates coordinates,
ActivationCause cause) {
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
if (varView == null) { // Should only happen with NOWHERE
fireLocationEvent(coordinates);
fireLocationEvent(coordinates, cause);
return AsyncUtils.NIL;
}
return materialize(coordinates).thenAcceptAsync(snap -> {
@ -751,12 +799,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return; // We navigated elsewhere before emulation completed
}
varView.setSnap(snap);
fireLocationEvent(coordinates);
fireLocationEvent(coordinates, cause);
}, SwingExecutorService.MAYBE_NOW);
}
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates));
protected void fireLocationEvent(DebuggerCoordinates coordinates, ActivationCause cause) {
firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates, cause));
}
@Override
@ -980,7 +1028,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
//Msg.debug(this, "Remaining Consumers of " + trace + ": " + trace.getConsumerList());
}
if (current.getTrace() == trace) {
activate(DebuggerCoordinates.NOWHERE);
activate(DebuggerCoordinates.NOWHERE, ActivationCause.ACTIVATE_DEFAULT);
}
else {
contextChanged();
@ -1004,7 +1052,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
protected void dispose() {
super.dispose();
activate(DebuggerCoordinates.NOWHERE);
activate(DebuggerCoordinates.NOWHERE, ActivationCause.ACTIVATE_DEFAULT);
synchronized (listenersByTrace) {
Iterator<Trace> it = listenersByTrace.keySet().iterator();
while (it.hasNext()) {
@ -1027,12 +1075,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return elem.toString();
}
protected void activateNoFocus(DebuggerCoordinates coordinates) {
protected void activateNoFocus(DebuggerCoordinates coordinates, ActivationCause cause) {
DebuggerCoordinates resolved = doSetCurrent(coordinates);
if (resolved == null) {
return;
}
prepareViewAndFireEvent(resolved);
prepareViewAndFireEvent(resolved, cause);
}
protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) {
@ -1082,7 +1130,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
boolean syncTargetFocus) {
ActivationCause cause, boolean syncTargetFocus) {
DebuggerCoordinates prev;
DebuggerCoordinates resolved;
@ -1098,7 +1146,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (resolved == null) {
return AsyncUtils.NIL;
}
CompletableFuture<Void> future = prepareViewAndFireEvent(resolved);
CompletableFuture<Void> future = prepareViewAndFireEvent(resolved, cause);
if (!syncTargetFocus) {
return future;
}
@ -1118,12 +1166,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
}
@Override
public void activate(DebuggerCoordinates coordinates) {
activateAndNotify(coordinates, true); // Drop future on floor
}
public void activateNoFocusChange(DebuggerCoordinates coordinates) {
activateAndNotify(coordinates, false); // Drop future on floor
public void activate(DebuggerCoordinates coordinates, ActivationCause cause) {
activateAndNotify(coordinates, cause, true); // Drop future on floor
}
@Override
@ -1169,38 +1213,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return current.object(object);
}
@Override
public void setAutoActivatePresent(boolean enabled) {
autoActivatePresent.set(enabled, null);
TraceRecorder curRecorder = current.getRecorder();
if (enabled) {
// TODO: Re-sync focus. This wasn't working. Not sure it's appropriate anyway.
/*if (synchronizeFocus && curRef != null) {
if (doModelObjectFocused(curRef, false)) {
return;
}
}*/
if (curRecorder != null) {
activateNoFocus(current.snap(curRecorder.getSnap()));
}
}
}
@Override
public boolean isAutoActivatePresent() {
return autoActivatePresent.get();
}
@Override
public void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener) {
autoActivatePresent.addChangeListener(listener);
}
@Override
public void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener) {
autoActivatePresent.removeChangeListener(listener);
}
@Override
public void setSynchronizeFocus(boolean enabled) {
synchronizeFocus.set(enabled, null);
@ -1315,6 +1327,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override
public void readDataState(SaveState saveState) {
synchronized (listenersByTrace) {
int traceCount = saveState.getInt(KEY_TRACE_COUNT, 0);
for (int index = 0; index < traceCount; index++) {
String stateName = PREFIX_OPEN_TRACE + index;
@ -1325,7 +1338,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
lastCoordsByTrace.put(coords.getTrace(), coords);
}
}
}
activate(DebuggerCoordinates.readDataState(tool, saveState, KEY_CURRENT_COORDS));
activate(DebuggerCoordinates.readDataState(tool, saveState, KEY_CURRENT_COORDS),
ActivationCause.RESTORE_STATE);
}
}

View file

@ -30,9 +30,9 @@ public class DebouncedRowWrappedEnumeratedColumnTableModel<C extends Enum<C> & E
AsyncDebouncer<Void> debouncer = new AsyncDebouncer<Void>(AsyncTimer.DEFAULT_TIMER, 100);
public DebouncedRowWrappedEnumeratedColumnTableModel(PluginTool tool, String name,
Class<C> colType,
Function<T, K> keyFunc, Function<T, R> wrapper) {
super(tool, name, colType, keyFunc, wrapper);
Class<C> colType, Function<T, K> keyFunc, Function<T, R> wrapper,
Function<R, T> getter) {
super(tool, name, colType, keyFunc, wrapper, getter);
debouncer.addListener(this::settled);
}

View file

@ -33,7 +33,7 @@ import ghidra.trace.model.program.TraceProgramView;
@ServiceInfo( //
defaultProvider = DebuggerLogicalBreakpointServicePlugin.class,
description = "Aggregate breakpoints for programs and live traces")
description = "Aggregate breakpoints for programs and traces")
public interface DebuggerLogicalBreakpointService {
/**
* Get all logical breakpoints known to the tool.
@ -54,11 +54,11 @@ public interface DebuggerLogicalBreakpointService {
NavigableMap<Address, Set<LogicalBreakpoint>> getBreakpoints(Program program);
/**
* Get a map of addresses to collected logical breakpoints (at present) for a given trace.
* Get a map of addresses to collected logical breakpoints for a given trace.
*
* <p>
* The trace must be associated with a live target. The returned map collects live breakpoints
* in the recorded target, using trace breakpoints from the recorder's current snapshot.
* The map only includes breakpoints visible in the trace's primary view. Visibility depends on
* the view's snapshot.
*
* @param trace the trace database
* @return the map of logical breakpoints
@ -78,12 +78,11 @@ public interface DebuggerLogicalBreakpointService {
Set<LogicalBreakpoint> getBreakpointsAt(Program program, Address address);
/**
* Get the collected logical breakpoints (at present) at the given trace location.
* Get the collected logical breakpoints at the given trace location.
*
* <p>
* The trace must be associated with a live target. The returned collection includes live
* breakpoints in the recorded target, using trace breakpoints from the recorders' current
* snapshot.
* The set only includes breakpoints visible in the trace's primary view. Visibility depends on
* the view's snapshot.
*
* @param trace the trace database
* @param address the address
@ -95,8 +94,8 @@ public interface DebuggerLogicalBreakpointService {
* Get the logical breakpoint of which the given trace breakpoint is a part
*
* <p>
* If the given trace breakpoint is not part of any logical breakpoint, e.g., because it is not
* on a live target, then null is returned.
* If the given trace breakpoint is not part of any logical breakpoint, e.g., because the trace
* is not opened in the tool or events are still being processed, then null is returned.
*
* @param bpt the trace breakpoint
* @return the logical breakpoint, or null
@ -108,8 +107,8 @@ public interface DebuggerLogicalBreakpointService {
*
* <p>
* The {@code program} field for the location may be either a program database (static image) or
* a view for a trace associated with a live target. If it is the latter, the view's current
* snapshot is ignored, in favor of the associated recorder's current snapshot.
* a view for a trace. If it is the latter, the view's snapshot is ignored in favor of the
* trace's primary view's snapshot.
*
* <p>
* If {@code program} is a static image, this is equivalent to using
@ -252,8 +251,7 @@ public interface DebuggerLogicalBreakpointService {
}
/**
* Create an enabled breakpoint at the given program location and each mapped live trace
* location.
* Create an enabled breakpoint at the given program location and each mapped trace location.
*
* <p>
* The implementation should take care not to create the same breakpoint multiple times. The
@ -278,12 +276,12 @@ public interface DebuggerLogicalBreakpointService {
* this is the case, the breakpoint will have no name.
*
* <p>
* Note, the debugger ultimately determines the placement behavior. If it is managing multiple
* targets, it is possible the breakpoint will be effective in another trace. This fact should
* be reflected correctly in the resulting logical markings once all resulting events have been
* processed.
* Note for live targets, the debugger ultimately determines the placement behavior. If it is
* managing multiple targets, it is possible the breakpoint will be effective in another trace.
* This fact should be reflected correctly in the resulting logical markings once all resulting
* events have been processed.
*
* @param trace the given trace, which must be live
* @param trace the given trace
* @param address the address in the trace (as viewed in the present)
* @param length size of the breakpoint, may be ignored by debugger
* @param kinds the kinds of breakpoint
@ -367,7 +365,7 @@ public interface DebuggerLogicalBreakpointService {
CompletableFuture<Void> deleteAll(Collection<LogicalBreakpoint> col, Trace trace);
/**
* Presuming the given locations are live, enable them
* Enable the given locations
*
* @param col the trace breakpoints
* @return a future which completes when the command has been processed
@ -375,7 +373,7 @@ public interface DebuggerLogicalBreakpointService {
CompletableFuture<Void> enableLocs(Collection<TraceBreakpoint> col);
/**
* Presuming the given locations are live, disable them
* Disable the given locations
*
* @param col the trace breakpoints
* @return a future which completes when the command has been processed
@ -383,7 +381,7 @@ public interface DebuggerLogicalBreakpointService {
CompletableFuture<Void> disableLocs(Collection<TraceBreakpoint> col);
/**
* Presuming the given locations are live, delete them
* Delete the given locations
*
* @param col the trace breakpoints
* @return a future which completes when the command has been processed

View file

@ -17,16 +17,12 @@ 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.lang.*;
import ghidra.program.model.mem.LiveMemoryHandler;
import ghidra.trace.model.Trace;
import ghidra.trace.model.program.TraceProgramView;
@ -35,47 +31,6 @@ import ghidra.trace.model.program.TraceProgramView;
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();

View file

@ -38,6 +38,49 @@ import ghidra.util.TriConsumer;
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
public interface DebuggerTraceManagerService {
/**
* The reason coordinates were activated
*/
public enum ActivationCause {
/**
* The change was driven by the user
*
* <p>
* TODO: Distinguish between API and GUI?
*/
USER,
/**
* A trace was activated because a recording was started, usually when a target is launched
*/
START_RECORDING,
/**
* The change was driven by the model focus, possibly indirectly by the user
*/
SYNC_MODEL,
/**
* The change was driven by the recorder advancing a snapshot
*/
FOLLOW_PRESENT,
/**
* The change was caused by a change to the mapper selection, probably indirectly by the
* user
*/
MAPPER_CHANGED,
/**
* Some default coordinates were activated
*
* <p>
* Please don't misunderstand this as the "default cause." Rather, e.g., when the current
* trace is closed, and the manager needs to activate new coordinates, it is activating
* "default coordinates."
*/
ACTIVATE_DEFAULT,
/**
* The tool is restoring its data state
*/
RESTORE_STATE,
}
/**
* An adapter that works nicely with an {@link AsyncReference}
*
@ -69,7 +112,7 @@ public interface DebuggerTraceManagerService {
* Get the current coordinates
*
* <p>
* This entails everything except the current address
* This entails everything except the current address.
*
* @return the current coordinates
*/
@ -233,11 +276,22 @@ public interface DebuggerTraceManagerService {
* thread for the desired trace.
*
* @param coordinates the desired coordinates
* @param cause the cause of the activation
* @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);
ActivationCause cause, boolean syncTargetFocus);
/**
* Activate the given coordinates, caused by the user
*
* @see #activate(DebuggerCoordinates, ActivationCause)
* @param coordinates the desired coordinates
*/
default void activate(DebuggerCoordinates coordinates) {
activate(coordinates, ActivationCause.USER);
}
/**
* Activate the given coordinates, synchronizing the current target, if possible
@ -247,8 +301,9 @@ public interface DebuggerTraceManagerService {
* {@link #activateAndNotify(DebuggerCoordinates, boolean)}.
*
* @param coordinates the desired coordinates
* @param cause the cause of activation
*/
void activate(DebuggerCoordinates coordinates);
void activate(DebuggerCoordinates coordinates, ActivationCause cause);
/**
* Resolve coordinates for the given trace using the manager's "best judgment"
@ -388,38 +443,6 @@ public interface DebuggerTraceManagerService {
activate(resolveObject(object));
}
/**
* Control whether the trace manager automatically activates the "present snapshot"
*
* <p>
* Auto activation only applies when the current trace advances. It never changes to another
* trace.
*
* @param enabled true to enable auto activation
*/
void setAutoActivatePresent(boolean enabled);
/**
* Check if the trace manager automatically activate the "present snapshot"
*
* @return true if auto activation is enabled
*/
boolean isAutoActivatePresent();
/**
* Add a listener for changes to auto activation enablement
*
* @param listener the listener to receive change notifications
*/
void addAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
/**
* Remove a listener for changes to auto activation enablement
*
* @param listener the listener receiving change notifications
*/
void removeAutoActivatePresentChangeListener(BooleanChangeAdapter listener);
/**
* Control whether trace activation is synchronized with debugger focus/select
*

View file

@ -30,6 +30,30 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
/**
* A logical breakpoint
*
* <p>
* This is a collection of at most one program breakpoint, which is actually a bookmark with a
* special type, and any number of trace breakpoints. The program breakpoint represents the logical
* breakpoint, as this is the most stable anchor for keeping the user's breakpoint set. All
* breakpoints in the set correspond to the same address when considering the module map (or other
* source of static-to-dynamic mapping), which may involve relocation. They also share the same
* kinds and length, since these are more or less intrinsic to the breakpoints specification. Thus,
* more than one logical breakpoint may occupy the same address. A logical breakpoints having a
* program bookmark (or that at least has a static address) is called a "mapped" breakpoint. This is
* the ideal, ordinary case. A breakpoint that cannot be mapped to a static address (and thus cannot
* have a program bookmark) is called a "lone" breakpoint.
*
* <p>
* <b>WARNING:</b> The lifecycle of a logical breakpoint is fairly volatile. It is generally not
* safe to "hold onto" a logical breakpoint, since with any event, the logical breakpoint service
* may discard and re-create the object, even if it's composed of the same program and trace
* breakpoints. If it is truly necessary to hold onto logical breakpoints, consider using
* {@link DebuggerLogicalBreakpointService#addChangeListener(LogicalBreakpointsChangeListener)}. A
* logical breakpoint is valid until the service invokes
* {@link LogicalBreakpointsChangeListener#breakpointRemoved(LogicalBreakpoint)}.
*/
public interface LogicalBreakpoint {
String BREAKPOINT_ENABLED_BOOKMARK_TYPE = "BreakpointEnabled";
String BREAKPOINT_DISABLED_BOOKMARK_TYPE = "BreakpointDisabled";
@ -605,6 +629,22 @@ public interface LogicalBreakpoint {
*/
void setName(String name);
/**
* Get the sleigh injection when emulating this breakpoint
*
* @return the sleigh injection
* @see TraceBreakpoint#getEmuSleigh()
*/
String getEmuSleigh();
/**
* Set the sleigh injection when emulating this breakpoint
*
* @param sleigh the sleigh injection
* @see TraceBreakpoint#setEmuSleigh(String)
*/
void setEmuSleigh(String sleigh);
/**
* If the logical breakpoint has a mapped program location, get that location.
*

View file

@ -0,0 +1,431 @@
/* ###
* 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.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import javax.swing.Icon;
import generic.theme.GIcon;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.services.DebuggerTraceManagerService.ActivationCause;
import ghidra.async.AsyncUtils;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.program.TraceVariableSnapProgramView;
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.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* The control / state editing modes
*/
public enum StateEditingMode {
/**
* Control actions, breakpoint commands are directed to the target, but state edits are
* rejected.
*/
RO_TARGET("Control Target w/ Edits Disabled", new GIcon("icon.debugger.edit.mode.ro.target")) {
@Override
public boolean followsPresent() {
return true;
}
@Override
public boolean canEdit(DebuggerCoordinates coordinates) {
return false;
}
@Override
public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
int length) {
return false;
}
@Override
public CompletableFuture<Void> setVariable(PluginTool tool,
DebuggerCoordinates coordinates, Address address, byte[] data) {
return CompletableFuture.failedFuture(new MemoryAccessException("Read-only mode"));
}
@Override
public boolean useEmulatedBreakpoints() {
return false;
}
@Override
public boolean isSelectable(DebuggerCoordinates coordinates) {
return coordinates.isAliveAndPresent();
}
@Override
public StateEditingMode getAlternative(DebuggerCoordinates coordinates) {
return RO_TRACE;
}
},
/**
* Control actions, breakpoint commands, and state edits are all directed to the target.
*/
RW_TARGET("Control Target", new GIcon("icon.debugger.edit.mode.rw.target")) {
@Override
public boolean followsPresent() {
return true;
}
@Override
public boolean canEdit(DebuggerCoordinates coordinates) {
return coordinates.isAliveAndPresent();
}
@Override
public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
int length) {
if (!coordinates.isAliveAndPresent()) {
return false;
}
TraceRecorder recorder = coordinates.getRecorder();
return recorder.isVariableOnTarget(coordinates.getPlatform(),
coordinates.getThread(), coordinates.getFrame(), address, length);
}
@Override
public CompletableFuture<Void> setVariable(PluginTool tool,
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.getPlatform(), coordinates.getThread(),
coordinates.getFrame(), address, data);
}
@Override
public boolean useEmulatedBreakpoints() {
return false;
}
@Override
public boolean isSelectable(DebuggerCoordinates coordinates) {
return coordinates.isAliveAndPresent();
}
@Override
public StateEditingMode getAlternative(DebuggerCoordinates coordinates) {
return RW_EMULATOR;
}
},
/**
* Control actions activate trace snapshots, breakpoint commands are directed to the emulator,
* and state edits are rejected.
*/
RO_TRACE("Control Trace w/ Edits Disabled", new GIcon("icon.debugger.edit.mode.ro.trace")) {
@Override
public boolean followsPresent() {
return false;
}
@Override
public boolean canEdit(DebuggerCoordinates coordinates) {
return false;
}
@Override
public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
int length) {
return false;
}
@Override
public CompletableFuture<Void> setVariable(PluginTool tool,
DebuggerCoordinates coordinates, Address address, byte[] data) {
return CompletableFuture.failedFuture(new MemoryAccessException("Read-only mode"));
}
@Override
public boolean useEmulatedBreakpoints() {
return true;
}
},
/**
* Control actions activate trace snapshots, breakpoint commands are directed to the emulator,
* and state edits modify the current trace snapshot.
*/
RW_TRACE("Control Trace", new GIcon("icon.debugger.edit.mode.rw.trace")) {
@Override
public boolean followsPresent() {
return false;
}
@Override
public boolean canEdit(DebuggerCoordinates coordinates) {
return coordinates.getTrace() != null;
}
@Override
public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
int length) {
return address.isMemoryAddress() || coordinates.getThread() != null;
}
@Override
public CompletableFuture<Void> setVariable(PluginTool tool,
DebuggerCoordinates coordinates, Address guestAddress, byte[] data) {
Trace trace = coordinates.getTrace();
TracePlatform platform = coordinates.getPlatform();
long snap = coordinates.getViewSnap();
Address hostAddress = platform.mapGuestToHost(guestAddress);
if (hostAddress == null) {
throw new IllegalArgumentException(
"Guest address " + guestAddress + " is not mapped");
}
TraceMemoryOperations memOrRegs;
Address overlayAddress;
try (UndoableTransaction txid =
UndoableTransaction.start(trace, "Edit Variable")) {
if (hostAddress.isRegisterAddress()) {
TraceThread thread = coordinates.getThread();
if (thread == null) {
throw new IllegalArgumentException("Register edits require a thread.");
}
TraceMemorySpace regs = trace.getMemoryManager()
.getMemoryRegisterSpace(thread, coordinates.getFrame(),
true);
memOrRegs = regs;
overlayAddress = regs.getAddressSpace().getOverlayAddress(hostAddress);
}
else {
memOrRegs = trace.getMemoryManager();
overlayAddress = hostAddress;
}
if (memOrRegs.putBytes(snap, overlayAddress,
ByteBuffer.wrap(data)) != data.length) {
return CompletableFuture.failedFuture(new MemoryAccessException());
}
}
return AsyncUtils.NIL;
}
@Override
public boolean useEmulatedBreakpoints() {
return true;
}
},
/**
* Control actions, breakpoint commands, and state edits are directed to the emulator.
*
* <p>
* Edits are accomplished by appending patch steps to the current schedule and activating that
* schedule.
*/
RW_EMULATOR("Control Emulator", new GIcon("icon.debugger.edit.mode.rw.emulator")) {
@Override
public boolean followsPresent() {
return false;
}
@Override
public boolean canEdit(DebuggerCoordinates coordinates) {
return coordinates.getTrace() != null;
}
@Override
public boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
int length) {
if (coordinates.getThread() == null) {
// A limitation in TraceSchedule, which is used to manifest patches
return false;
}
if (!RW_TRACE.isVariableEditable(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(PluginTool tool,
DebuggerCoordinates coordinates, Address address, byte[] data) {
if (!(coordinates.getView() instanceof TraceVariableSnapProgramView)) {
throw new IllegalArgumentException("Cannot emulate using a Fixed Program View");
}
TraceThread thread = coordinates.getThread();
if (thread == null) {
// TODO: Well, technically, only for register edits
// It's a limitation in TraceSchedule. Every step requires a thread
throw new IllegalArgumentException("Emulator edits require a thread.");
}
Language language = coordinates.getPlatform().getLanguage();
TraceSchedule time = coordinates.getTime()
.patched(thread, language,
PatchStep.generateSleigh(language, address, data));
DebuggerCoordinates withTime = coordinates.time(time);
DebuggerTraceManagerService traceManager =
Objects.requireNonNull(tool.getService(DebuggerTraceManagerService.class),
"No trace manager service");
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
DebuggerEmulationService emulationService = Objects.requireNonNull(
tool.getService(DebuggerEmulationService.class), "No emulation service");
try {
emulationService.emulate(coordinates.getPlatform(), time,
TaskMonitor.DUMMY);
}
catch (CancelledException e) {
throw new AssertionError(e);
}
}
return traceManager.activateAndNotify(withTime, ActivationCause.USER, false);
}
@Override
public boolean useEmulatedBreakpoints() {
return true;
}
};
public static final StateEditingMode DEFAULT = RO_TARGET;
public final String name;
public final Icon icon;
private StateEditingMode(String name, Icon icon) {
this.name = name;
this.icon = icon;
}
/**
* Check if the UI should keep its active snapshot in sync with the recorder's latest.
*
* @return true to follow, false if not
*/
public abstract boolean followsPresent();
/**
* Check if (broadly speaking) the mode supports editing the given coordinates
*
* @param coordinates the coordinates to check
* @return true if editable, false if not
*/
public abstract boolean canEdit(DebuggerCoordinates coordinates);
/**
* Check if the given variable can be edited under this mode
*
* @param coordinates the coordinates to check
* @param address the address of the variable
* @param length the length of the variable, in bytes
* @return true if editable, false if not
*/
public abstract boolean isVariableEditable(DebuggerCoordinates coordinates, Address address,
int length);
/**
* Set the value of a variable
*
* <p>
* Because the edit may be directed to a live target, the return value is a
* {@link CompletableFuture}. Additionally, when directed to the emulator, this allows the
* emulated state to be computed in the background.
*
* @param tool the tool requesting the edit
* @param coordinates the coordinates of the edit
* @param address the address of the variable
* @param data the desired value of the variable
* @return a future which completes when the edit is finished
*/
public abstract CompletableFuture<Void> setVariable(PluginTool tool,
DebuggerCoordinates coordinates, Address address, byte[] data);
/**
* Check if this mode operates on target breakpoints or emulator breakpoints
*
* @return false for target, true for emulator
*/
public abstract boolean useEmulatedBreakpoints();
/**
* Check if this mode can be selected for the given coordinates
*
* @param coordinates the current coordinates
* @return true to enable selection, false to disable
*/
public boolean isSelectable(DebuggerCoordinates coordinates) {
return true;
}
/**
* If the mode can no longer be selected for new coordinates, get the new mode
*
* <p>
* For example, if a target terminates while the mode is {@link #RO_TARGET}, this specifies the
* new mode.
*
* @param coordinates the new coordinates
* @return the new mode
*/
public StateEditingMode getAlternative(DebuggerCoordinates coordinates) {
throw new AssertionError("INTERNAL: Non-selectable mode must provide alternative");
}
/**
* Find the new mode (or same) mode when activating the given coordinates
*
* <p>
* The default is implemented using {@link #isSelectable(DebuggerCoordinates)} followed by
* {@link #getAlternative(DebuggerCoordinates)}.
*
* @param coordinates the new coordinates
* @return the mode
*/
public StateEditingMode modeOnChange(DebuggerCoordinates coordinates) {
if (isSelectable(coordinates)) {
return this;
}
return getAlternative(coordinates);
}
}

View file

@ -30,7 +30,6 @@ import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOf
import ghidra.app.script.GhidraScript;
import ghidra.app.script.GhidraState;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.dbg.AnnotatedDebuggerAttributeListener;
import ghidra.dbg.DebuggerObjectModel;
@ -766,9 +765,9 @@ public interface FlatDebuggerAPI {
}
/**
* Apply the given SLEIGH patch to the emulator
* Apply the given Sleigh patch to the emulator
*
* @param sleigh the SLEIGH source, without terminating semicolon
* @param sleigh the Sleigh source, without terminating semicolon
* @param monitor a monitor for the emulation
* @return true if successful, false otherwise
* @throws CancelledException if the user cancelled via the given monitor

View file

@ -16,12 +16,9 @@
package ghidra.pcode.exec;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import org.bouncycastle.util.Arrays;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
@ -214,7 +211,7 @@ public enum DebuggerPcodeUtils {
if (this.bigEndian != that.bigEndian) {
return false;
}
return Arrays.areEqual(this.bytes, that.bytes);
return Arrays.equals(this.bytes, that.bytes);
}
/**

View file

@ -137,6 +137,18 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
new DefaultTraceLocation(trace3, null, Lifespan.nowOn(0), addr(trace3, 0x7fac0000)),
new ProgramLocation(program, addr(program, 0x00400000)), 0x00010000, false);
}
waitForSwing();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add breakpoint")) {
program.getBookmarkManager()
.setBookmark(addr(program, 0x00401234),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1",
"before connect");
program.getBookmarkManager()
.setBookmark(addr(program, 0x00604321),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "WRITE;4",
"write version");
}
TargetBreakpointSpecContainer bc1 =
waitFor(() -> Unique.assertAtMostOne(recorder1.collectBreakpointContainers(null)),
@ -156,17 +168,6 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera
trace3.getBreakpointManager()
.getBreakpointsAt(recorder3.getSnap(), addr(trace3, 0x7fac1234))));
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add breakpoint")) {
program.getBookmarkManager()
.setBookmark(addr(program, 0x00401234),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1",
"before connect");
program.getBookmarkManager()
.setBookmark(addr(program, 0x00604321),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "WRITE;4",
"write version");
}
waitForPass(() -> {
Set<LogicalBreakpoint> allBreakpoints = breakpointService.getAllBreakpoints();
assertEquals(2, allBreakpoints.size());

View file

@ -38,7 +38,6 @@ import ghidra.app.plugin.core.debug.stack.*;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.async.AsyncTestUtils;
import ghidra.framework.model.DomainFolder;
@ -297,7 +296,7 @@ public class DebuggerStackPluginScreenShots extends GhidraScreenShotGenerator
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();

View file

@ -45,7 +45,6 @@ import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.async.AsyncTestUtils;
@ -259,7 +258,7 @@ public class VariableValueHoverPluginScreenShots extends GhidraScreenShotGenerat
}
waitForDomainObject(tb.trace);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();

View file

@ -39,7 +39,6 @@ import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlug
import ghidra.app.plugin.core.debug.service.workflow.DebuggerWorkflowServiceProxyPlugin;
import ghidra.app.plugin.core.debug.workflow.DisassembleAtPcDebuggerBot;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.dbg.target.TargetEnvironment;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
@ -411,7 +410,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
createLegacyTrace("ARM:LE:32:v8", 0x00400000, () -> tb.buf(0x00, 0x00, 0x00, 0x00));
Address start = tb.addr(0x00400000);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, null, 0));
@ -444,7 +443,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
// Don't cheat here and choose v8T!
createLegacyTrace("ARM:LE:32:v8", 0x00400000, () -> tb.buf(0x00, 0x00));
Address start = tb.addr(0x00400000);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, null, 0));
@ -481,7 +480,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
TraceObjectThread thread =
createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf(0x00, 0x00, 0x00, 0x00));
Address start = tb.addr(0x00400000);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0));
@ -516,7 +515,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
TraceObjectThread thread =
createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf(0x00, 0x00));
Address start = tb.addr(0x00400000);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0));
@ -573,7 +572,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
createLegacyTrace("ARM:LE:32:v8", 0x00400000, () -> tb.buf());
Address start = tb.addr(0x00400000);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, null, 0));
@ -593,7 +592,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
TraceObjectThread thread = createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf());
Address start = tb.addr(0x00400000);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0));
@ -613,7 +612,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerGUITest
TraceObjectThread thread = createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf());
Address start = tb.addr(0x00400000);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
// Ensure the mapper is added to the trace
assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0));

View file

@ -269,6 +269,24 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
}, () -> lastError.get().getMessage());
}
public static <T> T waitForPass(Supplier<T> supplier) {
var locals = new Object() {
AssertionError lastError;
T value;
};
waitForCondition(() -> {
try {
locals.value = supplier.get();
return true;
}
catch (AssertionError e) {
locals.lastError = e;
return false;
}
}, () -> locals.lastError.getMessage());
return locals.value;
}
protected static Set<String> getMenuElementsText(MenuElement menu) {
Set<String> result = new HashSet<>();
for (MenuElement sub : menu.getSubElements()) {

View file

@ -455,8 +455,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
}
waitForDomainObject(program);
performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
staticCtx(addr(program, 0x00400123)), false);
ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, false);
DebuggerPlaceBreakpointDialog dialog =
waitForDialogComponent(DebuggerPlaceBreakpointDialog.class);
runSwing(() -> dialog.okCallback());
@ -482,8 +483,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
}
waitForDomainObject(program);
performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
staticCtx(addr(program, 0x00400123)), false);
ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, false);
DebuggerPlaceBreakpointDialog dialog =
waitForDialogComponent(DebuggerPlaceBreakpointDialog.class);
runSwing(() -> dialog.okCallback());
@ -502,13 +504,14 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
addMappedBreakpointOpenAndWait();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
staticCtx(addr(program, 0x00400123)), false);
ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, false);
waitForPass(() -> assertEquals(State.DISABLED, lb.computeState()));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
staticCtx(addr(program, 0x00400123)), false);
assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, false);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeState()));
}
@ -522,13 +525,14 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
// NB. addMappedBreakpointOpenAndWait already makes this assertion. Just a reminder:
waitForPass(() -> assertEquals(State.ENABLED, lb.computeStateForTrace(trace)));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
dynamicCtx(trace, addr(trace, 0x55550123)), true);
ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550123));
assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.DISABLED, lb.computeStateForTrace(trace)));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
dynamicCtx(trace, addr(trace, 0x55550123)), true);
assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeStateForTrace(trace)));
@ -538,8 +542,8 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
lb.disableForProgram();
waitForPass(() -> assertEquals(State.INCONSISTENT_ENABLED, lb.computeStateForTrace(trace)));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
dynamicCtx(trace, addr(trace, 0x55550123)), true);
assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, true);
// NB. toggling from dynamic view, this toggles trace bpt, not logical/program bpt
waitForPass(() -> assertEquals(State.DISABLED, lb.computeStateForTrace(trace)));
@ -548,8 +552,8 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
waitForPass(
() -> assertEquals(State.INCONSISTENT_DISABLED, lb.computeStateForTrace(trace)));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint,
dynamicCtx(trace, addr(trace, 0x55550123)), true);
assertTrue(breakpointMarkerPlugin.actionToggleBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionToggleBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeStateForTrace(trace)));
}
@ -558,7 +562,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
Set<TraceBreakpointKind> expectedKinds) throws Throwable {
addMappedBreakpointOpenAndWait(); // Adds an unneeded breakpoint. Aw well.
performAction(action, staticCtx(addr(program, 0x0400321)), false);
ProgramLocationActionContext ctx = staticCtx(addr(program, 0x0400321));
assertTrue(action.isAddToPopup(ctx));
performAction(action, ctx, false);
DebuggerPlaceBreakpointDialog dialog =
waitForDialogComponent(DebuggerPlaceBreakpointDialog.class);
runSwing(() -> {
@ -580,7 +586,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
TraceRecorder recorder = addMappedBreakpointOpenAndWait(); // Adds an unneeded breakpoint. Aw well.
Trace trace = recorder.getTrace();
performAction(action, dynamicCtx(trace, addr(trace, 0x55550321)), false);
ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550321));
assertTrue(action.isAddToPopup(ctx));
performAction(action, ctx, false);
DebuggerPlaceBreakpointDialog dialog =
waitForDialogComponent(DebuggerPlaceBreakpointDialog.class);
runSwing(() -> dialog.okCallback());
@ -664,8 +672,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
lb.disable();
waitForPass(() -> assertEquals(State.DISABLED, lb.computeState()));
performAction(breakpointMarkerPlugin.actionEnableBreakpoint,
staticCtx(addr(program, 0x00400123)), true);
ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
assertTrue(breakpointMarkerPlugin.actionEnableBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionEnableBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeState()));
}
@ -678,8 +687,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
lb.disable();
waitForPass(() -> assertEquals(State.DISABLED, lb.computeState()));
performAction(breakpointMarkerPlugin.actionEnableBreakpoint,
dynamicCtx(trace, addr(trace, 0x55550123)), true);
ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550123));
assertTrue(breakpointMarkerPlugin.actionEnableBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionEnableBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.ENABLED, lb.computeStateForTrace(trace)));
// TODO: Same test but with multiple traces
@ -690,8 +700,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
addMappedBreakpointOpenAndWait();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
performAction(breakpointMarkerPlugin.actionDisableBreakpoint,
staticCtx(addr(program, 0x00400123)), true);
ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
assertTrue(breakpointMarkerPlugin.actionDisableBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionDisableBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.DISABLED, lb.computeState()));
}
@ -702,8 +713,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
Trace trace = recorder.getTrace();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
performAction(breakpointMarkerPlugin.actionDisableBreakpoint,
dynamicCtx(trace, addr(trace, 0x55550123)), true);
ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550123));
assertTrue(breakpointMarkerPlugin.actionDisableBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionDisableBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.DISABLED, lb.computeStateForTrace(trace)));
// TODO: Same test but with multiple traces
@ -713,8 +725,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
public void testActionClearBreakpointProgram() throws Throwable {
addMappedBreakpointOpenAndWait();
performAction(breakpointMarkerPlugin.actionClearBreakpoint,
staticCtx(addr(program, 0x00400123)), true);
ProgramLocationActionContext ctx = staticCtx(addr(program, 0x00400123));
assertTrue(breakpointMarkerPlugin.actionClearBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionClearBreakpoint, ctx, true);
waitForPass(() -> assertTrue(breakpointService.getAllBreakpoints().isEmpty()));
}
@ -725,8 +738,9 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu
Trace trace = recorder.getTrace();
LogicalBreakpoint lb = Unique.assertOne(breakpointService.getAllBreakpoints());
performAction(breakpointMarkerPlugin.actionClearBreakpoint,
dynamicCtx(trace, addr(trace, 0x55550123)), true);
ProgramLocationActionContext ctx = dynamicCtx(trace, addr(trace, 0x55550123));
assertTrue(breakpointMarkerPlugin.actionClearBreakpoint.isAddToPopup(ctx));
performAction(breakpointMarkerPlugin.actionClearBreakpoint, ctx, true);
waitForPass(() -> assertEquals(State.NONE, lb.computeStateForTrace(trace)));
// TODO: Same test but with multiple traces

View file

@ -24,6 +24,7 @@ import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.util.database.UndoableTransaction;
@Category(NightlyCategory.class)
@ -57,46 +58,58 @@ public class DebuggerBreakpointsProviderObjectTest extends DebuggerBreakpointsPr
// NOTE the use of index='...' allowing object-based managers to ID unique path
// TODO: I guess this'll burn down if the naming scheme changes....
int index = tb.trace.getName().startsWith("[3]") ? 3 : 1;
ctx = XmlSchemaContext.deserialize("" + //
"<context>" + //
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
" <attribute name='Processes' schema='ProcessContainer' />" + //
" </schema>" + //
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element index='" + index + "' schema='Process' />" + // <---- NOTE HERE
" </schema>" + //
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + //
" <attribute name='Threads' schema='ThreadContainer' />" + //
" <attribute name='Memory' schema='RegionContainer' />" + //
" <attribute name='Breakpoints' schema='BreakpointContainer' />" + //
" </schema>" + //
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Thread' />" + //
" </schema>" + //
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='Thread' />" + //
" </schema>" + //
" <schema name='RegionContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Region' />" + //
" </schema>" + //
" <schema name='Region' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='MemoryRegion' />" + //
" </schema>" + //
" <schema name='BreakpointContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Breakpoint' />" + //
" </schema>" + //
" <schema name='Breakpoint' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='BreakpointSpec' />" + //
" <interface name='BreakpointLocation' />" + //
" </schema>" + //
"</context>");
ctx = XmlSchemaContext.deserialize(String.format("""
<context>
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
<attribute name='Processes' schema='ProcessContainer' />
</schema>
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element index='%d' schema='Process' /> <!-- NOTE HERE -->
</schema>
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
<interface name='Aggregate' />
<attribute name='Threads' schema='ThreadContainer' />
<attribute name='Memory' schema='RegionContainer' />
<attribute name='Breakpoints' schema='BreakpointContainer' />
</schema>
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Thread' />
</schema>
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
<interface name='Aggregate' />
<interface name='Thread' />
<attribute name='Registers' schema='Registers' />
</schema>
<schema name='Registers' elementResync='NEVER' attributeResync='NEVER'>
<interface name='RegisterBank' />
<interface name='RegisterContainer' />
</schema>
<schema name='RegionContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Region' />
</schema>
<schema name='Region' elementResync='NEVER' attributeResync='NEVER'>
<interface name='MemoryRegion' />
</schema>
<schema name='BreakpointContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<interface name='BreakpointSpecContainer' />
<element schema='Breakpoint' />
</schema>
<schema name='Breakpoint' elementResync='NEVER' attributeResync='NEVER'>
<interface name='BreakpointSpec' />
<interface name='BreakpointLocation' />
</schema>
</context>
""", index));
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
tb.trace.getObjectManager()
.createObject(
TraceObjectKeyPath.of("Processes", "[" + index + "]", "Breakpoints"));
}
}
}

View file

@ -34,6 +34,8 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsProvider.LogicalBreakpointTableModel;
import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.*;
import ghidra.app.services.LogicalBreakpoint.State;
@ -41,12 +43,14 @@ import ghidra.dbg.model.TestTargetProcess;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer;
import ghidra.framework.store.LockException;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryConflictException;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.SystemUtilities;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.CancelledException;
@ -682,4 +686,110 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge
// NOTE: With no selection, no actions (even table built-in) apply, so no menu
}
@Test
public void testEmuBreakpointState() throws Throwable {
addPlugin(tool, DebuggerStateEditingServicePlugin.class);
createProgram();
intoProject(program);
programManager.openProgram(program);
waitForSwing();
addStaticMemoryAndBreakpoint();
waitForProgram(program);
LogicalBreakpointRow row = waitForValue(
() -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
// Do our own launch, so that object mode is enabled during load (region creation)
createTrace(program.getLanguageID().getIdAsString());
try (UndoableTransaction startTransaction = tb.startTransaction()) {
TraceSnapshot initial = tb.trace.getTimeManager().getSnapshot(0, true);
ProgramEmulationUtils.loadExecutable(initial, program);
Address pc = program.getMinAddress();
ProgramEmulationUtils.doLaunchEmulationThread(tb.trace, 0, program, pc, pc);
}
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
waitOn(mappingService.changesSettled());
waitOn(breakpointService.changesSettled());
waitForSwing();
row = waitForValue(
() -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
assertEquals(State.INEFFECTIVE_ENABLED, row.getState());
row.setEnabled(true);
waitForSwing();
row = waitForValue(
() -> Unique.assertAtMostOne(breakpointsProvider.breakpointTableModel.getModelData()));
assertEquals(State.ENABLED, row.getState());
}
@Test
public void testTablesAndStatesWhenhModeChanges() throws Throwable {
DebuggerStateEditingService editingService =
addPlugin(tool, DebuggerStateEditingServicePlugin.class);
createTestModel();
mb.createTestProcessesAndThreads();
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
Trace trace = recorder.getTrace();
createProgramFromTrace(trace);
intoProject(trace);
intoProject(program);
mb.testProcess1.addRegion("bin:.text", mb.rng(0x55550000, 0x55550fff), "rx");
waitRecorder(recorder);
addMapping(trace, program);
addStaticMemoryAndBreakpoint();
programManager.openProgram(program);
traceManager.openTrace(trace);
waitForSwing();
LogicalBreakpointRow lbRow1 = waitForPass(() -> {
LogicalBreakpointRow newRow =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
LogicalBreakpoint lb = newRow.getLogicalBreakpoint();
assertEquals(program, lb.getProgram());
assertEquals(Set.of(trace), lb.getMappedTraces());
assertEquals(Set.of(), lb.getParticipatingTraces());
assertEquals(State.INEFFECTIVE_ENABLED, newRow.getState());
return newRow;
});
editingService.setCurrentMode(trace, StateEditingMode.RW_EMULATOR);
lbRow1.setEnabled(true);
TraceBreakpoint emuBpt = waitForValue(
() -> Unique.assertAtMostOne(trace.getBreakpointManager().getAllBreakpoints()));
assertNull(recorder.getTargetBreakpoint(emuBpt));
LogicalBreakpointRow lbRow2 =
Unique.assertOne(breakpointsProvider.breakpointTableModel.getModelData());
waitForPass(() -> assertEquals(State.ENABLED, lbRow2.getState()));
waitForPass(() -> {
BreakpointLocationRow newRow =
Unique.assertOne(breakpointsProvider.locationTableModel.getModelData());
assertEquals(State.ENABLED, newRow.getState());
});
for (int i = 0; i < 3; i++) {
editingService.setCurrentMode(trace, StateEditingMode.RO_TARGET);
waitOn(breakpointService.changesSettled());
waitForSwing();
assertEquals(0, breakpointsProvider.locationTableModel.getModelData().size());
editingService.setCurrentMode(trace, StateEditingMode.RW_EMULATOR);
waitOn(breakpointService.changesSettled());
waitForSwing();
assertEquals(1, breakpointsProvider.locationTableModel.getModelData().size());
}
}
}

View file

@ -48,7 +48,6 @@ import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePl
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.CachedEmulator;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.dbg.model.*;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
@ -73,8 +72,8 @@ import ghidra.util.database.UndoableTransaction;
*
* <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
* {@link StateEditingMode#RW_EMULATOR} as a stand-in for any mode. We also use
* {@link StateEditingMode#RO_TARGET} just to verify the mode is heeded. Other modes may be tested
* if bugs crop up in various combinations.
*/
public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITest {
@ -294,7 +293,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateResumeAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@ -311,7 +310,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateInterruptAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@ -333,7 +332,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateStepBackAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@ -352,7 +351,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateStepIntoAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@ -365,7 +364,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testEmulateSkipOverAction() throws Throwable {
TraceThread thread = createToyLoopTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
traceManager.activateThread(thread);
waitForSwing();
@ -386,7 +385,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testTraceSnapBackwardAction() throws Throwable {
create2SnapTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
traceManager.activateTrace(tb.trace);
waitForSwing();
@ -403,7 +402,7 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
@Test
public void testTraceSnapForwardAction() throws Throwable {
create2SnapTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
traceManager.activateTrace(tb.trace);
waitForSwing();
@ -444,14 +443,14 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
assertTrue(controlPlugin.actionEditMode.isEnabled());
runSwing(() -> controlPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
.setCurrentActionStateByUserData(StateEditingMode.RO_TARGET));
assertEquals(StateEditingMode.RO_TARGET, editingService.getCurrentMode(tb.trace));
assertFalse(
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
runSwing(() -> controlPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
.setCurrentActionStateByUserData(StateEditingMode.RW_EMULATOR));
assertEquals(StateEditingMode.RW_EMULATOR, editingService.getCurrentMode(tb.trace));
assertTrue(
helper.patchInstructionAction.isAddToPopup(listingProvider.getActionContext(null)));
@ -494,13 +493,13 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
assertTrue(controlPlugin.actionEditMode.isEnabled());
runSwing(() -> controlPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
.setCurrentActionStateByUserData(StateEditingMode.RO_TARGET));
assertEquals(StateEditingMode.RO_TARGET, editingService.getCurrentMode(tb.trace));
assertFalse(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
runSwing(() -> controlPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
.setCurrentActionStateByUserData(StateEditingMode.RW_EMULATOR));
assertEquals(StateEditingMode.RW_EMULATOR, editingService.getCurrentMode(tb.trace));
goTo(listingProvider.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
assertTrue(helper.patchDataAction.isAddToPopup(listingProvider.getActionContext(null)));
@ -545,15 +544,15 @@ public class DebuggerControlPluginTest extends AbstractGhidraHeadedDebuggerGUITe
assertTrue(controlPlugin.actionEditMode.isEnabled());
runSwing(() -> controlPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.READ_ONLY));
assertEquals(StateEditingMode.READ_ONLY, editingService.getCurrentMode(tb.trace));
.setCurrentActionStateByUserData(StateEditingMode.RO_TARGET));
assertEquals(StateEditingMode.RO_TARGET, editingService.getCurrentMode(tb.trace));
ctx = listingProvider.getActionContext(null);
assertTrue(pasteAction.isAddToPopup(ctx));
assertFalse(pasteAction.isEnabledForContext(ctx));
runSwing(() -> controlPlugin.actionEditMode
.setCurrentActionStateByUserData(StateEditingMode.WRITE_EMULATOR));
assertEquals(StateEditingMode.WRITE_EMULATOR, editingService.getCurrentMode(tb.trace));
.setCurrentActionStateByUserData(StateEditingMode.RW_EMULATOR));
assertEquals(StateEditingMode.RW_EMULATOR, editingService.getCurrentMode(tb.trace));
goTo(listingPlugin.getListingPanel(), new ProgramLocation(view, tb.addr(0x00400123)));
ctx = listingProvider.getActionContext(null);

View file

@ -49,9 +49,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.FollowsCurrentThreadAc
import ghidra.app.plugin.core.debug.gui.action.*;
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.*;
import ghidra.async.SwingExecutorService;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Register;
@ -510,6 +508,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
* Assure ourselves the block under test is not on screen
*/
waitForPass(() -> {
goToDyn(addr(trace, 0x55551800));
AddressSetView visible = memBytesProvider.readsMemTrait.getVisible();
assertFalse(visible.isEmpty());
assertFalse(visible.contains(addr(trace, 0x55550000)));
@ -1101,10 +1100,11 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(trace, StateEditingMode.RW_TARGET);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitRecorder(recorder);
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
byte[] data = new byte[4];
@ -1132,12 +1132,17 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(trace, StateEditingMode.RW_TRACE);
DockingActionIf actionEdit = getAction(memBytesPlugin, "Enable/Disable Byteviewer Editing");
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitRecorder(recorder);
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
// Because mode is RW_TRACE, we're not necessarily at recorder's snap
traceManager.activateSnap(recorder.getSnap());
waitForSwing();
byte[] data = new byte[4];
performAction(actionEdit);
waitForPass(noExc(() -> {
@ -1174,7 +1179,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(trace, StateEditingMode.RW_TARGET);
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());

View file

@ -33,9 +33,7 @@ import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterDataSettingsDialog;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider.RegisterTableColumns;
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.*;
import ghidra.docking.settings.FormatSettingsDefinition;
import ghidra.docking.settings.Settings;
import ghidra.program.model.address.AddressSpace;
@ -385,7 +383,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
assertTrue(registersProvider.actionEnableEdits.isEnabled());
performAction(registersProvider.actionEnableEdits);
@ -423,7 +421,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
assertTrue(registersProvider.actionEnableEdits.isEnabled());
performAction(registersProvider.actionEnableEdits);

View file

@ -28,12 +28,10 @@ import org.junit.experimental.categories.Category;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.thread.DebuggerLegacyThreadsPanel.ThreadTableColumns;
import ghidra.app.services.TraceRecorder;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.database.UndoableTransaction;
@ -400,53 +398,4 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
assertEquals(6, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
}
@Test
public void testActionSeekTracePresent() throws Exception {
assertTrue(threadsProvider.actionSeekTracePresent.isSelected());
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForSwing();
assertEquals(0, traceManager.getCurrentSnap());
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getTimeManager().createSnapshot("Next snapshot");
}
waitForDomainObject(tb.trace);
// Not live, so no seek
assertEquals(0, traceManager.getCurrentSnap());
tb.close();
createTestModel();
mb.createTestProcessesAndThreads();
// Threads needs registers to be recognized by the recorder
mb.createTestThreadRegisterBanks();
TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
// Wait till two threads are observed in the database
waitForPass(() -> assertEquals(2, trace.getThreadManager().getAllThreads().size()));
waitForSwing();
TraceSnapshot snapshot = recorder.forceSnapshot();
waitForDomainObject(trace);
assertEquals(snapshot.getKey(), traceManager.getCurrentSnap());
performAction(threadsProvider.actionSeekTracePresent);
waitForSwing();
assertFalse(threadsProvider.actionSeekTracePresent.isSelected());
recorder.forceSnapshot();
waitForSwing();
assertEquals(snapshot.getKey(), traceManager.getCurrentSnap());
}
}

View file

@ -31,13 +31,13 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.*;
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.TargetExecutionStateful;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
@ -51,7 +51,6 @@ import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.target.TraceObjectManager;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.table.GhidraTable;
@ -564,52 +563,4 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
waitForPass(() -> assertEquals(Long.valueOf(6), renderer.getCursorPosition()));
}
@Test
public void testActionSeekTracePresent() throws Throwable {
assertTrue(provider.actionSeekTracePresent.isSelected());
createAndOpenTrace();
addThreads();
traceManager.activateTrace(tb.trace);
waitForTasks();
assertEquals(0, traceManager.getCurrentSnap());
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getTimeManager().createSnapshot("Next snapshot");
}
waitForDomainObject(tb.trace);
waitForTasks();
// Not live, so no seek
waitForPass(() -> assertEquals(0, traceManager.getCurrentSnap()));
tb.close();
createTestModel();
mb.createTestProcessesAndThreads();
// Threads needs registers to be recognized by the recorder
mb.createTestThreadRegisterBanks();
TraceRecorder recorder = recordAndWaitSync();
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
TraceSnapshot snapshot = recorder.forceSnapshot();
waitForDomainObject(tb.trace);
waitForTasks();
waitForPass(() -> assertEquals(snapshot.getKey(), traceManager.getCurrentSnap()));
performEnabledAction(provider, provider.actionSeekTracePresent, true);
waitForTasks();
assertFalse(provider.actionSeekTracePresent.isSelected());
recorder.forceSnapshot();
waitForTasks();
waitForPass(() -> assertEquals(snapshot.getKey(), traceManager.getCurrentSnap()));
}
}

View file

@ -35,7 +35,6 @@ import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesProvider.WatchDataS
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.docking.settings.FormatSettingsDefinition;
import ghidra.docking.settings.Settings;
@ -350,7 +349,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertFalse(row.isRawValueEditable());
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
waitForWatches();
assertNoErr(row);
@ -384,7 +383,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
waitForWatches();
performAction(watchesProvider.actionEnableEdits);
@ -547,7 +546,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.openTrace(trace);
traceManager.activateThread(thread);
editingService.setCurrentMode(trace, StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(trace, StateEditingMode.RW_TARGET);
waitForSwing();
performAction(watchesProvider.actionAdd);

View file

@ -21,6 +21,7 @@ import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.util.database.UndoableTransaction;
public class DebuggerLogicalBreakpointServiceObjectTest
@ -54,46 +55,58 @@ public class DebuggerLogicalBreakpointServiceObjectTest
// NOTE the use of index='...' allowing object-based managers to ID unique path
// TODO: I guess this'll burn down if the naming scheme changes....
int index = tb.trace.getName().startsWith("[3]") ? 3 : 1;
ctx = XmlSchemaContext.deserialize("" + //
"<context>" + //
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
" <attribute name='Processes' schema='ProcessContainer' />" + //
" </schema>" + //
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element index='" + index + "' schema='Process' />" + // <---- NOTE HERE
" </schema>" + //
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + //
" <attribute name='Threads' schema='ThreadContainer' />" + //
" <attribute name='Memory' schema='RegionContainer' />" + //
" <attribute name='Breakpoints' schema='BreakpointContainer' />" + //
" </schema>" + //
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Thread' />" + //
" </schema>" + //
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='Thread' />" + //
" </schema>" + //
" <schema name='RegionContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Region' />" + //
" </schema>" + //
" <schema name='Region' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='MemoryRegion' />" + //
" </schema>" + //
" <schema name='BreakpointContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Breakpoint' />" + //
" </schema>" + //
" <schema name='Breakpoint' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='BreakpointSpec' />" + //
" <interface name='BreakpointLocation' />" + //
" </schema>" + //
"</context>");
ctx = XmlSchemaContext.deserialize(String.format("""
<context>
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
<attribute name='Processes' schema='ProcessContainer' />
</schema>
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element index='%d' schema='Process' /> <!-- NOTE HERE -->
</schema>
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
<interface name='Aggregate' />
<attribute name='Threads' schema='ThreadContainer' />
<attribute name='Memory' schema='RegionContainer' />
<attribute name='Breakpoints' schema='BreakpointContainer' />
</schema>
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Thread' />
</schema>
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
<interface name='Aggregate' />
<interface name='Thread' />
<attribute name='Registers' schema='Registers' />
</schema>
<schema name='Registers' elementResync='NEVER' attributeResync='NEVER'>
<interface name='RegisterBank' />
<interface name='RegisterContainer' />
</schema>
<schema name='RegionContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Region' />
</schema>
<schema name='Region' elementResync='NEVER' attributeResync='NEVER'>
<interface name='MemoryRegion' />
</schema>
<schema name='BreakpointContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<interface name='BreakpointSpecContainer' />
<element schema='Breakpoint' />
</schema>
<schema name='Breakpoint' elementResync='NEVER' attributeResync='NEVER'>
<interface name='BreakpointSpec' />
<interface name='BreakpointLocation' />
</schema>
</context>
""", index));
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
tb.trace.getObjectManager()
.createObject(
TraceObjectKeyPath.of("Processes", "[" + index + "]", "Breakpoints"));
}
}
}

View file

@ -38,9 +38,10 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Bookmark;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.model.*;
import ghidra.trace.model.breakpoint.*;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.modules.TraceStaticMapping;
import ghidra.util.Msg;
@ -1552,4 +1553,125 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
waitForPass(
() -> assertEquals(State.MIXED, lbEx.computeState().sameAdddress(lbRw.computeState())));
}
protected void addTextMappingDead(Program p, ToyDBTraceBuilder tb) throws Throwable {
addProgramTextBlock(p);
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryRegion textRegion = tb.trace.getMemoryManager()
.addRegion("Processes[1].Memory[bin:.text]", Lifespan.nowOn(0),
tb.range(0x55550000, 0x55550fff),
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
DebuggerStaticMappingUtils.addMapping(
new DefaultTraceLocation(tb.trace, null, textRegion.getLifespan(),
textRegion.getMinAddress()),
new ProgramLocation(p, addr(p, 0x00400000)), 0x1000,
false);
}
}
protected void addEnabledProgramBreakpointWithSleigh(Program p) {
try (UndoableTransaction tid =
UndoableTransaction.start(p, "Create bookmark bp with sleigh")) {
enBm = p.getBookmarkManager()
.setBookmark(addr(p, 0x00400123),
LogicalBreakpoint.BREAKPOINT_ENABLED_BOOKMARK_TYPE, "SW_EXECUTE;1",
"{sleigh: 'r0=0xbeef;'}");
}
}
@Test
public void testMapThenAddProgramBreakpointWithSleighThenEnableOnTraceCopiesSleigh()
throws Throwable {
createTrace();
traceManager.openTrace(tb.trace);
createProgramFromTrace();
intoProject(program);
programManager.openProgram(program);
addTextMappingDead(program, tb);
waitForSwing();
addEnabledProgramBreakpointWithSleigh(program);
LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne(
breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))));
assertEquals("r0=0xbeef;", lb.getEmuSleigh());
waitOn(lb.enable());
waitForSwing();
TraceBreakpoint bpt = Unique.assertOne(
tb.trace.getBreakpointManager().getBreakpointsAt(0, tb.addr(0x55550123)));
assertEquals("r0=0xbeef;", bpt.getEmuSleigh());
}
@Test
public void testAddProgramBreakpointWithSleighThenMapThenEnableOnTraceCopiesSleigh()
throws Throwable {
createTrace();
traceManager.openTrace(tb.trace);
createProgramFromTrace();
intoProject(program);
programManager.openProgram(program);
addEnabledProgramBreakpointWithSleigh(program);
LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne(
breakpointService.getBreakpointsAt(program, addr(program, 0x00400123))));
assertEquals("r0=0xbeef;", lb.getEmuSleigh());
addTextMappingDead(program, tb);
lb = waitForPass(() -> {
LogicalBreakpoint newLb = Unique.assertOne(
breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)));
assertTrue(newLb.getMappedTraces().contains(tb.trace));
return newLb;
});
waitOn(lb.enable());
waitForSwing();
TraceBreakpoint bpt = Unique.assertOne(
tb.trace.getBreakpointManager().getBreakpointsAt(0, tb.addr(0x55550123)));
assertEquals("r0=0xbeef;", bpt.getEmuSleigh());
}
@Test
public void testAddTraceBreakpointSetSleighThenMapThenSaveToProgramCopiesSleigh()
throws Throwable {
// TODO: What if already mapped?
// Not sure I care about tb.setEmuSleigh() out of band
createTrace();
traceManager.openTrace(tb.trace);
createProgramFromTrace();
intoProject(program);
programManager.openProgram(program);
try (UndoableTransaction tid = tb.startTransaction()) {
TraceBreakpoint bpt = tb.trace.getBreakpointManager()
.addBreakpoint("Processes[1].Breakpoints[0]", Lifespan.nowOn(0),
tb.addr(0x55550123),
Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE),
false /* emuEnabled defaults to true */, "");
bpt.setEmuSleigh("r0=0xbeef;");
}
LogicalBreakpoint lb = waitForValue(() -> Unique.assertAtMostOne(
breakpointService.getBreakpointsAt(tb.trace, tb.addr(0x55550123))));
assertEquals("r0=0xbeef;", lb.getEmuSleigh());
addTextMappingDead(program, tb);
lb = waitForPass(() -> {
LogicalBreakpoint newLb = Unique.assertOne(
breakpointService.getBreakpointsAt(program, addr(program, 0x00400123)));
assertTrue(newLb.getMappedTraces().contains(tb.trace));
return newLb;
});
lb.enableForProgram();
waitForSwing();
assertEquals("{\"sleigh\":\"r0\\u003d0xbeef;\"}", lb.getProgramBookmark().getComment());
}
}

View file

@ -26,10 +26,8 @@ import org.junit.Test;
import ghidra.app.plugin.assembler.*;
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.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncUtils.TemperamentalRunnable;
import ghidra.dbg.target.TargetRegisterBank;
import ghidra.program.model.lang.*;
@ -56,6 +54,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
protected void activateTrace() {
traceManager.activateTrace(tb.trace);
waitForSwing();
}
protected TracePlatform getPlatform() {
@ -105,7 +104,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
createAndOpenTrace();
activateTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
StateEditor editor = createStateEditor();
assertFalse(editor.isVariableEditable(tb.addr(0x00400000), 4));
@ -117,7 +116,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuRegisterNoThreadErr() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
activateTrace();
waitForSwing();
@ -132,7 +131,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuMemory() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
@ -158,7 +157,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuRegister() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
@ -187,7 +186,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuMemoryAfterStep() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
@ -200,7 +199,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
tb.exec(getPlatform(), 0, thread, 0, "pc = 0x00400000;");
}
activateTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
waitForSwing();
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
@ -225,7 +224,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuRegisterAfterStep() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
@ -239,7 +238,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
tb.exec(getPlatform(), 0, thread, 0, "pc = 0x00400000;");
}
activateTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
waitForSwing();
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
@ -265,7 +264,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuMemoryTwice() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
@ -294,7 +293,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
@Test
public void testWriteEmuRegisterTwice() throws Throwable {
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_EMULATOR);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
@ -325,7 +324,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTraceMemory() throws Throwable {
// NB. Definitely no thread required
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
activateTrace();
waitForSwing();
@ -348,7 +347,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTraceRegisterNoThreadErr() throws Throwable {
// NB. Definitely no thread required
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
activateTrace();
waitForSwing();
@ -364,7 +363,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTraceRegister() throws Throwable {
// NB. Definitely no thread required
createAndOpenTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
@ -397,7 +396,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
activateTrace();
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertTrue(editor.isVariableEditable(tb.addr(0x00400000), 4));
@ -417,7 +416,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
waitForSwing();
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertTrue(editor.isRegisterEditable(r0));
@ -436,7 +435,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
TraceThread thread = recorder.getTraceThread(mb.testThread1);
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertTrue(editor.isRegisterEditable(r0));
@ -461,7 +460,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
activateTrace();
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
traceManager.activateSnap(traceManager.getCurrentSnap() - 1);
@ -479,7 +478,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
activateTrace();
traceManager.activateThread(recorder.getTraceThread(mb.testThread1));
waitForSwing();
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.RW_TARGET);
traceManager.activateSnap(traceManager.getCurrentSnap() - 1);
@ -494,7 +493,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTargetMemoryNotAliveErr() throws Throwable {
createAndOpenTrace();
activateTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertFalse(editor.isVariableEditable(tb.addr(0x00400000), 4));
@ -507,7 +506,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteTargetRegisterNotAliveErr() throws Throwable {
createAndOpenTrace();
activateTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TARGET);
StateEditor editor = createStateEditor();
assertFalse(editor.isRegisterEditable(r0));
@ -520,7 +519,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteReadOnlyMemoryErr() throws Throwable {
createAndOpenTrace();
activateTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY);
editingService.setCurrentMode(tb.trace, StateEditingMode.RO_TARGET);
StateEditor editor = createStateEditor();
assertFalse(editor.isVariableEditable(tb.addr(0x00400000), 4));
@ -533,7 +532,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
public void testWriteReadOnlyRegisterErr() throws Throwable {
createAndOpenTrace();
activateTrace();
editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY);
editingService.setCurrentMode(tb.trace, StateEditingMode.RO_TARGET);
StateEditor editor = createStateEditor();
assertFalse(editor.isRegisterEditable(r0));

View file

@ -47,6 +47,7 @@ import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryManager;
@ -402,6 +403,132 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
regs.getViewValue(scratch, regR2).getUnsignedValue());
}
@Test
public void testRunAfterExecutionBreakpoint() throws Exception {
createProgram();
intoProject(program);
Assembler asm = Assemblers.getAssembler(program);
Memory memory = program.getMemory();
Address addrText = addr(program, 0x000400000);
Address addrI1;
Address addrI2;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
InstructionIterator ii = asm.assemble(addrText,
"mov r0, r0",
"mov r0, r1",
"mov r2, r0");
ii.next(); // addrText
addrI1 = ii.next().getMinAddress();
addrI2 = ii.next().getMinAddress();
}
programManager.openProgram(program);
waitForSwing();
codeBrowser.goTo(new ProgramLocation(program, addrText));
waitForSwing();
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) {
trace.getBreakpointManager()
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrText, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
trace.getBreakpointManager()
.addBreakpoint("Breakpoints[1]", Lifespan.nowOn(0), addrI1, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
trace.getBreakpointManager()
.addBreakpoint("Breakpoints[2]", Lifespan.nowOn(0), addrI2, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
}
// This is already testing if the one set at the entry is ignored
EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread));
assertEquals(TraceSchedule.parse("0:t0-1"), result1.schedule());
assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
// This will test if the one just hit gets ignored
EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
result1.schedule(), monitor, Scheduler.oneThread(thread));
assertEquals(TraceSchedule.parse("0:t0-2"), result2.schedule());
assertTrue(result1.error() instanceof InterruptPcodeExecutionException);
}
@Test
public void testExecutionInjection() throws Exception {
createProgram();
intoProject(program);
Assembler asm = Assemblers.getAssembler(program);
Memory memory = program.getMemory();
Address addrText = addr(program, 0x000400000);
Register regPC = program.getRegister("pc");
Register regR0 = program.getRegister("r0");
Register regR1 = program.getRegister("r1");
Register regR2 = program.getRegister("r2");
Address addrI2;
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
InstructionIterator ii = asm.assemble(addrText,
"mov r0, r1",
"mov r2, r0");
ii.next();
addrI2 = ii.next().getMinAddress();
program.getProgramContext()
.setValue(regR1, addrText, addrText, new BigInteger("1234", 16));
}
programManager.openProgram(program);
waitForSwing();
codeBrowser.goTo(new ProgramLocation(program, addrText));
waitForSwing();
performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true);
Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) {
TraceBreakpoint tb = trace.getBreakpointManager()
.addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrI2, Set.of(thread),
Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test");
tb.setEmuSleigh("""
r1 = 0x5678;
emu_swi();
emu_exec_decoded();
""");
}
EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(),
TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread));
assertEquals(TraceSchedule.parse("0:t0-1.t0-2"), result.schedule());
assertTrue(result.error() instanceof InterruptPcodeExecutionException);
long scratch = result.snapshot();
assertEquals(new BigInteger("00400002", 16),
regs.getViewValue(scratch, regPC).getUnsignedValue());
assertEquals(new BigInteger("1234", 16),
regs.getViewValue(scratch, regR0).getUnsignedValue());
assertEquals(new BigInteger("5678", 16),
regs.getViewValue(scratch, regR1).getUnsignedValue());
assertEquals(new BigInteger("0", 16),
regs.getViewValue(scratch, regR2).getUnsignedValue());
}
@Test
public void testAccessBreakpoint() throws Exception {
createProgram();

View file

@ -19,14 +19,15 @@ import static org.junit.Assert.*;
import java.util.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.services.ActionSource;
import ghidra.app.services.TraceRecorder;
import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin;
import ghidra.app.services.*;
import ghidra.dbg.model.TestTargetStack;
import ghidra.dbg.model.TestTargetStackFrameHasRegisterBank;
import ghidra.dbg.target.schema.SchemaContext;
@ -46,6 +47,14 @@ import ghidra.util.database.UndoableTransaction;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebuggerGUITest {
protected DebuggerStateEditingService editingService;
@Before
public void setUpTraceManagerTest() throws Exception {
addPlugin(tool, DebuggerStateEditingServicePlugin.class);
editingService = tool.getService(DebuggerStateEditingService.class);
}
@Test
public void testGetOpenTraces() throws Exception {
assertEquals(Set.of(), traceManager.getOpenTraces());
@ -337,9 +346,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
}
@Test
public void testAutoActivatePresent() throws Throwable {
assertTrue(traceManager.isAutoActivatePresent());
public void testFollowPresent() throws Throwable {
createTestModel();
mb.createTestProcessesAndThreads();
@ -352,6 +359,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
traceManager.activateTrace(trace);
waitForSwing();
assertEquals(StateEditingMode.RO_TARGET, editingService.getCurrentMode(trace));
long initSnap = recorder.getSnap();
assertEquals(initSnap, traceManager.getCurrentSnap());
@ -361,7 +369,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
assertEquals(initSnap + 1, recorder.getSnap());
assertEquals(initSnap + 1, traceManager.getCurrentSnap());
traceManager.setAutoActivatePresent(false);
editingService.setCurrentMode(trace, StateEditingMode.RO_TRACE);
recorder.forceSnapshot();
waitForSwing();
@ -369,7 +377,11 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
assertEquals(initSnap + 2, recorder.getSnap());
assertEquals(initSnap + 1, traceManager.getCurrentSnap());
traceManager.setAutoActivatePresent(true);
editingService.setCurrentMode(trace, StateEditingMode.RO_TARGET);
waitForSwing();
assertEquals(initSnap + 2, recorder.getSnap());
assertEquals(initSnap + 2, traceManager.getCurrentSnap());
recorder.forceSnapshot();
waitForSwing();

View file

@ -49,7 +49,6 @@ import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.disassembler.DisassemblerPlugin;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerEmulationService.EmulationResult;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.app.util.viewer.field.FieldFactory;
import ghidra.app.util.viewer.field.ListingField;
@ -692,7 +691,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
@ -752,7 +751,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
@ -895,7 +894,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
DebuggerCoordinates atSetup = traceManager.getCurrent();
@ -945,7 +944,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
// Move stack where it shows in UI. Not required, but nice for debugging.
Register sp = program.getCompilerSpec().getStackPointer();
@ -989,7 +988,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
// Move stack where it shows in UI. Not required, but nice for debugging.
Register sp = program.getCompilerSpec().getStackPointer();
@ -1033,7 +1032,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerGUITest {
traceManager.activateThread(thread);
waitForSwing();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
StateEditor editor = editingService.createStateEditor(tb.trace);
// Move stack where it shows in UI. Not required, but nice for debugging.
Register sp = program.getCompilerSpec().getStackPointer();

View file

@ -44,7 +44,6 @@ import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOf
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer.LaunchResult;
import ghidra.app.script.GhidraState;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStateEditingService.StateEditingMode;
import ghidra.app.services.LogicalBreakpoint.State;
import ghidra.dbg.DebuggerModelFactory;
import ghidra.dbg.DebuggerObjectModel;
@ -690,7 +689,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testWriteMemoryGivenContext() throws Throwable {
createTraceWithBinText();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
assertTrue(flat.writeMemory(tb.trace, 0, tb.addr(0x00400123), tb.arr(3, 2, 1)));
ByteBuffer buf = ByteBuffer.allocate(3);
@ -701,7 +700,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testWriteMemoryCurrentContext() throws Throwable {
createTraceWithBinText();
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
assertTrue(flat.writeMemory(tb.addr(0x00400123), tb.arr(3, 2, 1)));
ByteBuffer buf = ByteBuffer.allocate(3);
@ -712,7 +711,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testWriteRegisterGivenContext() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
traceManager.activateThread(thread);
waitForSwing();
@ -728,7 +727,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testWriteRegisterCurrentContext() throws Throwable {
TraceThread thread = createTraceWithThreadAndStack(true);
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
editingService.setCurrentMode(tb.trace, StateEditingMode.RW_TRACE);
traceManager.activateThread(thread);
waitForSwing();

View file

@ -59,7 +59,7 @@ public enum TraceSleighUtils {
new DirectBytesTracePcodeExecutorState(platform, snap, thread, frame);
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("TracePlatform must use a SLEIGH language");
throw new IllegalArgumentException("TracePlatform must use a Sleigh language");
}
return new PcodeExecutor<>((SleighLanguage) language,
BytesPcodeArithmetic.forLanguage(language), state, Reason.INSPECT);
@ -99,7 +99,7 @@ public enum TraceSleighUtils {
PcodeExecutorState<Pair<byte[], TraceMemoryState>> paired = state.withMemoryState();
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("TracePlatform must use a SLEIGH language");
throw new IllegalArgumentException("TracePlatform must use a Sleigh language");
}
return new PcodeExecutor<>((SleighLanguage) language, new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), TraceMemoryStatePcodeArithmetic.INSTANCE),

View file

@ -19,6 +19,7 @@ import java.io.IOException;
import java.util.*;
import db.DBRecord;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.*;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceUtils;
@ -38,19 +39,21 @@ import ghidra.util.database.DBObjectColumn;
import ghidra.util.database.annot.*;
import ghidra.util.exception.DuplicateNameException;
@DBAnnotatedObjectInfo(version = 0)
@DBAnnotatedObjectInfo(version = 1)
public class DBTraceBreakpoint
extends AbstractDBTraceAddressSnapRangePropertyMapData<DBTraceBreakpoint>
implements TraceBreakpoint {
protected static final String TABLE_NAME = "Breakpoints";
private static final byte ENABLED_MASK = (byte) (1 << 7);
private static final byte EMU_ENABLED_MASK = (byte) (1 << 6);
static final String PATH_COLUMN_NAME = "Path";
static final String NAME_COLUMN_NAME = "Name";
static final String THREADS_COLUMN_NAME = "Threads";
static final String FLAGS_COLUMN_NAME = "Flags";
static final String COMMENT_COLUMN_NAME = "Comment";
static final String SLEIGH_COLUMN_NAME = "Sleigh";
@DBAnnotatedColumn(PATH_COLUMN_NAME)
static DBObjectColumn PATH_COLUMN;
@ -62,6 +65,8 @@ public class DBTraceBreakpoint
static DBObjectColumn FLAGS_COLUMN;
@DBAnnotatedColumn(COMMENT_COLUMN_NAME)
static DBObjectColumn COMMENT_COLUMN;
@DBAnnotatedColumn(SLEIGH_COLUMN_NAME)
static DBObjectColumn SLEIGH_COLUMN;
protected static String tableName(AddressSpace space, long threadKey) {
return DBTraceUtils.tableName(TABLE_NAME, space, threadKey, 0);
@ -77,10 +82,13 @@ public class DBTraceBreakpoint
private byte flagsByte;
@DBAnnotatedField(column = COMMENT_COLUMN_NAME)
private String comment;
@DBAnnotatedField(column = SLEIGH_COLUMN_NAME)
private String emuSleigh;
private final Set<TraceBreakpointKind> kinds = EnumSet.noneOf(TraceBreakpointKind.class);
private final Set<TraceBreakpointKind> kindsView = Collections.unmodifiableSet(kinds);
private boolean enabled;
private boolean emuEnabled;
protected final DBTraceBreakpointSpace space;
@ -108,7 +116,7 @@ public class DBTraceBreakpoint
}
}
enabled = (flagsByte & ENABLED_MASK) != 0;
// Msg.debug(this, "trace: breakpoint " + this + " enabled=" + enabled + ", because doFresh");
emuEnabled = (flagsByte & EMU_ENABLED_MASK) != 0;
}
@Override
@ -127,7 +135,8 @@ public class DBTraceBreakpoint
}
public void set(String path, String name, Collection<TraceThread> threads,
Collection<TraceBreakpointKind> kinds, boolean enabled, String comment) {
Collection<TraceBreakpointKind> kinds, boolean enabled, boolean emuEnabled,
String comment) {
// TODO: Check that the threads exist and that each's lifespan covers the breakpoint's
// TODO: This would require additional validation any time those are updated
// TODO: For efficiency, would also require index of breakpoints by thread
@ -150,9 +159,13 @@ public class DBTraceBreakpoint
if (enabled) {
this.flagsByte |= ENABLED_MASK;
}
if (emuEnabled) {
this.flagsByte |= EMU_ENABLED_MASK;
}
this.comment = comment;
update(PATH_COLUMN, NAME_COLUMN, THREADS_COLUMN, FLAGS_COLUMN, COMMENT_COLUMN);
this.enabled = enabled;
this.emuEnabled = emuEnabled;
// Msg.debug(this, "trace: breakpoint " + this + " enabled=" + enabled + ", because set");
}
@ -360,6 +373,17 @@ public class DBTraceBreakpoint
update(FLAGS_COLUMN);
}
protected void doSetEmuEnabled(boolean emuEnabled) {
this.emuEnabled = emuEnabled;
if (emuEnabled) {
flagsByte |= EMU_ENABLED_MASK;
}
else {
flagsByte &= ~EMU_ENABLED_MASK;
}
update(FLAGS_COLUMN);
}
protected void doSetKinds(Collection<TraceBreakpointKind> kinds) {
for (TraceBreakpointKind k : TraceBreakpointKind.values()) {
if (kinds.contains(k)) {
@ -391,6 +415,23 @@ public class DBTraceBreakpoint
}
}
@Override
public void setEmuEnabled(boolean enabled) {
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
doSetEmuEnabled(enabled);
}
space.trace.setChanged(new TraceChangeRecord<>(TraceBreakpointChangeType.CHANGED,
space, this));
}
@Override
public boolean isEmuEnabled(long snap) {
// NB. Only object mode support per-snap emu-enablement
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
return emuEnabled;
}
}
@Override
public void setKinds(Collection<TraceBreakpointKind> kinds) {
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
@ -424,6 +465,29 @@ public class DBTraceBreakpoint
}
}
@Override
public void setEmuSleigh(String emuSleigh) {
try (LockHold hold = LockHold.lock(space.lock.writeLock())) {
if (emuSleigh == null || SleighUtils.UNCONDITIONAL_BREAK.equals(emuSleigh)) {
this.emuSleigh = null;
}
else {
this.emuSleigh = emuSleigh.trim();
}
update(SLEIGH_COLUMN);
}
space.trace.setChanged(new TraceChangeRecord<>(TraceBreakpointChangeType.CHANGED,
space, this));
}
@Override
public String getEmuSleigh() {
try (LockHold hold = LockHold.lock(space.lock.readLock())) {
return emuSleigh == null || emuSleigh.isBlank() ? SleighUtils.UNCONDITIONAL_BREAK
: emuSleigh;
}
}
@Override
public void delete() {
space.deleteBreakpoint(this);

View file

@ -100,7 +100,7 @@ public class DBTraceBreakpointSpace implements DBTraceSpaceBased {
}
DBTraceBreakpoint breakpoint =
breakpointMapSpace.put(new ImmutableTraceAddressSnapRange(range, lifespan), null);
breakpoint.set(path, path, threads, kinds, enabled, comment);
breakpoint.set(path, path, threads, kinds, enabled, true, comment);
trace.setChanged(
new TraceChangeRecord<>(TraceBreakpointChangeType.ADDED, this, breakpoint));
return breakpoint;

View file

@ -15,13 +15,13 @@
*/
package ghidra.trace.database.breakpoint;
import java.util.Collection;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.dbg.target.*;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathMatcher;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.database.target.*;
@ -208,6 +208,11 @@ public class DBTraceObjectBreakpointLocation
object.setValue(Lifespan.span(snap, getClearedSnap()),
TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled);
}
Set<TraceBreakpointKind> asSet =
kinds instanceof Set<TraceBreakpointKind> yes ? yes : Set.copyOf(kinds);
if (!Objects.equals(asSet, getKinds())) {
this.setKinds(Lifespan.span(snap, getClearedSnap()), asSet);
}
return this;
}
}
@ -297,9 +302,56 @@ public class DBTraceObjectBreakpointLocation
@Override
public String getComment() {
try (LockHold hold = object.getTrace().lockRead()) {
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_COMMENT,
String.class, "");
}
}
@Override
public void setEmuEnabled(Lifespan lifespan, boolean emuEnabled) {
object.setValue(lifespan, KEY_EMU_ENABLED, emuEnabled ? null : false);
}
@Override
public void setEmuEnabled(boolean emuEnabled) {
try (LockHold hold = object.getTrace().lockWrite()) {
setEmuEnabled(getLifespan(), emuEnabled);
}
}
@Override
public boolean isEmuEnabled(long snap) {
try (LockHold hold = object.getTrace().lockRead()) {
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_EMU_ENABLED,
Boolean.class, true);
}
}
@Override
public void setEmuSleigh(Lifespan lifespan, String sleigh) {
if (sleigh == null || SleighUtils.UNCONDITIONAL_BREAK.equals(sleigh)) {
object.setValue(lifespan, KEY_EMU_SLEIGH, null);
}
else {
object.setValue(lifespan, KEY_EMU_SLEIGH, sleigh.trim());
}
}
@Override
public void setEmuSleigh(String sleigh) {
try (LockHold hold = object.getTrace().lockWrite()) {
setEmuSleigh(getLifespan(), sleigh);
}
}
@Override
public String getEmuSleigh() {
try (LockHold hold = object.getTrace().lockRead()) {
return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_EMU_SLEIGH,
String.class, SleighUtils.UNCONDITIONAL_BREAK);
}
}
@Override
public void delete() {

View file

@ -131,7 +131,8 @@ public class DBTraceObjectBreakpointSpec
@Override
public void setEnabled(boolean enabled) {
try (LockHold hold = object.getTrace().lockWrite()) {
object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled);
object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME,
enabled ? true : null);
}
}
@ -190,6 +191,26 @@ public class DBTraceObjectBreakpointSpec
throw new UnsupportedOperationException("Ask a location instead");
}
@Override
public void setEmuEnabled(boolean enabled) {
throw new UnsupportedOperationException("Set on a location instead");
}
@Override
public boolean isEmuEnabled(long snap) {
throw new UnsupportedOperationException("Ask a location instead");
}
@Override
public void setEmuSleigh(String sleigh) {
throw new UnsupportedOperationException("Set on a location instead");
}
@Override
public String getEmuSleigh() {
throw new UnsupportedOperationException("Ask a location instead");
}
@Override
public void delete() {
try (LockHold hold = object.getTrace().lockWrite()) {

View file

@ -53,6 +53,16 @@ import resources.ResourceManager;
public interface Trace extends DataTypeManagerDomainObject {
ImageIcon TRACE_ICON = ResourceManager.loadImage("images/video-x-generic16.png");
/**
* TEMPORARY: An a/b switch while both table- (legacy) and object-mode traces are supported
*
* @param trace the trace, or null
* @return true if the trace is non-null and has no root schema
*/
public static boolean isLegacy(Trace trace) {
return trace != null && trace.getObjectManager().getRootSchema() == null;
}
public static final class TraceObjectChangeType<T, U>
extends DefaultTraceChangeType<T, U> {
/**

View file

@ -28,7 +28,6 @@ import ghidra.util.exception.DuplicateNameException;
* A breakpoint in a trace
*/
public interface TraceBreakpoint extends TraceUniqueObject {
/**
* Get the trace containing this breakpoint
*
@ -161,10 +160,30 @@ public interface TraceBreakpoint extends TraceUniqueObject {
/**
* Check whether this breakpoint is enabled or disabled at the given snap
*
* @param snap the snap
* @return true if enabled, false if disabled
*/
boolean isEnabled(long snap);
/**
* Set whether this breakpoint is enabled or disabled for emulation
*
* <p>
* This change applies to the entire lifespan of the record. It's not intended to record a
* history, but to toggle the breakpoint in the integrated emulator.
*
* @param enabled true if enabled, false if disabled
*/
void setEmuEnabled(boolean enabled);
/**
* Check whether this breakpoint is enabled or disabled for emulation at the given snap
*
* @param snap the snap
* @return true if enabled, false if disabled
*/
boolean isEmuEnabled(long snap);
/**
* Set the kinds included in this breakpoint
*
@ -213,6 +232,36 @@ public interface TraceBreakpoint extends TraceUniqueObject {
*/
String getComment();
/**
* Set Sleigh source to replace the breakpointed instruction in emulation
*
* <p>
* The default is simply "<code>{@link PcodeEmulationLibrary#emu_swi() emu_swi()};
* {@link PcodeEmulationLibrary#emu_exec_decoded() emu_exec_decoded()};</code>", effectively a
* non-conditional breakpoint followed by execution of the actual instruction. Modifying this
* allows clients to create conditional breakpoints or simply override or inject additional
* logic into an emulated target.
*
* <p>
* <b>NOTE:</b> This current has no effect on access breakpoints, but only execution
* breakpoints.
*
* <p>
* If the specified source fails to compile during emulator set-up, this will fall back to
* {@link PcodeEmulationLibrary#emu_err
*
* @see #DEFAULT_SLEIGH
* @param sleigh the Sleigh source
*/
void setEmuSleigh(String sleigh);
/**
* Get the Sleigh source that replaces the breakpointed instruction in emulation
*
* @return the Sleigh source
*/
String getEmuSleigh();
/**
* Delete this breakpoint from the trace
*/

View file

@ -37,6 +37,8 @@ public enum TraceBreakpointKind {
HW_EXECUTE(1 << 2),
SW_EXECUTE(1 << 3);
public static final int COUNT = values().length;
public static class TraceBreakpointKindSet extends AbstractSetDecorator<TraceBreakpointKind> {
public static final TraceBreakpointKindSet SW_EXECUTE = of(TraceBreakpointKind.SW_EXECUTE);
public static final TraceBreakpointKindSet HW_EXECUTE = of(TraceBreakpointKind.HW_EXECUTE);

View file

@ -32,9 +32,12 @@ import ghidra.util.exception.DuplicateNameException;
TargetObject.DISPLAY_ATTRIBUTE_NAME,
TargetBreakpointLocation.RANGE_ATTRIBUTE_NAME,
TraceObjectBreakpointLocation.KEY_COMMENT,
TraceObjectBreakpointLocation.KEY_EMU_ENABLED,
})
public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObjectInterface {
String KEY_COMMENT = "_comment";
String KEY_EMU_ENABLED = "_emu_enabled";
String KEY_EMU_SLEIGH = "_emu_sleigh";
TraceObjectBreakpointSpec getSpecification();
@ -48,5 +51,9 @@ public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObj
void setEnabled(Lifespan lifespan, boolean enabled);
void setEmuEnabled(Lifespan lifespan, boolean emuEnabled);
void setEmuSleigh(Lifespan lifespan, String sleigh);
void setComment(Lifespan lifespan, String comment);
}

View file

@ -132,14 +132,16 @@ public interface Scheduler {
}
catch (PcodeExecutionException e) {
completedSteps = completedSteps.steppedForward(eventThread, completedTicks);
if (emuThread.getInstruction() == null) {
PcodeFrame frame = emuThread.getFrame();
if (frame == null) {
return new RecordRunResult(completedSteps, e);
}
PcodeFrame frame = e.getFrame();
// Rewind one so stepping retries the op causing the error
int count = frame.count() - 1;
if (frame == null || count == 0) {
// If we've decoded, but could execute the first op, just drop the p-code steps
frame.stepBack();
int count = frame.count();
if (count == 0) {
// If we've decoded, but could not execute the first op, just drop the p-code steps
emuThread.dropInstruction();
return new RecordRunResult(completedSteps, e);
}
// The +1 accounts for the decode step

View file

@ -18,6 +18,7 @@ package ghidra.trace.model.time.schedule;
import java.util.*;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeMachine.SwiMode;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
@ -25,9 +26,18 @@ import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* A sequence of emulator stepping commands, essentially comprising a "point in time."
*/
public class TraceSchedule implements Comparable<TraceSchedule> {
public static final TraceSchedule ZERO = TraceSchedule.snap(0);
/**
* Create a schedule that consists solely of a snapshot
*
* @param snap the snapshot key
* @return the schedule
*/
public static final TraceSchedule snap(long snap) {
return new TraceSchedule(snap, new Sequence(), new Sequence());
}
@ -337,11 +347,10 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
*/
public void execute(Trace trace, PcodeMachine<?> machine, TaskMonitor monitor)
throws CancelledException {
machine.setSoftwareInterruptMode(SwiMode.IGNORE_ALL);
TraceThread lastThread = getEventThread(trace);
lastThread =
steps.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
lastThread =
pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
lastThread = steps.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
lastThread = pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
}
/**
@ -380,6 +389,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
TaskMonitor monitor) throws CancelledException {
TraceThread lastThread = position.getLastThread(trace);
Sequence remains = steps.relativize(position.steps);
machine.setSoftwareInterruptMode(SwiMode.IGNORE_ALL);
if (remains.isNop()) {
Sequence pRemains = this.pSteps.relativize(position.pSteps);
lastThread =
@ -388,8 +398,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
else {
lastThread =
remains.execute(trace, lastThread, machine, Stepper.instruction(), monitor);
lastThread =
pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
lastThread = pSteps.execute(trace, lastThread, machine, Stepper.pcode(), monitor);
}
}

View file

@ -46,8 +46,8 @@ public class AbstractTracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegr
* <p>
* This creates a relatively bare-bones trace with initial state for testing trace
* emulation/interpolation. It adds ".text" and "stack" regions, creates a thread, assembles
* given instructions, and then executes the given SLEIGH source (in the context of the new
* thread) to finish initializing the trace. Note, though given first, the SLEIGH is executed
* given instructions, and then executes the given Sleigh source (in the context of the new
* thread) to finish initializing the trace. Note, though given first, the Sleigh is executed
* after assembly. Thus, it can be used to modify the resulting machine code by modifying the
* memory where it was assembled.
*

View file

@ -312,7 +312,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest
* This may not reflect the semantics of an actual processor in these situations, since they may
* have instruction caching. Emulating such semantics is TODO, if at all. NB. This also tests
* that PC-relative addressing works, since internally the emulator advances the counter after
* execution of each instruction. Addressing is computed by the SLEIGH instruction parser and
* execution of each instruction. Addressing is computed by the Sleigh instruction parser and
* encoded as a constant deref in the p-code.
*/
@Test

Some files were not shown because too many files have changed in this diff Show more