GP-569: Added trace interpolation and extrapolation (emulation)

This commit is contained in:
Dan
2021-01-06 16:04:02 -05:00
parent 57b69005c7
commit fcc0d97ae0
173 changed files with 10969 additions and 1424 deletions
@@ -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>
@@ -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
@@ -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 &larr; indicates read. A &rarr; 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>
@@ -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
@@ -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 &mdash; 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>
@@ -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>
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

@@ -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;
@@ -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();
}
}
@@ -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() {
@@ -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();
@@ -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;
@@ -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;
@@ -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()
@@ -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;
}
@@ -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
@@ -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;
}
}
@@ -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());
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
@@ -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);
}
}
@@ -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";
@@ -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() {
@@ -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();
}
@@ -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;
@@ -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())
@@ -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);
}
}
@@ -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);
}
}
@@ -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() {
@@ -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();
@@ -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);
}
@@ -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 {
@@ -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);
});
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
@@ -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));
@@ -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);
}
@@ -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,
@@ -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