mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-24 03:09:36 +08:00
GP-569: Added trace interpolation and extrapolation (emulation)
This commit is contained in:
+2
-4
@@ -35,10 +35,8 @@ import ghidra.util.Msg;
|
||||
|
||||
@TargetObjectSchemaInfo(
|
||||
name = "Thread",
|
||||
elements = {
|
||||
@TargetElementType(type = Void.class) },
|
||||
attributes = {
|
||||
@TargetAttributeType(type = Void.class) })
|
||||
elements = { @TargetElementType(type = Void.class) },
|
||||
attributes = { @TargetAttributeType(type = Void.class) })
|
||||
public class GdbModelTargetThread
|
||||
extends DefaultTargetObject<TargetObject, GdbModelTargetThreadContainer> implements
|
||||
TargetThread, TargetExecutionStateful, TargetSteppable, GdbModelSelectableObject {
|
||||
|
||||
@@ -85,6 +85,8 @@ src/main/help/help/topics/DebuggerObjectsPlugin/images/stepinto.png||GHIDRA||||E
|
||||
src/main/help/help/topics/DebuggerObjectsPlugin/images/stepout.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerObjectsPlugin/images/stepover.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerObjectsPlugin/images/stop.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerPcodeStepperPlugin/images/DebuggerPcodeStepperPlugin.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerRegionsPlugin/images/DebuggerRegionsPlugin.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html||GHIDRA||||END|
|
||||
|
||||
@@ -136,8 +136,12 @@
|
||||
sortgroup="o"
|
||||
target="help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerPcodeStepperPlugin" text="P-code Stepper"
|
||||
sortgroup="p"
|
||||
target="help/topics/DebuggerPcodeStepperPlugin/DebuggerPcodeStepperPlugin.html" />
|
||||
|
||||
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
|
||||
sortgroup="p"
|
||||
sortgroup="q"
|
||||
target="help/topics/DebuggerBots/DebuggerBots.html" />
|
||||
</tocdef>
|
||||
</tocref>
|
||||
|
||||
+3
-3
@@ -31,9 +31,9 @@
|
||||
determines whether both options are in play. For example, threads and inferiors/processes are
|
||||
both <B>resumable</B>, so the <A href="DebuggerObjectsPlugin.html#resume">Resume</A> action
|
||||
works on both. For many of our targets, processes are <B>interruptible</B> while threads are
|
||||
not. Nevertheless, if <B>Enable By Selection Only</B> is off, you can interrupt a thread because
|
||||
it descends from an inferior or process. In almost every case, the selection directly or
|
||||
indirectly determines the set of valid or enabled actions.</P>
|
||||
not. Nevertheless, if <B>Enable By Selection Only</B> is off, you can interrupt a thread
|
||||
because it descends from an inferior or process. In almost every case, the selection directly
|
||||
or indirectly determines the set of valid or enabled actions.</P>
|
||||
|
||||
<P>The application of these special properties to each object to determine its behavior and
|
||||
relevant actions allows all objects to be treated generically. This feature has several
|
||||
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="generator" content=
|
||||
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||
|
||||
<TITLE>Debugger: P-code Stepper</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="plugin"></A>Debugger: P-code Stepper</H1>
|
||||
|
||||
<TABLE width="100%">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD align="center" width="100%"><IMG alt="" border="1" src=
|
||||
"images/DebuggerPcodeStepperPlugin.png"></TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
|
||||
<P>P-code is the "microcode" of Ghidra's processor specifications, compiled from its SLEIGH
|
||||
specification. Originally designed to facilitate static analysis, it is easily applied to
|
||||
emulation as well. Stepping each p-code operation is an effective means of debugging the
|
||||
SLEIGH. The plugin provides two panes: 1) The p-code listing, and 2) Temporary ("Unique")
|
||||
variables. The listing works similarly to the dynamic listing. It displays each p-code
|
||||
operation, highlighting the current "counter", which is the next operation to be executed.
|
||||
There is also a cursor, allowing selection of an operation. The variables view operates
|
||||
similarly to the registers view, displaying the current value of each unique variable.</P>
|
||||
|
||||
<P>P-code stepping is built into the emulation framework, and so the other UI elements
|
||||
(listing, registers, etc.) will display machine state from emulated p-code operations, i.e.,
|
||||
partially executed machine instructions. The p-code stepper provides a means of navigating time
|
||||
at p-code-level and displaying p-code-level details of the machine state.</P>
|
||||
|
||||
<H2>Table Columns</H2>
|
||||
|
||||
<P>The unique variables table displays information about temporary variables, including their
|
||||
values and user-assigned types. It has the following columns:</P>
|
||||
|
||||
<UL>
|
||||
<LI>Ref - describes how the select p-code operation uses the variable. Blank indicates no
|
||||
reference. A ← indicates read. A → indicates write.</LI>
|
||||
|
||||
<LI>Unique - the name (address and size) of the variable.</LI>
|
||||
|
||||
<LI>Bytes - the value displayed as bytes in the machine's endianness.</LI>
|
||||
|
||||
<LI>Value - the value displayed in hexadecimal.</LI>
|
||||
|
||||
<LI>Type - the user-assigned, ephemeral type of the variable.</LI>
|
||||
|
||||
<LI>Representation - the value of the variable as interpreted by its data type.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<P>The p-code stepper provides the following actions:</P>
|
||||
|
||||
<H3><A name="step_trace_pcode_backward"></A>Step Trace p-code Backward</H3>
|
||||
|
||||
<P>This action is available when the current coordinates have some positive number of p-code
|
||||
ticks. It steps the trace backward to the previous p-code tick, possibly using emulation. Note
|
||||
that stepping backward does not affect the target, and many windows that would ordinarily
|
||||
interact with a live target, may no longer do so, until the user steps back to the present.
|
||||
Note also that any component or script that does interact with the target and record things
|
||||
"into the present" may not cause updates in windows that are not displaying the present.</P>
|
||||
|
||||
<H3><A name="step_trace_pcode_forward"></A>Step Trace p-code Forward</H3>
|
||||
|
||||
<P>This action is available when a thread is selected. It steps the current thread forward to
|
||||
the next p-code tick, using emulation. Note that emulation does not affect the target, and many
|
||||
windows that would ordinarily interact with a live target, may not longer do so, until the user
|
||||
steps back to the present. Note also that any component or script that does interact with the
|
||||
target and record things "into the present" may not cause updates in windows that are not
|
||||
displaying the present.</P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
+1
-1
@@ -31,7 +31,7 @@
|
||||
<H2>Table Columns</H2>
|
||||
|
||||
<P>The table displays information about registers, including their values and types. It has the
|
||||
following columns</P>
|
||||
following columns:</P>
|
||||
|
||||
<UL>
|
||||
<LI>Favorite - a toggle to mark the register as a favorite. By default this includes the
|
||||
|
||||
+4
-3
@@ -24,13 +24,14 @@
|
||||
|
||||
<P>The stack window displays the current trace's execution stack, as unwound and reported by
|
||||
the target. Not all debuggers will unwind the stack, in which case, this window displays a
|
||||
synthetic innermost frame. Level 0 always refers to the innermost frame, and each incremental
|
||||
synthetic innermost frame. When emulation was used to generate the current machine state, only
|
||||
a synthetic frame is shown. Level 0 always refers to the innermost frame, and each incremental
|
||||
level refers to the next caller in the chain — most of the time. The current frame
|
||||
comprises one element of the tool's current "coordinates." Selecting a frame changes those
|
||||
coordinates, potentially causing other windows to display different information. Namely, the <A
|
||||
href="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window
|
||||
will show registers for the current frame, assuming they can be retrieved The Listings may also
|
||||
navigate to the current frame's program counter.</P>
|
||||
will show registers for the current frame, assuming they can be retrieved. The Listings may
|
||||
also navigate to the current frame's program counter.</P>
|
||||
|
||||
<H2>Table Columns</H2>
|
||||
|
||||
|
||||
+35
-14
@@ -86,25 +86,46 @@
|
||||
<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.</P>
|
||||
|
||||
<H3><A name="step_trace_backward"></A><IMG alt="" src="images/stepback.png">Step Track
|
||||
Backward</H3>
|
||||
<H3><A name="step_trace_snap_backward"></A><IMG alt="" src="images/arrow_up.png">Step Track
|
||||
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. Note that stepping backward does not affect the target, and many windows
|
||||
that would ordinarily interact with a live target, may no longer do so, until the user steps
|
||||
back to the present. Note also that any component or script that does interact with the target
|
||||
and record things "into the present" will not cause updates in windows that are not displaying
|
||||
the present.</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. If the new point in time represents "the present" for a live trace, then
|
||||
many windows will resume interacting with the target. Note that stepping the trace does not
|
||||
affect the target; however, stepping back to the present may cause some windows to query the
|
||||
target.</P>
|
||||
|
||||
<H3><A name="step_trace_tick_backward"></A><IMG alt="" src="images/stepback.png">Step Track
|
||||
Tick Backward</H3>
|
||||
|
||||
<P>This action is available when there exists a point in time previous to the current. It steps
|
||||
the trace backward once, causing most windows to display the recorded data from the new point
|
||||
in time. Note that stepping backward does not affect the target, and many windows that would
|
||||
ordinarily interact with a live target, may no longer do so, until the user steps back to the
|
||||
present. Note also that any component or script that does interact with the target and record
|
||||
things "into the present" will not cause updates in windows that are not displaying the
|
||||
present.</P>
|
||||
the trace backward to the previous tick, possibly using emulation. Note that stepping backward
|
||||
does not affect the target, and many windows that would ordinarily interact with a live target,
|
||||
may no longer do so, until the user steps back to the present. Note also that any component or
|
||||
script that does interact with the target and record things "into the present" may not cause
|
||||
updates in windows that are not displaying the present.</P>
|
||||
|
||||
<H3><A name="step_trace_forward"></A><IMG alt="" src="images/stepinto.png">Step Trace
|
||||
<H3><A name="step_trace_tick_forward"></A><IMG alt="" src="images/stepinto.png">Step Trace Tick
|
||||
Forward</H3>
|
||||
|
||||
<P>This action is available when there exists a point in time ahead of the current. It steps
|
||||
the trace forward once, causing most windows to display the recorded data from the new point in
|
||||
time. If the new point in time represents "the present" for a live trace, then many windows
|
||||
will resume interacting with the target. Note that stepping the trace does not affect the
|
||||
target; however, stepping back to the present may cause some windows to query the target.</P>
|
||||
<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, and many
|
||||
windows that would ordinarily interact with a live target, may no longer do so, until the user
|
||||
steps back to the present. Note also that any component or script that does interact with the
|
||||
target and record things "into the present" may not cause updates in windows that are not
|
||||
displaying the present.</P>
|
||||
|
||||
<H3><A name="seek_trace_present"></A><IMG alt="" src="images/continue.png">Seek Trace to
|
||||
Present</H3>
|
||||
|
||||
+14
-1
@@ -24,7 +24,7 @@
|
||||
|
||||
<P>This window displays all recorded "snapshots" in the current trace. Typically, there is one
|
||||
snapshot per event recorded. Other tables often display the times of various events or use time
|
||||
ranges to describe lifespans of various records. Those times refer to the "Snap," which is a
|
||||
ranges to describe lifespans of various records. Those times refer to the "snap," which is a
|
||||
0-up counter of snapshot records. Thus, a snapshot is a collection of observations of a
|
||||
target's state, usually while suspended, along with any user mark up. Selecting a snapshot
|
||||
navigates to the selected point in time. Note that browsing the past may prevent other windows
|
||||
@@ -50,5 +50,18 @@
|
||||
<LI>Description - a user-modifiable description of the snapshot or event. This defaults to
|
||||
the debugger's description of the event.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<P>The time window provides the following action:</P>
|
||||
|
||||
<H3><A name="hide_scratch"></A>Hide Scratch</H3>
|
||||
|
||||
<P>This toggle action is always available. It is enabled by default. The emulation service,
|
||||
which enabled trace extrapolation and interpolation, writes emulated state into the trace's
|
||||
"scratch space," which comprises all negative snaps. When this toggle is enabled, those
|
||||
snapshots are hidden. They can be displayed by disabling this toggle. Note that navigating into
|
||||
scratch space may cause temporary undefined behavior in some windows, and may prevent
|
||||
interaction with the target.</P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
+3
@@ -17,6 +17,9 @@ package ghidra.app.plugin.core.debug;
|
||||
|
||||
import ghidra.framework.plugintool.*;
|
||||
|
||||
/**
|
||||
* All this really does anymore is handle the auto-service wiring thing
|
||||
*/
|
||||
public abstract class AbstractDebuggerPlugin extends Plugin {
|
||||
@SuppressWarnings("unused")
|
||||
private AutoService.Wiring autoServiceWiring;
|
||||
|
||||
+107
-51
@@ -16,6 +16,7 @@
|
||||
package ghidra.app.plugin.core.debug;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jdom.Element;
|
||||
@@ -30,12 +31,16 @@ import ghidra.trace.database.DBTraceContentHandler;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.util.DefaultTraceTimeViewport;
|
||||
import ghidra.trace.util.TraceTimeViewport;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.NotOwnerException;
|
||||
|
||||
public class DebuggerCoordinates {
|
||||
public static final DebuggerCoordinates NOWHERE =
|
||||
new DebuggerCoordinates(null, null, null, null, 0L, "", 0) {
|
||||
new DebuggerCoordinates(null, null, null, null, TraceSchedule.ZERO, 0) {
|
||||
@Override
|
||||
public void writeDataState(PluginTool tool, SaveState saveState, String key) {
|
||||
// Write nothing
|
||||
@@ -47,52 +52,59 @@ public class DebuggerCoordinates {
|
||||
private static final String KEY_TRACE_PATH = "TracePath";
|
||||
private static final String KEY_TRACE_VERSION = "TraceVersion";
|
||||
private static final String KEY_THREAD_KEY = "ThreadKey";
|
||||
private static final String KEY_SNAP = "Snap";
|
||||
private static final String KEY_TICKS = "Ticks";
|
||||
private static final String KEY_TIME = "Time";
|
||||
private static final String KEY_FRAME = "Frame";
|
||||
|
||||
public static DebuggerCoordinates all(Trace trace, TraceRecorder recorder, TraceThread thread,
|
||||
TraceProgramView view, Long snap, String ticks, Integer frame) {
|
||||
TraceProgramView view, TraceSchedule time, Integer frame) {
|
||||
if (trace == NOWHERE.trace && recorder == NOWHERE.recorder && thread == NOWHERE.thread &&
|
||||
view == NOWHERE.view && snap == NOWHERE.snap && ticks == NOWHERE.ticks &&
|
||||
frame == NOWHERE.frame) {
|
||||
view == NOWHERE.view && time == NOWHERE.time && frame == NOWHERE.frame) {
|
||||
return NOWHERE;
|
||||
}
|
||||
return new DebuggerCoordinates(trace, recorder, thread, view, snap, ticks, frame);
|
||||
return new DebuggerCoordinates(trace, recorder, thread, view, time, frame);
|
||||
}
|
||||
|
||||
public static DebuggerCoordinates trace(Trace trace) {
|
||||
if (trace == null) {
|
||||
return NOWHERE;
|
||||
}
|
||||
return all(trace, null, null, null, null, null, null);
|
||||
return all(trace, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public static DebuggerCoordinates recorder(TraceRecorder recorder) {
|
||||
return all(recorder == null ? null : recorder.getTrace(), recorder,
|
||||
null, null, recorder == null ? null : recorder.getSnap(), null, null);
|
||||
null, null, recorder == null ? null : TraceSchedule.snap(recorder.getSnap()), null);
|
||||
}
|
||||
|
||||
public static DebuggerCoordinates thread(TraceThread thread) {
|
||||
return all(thread == null ? null : thread.getTrace(), null, thread,
|
||||
null, null, null, null);
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
public static DebuggerCoordinates view(TraceProgramView view) {
|
||||
return all(view == null ? null : view.getTrace(), null, null, view,
|
||||
view == null ? null : view.getSnap(), null, null);
|
||||
view == null ? null : TraceSchedule.snap(view.getSnap()), null);
|
||||
}
|
||||
|
||||
public static DebuggerCoordinates snap(long snap) {
|
||||
return all(null, null, null, null, snap, null, null);
|
||||
return all(null, null, null, null, TraceSchedule.snap(snap), null);
|
||||
}
|
||||
|
||||
public static DebuggerCoordinates time(String time) {
|
||||
return time(TraceSchedule.parse(time));
|
||||
}
|
||||
|
||||
public static DebuggerCoordinates time(TraceSchedule time) {
|
||||
return all(null, null, null, null, time, null);
|
||||
}
|
||||
|
||||
public static DebuggerCoordinates frame(int frame) {
|
||||
return all(null, null, null, null, null, null, frame);
|
||||
return all(null, null, null, null, null, frame);
|
||||
}
|
||||
|
||||
public static DebuggerCoordinates threadSnap(TraceThread thread, long snap) {
|
||||
return all(thread == null ? null : thread.getTrace(), null, thread, null, snap, null, null);
|
||||
return all(thread == null ? null : thread.getTrace(), null, thread, null,
|
||||
TraceSchedule.snap(snap), null);
|
||||
}
|
||||
|
||||
public static boolean equalsIgnoreRecorderAndView(DebuggerCoordinates a,
|
||||
@@ -103,10 +115,7 @@ public class DebuggerCoordinates {
|
||||
if (!Objects.equals(a.thread, b.thread)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.snap, b.snap)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.ticks, b.ticks)) {
|
||||
if (!Objects.equals(a.time, b.time)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.frame, b.frame)) {
|
||||
@@ -119,30 +128,31 @@ public class DebuggerCoordinates {
|
||||
private final TraceRecorder recorder;
|
||||
private final TraceThread thread;
|
||||
private final TraceProgramView view;
|
||||
private final Long snap;
|
||||
private final String ticks;
|
||||
private final TraceSchedule time;
|
||||
private final Integer frame;
|
||||
|
||||
private final int hash;
|
||||
|
||||
private Long viewSnap;
|
||||
private DefaultTraceTimeViewport viewport;
|
||||
|
||||
protected DebuggerCoordinates(Trace trace, TraceRecorder recorder, TraceThread thread,
|
||||
TraceProgramView view, Long snap, String ticks, Integer frame) {
|
||||
TraceProgramView view, TraceSchedule time, Integer frame) {
|
||||
this.trace = trace;
|
||||
this.recorder = recorder;
|
||||
this.thread = thread;
|
||||
this.view = view;
|
||||
this.snap = snap;
|
||||
this.ticks = ticks;
|
||||
this.time = time;
|
||||
this.frame = frame;
|
||||
|
||||
this.hash = Objects.hash(trace, recorder, thread, view, snap, ticks, frame);
|
||||
this.hash = Objects.hash(trace, recorder, thread, view, time, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"Coords(trace=%s,recorder=%s,thread=%s,view=%s,snap=%d,ticks=%s,frame=%d)",
|
||||
trace, recorder, thread, view, snap, ticks, frame);
|
||||
"Coords(trace=%s,recorder=%s,thread=%s,view=%s,time=%s,frame=%d)",
|
||||
trace, recorder, thread, view, time, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -163,10 +173,7 @@ public class DebuggerCoordinates {
|
||||
if (!Objects.equals(this.view, that.view)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.snap, that.snap)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.ticks, that.ticks)) {
|
||||
if (!Objects.equals(this.time, that.time)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.frame, that.frame)) {
|
||||
@@ -189,7 +196,7 @@ public class DebuggerCoordinates {
|
||||
}
|
||||
|
||||
public DebuggerCoordinates withRecorder(TraceRecorder newRecorder) {
|
||||
return all(trace, newRecorder, thread, view, snap, ticks, frame);
|
||||
return all(trace, newRecorder, thread, view, time, frame);
|
||||
}
|
||||
|
||||
public TraceThread getThread() {
|
||||
@@ -208,7 +215,7 @@ public class DebuggerCoordinates {
|
||||
}
|
||||
|
||||
public DebuggerCoordinates withThread(TraceThread newThread) {
|
||||
return all(trace, recorder, newThread, view, snap, ticks, frame);
|
||||
return all(trace, recorder, newThread, view, time, frame);
|
||||
}
|
||||
|
||||
public TraceProgramView getView() {
|
||||
@@ -216,21 +223,60 @@ public class DebuggerCoordinates {
|
||||
}
|
||||
|
||||
public Long getSnap() {
|
||||
return snap;
|
||||
return time.getSnap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get these same coordinates with time replaced by the given snap-only coordinate
|
||||
*
|
||||
* @param newSnap the new snap
|
||||
* @return the new coordinates
|
||||
*/
|
||||
public DebuggerCoordinates withSnap(Long newSnap) {
|
||||
return all(trace, recorder, thread, view, newSnap, ticks, frame);
|
||||
return all(trace, recorder, thread, view,
|
||||
newSnap == null ? time : TraceSchedule.snap(newSnap), frame);
|
||||
}
|
||||
|
||||
public String getTicks() {
|
||||
return ticks;
|
||||
public DebuggerCoordinates withTime(TraceSchedule newTime) {
|
||||
return all(trace, recorder, thread, view, newTime, frame);
|
||||
}
|
||||
|
||||
public TraceSchedule getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public Integer getFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
public synchronized long getViewSnap() {
|
||||
if (viewSnap != null) {
|
||||
return viewSnap;
|
||||
}
|
||||
if (time.isSnapOnly()) {
|
||||
return viewSnap = time.getSnap();
|
||||
}
|
||||
Collection<? extends TraceSnapshot> snapshots =
|
||||
trace.getTimeManager().getSnapshotsWithSchedule(time);
|
||||
if (snapshots.isEmpty()) {
|
||||
Msg.warn(this, "Seems the emulation service did not create the requested snapshot");
|
||||
return viewSnap = time.getSnap();
|
||||
}
|
||||
return viewSnap = snapshots.iterator().next().getKey();
|
||||
}
|
||||
|
||||
public synchronized TraceTimeViewport getViewport() {
|
||||
if (viewport != null) {
|
||||
return viewport;
|
||||
}
|
||||
if (trace == null) {
|
||||
return null;
|
||||
}
|
||||
viewport = new DefaultTraceTimeViewport(trace);
|
||||
viewport.setSnap(getViewSnap());
|
||||
return viewport;
|
||||
}
|
||||
|
||||
public void writeDataState(PluginTool tool, SaveState saveState, String key) {
|
||||
SaveState coordState = new SaveState();
|
||||
// for NOWHERE, key should be completely omitted
|
||||
@@ -252,11 +298,8 @@ public class DebuggerCoordinates {
|
||||
if (thread != null) {
|
||||
coordState.putLong(KEY_THREAD_KEY, thread.getKey());
|
||||
}
|
||||
if (snap != null) {
|
||||
coordState.putLong(KEY_SNAP, snap);
|
||||
}
|
||||
if (ticks != null) {
|
||||
coordState.putString(KEY_TICKS, ticks);
|
||||
if (time != null) {
|
||||
coordState.putString(KEY_TIME, time.toString());
|
||||
}
|
||||
if (frame != null) {
|
||||
coordState.putInt(KEY_FRAME, frame);
|
||||
@@ -327,37 +370,50 @@ public class DebuggerCoordinates {
|
||||
long threadKey = coordState.getLong(KEY_THREAD_KEY, 0);
|
||||
thread = trace.getThreadManager().getThread(threadKey);
|
||||
}
|
||||
Long snap = null;
|
||||
if (coordState.hasValue(KEY_SNAP)) {
|
||||
snap = coordState.getLong(KEY_SNAP, 0);
|
||||
String timeSpec = coordState.getString(KEY_TIME, null);
|
||||
TraceSchedule time;
|
||||
try {
|
||||
time = TraceSchedule.parse(timeSpec);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(DebuggerCoordinates.class,
|
||||
"Could not restore invalid time specification: " + timeSpec);
|
||||
time = TraceSchedule.ZERO;
|
||||
}
|
||||
String ticks = coordState.getString(KEY_TICKS, null);
|
||||
Integer frame = null;
|
||||
if (coordState.hasValue(KEY_FRAME)) {
|
||||
frame = coordState.getInt(KEY_FRAME, 0);
|
||||
}
|
||||
|
||||
DebuggerCoordinates coords =
|
||||
DebuggerCoordinates.all(trace, null, thread, null, snap, ticks, frame);
|
||||
DebuggerCoordinates.all(trace, null, thread, null, time, frame);
|
||||
if (!resolve) {
|
||||
return coords;
|
||||
}
|
||||
return traceManager.resolveCoordinates(coords);
|
||||
}
|
||||
|
||||
public boolean isDeadOrPresent() {
|
||||
return recorder == null || isPresent();
|
||||
}
|
||||
|
||||
public boolean isAlive() {
|
||||
return recorder != null;
|
||||
}
|
||||
|
||||
public boolean isPresent() {
|
||||
return recorder.getSnap() == snap;
|
||||
return recorder.getSnap() == time.getSnap() && time.isSnapOnly();
|
||||
}
|
||||
|
||||
public boolean isReadsPresent() {
|
||||
return recorder.getSnap() == time.getSnap();
|
||||
}
|
||||
|
||||
public boolean isAliveAndPresent() {
|
||||
return isAlive() && isPresent();
|
||||
}
|
||||
|
||||
public boolean isDeadOrPresent() {
|
||||
return !isAlive() || isPresent();
|
||||
}
|
||||
|
||||
public boolean isAliveAndReadsPresent() {
|
||||
return isAlive() && isReadsPresent();
|
||||
}
|
||||
}
|
||||
|
||||
+129
-17
@@ -35,6 +35,7 @@ import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerStaticMappingPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.pcode.DebuggerPcodeStepperPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.stack.DebuggerStackPlugin;
|
||||
import ghidra.app.plugin.core.debug.gui.target.DebuggerTargetsPlugin;
|
||||
@@ -48,7 +49,9 @@ import ghidra.framework.plugintool.util.PluginUtils;
|
||||
import ghidra.program.database.ProgramContentHandler;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.*;
|
||||
import resources.MultiIcon;
|
||||
import resources.ResourceManager;
|
||||
import resources.icons.RotateIcon;
|
||||
|
||||
public interface DebuggerResources {
|
||||
String OPTIONS_CATEGORY_WORKFLOW = "Debugger.Workflow";
|
||||
@@ -66,16 +69,20 @@ public interface DebuggerResources {
|
||||
ImageIcon ICON_LAUNCH = ResourceManager.loadImage("images/launch.png");
|
||||
ImageIcon ICON_ATTACH = ResourceManager.loadImage("images/attach.png");
|
||||
ImageIcon ICON_RESUME = ResourceManager.loadImage("images/continue.png");
|
||||
ImageIcon ICON_STEP_INTO = ResourceManager.loadImage("images/stepinto.png");
|
||||
ImageIcon ICON_STEP_OVER = ResourceManager.loadImage("images/stepover.png");
|
||||
ImageIcon ICON_STEP_FINISH = ResourceManager.loadImage("images/stepout.png");
|
||||
ImageIcon ICON_REV_STEP_INTO = ResourceManager.loadImage("images/stepback.png");
|
||||
ImageIcon ICON_TERMINATE = ResourceManager.loadImage("images/stop.png");
|
||||
ImageIcon ICON_KILL = ResourceManager.loadImage("images/kill.png");
|
||||
ImageIcon ICON_DETACH = ResourceManager.loadImage("images/detach.png");
|
||||
ImageIcon ICON_SEEK_PRESENT = ICON_RESUME; // TODO: Draw a new icon?
|
||||
ImageIcon ICON_RECORD = ResourceManager.loadImage("images/record.png");
|
||||
|
||||
ImageIcon ICON_STEP_INTO = ResourceManager.loadImage("images/stepinto.png");
|
||||
ImageIcon ICON_STEP_OVER = ResourceManager.loadImage("images/stepover.png");
|
||||
ImageIcon ICON_STEP_FINISH = ResourceManager.loadImage("images/stepout.png");
|
||||
ImageIcon ICON_STEP_BACK = ResourceManager.loadImage("images/stepback.png");
|
||||
// TODO: Draw new icons?
|
||||
ImageIcon ICON_SNAP_FORWARD = ResourceManager.loadImage("images/2rightarrow.png");
|
||||
ImageIcon ICON_SNAP_BACKWARD = ResourceManager.loadImage("images/2leftarrow.png");
|
||||
ImageIcon ICON_SEEK_PRESENT = ICON_RESUME;
|
||||
|
||||
ImageIcon ICON_SET_BREAKPOINT = ResourceManager.loadImage("images/breakpoint-set.png");
|
||||
ImageIcon ICON_CLEAR_BREAKPOINT = ResourceManager.loadImage("images/breakpoint-clear.png");
|
||||
ImageIcon ICON_ENABLE_BREAKPOINT = ResourceManager.loadImage("images/breakpoint-enable.png");
|
||||
@@ -95,6 +102,7 @@ public interface DebuggerResources {
|
||||
ImageIcon ICON_BREAKPOINTS = ResourceManager.loadImage("images/breakpoints.png");
|
||||
ImageIcon ICON_MODULES = ResourceManager.loadImage("images/modules.png");
|
||||
ImageIcon ICON_MAPPINGS = ICON_PROGRAM; // TODO: A better icon
|
||||
ImageIcon ICON_PCODE = ResourceManager.loadImage("images/stepinto.png"); // TODO
|
||||
//ResourceManager.loadImage("images/mappings.png");
|
||||
ImageIcon ICON_REGIONS = ResourceManager.loadImage("images/memory16.gif");
|
||||
ImageIcon ICON_TIME = ResourceManager.loadImage("images/time.png");
|
||||
@@ -158,6 +166,11 @@ public interface DebuggerResources {
|
||||
HelpLocation HELP_PROVIDER_MODULES = new HelpLocation(
|
||||
PluginUtils.getPluginNameFromClass(DebuggerModulesPlugin.class), HELP_ANCHOR_PLUGIN);
|
||||
|
||||
String TITLE_PROVIDER_PCODE = "Pcode Stepper";
|
||||
ImageIcon ICON_PROVIDER_PCODE = ICON_PCODE;
|
||||
HelpLocation HELP_PROVIDER_PCODE = new HelpLocation(
|
||||
PluginUtils.getPluginNameFromClass(DebuggerPcodeStepperPlugin.class), HELP_ANCHOR_PLUGIN);
|
||||
|
||||
String TITLE_PROVIDER_REGIONS = "Regions";
|
||||
ImageIcon ICON_PROVIDER_REGIONS = ICON_REGIONS;
|
||||
HelpLocation HELP_PROVIDER_REGIONS = new HelpLocation(
|
||||
@@ -233,6 +246,9 @@ public interface DebuggerResources {
|
||||
String OPTION_NAME_COLORS_WATCH_CHANGED_SEL = "Colors.Changed Watches (selected)";
|
||||
Color DEFAULT_COLOR_WATCH_CHANGED_SEL = ColorUtils.blend(Color.RED, Color.WHITE, 0.5f);
|
||||
|
||||
String OPTION_NAME_COLORS_PCODE_COUNTER = "Colors.Pcode Counter";
|
||||
Color DEFAULT_COLOR_PCODE_COUNTER = new Color(0.75f, 0.875f, 0.75f);
|
||||
|
||||
String MARKER_NAME_BREAKPOINT_ENABLED = "Enabled Breakpoint";
|
||||
String MARKER_NAME_BREAKPOINT_DISABLED = "Disabled Breakpoint";
|
||||
String MARKER_NAME_BREAKPOINT_MIXED_ED = "Mixed Enabled-Disabled Breakpont";
|
||||
@@ -248,6 +264,11 @@ public interface DebuggerResources {
|
||||
ImageIcon ICON_BREAKPOINT_MIXED_DE_MARKER =
|
||||
ResourceManager.loadImage("images/breakpoint-mixed-de.png");
|
||||
|
||||
Icon ICON_UNIQUE_REF_READ =
|
||||
new RotateIcon(ResourceManager.loadImage("images/cursor_arrow.gif"), 180); // TODO
|
||||
ImageIcon ICON_UNIQUE_REF_WRITE = ResourceManager.loadImage("images/cursor_arrow.gif"); // TODO
|
||||
Icon ICON_UNIQUE_REF_RW = new MultiIcon(ICON_UNIQUE_REF_READ, ICON_UNIQUE_REF_WRITE); // TODO
|
||||
|
||||
String OPTION_NAME_COLORS_ENABLED_BREAKPOINT_MARKERS = "Colors.Enabled Breakpoint Markers";
|
||||
Color DEFAULT_COLOR_ENABLED_BREAKPOINT_MARKERS = new Color(0.875f, 0.75f, 0.75f);
|
||||
String OPTION_NAME_COLORS_DISABLED_BREAKPOINT_MARKERS = "Colors.Disabled Breakpoint Markers";
|
||||
@@ -1088,6 +1109,23 @@ public interface DebuggerResources {
|
||||
}
|
||||
}
|
||||
|
||||
/*interface SelectAddressesAction { // TODO: Finish this conversion
|
||||
String NAME = "Select Addresses";
|
||||
Icon ICON = ICON_SELECT_ADDRESSES;
|
||||
String GROUP = GROUP_GENERAL;
|
||||
String HELP_ANCHOR = "select_addresses";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.toolBarIcon(ICON)
|
||||
.toolBarGroup(GROUP)
|
||||
.popupMenuPath(NAME)
|
||||
.popupMenuIcon(ICON)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}*/
|
||||
|
||||
abstract class AbstractSelectAddressesAction extends DockingAction {
|
||||
public static final String NAME = "Select Addresses";
|
||||
public static final Icon ICON = ICON_SELECT_ADDRESSES;
|
||||
@@ -1146,30 +1184,88 @@ public interface DebuggerResources {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractStepTraceForwardAction extends DockingAction {
|
||||
public static final String NAME = "Step Trace Forward";
|
||||
public static final Icon ICON = ICON_STEP_INTO;
|
||||
public static final String HELP_ANCHOR = "step_trace_forward";
|
||||
abstract class AbstractStepSnapForwardAction extends DockingAction {
|
||||
public static final String NAME = "Step Trace Snap Forward";
|
||||
public static final Icon ICON = ICON_SNAP_FORWARD;
|
||||
public static final String HELP_ANCHOR = "step_trace_snap_forward";
|
||||
|
||||
public AbstractStepTraceForwardAction(Plugin owner) {
|
||||
public AbstractStepSnapForwardAction(Plugin owner) {
|
||||
super(NAME, owner.getName());
|
||||
setDescription("Move the recording forward one tick");
|
||||
setDescription("Navigate the recording forward one snap");
|
||||
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractStepTraceBackwardAction extends DockingAction {
|
||||
public static final String NAME = "Step Trace Backward";
|
||||
public static final Icon ICON = ICON_REV_STEP_INTO;
|
||||
public static final String HELP_ANCHOR = "step_trace_backward";
|
||||
abstract class AbstractStepTickForwardAction extends DockingAction {
|
||||
public static final String NAME = "Step Trace Tick Forward";
|
||||
public static final Icon ICON = ICON_STEP_INTO;
|
||||
public static final String HELP_ANCHOR = "step_trace_tick_forward";
|
||||
|
||||
public AbstractStepTraceBackwardAction(Plugin owner) {
|
||||
public AbstractStepTickForwardAction(Plugin owner) {
|
||||
super(NAME, owner.getName());
|
||||
setDescription("Move the recording backward one tick");
|
||||
setDescription("Navigate the recording forward one tick");
|
||||
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface StepPcodeForwardAction {
|
||||
String NAME = "Step Trace p-code Forward";
|
||||
String DESCRIPTION = "Navigate the recording forward one p-code tick";
|
||||
Icon ICON = ICON_STEP_INTO;
|
||||
String GROUP = GROUP_CONTROL;
|
||||
String HELP_ANCHOR = "step_trace_pcode_forward";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarIcon(ICON)
|
||||
.toolBarGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractStepTickBackwardAction extends DockingAction {
|
||||
public static final String NAME = "Step Trace Tick Backward";
|
||||
public static final Icon ICON = ICON_STEP_BACK;
|
||||
public static final String HELP_ANCHOR = "step_trace_tick_backward";
|
||||
|
||||
public AbstractStepTickBackwardAction(Plugin owner) {
|
||||
super(NAME, owner.getName());
|
||||
setDescription("Navigate the recording backward one tick");
|
||||
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractStepSnapBackwardAction extends DockingAction {
|
||||
public static final String NAME = "Step Trace Snap Backward";
|
||||
public static final Icon ICON = ICON_SNAP_BACKWARD;
|
||||
public static final String HELP_ANCHOR = "step_trace_snap_backward";
|
||||
|
||||
public AbstractStepSnapBackwardAction(Plugin owner) {
|
||||
super(NAME, owner.getName());
|
||||
setDescription("Navigate the recording backward one snap");
|
||||
setHelpLocation(new HelpLocation(owner.getName(), HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
interface StepPcodeBackwardAction {
|
||||
String NAME = "Step Trace p-code Backward";
|
||||
String DESCRIPTION = "Navigate the recording backward one p-code tick";
|
||||
Icon ICON = ICON_STEP_BACK;
|
||||
String GROUP = GROUP_CONTROL;
|
||||
String HELP_ANCHOR = "step_trace_pcode_backward";
|
||||
|
||||
static ActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.toolBarIcon(ICON)
|
||||
.toolBarGroup(GROUP)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractSeekTracePresentAction extends ToggleDockingAction {
|
||||
public static final String NAME = "Seek Trace Present";
|
||||
public static final Icon ICON = ICON_SEEK_PRESENT;
|
||||
@@ -1372,6 +1468,22 @@ public interface DebuggerResources {
|
||||
}
|
||||
}
|
||||
|
||||
interface HideScratchSnapshotsAction {
|
||||
String NAME = "Hide Scratch";
|
||||
String DESCRIPTION = "Hide negative snaps, typically used as emulation scratch space";
|
||||
String GROUP = GROUP_GENERAL;
|
||||
String HELP_ANCHOR = "hide_scratch";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
return new ToggleActionBuilder(NAME, ownerName)
|
||||
.description(DESCRIPTION)
|
||||
.menuGroup(GROUP_GENERAL)
|
||||
.menuPath(NAME)
|
||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
|
||||
@Override
|
||||
public String getName() {
|
||||
|
||||
+2
-2
@@ -104,7 +104,7 @@ public interface DebuggerListingAutoReadMemoryAction extends AutoReadMemoryActio
|
||||
@Override
|
||||
public CompletableFuture<Void> readMemory(DebuggerCoordinates coordinates,
|
||||
AddressSetView visible) {
|
||||
if (!coordinates.isAliveAndPresent()) {
|
||||
if (!coordinates.isAliveAndReadsPresent()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
TraceRecorder recorder = coordinates.getRecorder();
|
||||
@@ -135,7 +135,7 @@ public interface DebuggerListingAutoReadMemoryAction extends AutoReadMemoryActio
|
||||
@Override
|
||||
public CompletableFuture<Void> readMemory(DebuggerCoordinates coordinates,
|
||||
AddressSetView visible) {
|
||||
if (!coordinates.isAliveAndPresent()) {
|
||||
if (!coordinates.isAliveAndReadsPresent()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
TraceRecorder recorder = coordinates.getRecorder();
|
||||
|
||||
+47
-47
@@ -53,40 +53,40 @@ import ghidra.util.Swing;
|
||||
import utilities.util.SuppressableCallback;
|
||||
import utilities.util.SuppressableCallback.Suppression;
|
||||
|
||||
@PluginInfo(//
|
||||
shortDescription = "View and annotate listings of trace (possibly live) memory", //
|
||||
description = "Provides the memory listing display window. Functions similarly to " +
|
||||
"the main program listing display window, but for traces. If the trace is the " +
|
||||
"destination of a live recording, the view(s) retrieve live memory on demand.", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = { //
|
||||
// ProgramSelectionPluginEvent.class, // TODO: Later or remove
|
||||
// ProgramHighlightPluginEvent.class, // TODO: Later or remove
|
||||
ProgramClosedPluginEvent.class, // For marker set cleanup
|
||||
ProgramLocationPluginEvent.class, // For static listing sync
|
||||
TraceActivatedPluginEvent.class, // Trace/thread activation and register tracking
|
||||
TraceClosedPluginEvent.class, //
|
||||
}, //
|
||||
eventsProduced = { //
|
||||
ProgramLocationPluginEvent.class, //
|
||||
// ProgramSelectionPluginEvent.class, //
|
||||
TraceLocationPluginEvent.class, //
|
||||
TraceSelectionPluginEvent.class //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerModelService.class, // For memory capture
|
||||
DebuggerStaticMappingService.class, // For static listing sync. TODO: Optional?
|
||||
ProgramManager.class, // TODO: Needed?
|
||||
GoToService.class, // For static listing sync
|
||||
ClipboardService.class, //
|
||||
MarkerService.class // TODO: Make optional?
|
||||
}, //
|
||||
servicesProvided = { //
|
||||
DebuggerListingService.class, //
|
||||
} //
|
||||
)
|
||||
@PluginInfo(
|
||||
shortDescription = "View and annotate listings of trace (possibly live) memory",
|
||||
description = "Provides the memory listing display window. Functions similarly to " +
|
||||
"the main program listing display window, but for traces. If the trace is the " +
|
||||
"destination of a live recording, the view(s) retrieve live memory on demand.",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
eventsConsumed = {
|
||||
// ProgramSelectionPluginEvent.class, // TODO: Later or remove
|
||||
// ProgramHighlightPluginEvent.class, // TODO: Later or remove
|
||||
ProgramClosedPluginEvent.class, // For marker set cleanup
|
||||
ProgramLocationPluginEvent.class, // For static listing sync
|
||||
TraceActivatedPluginEvent.class, // Trace/thread activation and register tracking
|
||||
TraceClosedPluginEvent.class,
|
||||
},
|
||||
eventsProduced = {
|
||||
ProgramLocationPluginEvent.class,
|
||||
// ProgramSelectionPluginEvent.class,
|
||||
TraceLocationPluginEvent.class,
|
||||
TraceSelectionPluginEvent.class
|
||||
},
|
||||
servicesRequired = {
|
||||
DebuggerModelService.class, // For memory capture
|
||||
DebuggerStaticMappingService.class, // For static listing sync. TODO: Optional?
|
||||
DebuggerEmulationService.class, // TODO: Optional?
|
||||
ProgramManager.class, // For static listing sync
|
||||
//GoToService.class, // For static listing sync
|
||||
ClipboardService.class,
|
||||
MarkerService.class // TODO: Make optional?
|
||||
},
|
||||
servicesProvided = {
|
||||
DebuggerListingService.class,
|
||||
})
|
||||
public class DebuggerListingPlugin extends CodeBrowserPlugin implements DebuggerListingService {
|
||||
private static final String KEY_CONNECTED_PROVIDER = "connectedProvider";
|
||||
private static final String KEY_DISCONNECTED_COUNT = "disconnectedCount";
|
||||
@@ -118,8 +118,8 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||
|
||||
protected NewListingAction actionNewListing;
|
||||
|
||||
@AutoServiceConsumed
|
||||
private GoToService goToService;
|
||||
//@AutoServiceConsumed
|
||||
//private GoToService goToService;
|
||||
@AutoServiceConsumed
|
||||
private ProgramManager programManager;
|
||||
// NOTE: ListingPlugin doesn't extend AbstractDebuggerPlugin
|
||||
@@ -127,22 +127,22 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||
private AutoService.Wiring autoServiceWiring;
|
||||
|
||||
@AutoOptionDefined( //
|
||||
name = OPTION_NAME_COLORS_STALE_MEMORY, //
|
||||
description = "Color of memory addresses whose content is not known in the view's " +
|
||||
"snap", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
name = OPTION_NAME_COLORS_STALE_MEMORY, //
|
||||
description = "Color of memory addresses whose content is not known in the view's " +
|
||||
"snap", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
private Color staleMemoryColor = DEFAULT_COLOR_BACKGROUND_STALE;
|
||||
@AutoOptionDefined( //
|
||||
name = OPTION_NAME_COLORS_ERROR_MEMORY, //
|
||||
description = "Color of memory addresses whose content could not be read in the " +
|
||||
"view's snap", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
name = OPTION_NAME_COLORS_ERROR_MEMORY, //
|
||||
description = "Color of memory addresses whose content could not be read in the " +
|
||||
"view's snap", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
private Color errorMemoryColor = DEFAULT_COLOR_BACKGROUND_ERROR;
|
||||
// NOTE: Static programs are marked via markerSet. Dynamic are marked via custom color model
|
||||
@AutoOptionDefined( //
|
||||
name = OPTION_NAME_COLORS_REGISTER_MARKERS, //
|
||||
description = "Background color for locations referred to by a tracked register", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
name = OPTION_NAME_COLORS_REGISTER_MARKERS, //
|
||||
description = "Background color for locations referred to by a tracked register", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
private Color trackingColor = DEFAULT_COLOR_REGISTER_MARKERS;
|
||||
@SuppressWarnings("unused")
|
||||
private AutoOptions.Wiring autoOptionsWiring;
|
||||
|
||||
+20
-24
@@ -81,6 +81,7 @@ import ghidra.trace.model.Trace.*;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.modules.*;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.program.TraceVariableSnapProgramView;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
@@ -104,8 +105,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
if (!Objects.equals(a.getRecorder(), b.getRecorder())) {
|
||||
return false; // For capture memory action
|
||||
}
|
||||
if (!Objects.equals(a.getSnap(), b.getSnap())) {
|
||||
return false; // Subsumed by view, but I care that it's snap has changed
|
||||
if (!Objects.equals(a.getTime(), b.getTime())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getThread(), b.getThread())) {
|
||||
return false; // for reg/pc tracking
|
||||
@@ -113,7 +114,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
if (!Objects.equals(a.getFrame(), b.getFrame())) {
|
||||
return false; // for reg/pc tracking
|
||||
}
|
||||
// TODO: Ticks
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -129,14 +129,11 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
if (current.getView() == null) {
|
||||
if (!current.isAliveAndReadsPresent()) {
|
||||
return;
|
||||
}
|
||||
Trace trace = current.getTrace();
|
||||
TraceRecorder recorder = current.getRecorder();
|
||||
if (recorder == null) {
|
||||
return;
|
||||
}
|
||||
BackgroundUtils.async(plugin.getTool(), trace, NAME, true, true, false,
|
||||
(__, monitor) -> recorder
|
||||
.captureProcessMemory(getListingPanel().getProgramSelection(), monitor));
|
||||
@@ -144,13 +141,10 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
if (!current.isAliveAndReadsPresent()) {
|
||||
return false;
|
||||
}
|
||||
TraceRecorder recorder = current.getRecorder();
|
||||
if (recorder == null) {
|
||||
return false;
|
||||
}
|
||||
if (recorder.getSnap() != current.getSnap()) {
|
||||
return false;
|
||||
}
|
||||
ProgramSelection selection = getSelection();
|
||||
if (selection == null || selection.isEmpty()) {
|
||||
return false;
|
||||
@@ -334,7 +328,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
@AutoOptionConsumed(name = OPTION_NAME_COLORS_REGISTER_MARKERS)
|
||||
@AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS)
|
||||
private Color trackingColor;
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoOptions.Wiring autoOptionsWiring;
|
||||
@@ -757,7 +751,9 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
return;
|
||||
}
|
||||
// Avoid odd race conditions by fixing the snap
|
||||
TraceProgramView fixed = current.getTrace().getFixedProgramView(current.getSnap());
|
||||
TraceProgramView fixed = current.getView() instanceof TraceVariableSnapProgramView
|
||||
? current.getTrace().getFixedProgramView(current.getSnap())
|
||||
: current.getView();
|
||||
|
||||
ExporterDialog dialog =
|
||||
new ExporterDialog(tool, fixed.getDomainFile(), fixed, getSelection());
|
||||
@@ -1056,13 +1052,14 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
// Change of current snap (TODO)
|
||||
// Change of current frame (TODO)
|
||||
// Change of tracking settings
|
||||
DebuggerCoordinates cur = this.current;
|
||||
DebuggerCoordinates cur = current;
|
||||
TraceThread thread = cur.getThread();
|
||||
if (thread == null || trackingSpec == null) {
|
||||
return null;
|
||||
}
|
||||
Address address = trackingSpec.computeTraceAddress(cur);
|
||||
return address == null ? null : new ProgramLocation(cur.getView(), address);
|
||||
// NB: view's snap may be forked for emulation
|
||||
Address address = trackingSpec.computeTraceAddress(cur, current.getView().getSnap());
|
||||
return address == null ? null : new ProgramLocation(current.getView(), address);
|
||||
}
|
||||
|
||||
protected ProgramLocation doMarkTrackedLocation() {
|
||||
@@ -1082,16 +1079,16 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
if (loc == null) {
|
||||
return;
|
||||
}
|
||||
DebuggerCoordinates cur = current;
|
||||
TraceProgramView curView = current.getView();
|
||||
if (!syncToStaticListing || trackedStatic == null) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
goTo(cur.getView(), loc);
|
||||
goTo(curView, loc);
|
||||
doAutoImportCurrentModule();
|
||||
});
|
||||
}
|
||||
else {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
goTo(cur.getView(), loc);
|
||||
goTo(curView, loc);
|
||||
doAutoImportCurrentModule();
|
||||
plugin.fireStaticLocationEvent(trackedStatic);
|
||||
});
|
||||
@@ -1118,7 +1115,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
}
|
||||
|
||||
public void staticProgramLocationChanged(ProgramLocation location) {
|
||||
TraceProgramView view = current.getView();
|
||||
TraceProgramView view = current.getView(); // NB. Used for snap (don't want emuSnap)
|
||||
if (!isSyncToStaticListing() || view == null || location == null) {
|
||||
return;
|
||||
}
|
||||
@@ -1134,10 +1131,9 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||
return coordinates;
|
||||
}
|
||||
// Because the view's snap is changing with or without us.... So go with.
|
||||
return current.withSnap(coordinates.getSnap());
|
||||
return current.withTime(coordinates.getTime());
|
||||
}
|
||||
|
||||
// TODO: Figure out clone action
|
||||
public void goToCoordinates(DebuggerCoordinates coordinates) {
|
||||
if (sameCoordinates(current, coordinates)) {
|
||||
current = coordinates;
|
||||
|
||||
+21
-9
@@ -27,6 +27,7 @@ import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
@@ -90,7 +91,7 @@ public interface DebuggerListingTrackLocationAction extends TrackLocationAction
|
||||
*/
|
||||
String computeTitle(DebuggerCoordinates coordinates);
|
||||
|
||||
Address computeTraceAddress(DebuggerCoordinates coordinates);
|
||||
Address computeTraceAddress(DebuggerCoordinates coordinates, long emuSnap);
|
||||
|
||||
// TODO: Is there a way to generalize these so that other dependencies need not
|
||||
// have their own bespoke methods?
|
||||
@@ -115,7 +116,7 @@ public interface DebuggerListingTrackLocationAction extends TrackLocationAction
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address computeTraceAddress(DebuggerCoordinates coordinates) {
|
||||
public Address computeTraceAddress(DebuggerCoordinates coordinates, long emuSnap) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -147,7 +148,7 @@ public interface DebuggerListingTrackLocationAction extends TrackLocationAction
|
||||
}
|
||||
|
||||
@Override
|
||||
default Address computeTraceAddress(DebuggerCoordinates coordinates) {
|
||||
default Address computeTraceAddress(DebuggerCoordinates coordinates, long emuSnap) {
|
||||
Trace trace = coordinates.getTrace();
|
||||
TraceThread thread = coordinates.getThread();
|
||||
long snap = coordinates.getSnap();
|
||||
@@ -164,7 +165,13 @@ public interface DebuggerListingTrackLocationAction extends TrackLocationAction
|
||||
if (regs == null) {
|
||||
return null;
|
||||
}
|
||||
RegisterValue value = regs.getValue(snap, reg);
|
||||
RegisterValue value;
|
||||
if (regs.getState(emuSnap, reg) == TraceMemoryState.KNOWN) {
|
||||
value = regs.getValue(emuSnap, reg);
|
||||
}
|
||||
else {
|
||||
value = regs.getValue(snap, reg);
|
||||
}
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -230,12 +237,14 @@ public interface DebuggerListingTrackLocationAction extends TrackLocationAction
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address computeTraceAddress(DebuggerCoordinates coordinates) {
|
||||
Address pc = computePCViaStack(coordinates);
|
||||
if (pc != null) {
|
||||
return pc;
|
||||
public Address computeTraceAddress(DebuggerCoordinates coordinates, long emuSnap) {
|
||||
if (coordinates.getTime().isSnapOnly()) {
|
||||
Address pc = computePCViaStack(coordinates);
|
||||
if (pc != null) {
|
||||
return pc;
|
||||
}
|
||||
}
|
||||
return RegisterLocationTrackingSpec.super.computeTraceAddress(coordinates);
|
||||
return RegisterLocationTrackingSpec.super.computeTraceAddress(coordinates, emuSnap);
|
||||
}
|
||||
|
||||
// Note it does no good to override affectByRegChange. It must do what we'd avoid anyway.
|
||||
@@ -244,6 +253,9 @@ public interface DebuggerListingTrackLocationAction extends TrackLocationAction
|
||||
if (stack.getThread() != coordinates.getThread()) {
|
||||
return false;
|
||||
}
|
||||
if (!coordinates.getTime().isSnapOnly()) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Would be nice to have stack lifespan...
|
||||
TraceStack curStack = coordinates.getTrace()
|
||||
.getStackManager()
|
||||
|
||||
+4
-4
@@ -63,11 +63,11 @@ public class MemoryStateListingBackgroundColorModel implements ListingBackground
|
||||
return defaultBackgroundColor;
|
||||
}
|
||||
|
||||
TraceMemoryState state = memory.getState(view.getSnap(), address);
|
||||
Entry<Long, TraceMemoryState> state = memory.getViewState(view.getSnap(), address);
|
||||
if (state == null) {
|
||||
return defaultBackgroundColor;
|
||||
}
|
||||
switch (state) {
|
||||
switch (state.getValue()) {
|
||||
case UNKNOWN:
|
||||
return getUnknownColor(address);
|
||||
case ERROR:
|
||||
@@ -84,11 +84,11 @@ public class MemoryStateListingBackgroundColorModel implements ListingBackground
|
||||
|
||||
protected Color getUnknownColor(Address address) {
|
||||
Entry<TraceAddressSnapRange, TraceMemoryState> ent =
|
||||
memory.getMostRecentStateEntry(view.getSnap(), address);
|
||||
memory.getViewMostRecentStateEntry(view.getSnap(), address);
|
||||
if (ent == null || ent.getValue() != TraceMemoryState.KNOWN) {
|
||||
return unknownColor;
|
||||
}
|
||||
TraceMemoryRegion region = memory.getRegionContaining(view.getSnap(), address);
|
||||
TraceMemoryRegion region = memory.getRegionContaining(ent.getKey().getY1(), address);
|
||||
if (region != null && !region.isWrite()) {
|
||||
return unknownBlendedColor;
|
||||
}
|
||||
|
||||
-3
@@ -490,9 +490,6 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Display modules in timeline?
|
||||
// TODO: Select module/section containing current address?
|
||||
|
||||
private final DebuggerModulesPlugin plugin;
|
||||
|
||||
// @AutoServiceConsumed via method
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/* ###
|
||||
* 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.pcode;
|
||||
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
|
||||
public class BranchPcodeRow implements PcodeRow {
|
||||
private final int sequence;
|
||||
private final int fromSeq;
|
||||
|
||||
public BranchPcodeRow(int sequence, int fromSeq) {
|
||||
this.sequence = sequence;
|
||||
this.fromSeq = fromSeq;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "(branched from " + fromSeq + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeOp getOp() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/* ###
|
||||
* 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.pcode;
|
||||
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.services.DebuggerEmulationService;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger p-code stepper",
|
||||
description = "GUI to single-step emulation of p-code",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.UNSTABLE,
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class,
|
||||
},
|
||||
servicesRequired = {
|
||||
DebuggerTraceManagerService.class,
|
||||
DebuggerEmulationService.class,
|
||||
})
|
||||
public class DebuggerPcodeStepperPlugin extends AbstractDebuggerPlugin {
|
||||
protected DebuggerPcodeStepperProvider provider;
|
||||
|
||||
public DebuggerPcodeStepperPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
provider = new DebuggerPcodeStepperProvider(this);
|
||||
super.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
}
|
||||
+679
File diff suppressed because it is too large
Load Diff
+43
@@ -0,0 +1,43 @@
|
||||
/* ###
|
||||
* 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.pcode;
|
||||
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
|
||||
public enum EnumPcodeRow implements PcodeRow {
|
||||
NO_THREAD("no thread selected"), DECODE("decode instruction");
|
||||
|
||||
private final String message;
|
||||
|
||||
private EnumPcodeRow(String message) {
|
||||
this.message = "(" + message + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSequence() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeOp getOp() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+41
@@ -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.gui.pcode;
|
||||
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
|
||||
public class FallthroughPcodeRow implements PcodeRow {
|
||||
private final int sequence;
|
||||
|
||||
public FallthroughPcodeRow(int sequence) {
|
||||
this.sequence = sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "(fall-through)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeOp getOp() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/* ###
|
||||
* 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.pcode;
|
||||
|
||||
import ghidra.pcode.exec.PcodeProgram;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
|
||||
public class OpPcodeRow implements PcodeRow {
|
||||
protected final Language language;
|
||||
protected final PcodeOp op;
|
||||
protected final boolean isNext;
|
||||
|
||||
public OpPcodeRow(Language language, PcodeOp op, boolean isNext) {
|
||||
this.language = language;
|
||||
this.op = op;
|
||||
this.isNext = isNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSequence() {
|
||||
return op.getSeqnum().getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "<html>" + PcodeProgram.opToString(language, op, true) + "</html>";
|
||||
}
|
||||
|
||||
public boolean isNext() {
|
||||
return isNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PcodeOp getOp() {
|
||||
return op;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/* ###
|
||||
* 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.pcode;
|
||||
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
|
||||
public interface PcodeRow {
|
||||
int getSequence();
|
||||
|
||||
String getCode();
|
||||
|
||||
PcodeOp getOp();
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/* ###
|
||||
* 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.pcode;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import ghidra.docking.settings.SettingsImpl;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.pcode.exec.PcodeProgram;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.mem.ByteMemBufferImpl;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
import ghidra.util.NumericUtilities;
|
||||
|
||||
public class UniqueRow {
|
||||
public enum RefType {
|
||||
NONE, READ, WRITE, READ_WRITE;
|
||||
|
||||
static RefType fromRW(boolean isRead, boolean isWrite) {
|
||||
if (isRead) {
|
||||
if (isWrite) {
|
||||
return READ_WRITE;
|
||||
}
|
||||
else {
|
||||
return READ;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isWrite) {
|
||||
return WRITE;
|
||||
}
|
||||
else {
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final DebuggerPcodeStepperProvider provider;
|
||||
protected final Language language;
|
||||
protected final PcodeExecutorState<byte[]> state;
|
||||
protected final Varnode vn;
|
||||
|
||||
protected DataType dataType;
|
||||
|
||||
public UniqueRow(DebuggerPcodeStepperProvider provider, Language language,
|
||||
PcodeExecutorState<byte[]> state, Varnode vn) {
|
||||
if (!vn.isUnique()) {
|
||||
throw new AssertionError("Only uniques allowed in unique table");
|
||||
}
|
||||
this.provider = provider;
|
||||
this.language = language;
|
||||
this.state = state;
|
||||
this.vn = vn;
|
||||
}
|
||||
|
||||
protected static AddressRange rangeOf(Varnode vn) {
|
||||
try {
|
||||
return new AddressRangeImpl(vn.getAddress(), vn.getSize());
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean overlap(Varnode vn1, Varnode vn2) {
|
||||
return rangeOf(vn1).intersects(rangeOf(vn2));
|
||||
}
|
||||
|
||||
public RefType getRefType() {
|
||||
int index = provider.pcodeTable.getSelectedRow();
|
||||
if (index == -1) {
|
||||
return RefType.NONE;
|
||||
}
|
||||
PcodeRow row = provider.pcodeTableModel.getRowObject(index);
|
||||
PcodeOp op = row.getOp();
|
||||
if (op == null) {
|
||||
return RefType.NONE;
|
||||
}
|
||||
boolean isRead = Stream.of(op.getInputs()).anyMatch(in -> overlap(in, vn));
|
||||
Varnode out = op.getOutput();
|
||||
boolean isWrite = out != null && overlap(out, vn);
|
||||
return RefType.fromRW(isRead, isWrite);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return PcodeProgram.uniqueToString(vn, false);
|
||||
}
|
||||
|
||||
public String getBytes() {
|
||||
// TODO: Could keep value cached?
|
||||
byte[] bytes = state.getVar(vn);
|
||||
if (bytes == null) {
|
||||
return "??";
|
||||
}
|
||||
if (bytes.length > 20) {
|
||||
return NumericUtilities.convertBytesToString(bytes, 0, 20, " ") + " ...";
|
||||
}
|
||||
return NumericUtilities.convertBytesToString(bytes, " ");
|
||||
}
|
||||
|
||||
public BigInteger getValue() {
|
||||
byte[] bytes = state.getVar(vn);
|
||||
return Utils.bytesToBigInteger(bytes, bytes.length, language.isBigEndian(), false);
|
||||
}
|
||||
|
||||
public DataType getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public void setDataType(DataType dataType) {
|
||||
this.dataType = dataType;
|
||||
}
|
||||
|
||||
public String getValueRepresentation() {
|
||||
// TODO: Could compute this upon setting data type?
|
||||
if (dataType == null) {
|
||||
return "";
|
||||
}
|
||||
byte[] bytes = state.getVar(vn);
|
||||
if (bytes == null) {
|
||||
return "??";
|
||||
}
|
||||
MemBuffer buffer = new ByteMemBufferImpl(vn.getAddress(), bytes, language.isBigEndian());
|
||||
return dataType.getRepresentation(buffer, SettingsImpl.NO_SETTINGS, bytes.length);
|
||||
}
|
||||
}
|
||||
+16
-16
@@ -35,22 +35,22 @@ import ghidra.program.util.DefaultLanguageService;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@PluginInfo( //
|
||||
shortDescription = "Debugger registers manager", //
|
||||
description = "GUI to view and modify register values", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = { TraceActivatedPluginEvent.class, //
|
||||
TraceClosedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerModelService.class, //
|
||||
DebuggerTraceManagerService.class, //
|
||||
MarkerService.class, // TODO
|
||||
DataTypeManagerService.class, // For DataType selection field
|
||||
} //
|
||||
)
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger registers manager",
|
||||
description = "GUI to view and modify register values",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
eventsConsumed = {
|
||||
TraceActivatedPluginEvent.class,
|
||||
TraceClosedPluginEvent.class,
|
||||
},
|
||||
servicesRequired = {
|
||||
DebuggerModelService.class,
|
||||
DebuggerTraceManagerService.class,
|
||||
MarkerService.class, // TODO
|
||||
DataTypeManagerService.class, // For DataType selection field
|
||||
})
|
||||
public class DebuggerRegistersPlugin extends AbstractDebuggerPlugin {
|
||||
private static final String KEY_SELECTION_BY_CSPEC = "selectionByCSpec";
|
||||
private static final String KEY_FAVORITES_BY_CSPEC = "favoritesByCSpec";
|
||||
|
||||
+49
-36
@@ -70,9 +70,9 @@ import ghidra.trace.model.Trace.*;
|
||||
import ghidra.trace.model.listing.*;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.trace.util.TraceRegisterUtils;
|
||||
import ghidra.trace.util.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
|
||||
@@ -171,13 +171,12 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
if (!Objects.equals(a.getThread(), b.getThread())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getSnap(), b.getSnap())) {
|
||||
if (!Objects.equals(a.getTime(), b.getTime())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getFrame(), b.getFrame())) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Ticks
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -185,6 +184,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
public TraceChangeListener() {
|
||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored(e));
|
||||
listenFor(TraceMemoryBytesChangeType.CHANGED, this::registerValueChanged);
|
||||
listenFor(TraceMemoryStateChangeType.CHANGED, this::registerStateChanged);
|
||||
listenFor(TraceCodeChangeType.ADDED, this::registerTypeAdded);
|
||||
listenFor(TraceCodeChangeType.DATA_TYPE_REPLACED, this::registerTypeReplaced);
|
||||
listenFor(TraceCodeChangeType.LIFESPAN_CHANGED, this::registerTypeLifespanChanged);
|
||||
@@ -211,9 +211,11 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
if (!isVisible(space)) {
|
||||
return false;
|
||||
}
|
||||
if (!range.getLifespan().contains(current.getSnap())) {
|
||||
TraceProgramView view = current.getView();
|
||||
if (view == null || !view.getViewport().containsAnyUpper(range.getLifespan())) {
|
||||
return false;
|
||||
}
|
||||
// Probably not worth checking for occlusion here. Just a little refresh waste.
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -238,13 +240,21 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
refreshRange(range.getRange());
|
||||
}
|
||||
|
||||
private void registerStateChanged(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
TraceMemoryState oldState, TraceMemoryState newState) {
|
||||
if (!isVisible(space, range)) {
|
||||
return;
|
||||
}
|
||||
recomputeViewKnown();
|
||||
refreshRange(range.getRange());
|
||||
}
|
||||
|
||||
private void registerTypeAdded(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
TraceCodeUnit oldIsNull, TraceCodeUnit newUnit) {
|
||||
if (!isVisible(space, range)) {
|
||||
return;
|
||||
}
|
||||
refreshRange(range.getRange());
|
||||
|
||||
}
|
||||
|
||||
private void registerTypeReplaced(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
@@ -260,10 +270,15 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
if (!isVisible(space)) {
|
||||
return;
|
||||
}
|
||||
long snap = current.getSnap();
|
||||
if (oldSpan.contains(snap) == newSpan.contains(snap)) {
|
||||
TraceProgramView view = current.getView();
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
TraceTimeViewport viewport = view.getViewport();
|
||||
if (viewport.containsAnyUpper(oldSpan) == viewport.containsAnyUpper(newSpan)) {
|
||||
return;
|
||||
}
|
||||
// A little waste if occluded, but probably cheaper than checking.
|
||||
AddressRange range = new AddressRangeImpl(unit.getMinAddress(), unit.getMaxAddress());
|
||||
refreshRange(range); // Slightly wasteful, as we already have the data unit
|
||||
}
|
||||
@@ -418,6 +433,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
DockingAction actionClearDataType;
|
||||
|
||||
DebuggerRegisterActionContext myActionContext;
|
||||
AddressSetView viewKnown;
|
||||
|
||||
protected DebuggerRegistersProvider(final DebuggerRegistersPlugin plugin,
|
||||
Map<CompilerSpec, LinkedHashSet<Register>> selectionByCSpec,
|
||||
@@ -710,6 +726,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
doSetRecorder(current.getRecorder());
|
||||
updateSubTitle();
|
||||
|
||||
recomputeViewKnown();
|
||||
loadRegistersAndValues();
|
||||
contextChanged();
|
||||
//checkEditsEnabled();
|
||||
@@ -725,16 +742,10 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
}
|
||||
|
||||
boolean canWriteTarget() {
|
||||
if (!current.isAliveAndPresent()) {
|
||||
return false;
|
||||
}
|
||||
TraceRecorder recorder = current.getRecorder();
|
||||
if (recorder == null) {
|
||||
return false;
|
||||
}
|
||||
if (!recorder.isRecording()) {
|
||||
return false;
|
||||
}
|
||||
if (recorder.getSnap() != current.getSnap()) {
|
||||
return false;
|
||||
}
|
||||
TargetRegisterBank targetRegs =
|
||||
recorder.getTargetRegisterBank(current.getThread(), current.getFrame());
|
||||
if (targetRegs == null) {
|
||||
@@ -760,8 +771,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
if (regs == null) {
|
||||
return BigInteger.ZERO;
|
||||
}
|
||||
long snap = current.getSnap();
|
||||
return regs.getValue(snap, register).getUnsignedValue();
|
||||
return regs.getViewValue(current.getViewSnap(), register).getUnsignedValue();
|
||||
}
|
||||
|
||||
void writeRegisterValue(Register register, BigInteger value) {
|
||||
@@ -840,15 +850,24 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
return TraceRegisterUtils.getValueRepresentationHackPointer(data);
|
||||
}
|
||||
|
||||
boolean isRegisterKnown(Register register) {
|
||||
void recomputeViewKnown() {
|
||||
TraceMemoryRegisterSpace regs = getRegisterMemorySpace(false);
|
||||
if (regs == null) {
|
||||
TraceProgramView view = current.getView();
|
||||
if (regs == null || view == null) {
|
||||
viewKnown = null;
|
||||
return;
|
||||
}
|
||||
viewKnown = new AddressSet(view.getViewport()
|
||||
.unionedAddresses(snap -> regs.getAddressesWithState(snap,
|
||||
state -> state == TraceMemoryState.KNOWN)));
|
||||
}
|
||||
|
||||
boolean isRegisterKnown(Register register) {
|
||||
if (viewKnown == null) {
|
||||
return false;
|
||||
}
|
||||
AddressRange range = TraceRegisterUtils.rangeForRegister(register);
|
||||
return regs
|
||||
.getAddressesWithState(current.getSnap(), state -> state == TraceMemoryState.KNOWN)
|
||||
.contains(range.getMinAddress(), range.getMaxAddress());
|
||||
return viewKnown.contains(range.getMinAddress(), range.getMaxAddress());
|
||||
}
|
||||
|
||||
boolean isRegisterChanged(Register register) {
|
||||
@@ -861,20 +880,14 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||
if (!isRegisterKnown(register)) {
|
||||
return false;
|
||||
}
|
||||
TraceMemoryRegisterSpace curVals = getRegisterMemorySpace(current, false);
|
||||
TraceMemoryRegisterSpace prevVals = getRegisterMemorySpace(previous, false);
|
||||
if (prevVals == null) {
|
||||
TraceMemoryRegisterSpace curSpace = getRegisterMemorySpace(current, false);
|
||||
TraceMemoryRegisterSpace prevSpace = getRegisterMemorySpace(previous, false);
|
||||
if (prevSpace == null) {
|
||||
return false;
|
||||
}
|
||||
// is register known at previous? // Do I care?
|
||||
/*AddressRange range = TraceRegisterUtils.rangeForRegister(register);
|
||||
if (!prevVals
|
||||
.getAddressesWithState(previous.getSnap(), state -> state == TraceMemoryState.KNOWN)
|
||||
.contains(range.getMinAddress(), range.getMaxAddress())) {
|
||||
return false;
|
||||
}*/
|
||||
return !Objects.equals(curVals.getValue(current.getSnap(), register),
|
||||
prevVals.getValue(previous.getSnap(), register));
|
||||
RegisterValue curRegVal = curSpace.getViewValue(current.getViewSnap(), register);
|
||||
RegisterValue prevRegVal = prevSpace.getViewValue(previous.getViewSnap(), register);
|
||||
return !Objects.equals(curRegVal, prevRegVal);
|
||||
}
|
||||
|
||||
private boolean computeEditsEnabled() {
|
||||
|
||||
+43
-7
@@ -41,13 +41,15 @@ import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
|
||||
import ghidra.trace.model.Trace.TraceStackChangeType;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.trace.util.TraceRegisterUtils;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
@@ -129,13 +131,12 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||
if (!Objects.equals(a.getThread(), b.getThread())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getSnap(), b.getSnap())) {
|
||||
if (!Objects.equals(a.getTime(), b.getTime())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getFrame(), b.getFrame())) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Ticks
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -144,9 +145,14 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||
listenFor(TraceStackChangeType.ADDED, this::stackAdded);
|
||||
listenFor(TraceStackChangeType.CHANGED, this::stackChanged);
|
||||
listenFor(TraceStackChangeType.DELETED, this::stackDeleted);
|
||||
|
||||
listenFor(TraceMemoryBytesChangeType.CHANGED, this::bytesChanged);
|
||||
}
|
||||
|
||||
private void stackAdded(TraceStack stack) {
|
||||
if (stack.getSnap() != current.getViewSnap()) {
|
||||
return;
|
||||
}
|
||||
TraceThread curThread = current.getThread();
|
||||
if (curThread != stack.getThread()) {
|
||||
return;
|
||||
@@ -167,6 +173,35 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
loadStack();
|
||||
}
|
||||
|
||||
// For updating a synthetic frame
|
||||
private void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range) {
|
||||
TraceThread curThread = current.getThread();
|
||||
if (space.getThread() != curThread || space.getFrameLevel() != 0) {
|
||||
return;
|
||||
}
|
||||
if (!current.getView().getViewport().containsAnyUpper(range.getLifespan())) {
|
||||
return;
|
||||
}
|
||||
List<StackFrameRow> stackData = stackTableModel.getModelData();
|
||||
if (stackData.isEmpty() || !(stackData.get(0) instanceof StackFrameRow.Synthetic)) {
|
||||
return;
|
||||
}
|
||||
StackFrameRow.Synthetic frameRow = (StackFrameRow.Synthetic) stackData.get(0);
|
||||
Trace trace = current.getTrace();
|
||||
Register pc = trace.getBaseLanguage().getProgramCounter();
|
||||
if (!TraceRegisterUtils.rangeForRegister(pc).intersects(range.getRange())) {
|
||||
return;
|
||||
}
|
||||
TraceMemoryRegisterSpace regs =
|
||||
trace.getMemoryManager().getMemoryRegisterSpace(curThread, false);
|
||||
RegisterValue value = regs.getViewValue(current.getViewSnap(), pc);
|
||||
Address address = trace.getBaseLanguage()
|
||||
.getDefaultSpace()
|
||||
.getAddress(value.getUnsignedValue().longValue());
|
||||
frameRow.updateProgramCounter(address);
|
||||
stackTableModel.fireTableDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
class ForFunctionsListener implements DebuggerStaticMappingChangeListener {
|
||||
@@ -367,7 +402,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||
return;
|
||||
}
|
||||
Register pc = curTrace.getBaseLanguage().getProgramCounter();
|
||||
RegisterValue value = regs.getValue(current.getSnap(), pc);
|
||||
RegisterValue value = regs.getViewValue(current.getViewSnap(), pc);
|
||||
if (value == null) {
|
||||
contextChanged();
|
||||
return;
|
||||
@@ -375,7 +410,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||
Address address = curTrace.getBaseLanguage()
|
||||
.getDefaultSpace()
|
||||
.getAddress(value.getUnsignedValue().longValue());
|
||||
stackTableModel.add(new StackFrameRow(this, address));
|
||||
stackTableModel.add(new StackFrameRow.Synthetic(this, address));
|
||||
}
|
||||
|
||||
protected void loadStack() {
|
||||
@@ -384,8 +419,9 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
||||
doSetCurrentStack(null);
|
||||
return;
|
||||
}
|
||||
// TODO: getLatestViewStack? Conventionally, I don't expect any scratch stacks, yet.
|
||||
TraceStack stack =
|
||||
current.getTrace().getStackManager().getLatestStack(curThread, current.getSnap());
|
||||
current.getTrace().getStackManager().getLatestStack(curThread, current.getViewSnap());
|
||||
if (stack == null) {
|
||||
doSetSyntheticStack();
|
||||
}
|
||||
|
||||
+12
-2
@@ -27,11 +27,21 @@ import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public class StackFrameRow {
|
||||
public static class Synthetic extends StackFrameRow {
|
||||
public Synthetic(DebuggerStackProvider provider, Address pc) {
|
||||
super(provider, pc);
|
||||
}
|
||||
|
||||
public void updateProgramCounter(Address pc) {
|
||||
this.pc = pc;
|
||||
}
|
||||
}
|
||||
|
||||
private final DebuggerStackProvider provider;
|
||||
|
||||
final TraceStackFrame frame;
|
||||
private int level;
|
||||
private Address pc;
|
||||
Address pc;
|
||||
|
||||
public StackFrameRow(DebuggerStackProvider provider, TraceStackFrame frame) {
|
||||
this.provider = provider;
|
||||
@@ -40,7 +50,7 @@ public class StackFrameRow {
|
||||
this.pc = frame.getProgramCounter();
|
||||
}
|
||||
|
||||
public StackFrameRow(DebuggerStackProvider provider, Address pc) {
|
||||
private StackFrameRow(DebuggerStackProvider provider, Address pc) {
|
||||
this.provider = provider;
|
||||
this.frame = null;
|
||||
this.level = 0;
|
||||
|
||||
+106
-17
@@ -49,6 +49,7 @@ import ghidra.trace.model.Trace.TraceThreadChangeType;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.datastruct.CollectionChangeListener;
|
||||
@@ -73,26 +74,30 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
if (!Objects.equals(a.getThread(), b.getThread())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getSnap(), b.getSnap())) {
|
||||
if (!Objects.equals(a.getTime(), b.getTime())) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Ticks
|
||||
return true;
|
||||
}
|
||||
|
||||
protected class StepTraceBackwardAction extends AbstractStepTraceBackwardAction {
|
||||
public static final String GROUP = DebuggerResources.GROUP_GENERAL;
|
||||
protected class StepSnapBackwardAction extends AbstractStepSnapBackwardAction {
|
||||
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
|
||||
|
||||
public StepTraceBackwardAction() {
|
||||
public StepSnapBackwardAction() {
|
||||
super(plugin);
|
||||
setToolBarData(new ToolBarData(ICON, GROUP));
|
||||
setToolBarData(new ToolBarData(ICON, GROUP, "1"));
|
||||
addLocalAction(this);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
traceManager.activateSnap(current.getSnap() - 1);
|
||||
if (current.getTime().isSnapOnly()) {
|
||||
traceManager.activateSnap(current.getSnap() - 1);
|
||||
}
|
||||
else {
|
||||
traceManager.activateSnap(current.getSnap());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,7 +105,9 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
if (current.getTrace() == null) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Can I track minSnap?
|
||||
if (!current.getTime().isSnapOnly()) {
|
||||
return true;
|
||||
}
|
||||
if (current.getSnap() <= 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -108,12 +115,80 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
protected class StepTraceForwardAction extends AbstractStepTraceForwardAction {
|
||||
public static final String GROUP = DebuggerResources.GROUP_GENERAL;
|
||||
protected class StepTickBackwardAction extends AbstractStepTickBackwardAction {
|
||||
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
|
||||
|
||||
public StepTraceForwardAction() {
|
||||
public StepTickBackwardAction() {
|
||||
super(plugin);
|
||||
setToolBarData(new ToolBarData(ICON, GROUP));
|
||||
setToolBarData(new ToolBarData(ICON, GROUP, "2"));
|
||||
addLocalAction(this);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
if (current.getTrace() == null) {
|
||||
return;
|
||||
}
|
||||
TraceSchedule time = current.getTime().steppedBackward(current.getTrace(), 1);
|
||||
if (time == null) {
|
||||
return;
|
||||
}
|
||||
traceManager.activateTime(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
if (emulationService == null) {
|
||||
return false;
|
||||
}
|
||||
if (current.getTrace() == null) {
|
||||
return false;
|
||||
}
|
||||
if (current.getTime().steppedBackward(current.getTrace(), 1) == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected class StepTickForwardAction extends AbstractStepTickForwardAction {
|
||||
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
|
||||
|
||||
public StepTickForwardAction() {
|
||||
super(plugin);
|
||||
setToolBarData(new ToolBarData(ICON, GROUP, "3"));
|
||||
addLocalAction(this);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
if (current.getThread() == null) {
|
||||
return;
|
||||
}
|
||||
TraceSchedule time = current.getTime().steppedForward(current.getThread(), 1);
|
||||
traceManager.activateTime(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
if (emulationService == null) {
|
||||
return false;
|
||||
}
|
||||
if (current.getThread() == null) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected class StepSnapForwardAction extends AbstractStepSnapForwardAction {
|
||||
public static final String GROUP = DebuggerResources.GROUP_CONTROL;
|
||||
|
||||
public StepSnapForwardAction() {
|
||||
super(plugin);
|
||||
setToolBarData(new ToolBarData(ICON, GROUP, "4"));
|
||||
addLocalAction(this);
|
||||
setEnabled(false);
|
||||
}
|
||||
@@ -139,7 +214,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
|
||||
protected class SeekTracePresentAction extends AbstractSeekTracePresentAction
|
||||
implements BooleanChangeAdapter {
|
||||
public static final String GROUP = DebuggerResources.GROUP_GENERAL;
|
||||
public static final String GROUP = "zz";
|
||||
|
||||
public SeekTracePresentAction() {
|
||||
super(plugin);
|
||||
@@ -241,6 +316,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
private DebuggerModelService modelService;
|
||||
@AutoServiceConsumed // NB, also by method
|
||||
private DebuggerTraceManagerService traceManager;
|
||||
@AutoServiceConsumed // NB, also by method
|
||||
private DebuggerEmulationService emulationService;
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoWiring;
|
||||
|
||||
@@ -269,8 +346,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
private ActionContext myActionContext;
|
||||
|
||||
DockingAction actionSaveTrace;
|
||||
StepTraceBackwardAction actionStepTraceBackward;
|
||||
StepTraceForwardAction actionStepTraceForward;
|
||||
StepSnapBackwardAction actionStepSnapBackward;
|
||||
StepTickBackwardAction actionStepTickBackward;
|
||||
StepTickForwardAction actionStepTickForward;
|
||||
StepSnapForwardAction actionStepSnapForward;
|
||||
SeekTracePresentAction actionSeekTracePresent;
|
||||
ToggleDockingAction actionSyncFocus;
|
||||
Set<Object> strongRefs = new HashSet<>(); // Eww
|
||||
@@ -323,6 +402,11 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
public void setEmulationService(DebuggerEmulationService emulationService) {
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
private void removeOldListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
@@ -394,6 +478,9 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
doSetTrace(current.getTrace());
|
||||
doSetThread(current.getThread());
|
||||
doSetSnap(current.getSnap());
|
||||
|
||||
setSubTitle(current.getTime().toString());
|
||||
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
@@ -568,8 +655,10 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
||||
|
||||
protected void createActions() {
|
||||
// TODO: Make other actions use builder?
|
||||
actionStepTraceBackward = new StepTraceBackwardAction();
|
||||
actionStepTraceForward = new StepTraceForwardAction();
|
||||
actionStepSnapBackward = new StepSnapBackwardAction();
|
||||
actionStepTickBackward = new StepTickBackwardAction();
|
||||
actionStepTickForward = new StepTickForwardAction();
|
||||
actionStepSnapForward = new StepSnapForwardAction();
|
||||
actionSeekTracePresent = new SeekTracePresentAction();
|
||||
actionSyncFocus = SynchronizeFocusAction.builder(plugin)
|
||||
.selected(traceManager != null && traceManager.isSynchronizeFocus())
|
||||
|
||||
+22
-11
@@ -20,21 +20,22 @@ import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
|
||||
@PluginInfo( //
|
||||
shortDescription = "Lists recorded snapshots in a trace", //
|
||||
description = "Provides the component which lists snapshots and allows navigation", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = { //
|
||||
TraceActivatedPluginEvent.class //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerTraceManagerService.class //
|
||||
} //
|
||||
shortDescription = "Lists recorded snapshots in a trace", //
|
||||
description = "Provides the component which lists snapshots and allows navigation", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = { //
|
||||
TraceActivatedPluginEvent.class //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
DebuggerTraceManagerService.class //
|
||||
} //
|
||||
)
|
||||
public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
||||
protected DebuggerTimeProvider provider;
|
||||
@@ -63,4 +64,14 @@ public class DebuggerTimePlugin extends AbstractDebuggerPlugin {
|
||||
provider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
provider.writeConfigState(saveState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readConfigState(SaveState saveState) {
|
||||
provider.readConfigState(saveState);
|
||||
}
|
||||
}
|
||||
|
||||
+96
-22
@@ -19,6 +19,8 @@ import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
@@ -31,16 +33,19 @@ import com.google.common.collect.Collections2;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
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.DebuggerSnapActionContext;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||
@@ -50,13 +55,15 @@ import ghidra.trace.model.time.TraceTimeManager;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
|
||||
public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
private static final AutoConfigState.ClassHandler<DebuggerTimeProvider> CONFIG_STATE_HANDLER =
|
||||
AutoConfigState.wireHandler(DebuggerTimeProvider.class, MethodHandles.lookup());
|
||||
|
||||
protected enum SnapshotTableColumns
|
||||
implements EnumeratedTableColumn<SnapshotTableColumns, SnapshotRow> {
|
||||
SNAP("Snap", Long.class, SnapshotRow::getSnap),
|
||||
TIMESTAMP("Timestamp", String.class, SnapshotRow::getTimeStamp), // TODO: Use Date type here
|
||||
EVENT_THREAD("Event Thread", String.class, SnapshotRow::getEventThreadName),
|
||||
TICKS("Ticks", Long.class, SnapshotRow::getTicks),
|
||||
SCHEDULE("Schedule", String.class, SnapshotRow::getSchedule),
|
||||
DESCRIPTION("Description", String.class, SnapshotRow::getDescription, SnapshotRow::setDescription);
|
||||
|
||||
private final String header;
|
||||
@@ -103,17 +110,6 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getSnap(), b.getSnap())) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Ticks?
|
||||
return true;
|
||||
}
|
||||
|
||||
private class SnapshotListener extends TraceDomainObjectListener {
|
||||
public SnapshotListener() {
|
||||
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
|
||||
@@ -128,6 +124,9 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
|
||||
private void snapAdded(TraceSnapshot snapshot) {
|
||||
if (snapshot.getKey() < 0 && hideScratch) {
|
||||
return;
|
||||
}
|
||||
SnapshotRow row = new SnapshotRow(current.getTrace(), snapshot);
|
||||
snapshotTableModel.add(row);
|
||||
if (current.getSnap() == snapshot.getKey()) {
|
||||
@@ -136,14 +135,30 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
|
||||
private void snapChanged(TraceSnapshot snapshot) {
|
||||
if (snapshot.getKey() < 0 && hideScratch) {
|
||||
return;
|
||||
}
|
||||
snapshotTableModel.notifyUpdatedWith(row -> row.getSnapshot() == snapshot);
|
||||
}
|
||||
|
||||
private void snapDeleted(TraceSnapshot snapshot) {
|
||||
if (snapshot.getKey() < 0 && hideScratch) {
|
||||
return;
|
||||
}
|
||||
snapshotTableModel.deleteWith(row -> row.getSnapshot() == snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||
if (!Objects.equals(a.getTrace(), b.getTrace())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getTime(), b.getTime())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected final DebuggerTimePlugin plugin;
|
||||
|
||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
@@ -163,7 +178,12 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
/* testing */ GTable snapshotTable;
|
||||
/* testing */ GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
|
||||
|
||||
private DebuggerSnapActionContext currentCtx;
|
||||
private DebuggerSnapActionContext myActionContext;
|
||||
|
||||
ToggleDockingAction actionHideScratch;
|
||||
|
||||
@AutoConfigStateField
|
||||
/* testing */ boolean hideScratch = true;
|
||||
|
||||
public DebuggerTimeProvider(DebuggerTimePlugin plugin) {
|
||||
super(plugin.getTool(), TITLE_PROVIDER_TIME, plugin.getName());
|
||||
@@ -177,6 +197,11 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||
|
||||
buildMainPanel();
|
||||
|
||||
myActionContext = new DebuggerSnapActionContext(0);
|
||||
createActions();
|
||||
contextChanged();
|
||||
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@@ -192,7 +217,10 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
return currentCtx;
|
||||
if (myActionContext == null) {
|
||||
return super.getActionContext(event);
|
||||
}
|
||||
return myActionContext;
|
||||
}
|
||||
|
||||
protected void buildMainPanel() {
|
||||
@@ -209,15 +237,16 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
SnapshotRow row = snapshotFilterPanel.getSelectedItem();
|
||||
if (row == null) {
|
||||
currentCtx = null;
|
||||
myActionContext = null;
|
||||
return;
|
||||
}
|
||||
long snap = row.getSnap();
|
||||
if (snap == current.getSnap().longValue()) {
|
||||
return;
|
||||
}
|
||||
currentCtx = new DebuggerSnapActionContext(snap);
|
||||
myActionContext = new DebuggerSnapActionContext(snap);
|
||||
viewManager.activateSnap(snap);
|
||||
contextChanged();
|
||||
});
|
||||
|
||||
TableColumnModel columnModel = snapshotTable.getColumnModel();
|
||||
@@ -227,12 +256,29 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
timeCol.setPreferredWidth(200);
|
||||
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
|
||||
etCol.setPreferredWidth(40);
|
||||
TableColumn ticksCol = columnModel.getColumn(SnapshotTableColumns.TICKS.ordinal());
|
||||
ticksCol.setPreferredWidth(60);
|
||||
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
|
||||
schdCol.setPreferredWidth(60);
|
||||
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
|
||||
descCol.setPreferredWidth(200);
|
||||
}
|
||||
|
||||
protected void createActions() {
|
||||
actionHideScratch = DebuggerResources.HideScratchSnapshotsAction.builder(plugin)
|
||||
.selected(hideScratch)
|
||||
.onAction(this::activatedHideScratch)
|
||||
.buildAndInstallLocal(this);
|
||||
}
|
||||
|
||||
private void activatedHideScratch(ActionContext ctx) {
|
||||
hideScratch = !hideScratch;
|
||||
if (hideScratch) {
|
||||
deleteScratchSnapshots();
|
||||
}
|
||||
else {
|
||||
loadScratchSnapshots();
|
||||
}
|
||||
}
|
||||
|
||||
private void addNewListeners() {
|
||||
if (currentTrace == null) {
|
||||
return;
|
||||
@@ -290,7 +336,35 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
||||
return;
|
||||
}
|
||||
TraceTimeManager manager = curTrace.getTimeManager();
|
||||
snapshotTableModel.addAll(
|
||||
Collections2.transform(manager.getAllSnapshots(), s -> new SnapshotRow(curTrace, s)));
|
||||
Collection<? extends TraceSnapshot> snapshots = hideScratch
|
||||
? manager.getSnapshots(0, true, Long.MAX_VALUE, true)
|
||||
: manager.getAllSnapshots();
|
||||
snapshotTableModel.addAll(Collections2.transform(snapshots,
|
||||
s -> new SnapshotRow(curTrace, s)));
|
||||
}
|
||||
|
||||
protected void deleteScratchSnapshots() {
|
||||
snapshotTableModel.deleteWith(s -> s.getSnap() < 0);
|
||||
}
|
||||
|
||||
protected void loadScratchSnapshots() {
|
||||
Trace curTrace = current.getTrace();
|
||||
if (curTrace == null) {
|
||||
return;
|
||||
}
|
||||
TraceTimeManager manager = curTrace.getTimeManager();
|
||||
snapshotTableModel.addAll(Collections2.transform(
|
||||
manager.getSnapshots(Long.MIN_VALUE, true, 0, false),
|
||||
s -> new SnapshotRow(curTrace, s)));
|
||||
}
|
||||
|
||||
public void writeConfigState(SaveState saveState) {
|
||||
CONFIG_STATE_HANDLER.writeConfigState(this, saveState);
|
||||
}
|
||||
|
||||
public void readConfigState(SaveState saveState) {
|
||||
CONFIG_STATE_HANDLER.readConfigState(this, saveState);
|
||||
|
||||
actionHideScratch.setSelected(hideScratch);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -51,8 +51,8 @@ public class SnapshotRow {
|
||||
return thread == null ? "" : thread.getName();
|
||||
}
|
||||
|
||||
public long getTicks() {
|
||||
return snapshot.getTicks();
|
||||
public String getSchedule() {
|
||||
return snapshot.getScheduleString();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
||||
+18
-15
@@ -138,7 +138,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||
if (!Objects.equals(a.getRecorder(), b.getRecorder())) {
|
||||
return false; // May need to read target
|
||||
}
|
||||
if (!Objects.equals(a.getSnap(), b.getSnap())) {
|
||||
if (!Objects.equals(a.getTime(), b.getTime())) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(a.getThread(), b.getThread())) {
|
||||
@@ -147,7 +147,6 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||
if (!Objects.equals(a.getFrame(), b.getFrame())) {
|
||||
return false;
|
||||
}
|
||||
// TODO: Ticks
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -236,21 +235,25 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
@AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_WATCH_STALE, //
|
||||
description = "Text color for watches whose value is not known", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
@AutoOptionDefined(
|
||||
name = DebuggerResources.OPTION_NAME_COLORS_WATCH_STALE, //
|
||||
description = "Text color for watches whose value is not known", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
protected Color watchStaleColor = DebuggerResources.DEFAULT_COLOR_WATCH_STALE;
|
||||
@AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_WATCH_STALE_SEL, //
|
||||
description = "Selected text color for watches whose value is not known", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
@AutoOptionDefined(
|
||||
name = DebuggerResources.OPTION_NAME_COLORS_WATCH_STALE_SEL, //
|
||||
description = "Selected text color for watches whose value is not known", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
protected Color watchStaleSelColor = DebuggerResources.DEFAULT_COLOR_WATCH_STALE_SEL;
|
||||
@AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_WATCH_CHANGED, //
|
||||
description = "Text color for watches whose value just changed", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
@AutoOptionDefined(
|
||||
name = DebuggerResources.OPTION_NAME_COLORS_WATCH_CHANGED, //
|
||||
description = "Text color for watches whose value just changed", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
protected Color watchChangesColor = DebuggerResources.DEFAULT_COLOR_WATCH_CHANGED;
|
||||
@AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_WATCH_CHANGED_SEL, //
|
||||
description = "Selected text color for watches whose value just changed", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
@AutoOptionDefined(
|
||||
name = DebuggerResources.OPTION_NAME_COLORS_WATCH_CHANGED_SEL, //
|
||||
description = "Selected text color for watches whose value just changed", //
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
protected Color watchChangesSelColor = DebuggerResources.DEFAULT_COLOR_WATCH_CHANGED_SEL;
|
||||
|
||||
private final AddressSet changed = new AddressSet();
|
||||
@@ -554,7 +557,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||
|
||||
setRowsContext(coordinates);
|
||||
|
||||
if (current.isAliveAndPresent()) {
|
||||
if (current.isAliveAndReadsPresent()) {
|
||||
readTarget();
|
||||
}
|
||||
reevaluate();
|
||||
|
||||
+5
-5
@@ -189,10 +189,10 @@ public class WatchRow {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(SleighProgram program,
|
||||
public PcodeFrame execute(PcodeProgram program,
|
||||
SleighUseropLibrary<Pair<byte[], Address>> library) {
|
||||
depsState.reset();
|
||||
super.execute(program, library);
|
||||
return super.execute(program, library);
|
||||
}
|
||||
|
||||
public AddressSet getReads() {
|
||||
@@ -204,7 +204,7 @@ public class WatchRow {
|
||||
DebuggerCoordinates coordinates) {
|
||||
Trace trace = coordinates.getTrace();
|
||||
ReadDepsTraceBytesPcodeExecutorState state =
|
||||
new ReadDepsTraceBytesPcodeExecutorState(trace, coordinates.getSnap(),
|
||||
new ReadDepsTraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(),
|
||||
coordinates.getThread(), coordinates.getFrame());
|
||||
Language language = trace.getBaseLanguage();
|
||||
PcodeExecutorState<Pair<byte[], Address>> paired =
|
||||
@@ -232,11 +232,11 @@ public class WatchRow {
|
||||
language = (SleighLanguage) newLanguage;
|
||||
recompile();
|
||||
}
|
||||
if (coordinates.isAliveAndPresent()) {
|
||||
if (coordinates.isAliveAndReadsPresent()) {
|
||||
asyncExecutor = TracePcodeUtils.executorForCoordinates(coordinates);
|
||||
}
|
||||
executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace,
|
||||
coordinates.getSnap(), coordinates.getThread(), coordinates.getFrame());
|
||||
coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame());
|
||||
executorWithAddress = buildAddressDepsExecutor(coordinates);
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@DisassemblyInjectInfo(langIDs = { "x86:LE:64:default" })
|
||||
// TODO: Filter / selector on debugger. This is running for GDB, too....
|
||||
public class DbgengX64DisassemblyInject implements DisassemblyInject {
|
||||
|
||||
enum Mode {
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState;
|
||||
import ghidra.pcode.exec.trace.TraceSleighUtils;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
public abstract class AbstractReadsTargetPcodeExecutorState
|
||||
extends TraceCachedWriteBytesPcodeExecutorState {
|
||||
|
||||
abstract class AbstractReadsTargetCachedSpace extends CachedSpace {
|
||||
public AbstractReadsTargetCachedSpace(AddressSpace space,
|
||||
TraceMemorySpace source, long snap) {
|
||||
super(space, source, snap);
|
||||
}
|
||||
|
||||
protected abstract void fillUninitialized(AddressSet uninitialized);
|
||||
|
||||
protected boolean isLive() {
|
||||
return recorder != null && recorder.isRecording() && recorder.getSnap() == snap;
|
||||
}
|
||||
|
||||
protected AddressSet computeUnknown(AddressSet uninitialized) {
|
||||
return uninitialized.subtract(source.getAddressesWithState(snap, uninitialized,
|
||||
s -> s != null && s != TraceMemoryState.UNKNOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(long offset, int size) {
|
||||
if (source != null) {
|
||||
AddressSet uninitialized = new AddressSet();
|
||||
for (Range<UnsignedLong> rng : cache.getUninitialized(offset, offset + size)
|
||||
.asRanges()) {
|
||||
uninitialized.add(space.getAddress(lower(rng)),
|
||||
space.getAddress(upper(rng)));
|
||||
}
|
||||
if (uninitialized.isEmpty()) {
|
||||
return super.read(offset, size);
|
||||
}
|
||||
|
||||
fillUninitialized(uninitialized);
|
||||
}
|
||||
|
||||
// TODO: What to flush when bytes in the trace change?
|
||||
return super.read(offset, size);
|
||||
}
|
||||
|
||||
protected <T> T waitTimeout(CompletableFuture<T> future) {
|
||||
try {
|
||||
return future.get(1, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
throw new AccessPcodeExecutionException("Timed out reading target", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final TraceRecorder recorder;
|
||||
protected final PluginTool tool;
|
||||
|
||||
public AbstractReadsTargetPcodeExecutorState(PluginTool tool, Trace trace, long snap,
|
||||
TraceThread thread, int frame, TraceRecorder recorder) {
|
||||
super(trace, snap, thread, frame);
|
||||
this.tool = tool;
|
||||
this.recorder = recorder;
|
||||
}
|
||||
|
||||
protected abstract AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
|
||||
TraceMemorySpace tms);
|
||||
|
||||
@Override
|
||||
protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) {
|
||||
return spaces.computeIfAbsent(space, s -> {
|
||||
TraceMemorySpace tms;
|
||||
if (s.isUniqueSpace()) {
|
||||
tms = null;
|
||||
}
|
||||
else {
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(trace, "Create space", true)) {
|
||||
tms = TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, true);
|
||||
}
|
||||
}
|
||||
return createCachedSpace(s, tms);
|
||||
});
|
||||
}
|
||||
}
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.async.AsyncLazyMap;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.trace.model.time.TraceSchedule.CompareResult;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger Emulation Service Plugin",
|
||||
description = "Manages and cache trace emulation states",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.UNSTABLE,
|
||||
eventsConsumed = {
|
||||
TraceClosedPluginEvent.class
|
||||
},
|
||||
servicesRequired = {
|
||||
DebuggerTraceManagerService.class
|
||||
},
|
||||
servicesProvided = {
|
||||
DebuggerEmulationService.class
|
||||
})
|
||||
public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEmulationService {
|
||||
protected static final int MAX_CACHE_SIZE = 5;
|
||||
protected static long nextSnap = Long.MIN_VALUE; // HACK
|
||||
|
||||
protected static class CacheKey implements Comparable<CacheKey> {
|
||||
protected final Trace trace;
|
||||
protected final TraceSchedule time;
|
||||
|
||||
public CacheKey(Trace trace, TraceSchedule time) {
|
||||
this.trace = trace;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(trace, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof CacheKey)) {
|
||||
return false;
|
||||
}
|
||||
CacheKey that = (CacheKey) obj;
|
||||
if (this.trace != that.trace) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.time, that.time)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CacheKey that) {
|
||||
return compareKey(that).compareTo;
|
||||
}
|
||||
|
||||
public CompareResult compareKey(CacheKey that) {
|
||||
CompareResult result;
|
||||
|
||||
// I don't care the order, I just care that traces matter first
|
||||
result = CompareResult.unrelated(Integer.compare(System.identityHashCode(this.trace),
|
||||
System.identityHashCode(that.trace)));
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = this.time.compareSchedule(that.time);
|
||||
if (result != CompareResult.EQUALS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return CompareResult.EQUALS;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class CachedEmulator {
|
||||
final DebuggerTracePcodeEmulator emulator;
|
||||
|
||||
public CachedEmulator(DebuggerTracePcodeEmulator emulator) {
|
||||
this.emulator = emulator;
|
||||
}
|
||||
}
|
||||
|
||||
protected class EmulateTask extends Task {
|
||||
protected final CacheKey key;
|
||||
protected final CompletableFuture<Long> future = new CompletableFuture<>();
|
||||
|
||||
public EmulateTask(CacheKey key) {
|
||||
super("Emulate " + key.time + " in " + key.trace, true, true, false, false);
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
future.complete(doEmulate(key, monitor));
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
future.completeExceptionally(e);
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
future.completeExceptionally(e);
|
||||
ExceptionUtils.rethrow(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Set<CacheKey> eldest = new LinkedHashSet<>();
|
||||
protected final NavigableMap<CacheKey, CachedEmulator> cache = new TreeMap<>();
|
||||
protected final AsyncLazyMap<CacheKey, Long> requests =
|
||||
new AsyncLazyMap<>(new HashMap<>(), this::doBackgroundEmulate)
|
||||
.forgetErrors((key, t) -> true)
|
||||
.forgetValues((key, l) -> true);
|
||||
|
||||
@AutoServiceConsumed
|
||||
private DebuggerTraceManagerService traceManager;
|
||||
@AutoServiceConsumed
|
||||
private DebuggerModelService modelService;
|
||||
@SuppressWarnings("unused")
|
||||
private AutoService.Wiring autoServiceWiring;
|
||||
|
||||
public DebuggerEmulationServicePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this);
|
||||
}
|
||||
|
||||
protected Map.Entry<CacheKey, CachedEmulator> findNearestPrefix(CacheKey key) {
|
||||
Map.Entry<CacheKey, CachedEmulator> candidate = cache.floorEntry(key);
|
||||
if (candidate == null) {
|
||||
return null;
|
||||
}
|
||||
if (!candidate.getKey().compareKey(key).related) {
|
||||
return null;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
protected CompletableFuture<Long> doBackgroundEmulate(CacheKey key) {
|
||||
EmulateTask task = new EmulateTask(key);
|
||||
tool.execute(task, 500);
|
||||
return task.future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Long> backgroundEmulate(Trace trace, TraceSchedule time) {
|
||||
if (!traceManager.getOpenTraces().contains(trace)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot emulate a trace unless it's opened in the tool.");
|
||||
}
|
||||
if (time.isSnapOnly()) {
|
||||
return CompletableFuture.completedFuture(time.getSnap());
|
||||
}
|
||||
return requests.get(new CacheKey(trace, time));
|
||||
}
|
||||
|
||||
protected TraceSnapshot findScratch(Trace trace, TraceSchedule time) {
|
||||
Collection<? extends TraceSnapshot> exist =
|
||||
trace.getTimeManager().getSnapshotsWithSchedule(time);
|
||||
if (!exist.isEmpty()) {
|
||||
return exist.iterator().next();
|
||||
}
|
||||
/**
|
||||
* TODO: This could be more sophisticated.... Does it need to be, though? Ideally, we'd only
|
||||
* keep state around that has annotations, e.g., bookmarks and code units. That needs a new
|
||||
* query (latestStartSince) on those managers, though. It must find the latest start tick
|
||||
* since a given snap. We consider only start snaps because placed code units go "from now
|
||||
* on out".
|
||||
*/
|
||||
TraceSnapshot last = trace.getTimeManager().getMostRecentSnapshot(-1);
|
||||
long snap = last == null ? Long.MIN_VALUE : last.getKey() + 1;
|
||||
TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(snap, true);
|
||||
snapshot.setDescription("Emulated");
|
||||
snapshot.setSchedule(time);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
protected long doEmulate(CacheKey key, TaskMonitor monitor) throws CancelledException {
|
||||
Trace trace = key.trace;
|
||||
TraceSchedule time = key.time;
|
||||
CachedEmulator ce;
|
||||
DebuggerTracePcodeEmulator emu;
|
||||
Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key);
|
||||
if (ancestor != null) {
|
||||
CacheKey prevKey = ancestor.getKey();
|
||||
|
||||
cache.remove(prevKey);
|
||||
eldest.remove(prevKey);
|
||||
|
||||
// TODO: Handle errors, and add to proper place in cache?
|
||||
// TODO: Finish partially-executed instructions?
|
||||
ce = ancestor.getValue();
|
||||
emu = ce.emulator;
|
||||
monitor.initialize(time.totalTickCount() - prevKey.time.totalTickCount());
|
||||
time.finish(trace, prevKey.time, emu, monitor);
|
||||
}
|
||||
else {
|
||||
emu = new DebuggerTracePcodeEmulator(tool, trace, time.getSnap(),
|
||||
modelService == null ? null : modelService.getRecorder(trace));
|
||||
ce = new CachedEmulator(emu);
|
||||
monitor.initialize(time.totalTickCount());
|
||||
time.execute(trace, emu, monitor);
|
||||
}
|
||||
TraceSnapshot destSnap;
|
||||
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate", true)) {
|
||||
destSnap = findScratch(trace, time);
|
||||
emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false);
|
||||
}
|
||||
|
||||
cache.put(key, ce);
|
||||
eldest.add(key);
|
||||
|
||||
assert cache.size() == eldest.size();
|
||||
while (cache.size() > MAX_CACHE_SIZE) {
|
||||
CacheKey expired = eldest.iterator().next();
|
||||
eldest.remove(expired);
|
||||
cache.remove(expired);
|
||||
}
|
||||
|
||||
return destSnap.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
if (!traceManager.getOpenTraces().contains(trace)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot emulate a trace unless it's opened in the tool.");
|
||||
}
|
||||
if (time.isSnapOnly()) {
|
||||
return time.getSnap();
|
||||
}
|
||||
return doEmulate(new CacheKey(trace, time), monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time) {
|
||||
CachedEmulator ce = cache.get(new CacheKey(trace, time));
|
||||
return ce == null ? null : ce.emulator;
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setTraceManager(DebuggerTraceManagerService traceManager) {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setModelService(DebuggerModelService modelService) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.pcode.emu.BytesPcodeThread;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.pcode.exec.trace.TracePcodeEmulator;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.RegisterValue;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
|
||||
/**
|
||||
* A trace emulator that knows how to read target memory when necessary
|
||||
*
|
||||
* <p>
|
||||
* This emulator must always be run in its own thread, or at least a thread that can never lock the
|
||||
* UI. It blocks on target reads so that execution can proceed synchronously. Probably the most
|
||||
* suitable option is to use a background task.
|
||||
*/
|
||||
public class DebuggerTracePcodeEmulator extends TracePcodeEmulator {
|
||||
protected final PluginTool tool;
|
||||
protected final TraceRecorder recorder;
|
||||
|
||||
public DebuggerTracePcodeEmulator(PluginTool tool, Trace trace, long snap,
|
||||
TraceRecorder recorder) {
|
||||
super(trace, snap);
|
||||
this.tool = tool;
|
||||
this.recorder = recorder;
|
||||
}
|
||||
|
||||
protected boolean isRegisterKnown(String threadName, Register register) {
|
||||
TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, threadName);
|
||||
TraceMemoryRegisterSpace space =
|
||||
trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||
if (space == null) {
|
||||
return false;
|
||||
}
|
||||
return space.getState(snap, register) == TraceMemoryState.KNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BytesPcodeThread createThread(String name) {
|
||||
BytesPcodeThread thread = super.createThread(name);
|
||||
Register contextreg = language.getContextBaseRegister();
|
||||
if (contextreg != null && !isRegisterKnown(name, contextreg)) {
|
||||
RegisterValue context = trace.getRegisterContextManager()
|
||||
.getValueWithDefault(language, contextreg, snap, thread.getCounter());
|
||||
thread.overrideContext(context);
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createMemoryState() {
|
||||
return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0,
|
||||
recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createRegisterState(PcodeThread<byte[]> emuThread) {
|
||||
TraceThread traceThread =
|
||||
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
|
||||
return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0,
|
||||
recorder);
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.MathUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ReadsTargetMemoryPcodeExecutorState
|
||||
extends AbstractReadsTargetPcodeExecutorState {
|
||||
|
||||
protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace {
|
||||
|
||||
public ReadsTargetMemoryCachedSpace(AddressSpace space, TraceMemorySpace source,
|
||||
long snap) {
|
||||
super(space, source, snap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillUninitialized(AddressSet uninitialized) {
|
||||
// TODO: fillUnknownWithStaticImages?
|
||||
if (!isLive()) {
|
||||
return;
|
||||
}
|
||||
AddressSet unknown = computeUnknown(uninitialized);
|
||||
if (unknown.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
fillUnknownWithRecorder(unknown);
|
||||
unknown = computeUnknown(uninitialized);
|
||||
if (unknown.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Msg.warn(this, "Emulator read from UNKNOWN state: " + unknown);
|
||||
}
|
||||
|
||||
protected void fillUnknownWithRecorder(AddressSet unknown) {
|
||||
waitTimeout(recorder.captureProcessMemory(unknown, TaskMonitor.DUMMY));
|
||||
}
|
||||
|
||||
private void fillUnknownWithStaticImages(AddressSet unknown) {
|
||||
if (!space.isMemorySpace()) {
|
||||
return;
|
||||
}
|
||||
DebuggerStaticMappingService mappingService =
|
||||
tool.getService(DebuggerStaticMappingService.class);
|
||||
byte[] data = new byte[4096];
|
||||
for (Entry<Program, Pair<Long, AddressSetView>> ent : mappingService
|
||||
.getOpenMappedViews(trace, unknown, snap)
|
||||
.entrySet()) {
|
||||
Program program = ent.getKey();
|
||||
Pair<Long, AddressSetView> pair = ent.getValue();
|
||||
Msg.warn(this,
|
||||
"Filling in unknown trace memory in emulator using mapped image: " +
|
||||
program + ": " + pair.getRight());
|
||||
long shift = pair.getLeft();
|
||||
Memory memory = program.getMemory();
|
||||
for (AddressRange rng : pair.getRight()) {
|
||||
long lower = rng.getMinAddress().getOffset();
|
||||
long fullLen = rng.getLength();
|
||||
while (fullLen > 0) {
|
||||
int len = MathUtilities.unsignedMin(data.length, fullLen);
|
||||
try {
|
||||
int read =
|
||||
memory.getBytes(space.getAddress(lower), data, 0, len);
|
||||
if (read < len) {
|
||||
Msg.warn(this,
|
||||
" Partial read of " + rng + ". Got " + read + " bytes");
|
||||
}
|
||||
cache.putData(lower - shift, data, 0, read);
|
||||
}
|
||||
catch (MemoryAccessException | AddressOutOfBoundsException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
lower += len;
|
||||
fullLen -= len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ReadsTargetMemoryPcodeExecutorState(PluginTool tool, Trace trace, long snap,
|
||||
TraceThread thread, int frame, TraceRecorder recorder) {
|
||||
super(tool, trace, snap, thread, frame, recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
|
||||
TraceMemorySpace tms) {
|
||||
return new ReadsTargetMemoryCachedSpace(s, tms, snap);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/* ###
|
||||
* 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.emulation;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class ReadsTargetRegistersPcodeExecutorState
|
||||
extends AbstractReadsTargetPcodeExecutorState {
|
||||
|
||||
protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace {
|
||||
|
||||
public ReadsTargetRegistersCachedSpace(AddressSpace space, TraceMemorySpace source,
|
||||
long snap) {
|
||||
super(space, source, snap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillUninitialized(AddressSet uninitialized) {
|
||||
if (!isLive()) {
|
||||
return;
|
||||
}
|
||||
AddressSet unknown = computeUnknown(uninitialized);
|
||||
Set<Register> toRead = new HashSet<>();
|
||||
for (AddressRange rng : unknown) {
|
||||
Register register =
|
||||
language.getRegister(rng.getMinAddress(), (int) rng.getLength());
|
||||
if (register == null) {
|
||||
Msg.error(this, "Could not figure register for " + rng);
|
||||
}
|
||||
else if (!recorder.getRegisterMapper(thread)
|
||||
.getRegistersOnTarget()
|
||||
.contains(register)) {
|
||||
Msg.warn(this, "Register not recognized by target: " + register);
|
||||
}
|
||||
else {
|
||||
toRead.add(register);
|
||||
}
|
||||
}
|
||||
waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead));
|
||||
}
|
||||
}
|
||||
|
||||
public ReadsTargetRegistersPcodeExecutorState(PluginTool tool, Trace trace, long snap,
|
||||
TraceThread thread, int frame, TraceRecorder recorder) {
|
||||
super(tool, trace, snap, thread, frame, recorder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s,
|
||||
TraceMemorySpace tms) {
|
||||
return new ReadsTargetRegistersCachedSpace(s, tms, snap);
|
||||
}
|
||||
}
|
||||
+48
-33
@@ -23,6 +23,8 @@ import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
@@ -64,24 +66,24 @@ import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@PluginInfo( //
|
||||
shortDescription = "Debugger static mapping manager", //
|
||||
description = "Track and manage static mappings (program-trace relocations)", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = {
|
||||
ProgramOpenedPluginEvent.class, //
|
||||
ProgramClosedPluginEvent.class, //
|
||||
TraceOpenedPluginEvent.class, //
|
||||
TraceClosedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
ProgramManager.class, //
|
||||
DebuggerTraceManagerService.class, //
|
||||
}, //
|
||||
servicesProvided = { //
|
||||
DebuggerStaticMappingService.class, //
|
||||
} //
|
||||
shortDescription = "Debugger static mapping manager", //
|
||||
description = "Track and manage static mappings (program-trace relocations)", //
|
||||
category = PluginCategoryNames.DEBUGGER, //
|
||||
packageName = DebuggerPluginPackage.NAME, //
|
||||
status = PluginStatus.RELEASED, //
|
||||
eventsConsumed = {
|
||||
ProgramOpenedPluginEvent.class, //
|
||||
ProgramClosedPluginEvent.class, //
|
||||
TraceOpenedPluginEvent.class, //
|
||||
TraceClosedPluginEvent.class, //
|
||||
}, //
|
||||
servicesRequired = { //
|
||||
ProgramManager.class, //
|
||||
DebuggerTraceManagerService.class, //
|
||||
}, //
|
||||
servicesProvided = { //
|
||||
DebuggerStaticMappingService.class, //
|
||||
} //
|
||||
)
|
||||
public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
implements DebuggerStaticMappingService, DomainFolderChangeAdapter {
|
||||
@@ -284,6 +286,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
|
||||
private Program program;
|
||||
private AddressRange staticRange;
|
||||
private Long shift;
|
||||
|
||||
public MappingEntry(TraceStaticMapping mapping) {
|
||||
this.mapping = mapping;
|
||||
@@ -309,6 +312,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
Address minAddr = opened.getAddressFactory().getAddress(mapping.getStaticAddress());
|
||||
Address maxAddr = addOrMax(minAddr, mapping.getLength() - 1);
|
||||
this.staticRange = new AddressRangeImpl(minAddr, maxAddr);
|
||||
this.shift = mapping.getMinTraceAddress().subtract(staticRange.getMinAddress());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -318,6 +322,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
if (this.program == closed) {
|
||||
this.program = null;
|
||||
this.staticRange = null;
|
||||
this.shift = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -562,7 +567,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
}
|
||||
|
||||
protected void collectOpenMappedPrograms(AddressRange rng, Range<Long> span,
|
||||
Map<Program, AddressSet> result) {
|
||||
Map<Program, Pair<Long, AddressSetView>> result) {
|
||||
TraceAddressSnapRange tatr = new ImmutableTraceAddressSnapRange(rng, span);
|
||||
for (Entry<TraceAddressSnapRange, MappingEntry> out : outbound.entrySet()) {
|
||||
MappingEntry me = out.getValue();
|
||||
@@ -572,14 +577,16 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
if (!out.getKey().intersects(tatr)) {
|
||||
continue;
|
||||
}
|
||||
AddressSet set = result.computeIfAbsent(me.program, p -> new AddressSet());
|
||||
set.add(me.mapTraceRangeToProgram(rng));
|
||||
|
||||
Pair<Long, AddressSetView> set = result.computeIfAbsent(me.program,
|
||||
p -> new ImmutablePair<>(me.shift, new AddressSet()));
|
||||
((AddressSet) set.getRight()).add(me.mapTraceRangeToProgram(rng));
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Program, AddressSetView> getOpenMappedViews(AddressSetView set,
|
||||
public Map<Program, Pair<Long, AddressSetView>> getOpenMappedViews(AddressSetView set,
|
||||
Range<Long> span) {
|
||||
Map<Program, AddressSet> result = new HashMap<>();
|
||||
Map<Program, Pair<Long, AddressSetView>> result = new HashMap<>();
|
||||
for (AddressRange rng : set) {
|
||||
collectOpenMappedPrograms(rng, span, result);
|
||||
}
|
||||
@@ -709,7 +716,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void collectOpenMappedViews(AddressRange rng, Map<TraceSnap, AddressSet> result) {
|
||||
protected void collectOpenMappedViews(AddressRange rng,
|
||||
Map<TraceSnap, Pair<Long, AddressSetView>> result) {
|
||||
for (Entry<MappingEntry, Address> inPreceeding : inbound.headMapByValue(
|
||||
rng.getMaxAddress(), true).entrySet()) {
|
||||
Address start = inPreceeding.getValue();
|
||||
@@ -720,13 +728,14 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
if (!me.isInProgramRange(rng)) {
|
||||
continue;
|
||||
}
|
||||
AddressSet set = result.computeIfAbsent(me.getTraceSnap(), p -> new AddressSet());
|
||||
set.add(me.mapProgramRangeToTrace(rng));
|
||||
Pair<Long, AddressSetView> set = result.computeIfAbsent(me.getTraceSnap(),
|
||||
p -> new ImmutablePair<>(me.shift, new AddressSet()));
|
||||
((AddressSet) set.getRight()).add(me.mapProgramRangeToTrace(rng));
|
||||
}
|
||||
}
|
||||
|
||||
public Map<TraceSnap, AddressSetView> getOpenMappedViews(AddressSetView set) {
|
||||
Map<TraceSnap, AddressSet> result = new HashMap<>();
|
||||
public Map<TraceSnap, Pair<Long, AddressSetView>> getOpenMappedViews(AddressSetView set) {
|
||||
Map<TraceSnap, Pair<Long, AddressSetView>> result = new HashMap<>();
|
||||
for (AddressRange rng : set) {
|
||||
collectOpenMappedViews(rng, result);
|
||||
}
|
||||
@@ -1099,13 +1108,17 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
return info.getOpenMappedLocations(loc.getAddress(), loc.getLifespan());
|
||||
}
|
||||
|
||||
protected long getNonScratchSnap(TraceProgramView view) {
|
||||
return view.getViewport().getTop(s -> s >= 0 ? s : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramLocation getStaticLocationFromDynamic(ProgramLocation loc) {
|
||||
loc = traceManager.fixLocation(loc, true);
|
||||
TraceProgramView view = (TraceProgramView) loc.getProgram();
|
||||
Trace trace = view.getTrace();
|
||||
TraceLocation tloc = new DefaultTraceLocation(trace, null, Range.singleton(view.getSnap()),
|
||||
loc.getAddress());
|
||||
TraceLocation tloc = new DefaultTraceLocation(trace, null,
|
||||
Range.singleton(getNonScratchSnap(view)), loc.getAddress());
|
||||
ProgramLocation mapped = getOpenMappedLocation(tloc);
|
||||
if (mapped == null) {
|
||||
return null;
|
||||
@@ -1134,7 +1147,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
@Override
|
||||
public ProgramLocation getDynamicLocationFromStatic(TraceProgramView view,
|
||||
ProgramLocation loc) {
|
||||
TraceLocation tloc = getOpenMappedLocation(view.getTrace(), loc, view.getSnap());
|
||||
TraceLocation tloc = getOpenMappedLocation(view.getTrace(), loc, getNonScratchSnap(view));
|
||||
if (tloc == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -1142,7 +1155,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Program, AddressSetView> getOpenMappedViews(Trace trace, AddressSetView set,
|
||||
public Map<Program, Pair<Long, AddressSetView>> getOpenMappedViews(Trace trace,
|
||||
AddressSetView set,
|
||||
long snap) {
|
||||
InfoPerTrace info = requireTrackedInfo(trace);
|
||||
if (info == null) {
|
||||
@@ -1152,7 +1166,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TraceSnap, AddressSetView> getOpenMappedViews(Program program, AddressSetView set) {
|
||||
public Map<TraceSnap, Pair<Long, AddressSetView>> getOpenMappedViews(Program program,
|
||||
AddressSetView set) {
|
||||
InfoPerProgram info = requireTrackedInfo(program);
|
||||
if (info == null) {
|
||||
return null;
|
||||
|
||||
+83
-47
@@ -56,6 +56,8 @@ import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.program.TraceVariableSnapProgramView;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.datastruct.CollectionChangeListener;
|
||||
import ghidra.util.exception.*;
|
||||
@@ -119,8 +121,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
|
||||
private void threadDeleted(TraceThread thread) {
|
||||
if (threadFocusByTrace.get(trace) == thread) {
|
||||
threadFocusByTrace.remove(trace);
|
||||
DebuggerCoordinates last = lastCoordsByTrace.get(trace);
|
||||
if (last != null && last.getThread() == thread) {
|
||||
lastCoordsByTrace.remove(trace);
|
||||
}
|
||||
if (current.getThread() == thread) {
|
||||
activate(DebuggerCoordinates.trace(trace));
|
||||
@@ -158,7 +161,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
protected final Map<Trace, TraceThread> threadFocusByTrace = new WeakHashMap<>();
|
||||
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());
|
||||
|
||||
@@ -177,6 +180,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
|
||||
// @AutoServiceConsumed via method
|
||||
private DebuggerModelService modelService;
|
||||
@AutoServiceConsumed
|
||||
private DebuggerEmulationService emulationService;
|
||||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
@@ -422,6 +427,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
if (trace == null) {
|
||||
return DebuggerCoordinates.NOWHERE;
|
||||
}
|
||||
DebuggerCoordinates lastForTrace = lastCoordsByTrace.get(trace);
|
||||
// Note: override recorder with that known to service
|
||||
TraceRecorder recorder = computeRecorder(trace);
|
||||
TargetObject focus = recorder == null ? null : recorder.getFocus();
|
||||
@@ -431,7 +437,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
thread = threadFromTargetFocus(recorder, focus);
|
||||
}
|
||||
if (thread /*still*/ == null) { // either no focus support, or focus is not a thread
|
||||
thread = threadFocusByTrace.get(trace);
|
||||
thread = lastForTrace == null ? null : lastForTrace.getThread();
|
||||
}
|
||||
// NOTE, if still null without focus support,
|
||||
// we will take the eldest live thread at the resolved snap
|
||||
@@ -444,20 +450,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
*/
|
||||
// Note: override view. May not agree on snap now, but will upon activation
|
||||
TraceProgramView view = trace.getProgramView();
|
||||
Long snap = coordinates.getSnap();
|
||||
if (snap == null) {
|
||||
TraceSchedule time = coordinates.getTime();
|
||||
if (time == null) {
|
||||
if (recorder != null && autoActivatePresent.get() && trace != current.getTrace()) {
|
||||
snap = recorder.getSnap();
|
||||
time = TraceSchedule.snap(recorder.getSnap());
|
||||
}
|
||||
else {
|
||||
snap = view.getSnap();
|
||||
time = lastForTrace == null ? TraceSchedule.snap(0) : lastForTrace.getTime();
|
||||
}
|
||||
}
|
||||
|
||||
if (!supportsFocus(recorder)) {
|
||||
if (thread /*still*/ == null) {
|
||||
Iterator<? extends TraceThread> it =
|
||||
trace.getThreadManager().getLiveThreads(snap).iterator();
|
||||
trace.getThreadManager().getLiveThreads(time.getSnap()).iterator();
|
||||
// docs say eldest come first
|
||||
if (it.hasNext()) {
|
||||
thread = it.next();
|
||||
@@ -472,15 +478,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
String ticks = coordinates.getTicks();
|
||||
// TODO: Memorize ticks by trace and frame by thread
|
||||
if (ticks == null && trace == current.getTrace()) {
|
||||
ticks = current.getTicks();
|
||||
}
|
||||
if (ticks == null) {
|
||||
ticks = "";
|
||||
}
|
||||
// Note, not likely we can view non-zero frame with emulated ticks
|
||||
Integer frame = coordinates.getFrame();
|
||||
if (frame == null) {
|
||||
if (supportsFocus(recorder)) {
|
||||
@@ -493,17 +490,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
frame = traceFrame.getLevel();
|
||||
}
|
||||
}
|
||||
if (frame /*still*/ == null && thread == current.getThread()) {
|
||||
frame = current.getFrame();
|
||||
if (frame /*still*/ == null && lastForTrace != null &&
|
||||
thread == lastForTrace.getThread()) {
|
||||
// TODO: Memorize frame by thread, instead of by trace?
|
||||
frame = lastForTrace.getFrame();
|
||||
}
|
||||
}
|
||||
// TODO: Is it reasonable to change back to frame 0 on snap change?
|
||||
// Only 0 (possibly synthetic) is guaranteed to exist in any snap
|
||||
if (frame == null || !"".equals(ticks) || !Objects.equals(snap, current.getSnap())) {
|
||||
if (frame == null || !time.isSnapOnly() ||
|
||||
!Objects.equals(time.getSnap(), current.getSnap())) {
|
||||
frame = 0;
|
||||
}
|
||||
return DebuggerCoordinates.all(trace, recorder, thread, view, Objects.requireNonNull(snap),
|
||||
Objects.requireNonNull(ticks), Objects.requireNonNull(frame));
|
||||
return DebuggerCoordinates.all(trace, recorder, thread, view, Objects.requireNonNull(time),
|
||||
Objects.requireNonNull(frame));
|
||||
}
|
||||
|
||||
protected DebuggerCoordinates doSetCurrent(DebuggerCoordinates newCurrent) {
|
||||
@@ -515,8 +515,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
current = resolved;
|
||||
contextChanged();
|
||||
if (current.getTrace() != null && current.getThread() != null) {
|
||||
threadFocusByTrace.put(current.getTrace(), current.getThread());
|
||||
if (resolved.getTrace() != null) {
|
||||
lastCoordsByTrace.put(resolved.getTrace(), resolved);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
@@ -561,10 +561,10 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
TraceThread thread = threadFromTargetFocus(recorder, obj);
|
||||
long snap = recorder.getSnap();
|
||||
String ticks = "";
|
||||
TraceStackFrame traceFrame = frameFromTargetFocus(recorder, obj);
|
||||
Integer frame = traceFrame == null ? null : traceFrame.getLevel();
|
||||
activateNoFocus(DebuggerCoordinates.all(trace, recorder, thread, null, snap, ticks, frame));
|
||||
activateNoFocus(DebuggerCoordinates.all(trace, recorder, thread, null,
|
||||
TraceSchedule.snap(snap), frame));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -651,7 +651,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
|
||||
@Override
|
||||
public TraceThread getCurrentThreadFor(Trace trace) {
|
||||
return threadFocusByTrace.get(trace);
|
||||
DebuggerCoordinates coords = lastCoordsByTrace.get(trace);
|
||||
return coords == null ? null : coords.getThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -664,14 +665,44 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
return current.getFrame();
|
||||
}
|
||||
|
||||
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
|
||||
protected void prepareViewAndFireEvent(DebuggerCoordinates coordinates) {
|
||||
TraceVariableSnapProgramView varView = (TraceVariableSnapProgramView) coordinates.getView();
|
||||
if (varView != null) {
|
||||
varView.setSnap(coordinates.getSnap());
|
||||
if (varView == null) { // Should only happen with NOWHERE
|
||||
fireLocationEvent(coordinates);
|
||||
}
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates));
|
||||
});
|
||||
else if (coordinates.getTime().isSnapOnly()) {
|
||||
varView.setSnap(coordinates.getSnap());
|
||||
fireLocationEvent(coordinates);
|
||||
}
|
||||
else {
|
||||
Collection<? extends TraceSnapshot> suitable = coordinates.getTrace()
|
||||
.getTimeManager()
|
||||
.getSnapshotsWithSchedule(coordinates.getTime());
|
||||
if (!suitable.isEmpty()) {
|
||||
TraceSnapshot found = suitable.iterator().next();
|
||||
varView.setSnap(found.getKey());
|
||||
fireLocationEvent(coordinates);
|
||||
return;
|
||||
}
|
||||
if (emulationService == null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot navigate to coordinates with execution schedules, " +
|
||||
"because the emulation service is not available.");
|
||||
}
|
||||
CompletableFuture<Long> bg =
|
||||
emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime());
|
||||
bg.thenAccept(emuSnap -> Swing.runLater(() -> {
|
||||
if (!coordinates.equals(current)) {
|
||||
return; // We navigated elsewhere before emulation completed
|
||||
}
|
||||
varView.setSnap(emuSnap);
|
||||
fireLocationEvent(coordinates);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
protected void fireLocationEvent(DebuggerCoordinates coordinates) {
|
||||
firePluginEvent(new TraceActivatedPluginEvent(getName(), coordinates));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -874,7 +905,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
protected void doTraceClosed(Trace trace) {
|
||||
synchronized (listenersByTrace) {
|
||||
trace.release(this);
|
||||
threadFocusByTrace.remove(trace);
|
||||
lastCoordsByTrace.remove(trace);
|
||||
trace.removeListener(listenersByTrace.remove(trace));
|
||||
//Msg.debug(this, "Remaining Consumers of " + trace + ": " + trace.getConsumerList());
|
||||
}
|
||||
@@ -909,12 +940,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
while (it.hasNext()) {
|
||||
Trace trace = it.next();
|
||||
trace.release(this);
|
||||
threadFocusByTrace.remove(trace);
|
||||
lastCoordsByTrace.remove(trace);
|
||||
trace.removeListener(listenersByTrace.get(trace));
|
||||
it.remove();
|
||||
}
|
||||
// Be certain
|
||||
threadFocusByTrace.clear();
|
||||
lastCoordsByTrace.clear();
|
||||
}
|
||||
autoServiceWiring.dispose();
|
||||
}
|
||||
@@ -931,7 +962,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
if (resolved == null) {
|
||||
return;
|
||||
}
|
||||
fireLocationEvent(resolved);
|
||||
prepareViewAndFireEvent(resolved);
|
||||
}
|
||||
|
||||
protected static TargetObject translateToFocus(DebuggerCoordinates prev,
|
||||
@@ -970,7 +1001,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
if (resolved == null) {
|
||||
return;
|
||||
}
|
||||
fireLocationEvent(resolved);
|
||||
prepareViewAndFireEvent(resolved);
|
||||
if (!synchronizeFocus.get()) {
|
||||
return;
|
||||
}
|
||||
@@ -997,6 +1028,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
activate(DebuggerCoordinates.snap(snap));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateTime(TraceSchedule time) {
|
||||
activate(DebuggerCoordinates.time(time));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activateFrame(int frameLevel) {
|
||||
activate(DebuggerCoordinates.frame(frameLevel));
|
||||
@@ -1142,24 +1178,20 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
}
|
||||
List<Trace> traces;
|
||||
DebuggerCoordinates currentCoords;
|
||||
Map<Trace, TraceThread> threadByTrace;
|
||||
Map<Trace, Long> snapByTrace;
|
||||
Map<Trace, DebuggerCoordinates> coordsByTrace;
|
||||
synchronized (listenersByTrace) {
|
||||
currentCoords = current;
|
||||
traces = tracesView.stream().filter(t -> {
|
||||
ProjectLocator loc = t.getDomainFile().getProjectLocator();
|
||||
return loc != null && !loc.isTransient();
|
||||
}).collect(Collectors.toList());
|
||||
threadByTrace = Map.copyOf(threadFocusByTrace);
|
||||
snapByTrace = tracesView.stream()
|
||||
.collect(Collectors.toMap(t -> t, t -> t.getProgramView().getSnap()));
|
||||
coordsByTrace = Map.copyOf(lastCoordsByTrace);
|
||||
}
|
||||
|
||||
saveState.putInt(KEY_TRACE_COUNT, traces.size());
|
||||
for (int index = 0; index < traces.size(); index++) {
|
||||
Trace t = traces.get(index);
|
||||
DebuggerCoordinates coords = DebuggerCoordinates.all(t, null, threadByTrace.get(t),
|
||||
null, snapByTrace.get(t), null, null);
|
||||
DebuggerCoordinates coords = coordsByTrace.get(t);
|
||||
String stateName = PREFIX_OPEN_TRACE + index;
|
||||
coords.writeDataState(tool, saveState, stateName);
|
||||
}
|
||||
@@ -1173,7 +1205,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||
for (int index = 0; index < traceCount; index++) {
|
||||
String stateName = PREFIX_OPEN_TRACE + index;
|
||||
// Trace will be opened by readDataState, resolve causes update to focus and view
|
||||
DebuggerCoordinates.readDataState(tool, saveState, stateName, true);
|
||||
DebuggerCoordinates coords =
|
||||
DebuggerCoordinates.readDataState(tool, saveState, stateName, true);
|
||||
if (coords.getTrace() != null) {
|
||||
lastCoordsByTrace.put(coords.getTrace(), coords);
|
||||
}
|
||||
}
|
||||
|
||||
activate(DebuggerCoordinates.readDataState(tool, saveState, KEY_CURRENT_COORDS, false));
|
||||
|
||||
+87
-52
@@ -44,19 +44,17 @@ import ghidra.trace.model.memory.*;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.stack.*;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.trace.util.TraceRegisterUtils;
|
||||
import ghidra.util.IntersectionAddressSetView;
|
||||
import ghidra.util.UnionAddressSetView;
|
||||
import ghidra.trace.util.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@DebuggerBotInfo( //
|
||||
description = "Disassemble memory at the program counter", //
|
||||
details = "Listens for changes in memory or pc (stack or registers) and disassembles", //
|
||||
help = @HelpInfo(anchor = "disassemble_at_pc"), //
|
||||
enabledByDefault = true //
|
||||
description = "Disassemble memory at the program counter", //
|
||||
details = "Listens for changes in memory or pc (stack or registers) and disassembles", //
|
||||
help = @HelpInfo(anchor = "disassemble_at_pc"), //
|
||||
enabledByDefault = true //
|
||||
)
|
||||
public class DisassembleAtPcDebuggerBot implements DebuggerBot {
|
||||
|
||||
@@ -64,12 +62,11 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
|
||||
private final TraceStackManager stackManager;
|
||||
private final TraceMemoryManager memoryManager;
|
||||
private final TraceCodeManager codeManager;
|
||||
private final TraceTimeViewport viewport;
|
||||
|
||||
private final Register pc;
|
||||
private final AddressRange pcRange;
|
||||
|
||||
private boolean usesStacks = false;
|
||||
|
||||
private final Set<DisassemblyInject> injects = new LinkedHashSet<>();
|
||||
private final ChangeListener injectsChangeListener = e -> updateInjects();
|
||||
|
||||
@@ -83,6 +80,7 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
|
||||
this.stackManager = trace.getStackManager();
|
||||
this.memoryManager = trace.getMemoryManager();
|
||||
this.codeManager = trace.getCodeManager();
|
||||
this.viewport = trace.getProgramView().getViewport();
|
||||
|
||||
this.pc = trace.getBaseLanguage().getProgramCounter();
|
||||
this.pcRange = TraceRegisterUtils.rangeForRegister(pc);
|
||||
@@ -117,13 +115,18 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
|
||||
}
|
||||
|
||||
private void processQueue(Void __) {
|
||||
List<Runnable> copy;
|
||||
synchronized (runQueue) {
|
||||
copy = List.copyOf(runQueue);
|
||||
runQueue.clear();
|
||||
try {
|
||||
List<Runnable> copy;
|
||||
synchronized (runQueue) {
|
||||
copy = List.copyOf(runQueue);
|
||||
runQueue.clear();
|
||||
}
|
||||
for (Runnable r : copy) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
for (Runnable r : copy) {
|
||||
r.run();
|
||||
catch (Throwable e) {
|
||||
Msg.error(this, "Error processing queue", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,21 +142,41 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
|
||||
|
||||
private void stackChanged(TraceStack stack) {
|
||||
queueRunnable(() -> {
|
||||
usesStacks = true;
|
||||
disassembleStackPcVals(stack, stack.getSnap(), null);
|
||||
});
|
||||
}
|
||||
|
||||
private long findNonScratchSnap(long snap) {
|
||||
if (snap >= 0) {
|
||||
return snap;
|
||||
}
|
||||
TraceViewportSpanIterator spit = new TraceViewportSpanIterator(trace, snap);
|
||||
while (spit.hasNext()) {
|
||||
Range<Long> span = spit.next();
|
||||
if (span.upperEndpoint() >= 0) {
|
||||
return span.upperEndpoint();
|
||||
}
|
||||
}
|
||||
return snap;
|
||||
}
|
||||
|
||||
private void memoryChanged(TraceAddressSnapRange range) {
|
||||
if (!viewport.containsAnyUpper(range.getLifespan())) {
|
||||
return;
|
||||
}
|
||||
// This is a wonky case, because we care about where the user is looking.
|
||||
long pcSnap = trace.getProgramView().getSnap();
|
||||
long memSnap = range.getY1();
|
||||
queueRunnable(() -> {
|
||||
long snap = range.getY1();
|
||||
for (TraceThread thread : trace.getThreadManager().getLiveThreads(snap)) {
|
||||
TraceStack stack = stackManager.getLatestStack(thread, snap);
|
||||
for (TraceThread thread : trace.getThreadManager()
|
||||
.getLiveThreads(findNonScratchSnap(pcSnap))) {
|
||||
TraceStack stack = stackManager.getLatestStack(thread, pcSnap);
|
||||
if (stack != null) {
|
||||
usesStacks = true;
|
||||
disassembleStackPcVals(stack, snap, range.getRange());
|
||||
disassembleStackPcVals(stack, memSnap, range.getRange());
|
||||
}
|
||||
else {
|
||||
disassembleRegPcVal(thread, 0, pcSnap, memSnap);
|
||||
}
|
||||
disassembleRegPcVal(thread, 0, snap);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -166,11 +189,16 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
|
||||
if (!range.getRange().intersects(pcRange)) {
|
||||
return;
|
||||
}
|
||||
disassembleRegPcVal(space.getThread(), space.getFrameLevel(), range.getY1());
|
||||
TraceThread thread = space.getThread();
|
||||
long snap = range.getY1();
|
||||
if (stackManager.getLatestStack(thread, snap) != null) {
|
||||
return;
|
||||
}
|
||||
disassembleRegPcVal(thread, space.getFrameLevel(), snap, snap);
|
||||
});
|
||||
}
|
||||
|
||||
protected void disassembleStackPcVals(TraceStack stack, long snap, AddressRange range) {
|
||||
protected void disassembleStackPcVals(TraceStack stack, long memSnap, AddressRange range) {
|
||||
TraceStackFrame frame = stack.getFrame(0, false);
|
||||
if (frame == null) {
|
||||
return;
|
||||
@@ -181,11 +209,12 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
|
||||
}
|
||||
if (range == null || range.contains(pcVal)) {
|
||||
// NOTE: If non-0 frames are ever used, level should be passed in for injects
|
||||
disassemble(pcVal, stack.getThread(), snap);
|
||||
disassemble(pcVal, stack.getThread(), memSnap);
|
||||
}
|
||||
}
|
||||
|
||||
protected void disassembleRegPcVal(TraceThread thread, int frameLevel, long snap) {
|
||||
protected void disassembleRegPcVal(TraceThread thread, int frameLevel, long pcSnap,
|
||||
long memSnap) {
|
||||
TraceData pcUnit = null;
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(trace, "Disassemble: PC is code pointer", true)) {
|
||||
@@ -193,74 +222,80 @@ public class DisassembleAtPcDebuggerBot implements DebuggerBot {
|
||||
codeManager.getCodeRegisterSpace(thread, frameLevel, true);
|
||||
try {
|
||||
pcUnit = regCode.definedData()
|
||||
.create(Range.atLeast(snap), pc, PointerDataType.dataType);
|
||||
.create(Range.atLeast(pcSnap), pc, PointerDataType.dataType);
|
||||
}
|
||||
catch (CodeUnitInsertionException e) {
|
||||
// I guess something's already there. Leave it, then!
|
||||
// Try to get it, in case it's already a pointer type
|
||||
pcUnit = regCode.definedData().getForRegister(snap, pc);
|
||||
pcUnit = regCode.definedData().getForRegister(pcSnap, pc);
|
||||
}
|
||||
}
|
||||
if (!usesStacks && pcUnit != null) {
|
||||
if (pcUnit != null) {
|
||||
Address pcVal = (Address) TraceRegisterUtils.getValueHackPointer(pcUnit);
|
||||
if (pcVal != null) {
|
||||
disassemble(pcVal, thread, snap);
|
||||
disassemble(pcVal, thread, memSnap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isKnownRWOrEverKnownRO(Address start, long snap) {
|
||||
Entry<TraceAddressSnapRange, TraceMemoryState> ent =
|
||||
memoryManager.getMostRecentStateEntry(snap, start);
|
||||
if (ent == null || ent.getValue() != TraceMemoryState.KNOWN) {
|
||||
protected Long isKnownRWOrEverKnownRO(Address start, long snap) {
|
||||
Entry<Long, TraceMemoryState> kent = memoryManager.getViewState(snap, start);
|
||||
if (kent != null && kent.getValue() == TraceMemoryState.KNOWN) {
|
||||
return kent.getKey();
|
||||
}
|
||||
Entry<TraceAddressSnapRange, TraceMemoryState> mrent =
|
||||
memoryManager.getViewMostRecentStateEntry(snap, start);
|
||||
if (mrent == null || mrent.getValue() != TraceMemoryState.KNOWN) {
|
||||
// It has never been known up to this snap
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
if (ent.getKey().getLifespan().contains(snap)) {
|
||||
// It is known at this snap, so RO vs RW is irrelevant
|
||||
return true;
|
||||
}
|
||||
TraceMemoryRegion region = memoryManager.getRegionContaining(snap, start);
|
||||
if (region.isWrite()) {
|
||||
TraceMemoryRegion region =
|
||||
memoryManager.getRegionContaining(mrent.getKey().getY1(), start);
|
||||
if (region == null || region.isWrite()) {
|
||||
// It could have changed this snap, so unknown
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
return true;
|
||||
return mrent.getKey().getY1();
|
||||
}
|
||||
|
||||
protected void disassemble(Address start, TraceThread thread, long snap) {
|
||||
if (!isKnownRWOrEverKnownRO(start, snap)) {
|
||||
Long knownSnap = isKnownRWOrEverKnownRO(start, snap);
|
||||
if (knownSnap == null) {
|
||||
return;
|
||||
}
|
||||
if (codeManager.definedUnits().containsAddress(snap, start)) {
|
||||
long ks = knownSnap;
|
||||
if (codeManager.definedUnits().containsAddress(ks, start)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Is this composition of laziness upon laziness efficient enough?
|
||||
*
|
||||
* <p>
|
||||
* Can experiment with ordering of address-set-view "expression" to optimize early
|
||||
* termination.
|
||||
*
|
||||
* <p>
|
||||
* Want addresses satisfying {@code known | (readOnly & everKnown)}
|
||||
*/
|
||||
AddressSetView readOnly =
|
||||
memoryManager.getRegionsAddressSetWith(snap, r -> !r.isWrite());
|
||||
AddressSetView everKnown = memoryManager.getAddressesWithState(Range.atMost(snap),
|
||||
memoryManager.getRegionsAddressSetWith(ks, r -> !r.isWrite());
|
||||
AddressSetView everKnown = memoryManager.getAddressesWithState(Range.atMost(ks),
|
||||
s -> s == TraceMemoryState.KNOWN);
|
||||
AddressSetView roEverKnown = new IntersectionAddressSetView(readOnly, everKnown);
|
||||
AddressSetView known =
|
||||
memoryManager.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN);
|
||||
AddressSetView disassemblable = new UnionAddressSetView(known, roEverKnown);
|
||||
memoryManager.getAddressesWithState(ks, s -> s == TraceMemoryState.KNOWN);
|
||||
AddressSetView disassemblable =
|
||||
new AddressSet(new UnionAddressSetView(known, roEverKnown));
|
||||
|
||||
// TODO: Should I just keep a variable-snap view around?
|
||||
TraceProgramView view = trace.getFixedProgramView(snap);
|
||||
TraceProgramView view = trace.getFixedProgramView(ks);
|
||||
DisassembleCommand dis =
|
||||
new DisassembleCommand(start, disassemblable, true) {
|
||||
@Override
|
||||
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
|
||||
synchronized (injects) {
|
||||
if (codeManager.definedUnits().containsAddress(snap, start)) {
|
||||
if (codeManager.definedUnits().containsAddress(ks, start)) {
|
||||
return true;
|
||||
}
|
||||
for (DisassemblyInject i : injects) {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.services;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
|
||||
import ghidra.framework.plugintool.ServiceInfo;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@ServiceInfo(defaultProvider = DebuggerEmulationServicePlugin.class)
|
||||
public interface DebuggerEmulationService {
|
||||
|
||||
/**
|
||||
* Perform emulation to realize the machine state of the given time coordinates
|
||||
*
|
||||
* <p>
|
||||
* Only those address ranges actually modified during emulation are written into the scratch
|
||||
* space. It is the responsibility of anyone reading from scratch space to retrieve state and/or
|
||||
* annotations from the initial snap, when needed. The scratch snapshot is given the description
|
||||
* "{@code emu:[time]}", where {@code [time]} is the given time parameter as a string.
|
||||
*
|
||||
* <p>
|
||||
* The service may use a cached emulator in order to realize the requested machine state. This
|
||||
* is especially important to ensure that a user stepping forward does not incur ever increasing
|
||||
* costs. On the other hand, the service should be careful to invalidate cached results when the
|
||||
* recorded machine state in a trace changes.
|
||||
*
|
||||
* @param trace the trace containing the initial state
|
||||
* @param time the time coordinates, including initial snap, steps, and p-code steps
|
||||
* @param monitor a monitor for cancellation and progress reporting
|
||||
* @return the snap in the trace's scratch space where the realized state is stored
|
||||
* @throws CancelledException if the emulation is cancelled
|
||||
*/
|
||||
long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor) throws CancelledException;
|
||||
|
||||
/**
|
||||
* Invoke {@link #emulate(Trace, TraceSchedule, TaskMonitor)} in the background
|
||||
*
|
||||
* <p>
|
||||
* This is the preferred means of performing emulation. Because the underlying emulator may
|
||||
* request <em>blocking</em> read of a target, it is important that
|
||||
* {@link #emulate(Trace, TraceSchedule, TaskMonitor)} is <em>never</em> called by the Swing
|
||||
* thread.
|
||||
*
|
||||
* @param trace the trace containing the initial state
|
||||
* @param time the time coordinates, including initial snap, steps, and p-code steps
|
||||
* @return a future which completes with the result of
|
||||
* {@link #emulate(Trace, TraceSchedule, TaskMonitor)}
|
||||
*/
|
||||
CompletableFuture<Long> backgroundEmulate(Trace trace, TraceSchedule time);
|
||||
|
||||
/**
|
||||
* The the cached emulator for the given trace and time
|
||||
*
|
||||
* <p>
|
||||
* To guarantee the emulator is present, call {@link #backgroundEmulate(Trace, TraceSchedule)}
|
||||
* first. WARNING: This emulator belongs to this service. Stepping it, or otherwise manipulating
|
||||
* it without the service's knowledge can lead to unintended consequences.
|
||||
*
|
||||
* @param trace the trace containing the initial state
|
||||
* @param time the time coordinates, including initial snap, steps, and p-code steps
|
||||
* @return the copied p-code frame
|
||||
*/
|
||||
DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time);
|
||||
}
|
||||
+6
-2
@@ -18,6 +18,8 @@ package ghidra.app.services;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.framework.model.DomainFile;
|
||||
@@ -637,7 +639,8 @@ public interface DebuggerStaticMappingService {
|
||||
* @param snap the source snap
|
||||
* @return a map of destination programs to corresponding computed destination address sets
|
||||
*/
|
||||
Map<Program, AddressSetView> getOpenMappedViews(Trace trace, AddressSetView set, long snap);
|
||||
Map<Program, Pair<Long, AddressSetView>> getOpenMappedViews(Trace trace,
|
||||
AddressSetView set, long snap);
|
||||
|
||||
/**
|
||||
* Find/compute all source address sets given a destination program address set
|
||||
@@ -646,7 +649,8 @@ public interface DebuggerStaticMappingService {
|
||||
* @param set the destination address set, from which we are mapping back
|
||||
* @return a map of source traces to corresponding computed source address sets
|
||||
*/
|
||||
Map<TraceSnap, AddressSetView> getOpenMappedViews(Program program, AddressSetView set);
|
||||
Map<TraceSnap, Pair<Long, AddressSetView>> getOpenMappedViews(Program program,
|
||||
AddressSetView set);
|
||||
|
||||
/**
|
||||
* Open all destination programs in mappings intersecting the given source trace, address set,
|
||||
|
||||
+3
@@ -26,6 +26,7 @@ import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.util.TriConsumer;
|
||||
|
||||
@ServiceInfo(defaultProvider = DebuggerTraceManagerServicePlugin.class)
|
||||
@@ -128,6 +129,8 @@ public interface DebuggerTraceManagerService {
|
||||
|
||||
void activateSnap(long snap);
|
||||
|
||||
void activateTime(TraceSchedule time);
|
||||
|
||||
void activateFrame(int frameLevel);
|
||||
|
||||
void setAutoActivatePresent(boolean enabled);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user