mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-2676: Breakpoints can now be placed in emulator with Sleigh injections or custom conditions.
This commit is contained in:
parent
df0274a4d7
commit
888c8c911d
116 changed files with 5676 additions and 1993 deletions
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 — 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 — 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 >= 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) & 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 >= 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 < 0x1000 goto <L1>;
|
||||
emu_swi();
|
||||
<L1>
|
||||
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 —
|
||||
<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 |
|
@ -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=
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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> {
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue