mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-569: Added trace interpolation and extrapolation (emulation)
This commit is contained in:
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 ← indicates read. A → indicates write.</LI>
|
||||
|
||||
<LI>Unique - the name (address and size) of the variable.</LI>
|
||||
|
||||
<LI>Bytes - the value displayed as bytes in the machine's endianness.</LI>
|
||||
|
||||
<LI>Value - the value displayed in hexadecimal.</LI>
|
||||
|
||||
<LI>Type - the user-assigned, ephemeral type of the variable.</LI>
|
||||
|
||||
<LI>Representation - the value of the variable as interpreted by its data type.</LI>
|
||||
</UL>
|
||||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<P>The p-code stepper provides the following actions:</P>
|
||||
|
||||
<H3><A name="step_trace_pcode_backward"></A>Step Trace p-code Backward</H3>
|
||||
|
||||
<P>This action is available when the current coordinates have some positive number of p-code
|
||||
ticks. It steps the trace backward to the previous p-code tick, possibly using emulation. Note
|
||||
that stepping backward does not affect the target, and many windows that would ordinarily
|
||||
interact with a live target, may no longer do so, until the user steps back to the present.
|
||||
Note also that any component or script that does interact with the target and record things
|
||||
"into the present" may not cause updates in windows that are not displaying the present.</P>
|
||||
|
||||
<H3><A name="step_trace_pcode_forward"></A>Step Trace p-code Forward</H3>
|
||||
|
||||
<P>This action is available when a thread is selected. It steps the current thread forward to
|
||||
the next p-code tick, using emulation. Note that emulation does not affect the target, and many
|
||||
windows that would ordinarily interact with a live target, may not longer do so, until the user
|
||||
steps back to the present. Note also that any component or script that does interact with the
|
||||
target and record things "into the present" may not cause updates in windows that are not
|
||||
displaying the present.</P>
|
||||
</BODY>
|
||||
</HTML>
|
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
|
@ -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 — 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,679 @@
|
|||
/* ###
|
||||
* 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.awt.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.action.DockingAction;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import ghidra.GhidraOptions;
|
||||
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.pcode.UniqueRow.RefType;
|
||||
import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator;
|
||||
import ghidra.app.services.DebuggerEmulationService;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.async.SwingExecutorService;
|
||||
import ghidra.base.widgets.table.DataTypeTableCellEditor;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.options.AutoOptions;
|
||||
import ghidra.framework.options.annotation.*;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.pcode.exec.PcodeFrame;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.pcode.PcodeOp;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.util.ColorUtils;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
|
||||
public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter {
|
||||
private static final String BACKGROUND_COLOR = "Background Color";
|
||||
private static final String ADDRESS_COLOR = "Address Color";
|
||||
private static final String CONSTANT_COLOR = "Constant Color";
|
||||
private static final String REGISTERS_COLOR = "Registers Color";
|
||||
private static final String LABELS_LOCAL_COLOR = "Labels, Local Color";
|
||||
private static final String MNEMONIC_COLOR = "Mnemonic Color";
|
||||
|
||||
protected static final Comparator<Varnode> UNIQUE_COMPARATOR = (u1, u2) -> {
|
||||
assert u1.isUnique() && u2.isUnique();
|
||||
return u1.getAddress().compareTo(u2.getAddress());
|
||||
};
|
||||
|
||||
protected enum PcodeTableColumns implements EnumeratedTableColumn<PcodeTableColumns, PcodeRow> {
|
||||
SEQUENCE("Sequence", Integer.class, PcodeRow::getSequence),
|
||||
CODE("Code", String.class, PcodeRow::getCode);
|
||||
|
||||
private final String header;
|
||||
private final Function<PcodeRow, ?> getter;
|
||||
private final Class<?> cls;
|
||||
|
||||
<T> PcodeTableColumns(String header, Class<T> cls, Function<PcodeRow, T> getter) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = getter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(PcodeRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSortable() {
|
||||
return this == SEQUENCE; // HACK
|
||||
}
|
||||
}
|
||||
|
||||
protected static class PcodeTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<PcodeTableColumns, PcodeRow> {
|
||||
public PcodeTableModel() {
|
||||
super("p-code", PcodeTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PcodeTableColumns> defaultSortOrder() {
|
||||
return List.of(PcodeTableColumns.SEQUENCE);
|
||||
}
|
||||
}
|
||||
|
||||
protected enum UniqueTableColumns
|
||||
implements EnumeratedTableColumn<UniqueTableColumns, UniqueRow> {
|
||||
REF("Ref", RefType.class, UniqueRow::getRefType),
|
||||
UNIQUE("Unique", String.class, UniqueRow::getName),
|
||||
BYTES("Bytes", String.class, UniqueRow::getBytes),
|
||||
VALUE("Value", BigInteger.class, UniqueRow::getValue),
|
||||
TYPE("Type", DataType.class, UniqueRow::getDataType, UniqueRow::setDataType),
|
||||
REPR("Repr", String.class, UniqueRow::getValueRepresentation);
|
||||
|
||||
private final String header;
|
||||
private final Function<UniqueRow, ?> getter;
|
||||
private final BiConsumer<UniqueRow, Object> setter;
|
||||
private final Class<?> cls;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> UniqueTableColumns(String header, Class<T> cls, Function<UniqueRow, T> getter,
|
||||
BiConsumer<UniqueRow, T> setter) {
|
||||
this.header = header;
|
||||
this.cls = cls;
|
||||
this.getter = getter;
|
||||
this.setter = (BiConsumer<UniqueRow, Object>) setter;
|
||||
}
|
||||
|
||||
<T> UniqueTableColumns(String header, Class<T> cls, Function<UniqueRow, T> getter) {
|
||||
this(header, cls, getter, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getValueClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueOf(UniqueRow row) {
|
||||
return getter.apply(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueOf(UniqueRow row, Object value) {
|
||||
setter.accept(row, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable(UniqueRow row) {
|
||||
return setter != null;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class UniqueTableModel
|
||||
extends DefaultEnumeratedColumnTableModel<UniqueTableColumns, UniqueRow> {
|
||||
public UniqueTableModel() {
|
||||
super("Unique", UniqueTableColumns.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UniqueTableColumns> defaultSortOrder() {
|
||||
return List.of(UniqueTableColumns.UNIQUE);
|
||||
}
|
||||
}
|
||||
|
||||
class UniqueDataTypeEditor extends DataTypeTableCellEditor {
|
||||
public UniqueDataTypeEditor() {
|
||||
super(plugin.getTool());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataType resolveSelection(DataType dataType) {
|
||||
if (dataType == null) {
|
||||
return null;
|
||||
}
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(current.getTrace(), "Resolve DataType", true)) {
|
||||
return current.getTrace().getDataTypeManager().resolve(dataType, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CounterBackgroundCellRenderer extends AbstractGColumnRenderer<String> {
|
||||
Color foregroundColor = getForeground();
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
setForeground(pcodeTable.getForeground());
|
||||
boolean isCurrent = counter == data.getRowModelIndex();
|
||||
if (data.isSelected()) {
|
||||
if (isCurrent) {
|
||||
setBackground(ColorUtils.blend(counterColor, cursorColor, 0.5f));
|
||||
}
|
||||
// else background is already set. Leave it alone
|
||||
}
|
||||
else if (isCurrent) {
|
||||
setBackground(counterColor);
|
||||
}
|
||||
else {
|
||||
setBackground(pcodeTable.getBackground());
|
||||
setOpaque(true);
|
||||
}
|
||||
setBorder(noFocusBorder);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(String t, Settings settings) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
class PcodeCellRenderer extends CounterBackgroundCellRenderer {
|
||||
{
|
||||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureFont(JTable table, TableModel model, int column) {
|
||||
setFont(fixedWidthFont);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
setText(injectStyle(getText()));
|
||||
return this;
|
||||
}
|
||||
|
||||
String injectStyle(String html) {
|
||||
if (StringUtils.startsWithIgnoreCase(html, "<html>")) {
|
||||
return style + html.substring("<html>".length());
|
||||
}
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
class UniqueRefCellRenderer extends AbstractGColumnRenderer<RefType> {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
setText("");
|
||||
switch ((RefType) data.getValue()) {
|
||||
case NONE:
|
||||
setIcon(null);
|
||||
break;
|
||||
case READ:
|
||||
setIcon(DebuggerResources.ICON_UNIQUE_REF_READ);
|
||||
break;
|
||||
case WRITE:
|
||||
setIcon(DebuggerResources.ICON_UNIQUE_REF_WRITE);
|
||||
break;
|
||||
case READ_WRITE:
|
||||
setIcon(DebuggerResources.ICON_UNIQUE_REF_RW);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(RefType t, Settings settings) {
|
||||
return t.name();
|
||||
}
|
||||
}
|
||||
|
||||
protected static String createColoredStyle(String cls, Color color) {
|
||||
if (color == null) {
|
||||
return "";
|
||||
}
|
||||
return " ." + cls + " { color:" + HTMLUtilities.toHexString(color) + "; }";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!Objects.equals(a.getThread(), b.getThread())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private final DebuggerPcodeStepperPlugin plugin;
|
||||
|
||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
DebuggerCoordinates previous = DebuggerCoordinates.NOWHERE;
|
||||
int counter;
|
||||
|
||||
@AutoServiceConsumed
|
||||
private DebuggerTraceManagerService traceManager;
|
||||
@AutoServiceConsumed // NB. also by method
|
||||
private DebuggerEmulationService emulationService;
|
||||
@SuppressWarnings("unused")
|
||||
private AutoService.Wiring autoServiceWiring;
|
||||
|
||||
@AutoOptionDefined(
|
||||
name = DebuggerResources.OPTION_NAME_COLORS_PCODE_COUNTER,
|
||||
description = "Background color for the current p-code operation",
|
||||
help = @HelpInfo(anchor = "colors"))
|
||||
private Color counterColor = DebuggerResources.DEFAULT_COLOR_PCODE_COUNTER;
|
||||
|
||||
private Color backgroundColor;
|
||||
private Color cursorColor;
|
||||
private Color addressColor;
|
||||
private Color constantColor;
|
||||
private Color registerColor;
|
||||
private Color uniqueColor;
|
||||
private Color opColor;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private AutoOptions.Wiring autoOptionsWiring;
|
||||
|
||||
String style = "<html>";
|
||||
|
||||
JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
|
||||
GhidraTable uniqueTable;
|
||||
UniqueTableModel uniqueTableModel = new UniqueTableModel();
|
||||
private GhidraTableFilterPanel<UniqueRow> uniqueFilterPanel;
|
||||
|
||||
GhidraTable pcodeTable;
|
||||
PcodeTableModel pcodeTableModel = new PcodeTableModel();
|
||||
// No filter panel on p-code
|
||||
|
||||
DockingAction actionStepBackward;
|
||||
DockingAction actionStepForward;
|
||||
|
||||
public DebuggerPcodeStepperProvider(DebuggerPcodeStepperPlugin plugin) {
|
||||
super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_PCODE, plugin.getName(), null);
|
||||
this.plugin = plugin;
|
||||
|
||||
this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this);
|
||||
this.autoOptionsWiring = AutoOptions.wireOptions(plugin, this);
|
||||
|
||||
setIcon(DebuggerResources.ICON_PROVIDER_PCODE);
|
||||
setHelpLocation(DebuggerResources.HELP_PROVIDER_PCODE);
|
||||
setWindowMenuGroup(DebuggerPluginPackage.NAME);
|
||||
|
||||
buildMainPanel();
|
||||
|
||||
createActions();
|
||||
|
||||
setVisible(true);
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
@AutoOptionConsumed(
|
||||
name = DebuggerResources.OPTION_NAME_COLORS_PCODE_COUNTER)
|
||||
private void setCounterColor() {
|
||||
pcodeTableModel.fireTableDataChanged();
|
||||
}
|
||||
|
||||
@AutoOptionConsumed(
|
||||
category = GhidraOptions.CATEGORY_BROWSER_DISPLAY,
|
||||
name = BACKGROUND_COLOR)
|
||||
private void setBackgroundColor(Color backgroundColor) {
|
||||
this.backgroundColor = backgroundColor;
|
||||
if (pcodeTable != null) {
|
||||
pcodeTable.setBackground(backgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
@AutoOptionConsumed(
|
||||
category = GhidraOptions.CATEGORY_BROWSER_FIELDS,
|
||||
name = GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)
|
||||
private void setCursorColor(Color cursorColor) {
|
||||
this.cursorColor = cursorColor;
|
||||
if (pcodeTable != null) {
|
||||
pcodeTable.setSelectionBackground(cursorColor);
|
||||
}
|
||||
}
|
||||
|
||||
@AutoOptionConsumed(
|
||||
category = GhidraOptions.CATEGORY_BROWSER_DISPLAY,
|
||||
name = ADDRESS_COLOR)
|
||||
private void setAddressColor(Color addressColor) {
|
||||
this.addressColor = addressColor;
|
||||
recomputeStyle();
|
||||
}
|
||||
|
||||
@AutoOptionConsumed(
|
||||
category = GhidraOptions.CATEGORY_BROWSER_DISPLAY,
|
||||
name = CONSTANT_COLOR)
|
||||
private void setConstantColor(Color constantColor) {
|
||||
this.constantColor = constantColor;
|
||||
recomputeStyle();
|
||||
}
|
||||
|
||||
@AutoOptionConsumed(
|
||||
category = GhidraOptions.CATEGORY_BROWSER_DISPLAY,
|
||||
name = REGISTERS_COLOR)
|
||||
private void setRegisterColor(Color registerColor) {
|
||||
this.registerColor = registerColor;
|
||||
recomputeStyle();
|
||||
}
|
||||
|
||||
@AutoOptionConsumed(
|
||||
category = GhidraOptions.CATEGORY_BROWSER_DISPLAY,
|
||||
name = LABELS_LOCAL_COLOR)
|
||||
private void setUniqueColor(Color uniqueColor) {
|
||||
this.uniqueColor = uniqueColor;
|
||||
recomputeStyle();
|
||||
}
|
||||
|
||||
@AutoOptionConsumed(
|
||||
category = GhidraOptions.CATEGORY_BROWSER_DISPLAY,
|
||||
name = MNEMONIC_COLOR)
|
||||
private void setOpColor(Color opColor) {
|
||||
this.opColor = opColor;
|
||||
recomputeStyle();
|
||||
}
|
||||
|
||||
protected void recomputeStyle() {
|
||||
StringBuilder sb = new StringBuilder("<html><head><style>");
|
||||
sb.append(createColoredStyle("address", addressColor));
|
||||
sb.append(createColoredStyle("constant", constantColor));
|
||||
sb.append(createColoredStyle("register", registerColor));
|
||||
sb.append(createColoredStyle("unique", uniqueColor));
|
||||
sb.append(createColoredStyle("op", opColor));
|
||||
sb.append("</style></head>"); // NB. </html> should already be at end
|
||||
style = sb.toString();
|
||||
pcodeTableModel.fireTableDataChanged();
|
||||
}
|
||||
|
||||
protected void buildMainPanel() {
|
||||
JPanel pcodePanel = new JPanel(new BorderLayout());
|
||||
pcodeTable = new GhidraTable(pcodeTableModel);
|
||||
pcodePanel.add(new JScrollPane(pcodeTable));
|
||||
mainPanel.setLeftComponent(pcodePanel);
|
||||
|
||||
JPanel uniquePanel = new JPanel(new BorderLayout());
|
||||
uniqueTable = new GhidraTable(uniqueTableModel);
|
||||
uniquePanel.add(new JScrollPane(uniqueTable));
|
||||
uniqueFilterPanel = new GhidraTableFilterPanel<>(uniqueTable, uniqueTableModel);
|
||||
uniquePanel.add(uniqueFilterPanel, BorderLayout.SOUTH);
|
||||
mainPanel.setRightComponent(uniquePanel);
|
||||
|
||||
pcodeTable.setTableHeader(null);
|
||||
pcodeTable.setBackground(backgroundColor);
|
||||
pcodeTable.setSelectionBackground(cursorColor);
|
||||
pcodeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
pcodeTable.getSelectionModel().addListSelectionListener(evt -> {
|
||||
if (evt.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
uniqueTableModel.fireTableDataChanged();
|
||||
});
|
||||
|
||||
TableColumnModel pcodeColModel = pcodeTable.getColumnModel();
|
||||
TableColumn seqCol = pcodeColModel.getColumn(PcodeTableColumns.SEQUENCE.ordinal());
|
||||
seqCol.setCellRenderer(new CounterBackgroundCellRenderer());
|
||||
seqCol.setMinWidth(24);
|
||||
seqCol.setMaxWidth(24);
|
||||
TableColumn codeCol = pcodeColModel.getColumn(PcodeTableColumns.CODE.ordinal());
|
||||
codeCol.setCellRenderer(new PcodeCellRenderer());
|
||||
//codeCol.setPreferredWidth(75);
|
||||
|
||||
TableColumnModel uniqueColModel = uniqueTable.getColumnModel();
|
||||
TableColumn refCol = uniqueColModel.getColumn(UniqueTableColumns.REF.ordinal());
|
||||
refCol.setCellRenderer(new UniqueRefCellRenderer());
|
||||
refCol.setMinWidth(24);
|
||||
refCol.setMaxWidth(24);
|
||||
TableColumn uniqCol = uniqueColModel.getColumn(UniqueTableColumns.UNIQUE.ordinal());
|
||||
uniqCol.setPreferredWidth(45);
|
||||
TableColumn bytesCol = uniqueColModel.getColumn(UniqueTableColumns.BYTES.ordinal());
|
||||
bytesCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
bytesCol.setPreferredWidth(65);
|
||||
TableColumn valCol = uniqueColModel.getColumn(UniqueTableColumns.VALUE.ordinal());
|
||||
valCol.setCellRenderer(CustomToStringCellRenderer.MONO_BIG_HEX); // TODO: Changed coloring
|
||||
valCol.setPreferredWidth(45);
|
||||
TableColumn typeCol = uniqueColModel.getColumn(UniqueTableColumns.TYPE.ordinal());
|
||||
typeCol.setCellEditor(new UniqueDataTypeEditor());
|
||||
typeCol.setPreferredWidth(45);
|
||||
TableColumn reprCol = uniqueColModel.getColumn(UniqueTableColumns.REPR.ordinal());
|
||||
reprCol.setPreferredWidth(45);
|
||||
}
|
||||
|
||||
protected void createActions() {
|
||||
actionStepBackward = DebuggerResources.StepPcodeBackwardAction.builder(plugin)
|
||||
.enabledWhen(c -> current.getTrace() != null && current.getTime().pTickCount() != 0)
|
||||
.onAction(c -> stepBackwardActivated())
|
||||
.buildAndInstallLocal(this);
|
||||
actionStepForward = DebuggerResources.StepPcodeForwardAction.builder(plugin)
|
||||
.enabledWhen(
|
||||
c -> current.getThread() != null)
|
||||
.onAction(c -> stepForwardActivated())
|
||||
.buildAndInstallLocal(this);
|
||||
}
|
||||
|
||||
private void stepBackwardActivated() {
|
||||
if (current.getTrace() == null) {
|
||||
return;
|
||||
}
|
||||
TraceSchedule time = current.getTime().steppedPcodeBackward(1);
|
||||
if (time == null) {
|
||||
return;
|
||||
}
|
||||
traceManager.activateTime(time);
|
||||
}
|
||||
|
||||
private void stepForwardActivated() {
|
||||
if (current.getThread() == null) {
|
||||
return;
|
||||
}
|
||||
TraceSchedule time = current.getTime().steppedPcodeForward(current.getThread(), 1);
|
||||
traceManager.activateTime(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||
if (sameCoordinates(current, coordinates)) {
|
||||
current = coordinates;
|
||||
return;
|
||||
}
|
||||
previous = current;
|
||||
current = coordinates;
|
||||
|
||||
doLoadPcodeFrame();
|
||||
|
||||
setSubTitle(current.getTime().toString());
|
||||
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
protected void populateSingleton(PcodeRow row) {
|
||||
counter = 0;
|
||||
pcodeTableModel.clear();
|
||||
pcodeTableModel.add(row);
|
||||
uniqueTableModel.clear();
|
||||
}
|
||||
|
||||
protected void populateFromFrame(PcodeFrame frame, PcodeExecutorState<byte[]> state) {
|
||||
populatePcode(frame);
|
||||
populateUnique(frame, state);
|
||||
}
|
||||
|
||||
protected void populatePcode(PcodeFrame frame) {
|
||||
Language language = current.getTrace().getBaseLanguage();
|
||||
int index = frame.index();
|
||||
List<PcodeRow> toAdd = frame.getCode()
|
||||
.stream()
|
||||
.map(op -> new OpPcodeRow(language, op, index == op.getSeqnum().getTime()))
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
if (frame.isBranch()) {
|
||||
counter = toAdd.size();
|
||||
toAdd.add(new BranchPcodeRow(counter, frame.getBranched()));
|
||||
}
|
||||
else if (frame.isFallThrough()) {
|
||||
counter = toAdd.size();
|
||||
toAdd.add(new FallthroughPcodeRow(counter));
|
||||
}
|
||||
else {
|
||||
counter = index;
|
||||
}
|
||||
pcodeTableModel.clear();
|
||||
pcodeTableModel.addAll(toAdd);
|
||||
pcodeTable.getSelectionModel().setSelectionInterval(counter, counter);
|
||||
pcodeTable.scrollToSelectedRow();
|
||||
}
|
||||
|
||||
protected void populateUnique(PcodeFrame frame, PcodeExecutorState<byte[]> state) {
|
||||
Language language = current.getTrace().getBaseLanguage();
|
||||
// NOTE: They may overlap. I don't think I care.
|
||||
Set<Varnode> uniques = new TreeSet<>(UNIQUE_COMPARATOR);
|
||||
for (PcodeOp op : frame.getCode()) {
|
||||
Varnode out = op.getOutput();
|
||||
if (out != null && out.isUnique()) {
|
||||
uniques.add(out);
|
||||
}
|
||||
for (Varnode in : op.getInputs()) {
|
||||
if (in.isUnique()) {
|
||||
uniques.add(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: Highlight uniques that the selected op(s) reference
|
||||
// (including overlaps)
|
||||
// TODO: Permit modification of unique variables
|
||||
List<UniqueRow> toAdd =
|
||||
uniques.stream()
|
||||
.map(u -> new UniqueRow(this, language, state, u))
|
||||
.collect(Collectors.toList());
|
||||
uniqueTableModel.clear();
|
||||
uniqueTableModel.addAll(toAdd);
|
||||
}
|
||||
|
||||
protected void doLoadPcodeFrame() {
|
||||
if (emulationService == null) {
|
||||
return;
|
||||
}
|
||||
DebuggerCoordinates current = this.current; // Volatile, also after background
|
||||
Trace trace = current.getTrace();
|
||||
if (trace == null) {
|
||||
return;
|
||||
}
|
||||
if (current.getThread() == null) {
|
||||
populateSingleton(EnumPcodeRow.NO_THREAD);
|
||||
return;
|
||||
}
|
||||
TraceSchedule time = current.getTime();
|
||||
if (time.pTickCount() == 0) {
|
||||
populateSingleton(EnumPcodeRow.DECODE);
|
||||
return;
|
||||
}
|
||||
DebuggerTracePcodeEmulator emu = emulationService.getCachedEmulator(trace, time);
|
||||
if (emu != null) {
|
||||
doLoadPcodeFrameFromEmulator(emu);
|
||||
return;
|
||||
}
|
||||
emulationService.backgroundEmulate(trace, time).thenAcceptAsync(__ -> {
|
||||
if (current != this.current) {
|
||||
return;
|
||||
}
|
||||
doLoadPcodeFrameFromEmulator(emulationService.getCachedEmulator(trace, time));
|
||||
}, SwingExecutorService.INSTANCE);
|
||||
}
|
||||
|
||||
protected void doLoadPcodeFrameFromEmulator(DebuggerTracePcodeEmulator emu) {
|
||||
PcodeThread<byte[]> thread = emu.getThread(current.getThread().getPath(), false);
|
||||
if (thread == null) {
|
||||
/**
|
||||
* Happens when focus is on a thread not stepped in the schedule. Stepping it would
|
||||
* create it and decode its first instruction.
|
||||
*/
|
||||
populateSingleton(EnumPcodeRow.DECODE);
|
||||
return;
|
||||
}
|
||||
PcodeFrame frame = thread.getFrame();
|
||||
if (frame == null) {
|
||||
/**
|
||||
* Happens when an instruction is completed via p-code stepping, but the next
|
||||
* instruction has not been decoded, yet.
|
||||
*/
|
||||
populateSingleton(EnumPcodeRow.DECODE);
|
||||
return;
|
||||
}
|
||||
populateFromFrame(frame, thread.getState());
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setEmulationService(DebuggerEmulationService emulationService) {
|
||||
doLoadPcodeFrame();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -45,17 +45,27 @@ public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
|
|||
}
|
||||
|
||||
public CompletableFuture<Void> stepOpAsync(PcodeOp op, PcodeFrame frame,
|
||||
Map<Integer, String> useropNames, SleighUseropLibrary<CompletableFuture<T>> library) {
|
||||
SleighUseropLibrary<CompletableFuture<T>> library) {
|
||||
if (op.getOpcode() == PcodeOp.CBRANCH) {
|
||||
return executeConditionalBranchAsync(op, frame);
|
||||
}
|
||||
stepOp(op, frame, useropNames, library);
|
||||
stepOp(op, frame, library);
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> stepAsync(PcodeFrame frame, Map<Integer, String> useropNames,
|
||||
public CompletableFuture<Void> stepAsync(PcodeFrame frame,
|
||||
SleighUseropLibrary<CompletableFuture<T>> library) {
|
||||
return stepOpAsync(frame.nextOp(), frame, useropNames, library);
|
||||
try {
|
||||
return stepOpAsync(frame.nextOp(), frame, library);
|
||||
}
|
||||
catch (PcodeExecutionException e) {
|
||||
e.frame = frame;
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return CompletableFuture.failedFuture(
|
||||
new PcodeExecutionException("Exception during pcode execution", frame, e));
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> executeConditionalBranchAsync(PcodeOp op, PcodeFrame frame) {
|
||||
|
@ -68,23 +78,23 @@ public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
|
|||
});
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> executeAsync(SleighProgram program,
|
||||
public CompletableFuture<Void> executeAsync(PcodeProgram program,
|
||||
SleighUseropLibrary<CompletableFuture<T>> library) {
|
||||
return executeAsync(program.code, program.useropNames, library);
|
||||
}
|
||||
|
||||
protected CompletableFuture<Void> executeAsyncLoop(PcodeFrame frame,
|
||||
Map<Integer, String> useropNames, SleighUseropLibrary<CompletableFuture<T>> library) {
|
||||
SleighUseropLibrary<CompletableFuture<T>> library) {
|
||||
if (frame.isFinished()) {
|
||||
return AsyncUtils.NIL;
|
||||
}
|
||||
return stepAsync(frame, useropNames, library)
|
||||
.thenComposeAsync(__ -> executeAsyncLoop(frame, useropNames, library));
|
||||
return stepAsync(frame, library)
|
||||
.thenComposeAsync(__ -> executeAsyncLoop(frame, library));
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> executeAsync(List<PcodeOp> code,
|
||||
Map<Integer, String> useropNames, SleighUseropLibrary<CompletableFuture<T>> library) {
|
||||
PcodeFrame frame = new PcodeFrame(code);
|
||||
return executeAsyncLoop(frame, useropNames, library);
|
||||
PcodeFrame frame = new PcodeFrame(language, code, useropNames);
|
||||
return executeAsyncLoop(frame, library);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,11 @@ public class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<Completab
|
|||
return CompletableFuture.completedFuture(arithmetic.fromConst(value, size));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<T> fromConst(BigInteger value, int size) {
|
||||
return CompletableFuture.completedFuture(arithmetic.fromConst(value, size));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrue(CompletableFuture<T> cond) {
|
||||
if (!cond.isDone()) {
|
||||
|
@ -68,4 +73,12 @@ public class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<Completab
|
|||
}
|
||||
return arithmetic.isTrue(cond.getNow(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger toConcrete(CompletableFuture<T> cond) {
|
||||
if (!cond.isDone()) {
|
||||
throw new AssertionError("You need a better 8-ball");
|
||||
}
|
||||
return arithmetic.toConcrete(cond.getNow(null));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ import java.util.concurrent.CompletableFuture;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
|
||||
public class AsyncWrappedPcodeExecutorStatePiece<A, T>
|
||||
implements PcodeExecutorStatePiece<CompletableFuture<A>, CompletableFuture<T>> {
|
||||
|
@ -30,6 +32,10 @@ public class AsyncWrappedPcodeExecutorStatePiece<A, T>
|
|||
this.state = state;
|
||||
}
|
||||
|
||||
protected boolean isWriteDone() {
|
||||
return lastWrite.isDone();
|
||||
}
|
||||
|
||||
protected <U> CompletableFuture<U> nextRead(Supplier<CompletableFuture<U>> next) {
|
||||
return lastWrite.thenCompose(__ -> next.get()).exceptionally(ex -> null);
|
||||
}
|
||||
|
@ -68,4 +74,12 @@ public class AsyncWrappedPcodeExecutorStatePiece<A, T>
|
|||
public CompletableFuture<A> longToOffset(AddressSpace space, long l) {
|
||||
return CompletableFuture.completedFuture(state.longToOffset(space, l));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address) {
|
||||
if (!isWriteDone()) {
|
||||
throw new AssertionError("An async write is still pending");
|
||||
}
|
||||
return state.getConcreteBuffer(address);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public enum TracePcodeUtils {
|
|||
PcodeExecutorState<CompletableFuture<byte[]>> state;
|
||||
if (coordinates.getRecorder() == null) {
|
||||
state = new AsyncWrappedPcodeExecutorState<>(
|
||||
new TraceBytesPcodeExecutorState(trace, coordinates.getSnap(),
|
||||
new TraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(),
|
||||
coordinates.getThread(), coordinates.getFrame()));
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/* ###
|
||||
* 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 org.junit.*;
|
||||
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.app.plugin.assembler.Assembler;
|
||||
import ghidra.app.plugin.assembler.Assemblers;
|
||||
import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.pcode.exec.PcodeExecutor;
|
||||
import ghidra.pcode.exec.trace.TraceSleighUtils;
|
||||
import ghidra.test.ToyProgramBuilder;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import help.screenshot.GhidraScreenShotGenerator;
|
||||
|
||||
public class DebuggerPcodeStepperPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
DebuggerTraceManagerService traceManager;
|
||||
DebuggerPcodeStepperPlugin pcodePlugin;
|
||||
DebuggerPcodeStepperProvider pcodeProvider;
|
||||
ToyDBTraceBuilder tb;
|
||||
|
||||
@Before
|
||||
public void setUpMine() throws Throwable {
|
||||
traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class);
|
||||
pcodePlugin = addPlugin(tool, DebuggerPcodeStepperPlugin.class);
|
||||
|
||||
pcodeProvider = waitForComponentProvider(DebuggerPcodeStepperProvider.class);
|
||||
|
||||
tb = new ToyDBTraceBuilder("echo", ToyProgramBuilder._X64);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDownMine() {
|
||||
tb.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaptureDebuggerPcodeStepperPlugin() throws Throwable {
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
long snap0 = tb.trace.getTimeManager().createSnapshot("First").getKey();
|
||||
|
||||
tb.trace.getMemoryManager()
|
||||
.addRegion("[echo:.text]", Range.atLeast(snap0),
|
||||
tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ,
|
||||
TraceMemoryFlag.EXECUTE);
|
||||
|
||||
TraceThread thread = tb.getOrAddThread("[1]", snap0);
|
||||
|
||||
PcodeExecutor<byte[]> exe =
|
||||
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
|
||||
exe.executeLine("RIP = 0x00400000");
|
||||
exe.executeLine("RSP = 0x0010fff8");
|
||||
|
||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(snap0));
|
||||
asm.assemble(tb.addr(0x00400000), "SUB RSP,0x40");
|
||||
|
||||
traceManager.openTrace(tb.trace);
|
||||
traceManager.activateThread(thread);
|
||||
traceManager.activateTime(TraceSchedule.parse("0:.t0-7"));
|
||||
|
||||
pcodeProvider.mainPanel.setDividerLocation(0.4);
|
||||
captureIsolatedProvider(pcodeProvider, 900, 300);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import ghidra.app.services.DebuggerTraceManagerService;
|
|||
import ghidra.test.ToyProgramBuilder;
|
||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import help.screenshot.GhidraScreenShotGenerator;
|
||||
|
@ -65,18 +66,18 @@ public class DebuggerTimePluginScreenShots extends GhidraScreenShotGenerator {
|
|||
snap = tb.trace.getTimeManager().createSnapshot("Thread BREAKPOINT_HIT");
|
||||
snap.setEventThread(thread);
|
||||
snap.setRealTime(fakeClock);
|
||||
snap.setTicks(1);
|
||||
fakeClock += 2300;
|
||||
|
||||
snap = tb.trace.getTimeManager().createSnapshot("Thread STEP_COMPLETED");
|
||||
snap.setEventThread(thread);
|
||||
snap.setRealTime(fakeClock);
|
||||
snap.setTicks(1);
|
||||
snap.setSchedule(TraceSchedule.parse(snap.getKey() - 1 + ":1"));
|
||||
fakeClock += 444;
|
||||
|
||||
snap = tb.trace.getTimeManager().createSnapshot("Thread STEP_COMPLETED");
|
||||
snap.setEventThread(thread);
|
||||
snap.setRealTime(fakeClock);
|
||||
snap.setSchedule(TraceSchedule.parse(snap.getKey() - 1 + ":1"));
|
||||
fakeClock += 100;
|
||||
}
|
||||
|
||||
|
|
|
@ -448,28 +448,28 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
|
||||
@Test
|
||||
public void testActionStepTraceBackward() throws Exception {
|
||||
assertFalse(threadsProvider.actionStepTraceBackward.isEnabled());
|
||||
assertFalse(threadsProvider.actionStepSnapBackward.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
addThreads();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertFalse(threadsProvider.actionStepTraceBackward.isEnabled());
|
||||
assertFalse(threadsProvider.actionStepSnapBackward.isEnabled());
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.trace.getTimeManager().getSnapshot(10, true);
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
assertFalse(threadsProvider.actionStepTraceBackward.isEnabled());
|
||||
assertFalse(threadsProvider.actionStepSnapBackward.isEnabled());
|
||||
|
||||
traceManager.activateSnap(2);
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(threadsProvider.actionStepTraceBackward.isEnabled());
|
||||
assertTrue(threadsProvider.actionStepSnapBackward.isEnabled());
|
||||
|
||||
performAction(threadsProvider.actionStepTraceBackward);
|
||||
performAction(threadsProvider.actionStepSnapBackward);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(1, traceManager.getCurrentSnap());
|
||||
|
@ -477,32 +477,32 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
|
||||
@Test
|
||||
public void testActionStepTraceForward() throws Exception {
|
||||
assertFalse(threadsProvider.actionStepTraceForward.isEnabled());
|
||||
assertFalse(threadsProvider.actionStepSnapForward.isEnabled());
|
||||
|
||||
createAndOpenTrace();
|
||||
addThreads();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertFalse(threadsProvider.actionStepTraceForward.isEnabled());
|
||||
assertFalse(threadsProvider.actionStepSnapForward.isEnabled());
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.trace.getTimeManager().getSnapshot(10, true);
|
||||
}
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
assertTrue(threadsProvider.actionStepTraceForward.isEnabled());
|
||||
assertTrue(threadsProvider.actionStepSnapForward.isEnabled());
|
||||
|
||||
performAction(threadsProvider.actionStepTraceForward);
|
||||
performAction(threadsProvider.actionStepSnapForward);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(1, traceManager.getCurrentSnap());
|
||||
assertTrue(threadsProvider.actionStepTraceForward.isEnabled());
|
||||
assertTrue(threadsProvider.actionStepSnapForward.isEnabled());
|
||||
|
||||
traceManager.activateSnap(10);
|
||||
waitForSwing();
|
||||
|
||||
assertFalse(threadsProvider.actionStepTraceForward.isEnabled());
|
||||
assertFalse(threadsProvider.actionStepSnapForward.isEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.junit.Test;
|
|||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.trace.database.time.DBTraceTimeManager;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.time.TraceSchedule;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
|
||||
|
@ -44,11 +45,21 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
DBTraceTimeManager timeManager = tb.trace.getTimeManager();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
TraceSnapshot first = timeManager.createSnapshot("First");
|
||||
first.setTicks(123);
|
||||
Calendar c = Calendar.getInstance(); // System time zone
|
||||
c.set(2020, 0, 1, 9, 0, 0);
|
||||
first.setRealTime(c.getTimeInMillis());
|
||||
timeManager.getSnapshot(10, true).setDescription("Snap 10");
|
||||
TraceSnapshot second = timeManager.getSnapshot(10, true);
|
||||
second.setDescription("Snap 10");
|
||||
second.setSchedule(TraceSchedule.parse("0:5,t1-5"));
|
||||
}
|
||||
}
|
||||
|
||||
protected void addScratchSnapshot() {
|
||||
DBTraceTimeManager timeManager = tb.trace.getTimeManager();
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
TraceSnapshot scratch = timeManager.getSnapshot(Long.MIN_VALUE, true);
|
||||
scratch.setDescription("Scratch");
|
||||
scratch.setSchedule(TraceSchedule.parse("0:t0-5"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,13 +75,13 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
SnapshotRow firstRow = snapsDisplayed.get(0);
|
||||
assertEquals(0, firstRow.getSnap());
|
||||
assertEquals("First", firstRow.getDescription());
|
||||
assertEquals(123, firstRow.getTicks());
|
||||
assertEquals("0", firstRow.getSchedule()); // Snap 0 has "0" schedule
|
||||
assertEquals("Jan 01, 2020 09:00 AM", firstRow.getTimeStamp());
|
||||
|
||||
SnapshotRow secondRow = snapsDisplayed.get(1);
|
||||
assertEquals(10, secondRow.getSnap());
|
||||
assertEquals("Snap 10", secondRow.getDescription());
|
||||
assertEquals(0, secondRow.getTicks());
|
||||
assertEquals("0:5,t1-5", secondRow.getSchedule());
|
||||
// Timestamp is left unchecked, since default is current time
|
||||
}
|
||||
|
||||
|
@ -289,4 +300,86 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
|
||||
assertNull(timeProvider.snapshotFilterPanel.getSelectedItem());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddScratchThenActivateIsHidden() throws Exception {
|
||||
createSnaplessTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
addSnapshots();
|
||||
addScratchSnapshot();
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
||||
assertEquals(2, data.size());
|
||||
for (SnapshotRow row : data) {
|
||||
assertTrue(row.getSnap() >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActiveThenAddScratchIsHidden() throws Exception {
|
||||
createSnaplessTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
addSnapshots();
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
|
||||
|
||||
addScratchSnapshot();
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
List<SnapshotRow> data = timeProvider.snapshotTableModel.getModelData();
|
||||
assertEquals(2, data.size());
|
||||
for (SnapshotRow row : data) {
|
||||
assertTrue(row.getSnap() >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddScratchThenActivateThenToggleIsShown() throws Exception {
|
||||
createSnaplessTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
addSnapshots();
|
||||
addScratchSnapshot();
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(true, timeProvider.hideScratch);
|
||||
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
|
||||
|
||||
performAction(timeProvider.actionHideScratch);
|
||||
|
||||
assertEquals(false, timeProvider.hideScratch);
|
||||
assertEquals(3, timeProvider.snapshotTableModel.getModelData().size());
|
||||
|
||||
performAction(timeProvider.actionHideScratch);
|
||||
|
||||
assertEquals(true, timeProvider.hideScratch);
|
||||
assertEquals(2, timeProvider.snapshotTableModel.getModelData().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToggleThenAddScratchThenActivateIsShown() throws Exception {
|
||||
performAction(timeProvider.actionHideScratch);
|
||||
|
||||
createSnaplessTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
addSnapshots();
|
||||
addScratchSnapshot();
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(false, timeProvider.hideScratch);
|
||||
assertEquals(3, timeProvider.snapshotTableModel.getModelData().size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import static org.junit.Assert.*;
|
|||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -314,7 +315,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
|
|||
public void testAddMappingThenTranslateTraceViewToStaticEmpty() throws Exception {
|
||||
addMapping();
|
||||
|
||||
Map<Program, AddressSetView> views =
|
||||
Map<Program, Pair<Long, AddressSetView>> views =
|
||||
mappingService.getOpenMappedViews(tb.trace, new AddressSet(), 0);
|
||||
assertTrue(views.isEmpty());
|
||||
}
|
||||
|
@ -335,9 +336,12 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
|
|||
// After
|
||||
set.add(dynSpace.getAddress(0xbadbadbadL), dynSpace.getAddress(0xbadbadbadL + 0xff));
|
||||
|
||||
Map<Program, AddressSetView> views = mappingService.getOpenMappedViews(tb.trace, set, 0);
|
||||
Map<Program, Pair<Long, AddressSetView>> views =
|
||||
mappingService.getOpenMappedViews(tb.trace, set, 0);
|
||||
assertEquals(1, views.size());
|
||||
AddressSetView inStatic = views.get(program);
|
||||
Pair<Long, AddressSetView> pair = views.get(program);
|
||||
assertEquals(0x100000, pair.getLeft().longValue());
|
||||
AddressSetView inStatic = pair.getRight();
|
||||
assertEquals(3, inStatic.getNumAddressRanges());
|
||||
AddressSet expected = new AddressSet();
|
||||
expected.add(stSpace.getAddress(0x00200000), stSpace.getAddress(0x002000ff));
|
||||
|
@ -352,7 +356,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
|
|||
copyTrace();
|
||||
add2ndMapping();
|
||||
|
||||
Map<TraceSnap, AddressSetView> views =
|
||||
Map<TraceSnap, Pair<Long, AddressSetView>> views =
|
||||
mappingService.getOpenMappedViews(program, new AddressSet());
|
||||
assertTrue(views.isEmpty());
|
||||
}
|
||||
|
@ -375,12 +379,13 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
|
|||
// After
|
||||
set.add(stSpace.getAddress(0xbadbadbadL), stSpace.getAddress(0xbadbadbadL + 0xff));
|
||||
|
||||
Map<TraceSnap, AddressSetView> views = mappingService.getOpenMappedViews(program, set);
|
||||
Map<TraceSnap, Pair<Long, AddressSetView>> views =
|
||||
mappingService.getOpenMappedViews(program, set);
|
||||
Msg.info(this, views);
|
||||
assertEquals(2, views.size());
|
||||
AddressSetView in1st = views.get(new DefaultTraceSnap(tb.trace, 0));
|
||||
AddressSetView in1st = views.get(new DefaultTraceSnap(tb.trace, 0)).getRight();
|
||||
assertEquals(5, in1st.getNumAddressRanges());
|
||||
AddressSetView in2nd = views.get(new DefaultTraceSnap(copy, 0));
|
||||
AddressSetView in2nd = views.get(new DefaultTraceSnap(copy, 0)).getRight();
|
||||
assertEquals(3, in2nd.getNumAddressRanges());
|
||||
|
||||
AddressSet expectedIn1st = new AddressSet();
|
||||
|
|
|
@ -90,7 +90,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
|
|||
Trace trace = recorder.getTrace();
|
||||
Language language = trace.getBaseLanguage();
|
||||
|
||||
SleighProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language, "test",
|
||||
PcodeProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language, "test",
|
||||
List.of("r2 = r0 + r1;"), SleighUseropLibrary.NIL);
|
||||
|
||||
TraceRecorderAsyncPcodeExecutorState asyncState =
|
||||
|
|
|
@ -28,4 +28,5 @@ dependencies {
|
|||
annotationProcessor project(':AnnotationValidator')
|
||||
|
||||
testCompile project(':Base')
|
||||
testRuntime project(':ARM') // For its emulator state modifier
|
||||
}
|
||||
|
|
|
@ -23,11 +23,14 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||
import ghidra.generic.util.datastruct.SemisparseByteArray;
|
||||
import ghidra.pcode.exec.*;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
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.trace.util.DefaultTraceTimeViewport;
|
||||
|
||||
public class TraceBytesPcodeExecutorState
|
||||
extends AbstractLongOffsetPcodeExecutorState<byte[], TraceMemorySpace> {
|
||||
|
@ -38,12 +41,17 @@ public class TraceBytesPcodeExecutorState
|
|||
private TraceThread thread;
|
||||
private int frame;
|
||||
|
||||
private final DefaultTraceTimeViewport viewport;
|
||||
|
||||
public TraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()));
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
this.thread = thread;
|
||||
this.frame = frame;
|
||||
|
||||
this.viewport = new DefaultTraceTimeViewport(trace);
|
||||
this.viewport.setSnap(snap);
|
||||
}
|
||||
|
||||
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() {
|
||||
|
@ -81,6 +89,7 @@ public class TraceBytesPcodeExecutorState
|
|||
|
||||
public void setSnap(long snap) {
|
||||
this.snap = snap;
|
||||
this.viewport.setSnap(snap);
|
||||
}
|
||||
|
||||
public long getSnap() {
|
||||
|
@ -147,10 +156,15 @@ public class TraceBytesPcodeExecutorState
|
|||
@Override
|
||||
protected byte[] getFromSpace(TraceMemorySpace space, long offset, int size) {
|
||||
ByteBuffer buf = ByteBuffer.allocate(size);
|
||||
int read = space.getBytes(snap, space.getAddressSpace().getAddress(offset), buf);
|
||||
int read = space.getViewBytes(snap, space.getAddressSpace().getAddress(offset), buf);
|
||||
if (read != size) {
|
||||
throw new RuntimeException("Could not read full value from trace");
|
||||
}
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address) {
|
||||
return trace.getMemoryManager().getBufferAt(snap, address);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
/* ###
|
||||
* 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.pcode.exec.trace;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
|
||||
import ghidra.generic.util.datastruct.SemisparseByteArray;
|
||||
import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorState;
|
||||
import ghidra.pcode.exec.BytesPcodeArithmetic;
|
||||
import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState.CachedSpace;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.MemBufferAdapter;
|
||||
import ghidra.util.MathUtilities;
|
||||
|
||||
/**
|
||||
* A state which reads bytes from a trace, but caches writes internally.
|
||||
*
|
||||
* <p>
|
||||
* This provides for "read-only" emulation on a trace. Writes do not affect the source trace, but
|
||||
* rather are cached in this state. If desired, those cached writes can be written back out at a
|
||||
* later time.
|
||||
*/
|
||||
public class TraceCachedWriteBytesPcodeExecutorState
|
||||
extends AbstractLongOffsetPcodeExecutorState<byte[], CachedSpace> {
|
||||
|
||||
protected class StateMemBuffer implements MemBufferAdapter {
|
||||
protected final Address address;
|
||||
protected final CachedSpace source;
|
||||
|
||||
public StateMemBuffer(Address address, CachedSpace source) {
|
||||
this.address = address;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Memory getMemory() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBigEndian() {
|
||||
return trace.getBaseLanguage().isBigEndian();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(ByteBuffer buffer, int addressOffset) {
|
||||
byte[] data = source.read(address.getOffset() + addressOffset, buffer.remaining());
|
||||
buffer.put(data);
|
||||
return data.length;
|
||||
}
|
||||
}
|
||||
|
||||
protected final Map<AddressSpace, CachedSpace> spaces = new HashMap<>();
|
||||
|
||||
protected final Trace trace;
|
||||
protected final long snap;
|
||||
protected final TraceThread thread;
|
||||
protected final int frame;
|
||||
|
||||
public TraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()));
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
this.thread = thread;
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
protected static class CachedSpace {
|
||||
protected final SemisparseByteArray cache = new SemisparseByteArray();
|
||||
protected final RangeSet<UnsignedLong> written = TreeRangeSet.create();
|
||||
protected final AddressSpace space;
|
||||
protected final TraceMemorySpace source;
|
||||
protected final long snap;
|
||||
|
||||
public CachedSpace(AddressSpace space, TraceMemorySpace source, long snap) {
|
||||
this.space = space;
|
||||
this.source = source;
|
||||
this.snap = snap;
|
||||
}
|
||||
|
||||
public void write(long offset, byte[] val) {
|
||||
cache.putData(offset, val);
|
||||
UnsignedLong uLoc = UnsignedLong.fromLongBits(offset);
|
||||
UnsignedLong uEnd = UnsignedLong.fromLongBits(offset + val.length);
|
||||
written.add(Range.closedOpen(uLoc, uEnd));
|
||||
}
|
||||
|
||||
public static long lower(Range<UnsignedLong> rng) {
|
||||
return rng.lowerBoundType() == BoundType.CLOSED
|
||||
? rng.lowerEndpoint().longValue()
|
||||
: rng.lowerEndpoint().longValue() + 1;
|
||||
}
|
||||
|
||||
public static long upper(Range<UnsignedLong> rng) {
|
||||
return rng.upperBoundType() == BoundType.CLOSED
|
||||
? rng.upperEndpoint().longValue()
|
||||
: rng.upperEndpoint().longValue() - 1;
|
||||
}
|
||||
|
||||
public byte[] read(long offset, int size) {
|
||||
if (source != null) {
|
||||
// TODO: Warn or bail when reading UNKNOWN bytes
|
||||
// NOTE: Not going to worry about gaps here:
|
||||
RangeSet<UnsignedLong> uninitialized =
|
||||
cache.getUninitialized(offset, offset + size);
|
||||
if (!uninitialized.isEmpty()) {
|
||||
Range<UnsignedLong> toRead = uninitialized.span();
|
||||
assert toRead.hasUpperBound() && toRead.hasLowerBound();
|
||||
long lower = lower(toRead);
|
||||
long upper = upper(toRead);
|
||||
ByteBuffer buf = ByteBuffer.allocate((int) (upper - lower + 1));
|
||||
source.getBytes(snap, space.getAddress(lower), buf);
|
||||
cache.putData(lower, buf.array());
|
||||
}
|
||||
}
|
||||
byte[] data = new byte[size];
|
||||
cache.getData(offset, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
// Must already have started a transaction
|
||||
protected void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
if (space.isUniqueSpace()) {
|
||||
return;
|
||||
}
|
||||
byte[] data = new byte[4096];
|
||||
ByteBuffer buf = ByteBuffer.wrap(data);
|
||||
TraceMemorySpace mem =
|
||||
TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true);
|
||||
for (Range<UnsignedLong> range : written.asRanges()) {
|
||||
assert range.lowerBoundType() == BoundType.CLOSED;
|
||||
assert range.upperBoundType() == BoundType.OPEN;
|
||||
long lower = range.lowerEndpoint().longValue();
|
||||
long fullLen = range.upperEndpoint().longValue() - lower;
|
||||
while (fullLen > 0) {
|
||||
int len = MathUtilities.unsignedMin(data.length, fullLen);
|
||||
cache.getData(lower, data, 0, len);
|
||||
buf.position(0);
|
||||
buf.limit(len);
|
||||
mem.putBytes(snap, space.getAddress(lower), buf);
|
||||
|
||||
lower += len;
|
||||
fullLen -= len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Trace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
||||
public long getSnap() {
|
||||
return snap;
|
||||
}
|
||||
|
||||
public TraceThread getThread() {
|
||||
return thread;
|
||||
}
|
||||
|
||||
public int getFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the accumulated writes into the given trace
|
||||
*
|
||||
* <p>
|
||||
* NOTE: This method requires a transaction to have already been started on the destination
|
||||
* trace.
|
||||
*
|
||||
* @param trace the trace to modify
|
||||
* @param snap the snap within the trace
|
||||
* @param thread the thread to take register writes
|
||||
* @param frame the frame for register writes
|
||||
*/
|
||||
public void writeCacheDown(Trace trace, long snap, TraceThread thread, int frame) {
|
||||
if (trace.getBaseLanguage() != language) {
|
||||
throw new IllegalArgumentException("Destination trace must be same language as source");
|
||||
}
|
||||
for (CachedSpace cached : spaces.values()) {
|
||||
cached.writeDown(trace, snap, thread, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long offsetToLong(byte[] offset) {
|
||||
return Utils.bytesToLong(offset, offset.length, language.isBigEndian());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] longToOffset(AddressSpace space, long l) {
|
||||
return arithmetic.fromConst(l, space.getPointerSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) {
|
||||
return spaces.computeIfAbsent(space, s -> {
|
||||
TraceMemorySpace tms = s.isUniqueSpace() ? null
|
||||
: TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, false);
|
||||
return new CachedSpace(s, tms, snap);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setInSpace(CachedSpace space, long offset, int size, byte[] val) {
|
||||
assert size == val.length;
|
||||
space.write(offset, val);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] getFromSpace(CachedSpace space, long offset, int size) {
|
||||
byte[] read = space.read(offset, size);
|
||||
if (read.length != size) {
|
||||
Address addr = space.space.getAddress(offset);
|
||||
throw new UnknownStatePcodeExecutionException("Incomplete read (" + read.length +
|
||||
" of " + size + " bytes)", language, addr.add(read.length), size - read.length);
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address) {
|
||||
return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false));
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package ghidra.pcode.exec.trace;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import ghidra.pcode.exec.PcodeArithmetic;
|
||||
import ghidra.pcode.opbehavior.BinaryOpBehavior;
|
||||
import ghidra.pcode.opbehavior.UnaryOpBehavior;
|
||||
|
@ -43,8 +45,18 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic<TraceMemo
|
|||
return TraceMemoryState.KNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceMemoryState fromConst(BigInteger value, int size) {
|
||||
return TraceMemoryState.KNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrue(TraceMemoryState cond) {
|
||||
throw new AssertionError("Cannot decide branches using TraceMemoryState");
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger toConcrete(TraceMemoryState value) {
|
||||
throw new AssertionError("Cannot make TraceMemoryState a 'concrete value'");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,10 +23,12 @@ import com.google.common.primitives.UnsignedLong;
|
|||
import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorStatePiece;
|
||||
import ghidra.pcode.utils.Utils;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.mem.MemBuffer;
|
||||
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.trace.util.DefaultTraceTimeViewport;
|
||||
|
||||
public class TraceMemoryStatePcodeExecutorStatePiece extends
|
||||
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, TraceMemorySpace> {
|
||||
|
@ -37,6 +39,8 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
private TraceThread thread;
|
||||
private int frame;
|
||||
|
||||
private final DefaultTraceTimeViewport viewport;
|
||||
|
||||
public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
|
||||
int frame) {
|
||||
super(trace.getBaseLanguage(), TraceMemoryStatePcodeArithmetic.INSTANCE);
|
||||
|
@ -44,6 +48,9 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
this.snap = snap;
|
||||
this.thread = thread;
|
||||
this.frame = frame;
|
||||
|
||||
this.viewport = new DefaultTraceTimeViewport(trace);
|
||||
this.viewport.setSnap(snap);
|
||||
}
|
||||
|
||||
public Trace getTrace() {
|
||||
|
@ -52,6 +59,7 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
|
||||
public void setSnap(long snap) {
|
||||
this.snap = snap;
|
||||
this.viewport.setSnap(snap);
|
||||
}
|
||||
|
||||
public long getSnap() {
|
||||
|
@ -137,7 +145,15 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
|
|||
@Override
|
||||
protected TraceMemoryState getFromSpace(TraceMemorySpace space, long offset, int size) {
|
||||
AddressSet set = new AddressSet(range(space.getAddressSpace(), offset, size));
|
||||
set.delete(space.getAddressesWithState(snap, set, s -> s == TraceMemoryState.KNOWN));
|
||||
for (long snap : viewport.getOrderedSnaps()) {
|
||||
set.delete(
|
||||
space.getAddressesWithState(snap, set, state -> state == TraceMemoryState.KNOWN));
|
||||
}
|
||||
return set.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemBuffer getConcreteBuffer(Address address) {
|
||||
throw new AssertionError("Cannot make TraceMemoryState into a concrete buffer");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/* ###
|
||||
* 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.pcode.exec.trace;
|
||||
|
||||
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
|
||||
import ghidra.pcode.emu.AbstractPcodeEmulator;
|
||||
import ghidra.pcode.emu.PcodeThread;
|
||||
import ghidra.pcode.exec.PcodeExecutorState;
|
||||
import ghidra.pcode.exec.SleighUseropLibrary;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.stack.TraceStack;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.model.thread.TraceThreadManager;
|
||||
|
||||
/**
|
||||
* An emulator that can read initial state from a trace
|
||||
*/
|
||||
public class TracePcodeEmulator extends AbstractPcodeEmulator {
|
||||
private static SleighLanguage assertSleigh(Language language) {
|
||||
if (!(language instanceof SleighLanguage)) {
|
||||
throw new IllegalArgumentException("Emulation requires a sleigh language");
|
||||
}
|
||||
return (SleighLanguage) language;
|
||||
}
|
||||
|
||||
protected final Trace trace;
|
||||
protected final long snap;
|
||||
|
||||
public TracePcodeEmulator(Trace trace, long snap, SleighUseropLibrary<byte[]> library) {
|
||||
super(assertSleigh(trace.getBaseLanguage()), library);
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
}
|
||||
|
||||
public TracePcodeEmulator(Trace trace, long snap) {
|
||||
this(trace, snap, SleighUseropLibrary.nil());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createMemoryState() {
|
||||
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PcodeExecutorState<byte[]> createRegisterState(PcodeThread<byte[]> emuThread) {
|
||||
TraceThread traceThread =
|
||||
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
|
||||
return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, traceThread, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the accumulated writes into the given trace at the given snap
|
||||
*
|
||||
* <p>
|
||||
* NOTE: This method requires a transaction to have already been started on the destination
|
||||
* trace. The destination threads must have equal names/paths at the given threadsSnap. When
|
||||
* using scratch space, threadsSnap should be the source snap. If populating a new trace,
|
||||
* threadsSnap should probably be the destination snap.
|
||||
*
|
||||
* @param trace the trace to modify
|
||||
* @param destSnap the destination snap within the trace
|
||||
* @param threadsSnap the snap at which to find corresponding threads
|
||||
* @param synthesizeStacks true to synthesize the innermost stack frame of each thread
|
||||
*/
|
||||
public void writeDown(Trace trace, long destSnap, long threadsSnap, boolean synthesizeStacks) {
|
||||
TraceCachedWriteBytesPcodeExecutorState ms =
|
||||
(TraceCachedWriteBytesPcodeExecutorState) getMemoryState();
|
||||
ms.writeCacheDown(trace, destSnap, null, 0);
|
||||
TraceThreadManager threadManager = trace.getThreadManager();
|
||||
for (PcodeThread<byte[]> emuThread : threads.values()) {
|
||||
TraceCachedWriteBytesPcodeExecutorState rs =
|
||||
(TraceCachedWriteBytesPcodeExecutorState) emuThread.getState().getRegisterState();
|
||||
TraceThread traceThread = threadManager.getLiveThreadByPath(
|
||||
threadsSnap, emuThread.getName());
|
||||
if (traceThread == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Given trace does not have thread with name/path '" + emuThread.getName() +
|
||||
"' at snap " + destSnap);
|
||||
}
|
||||
rs.writeCacheDown(trace, destSnap, traceThread, 0);
|
||||
if (synthesizeStacks) {
|
||||
TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true);
|
||||
stack.getFrame(0, true).setProgramCounter(emuThread.getCounter());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ public enum TraceSleighUtils {
|
|||
if (space.isRegisterSpace()) {
|
||||
if (thread == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot execute with register context unless a thread is given.");
|
||||
"Cannot access register unless a thread is given.");
|
||||
}
|
||||
return trace.getMemoryManager().getMemoryRegisterSpace(thread, frame, toWrite);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/* ###
|
||||
* 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.pcode.exec.trace;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import ghidra.pcode.exec.AccessPcodeExecutionException;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.lang.Register;
|
||||
|
||||
public class UnknownStatePcodeExecutionException extends AccessPcodeExecutionException {
|
||||
|
||||
public static String getMessage(Language language, Address address, int size) {
|
||||
if (address.getAddressSpace().isRegisterSpace()) {
|
||||
Register reg = language.getRegister(address, size);
|
||||
if (reg != null) {
|
||||
return "No recorded value for register " + reg;
|
||||
}
|
||||
return "No recorded value for register(s) " +
|
||||
Arrays.asList(language.getRegisters(address));
|
||||
}
|
||||
try {
|
||||
return "No recorded value for memory at " + new AddressRangeImpl(address, size);
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public UnknownStatePcodeExecutionException(Language language, Address address, int size) {
|
||||
super(getMessage(language, address, size));
|
||||
}
|
||||
|
||||
public UnknownStatePcodeExecutionException(String message, Language language, Address address,
|
||||
int size) {
|
||||
super(message + ": " + getMessage(language, address, size));
|
||||
}
|
||||
}
|
|
@ -185,7 +185,8 @@ public class DBTraceRegisterContextManager extends
|
|||
public RegisterValue getValueWithDefault(Language language, Register register, long snap,
|
||||
Address address) {
|
||||
return delegateRead(address.getAddressSpace(),
|
||||
m -> m.getValueWithDefault(language, register, snap, address));
|
||||
m -> m.getValueWithDefault(language, register, snap, address),
|
||||
() -> getDefaultValue(language, register, address));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,10 +29,10 @@ import ghidra.trace.database.DBTraceUtils;
|
|||
import ghidra.trace.database.data.DBTraceDataSettingsAdapter.DBTraceSettingsEntry;
|
||||
import ghidra.trace.database.map.*;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
||||
import ghidra.trace.database.space.DBTraceSpaceKey;
|
||||
import ghidra.trace.database.thread.DBTraceThread;
|
||||
import ghidra.trace.database.thread.DBTraceThreadManager;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.annot.*;
|
||||
import ghidra.util.exception.VersionException;
|
||||
|
@ -226,8 +226,8 @@ public class DBTraceDataSettingsAdapter
|
|||
}
|
||||
|
||||
@Override
|
||||
public DBTraceDataSettingsSpace get(DBTraceSpaceKey key, boolean createIfAbsent) {
|
||||
return (DBTraceDataSettingsSpace) super.get(key, createIfAbsent);
|
||||
public DBTraceDataSettingsSpace get(TraceAddressSpace space, boolean createIfAbsent) {
|
||||
return (DBTraceDataSettingsSpace) super.get(space, createIfAbsent);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -25,6 +25,7 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
|
|||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.database.DBCachedObjectStore;
|
||||
|
||||
|
@ -42,6 +43,11 @@ public abstract class AbstractDBTraceCodeUnit<T extends AbstractDBTraceCodeUnit<
|
|||
this.space = space;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceAddressSpace getTraceSpace() {
|
||||
return space;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
return getX1();
|
||||
|
@ -96,7 +102,7 @@ public abstract class AbstractDBTraceCodeUnit<T extends AbstractDBTraceCodeUnit<
|
|||
byteCache.limit(Math.min(byteCache.capacity(), end));
|
||||
// TODO: Retrieve the memory space at code space construction time
|
||||
DBTraceMemorySpace mem = space.trace.getMemoryManager().get(space, false);
|
||||
mem.getBytes(getStartSnap(), address.add(byteCache.position()), byteCache);
|
||||
mem.getViewBytes(getStartSnap(), address.add(byteCache.position()), byteCache);
|
||||
}
|
||||
// Copy from the cache
|
||||
int toCopyFromCache =
|
||||
|
@ -110,7 +116,7 @@ public abstract class AbstractDBTraceCodeUnit<T extends AbstractDBTraceCodeUnit<
|
|||
int startRemains = Math.max(addressOffset, byteCache.position());
|
||||
DBTraceMemorySpace mem = space.trace.getMemoryManager().get(space, false);
|
||||
return toCopyFromCache +
|
||||
mem.getBytes(getStartSnap(), address.add(startRemains), buffer);
|
||||
mem.getViewBytes(getStartSnap(), address.add(startRemains), buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,9 @@ import ghidra.program.model.lang.Language;
|
|||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.data.DBTraceDataSettingsAdapter.DBTraceDataSettingsSpace;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.DataAdapterFromDataType;
|
||||
import ghidra.util.LockHold;
|
||||
|
||||
public abstract class AbstractDBTraceDataComponent
|
||||
implements DBTraceDefinedDataAdapter, DataAdapterFromDataType {
|
||||
public abstract class AbstractDBTraceDataComponent implements DBTraceDefinedDataAdapter {
|
||||
|
||||
protected final DBTraceData root;
|
||||
protected final DBTraceDefinedDataAdapter parent;
|
||||
|
|
|
@ -50,8 +50,10 @@ import ghidra.trace.database.thread.DBTraceThreadManager;
|
|||
import ghidra.trace.model.AddressSnap;
|
||||
import ghidra.trace.model.DefaultAddressSnap;
|
||||
import ghidra.trace.model.listing.TraceCodeManager;
|
||||
import ghidra.trace.model.listing.TraceCodeSpace;
|
||||
import ghidra.trace.model.stack.TraceStackFrame;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.annot.*;
|
||||
|
@ -295,6 +297,11 @@ public class DBTraceCodeManager
|
|||
return spaceStore.writeLock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceCodeSpace getCodeSpace(TraceAddressSpace space, boolean createIfAbsent) {
|
||||
return get(space, createIfAbsent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBTraceCodeSpace getCodeSpace(AddressSpace space, boolean createIfAbsent) {
|
||||
return getForSpace(space, createIfAbsent);
|
||||
|
|
|
@ -27,7 +27,6 @@ import ghidra.program.model.lang.Language;
|
|||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.data.DBTraceDataSettingsAdapter.DBTraceDataSettingsSpace;
|
||||
import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree;
|
||||
import ghidra.trace.util.DataAdapterFromDataType;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.database.DBCachedObjectStore;
|
||||
import ghidra.util.database.DBObjectColumn;
|
||||
|
@ -35,8 +34,7 @@ import ghidra.util.database.annot.*;
|
|||
|
||||
@DBAnnotatedObjectInfo(version = 0)
|
||||
public class DBTraceData extends AbstractDBTraceCodeUnit<DBTraceData>
|
||||
implements DBTraceDefinedDataAdapter, DataAdapterFromDataType {
|
||||
static final int[] EMPTY_INT_ARRAY = new int[0];
|
||||
implements DBTraceDefinedDataAdapter {
|
||||
private static final String TABLE_NAME = "Data";
|
||||
|
||||
static final String LANGUAGE_COLUMN_NAME = "Langauge";
|
||||
|
|
|
@ -20,66 +20,39 @@ import java.util.Collection;
|
|||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.docking.settings.SettingsDefinition;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.MutabilitySettingsDefinition;
|
||||
import ghidra.program.model.symbol.RefType;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.trace.database.data.DBTraceDataSettingsOperations;
|
||||
import ghidra.trace.database.symbol.DBTraceReference;
|
||||
import ghidra.trace.model.listing.TraceCodeManager;
|
||||
import ghidra.trace.model.listing.TraceData;
|
||||
import ghidra.trace.model.symbol.TraceReference;
|
||||
import ghidra.trace.util.*;
|
||||
import ghidra.util.LockHold;
|
||||
|
||||
public interface DBTraceDataAdapter extends DBTraceCodeUnitAdapter, TraceData {
|
||||
public interface DBTraceDataAdapter extends DBTraceCodeUnitAdapter, DataAdapterMinimal,
|
||||
DataAdapterFromDataType, DataAdapterFromSettings, TraceData {
|
||||
static String[] EMPTY_STRING_ARRAY = new String[] {};
|
||||
|
||||
default String doToString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(getMnemonicString());
|
||||
String valueRepresentation = getDefaultValueRepresentation();
|
||||
if (valueRepresentation != null) {
|
||||
builder.append(' ');
|
||||
builder.append(valueRepresentation);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getMnemonicString() {
|
||||
return getDataType().getMnemonic(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
DBTraceDataAdapter getRoot();
|
||||
|
||||
default String getPrimarySymbolOrDynamicName() {
|
||||
/** TODO: Use primary symbol or dynamic name as in {@link DataDB#getPathName()} */
|
||||
return "DAT_" + getAddressString(false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getNumOperands() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
default TraceReference[] getValueReferences() {
|
||||
return getOperandReferences(TraceCodeManager.DATA_OP_INDEX);
|
||||
return (TraceReference[]) DataAdapterMinimal.super.getValueReferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void addValueReference(Address refAddr, RefType type) {
|
||||
getTrace().getReferenceManager()
|
||||
.addMemoryReference(getLifespan(), getAddress(), refAddr,
|
||||
type, SourceType.USER_DEFINED, TraceCodeManager.DATA_OP_INDEX);
|
||||
type, SourceType.USER_DEFINED, DATA_OP_INDEX);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void removeValueReference(Address refAddr) {
|
||||
DBTraceReference ref = getTrace().getReferenceManager()
|
||||
.getReference(getStartSnap(),
|
||||
getAddress(), refAddr, TraceCodeManager.DATA_OP_INDEX);
|
||||
getAddress(), refAddr, DATA_OP_INDEX);
|
||||
if (ref == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -198,38 +171,21 @@ public interface DBTraceDataAdapter extends DBTraceCodeUnitAdapter, TraceData {
|
|||
return space.isEmpty(getLifespan(), getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
default <T extends SettingsDefinition> T getSettingsDefinition(
|
||||
Class<T> settingsDefinitionClass) {
|
||||
DataType dt = getBaseDataType();
|
||||
for (SettingsDefinition def : dt.getSettingsDefinitions()) {
|
||||
if (settingsDefinitionClass.isAssignableFrom(def.getClass())) {
|
||||
return settingsDefinitionClass.cast(def);
|
||||
}
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
return DataAdapterFromSettings.super.getSettingsDefinition(settingsDefinitionClass);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean hasMutability(int mutabilityType) {
|
||||
try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) {
|
||||
MutabilitySettingsDefinition def =
|
||||
getSettingsDefinition(MutabilitySettingsDefinition.class);
|
||||
if (def != null) {
|
||||
return def.getChoice(this) == mutabilityType;
|
||||
}
|
||||
return false;
|
||||
return DataAdapterFromSettings.super.hasMutability(mutabilityType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isConstant() {
|
||||
return hasMutability(MutabilitySettingsDefinition.CONSTANT);
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isVolatile() {
|
||||
return hasMutability(MutabilitySettingsDefinition.VOLATILE);
|
||||
}
|
||||
|
||||
@Override
|
||||
DBTraceDataAdapter getPrimitiveAt(int offset);
|
||||
}
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
*/
|
||||
package ghidra.trace.database.listing;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
|
||||
public class DBTraceDataArrayElementComponent extends AbstractDBTraceDataComponent {
|
||||
public DBTraceDataArrayElementComponent(DBTraceData root, DBTraceDefinedDataAdapter parent,
|
||||
|
@ -24,6 +27,11 @@ public class DBTraceDataArrayElementComponent extends AbstractDBTraceDataCompone
|
|||
super(root, parent, index, address, dataType, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceAddressSpace getTraceSpace() {
|
||||
return parent.getTraceSpace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
return "[" + index + "]";
|
||||
|
@ -33,4 +41,16 @@ public class DBTraceDataArrayElementComponent extends AbstractDBTraceDataCompone
|
|||
public String getFieldSyntax() {
|
||||
return getFieldName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange getRange() {
|
||||
// TODO: Cache this?
|
||||
return new AddressRangeImpl(getMinAddress(), getMaxAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceAddressSnapRange getBounds() {
|
||||
// TODO: Cache this?
|
||||
return new ImmutableTraceAddressSnapRange(getMinAddress(), getMaxAddress(), getLifespan());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
*/
|
||||
package ghidra.trace.database.listing;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.DataTypeComponent;
|
||||
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
|
||||
public class DBTraceDataCompositeFieldComponent extends AbstractDBTraceDataComponent {
|
||||
protected final DataTypeComponent dtc;
|
||||
|
@ -27,6 +30,11 @@ public class DBTraceDataCompositeFieldComponent extends AbstractDBTraceDataCompo
|
|||
this.dtc = dtc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceAddressSpace getTraceSpace() {
|
||||
return parent.getTraceSpace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
String fieldName = dtc.getFieldName();
|
||||
|
@ -40,4 +48,16 @@ public class DBTraceDataCompositeFieldComponent extends AbstractDBTraceDataCompo
|
|||
public String getFieldSyntax() {
|
||||
return "." + getFieldName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange getRange() {
|
||||
// TODO: Cache this?
|
||||
return new AddressRangeImpl(getMinAddress(), getMaxAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceAddressSnapRange getBounds() {
|
||||
// TODO: Cache this?
|
||||
return new ImmutableTraceAddressSnapRange(getMinAddress(), getMaxAddress(), getLifespan());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.util.*;
|
|||
import com.google.common.collect.Range;
|
||||
|
||||
import db.DBRecord;
|
||||
import ghidra.lifecycle.Unfinished;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.ContextChangeException;
|
||||
|
@ -47,7 +46,7 @@ import ghidra.util.database.annot.*;
|
|||
|
||||
@DBAnnotatedObjectInfo(version = 0)
|
||||
public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstruction> implements
|
||||
TraceInstruction, InstructionAdapterFromPrototype, InstructionContext, Unfinished {
|
||||
TraceInstruction, InstructionAdapterFromPrototype, InstructionContext {
|
||||
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[] {};
|
||||
private static final String TABLE_NAME = "Instructions";
|
||||
|
||||
|
@ -347,7 +346,7 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
|
|||
return EMPTY_ADDRESS_ARRAY;
|
||||
}
|
||||
Collection<? extends DBTraceReference> refs =
|
||||
refSpace.getFlowRefrencesFrom(getStartSnap(), getAddress());
|
||||
refSpace.getFlowReferencesFrom(getStartSnap(), getAddress());
|
||||
if (refs.isEmpty()) {
|
||||
return EMPTY_ADDRESS_ARRAY;
|
||||
}
|
||||
|
@ -461,7 +460,7 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit<DBTraceInstructi
|
|||
update(FLAGS_COLUMN);
|
||||
|
||||
DBTraceReferenceSpace refSpace = space.referenceManager.get(space, true);
|
||||
for (DBTraceReference ref : refSpace.getFlowRefrencesFrom(getStartSnap(), getX1())) {
|
||||
for (DBTraceReference ref : refSpace.getFlowReferencesFrom(getStartSnap(), getX1())) {
|
||||
if (!isSameFlowType(origFlowType, ref.getReferenceType())) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,7 @@ import java.util.List;
|
|||
import com.google.common.collect.Range;
|
||||
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.lang.Language;
|
||||
import ghidra.program.model.listing.Data;
|
||||
|
@ -33,11 +32,12 @@ import ghidra.trace.database.data.DBTraceDataSettingsOperations;
|
|||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.database.space.DBTraceSpaceKey;
|
||||
import ghidra.trace.database.thread.DBTraceThread;
|
||||
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.listing.TraceData;
|
||||
import ghidra.trace.util.DataAdapterFromDataType;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
|
||||
public class UndefinedDBTraceData
|
||||
implements DBTraceDataAdapter, DataAdapterFromDataType, DBTraceSpaceKey {
|
||||
public class UndefinedDBTraceData implements DBTraceDataAdapter, DBTraceSpaceKey {
|
||||
protected final DBTrace trace;
|
||||
protected final long snap;
|
||||
protected final Range<Long> lifespan;
|
||||
|
@ -55,6 +55,11 @@ public class UndefinedDBTraceData
|
|||
this.frameLevel = frameLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceAddressSpace getTraceSpace() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSpace getAddressSpace() {
|
||||
return address.getAddressSpace();
|
||||
|
@ -75,6 +80,18 @@ public class UndefinedDBTraceData
|
|||
return trace.getBaseLanguage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange getRange() {
|
||||
// TODO: Cache this?
|
||||
return new AddressRangeImpl(getMinAddress(), getMaxAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceAddressSnapRange getBounds() {
|
||||
// TODO: Cache this?
|
||||
return new ImmutableTraceAddressSnapRange(getMinAddress(), getMaxAddress(), getLifespan());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<Long> getLifespan() {
|
||||
return lifespan;
|
||||
|
@ -207,7 +224,7 @@ public class UndefinedDBTraceData
|
|||
|
||||
@Override
|
||||
public int[] getComponentPath() {
|
||||
return DBTraceData.EMPTY_INT_ARRAY;
|
||||
return EMPTY_INT_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -165,7 +165,8 @@ public class DBTraceAddressSnapRangePropertyMapAddressSetView<T> extends Abstrac
|
|||
: new AddressRangeImpl(fullSpace.getMinAddress(), start);
|
||||
Iterator<Entry<TraceAddressSnapRange, T>> mapIt = map
|
||||
.reduce(TraceAddressSnapRangeQuery.intersecting(within, Range.all())
|
||||
.starting(forward ? Rectangle2DDirection.LEFTMOST
|
||||
.starting(forward
|
||||
? Rectangle2DDirection.LEFTMOST
|
||||
: Rectangle2DDirection.RIGHTMOST))
|
||||
.orderedEntries()
|
||||
.iterator();
|
||||
|
|
|
@ -505,6 +505,12 @@ public class DBTraceAddressSnapRangePropertyMapTree<T, DR extends AbstractDBTrac
|
|||
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
|
||||
}
|
||||
|
||||
public static TraceAddressSnapRangeQuery mostRecent(Address address, Range<Long> span) {
|
||||
return intersecting(
|
||||
new ImmutableTraceAddressSnapRange(address, span),
|
||||
Rectangle2DDirection.TOPMOST, TraceAddressSnapRangeQuery::new);
|
||||
}
|
||||
|
||||
public static TraceAddressSnapRangeQuery equalTo(TraceAddressSnapRange shape) {
|
||||
return equalTo(shape, null, TraceAddressSnapRangeQuery::new);
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class DBTraceMemBuffer implements MemBufferAdapter {
|
|||
@Override
|
||||
public int getBytes(ByteBuffer buffer, int offset) {
|
||||
try {
|
||||
return space.getBytes(snap, start.addNoWrap(offset), buffer);
|
||||
return space.getViewBytes(snap, start.addNoWrap(offset), buffer);
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
return 0;
|
||||
|
|
|
@ -211,6 +211,11 @@ public class DBTraceMemoryManager
|
|||
return delegateRead(address.getAddressSpace(), m -> m.getState(snap, address));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Long, TraceMemoryState> getViewState(long snap, Address address) {
|
||||
return delegateRead(address.getAddressSpace(), m -> m.getViewState(snap, address));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<TraceAddressSnapRange, TraceMemoryState> getMostRecentStateEntry(long snap,
|
||||
Address address) {
|
||||
|
@ -218,6 +223,13 @@ public class DBTraceMemoryManager
|
|||
m -> m.getMostRecentStateEntry(snap, address));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
|
||||
Address address) {
|
||||
return delegateRead(address.getAddressSpace(),
|
||||
m -> m.getViewMostRecentStateEntry(snap, address));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView getAddressesWithState(long snap, AddressSetView set,
|
||||
Predicate<TraceMemoryState> predicate) {
|
||||
|
@ -267,6 +279,16 @@ public class DBTraceMemoryManager
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewBytes(long snap, Address start, ByteBuffer buf) {
|
||||
return delegateReadI(start.getAddressSpace(), m -> m.getViewBytes(snap, start, buf), () -> {
|
||||
Address max = start.getAddressSpace().getMaxAddress();
|
||||
int len = MathUtilities.unsignedMin(buf.remaining(), max.subtract(start));
|
||||
buf.position(buf.position() + len);
|
||||
return len;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBytes(long snap, Address start, int len) {
|
||||
delegateDeleteV(start.getAddressSpace(), m -> m.removeBytes(snap, start, len));
|
||||
|
|
|
@ -46,7 +46,9 @@ import ghidra.trace.model.*;
|
|||
import ghidra.trace.model.Trace.*;
|
||||
import ghidra.trace.model.memory.*;
|
||||
import ghidra.trace.util.TraceChangeRecord;
|
||||
import ghidra.trace.util.TraceViewportSpanIterator;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.MathUtilities;
|
||||
import ghidra.util.database.*;
|
||||
import ghidra.util.database.spatial.rect.Rectangle2DDirection;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
@ -329,8 +331,7 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
|
|||
|
||||
protected void checkState(TraceMemoryState state) {
|
||||
if (state == null || state == TraceMemoryState.UNKNOWN) {
|
||||
throw new IllegalArgumentException(
|
||||
"User cannot erase memory state without removing bytes");
|
||||
throw new IllegalArgumentException("Cannot erase memory state without removing bytes");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,6 +377,26 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
|
|||
return state == null ? TraceMemoryState.UNKNOWN : state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Long, TraceMemoryState> getViewState(long snap, Address address) {
|
||||
TraceViewportSpanIterator spit = new TraceViewportSpanIterator(trace, snap);
|
||||
while (spit.hasNext()) {
|
||||
Range<Long> span = spit.next();
|
||||
TraceMemoryState state = getState(span.upperEndpoint(), address);
|
||||
switch (state) {
|
||||
case KNOWN:
|
||||
case ERROR:
|
||||
return Map.entry(span.upperEndpoint(), state);
|
||||
default: // fall through
|
||||
}
|
||||
// Only the snap with the schedule specified gets the source snap's states
|
||||
if (span.upperEndpoint() - span.lowerEndpoint() > 0) {
|
||||
return Map.entry(snap, TraceMemoryState.UNKNOWN);
|
||||
}
|
||||
}
|
||||
return Map.entry(snap, TraceMemoryState.UNKNOWN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<TraceAddressSnapRange, TraceMemoryState> getMostRecentStateEntry(long snap,
|
||||
Address address) {
|
||||
|
@ -383,6 +404,22 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
|
|||
TraceAddressSnapRangeQuery.mostRecent(address, snap)).firstEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<TraceAddressSnapRange, TraceMemoryState> getViewMostRecentStateEntry(long snap,
|
||||
Address address) {
|
||||
TraceViewportSpanIterator spit = new TraceViewportSpanIterator(trace, snap);
|
||||
while (spit.hasNext()) {
|
||||
Range<Long> span = spit.next();
|
||||
Entry<TraceAddressSnapRange, TraceMemoryState> entry =
|
||||
stateMapSpace.reduce(TraceAddressSnapRangeQuery.mostRecent(address, span))
|
||||
.firstEntry();
|
||||
if (entry != null) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView getAddressesWithState(long snap, Predicate<TraceMemoryState> predicate) {
|
||||
return new DBTraceAddressSnapRangePropertyMapAddressSetView<>(space, lock,
|
||||
|
@ -658,6 +695,58 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewBytes(long snap, Address start, ByteBuffer buf) {
|
||||
AddressRange toRead;
|
||||
int len = MathUtilities.unsignedMin(buf.remaining(),
|
||||
start.getAddressSpace().getMaxAddress().subtract(start) + 1);
|
||||
try {
|
||||
toRead = new AddressRangeImpl(start, len);
|
||||
}
|
||||
catch (AddressOverflowException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
Map<AddressRange, Long> sources = new TreeMap<>();
|
||||
AddressSet remains = new AddressSet(toRead);
|
||||
TraceViewportSpanIterator spit = new TraceViewportSpanIterator(trace, snap);
|
||||
spans: while (spit.hasNext()) {
|
||||
Range<Long> span = spit.next();
|
||||
Iterator<AddressRange> arit =
|
||||
getAddressesWithState(span, s -> s == TraceMemoryState.KNOWN).iterator(start, true);
|
||||
while (arit.hasNext()) {
|
||||
AddressRange rng = arit.next();
|
||||
if (rng.getMinAddress().compareTo(toRead.getMaxAddress()) > 0) {
|
||||
break;
|
||||
}
|
||||
for (AddressRange sub : remains.intersectRange(rng.getMinAddress(),
|
||||
rng.getMaxAddress())) {
|
||||
sources.put(sub, span.upperEndpoint());
|
||||
}
|
||||
remains.delete(rng);
|
||||
if (remains.isEmpty()) {
|
||||
break spans;
|
||||
}
|
||||
}
|
||||
}
|
||||
int lim = buf.limit();
|
||||
int pos = buf.position();
|
||||
for (Map.Entry<AddressRange, Long> ent : sources.entrySet()) {
|
||||
AddressRange rng = ent.getKey();
|
||||
int offset = (int) rng.getMinAddress().subtract(toRead.getMinAddress());
|
||||
int length = (int) rng.getLength();
|
||||
buf.position(pos + offset);
|
||||
buf.limit(pos + offset + length);
|
||||
int read = getBytes(ent.getValue(), rng.getMinAddress(), buf);
|
||||
if (read < length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// We "got it all", even if there were gaps in "KNOWN"
|
||||
buf.limit(lim);
|
||||
buf.position(pos + len);
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address findBytes(long snap, AddressRange range, ByteBuffer data, ByteBuffer mask,
|
||||
boolean forward, TaskMonitor monitor) {
|
||||
|
|
|
@ -15,8 +15,12 @@
|
|||
*/
|
||||
package ghidra.trace.database.program;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import generic.NestedIterator;
|
||||
|
@ -32,17 +36,21 @@ import ghidra.program.model.symbol.Namespace;
|
|||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.program.model.util.CodeUnitInsertionException;
|
||||
import ghidra.program.model.util.PropertyMap;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.listing.UndefinedDBTraceData;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryRegion;
|
||||
import ghidra.trace.database.memory.DBTraceMemorySpace;
|
||||
import ghidra.trace.database.symbol.DBTraceFunctionSymbol;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.listing.TraceCodeOperations;
|
||||
import ghidra.trace.model.listing.TraceData;
|
||||
import ghidra.trace.database.thread.DBTraceThread;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.listing.*;
|
||||
import ghidra.trace.model.map.TracePropertyMap;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.program.TraceProgramViewListing;
|
||||
import ghidra.trace.model.symbol.TraceFunctionSymbol;
|
||||
import ghidra.util.IntersectionAddressSetView;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.trace.util.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.AddressIteratorAdapter;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -50,72 +58,19 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
public static final String[] EMPTY_STRING_ARRAY = new String[] {};
|
||||
public static final String TREE_NAME = "Trace Tree";
|
||||
|
||||
protected static class WrappingCodeUnitIterator implements CodeUnitIterator {
|
||||
protected final Iterator<? extends CodeUnit> it;
|
||||
|
||||
public WrappingCodeUnitIterator(Iterator<? extends CodeUnit> it) {
|
||||
this.it = it;
|
||||
protected class DBTraceProgramViewUndefinedData extends UndefinedDBTraceData {
|
||||
public DBTraceProgramViewUndefinedData(DBTrace trace, long snap, Address address,
|
||||
DBTraceThread thread, int frameLevel) {
|
||||
super(trace, snap, address, thread, frameLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<CodeUnit> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnit next() {
|
||||
return it.next();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class WrappingInstructionIterator implements InstructionIterator {
|
||||
protected final Iterator<? extends Instruction> it;
|
||||
|
||||
public WrappingInstructionIterator(Iterator<? extends Instruction> it) {
|
||||
this.it = it;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Instruction> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instruction next() {
|
||||
return it.next();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class WrappingDataIterator implements DataIterator {
|
||||
protected final Iterator<? extends Data> it;
|
||||
|
||||
public WrappingDataIterator(Iterator<? extends Data> it) {
|
||||
this.it = it;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Data> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data next() {
|
||||
return it.next();
|
||||
public int getBytes(ByteBuffer buffer, int addressOffset) {
|
||||
DBTraceMemorySpace mem = trace.getMemoryManager().get(this, false);
|
||||
if (mem == null) {
|
||||
// TODO: 0-fill instead? Will need to check memory space bounds.
|
||||
}
|
||||
return mem.getViewBytes(program.snap, address.add(addressOffset), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,6 +81,14 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
protected final Map<DBTraceMemoryRegion, DBTraceProgramViewFragment> fragmentsByRegion =
|
||||
new HashMap<>();
|
||||
|
||||
protected final Map<AddressSnap, UndefinedDBTraceData> undefinedCache =
|
||||
CacheBuilder.newBuilder()
|
||||
.removalListener(
|
||||
this::undefinedRemovedFromCache)
|
||||
.weakValues()
|
||||
.build()
|
||||
.asMap();
|
||||
|
||||
public AbstractDBTraceProgramViewListing(DBTraceProgramView program,
|
||||
TraceCodeOperations codeOperations) {
|
||||
this.program = program;
|
||||
|
@ -134,6 +97,11 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
this.rootModule = new DBTraceProgramViewRootModule(this);
|
||||
}
|
||||
|
||||
private void undefinedRemovedFromCache(
|
||||
RemovalNotification<AddressSnap, UndefinedDBTraceData> rn) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceProgramView getProgram() {
|
||||
return program;
|
||||
|
@ -149,32 +117,241 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
return program.snap;
|
||||
}
|
||||
|
||||
protected <T extends TraceCodeUnit> T getTopCode(
|
||||
java.util.function.Function<Long, T> codeFunc) {
|
||||
return program.viewport.getTop(s -> {
|
||||
T cu = codeFunc.apply(s);
|
||||
if (cu != null && program.isCodeVisible(cu, cu.getLifespan())) {
|
||||
return cu;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected TraceCodeUnit orUndef(TraceCodeUnit cu, Address address) {
|
||||
if (cu != null) {
|
||||
return cu;
|
||||
}
|
||||
return doCreateUndefinedUnit(address);
|
||||
}
|
||||
|
||||
protected TraceData orUndefData(TraceData data, Address address) {
|
||||
return (TraceData) orUndef(data, address);
|
||||
}
|
||||
|
||||
protected TraceData reqUndef(TraceCodeUnit cu, Address address) {
|
||||
if (cu != null) {
|
||||
return null;
|
||||
}
|
||||
return doCreateUndefinedUnit(address);
|
||||
}
|
||||
|
||||
protected <T> T next(Iterator<T> it) {
|
||||
if (it.hasNext()) {
|
||||
return it.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Comparator<CodeUnit> getUnitComparator(boolean forward) {
|
||||
return forward
|
||||
? (u1, u2) -> u1.getMinAddress().compareTo(u2.getMinAddress())
|
||||
: (u1, u2) -> -u1.getMinAddress().compareTo(u2.getMinAddress());
|
||||
}
|
||||
|
||||
protected <T extends TraceCodeUnit> Iterator<T> getTopCodeIterator(
|
||||
java.util.function.Function<Long, Iterator<T>> iterFunc, boolean forward) {
|
||||
return Iterators.filter(
|
||||
program.viewport.mergedIterator(iterFunc, getUnitComparator(forward)),
|
||||
cu -> program.isCodeVisible(cu, cu.getLifespan()));
|
||||
}
|
||||
|
||||
protected AddressSet getAddressSet(Address start, boolean forward) {
|
||||
AddressFactory factory = program.getAddressFactory();
|
||||
AddressSet all = program.allAddresses;
|
||||
return forward
|
||||
? factory.getAddressSet(start, all.getMaxAddress())
|
||||
: factory.getAddressSet(all.getMinAddress(), start);
|
||||
}
|
||||
|
||||
protected UndefinedDBTraceData doCreateUndefinedUnit(Address address) {
|
||||
return undefinedCache.computeIfAbsent(new DefaultAddressSnap(address, program.snap),
|
||||
ot -> new DBTraceProgramViewUndefinedData(program.trace, program.snap, address, null,
|
||||
0));
|
||||
}
|
||||
|
||||
protected Iterator<? extends TraceInstruction> getInstructionIterator(Address start,
|
||||
boolean forward) {
|
||||
return getTopCodeIterator(
|
||||
s -> codeOperations.instructions().get(s, start, forward).iterator(), forward);
|
||||
}
|
||||
|
||||
protected Iterator<? extends TraceInstruction> getInstructionIterator(AddressSetView set,
|
||||
boolean forward) {
|
||||
return getTopCodeIterator(
|
||||
s -> codeOperations.instructions().get(s, set, forward).iterator(), forward);
|
||||
}
|
||||
|
||||
protected Iterator<? extends TraceInstruction> getInstructionIterator(boolean forward) {
|
||||
return getTopCodeIterator(
|
||||
s -> codeOperations.instructions().get(s, forward).iterator(), forward);
|
||||
}
|
||||
|
||||
protected Iterator<? extends TraceData> getDefinedDataIterator(Address start, boolean forward) {
|
||||
return getTopCodeIterator(
|
||||
s -> codeOperations.definedData().get(s, start, forward).iterator(), forward);
|
||||
}
|
||||
|
||||
protected Iterator<? extends TraceData> getDefinedDataIterator(AddressSetView set,
|
||||
boolean forward) {
|
||||
return getTopCodeIterator(
|
||||
s -> codeOperations.definedData().get(s, set, forward).iterator(), forward);
|
||||
}
|
||||
|
||||
protected Iterator<? extends TraceData> getDefinedDataIterator(boolean forward) {
|
||||
return getTopCodeIterator(
|
||||
s -> codeOperations.definedData().get(s, forward).iterator(), forward);
|
||||
}
|
||||
|
||||
protected Iterator<? extends TraceCodeUnit> getDefinedUnitIterator(Address start,
|
||||
boolean forward) {
|
||||
return getTopCodeIterator(
|
||||
s -> codeOperations.definedUnits().get(s, start, forward).iterator(), forward);
|
||||
}
|
||||
|
||||
protected Iterator<? extends TraceCodeUnit> getDefinedUnitIterator(AddressSetView set,
|
||||
boolean forward) {
|
||||
return getTopCodeIterator(
|
||||
s -> codeOperations.definedUnits().get(s, set, forward).iterator(), forward);
|
||||
}
|
||||
|
||||
protected Iterator<TraceData> getUndefinedDataIterator(Address start, boolean forward) {
|
||||
AddressSet set = getAddressSet(start, forward);
|
||||
Address defStart = start;
|
||||
if (forward) {
|
||||
CodeUnit defUnit =
|
||||
getTopCode(s -> codeOperations.definedUnits().getContaining(s, start));
|
||||
if (defUnit != null) {
|
||||
defStart = defUnit.getMinAddress();
|
||||
}
|
||||
}
|
||||
Iterator<AddressRange> defIter = Iterators.transform(
|
||||
getDefinedUnitIterator(defStart, forward), u -> u.getRange());
|
||||
AddressRangeIterator undefIter =
|
||||
AddressRangeIterators.subtract(set.iterator(forward), defIter, start, forward);
|
||||
AddressIteratorAdapter undefAddrIter = new AddressIteratorAdapter(undefIter, forward);
|
||||
return Iterators.transform(undefAddrIter.iterator(), a -> doCreateUndefinedUnit(a));
|
||||
}
|
||||
|
||||
protected AddressRangeIterator getUndefinedRangeIterator(AddressSetView set, boolean forward) {
|
||||
Iterator<AddressRange> defIter = Iterators.transform(
|
||||
getDefinedUnitIterator(set, forward), u -> u.getRange());
|
||||
return AddressRangeIterators.subtract(set.iterator(forward), defIter,
|
||||
forward ? set.getMinAddress() : set.getMaxAddress(), forward);
|
||||
}
|
||||
|
||||
protected boolean isUndefinedRange(long snap, AddressRange range) {
|
||||
if (codeOperations.undefinedData().coversRange(Range.singleton(snap), range)) {
|
||||
return true;
|
||||
}
|
||||
TraceCodeUnit minUnit =
|
||||
codeOperations.definedUnits().getContaining(snap, range.getMinAddress());
|
||||
if (minUnit != null && program.isCodeVisible(minUnit, minUnit.getLifespan())) {
|
||||
return false;
|
||||
}
|
||||
TraceCodeUnit maxUnit =
|
||||
codeOperations.definedUnits().getContaining(snap, range.getMaxAddress());
|
||||
if (maxUnit != null && program.isCodeVisible(maxUnit, maxUnit.getLifespan())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Iterator<TraceData> getUndefinedDataIterator(AddressSetView set, boolean forward) {
|
||||
AddressRangeIterator undefIter = getUndefinedRangeIterator(set, forward);
|
||||
AddressIteratorAdapter undefAddrIter = new AddressIteratorAdapter(undefIter, forward);
|
||||
return Iterators.transform(undefAddrIter.iterator(), a -> doCreateUndefinedUnit(a));
|
||||
}
|
||||
|
||||
protected Iterator<TraceCodeUnit> getCodeUnitIterator(AddressSetView set, boolean forward) {
|
||||
return new MergeSortingIterator<>(List.of(
|
||||
getDefinedUnitIterator(set, forward),
|
||||
getUndefinedDataIterator(set, forward)),
|
||||
getUnitComparator(forward));
|
||||
}
|
||||
|
||||
protected Iterator<TraceCodeUnit> getCodeUnitIterator(Address start, boolean forward) {
|
||||
return new MergeSortingIterator<>(List.of(
|
||||
getDefinedUnitIterator(start, forward),
|
||||
getUndefinedDataIterator(start, forward)),
|
||||
getUnitComparator(forward));
|
||||
}
|
||||
|
||||
protected Iterator<TraceCodeUnit> getCodeUnitIterator(boolean forward) {
|
||||
AddressSetView set = program.allAddresses;
|
||||
return getCodeUnitIterator(forward ? set.getMinAddress() : set.getMaxAddress(), forward);
|
||||
}
|
||||
|
||||
protected Iterator<TraceData> getDataIterator(AddressSetView set, boolean forward) {
|
||||
return new MergeSortingIterator<>(List.of(
|
||||
getDefinedDataIterator(set, forward),
|
||||
getUndefinedDataIterator(set, forward)),
|
||||
getUnitComparator(forward));
|
||||
}
|
||||
|
||||
protected Iterator<TraceData> getDataIterator(Address start, boolean forward) {
|
||||
return new MergeSortingIterator<>(List.of(
|
||||
getDefinedDataIterator(start, forward),
|
||||
getUndefinedDataIterator(start, forward)),
|
||||
getUnitComparator(forward));
|
||||
}
|
||||
|
||||
protected Iterator<TraceData> getDataIterator(boolean forward) {
|
||||
AddressSetView set = program.allAddresses;
|
||||
return getDataIterator(forward ? set.getMinAddress() : set.getMaxAddress(), forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnit getCodeUnitAt(Address addr) {
|
||||
return codeOperations.codeUnits().getAt(program.snap, addr);
|
||||
CodeUnit containing = getCodeUnitContaining(addr);
|
||||
if (containing == null) {
|
||||
return doCreateUndefinedUnit(addr);
|
||||
}
|
||||
if (!containing.getMinAddress().equals(addr)) {
|
||||
return null;
|
||||
}
|
||||
return containing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnit getCodeUnitContaining(Address addr) {
|
||||
return codeOperations.codeUnits().getContaining(program.snap, addr);
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return orUndef(getTopCode(s -> codeOperations.definedUnits().getContaining(s, addr)),
|
||||
addr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnit getCodeUnitAfter(Address addr) {
|
||||
return codeOperations.codeUnits().getAfter(program.snap, addr);
|
||||
addr = addr.next();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getCodeUnitIterator(addr, true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnit getCodeUnitBefore(Address addr) {
|
||||
return codeOperations.codeUnits().getBefore(program.snap, addr);
|
||||
addr = addr.previous();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getCodeUnitIterator(addr, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnitIterator getCodeUnitIterator(String property, boolean forward) {
|
||||
// HACK
|
||||
if (CodeUnit.INSTRUCTION_PROPERTY.equals(property)) {
|
||||
return new WrappingCodeUnitIterator(
|
||||
codeOperations.instructions().get(program.snap, forward).iterator());
|
||||
return new WrappingCodeUnitIterator(getInstructionIterator(forward));
|
||||
}
|
||||
// TODO: Other "special" property types
|
||||
|
||||
|
@ -184,28 +361,19 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
if (map == null) {
|
||||
return new WrappingCodeUnitIterator(Collections.emptyIterator());
|
||||
}
|
||||
// TODO: The property map doesn't heed forking.
|
||||
return new WrappingCodeUnitIterator(NestedIterator.start(
|
||||
map.getAddressSetView(Range.singleton(program.snap)).iterator(forward),
|
||||
rng -> program.trace.getCodeManager()
|
||||
.codeUnits()
|
||||
.get(program.snap, rng, forward)
|
||||
.iterator()));
|
||||
}
|
||||
|
||||
protected static AddressRange fixRange(AddressRange range, Address start, boolean forward) {
|
||||
if (!range.contains(start)) {
|
||||
return range;
|
||||
}
|
||||
return forward ? new AddressRangeImpl(start, range.getMaxAddress())
|
||||
: new AddressRangeImpl(range.getMinAddress(), start);
|
||||
rng -> getTopCodeIterator(
|
||||
s -> codeOperations.codeUnits().get(s, rng, forward).iterator(),
|
||||
forward)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnitIterator getCodeUnitIterator(String property, Address addr, boolean forward) {
|
||||
// HACK
|
||||
if (CodeUnit.INSTRUCTION_PROPERTY.equals(property)) {
|
||||
return new WrappingCodeUnitIterator(
|
||||
codeOperations.instructions().get(program.snap, addr, forward).iterator());
|
||||
return new WrappingCodeUnitIterator(getInstructionIterator(addr, forward));
|
||||
}
|
||||
// TODO: Other "special" property types
|
||||
|
||||
|
@ -215,12 +383,12 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
if (map == null) {
|
||||
return new WrappingCodeUnitIterator(Collections.emptyIterator());
|
||||
}
|
||||
// TODO: The property map doesn't heed forking.
|
||||
return new WrappingCodeUnitIterator(NestedIterator.start(
|
||||
map.getAddressSetView(Range.singleton(program.snap)).iterator(addr, forward),
|
||||
rng -> program.trace.getCodeManager()
|
||||
.codeUnits()
|
||||
.get(program.snap, fixRange(rng, addr, forward), forward)
|
||||
.iterator()));
|
||||
rng -> getTopCodeIterator(
|
||||
s -> codeOperations.codeUnits().get(s, rng, forward).iterator(),
|
||||
forward)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -228,8 +396,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
boolean forward) {
|
||||
// HACK
|
||||
if (CodeUnit.INSTRUCTION_PROPERTY.equals(property)) {
|
||||
return new WrappingCodeUnitIterator(
|
||||
codeOperations.instructions().get(program.snap, addrSet, forward).iterator());
|
||||
return new WrappingCodeUnitIterator(getInstructionIterator(addrSet, forward));
|
||||
}
|
||||
// TODO: Other "special" property types
|
||||
|
||||
|
@ -239,13 +406,13 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
if (map == null) {
|
||||
return new WrappingCodeUnitIterator(Collections.emptyIterator());
|
||||
}
|
||||
// TODO: The property map doesn't heed forking.
|
||||
return new WrappingCodeUnitIterator(NestedIterator.start(
|
||||
new IntersectionAddressSetView(map.getAddressSetView(Range.singleton(program.snap)),
|
||||
addrSet).iterator(forward),
|
||||
rng -> program.trace.getCodeManager()
|
||||
.codeUnits()
|
||||
.get(program.snap, rng, forward)
|
||||
.iterator()));
|
||||
rng -> getTopCodeIterator(
|
||||
s -> codeOperations.codeUnits().get(s, rng, forward).iterator(),
|
||||
forward)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -269,7 +436,10 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
|
||||
@Override
|
||||
public String getComment(int commentType, Address address) {
|
||||
return program.trace.getCommentAdapter().getComment(program.snap, address, commentType);
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return program.viewport.getTop(
|
||||
s -> program.trace.getCommentAdapter().getComment(s, address, commentType));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -281,190 +451,227 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
|
||||
@Override
|
||||
public CodeUnitIterator getCodeUnits(boolean forward) {
|
||||
return new WrappingCodeUnitIterator(
|
||||
codeOperations.codeUnits().get(program.snap, forward).iterator());
|
||||
return new WrappingCodeUnitIterator(getCodeUnitIterator(forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnitIterator getCodeUnits(Address start, boolean forward) {
|
||||
return new WrappingCodeUnitIterator(
|
||||
codeOperations.codeUnits().get(program.snap, start, forward).iterator());
|
||||
return new WrappingCodeUnitIterator(getCodeUnitIterator(start, forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnitIterator getCodeUnits(AddressSetView addressSet, boolean forward) {
|
||||
return new WrappingCodeUnitIterator(
|
||||
codeOperations.codeUnits().get(program.snap, addressSet, forward).iterator());
|
||||
return new WrappingCodeUnitIterator(getCodeUnitIterator(addressSet, forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instruction getInstructionAt(Address addr) {
|
||||
return codeOperations.instructions().getAt(program.snap, addr);
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return getTopCode(s -> codeOperations.instructions().getAt(s, addr));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instruction getInstructionContaining(Address addr) {
|
||||
return codeOperations.instructions().getContaining(program.snap, addr);
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return getTopCode(s -> codeOperations.instructions().getContaining(s, addr));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instruction getInstructionAfter(Address addr) {
|
||||
return codeOperations.instructions().getAfter(program.snap, addr);
|
||||
addr = addr.next();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getInstructionIterator(addr, true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instruction getInstructionBefore(Address addr) {
|
||||
return codeOperations.instructions().getBefore(program.snap, addr);
|
||||
addr = addr.previous();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getInstructionIterator(addr, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstructionIterator getInstructions(boolean forward) {
|
||||
return new WrappingInstructionIterator(
|
||||
codeOperations.instructions().get(program.snap, forward).iterator());
|
||||
return new WrappingInstructionIterator(getInstructionIterator(forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstructionIterator getInstructions(Address start, boolean forward) {
|
||||
return new WrappingInstructionIterator(
|
||||
codeOperations.instructions().get(program.snap, start, forward).iterator());
|
||||
return new WrappingInstructionIterator(getInstructionIterator(start, forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstructionIterator getInstructions(AddressSetView addressSet, boolean forward) {
|
||||
return new WrappingInstructionIterator(
|
||||
codeOperations.instructions().get(program.snap, addressSet, forward).iterator());
|
||||
return new WrappingInstructionIterator(getInstructionIterator(addressSet, forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getDataAt(Address addr) {
|
||||
return codeOperations.data().getAt(program.snap, addr);
|
||||
CodeUnit containing = getCodeUnitContaining(addr);
|
||||
if (containing == null) {
|
||||
return doCreateUndefinedUnit(addr);
|
||||
}
|
||||
if (!(containing instanceof Data)) {
|
||||
return null;
|
||||
}
|
||||
if (!containing.getMinAddress().equals(addr)) {
|
||||
return null;
|
||||
}
|
||||
return (Data) containing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getDataContaining(Address addr) {
|
||||
return codeOperations.data().getContaining(program.snap, addr);
|
||||
CodeUnit cu = getCodeUnitContaining(addr);
|
||||
if (cu instanceof Data) {
|
||||
return (Data) cu;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getDataAfter(Address addr) {
|
||||
return codeOperations.data().getAfter(program.snap, addr);
|
||||
addr = addr.next();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getDataIterator(addr, true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getDataBefore(Address addr) {
|
||||
return codeOperations.data().getBefore(program.snap, addr);
|
||||
addr = addr.previous();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getDataIterator(addr, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataIterator getData(boolean forward) {
|
||||
return new WrappingDataIterator(
|
||||
codeOperations.data().get(program.snap, forward).iterator());
|
||||
return new WrappingDataIterator(getDataIterator(forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataIterator getData(Address start, boolean forward) {
|
||||
return new WrappingDataIterator(
|
||||
codeOperations.data().get(program.snap, start, forward).iterator());
|
||||
return new WrappingDataIterator(getDataIterator(start, forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataIterator getData(AddressSetView addressSet, boolean forward) {
|
||||
return new WrappingDataIterator(
|
||||
codeOperations.data().get(program.snap, addressSet, forward).iterator());
|
||||
return new WrappingDataIterator(getDataIterator(addressSet, forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getDefinedDataAt(Address addr) {
|
||||
return codeOperations.definedData().getAt(program.snap, addr);
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return getTopCode(s -> codeOperations.definedData().getAt(s, addr));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getDefinedDataContaining(Address addr) {
|
||||
return codeOperations.definedData().getContaining(program.snap, addr);
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return getTopCode(s -> codeOperations.definedData().getContaining(s, addr));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getDefinedDataAfter(Address addr) {
|
||||
return codeOperations.definedData().getAfter(program.snap, addr);
|
||||
addr = addr.next();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getDefinedDataIterator(addr, true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getDefinedDataBefore(Address addr) {
|
||||
return codeOperations.definedData().getBefore(program.snap, addr);
|
||||
addr = addr.previous();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getDefinedDataIterator(addr, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataIterator getDefinedData(boolean forward) {
|
||||
return new WrappingDataIterator(
|
||||
codeOperations.definedData().get(program.snap, forward).iterator());
|
||||
return new WrappingDataIterator(getDefinedDataIterator(forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataIterator getDefinedData(Address start, boolean forward) {
|
||||
return new WrappingDataIterator(
|
||||
codeOperations.definedData().get(program.snap, start, forward).iterator());
|
||||
return new WrappingDataIterator(getDefinedDataIterator(start, forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataIterator getDefinedData(AddressSetView addressSet, boolean forward) {
|
||||
return new WrappingDataIterator(
|
||||
codeOperations.definedData().get(program.snap, addressSet, forward).iterator());
|
||||
return new WrappingDataIterator(getDefinedDataIterator(addressSet, forward));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getUndefinedDataAt(Address addr) {
|
||||
return codeOperations.undefinedData().getAt(program.snap, addr);
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return reqUndef(getTopCode(s -> codeOperations.definedUnits().getContaining(s, addr)),
|
||||
addr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getUndefinedDataAfter(Address addr, TaskMonitor monitor) {
|
||||
return codeOperations.undefinedData().getAfter(program.snap, addr);
|
||||
addr = addr.next();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getUndefinedDataIterator(addr, true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getUndefinedDataBefore(Address addr, TaskMonitor monitor) {
|
||||
return codeOperations.undefinedData().getBefore(program.snap, addr);
|
||||
addr = addr.previous();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return addr == null ? null : next(getUndefinedDataIterator(addr, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getFirstUndefinedData(AddressSetView addressSet, TaskMonitor monitor) {
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
for (TraceData u : codeOperations.undefinedData().get(program.snap, addressSet, true)) {
|
||||
return u;
|
||||
}
|
||||
return null;
|
||||
return next(getUndefinedDataIterator(addressSet, true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @implNote This could technically use a (lazy) view; however, to be consistent with
|
||||
* expectations established by {@link ProgramDB}, it constructs the actual set, and
|
||||
* permits cancellation by the monitor.
|
||||
* @implNote This could maybe use a (lazy) view; however, to be consistent with expectations
|
||||
* established by {@link ProgramDB}, it constructs the actual set, and permits
|
||||
* cancellation by the monitor.
|
||||
*/
|
||||
@Override
|
||||
public AddressSet getUndefinedRanges(AddressSetView set, boolean initializedMemoryOnly,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
AddressSet result = new AddressSet();
|
||||
for (AddressRange range : set) {
|
||||
for (AddressRange und : codeOperations.undefinedData()
|
||||
.getAddressSetView(program.snap, range)) {
|
||||
monitor.checkCanceled();
|
||||
result.add(und.intersect(range));
|
||||
}
|
||||
for (AddressRange range : getUndefinedRangeIterator(set, true)) {
|
||||
result.add(range);
|
||||
monitor.checkCanceled();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnit getDefinedCodeUnitAfter(Address addr) {
|
||||
return codeOperations.definedUnits().getAfter(program.snap, addr);
|
||||
addr = addr.next();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return next(getDefinedUnitIterator(addr, true));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnit getDefinedCodeUnitBefore(Address addr) {
|
||||
return codeOperations.definedUnits().getBefore(program.snap, addr);
|
||||
addr = addr.previous();
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return next(getDefinedUnitIterator(addr, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -561,8 +768,8 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
|
||||
@Override
|
||||
public ProgramFragment getFragment(String treeName, Address addr) {
|
||||
DBTraceMemoryRegion region =
|
||||
program.trace.getMemoryManager().getRegionContaining(program.snap, addr);
|
||||
DBTraceMemoryRegion region = program.memory.getTopRegion(
|
||||
s -> program.trace.getMemoryManager().getRegionContaining(s, addr));
|
||||
if (region == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -580,8 +787,8 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
|
||||
@Override
|
||||
public ProgramFragment getFragment(String treeName, String name) {
|
||||
DBTraceMemoryRegion region =
|
||||
program.trace.getMemoryManager().getLiveRegionByPath(program.snap, name);
|
||||
DBTraceMemoryRegion region = program.memory.getTopRegion(
|
||||
s -> program.trace.getMemoryManager().getLiveRegionByPath(s, name));
|
||||
if (region == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -648,8 +855,8 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
|||
|
||||
@Override
|
||||
public long getNumInstructions() {
|
||||
// TODO: See getNumCodeUnits
|
||||
return Long.MAX_VALUE;
|
||||
// TODO: See getNumCodeUnits... Why was this Long.MAX_VALUE before?
|
||||
return codeOperations.instructions().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,7 +18,6 @@ package ghidra.trace.database.program;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
|
@ -31,12 +30,14 @@ import ghidra.trace.database.memory.*;
|
|||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.program.TraceProgramViewMemory;
|
||||
import ghidra.trace.util.MemoryAdapter;
|
||||
import ghidra.util.MathUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.NotFoundException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramViewMemory {
|
||||
public abstract class AbstractDBTraceProgramViewMemory
|
||||
implements TraceProgramViewMemory, MemoryAdapter {
|
||||
protected final DBTraceProgramView program;
|
||||
protected final DBTraceMemoryManager memoryManager;
|
||||
|
||||
|
@ -95,7 +96,7 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
|
|||
public AddressSetView getExecuteSet() {
|
||||
AddressSet result = new AddressSet();
|
||||
for (DBTraceMemoryRegion region : memoryManager.getRegionsInternal()) {
|
||||
if (!region.isExecute()) {
|
||||
if (!region.isExecute() || !program.isRegionVisible(region, region.getLifespan())) {
|
||||
continue;
|
||||
}
|
||||
result.add(region.getRange());
|
||||
|
@ -247,6 +248,8 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
|
|||
if (space == null) {
|
||||
continue;
|
||||
}
|
||||
// TODO: findBytes must heed fork, or there should exist a variant that does....
|
||||
// Lest I have to implement the forked search here.
|
||||
Address found =
|
||||
space.findBytes(snap, range, bufBytes, bufMasks, forward, monitor);
|
||||
if (found != null) {
|
||||
|
@ -265,11 +268,6 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
|
|||
return block.getByte(addr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(Address addr, byte[] dest) throws MemoryAccessException {
|
||||
return getBytes(addr, dest, 0, dest.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(Address addr, byte[] dest, int destIndex, int size)
|
||||
throws MemoryAccessException {
|
||||
|
@ -283,109 +281,6 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
|
|||
return block.getBytes(addr, dest, destIndex, size);
|
||||
}
|
||||
|
||||
protected ByteBuffer mustRead(Address addr, int length, boolean bigEndian)
|
||||
throws MemoryAccessException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(length);
|
||||
if (getBytes(addr, buf.array()) != length) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(Address addr) throws MemoryAccessException {
|
||||
return mustRead(addr, Short.BYTES, true).getShort(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getShort(Address addr, boolean bigEndian) throws MemoryAccessException {
|
||||
return mustRead(addr, Short.BYTES, bigEndian).getShort(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShorts(Address addr, short[] dest) throws MemoryAccessException {
|
||||
return getShorts(addr, dest, 0, dest.length, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShorts(Address addr, short[] dest, int dIndex, int nElem)
|
||||
throws MemoryAccessException {
|
||||
return getShorts(addr, dest, dIndex, nElem, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShorts(Address addr, short[] dest, int dIndex, int nElem, boolean bigEndian)
|
||||
throws MemoryAccessException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Short.BYTES * nElem);
|
||||
int got = getBytes(addr, buf.array()) / Short.BYTES;
|
||||
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
|
||||
buf.asShortBuffer().get(dest, dIndex, got);
|
||||
return got;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(Address addr) throws MemoryAccessException {
|
||||
return mustRead(addr, Integer.BYTES, true).getInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(Address addr, boolean bigEndian) throws MemoryAccessException {
|
||||
return mustRead(addr, Integer.BYTES, bigEndian).getInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInts(Address addr, int[] dest) throws MemoryAccessException {
|
||||
return getInts(addr, dest, 0, dest.length, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInts(Address addr, int[] dest, int dIndex, int nElem)
|
||||
throws MemoryAccessException {
|
||||
return getInts(addr, dest, dIndex, nElem, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInts(Address addr, int[] dest, int dIndex, int nElem, boolean bigEndian)
|
||||
throws MemoryAccessException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES * nElem);
|
||||
int got = getBytes(addr, buf.array()) / Integer.BYTES;
|
||||
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
|
||||
buf.asIntBuffer().get(dest, dIndex, got);
|
||||
return got;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(Address addr) throws MemoryAccessException {
|
||||
return mustRead(addr, Long.BYTES, true).getLong(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLong(Address addr, boolean bigEndian) throws MemoryAccessException {
|
||||
return mustRead(addr, Long.BYTES, bigEndian).getLong(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLongs(Address addr, long[] dest) throws MemoryAccessException {
|
||||
return getLongs(addr, dest, 0, dest.length, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLongs(Address addr, long[] dest, int dIndex, int nElem)
|
||||
throws MemoryAccessException {
|
||||
return getLongs(addr, dest, dIndex, nElem, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLongs(Address addr, long[] dest, int dIndex, int nElem, boolean bigEndian)
|
||||
throws MemoryAccessException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES * nElem);
|
||||
int got = getBytes(addr, buf.array()) / Long.BYTES;
|
||||
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
|
||||
buf.asLongBuffer().get(dest, dIndex, got);
|
||||
return got;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setByte(Address addr, byte value) throws MemoryAccessException {
|
||||
DBTraceMemorySpace space = memoryManager.getMemorySpace(addr.getAddressSpace(), true);
|
||||
|
@ -394,11 +289,6 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBytes(Address addr, byte[] source) throws MemoryAccessException {
|
||||
setBytes(addr, source, 0, source.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBytes(Address addr, byte[] source, int sIndex, int size)
|
||||
throws MemoryAccessException {
|
||||
|
@ -408,46 +298,6 @@ public abstract class AbstractDBTraceProgramViewMemory implements TraceProgramVi
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShort(Address addr, short value) throws MemoryAccessException {
|
||||
setShort(addr, value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShort(Address addr, short value, boolean bigEndian)
|
||||
throws MemoryAccessException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Short.BYTES);
|
||||
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
|
||||
buf.putShort(value);
|
||||
setBytes(addr, buf.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInt(Address addr, int value) throws MemoryAccessException {
|
||||
setInt(addr, value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInt(Address addr, int value, boolean bigEndian) throws MemoryAccessException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
|
||||
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
|
||||
buf.putInt(value);
|
||||
setBytes(addr, buf.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLong(Address addr, long value) throws MemoryAccessException {
|
||||
setLong(addr, value, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLong(Address addr, long value, boolean bigEndian) throws MemoryAccessException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(Long.BYTES);
|
||||
buf.order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
|
||||
buf.putLong(value);
|
||||
setBytes(addr, buf.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileBytes createFileBytes(String filename, long offset, long size, InputStream is,
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
|
|
|
@ -17,8 +17,9 @@ package ghidra.trace.database.program;
|
|||
|
||||
import static ghidra.lifecycle.Unfinished.TODO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
|
@ -175,21 +176,42 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
|
|||
dbRef.setPrimary(isPrimary);
|
||||
}
|
||||
|
||||
protected boolean any(boolean noSpace, Predicate<Long> predicate) {
|
||||
if (refs(false) == null) {
|
||||
return noSpace;
|
||||
}
|
||||
for (long s : program.viewport.getOrderedSnaps()) {
|
||||
if (predicate.test(s)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Collection<Reference> collect(
|
||||
Function<Long, Collection<? extends Reference>> refFunc) {
|
||||
if (refs(false) == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Set<Reference> result = new LinkedHashSet<>();
|
||||
for (long s : program.viewport.getOrderedSnaps()) {
|
||||
Collection<? extends Reference> from = refFunc.apply(s);
|
||||
if (from != null) {
|
||||
result.addAll(from);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFlowReferencesFrom(Address addr) {
|
||||
if (refs(false) == null) {
|
||||
return false;
|
||||
}
|
||||
return !refs.getFlowRefrencesFrom(program.snap, addr).isEmpty();
|
||||
return any(false, s -> !refs.getFlowReferencesFrom(s, addr).isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reference[] getFlowReferencesFrom(Address addr) {
|
||||
Collection<? extends TraceReference> from = refs(false) == null
|
||||
? Collections.emptyList()
|
||||
: refs.getFlowRefrencesFrom(program.snap, addr);
|
||||
// TODO: Requires two traversals. Not terrible for this size....
|
||||
return from.toArray(new Reference[from.size()]);
|
||||
Collection<Reference> result = collect(s -> refs.getFlowReferencesFrom(s, addr));
|
||||
return result.toArray(new Reference[result.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -199,138 +221,151 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
|
|||
|
||||
@Override
|
||||
public ReferenceIterator getReferencesTo(Address addr) {
|
||||
Collection<? extends TraceReference> to = refs(false) == null
|
||||
? Collections.emptyList()
|
||||
: refs.getReferencesTo(program.snap, addr);
|
||||
return new ReferenceIteratorAdapter(to.iterator());
|
||||
Collection<Reference> result = collect(s -> refs.getReferencesTo(s, addr));
|
||||
return new ReferenceIteratorAdapter(result.iterator());
|
||||
}
|
||||
|
||||
protected Comparator<Reference> getReferenceFromComparator(boolean forward) {
|
||||
return forward
|
||||
? (r1, r2) -> r1.getFromAddress().compareTo(r2.getFromAddress())
|
||||
: (r1, r2) -> -r1.getFromAddress().compareTo(r2.getFromAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReferenceIterator getReferenceIterator(Address startAddr) {
|
||||
Collection<? extends TraceReference> from = refs(false) == null
|
||||
? Collections.emptyList()
|
||||
: refs.getReferencesFrom(program.snap, startAddr);
|
||||
return new ReferenceIteratorAdapter(from.iterator());
|
||||
if (refs(false) == null) {
|
||||
return new ReferenceIteratorAdapter(Collections.emptyIterator());
|
||||
}
|
||||
return new ReferenceIteratorAdapter(
|
||||
program.viewport.mergedIterator(s -> refs.getReferencesFrom(s, startAddr).iterator(),
|
||||
getReferenceFromComparator(true)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reference getReference(Address fromAddr, Address toAddr, int opIndex) {
|
||||
return refs(false) == null
|
||||
? null
|
||||
: refs.getReference(program.snap, fromAddr, toAddr, opIndex);
|
||||
if (refs(false) == null) {
|
||||
return null;
|
||||
}
|
||||
return program.viewport.getTop(s -> refs.getReference(s, fromAddr, toAddr, opIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reference[] getReferencesFrom(Address addr) {
|
||||
Collection<? extends TraceReference> from = refs(false) == null
|
||||
? Collections.emptyList()
|
||||
: refs.getReferencesFrom(program.snap, addr);
|
||||
return from.toArray(new Reference[from.size()]);
|
||||
Collection<Reference> result = collect(s -> refs.getReferencesFrom(s, addr));
|
||||
return result.toArray(new Reference[result.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reference[] getReferencesFrom(Address fromAddr, int opIndex) {
|
||||
Collection<? extends TraceReference> from = refs(false) == null
|
||||
? Collections.emptyList()
|
||||
: refs.getReferencesFrom(program.snap, fromAddr, opIndex);
|
||||
return from.toArray(new Reference[from.size()]);
|
||||
Collection<Reference> result = collect(s -> refs.getReferencesFrom(s, fromAddr, opIndex));
|
||||
return result.toArray(new Reference[result.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasReferencesFrom(Address fromAddr, int opIndex) {
|
||||
return refs(false) == null
|
||||
? false
|
||||
: !refs.getReferencesFrom(program.snap, fromAddr, opIndex).isEmpty();
|
||||
return any(false, s -> !refs.getReferencesFrom(s, fromAddr, opIndex).isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasReferencesFrom(Address fromAddr) {
|
||||
return refs(false) == null
|
||||
? false
|
||||
: !refs.getReferencesFrom(program.snap, fromAddr).isEmpty();
|
||||
return any(false, s -> !refs.getReferencesFrom(s, fromAddr).isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reference getPrimaryReferenceFrom(Address addr, int opIndex) {
|
||||
return refs(false) == null
|
||||
? null
|
||||
: refs.getPrimaryReferenceFrom(program.snap, addr, opIndex);
|
||||
if (refs(false) == null) {
|
||||
return null;
|
||||
}
|
||||
return program.viewport.getTop(s -> refs.getPrimaryReferenceFrom(s, addr, opIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getReferenceSourceIterator(Address startAddr, boolean forward) {
|
||||
return refs(false) == null
|
||||
? new EmptyAddressIterator()
|
||||
: refs.getReferenceSources(Range.closed(program.snap, program.snap))
|
||||
.getAddresses(startAddr, forward);
|
||||
if (refs(false) == null) {
|
||||
return new EmptyAddressIterator();
|
||||
}
|
||||
return program.viewport.unionedAddresses(
|
||||
s -> refs.getReferenceSources(Range.singleton(s))).getAddresses(startAddr, forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getReferenceSourceIterator(AddressSetView addrSet, boolean forward) {
|
||||
return refs(false) == null
|
||||
? new EmptyAddressIterator()
|
||||
: new IntersectionAddressSetView(
|
||||
refs.getReferenceSources(Range.closed(program.snap, program.snap)), addrSet)
|
||||
.getAddresses(forward);
|
||||
if (refs(false) == null) {
|
||||
return new EmptyAddressIterator();
|
||||
}
|
||||
return new IntersectionAddressSetView(addrSet, program.viewport.unionedAddresses(
|
||||
s -> refs.getReferenceSources(Range.singleton(s)))).getAddresses(forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getReferenceDestinationIterator(Address startAddr, boolean forward) {
|
||||
return refs(false) == null
|
||||
? new EmptyAddressIterator()
|
||||
: refs.getReferenceDestinations(Range.closed(program.snap, program.snap))
|
||||
.getAddresses(startAddr, forward);
|
||||
if (refs(false) == null) {
|
||||
return new EmptyAddressIterator();
|
||||
}
|
||||
return program.viewport.unionedAddresses(
|
||||
s -> refs.getReferenceDestinations(Range.singleton(s)))
|
||||
.getAddresses(startAddr, forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getReferenceDestinationIterator(AddressSetView addrSet,
|
||||
boolean forward) {
|
||||
return refs(false) == null
|
||||
? new EmptyAddressIterator()
|
||||
: new IntersectionAddressSetView(
|
||||
refs.getReferenceDestinations(Range.closed(program.snap, program.snap)),
|
||||
addrSet).getAddresses(forward);
|
||||
if (refs(false) == null) {
|
||||
return new EmptyAddressIterator();
|
||||
}
|
||||
return new IntersectionAddressSetView(addrSet, program.viewport.unionedAddresses(
|
||||
s -> refs.getReferenceDestinations(Range.singleton(s)))).getAddresses(forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReferenceCountTo(Address toAddr) {
|
||||
return refs(false) == null
|
||||
? 0
|
||||
: refs.getReferenceCountTo(program.snap, toAddr);
|
||||
if (refs(false) == null) {
|
||||
return 0;
|
||||
}
|
||||
if (!program.viewport.isForked()) {
|
||||
return refs.getReferenceCountTo(program.snap, toAddr);
|
||||
}
|
||||
return collect(s -> refs.getReferencesTo(s, toAddr)).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReferenceCountFrom(Address fromAddr) {
|
||||
return refs(false) == null
|
||||
? 0
|
||||
: refs.getReferenceCountFrom(program.snap, fromAddr);
|
||||
if (refs(false) == null) {
|
||||
return 0;
|
||||
}
|
||||
if (!program.viewport.isForked()) {
|
||||
return refs.getReferenceCountFrom(program.snap, fromAddr);
|
||||
}
|
||||
return collect(s -> refs.getReferencesFrom(s, fromAddr)).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReferenceDestinationCount() {
|
||||
// TODO: It is unclear if the interface definition means to include unique addresses
|
||||
// or also unique references
|
||||
return refs(false) == null
|
||||
? 0
|
||||
: (int) refs.getReferenceDestinations(Range.closed(program.snap, program.snap))
|
||||
.getNumAddresses();
|
||||
if (refs(false) == null) {
|
||||
return 0;
|
||||
}
|
||||
return (int) program.viewport
|
||||
.unionedAddresses(s -> refs.getReferenceDestinations(Range.singleton(s)))
|
||||
.getNumAddresses();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getReferenceSourceCount() {
|
||||
// TODO: It is unclear if the interface definition means to include unique addresses
|
||||
// or also unique references
|
||||
return refs(false) == null
|
||||
? 0
|
||||
: (int) refs.getReferenceSources(Range.closed(program.snap, program.snap))
|
||||
.getNumAddresses();
|
||||
if (refs(false) == null) {
|
||||
return 0;
|
||||
}
|
||||
return (int) program.viewport
|
||||
.unionedAddresses(s -> refs.getReferenceSources(Range.singleton(s)))
|
||||
.getNumAddresses();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasReferencesTo(Address toAddr) {
|
||||
return refs(false) == null
|
||||
? false
|
||||
: !refs.getReferencesTo(program.snap, toAddr).isEmpty();
|
||||
return any(false, s -> !refs.getReferencesTo(s, toAddr).isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -361,9 +396,11 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
|
|||
/**
|
||||
* Get the reference level for a given reference type
|
||||
*
|
||||
* <p>
|
||||
* TODO: Why is this not a property of {@link RefType}, or a static method of
|
||||
* {@link SymbolUtilities}?
|
||||
*
|
||||
* <p>
|
||||
* Note that this was copy-pasted from {@code BigRefListV0}, and there's an exact copy also in
|
||||
* {@code RefListV0}.
|
||||
*
|
||||
|
@ -389,11 +426,13 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
|
|||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* To clarify, "reference level" is a sort of priority assigned to each reference type. See,
|
||||
* e.g., {@link SymbolUtilities#SUB_LEVEL}. Each is a byte constant, and greater values imply
|
||||
* higher priority. This method returns the highest priority of any reference to the given
|
||||
* address.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Track this in the database?
|
||||
*/
|
||||
@Override
|
||||
|
@ -402,8 +441,10 @@ public abstract class AbstractDBTraceProgramViewReferenceManager implements Refe
|
|||
return SymbolUtilities.UNK_LEVEL;
|
||||
}
|
||||
byte highest = SymbolUtilities.UNK_LEVEL;
|
||||
for (TraceReference ref : refs.getReferencesTo(program.snap, toAddr)) {
|
||||
highest = (byte) Math.max(highest, getRefLevel(ref.getReferenceType()));
|
||||
for (long s : program.viewport.getOrderedSnaps()) {
|
||||
for (TraceReference ref : refs.getReferencesTo(s, toAddr)) {
|
||||
highest = (byte) Math.max(highest, getRefLevel(ref.getReferenceType()));
|
||||
}
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
|
|
@ -17,8 +17,10 @@ package ghidra.trace.database.program;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
|
@ -34,6 +36,7 @@ import ghidra.program.model.address.*;
|
|||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.MemoryAccessException;
|
||||
import ghidra.program.model.pcode.Varnode;
|
||||
import ghidra.program.model.reloc.RelocationTable;
|
||||
import ghidra.program.model.symbol.*;
|
||||
|
@ -42,9 +45,9 @@ import ghidra.program.model.util.PropertyMapManager;
|
|||
import ghidra.program.util.ChangeManager;
|
||||
import ghidra.program.util.ProgramChangeRecord;
|
||||
import ghidra.trace.database.DBTrace;
|
||||
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryRegion;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryRegisterSpace;
|
||||
import ghidra.trace.database.listing.*;
|
||||
import ghidra.trace.database.memory.*;
|
||||
import ghidra.trace.database.symbol.DBTraceFunctionSymbolView;
|
||||
import ghidra.trace.model.Trace.*;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
|
@ -53,26 +56,29 @@ import ghidra.trace.model.bookmark.TraceBookmarkType;
|
|||
import ghidra.trace.model.data.TraceBasedDataTypeManager;
|
||||
import ghidra.trace.model.listing.*;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.program.TraceProgramViewMemory;
|
||||
import ghidra.trace.model.symbol.*;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceAddressSpace;
|
||||
import ghidra.util.PairingIteratorMerger;
|
||||
import ghidra.util.UniversalID;
|
||||
import ghidra.trace.util.*;
|
||||
import ghidra.trace.util.TraceTimeViewport.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.datastruct.WeakValueHashMap;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* A wrapper on a trace, which given a snap, implements the {@link Program} interface
|
||||
*
|
||||
* <p>
|
||||
* NOTE: Calling {@link CodeUnit#getProgram()} from units contained in this view may not necessarily
|
||||
* return this same view. If the code unit comes from a less-recent snap than the snap associated
|
||||
* with this view, the view for that snap is returned instead.
|
||||
*
|
||||
* TODO: Unit tests for all of this
|
||||
* <p>
|
||||
* TODO: Unit tests for all of this.
|
||||
*/
|
||||
public class DBTraceProgramView implements TraceProgramView {
|
||||
public static final int TIME_INTERVAL = 100;
|
||||
|
@ -160,58 +166,6 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
listenFor(TraceSymbolChangeType.DELETED, this::symbolDeleted);
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues getEventQueues(TraceAddressSpace space) {
|
||||
// TODO: Should there be views on other frames?
|
||||
// IIRC, this was an abandoned experiment for "register listings"
|
||||
TraceThread thread = space == null ? null : space.getThread();
|
||||
if (thread == null) {
|
||||
return eventQueues;
|
||||
}
|
||||
DBTraceProgramViewRegisters viewRegisters;
|
||||
synchronized (regViewsByThread) {
|
||||
viewRegisters = regViewsByThread.get(thread);
|
||||
}
|
||||
return viewRegisters == null ? null : viewRegisters.eventQueues;
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues isVisible(TraceAddressSpace space,
|
||||
TraceAddressSnapRange range) {
|
||||
if (!range.getLifespan().contains(snap)) {
|
||||
return null;
|
||||
}
|
||||
return getEventQueues(space);
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues isVisible(TraceAddressSpace space, TraceCodeUnit cu) {
|
||||
if (!cu.getLifespan().contains(snap)) {
|
||||
return null;
|
||||
}
|
||||
return getEventQueues(space);
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues isVisible(TraceAddressSpace space, TraceSymbol symbol) {
|
||||
DomainObjectEventQueues queues = getEventQueues(space);
|
||||
if (queues == null) {
|
||||
return null;
|
||||
}
|
||||
if (symbol instanceof TraceVariableSymbol) {
|
||||
TraceVariableSymbol var = (TraceVariableSymbol) symbol;
|
||||
TraceFunctionSymbol func = var.getFunction();
|
||||
if (func == null) {
|
||||
return queues;
|
||||
}
|
||||
return func.getLifespan().contains(snap) ? queues : null;
|
||||
}
|
||||
if (!(symbol instanceof TraceSymbolWithLifespan)) {
|
||||
return queues;
|
||||
}
|
||||
TraceSymbolWithLifespan symWl = (TraceSymbolWithLifespan) symbol;
|
||||
if (!symWl.getLifespan().contains(snap)) {
|
||||
return null;
|
||||
}
|
||||
return queues;
|
||||
}
|
||||
|
||||
private void eventPassthrough(DomainObjectChangeRecord rec) {
|
||||
fireEventAllViews(rec);
|
||||
}
|
||||
|
@ -227,13 +181,10 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
private void bookmarkAdded(TraceAddressSpace space, TraceBookmark bm) {
|
||||
DomainObjectEventQueues queues = getEventQueues(space);
|
||||
DomainObjectEventQueues queues = isBookmarkVisible(space, bm);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
if (!bm.getLifespan().contains(snap)) {
|
||||
return;
|
||||
}
|
||||
fireBookmarkAdded(queues, bm);
|
||||
}
|
||||
|
||||
|
@ -243,13 +194,10 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
private void bookmarkChanged(TraceAddressSpace space, TraceBookmark bm) {
|
||||
DomainObjectEventQueues queues = getEventQueues(space);
|
||||
DomainObjectEventQueues queues = isBookmarkVisible(space, bm);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
if (!bm.getLifespan().contains(snap)) {
|
||||
return;
|
||||
}
|
||||
fireBookmarkChanged(queues, bm);
|
||||
}
|
||||
|
||||
|
@ -259,14 +207,13 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
private void bookmarkLifespanChanged(TraceAddressSpace space, TraceBookmark bm,
|
||||
Range<Long> oldSpan,
|
||||
Range<Long> newSpan) {
|
||||
Range<Long> oldSpan, Range<Long> newSpan) {
|
||||
DomainObjectEventQueues queues = getEventQueues(space);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
boolean inOld = oldSpan.contains(snap);
|
||||
boolean inNew = newSpan.contains(snap);
|
||||
boolean inOld = isBookmarkVisible(bm, oldSpan);
|
||||
boolean inNew = isBookmarkVisible(bm, newSpan);
|
||||
if (inOld && !inNew) {
|
||||
fireBookmarkRemoved(queues, bm);
|
||||
}
|
||||
|
@ -276,13 +223,10 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
private void bookmarkDeleted(TraceAddressSpace space, TraceBookmark bm) {
|
||||
DomainObjectEventQueues queues = getEventQueues(space);
|
||||
DomainObjectEventQueues queues = isBookmarkVisible(space, bm);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
if (!bm.getLifespan().contains(snap)) {
|
||||
return;
|
||||
}
|
||||
fireBookmarkRemoved(queues, bm);
|
||||
}
|
||||
|
||||
|
@ -317,7 +261,9 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
private void codeAdded(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
TraceCodeUnit oldIsNull, TraceCodeUnit added) {
|
||||
// NOTE: Added code may be coalesced range. -added- is just first unit.
|
||||
DomainObjectEventQueues queues = isVisible(space, range);
|
||||
// TODO: The range may contain many units, so this could be broken down
|
||||
DomainObjectEventQueues queues =
|
||||
isCodeVisible(space, range) ? getEventQueues(space) : null;
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -335,8 +281,8 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
boolean inOld = oldSpan.contains(snap);
|
||||
boolean inNew = newSpan.contains(snap);
|
||||
boolean inOld = isCodeVisible(unit, oldSpan);
|
||||
boolean inNew = isCodeVisible(unit, newSpan);
|
||||
if (inOld && !inNew) {
|
||||
fireCodeRemoved(queues, unit.getMinAddress(), unit.getMaxAddress(), unit);
|
||||
}
|
||||
|
@ -348,7 +294,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
private void codeRemoved(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
TraceCodeUnit removed, TraceCodeUnit newIsNull) {
|
||||
// NOTE: Removed code may be coalesced range. -removed- is just first unit.
|
||||
DomainObjectEventQueues queues = isVisible(space, range);
|
||||
DomainObjectEventQueues queues = isCodeVisible(space, removed);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -373,6 +319,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void codeDataTypeReplaced(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
Long oldDataTypeID, Long newDataTypeID) {
|
||||
// TODO??: "code" visibility check may not be necessary or advantageous
|
||||
DomainObjectEventQueues queues = isVisible(space, range);
|
||||
if (queues == null) {
|
||||
return;
|
||||
|
@ -383,7 +330,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void compositeDataAdded(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
TraceData oldIsNull, TraceData added) {
|
||||
DomainObjectEventQueues queues = isVisible(space, range);
|
||||
DomainObjectEventQueues queues = isCodeVisible(space, added);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -397,8 +344,8 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
boolean inOld = oldSpan.contains(snap);
|
||||
boolean inNew = newSpan.contains(snap);
|
||||
boolean inOld = isCodeVisible(data, oldSpan);
|
||||
boolean inNew = isCodeVisible(data, newSpan);
|
||||
if (inOld && !inNew) {
|
||||
queues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_COMPOSITE_REMOVED,
|
||||
data.getMinAddress(), data.getMaxAddress(), null, data, null));
|
||||
|
@ -411,7 +358,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void compositeDataRemoved(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
TraceData removed, TraceData newIsNull) {
|
||||
DomainObjectEventQueues queues = isVisible(space, range);
|
||||
DomainObjectEventQueues queues = isCodeVisible(space, removed);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -473,7 +420,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void functionChangedGeneric(TraceAddressSpace space, TraceFunctionSymbol function,
|
||||
int type, int subType) {
|
||||
DomainObjectEventQueues queues = isVisible(space, function);
|
||||
DomainObjectEventQueues queues = isFunctionVisible(space, function);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -557,7 +504,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void instructionFlowOverrideChanged(TraceAddressSpace space,
|
||||
TraceInstruction instruction, FlowOverride oldOverride, FlowOverride newOverride) {
|
||||
DomainObjectEventQueues queues = isVisible(space, instruction);
|
||||
DomainObjectEventQueues queues = isCodeVisible(space, instruction);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -567,7 +514,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void instructionFallThroughChanged(TraceAddressSpace space,
|
||||
TraceInstruction instruction, boolean oldFallThrough, boolean newFallThrough) {
|
||||
DomainObjectEventQueues queues = isVisible(space, instruction);
|
||||
DomainObjectEventQueues queues = isCodeVisible(space, instruction);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -577,7 +524,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void memoryBytesChanged(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||
byte[] oldIsNull, byte[] bytes) {
|
||||
DomainObjectEventQueues queues = isVisible(space, range);
|
||||
DomainObjectEventQueues queues = isBytesVisible(space, range);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -591,7 +538,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
private void memoryRegionAdded(TraceAddressSpace space, TraceMemoryRegion region) {
|
||||
if (!region.getLifespan().contains(snap)) {
|
||||
if (!isRegionVisible(region)) {
|
||||
return;
|
||||
}
|
||||
// NOTE: Register view regions are fixed
|
||||
|
@ -602,7 +549,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
private void memoryRegionChanged(TraceAddressSpace space, TraceMemoryRegion region) {
|
||||
if (!region.getLifespan().contains(snap)) {
|
||||
if (!isRegionVisible(region)) {
|
||||
return;
|
||||
}
|
||||
eventQueues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_MEMORY_BLOCK_CHANGED,
|
||||
|
@ -614,8 +561,8 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void memoryRegionLifespanChanged(TraceAddressSpace space, TraceMemoryRegion region,
|
||||
Range<Long> oldSpan, Range<Long> newSpan) {
|
||||
boolean inOld = oldSpan.contains(snap);
|
||||
boolean inNew = newSpan.contains(snap);
|
||||
boolean inOld = isRegionVisible(region, oldSpan);
|
||||
boolean inNew = isRegionVisible(region, newSpan);
|
||||
if (inOld && !inNew) {
|
||||
eventQueues.fireEvent(
|
||||
new ProgramChangeRecord(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED,
|
||||
|
@ -637,7 +584,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
// HACK
|
||||
listing.fragmentsByRegion.remove(region);
|
||||
// END HACK
|
||||
if (!region.getLifespan().contains(snap)) {
|
||||
if (!isRegionVisible(region)) {
|
||||
return;
|
||||
}
|
||||
eventQueues.fireEvent(new ProgramChangeRecord(ChangeManager.DOCR_MEMORY_BLOCK_REMOVED,
|
||||
|
@ -678,7 +625,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
private void symbolAdded(TraceAddressSpace space, TraceSymbol symbol) {
|
||||
DomainObjectEventQueues queues = isVisible(space, symbol);
|
||||
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -697,7 +644,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
private void symbolSourceChanged(TraceAddressSpace space, TraceSymbol symbol) {
|
||||
DomainObjectEventQueues queues = isVisible(space, symbol);
|
||||
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -709,11 +656,11 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
private void symbolSetAsPrimary(TraceAddressSpace space, TraceSymbol symbol,
|
||||
TraceSymbol oldPrimary, TraceSymbol newPrimary) {
|
||||
// NOTE symbol == newPrimary
|
||||
DomainObjectEventQueues newQueues = isVisible(space, symbol);
|
||||
DomainObjectEventQueues newQueues = isSymbolVisible(space, symbol);
|
||||
if (newQueues == null) {
|
||||
return;
|
||||
}
|
||||
DomainObjectEventQueues oldQueues = isVisible(space, oldPrimary);
|
||||
DomainObjectEventQueues oldQueues = isSymbolVisible(space, oldPrimary);
|
||||
if (oldPrimary != null && oldQueues == null) {
|
||||
oldPrimary = null;
|
||||
}
|
||||
|
@ -725,7 +672,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void symbolRenamed(TraceAddressSpace space, TraceSymbol symbol, String oldName,
|
||||
String newName) {
|
||||
DomainObjectEventQueues queues = isVisible(space, symbol);
|
||||
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -736,7 +683,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void symbolParentChanged(TraceAddressSpace space, TraceSymbol symbol,
|
||||
TraceNamespaceSymbol oldParent, TraceNamespaceSymbol newParent) {
|
||||
DomainObjectEventQueues queues = isVisible(space, symbol);
|
||||
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -747,7 +694,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void symbolAssociationAdded(TraceAddressSpace space, TraceSymbol symbol,
|
||||
TraceReference oldRefIsNull, TraceReference newRef) {
|
||||
DomainObjectEventQueues queues = isVisible(space, symbol);
|
||||
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -759,7 +706,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void symbolAssociationRemoved(TraceAddressSpace space, TraceSymbol symbol,
|
||||
TraceReference oldRef, TraceReference newRefIsNull) {
|
||||
DomainObjectEventQueues queues = isVisible(space, symbol);
|
||||
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -770,7 +717,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
private void symbolAddressChanged(TraceAddressSpace space, TraceSymbol symbol,
|
||||
Address oldAddress, Address newAddress) {
|
||||
DomainObjectEventQueues queues = isVisible(space, symbol);
|
||||
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -779,14 +726,14 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
checkVariableFunctionChanged(space, symbol);
|
||||
}
|
||||
|
||||
private void symbolLifespanChanged(TraceAddressSpace space, TraceSymbol symbol,
|
||||
private void symbolLifespanChanged(TraceAddressSpace space, TraceSymbolWithLifespan symbol,
|
||||
Range<Long> oldSpan, Range<Long> newSpan) {
|
||||
DomainObjectEventQueues queues = getEventQueues(space);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
boolean inOld = oldSpan.contains(snap);
|
||||
boolean inNew = newSpan.contains(snap);
|
||||
boolean inOld = isSymbolWithLifespanVisible(symbol, oldSpan);
|
||||
boolean inNew = isSymbolWithLifespanVisible(symbol, newSpan);
|
||||
if (inOld && !inNew) {
|
||||
fireSymbolRemoved(queues, symbol);
|
||||
if (symbol instanceof TraceFunctionSymbol) {
|
||||
|
@ -807,7 +754,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
private void symbolDeleted(TraceAddressSpace space, TraceSymbol symbol) {
|
||||
DomainObjectEventQueues queues = isVisible(space, symbol);
|
||||
DomainObjectEventQueues queues = isSymbolVisible(space, symbol);
|
||||
if (queues == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -830,6 +777,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
protected static class OverlappingAddressRangeKeyIteratorMerger<T> extends
|
||||
PairingIteratorMerger<Entry<AddressRange, T>, Entry<AddressRange, T>, Entry<AddressRange, T>> {
|
||||
|
||||
protected static <T> Iterable<Pair<Entry<AddressRange, T>, Entry<AddressRange, T>>> iter(
|
||||
Iterable<Entry<AddressRange, T>> left, Iterable<Entry<AddressRange, T>> right) {
|
||||
return new Iterable<>() {
|
||||
|
@ -864,6 +812,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
protected final DomainObjectEventQueues eventQueues;
|
||||
protected EventTranslator eventTranslator;
|
||||
protected final AddressSet allAddresses = new AddressSet();
|
||||
|
||||
protected final DBTraceProgramViewBookmarkManager bookmarkManager;
|
||||
protected final DBTraceProgramViewEquateTable equateTable;
|
||||
|
@ -881,17 +830,28 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
protected final Map<TraceThread, DBTraceProgramViewRegisters> regViewsByThread;
|
||||
|
||||
protected long snap;
|
||||
protected final DefaultTraceTimeViewport viewport;
|
||||
protected final Runnable viewportChangeListener = this::viewportChanged;
|
||||
|
||||
// This is a strange thing
|
||||
Long versionTag = 0L;
|
||||
|
||||
public DBTraceProgramView(DBTrace trace, long snap, CompilerSpec compilerSpec) {
|
||||
for (AddressSpace space : trace.getBaseAddressFactory().getPhysicalSpaces()) {
|
||||
if (space.getType() == AddressSpace.TYPE_OTHER) {
|
||||
continue;
|
||||
}
|
||||
allAddresses.add(space.getMinAddress(), space.getMaxAddress());
|
||||
}
|
||||
this.trace = trace;
|
||||
this.snap = snap;
|
||||
this.languageID = compilerSpec.getLanguage().getLanguageID();
|
||||
this.language = compilerSpec.getLanguage();
|
||||
this.compilerSpec = compilerSpec;
|
||||
|
||||
this.viewport = new DefaultTraceTimeViewport(trace);
|
||||
this.viewport.setSnap(snap);
|
||||
|
||||
this.eventQueues =
|
||||
new DomainObjectEventQueues(this, TIME_INTERVAL, BUF_SIZE, trace.getLock());
|
||||
|
||||
|
@ -911,6 +871,10 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
}
|
||||
|
||||
protected void viewportChanged() {
|
||||
eventQueues.fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
|
||||
}
|
||||
|
||||
protected void fireEventAllViews(DomainObjectChangeRecord ev) {
|
||||
// TODO: Do I need to make copies?
|
||||
eventQueues.fireEvent(ev);
|
||||
|
@ -934,6 +898,11 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
return snap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceTimeViewport getViewport() {
|
||||
return viewport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getMaxSnap() {
|
||||
return trace.getTimeManager().getMaxSnap();
|
||||
|
@ -1527,21 +1496,21 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
public void updateMemoryAddBlock(DBTraceMemoryRegion region) {
|
||||
if (!region.getLifespan().contains(snap)) {
|
||||
if (!isRegionVisible(region)) {
|
||||
return;
|
||||
}
|
||||
memory.updateAddBlock(region);
|
||||
}
|
||||
|
||||
public void updateMemoryChangeBlockName(DBTraceMemoryRegion region) {
|
||||
if (!region.getLifespan().contains(snap)) {
|
||||
if (!isRegionVisible(region)) {
|
||||
return;
|
||||
}
|
||||
memory.updateChangeBlockName(region);
|
||||
}
|
||||
|
||||
public void updateMemoryChangeBlockFlags(DBTraceMemoryRegion region) {
|
||||
if (!region.getLifespan().contains(snap)) {
|
||||
if (!isRegionVisible(region)) {
|
||||
return;
|
||||
}
|
||||
memory.updateChangeBlockFlags(region);
|
||||
|
@ -1549,7 +1518,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
public void updateMemoryChangeBlockRange(DBTraceMemoryRegion region, AddressRange oldRange,
|
||||
AddressRange newRange) {
|
||||
if (!region.getLifespan().contains(snap)) {
|
||||
if (!isRegionVisible(region)) {
|
||||
return;
|
||||
}
|
||||
memory.updateChangeBlockRange(region, oldRange, newRange);
|
||||
|
@ -1557,8 +1526,8 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
|
||||
public void updateMemoryChangeBlockLifespan(DBTraceMemoryRegion region,
|
||||
Range<Long> oldLifespan, Range<Long> newLifespan) {
|
||||
boolean inOld = oldLifespan.contains(snap);
|
||||
boolean inNew = newLifespan.contains(snap);
|
||||
boolean inOld = isRegionVisible(region, oldLifespan);
|
||||
boolean inNew = isRegionVisible(region, newLifespan);
|
||||
if (inOld && !inNew) {
|
||||
memory.updateDeleteBlock(region);
|
||||
}
|
||||
|
@ -1568,7 +1537,7 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
}
|
||||
|
||||
public void updateMemoryDeleteBlock(DBTraceMemoryRegion region) {
|
||||
if (!region.getLifespan().contains(snap)) {
|
||||
if (!isRegionVisible(region)) {
|
||||
return;
|
||||
}
|
||||
memory.updateAddBlock(region);
|
||||
|
@ -1577,4 +1546,234 @@ public class DBTraceProgramView implements TraceProgramView {
|
|||
public void updateMemoryRefreshBlocks() {
|
||||
memory.updateRefreshBlocks();
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues getEventQueues(TraceAddressSpace space) {
|
||||
// TODO: Should there be views on other frames?
|
||||
// IIRC, this was an abandoned experiment for "register listings"
|
||||
TraceThread thread = space == null ? null : space.getThread();
|
||||
if (thread == null) {
|
||||
return eventQueues;
|
||||
}
|
||||
DBTraceProgramViewRegisters viewRegisters;
|
||||
synchronized (regViewsByThread) {
|
||||
viewRegisters = regViewsByThread.get(thread);
|
||||
}
|
||||
return viewRegisters == null ? null : viewRegisters.eventQueues;
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues isVisible(TraceAddressSpace space,
|
||||
TraceAddressSnapRange range) {
|
||||
return viewport.containsAnyUpper(range.getLifespan()) ? getEventQueues(space) : null;
|
||||
}
|
||||
|
||||
protected boolean isBookmarkVisible(TraceBookmark bm, Range<Long> lifespan) {
|
||||
return viewport.containsAnyUpper(lifespan);
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues isBookmarkVisible(TraceAddressSpace space, TraceBookmark bm) {
|
||||
return isBookmarkVisible(bm, bm.getLifespan()) ? getEventQueues(space) : null;
|
||||
}
|
||||
|
||||
protected boolean bytesDifferForSet(byte[] b1, byte[] b2, AddressSetView set) {
|
||||
Address min = set.getMinAddress();
|
||||
for (AddressRange rng : set) {
|
||||
int beg = (int) rng.getMinAddress().subtract(min);
|
||||
int end = beg + (int) rng.getLength();
|
||||
if (!Arrays.equals(b1, beg, end, b2, beg, end)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Occlusion<TraceCodeUnit> getCodeOcclusion(TraceAddressSpace space) {
|
||||
return new RangeQueryOcclusion<>() {
|
||||
final DBTraceCodeSpace codeSpace = trace.getCodeManager().get(space, false);
|
||||
final DBTraceMemorySpace memSpace = trace.getMemoryManager().get(space, false);
|
||||
final DBTraceDefinedUnitsView definedUnits =
|
||||
codeSpace == null ? null : codeSpace.definedUnits();
|
||||
|
||||
public boolean occluded(TraceCodeUnit cu, AddressRange range, Range<Long> span) {
|
||||
if (cu == null) {
|
||||
return RangeQueryOcclusion.super.occluded(cu, range, span);
|
||||
}
|
||||
AddressSetView known =
|
||||
memSpace.getAddressesWithState(span, s -> s == TraceMemoryState.KNOWN);
|
||||
if (!known.intersects(range.getMinAddress(), range.getMaxAddress())) {
|
||||
return RangeQueryOcclusion.super.occluded(cu, range, span);
|
||||
}
|
||||
byte[] memBytes = new byte[cu.getLength()];
|
||||
memSpace.getBytes(span.upperEndpoint(), cu.getMinAddress(),
|
||||
ByteBuffer.wrap(memBytes));
|
||||
byte[] cuBytes;
|
||||
try {
|
||||
cuBytes = cu.getBytes();
|
||||
}
|
||||
catch (MemoryAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
AddressSetView intersectKnown =
|
||||
new IntersectionAddressSetView(new AddressSet(range), known);
|
||||
if (bytesDifferForSet(memBytes, cuBytes, intersectKnown)) {
|
||||
return true;
|
||||
}
|
||||
return RangeQueryOcclusion.super.occluded(cu, range, span);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends TraceCodeUnit> query(AddressRange range, Range<Long> span) {
|
||||
return definedUnits == null
|
||||
? Collections.emptyList()
|
||||
: definedUnits.get(span.upperEndpoint(), range, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange range(TraceCodeUnit cu) {
|
||||
return cu.getRange();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected <T extends TraceCodeUnit> T getTopCode(Address address,
|
||||
BiFunction<TraceCodeSpace, Long, T> codeFunc) {
|
||||
DBTraceCodeSpace codeSpace =
|
||||
trace.getCodeManager().getCodeSpace(address.getAddressSpace(), false);
|
||||
if (codeSpace == null) {
|
||||
return null;
|
||||
}
|
||||
return viewport.getTop(s -> {
|
||||
T t = codeFunc.apply(codeSpace, s);
|
||||
if (t != null && isCodeVisible(t, t.getLifespan())) {
|
||||
return t;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected boolean isCodeVisible(TraceCodeUnit cu, Range<Long> lifespan) {
|
||||
return viewport.isCompletelyVisible(cu.getRange(), lifespan, cu,
|
||||
getCodeOcclusion(cu.getTraceSpace()));
|
||||
}
|
||||
|
||||
protected boolean isCodeVisible(TraceAddressSpace space, TraceAddressSnapRange range) {
|
||||
return viewport.isCompletelyVisible(range.getRange(), range.getLifespan(), null,
|
||||
getCodeOcclusion(space));
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues isCodeVisible(TraceAddressSpace space, TraceCodeUnit cu) {
|
||||
if (!isCodeVisible(cu, cu.getLifespan())) {
|
||||
return null;
|
||||
}
|
||||
return getEventQueues(space);
|
||||
}
|
||||
|
||||
protected Occlusion<TraceFunctionSymbol> getFunctionOcclusion(TraceFunctionSymbol func) {
|
||||
return new QueryOcclusion<>() {
|
||||
DBTraceFunctionSymbolView functions = trace.getSymbolManager().functions();
|
||||
AddressSetView body = func.getBody();
|
||||
|
||||
@Override
|
||||
public Iterable<? extends TraceFunctionSymbol> query(AddressRange range,
|
||||
Range<Long> span) {
|
||||
// NB. No functions in register space!
|
||||
return functions.getIntersecting(Range.singleton(span.upperEndpoint()), null, range,
|
||||
false);
|
||||
}
|
||||
|
||||
public boolean itemOccludes(AddressRange range, TraceFunctionSymbol f) {
|
||||
return body.intersects(f.getBody());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeItem(AddressSet remains, TraceFunctionSymbol t) {
|
||||
remains.delete(t.getBody());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected boolean isFunctionVisible(TraceFunctionSymbol function, Range<Long> lifespan) {
|
||||
AddressSetView body = function.getBody();
|
||||
AddressRange bodySpan =
|
||||
new AddressRangeImpl(body.getMinAddress(), body.getMaxAddress());
|
||||
return viewport.isCompletelyVisible(bodySpan, function.getLifespan(), function,
|
||||
getFunctionOcclusion(function));
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues isFunctionVisible(TraceAddressSpace space,
|
||||
TraceFunctionSymbol function) {
|
||||
DomainObjectEventQueues queues = getEventQueues(space);
|
||||
if (queues == null) {
|
||||
return null;
|
||||
}
|
||||
return isFunctionVisible(function, function.getLifespan()) ? queues : null;
|
||||
}
|
||||
|
||||
protected boolean isSymbolWithLifespanVisible(TraceSymbolWithLifespan symbol,
|
||||
Range<Long> lifespan) {
|
||||
if (symbol instanceof TraceFunctionSymbol) {
|
||||
TraceFunctionSymbol func = (TraceFunctionSymbol) symbol;
|
||||
return isFunctionVisible(func, lifespan);
|
||||
}
|
||||
if (!viewport.containsAnyUpper(lifespan)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues isSymbolVisible(TraceAddressSpace space,
|
||||
TraceSymbol symbol) {
|
||||
// NB. Most symbols do not occlude each other
|
||||
DomainObjectEventQueues queues = getEventQueues(space);
|
||||
if (queues == null) {
|
||||
return null;
|
||||
}
|
||||
if (symbol instanceof TraceVariableSymbol) {
|
||||
TraceVariableSymbol var = (TraceVariableSymbol) symbol;
|
||||
TraceFunctionSymbol func = var.getFunction();
|
||||
if (func == null) {
|
||||
return queues;
|
||||
}
|
||||
return isFunctionVisible(space, func);
|
||||
}
|
||||
if (!(symbol instanceof TraceSymbolWithLifespan)) {
|
||||
return queues;
|
||||
}
|
||||
TraceSymbolWithLifespan symWl = (TraceSymbolWithLifespan) symbol;
|
||||
return isSymbolWithLifespanVisible(symWl, symWl.getLifespan()) ? queues : null;
|
||||
}
|
||||
|
||||
protected DomainObjectEventQueues isBytesVisible(TraceAddressSpace space,
|
||||
TraceAddressSnapRange range) {
|
||||
// NB. This need not be precise....
|
||||
DomainObjectEventQueues queues = getEventQueues(space);
|
||||
if (queues == null) {
|
||||
return null;
|
||||
}
|
||||
if (!viewport.containsAnyUpper(range.getLifespan())) {
|
||||
return null;
|
||||
}
|
||||
return queues;
|
||||
}
|
||||
|
||||
protected Occlusion<TraceMemoryRegion> regionOcclusion = new RangeQueryOcclusion<>() {
|
||||
@Override
|
||||
public Iterable<? extends TraceMemoryRegion> query(AddressRange range, Range<Long> span) {
|
||||
return trace.getMemoryManager()
|
||||
.getRegionsIntersecting(Range.singleton(span.upperEndpoint()), range);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange range(TraceMemoryRegion r) {
|
||||
return r.getRange();
|
||||
}
|
||||
};
|
||||
|
||||
protected boolean isRegionVisible(TraceMemoryRegion reg) {
|
||||
return isRegionVisible(reg, reg.getLifespan());
|
||||
}
|
||||
|
||||
protected boolean isRegionVisible(TraceMemoryRegion reg, Range<Long> lifespan) {
|
||||
return viewport.isCompletelyVisible(reg.getRange(), lifespan, reg,
|
||||
regionOcclusion);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import javax.swing.ImageIcon;
|
|||
|
||||
import org.apache.commons.collections4.IteratorUtils;
|
||||
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import generic.NestedIterator;
|
||||
|
@ -96,14 +97,16 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
|
|||
if (space == null) {
|
||||
return null;
|
||||
}
|
||||
for (TraceBookmark bm : space.getBookmarksAt(program.snap, addr)) {
|
||||
if (!type.equals(bm.getTypeString())) {
|
||||
continue;
|
||||
for (long s : program.viewport.getOrderedSnaps()) {
|
||||
for (TraceBookmark bm : space.getBookmarksAt(s, addr)) {
|
||||
if (!type.equals(bm.getTypeString())) {
|
||||
continue;
|
||||
}
|
||||
if (!category.equals(bm.getCategory())) {
|
||||
continue;
|
||||
}
|
||||
return bm;
|
||||
}
|
||||
if (!category.equals(bm.getCategory())) {
|
||||
continue;
|
||||
}
|
||||
return bm;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -222,11 +225,13 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
|
|||
return EMPTY_BOOKMARK_ARRAY;
|
||||
}
|
||||
List<Bookmark> list = new ArrayList<>();
|
||||
for (TraceBookmark bm : space.getBookmarksAt(program.snap, addr)) {
|
||||
if (!bm.getLifespan().contains(program.snap)) {
|
||||
continue;
|
||||
for (long s : program.viewport.getOrderedSnaps()) {
|
||||
for (TraceBookmark bm : space.getBookmarksAt(s, addr)) {
|
||||
if (!bm.getLifespan().contains(program.snap)) {
|
||||
continue;
|
||||
}
|
||||
list.add(bm);
|
||||
}
|
||||
list.add(bm);
|
||||
}
|
||||
return list.toArray(new Bookmark[list.size()]);
|
||||
}
|
||||
|
@ -241,11 +246,13 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
|
|||
return EMPTY_BOOKMARK_ARRAY;
|
||||
}
|
||||
List<Bookmark> list = new ArrayList<>();
|
||||
for (TraceBookmark bm : space.getBookmarksAt(program.snap, address)) {
|
||||
if (!type.equals(bm.getTypeString())) {
|
||||
continue;
|
||||
for (long s : program.viewport.getOrderedSnaps()) {
|
||||
for (TraceBookmark bm : space.getBookmarksAt(s, address)) {
|
||||
if (!type.equals(bm.getTypeString())) {
|
||||
continue;
|
||||
}
|
||||
list.add(bm);
|
||||
}
|
||||
list.add(bm);
|
||||
}
|
||||
return list.toArray(new Bookmark[list.size()]);
|
||||
}
|
||||
|
@ -261,10 +268,10 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
|
|||
return result;
|
||||
}
|
||||
for (TraceBookmark bm : bmt.getBookmarks()) {
|
||||
if (bm.getAddress().isRegisterAddress()) {
|
||||
if (bm.getAddress().getAddressSpace().isRegisterSpace()) {
|
||||
continue;
|
||||
}
|
||||
if (!bm.getLifespan().contains(program.snap)) {
|
||||
if (!program.viewport.containsAnyUpper(bm.getLifespan())) {
|
||||
continue;
|
||||
}
|
||||
result.add(bm.getAddress());
|
||||
|
@ -287,7 +294,7 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
|
|||
@SuppressWarnings("unchecked")
|
||||
protected static <T, U extends T> Iterator<T> filteredIterator(Iterator<U> it,
|
||||
Predicate<? super U> predicate) {
|
||||
return IteratorUtils.filteredIterator(it, e -> predicate.test((U) e));
|
||||
return (Iterator<T>) Iterators.filter(it, e -> predicate.test(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -298,14 +305,22 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
|
|||
}
|
||||
// TODO: May want to offer memory-only and/or register-only bookmark iterators
|
||||
return filteredIterator(bmt.getBookmarks().iterator(),
|
||||
bm -> !bm.getAddress().isRegisterAddress() && bm.getLifespan().contains(program.snap));
|
||||
bm -> !bm.getAddress().getAddressSpace().isRegisterSpace() &&
|
||||
program.viewport.containsAnyUpper(bm.getLifespan()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Bookmark> getBookmarksIterator() {
|
||||
// TODO: This seems terribly inefficient. We'll have to see how/when it's used.
|
||||
return NestedIterator.start(bookmarkManager.getActiveMemorySpaces().iterator(),
|
||||
space -> filteredIterator(space.getAllBookmarks().iterator(),
|
||||
bm -> bm.getLifespan().contains(program.snap)));
|
||||
bm -> program.viewport.containsAnyUpper(bm.getLifespan())));
|
||||
}
|
||||
|
||||
protected Comparator<Bookmark> getBookmarkComparator(boolean forward) {
|
||||
return forward
|
||||
? (b1, b2) -> b1.getAddress().compareTo(b2.getAddress())
|
||||
: (b1, b2) -> -b1.getAddress().compareTo(b2.getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -320,8 +335,9 @@ public class DBTraceProgramViewBookmarkManager implements TraceProgramViewBookma
|
|||
if (space == null) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
return space.getBookmarksIntersecting(Range.closed(program.snap, program.snap),
|
||||
rng).iterator();
|
||||
return program.viewport.mergedIterator(
|
||||
s -> space.getBookmarksIntersecting(Range.closed(s, s), rng).iterator(),
|
||||
getBookmarkComparator(forward));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import ghidra.program.model.symbol.Equate;
|
|||
import ghidra.program.model.symbol.EquateTable;
|
||||
import ghidra.trace.database.symbol.DBTraceEquate;
|
||||
import ghidra.trace.database.symbol.DBTraceEquateManager;
|
||||
import ghidra.trace.model.listing.TraceCodeUnit;
|
||||
import ghidra.util.IntersectionAddressSetView;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.exception.*;
|
||||
|
@ -97,8 +98,13 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
|
|||
@Override
|
||||
public Equate getEquate(Address reference, int opndPosition, long value) {
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
return doGetViewEquate(
|
||||
equateManager.getReferencedByValue(program.snap, reference, opndPosition, value));
|
||||
TraceCodeUnit cu = program.getTopCode(reference,
|
||||
(space, s) -> space.definedUnits().getContaining(s, reference));
|
||||
if (cu == null) {
|
||||
return null;
|
||||
}
|
||||
return doGetViewEquate(equateManager.getReferencedByValue(cu.getStartSnap(), reference,
|
||||
opndPosition, value));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +112,12 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
|
|||
public List<Equate> getEquates(Address reference, int opndPosition) {
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
List<Equate> result = new ArrayList<>();
|
||||
for (DBTraceEquate equate : equateManager.getReferenced(program.snap, reference,
|
||||
TraceCodeUnit cu = program.getTopCode(reference,
|
||||
(space, s) -> space.definedUnits().getContaining(s, reference));
|
||||
if (cu == null) {
|
||||
return result;
|
||||
}
|
||||
for (DBTraceEquate equate : equateManager.getReferenced(cu.getStartSnap(), reference,
|
||||
opndPosition)) {
|
||||
result.add(doGetViewEquate(equate));
|
||||
}
|
||||
|
@ -118,7 +129,12 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
|
|||
public List<Equate> getEquates(Address reference) {
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
List<Equate> result = new ArrayList<>();
|
||||
for (DBTraceEquate equate : equateManager.getReferenced(program.snap, reference)) {
|
||||
TraceCodeUnit cu = program.getTopCode(reference,
|
||||
(space, s) -> space.definedUnits().getContaining(s, reference));
|
||||
if (cu == null) {
|
||||
return result;
|
||||
}
|
||||
for (DBTraceEquate equate : equateManager.getReferenced(cu.getStartSnap(), reference)) {
|
||||
result.add(doGetViewEquate(equate));
|
||||
}
|
||||
return result;
|
||||
|
@ -127,9 +143,9 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
|
|||
|
||||
@Override
|
||||
public AddressIterator getEquateAddresses() {
|
||||
return equateManager.getReferringAddresses(Range.singleton(program.snap))
|
||||
.getAddresses(
|
||||
true);
|
||||
return program.viewport
|
||||
.unionedAddresses(s -> equateManager.getReferringAddresses(Range.singleton(s)))
|
||||
.getAddresses(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -150,14 +166,15 @@ public class DBTraceProgramViewEquateTable implements EquateTable {
|
|||
|
||||
@Override
|
||||
public AddressIterator getEquateAddresses(Address start) {
|
||||
return equateManager.getReferringAddresses(Range.singleton(program.snap))
|
||||
.getAddresses(
|
||||
start, true);
|
||||
return program.viewport
|
||||
.unionedAddresses(s -> equateManager.getReferringAddresses(Range.singleton(s)))
|
||||
.getAddresses(start, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressIterator getEquateAddresses(AddressSetView asv) {
|
||||
return new IntersectionAddressSetView(asv,
|
||||
equateManager.getReferringAddresses(Range.singleton(program.snap))).getAddresses(true);
|
||||
return new IntersectionAddressSetView(asv, program.viewport
|
||||
.unionedAddresses(s -> equateManager.getReferringAddresses(Range.singleton(s))))
|
||||
.getAddresses(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@ import java.io.IOException;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import generic.NestedIterator;
|
||||
|
@ -35,8 +33,10 @@ import ghidra.program.model.symbol.Namespace;
|
|||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.trace.database.DBTraceUtils;
|
||||
import ghidra.trace.database.symbol.*;
|
||||
import ghidra.trace.model.listing.TraceData;
|
||||
import ghidra.trace.model.symbol.TraceFunctionSymbol;
|
||||
import ghidra.trace.util.EmptyFunctionIterator;
|
||||
import ghidra.trace.util.WrappingFunctionIterator;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.InvalidInputException;
|
||||
|
@ -44,34 +44,6 @@ import ghidra.util.task.TaskMonitor;
|
|||
|
||||
public class DBTraceProgramViewFunctionManager implements FunctionManager {
|
||||
|
||||
public static class FunctionIteratorAdapter implements FunctionIterator {
|
||||
private Iterator<? extends Function> it;
|
||||
|
||||
public FunctionIteratorAdapter(Iterator<? extends Function> it) {
|
||||
this.it = it;
|
||||
}
|
||||
|
||||
public <T extends Function> FunctionIteratorAdapter(Iterator<T> it,
|
||||
Predicate<? super T> filter) {
|
||||
this.it = Iterators.filter(it, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function next() {
|
||||
return it.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Function> iterator() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
protected final DBTraceProgramView program;
|
||||
protected final DBTraceFunctionSymbolView functions;
|
||||
protected final DBTraceNamespaceSymbol global;
|
||||
|
@ -176,10 +148,16 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
|
|||
if (!entryPoint.getAddressSpace().isMemorySpace()) {
|
||||
return null;
|
||||
}
|
||||
// NOTE: There ought only to be one, since no overlaps allowed.
|
||||
for (TraceFunctionSymbol at : functions.getAt(program.snap, null, entryPoint, false)) {
|
||||
if (entryPoint.equals(at.getEntryPoint())) {
|
||||
return at;
|
||||
|
||||
for (long s : program.viewport.getOrderedSnaps()) {
|
||||
// NOTE: There ought only to be one, since no overlaps allowed.
|
||||
for (TraceFunctionSymbol at : functions.getAt(s, null, entryPoint, false)) {
|
||||
if (entryPoint.equals(at.getEntryPoint())) {
|
||||
return at;
|
||||
}
|
||||
else {
|
||||
return null; // Anything below is occluded by the found function
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -187,16 +165,20 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
|
|||
|
||||
@Override
|
||||
public TraceFunctionSymbol getReferencedFunction(Address address) {
|
||||
if (!address.getAddressSpace().isMemorySpace()) {
|
||||
return null;
|
||||
}
|
||||
TraceFunctionSymbol found = getFunctionAt(address);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
// We're assuming a data reference
|
||||
if (program.trace.getCodeManager().data().getContaining(program.snap, address) == null) {
|
||||
TraceData data =
|
||||
program.getTopCode(address, (space, s) -> space.data().getContaining(s, address));
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
DBTraceReference ref =
|
||||
program.trace.getReferenceManager().getPrimaryReferenceFrom(program.snap, address, 0);
|
||||
DBTraceReference ref = program.trace.getReferenceManager()
|
||||
.getPrimaryReferenceFrom(data.getStartSnap(), address, 0);
|
||||
return ref == null ? null : getFunctionAt(ref.getToAddress());
|
||||
}
|
||||
|
||||
|
@ -228,7 +210,7 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
|
|||
|
||||
@Override
|
||||
public FunctionIterator getFunctions(AddressSetView asv, boolean forward) {
|
||||
return new FunctionIteratorAdapter(
|
||||
return new WrappingFunctionIterator(
|
||||
NestedIterator.start(asv.iterator(forward), rng -> getFunctionsInRange(rng, forward)),
|
||||
f -> {
|
||||
if (!asv.contains(f.getEntryPoint())) {
|
||||
|
@ -251,7 +233,7 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
|
|||
|
||||
@Override
|
||||
public FunctionIterator getFunctionsNoStubs(AddressSetView asv, boolean forward) {
|
||||
return new FunctionIteratorAdapter(
|
||||
return new WrappingFunctionIterator(
|
||||
NestedIterator.start(asv.iterator(forward), rng -> getFunctionsInRange(rng, forward)),
|
||||
f -> {
|
||||
if (f.isThunk()) {
|
||||
|
@ -316,7 +298,7 @@ public class DBTraceProgramViewFunctionManager implements FunctionManager {
|
|||
|
||||
@Override
|
||||
public Iterator<Function> getFunctionsOverlapping(AddressSetView set) {
|
||||
return new FunctionIteratorAdapter(
|
||||
return new WrappingFunctionIterator(
|
||||
NestedIterator.start(set.iterator(true), rng -> getFunctionsInRange(rng, true)));
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,6 @@ import ghidra.util.LockHold;
|
|||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
// TODO: Make at/since configurable?
|
||||
// NOTE: Probably not, esp., if I get the coloring right.
|
||||
public class DBTraceProgramViewListing extends AbstractDBTraceProgramViewListing {
|
||||
protected final AddressSet allMemory;
|
||||
|
||||
|
@ -36,10 +34,10 @@ public class DBTraceProgramViewListing extends AbstractDBTraceProgramViewListing
|
|||
public boolean isUndefined(Address start, Address end) {
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
for (AddressRange range : program.getAddressFactory().getAddressSet(start, end)) {
|
||||
if (!codeOperations.undefinedData()
|
||||
.coversRange(
|
||||
Range.closed(program.snap, program.snap), range)) {
|
||||
return false;
|
||||
for (long s : program.viewport.getOrderedSnaps()) {
|
||||
if (!isUndefinedRange(s, range)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
package ghidra.trace.database.program;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
|
@ -32,13 +34,33 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
|||
super(program);
|
||||
}
|
||||
|
||||
protected DBTraceMemoryRegion getTopRegion(Function<Long, DBTraceMemoryRegion> regFunc) {
|
||||
return program.viewport.getTop(s -> {
|
||||
// TODO: There is probably an early-bail condition I can check for.
|
||||
DBTraceMemoryRegion reg = regFunc.apply(s);
|
||||
if (reg != null && program.isRegionVisible(reg)) {
|
||||
return reg;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected void forVisibleRegions(Consumer<? super DBTraceMemoryRegion> action) {
|
||||
for (long s : program.viewport.getOrderedSnaps()) {
|
||||
// NOTE: This is slightly faster than new AddressSet(mm.getRegionsAddressSet(snap))
|
||||
for (DBTraceMemoryRegion reg : memoryManager.getRegionsAtSnap(s)) {
|
||||
if (program.isRegionVisible(reg)) {
|
||||
action.accept(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void recomputeAddressSet() {
|
||||
AddressSet temp = new AddressSet();
|
||||
// NOTE: This is slightly faster than new AddressSet(mm.getRegionsAddressSet(snap))
|
||||
for (DBTraceMemoryRegion reg : memoryManager.getRegionsAtSnap(snap)) {
|
||||
temp.add(reg.getRange());
|
||||
}
|
||||
// TODO: Performance test this
|
||||
forVisibleRegions(reg -> temp.add(reg.getRange()));
|
||||
addressSet = temp;
|
||||
}
|
||||
|
||||
|
@ -49,26 +71,21 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory {
|
|||
|
||||
@Override
|
||||
public MemoryBlock getBlock(Address addr) {
|
||||
DBTraceMemoryRegion region = memoryManager.getRegionContaining(snap, addr);
|
||||
DBTraceMemoryRegion region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr));
|
||||
return region == null ? null : getBlock(region);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryBlock getBlock(String blockName) {
|
||||
DBTraceMemoryRegion region = memoryManager.getLiveRegionByPath(snap, blockName);
|
||||
DBTraceMemoryRegion region =
|
||||
getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName));
|
||||
return region == null ? null : getBlock(region);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryBlock[] getBlocks() {
|
||||
List<MemoryBlock> result = new ArrayList<>();
|
||||
for (DBTraceMemoryRegion region : memoryManager.getRegionsInternal()) {
|
||||
MemoryBlock block = getBlock(region);
|
||||
if (block == null) {
|
||||
continue;
|
||||
}
|
||||
result.add(block);
|
||||
}
|
||||
forVisibleRegions(reg -> result.add(getBlock(reg)));
|
||||
return result.toArray(new MemoryBlock[result.size()]);
|
||||
}
|
||||
|
||||
|
|
|
@ -241,7 +241,7 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
|
|||
throw new MemoryAccessException("Space does not exist");
|
||||
}
|
||||
ByteBuffer buf = ByteBuffer.allocate(1);
|
||||
if (space.getBytes(program.snap, addr, buf) != 1) {
|
||||
if (space.getViewBytes(program.snap, addr, buf) != 1) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
return buf.get(0);
|
||||
|
@ -264,7 +264,7 @@ public class DBTraceProgramViewMemoryBlock implements MemoryBlock {
|
|||
throw new MemoryAccessException("Space does not exist");
|
||||
}
|
||||
len = (int) Math.min(len, range.getMaxAddress().subtract(addr) + 1);
|
||||
return space.getBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
|
||||
return space.getViewBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -53,8 +53,11 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
|
|||
List<Register> registers = language.getRegisters();
|
||||
List<Register> result = new ArrayList<>(registers.size());
|
||||
for (Register register : registers) {
|
||||
if (registerContextManager.hasRegisterValue(language, register, program.snap)) {
|
||||
result.add(register);
|
||||
for (long s : program.viewport.getReversedSnaps()) {
|
||||
if (registerContextManager.hasRegisterValue(language, register, s)) {
|
||||
result.add(register);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toArray(new Register[result.size()]);
|
||||
|
@ -66,10 +69,27 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
|
|||
return value == null ? null : signed ? value.getSignedValue() : value.getUnsignedValue();
|
||||
}
|
||||
|
||||
protected RegisterValue combine(RegisterValue v1, RegisterValue v2) {
|
||||
if (v1 == null) {
|
||||
return v2;
|
||||
}
|
||||
else if (v2 == null) {
|
||||
return v1;
|
||||
}
|
||||
return v1.combineValues(v2);
|
||||
}
|
||||
|
||||
protected RegisterValue stack(RegisterValue value, Register register, Address address) {
|
||||
for (long s : program.viewport.getReversedSnaps()) {
|
||||
value = combine(value, registerContextManager.getValue(language, register, s, address));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegisterValue getRegisterValue(Register register, Address address) {
|
||||
return registerContextManager.getValueWithDefault(language, register, program.snap,
|
||||
address);
|
||||
RegisterValue value = registerContextManager.getDefaultValue(language, register, address);
|
||||
return stack(value, register, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -81,7 +101,8 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
|
|||
|
||||
@Override
|
||||
public RegisterValue getNonDefaultValue(Register register, Address address) {
|
||||
return registerContextManager.getValue(language, register, program.snap, address);
|
||||
RegisterValue value = new RegisterValue(register);
|
||||
return stack(value, register, address);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -105,22 +126,24 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
|
|||
|
||||
@Override
|
||||
public AddressRangeIterator getRegisterValueAddressRanges(Register register) {
|
||||
return registerContextManager.getRegisterValueAddressRanges(language, register,
|
||||
program.snap).getAddressRanges();
|
||||
return program.viewport.unionedAddresses(
|
||||
s -> registerContextManager.getRegisterValueAddressRanges(language, register,
|
||||
s)).getAddressRanges();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRangeIterator getRegisterValueAddressRanges(Register register, Address start,
|
||||
Address end) {
|
||||
return new NestedAddressRangeIterator<>(
|
||||
language.getAddressFactory().getAddressSet(start, end).iterator(), range -> {
|
||||
return registerContextManager.getRegisterValueAddressRanges(language, register,
|
||||
program.snap, range).iterator();
|
||||
});
|
||||
language.getAddressFactory().getAddressSet(start, end).iterator(),
|
||||
range -> program.viewport.unionedAddresses(
|
||||
s -> registerContextManager.getRegisterValueAddressRanges(language, register,
|
||||
s, range)).iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressRange getRegisterValueRangeContaining(Register register, Address address) {
|
||||
// TODO: I don't know the value of making this work through the viewport.
|
||||
Entry<TraceAddressSnapRange, RegisterValue> entry =
|
||||
registerContextManager.getEntry(language, register, program.snap, address);
|
||||
if (entry != null) {
|
||||
|
@ -164,6 +187,7 @@ public class DBTraceProgramViewProgramContext extends AbstractProgramContext {
|
|||
@Override
|
||||
public boolean hasValueOverRange(Register register, BigInteger value,
|
||||
AddressSetView addressSet) {
|
||||
// TODO: Not sure the value of making this use the viewport
|
||||
RegisterValue regVal = new RegisterValue(register, value);
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
AddressSet remains = new AddressSet(addressSet);
|
||||
|
|
|
@ -19,6 +19,7 @@ import com.google.common.collect.Range;
|
|||
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.database.listing.DBTraceCodeRegisterSpace;
|
||||
import ghidra.trace.database.listing.UndefinedDBTraceData;
|
||||
import ghidra.trace.database.thread.DBTraceThread;
|
||||
import ghidra.trace.model.program.TraceProgramViewRegisterListing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
@ -45,6 +46,11 @@ public class DBTraceProgramViewRegisterListing extends AbstractDBTraceProgramVie
|
|||
return thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UndefinedDBTraceData doCreateUndefinedUnit(Address address) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUndefined(Address start, Address end) {
|
||||
return codeOperations.undefinedData()
|
||||
|
|
|
@ -225,7 +225,7 @@ public class DBTraceProgramViewRegisterMemoryBlock implements MemoryBlock {
|
|||
throw new MemoryAccessException();
|
||||
}
|
||||
ByteBuffer buf = ByteBuffer.allocate(1);
|
||||
if (space.getBytes(program.snap, addr, buf) != 1) {
|
||||
if (space.getViewBytes(program.snap, addr, buf) != 1) {
|
||||
throw new MemoryAccessException();
|
||||
}
|
||||
return buf.get(0);
|
||||
|
@ -242,7 +242,7 @@ public class DBTraceProgramViewRegisterMemoryBlock implements MemoryBlock {
|
|||
throw new MemoryAccessException();
|
||||
}
|
||||
len = (int) Math.min(len, range.getMaxAddress().subtract(addr) + 1);
|
||||
return space.getBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
|
||||
return space.getViewBytes(program.snap, addr, ByteBuffer.wrap(b, off, len));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -41,6 +41,7 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.data.TraceBasedDataTypeManager;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.thread.TraceThread;
|
||||
import ghidra.trace.util.TraceTimeViewport;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -616,6 +617,11 @@ public class DBTraceProgramViewRegisters implements TraceProgramView {
|
|||
return view.getSnap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceTimeViewport getViewport() {
|
||||
return view.getViewport();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getMaxSnap() {
|
||||
return view.getMaxSnap();
|
||||
|
|
|
@ -17,11 +17,14 @@ package ghidra.trace.database.program;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.trace.database.memory.DBTraceMemoryRegion;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegion;
|
||||
import ghidra.util.ComparatorMath;
|
||||
import ghidra.util.LockHold;
|
||||
import ghidra.util.exception.*;
|
||||
|
||||
|
@ -102,11 +105,10 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
|
|||
// NOTE: Would flush on snap change
|
||||
try (LockHold hold = LockHold.lock(program.trace.getReadWriteLock().readLock())) {
|
||||
List<DBTraceProgramViewFragment> frags = new ArrayList<>();
|
||||
for (DBTraceMemoryRegion region : program.trace.getMemoryManager()
|
||||
.getRegionsAtSnap(program.snap)) {
|
||||
program.memory.forVisibleRegions(region -> {
|
||||
frags.add(listing.fragmentsByRegion.computeIfAbsent(region,
|
||||
r -> new DBTraceProgramViewFragment(listing, r)));
|
||||
}
|
||||
});
|
||||
return frags.toArray(new DBTraceProgramViewFragment[frags.size()]);
|
||||
}
|
||||
}
|
||||
|
@ -114,17 +116,11 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
|
|||
@Override
|
||||
public int getIndex(String name) {
|
||||
// TODO: This isn't pretty at all. Really should database these.
|
||||
List<String> names = new ArrayList<>();
|
||||
try (LockHold hold = LockHold.lock(program.trace.getReadWriteLock().readLock())) {
|
||||
int i = 0;
|
||||
for (DBTraceMemoryRegion region : program.trace.getMemoryManager()
|
||||
.getRegionsAtSnap(program.snap)) {
|
||||
if (name.equals(region.getName())) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
program.memory.forVisibleRegions(region -> names.add(region.getName()));
|
||||
}
|
||||
return -1;
|
||||
return names.indexOf(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -173,14 +169,44 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
|
|||
return true;
|
||||
}
|
||||
|
||||
protected <T> T reduceRegions(java.util.function.Function<TraceMemoryRegion, T> func,
|
||||
BiFunction<T, T, T> reducer) {
|
||||
var action = new Consumer<TraceMemoryRegion>() {
|
||||
public T cur;
|
||||
|
||||
@Override
|
||||
public void accept(TraceMemoryRegion region) {
|
||||
if (cur == null) {
|
||||
cur = func.apply(region);
|
||||
}
|
||||
else {
|
||||
cur = reducer.apply(cur, func.apply(region));
|
||||
}
|
||||
}
|
||||
};
|
||||
return action.cur;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getMinAddress() {
|
||||
return program.trace.getMemoryManager().getRegionsAddressSet(program.snap).getMinAddress();
|
||||
if (!program.viewport.isForked()) {
|
||||
return program.trace.getMemoryManager()
|
||||
.getRegionsAddressSet(program.snap)
|
||||
.getMinAddress();
|
||||
}
|
||||
// TODO: There has got to be a better way
|
||||
return reduceRegions(TraceMemoryRegion::getMinAddress, ComparatorMath::cmin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getMaxAddress() {
|
||||
return program.trace.getMemoryManager().getRegionsAddressSet(program.snap).getMaxAddress();
|
||||
if (!program.viewport.isForked()) {
|
||||
return program.trace.getMemoryManager()
|
||||
.getRegionsAddressSet(program.snap)
|
||||
.getMaxAddress();
|
||||
}
|
||||
// TODO: There has got to be a better way
|
||||
return reduceRegions(TraceMemoryRegion::getMaxAddress, ComparatorMath::cmax);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -195,7 +221,8 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
|
|||
|
||||
@Override
|
||||
public AddressSetView getAddressSet() {
|
||||
return program.trace.getMemoryManager().getRegionsAddressSet(program.snap);
|
||||
return program.viewport
|
||||
.unionedAddresses(s -> program.trace.getMemoryManager().getRegionsAddressSet(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -153,12 +153,16 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
|
|||
}
|
||||
}
|
||||
|
||||
protected <T extends TraceSymbol> T requireInLifespan(T sym) {
|
||||
protected <T extends TraceSymbol> T requireVisible(T sym) {
|
||||
if (!(sym instanceof TraceSymbolWithLifespan)) {
|
||||
return sym;
|
||||
}
|
||||
if (sym instanceof TraceFunctionSymbol) {
|
||||
TraceFunctionSymbol function = (TraceFunctionSymbol) sym;
|
||||
return program.isFunctionVisible(function, function.getLifespan()) ? sym : null;
|
||||
}
|
||||
TraceSymbolWithLifespan wl = (TraceSymbolWithLifespan) sym;
|
||||
if (wl.getLifespan().contains(program.snap)) {
|
||||
if (program.viewport.containsAnyUpper(wl.getLifespan())) {
|
||||
return sym;
|
||||
}
|
||||
return null;
|
||||
|
@ -166,7 +170,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
|
|||
|
||||
@Override
|
||||
public Symbol getSymbol(long symbolID) {
|
||||
return requireInLifespan(symbolManager.getSymbolByID(symbolID));
|
||||
return requireVisible(symbolManager.getSymbolByID(symbolID));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -178,7 +182,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
|
|||
if (!addr.equals(sym.getAddress())) {
|
||||
continue;
|
||||
}
|
||||
if (requireInLifespan(sym) == null) {
|
||||
if (requireVisible(sym) == null) {
|
||||
continue;
|
||||
}
|
||||
return sym;
|
||||
|
@ -198,7 +202,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
|
|||
for (TraceSymbol sym : symbolManager.allSymbols()
|
||||
.getChildrenNamed(name,
|
||||
assertTraceNamespace(namespace))) {
|
||||
if (requireInLifespan(sym) == null) {
|
||||
if (requireVisible(sym) == null) {
|
||||
continue;
|
||||
}
|
||||
return sym;
|
||||
|
@ -218,7 +222,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
|
|||
try (LockHold hold = program.trace.lockRead()) {
|
||||
List<Symbol> result = new ArrayList<>();
|
||||
for (TraceSymbol sym : symbolManager.allSymbols().getChildrenNamed(name, parent)) {
|
||||
if (requireInLifespan(sym) != null) {
|
||||
if (requireVisible(sym) != null) {
|
||||
result.add(sym);
|
||||
}
|
||||
}
|
||||
|
@ -239,7 +243,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
|
|||
for (TraceSymbol sym : symbolManager.labelsAndFunctions()
|
||||
.getChildrenNamed(name,
|
||||
parent)) {
|
||||
if (requireInLifespan(sym) != null) {
|
||||
if (requireVisible(sym) != null) {
|
||||
result.add(sym);
|
||||
}
|
||||
}
|
||||
|
@ -352,7 +356,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
|
|||
if (addr.isMemoryAddress()) {
|
||||
return symbolManager.labelsAndFunctions().hasAt(program.snap, null, addr, true);
|
||||
}
|
||||
if (addr.isRegisterAddress() || addr.isStackAddress()) {
|
||||
if (addr.getAddressSpace().isRegisterSpace() || addr.isStackAddress()) {
|
||||
return symbolManager.allVariables().hasAt(addr, true);
|
||||
}
|
||||
return false;
|
||||
|
@ -527,7 +531,7 @@ public class DBTraceProgramViewSymbolTable implements SymbolTable {
|
|||
return sym.getParentNamespace();
|
||||
}
|
||||
}
|
||||
if (addr.isRegisterAddress() || addr.isStackAddress()) {
|
||||
if (addr.getAddressSpace().isRegisterSpace() || addr.isStackAddress()) {
|
||||
for (TraceSymbol sym : symbolManager.allVariables().getAt(addr, true)) {
|
||||
return sym.getParentNamespace();
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue