mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-12-12 14:13:01 +00:00
GP-2448: Sync DebuggerModelProvider to tool
This commit is contained in:
parent
d970c024a6
commit
2c0678ed83
@ -21,8 +21,11 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.app.services.TraceRecorder;
|
import ghidra.app.services.TraceRecorder;
|
||||||
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.ProjectFileManager;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
@ -31,7 +34,7 @@ import ghidra.trace.database.DBTraceContentHandler;
|
|||||||
import ghidra.trace.database.DBTraceUtils;
|
import ghidra.trace.database.DBTraceUtils;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.stack.TraceObjectStackFrame;
|
import ghidra.trace.model.stack.*;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||||
import ghidra.trace.model.thread.TraceObjectThread;
|
import ghidra.trace.model.thread.TraceObjectThread;
|
||||||
@ -46,7 +49,7 @@ import ghidra.util.NotOwnerException;
|
|||||||
public class DebuggerCoordinates {
|
public class DebuggerCoordinates {
|
||||||
|
|
||||||
public static final DebuggerCoordinates NOWHERE =
|
public static final DebuggerCoordinates NOWHERE =
|
||||||
new DebuggerCoordinates(null, null, null, null, TraceSchedule.ZERO, 0, null);
|
new DebuggerCoordinates(null, null, null, null, null, null, null);
|
||||||
|
|
||||||
private static final String KEY_TRACE_PROJ_LOC = "TraceProjLoc";
|
private static final String KEY_TRACE_PROJ_LOC = "TraceProjLoc";
|
||||||
private static final String KEY_TRACE_PROJ_NAME = "TraceProjName";
|
private static final String KEY_TRACE_PROJ_NAME = "TraceProjName";
|
||||||
@ -57,94 +60,6 @@ public class DebuggerCoordinates {
|
|||||||
private static final String KEY_FRAME = "Frame";
|
private static final String KEY_FRAME = "Frame";
|
||||||
private static final String KEY_OBJ_PATH = "ObjectPath";
|
private static final String KEY_OBJ_PATH = "ObjectPath";
|
||||||
|
|
||||||
public static DebuggerCoordinates all(Trace trace, TraceRecorder recorder, TraceThread thread,
|
|
||||||
TraceProgramView view, TraceSchedule time, Integer frame, TraceObject object) {
|
|
||||||
if (trace == NOWHERE.trace && recorder == NOWHERE.recorder && thread == NOWHERE.thread &&
|
|
||||||
view == NOWHERE.view && time == NOWHERE.time && frame == NOWHERE.frame &&
|
|
||||||
object == NOWHERE.object) {
|
|
||||||
return NOWHERE;
|
|
||||||
}
|
|
||||||
return new DebuggerCoordinates(trace, recorder, thread, view, time, frame, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DebuggerCoordinates trace(Trace trace) {
|
|
||||||
if (trace == null) {
|
|
||||||
return NOWHERE;
|
|
||||||
}
|
|
||||||
return all(trace, null, null, null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DebuggerCoordinates recorder(TraceRecorder recorder) {
|
|
||||||
return all(recorder == null ? null : recorder.getTrace(), recorder,
|
|
||||||
null, null, recorder == null ? null : TraceSchedule.snap(recorder.getSnap()), null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DebuggerCoordinates thread(TraceThread thread) {
|
|
||||||
return all(thread == null ? null : thread.getTrace(), null, thread, null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DebuggerCoordinates rawView(TraceProgramView view) {
|
|
||||||
return all(view.getTrace(), null, null, view, TraceSchedule.snap(view.getSnap()), null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DebuggerCoordinates view(TraceProgramView view) {
|
|
||||||
if (view == null) {
|
|
||||||
return NOWHERE;
|
|
||||||
}
|
|
||||||
long snap = view.getSnap();
|
|
||||||
if (!DBTraceUtils.isScratch(snap)) {
|
|
||||||
return rawView(view);
|
|
||||||
}
|
|
||||||
Trace trace = view.getTrace();
|
|
||||||
TraceSnapshot snapshot = trace.getTimeManager().getSnapshot(snap, false);
|
|
||||||
if (snapshot == null) {
|
|
||||||
return rawView(view);
|
|
||||||
}
|
|
||||||
TraceSchedule schedule = snapshot.getSchedule();
|
|
||||||
if (schedule == null) {
|
|
||||||
return rawView(view);
|
|
||||||
}
|
|
||||||
return trace(trace).withTime(schedule);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DebuggerCoordinates snap(long snap) {
|
|
||||||
return all(null, null, null, null, TraceSchedule.snap(snap), null, 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, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DebuggerCoordinates frame(int frame) {
|
|
||||||
return all(null, null, null, null, null, frame, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DebuggerCoordinates object(TraceObject object) {
|
|
||||||
if (object == null) {
|
|
||||||
return NOWHERE;
|
|
||||||
}
|
|
||||||
TraceObjectThread thread = object.queryCanonicalAncestorsInterface(TraceObjectThread.class)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
TraceObjectStackFrame frame =
|
|
||||||
object.queryCanonicalAncestorsInterface(TraceObjectStackFrame.class)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
return all(object.getTrace(), null, thread, null, null,
|
|
||||||
frame == null ? null : frame.getLevel(), object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DebuggerCoordinates threadSnap(TraceThread thread, long snap) {
|
|
||||||
return all(thread == null ? null : thread.getTrace(), null, thread, null,
|
|
||||||
TraceSchedule.snap(snap), null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean equalsIgnoreRecorderAndView(DebuggerCoordinates a,
|
public static boolean equalsIgnoreRecorderAndView(DebuggerCoordinates a,
|
||||||
DebuggerCoordinates b) {
|
DebuggerCoordinates b) {
|
||||||
if (!Objects.equals(a.trace, b.trace)) {
|
if (!Objects.equals(a.trace, b.trace)) {
|
||||||
@ -153,10 +68,14 @@ public class DebuggerCoordinates {
|
|||||||
if (!Objects.equals(a.thread, b.thread)) {
|
if (!Objects.equals(a.thread, b.thread)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!Objects.equals(a.time, b.time)) {
|
// Consider defaults
|
||||||
|
if (!Objects.equals(a.getTime(), b.getTime())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!Objects.equals(a.frame, b.frame)) {
|
if (!Objects.equals(a.getFrame(), b.getFrame())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(a.getObject(), b.getObject())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -175,7 +94,7 @@ public class DebuggerCoordinates {
|
|||||||
private Long viewSnap;
|
private Long viewSnap;
|
||||||
private DefaultTraceTimeViewport viewport;
|
private DefaultTraceTimeViewport viewport;
|
||||||
|
|
||||||
protected DebuggerCoordinates(Trace trace, TraceRecorder recorder, TraceThread thread,
|
DebuggerCoordinates(Trace trace, TraceRecorder recorder, TraceThread thread,
|
||||||
TraceProgramView view, TraceSchedule time, Integer frame, TraceObject object) {
|
TraceProgramView view, TraceSchedule time, Integer frame, TraceObject object) {
|
||||||
this.trace = trace;
|
this.trace = trace;
|
||||||
this.recorder = recorder;
|
this.recorder = recorder;
|
||||||
@ -191,7 +110,7 @@ public class DebuggerCoordinates {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format(
|
return String.format(
|
||||||
"Coords(trace=%s,recorder=%s,thread=%s,view=%s,time=%s,frame=%d,object=%d)",
|
"Coords(trace=%s,recorder=%s,thread=%s,view=%s,time=%s,frame=%d,object=%s)",
|
||||||
trace, recorder, thread, view, time, frame, object);
|
trace, recorder, thread, view, time, frame, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,6 +132,7 @@ public class DebuggerCoordinates {
|
|||||||
if (!Objects.equals(this.view, that.view)) {
|
if (!Objects.equals(this.view, that.view)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Do not consider defaults
|
||||||
if (!Objects.equals(this.time, that.time)) {
|
if (!Objects.equals(this.time, that.time)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -222,7 +142,7 @@ public class DebuggerCoordinates {
|
|||||||
if (!Objects.equals(this.object, that.object)) {
|
if (!Objects.equals(this.object, that.object)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,6 +151,325 @@ public class DebuggerCoordinates {
|
|||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TraceThread resolveThread(Trace trace, TraceSchedule time) {
|
||||||
|
long snap = time.getSnap();
|
||||||
|
return trace.getThreadManager()
|
||||||
|
.getLiveThreads(snap)
|
||||||
|
.stream()
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceThread resolveThread(Trace trace) {
|
||||||
|
return resolveThread(trace, TraceSchedule.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceObject resolveObject(Trace trace) {
|
||||||
|
return trace.getObjectManager().getRootObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceProgramView resolveView(Trace trace, TraceSchedule time) {
|
||||||
|
// TODO: Allow multiple times viewed of the same trace? (Aside from snap compare)
|
||||||
|
// Trace manager will adjust the view's snap to match coordinates
|
||||||
|
return trace.getProgramView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceProgramView resolveView(Trace trace) {
|
||||||
|
return resolveView(trace, TraceSchedule.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates trace(Trace newTrace) {
|
||||||
|
if (newTrace == null) {
|
||||||
|
return NOWHERE;
|
||||||
|
}
|
||||||
|
if (trace == newTrace) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (trace == null) {
|
||||||
|
TraceThread newThread = resolveThread(newTrace);
|
||||||
|
TraceProgramView newView = resolveView(newTrace);
|
||||||
|
TraceSchedule newTime = null; // Allow later resolution
|
||||||
|
Integer newFrame = resolveFrame(newThread, newTime);
|
||||||
|
TraceObject newObject = resolveObject(newTrace);
|
||||||
|
return new DebuggerCoordinates(newTrace, null, newThread, newView, newTime, newFrame,
|
||||||
|
newObject);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Cannot change trace");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceThread resolveThread(TraceRecorder recorder, TraceSchedule time) {
|
||||||
|
if (recorder.getSnap() != time.getSnap() || !recorder.isSupportsFocus()) {
|
||||||
|
return resolveThread(recorder.getTrace(), time);
|
||||||
|
}
|
||||||
|
return resolveThread(recorder, recorder.getFocus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceThread resolveThread(Trace trace, TraceRecorder recorder,
|
||||||
|
TraceSchedule time) {
|
||||||
|
if (recorder == null) {
|
||||||
|
return resolveThread(trace, time);
|
||||||
|
}
|
||||||
|
return resolveThread(recorder, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer resolveFrame(TraceThread thread, TraceSchedule time) {
|
||||||
|
// Use null to allow later resolution. Getter will default to 0
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer resolveFrame(TraceRecorder recorder, TraceThread thread,
|
||||||
|
TraceSchedule time) {
|
||||||
|
if (recorder == null || recorder.getSnap() != time.getSnap() ||
|
||||||
|
!recorder.isSupportsFocus()) {
|
||||||
|
return resolveFrame(thread, time);
|
||||||
|
}
|
||||||
|
return resolveFrame(recorder, recorder.getFocus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceObject resolveObject(Trace trace, TargetObject object) {
|
||||||
|
if (object == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return trace.getObjectManager()
|
||||||
|
.getObjectByCanonicalPath(TraceObjectKeyPath.of(object.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceObject resolveObject(TraceRecorder recorder, TraceSchedule time) {
|
||||||
|
if (recorder.getSnap() != time.getSnap() || !recorder.isSupportsFocus()) {
|
||||||
|
return resolveObject(recorder.getTrace());
|
||||||
|
}
|
||||||
|
return resolveObject(recorder.getTrace(), recorder.getFocus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates recorder(TraceRecorder newRecorder) {
|
||||||
|
if (recorder == newRecorder) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (newRecorder == null) {
|
||||||
|
return new DebuggerCoordinates(trace, newRecorder, thread, view, time, frame, object);
|
||||||
|
}
|
||||||
|
if (newRecorder != null && trace != null && newRecorder.getTrace() != trace) {
|
||||||
|
throw new IllegalArgumentException("Cannot change trace");
|
||||||
|
}
|
||||||
|
Trace newTrace = trace != null ? trace : newRecorder.getTrace();
|
||||||
|
TraceSchedule newTime = time != null ? time : TraceSchedule.snap(newRecorder.getSnap());
|
||||||
|
TraceThread newThread = thread != null ? thread : resolveThread(newRecorder, newTime);
|
||||||
|
TraceProgramView newView = view != null ? view : resolveView(newTrace, newTime);
|
||||||
|
Integer newFrame = frame != null ? frame : resolveFrame(newRecorder, newThread, newTime);
|
||||||
|
TraceObject newObject = object != null ? object : resolveObject(newRecorder, newTime);
|
||||||
|
return new DebuggerCoordinates(newTrace, newRecorder, newThread, newView, newTime, newFrame,
|
||||||
|
newObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates reFindThread() {
|
||||||
|
if (trace == null || thread == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return thread(trace.getThreadManager().getThread(thread.getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceObject resolveObject(TraceThread thread, Integer frameLevel,
|
||||||
|
TraceSchedule time) {
|
||||||
|
if (thread instanceof TraceObjectThread tot) {
|
||||||
|
TraceObject objThread = tot.getObject();
|
||||||
|
if (frameLevel == null) {
|
||||||
|
return objThread;
|
||||||
|
}
|
||||||
|
TraceStack stack =
|
||||||
|
thread.getTrace().getStackManager().getStack(thread, time.getSnap(), false);
|
||||||
|
if (stack == null) {
|
||||||
|
return objThread;
|
||||||
|
}
|
||||||
|
TraceStackFrame frame = stack.getFrame(frameLevel, false);
|
||||||
|
if (frame == null) {
|
||||||
|
return objThread;
|
||||||
|
}
|
||||||
|
return ((TraceObjectStackFrame) frame).getObject();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the object is a <em>canonical</em> ancestor
|
||||||
|
*
|
||||||
|
* @param ancestor the proposed ancestor
|
||||||
|
* @param successor the proposed successor
|
||||||
|
* @param time the time to consider (only the snap matters)
|
||||||
|
* @return true if ancestor is in fact an ancestor of successor at the given time
|
||||||
|
*/
|
||||||
|
private static boolean isAncestor(TraceObject ancestor, TraceObject successor,
|
||||||
|
TraceSchedule time) {
|
||||||
|
return successor.getCanonicalParents(Range.singleton(time.getSnap()))
|
||||||
|
.anyMatch(p -> p == ancestor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates thread(TraceThread newThread) {
|
||||||
|
if (thread == newThread) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (newThread != null && trace != null && trace != newThread.getTrace()) {
|
||||||
|
throw new IllegalArgumentException("Cannot change trace");
|
||||||
|
}
|
||||||
|
if (newThread == null) {
|
||||||
|
newThread = resolveThread(recorder, getTime());
|
||||||
|
}
|
||||||
|
Trace newTrace = trace != null ? trace : newThread.getTrace();
|
||||||
|
TraceSchedule newTime = time != null ? time : resolveTime(view);
|
||||||
|
TraceProgramView newView = view != null ? view : resolveView(newTrace, newTime);
|
||||||
|
// Yes, override frame with 0 on thread changes, unless target says otherwise
|
||||||
|
Integer newFrame = resolveFrame(recorder, newThread, newTime);
|
||||||
|
// Yes, forced frame change may also force object change
|
||||||
|
TraceObject ancestor = resolveObject(newThread, newFrame, newTime);
|
||||||
|
TraceObject newObject =
|
||||||
|
object != null && isAncestor(ancestor, object, newTime) ? object : ancestor;
|
||||||
|
return new DebuggerCoordinates(newTrace, recorder, newThread, newView, newTime, newFrame,
|
||||||
|
newObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get these same coordinates with time replaced by the given snap-only schedule
|
||||||
|
*
|
||||||
|
* @param snap the new snap
|
||||||
|
* @return the new coordinates
|
||||||
|
*/
|
||||||
|
public DebuggerCoordinates snap(long snap) {
|
||||||
|
return time(TraceSchedule.snap(snap));
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates time(TraceSchedule newTime) {
|
||||||
|
if (trace == null) {
|
||||||
|
return NOWHERE;
|
||||||
|
}
|
||||||
|
long snap = newTime.getSnap();
|
||||||
|
TraceThread newThread = thread != null && thread.getLifespan().contains(snap) ? thread
|
||||||
|
: resolveThread(trace, recorder, newTime);
|
||||||
|
// This will cause the frame to reset to 0 on every snap change. That's fair....
|
||||||
|
Integer newFrame = resolveFrame(newThread, newTime);
|
||||||
|
TraceObject ancestor = resolveObject(newThread, newFrame, newTime);
|
||||||
|
TraceObject newObject =
|
||||||
|
object != null && isAncestor(ancestor, object, newTime) ? object : ancestor;
|
||||||
|
return new DebuggerCoordinates(trace, recorder, newThread, view, newTime, newFrame,
|
||||||
|
newObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates frame(int newFrame) {
|
||||||
|
if (trace == null) {
|
||||||
|
return NOWHERE;
|
||||||
|
}
|
||||||
|
if (Objects.equals(frame, newFrame)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
TraceObject ancestor = resolveObject(thread, newFrame, getTime());
|
||||||
|
TraceObject newObject =
|
||||||
|
object != null && isAncestor(ancestor, object, getTime()) ? object : ancestor;
|
||||||
|
return new DebuggerCoordinates(trace, recorder, thread, view, time, newFrame, newObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DebuggerCoordinates replaceView(TraceProgramView newView) {
|
||||||
|
return new DebuggerCoordinates(trace, recorder, thread, newView, time, frame, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceSchedule resolveTime(TraceProgramView view) {
|
||||||
|
if (view == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long snap = view.getSnap();
|
||||||
|
if (!DBTraceUtils.isScratch(snap)) {
|
||||||
|
return TraceSchedule.snap(snap);
|
||||||
|
}
|
||||||
|
TraceSnapshot snapshot = view.getTrace().getTimeManager().getSnapshot(snap, false);
|
||||||
|
if (snapshot == null) {
|
||||||
|
return TraceSchedule.snap(snap);
|
||||||
|
}
|
||||||
|
TraceSchedule schedule = snapshot.getSchedule();
|
||||||
|
if (schedule == null) {
|
||||||
|
return TraceSchedule.snap(snap);
|
||||||
|
}
|
||||||
|
return schedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates view(TraceProgramView newView) {
|
||||||
|
if (view == newView) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (trace == null) {
|
||||||
|
if (newView == null) {
|
||||||
|
return NOWHERE;
|
||||||
|
}
|
||||||
|
return NOWHERE.trace(newView.getTrace())
|
||||||
|
.time(resolveTime(newView))
|
||||||
|
.replaceView(newView);
|
||||||
|
}
|
||||||
|
if (newView.getTrace() != trace) {
|
||||||
|
throw new IllegalArgumentException("Cannot change trace");
|
||||||
|
}
|
||||||
|
return time(resolveTime(newView)).replaceView(newView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TraceThread resolveThread(TraceObject object) {
|
||||||
|
return object.queryCanonicalAncestorsInterface(TraceObjectThread.class)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer resolveFrame(TraceObject object) {
|
||||||
|
TraceObjectStackFrame frame =
|
||||||
|
object.queryCanonicalAncestorsInterface(TraceObjectStackFrame.class)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
return frame == null ? null : frame.getLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates object(TraceObject newObject) {
|
||||||
|
Trace newTrace;
|
||||||
|
if (trace == null) {
|
||||||
|
if (newObject == null) {
|
||||||
|
return NOWHERE;
|
||||||
|
}
|
||||||
|
newTrace = newObject.getTrace();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (newObject == null) {
|
||||||
|
return new DebuggerCoordinates(trace, recorder, thread, view, time, frame,
|
||||||
|
newObject);
|
||||||
|
}
|
||||||
|
if (newObject.getTrace() != trace) {
|
||||||
|
throw new IllegalArgumentException("Cannot change trace");
|
||||||
|
}
|
||||||
|
newTrace = trace;
|
||||||
|
}
|
||||||
|
TraceThread newThread = resolveThread(newObject);
|
||||||
|
Integer newFrame = resolveFrame(newObject);
|
||||||
|
|
||||||
|
return new DebuggerCoordinates(newTrace, recorder, newThread, view, time, newFrame,
|
||||||
|
newObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static TraceThread resolveThread(TraceRecorder recorder, TargetObject targetObject) {
|
||||||
|
return recorder.getTraceThreadForSuccessor(targetObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Integer resolveFrame(TraceRecorder recorder, TargetObject targetObject) {
|
||||||
|
TraceStackFrame frame = recorder.getTraceStackFrameForSuccessor(targetObject);
|
||||||
|
return frame == null ? null : frame.getLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DebuggerCoordinates object(TraceObject traceObject, TargetObject targetObject) {
|
||||||
|
if (traceObject != null) {
|
||||||
|
return object(traceObject);
|
||||||
|
}
|
||||||
|
if (recorder == null) {
|
||||||
|
throw new IllegalArgumentException("No recorder");
|
||||||
|
}
|
||||||
|
TraceThread newThread = resolveThread(recorder, targetObject);
|
||||||
|
Integer newFrame = resolveFrame(recorder, targetObject);
|
||||||
|
return new DebuggerCoordinates(trace, recorder, newThread, view, time, newFrame, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DebuggerCoordinates object(TargetObject newObject) {
|
||||||
|
return object(resolveObject(trace, newObject), newObject);
|
||||||
|
}
|
||||||
|
|
||||||
public Trace getTrace() {
|
public Trace getTrace() {
|
||||||
return trace;
|
return trace;
|
||||||
}
|
}
|
||||||
@ -239,70 +478,24 @@ public class DebuggerCoordinates {
|
|||||||
return recorder;
|
return recorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebuggerCoordinates withRecorder(TraceRecorder newRecorder) {
|
|
||||||
return all(trace, newRecorder, thread, view, time, frame, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TraceThread getThread() {
|
public TraceThread getThread() {
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DebuggerCoordinates withReFoundThread() {
|
|
||||||
if (trace == null || thread == null) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
TraceThread newThread = trace.getThreadManager().getThread(thread.getKey());
|
|
||||||
if (thread == newThread) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
return withThread(newThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebuggerCoordinates withThread(TraceThread newThread) {
|
|
||||||
return all(trace, recorder, newThread, view, time, frame, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TraceProgramView getView() {
|
public TraceProgramView getView() {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getSnap() {
|
public long getSnap() {
|
||||||
return time.getSnap();
|
return getTime().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 == null ? time : TraceSchedule.snap(newSnap), frame, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebuggerCoordinates withTime(TraceSchedule newTime) {
|
|
||||||
return all(trace, recorder, thread, view, newTime, frame, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebuggerCoordinates withFrame(int newFrame) {
|
|
||||||
return all(trace, recorder, thread, view, time, newFrame, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebuggerCoordinates withView(TraceProgramView newView) {
|
|
||||||
return all(trace, recorder, thread, newView, time, frame, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DebuggerCoordinates withObject(TraceObject newObject) {
|
|
||||||
return all(trace, recorder, thread, view, time, frame, newObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceSchedule getTime() {
|
public TraceSchedule getTime() {
|
||||||
return time;
|
return time == null ? TraceSchedule.ZERO : time;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getFrame() {
|
public int getFrame() {
|
||||||
return frame;
|
return frame == null ? 0 : frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceObject getObject() {
|
public TraceObject getObject() {
|
||||||
@ -313,16 +506,17 @@ public class DebuggerCoordinates {
|
|||||||
if (viewSnap != null) {
|
if (viewSnap != null) {
|
||||||
return viewSnap;
|
return viewSnap;
|
||||||
}
|
}
|
||||||
if (time.isSnapOnly()) {
|
TraceSchedule defaultedTime = getTime();
|
||||||
return viewSnap = time.getSnap();
|
if (defaultedTime.isSnapOnly()) {
|
||||||
|
return viewSnap = defaultedTime.getSnap();
|
||||||
}
|
}
|
||||||
Collection<? extends TraceSnapshot> snapshots =
|
Collection<? extends TraceSnapshot> snapshots =
|
||||||
trace.getTimeManager().getSnapshotsWithSchedule(time);
|
trace.getTimeManager().getSnapshotsWithSchedule(defaultedTime);
|
||||||
if (snapshots.isEmpty()) {
|
if (snapshots.isEmpty()) {
|
||||||
Msg.warn(this,
|
Msg.warn(this,
|
||||||
"Seems the emulation service did not create the requested snapshot, yet");
|
"Seems the emulation service did not create the requested snapshot, yet");
|
||||||
// NB. Don't cache viewSnap. Maybe next time, we'll get it.
|
// NB. Don't cache viewSnap. Maybe next time, we'll get it.
|
||||||
return time.getSnap();
|
return defaultedTime.getSnap();
|
||||||
}
|
}
|
||||||
return viewSnap = snapshots.iterator().next().getKey();
|
return viewSnap = snapshots.iterator().next().getKey();
|
||||||
}
|
}
|
||||||
@ -414,7 +608,7 @@ public class DebuggerCoordinates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static DebuggerCoordinates readDataState(PluginTool tool, SaveState saveState,
|
public static DebuggerCoordinates readDataState(PluginTool tool, SaveState saveState,
|
||||||
String key, boolean resolve) {
|
String key) {
|
||||||
if (!saveState.hasValue(key)) {
|
if (!saveState.hasValue(key)) {
|
||||||
return NOWHERE;
|
return NOWHERE;
|
||||||
}
|
}
|
||||||
@ -443,7 +637,7 @@ public class DebuggerCoordinates {
|
|||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.error(DebuggerCoordinates.class,
|
Msg.error(DebuggerCoordinates.class,
|
||||||
"Could not restore invalid time specification: " + timeSpec);
|
"Could not restore invalid time specification: " + timeSpec);
|
||||||
time = TraceSchedule.ZERO;
|
time = null;
|
||||||
}
|
}
|
||||||
Integer frame = null;
|
Integer frame = null;
|
||||||
if (coordState.hasValue(KEY_FRAME)) {
|
if (coordState.hasValue(KEY_FRAME)) {
|
||||||
@ -462,12 +656,12 @@ public class DebuggerCoordinates {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DebuggerCoordinates coords =
|
DebuggerCoordinates coords = DebuggerCoordinates.NOWHERE.trace(trace)
|
||||||
DebuggerCoordinates.all(trace, null, thread, null, time, frame, object);
|
.thread(thread)
|
||||||
if (!resolve) {
|
.time(time)
|
||||||
return coords;
|
.frame(frame)
|
||||||
}
|
.object(object);
|
||||||
return traceManager.resolveCoordinates(coords);
|
return coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAlive() {
|
public boolean isAlive() {
|
||||||
@ -475,11 +669,12 @@ public class DebuggerCoordinates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPresent() {
|
public boolean isPresent() {
|
||||||
return recorder.getSnap() == time.getSnap() && time.isSnapOnly();
|
TraceSchedule defaultedTime = getTime();
|
||||||
|
return recorder.getSnap() == defaultedTime.getSnap() && defaultedTime.isSnapOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReadsPresent() {
|
public boolean isReadsPresent() {
|
||||||
return recorder.getSnap() == time.getSnap();
|
return recorder.getSnap() == getTime().getSnap();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAliveAndPresent() {
|
public boolean isAliveAndPresent() {
|
||||||
|
@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.disassemble;
|
|||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import ghidra.app.context.ListingActionContext;
|
import ghidra.app.context.ListingActionContext;
|
||||||
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin.Reqs;
|
import ghidra.app.plugin.core.debug.disassemble.DebuggerDisassemblerPlugin.Reqs;
|
||||||
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
|
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
|
||||||
import ghidra.app.plugin.core.debug.mapping.DisassemblyResult;
|
import ghidra.app.plugin.core.debug.mapping.DisassemblyResult;
|
||||||
@ -28,7 +29,6 @@ import ghidra.program.util.ProgramSelection;
|
|||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.TraceProgramView;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.thread.TraceObjectThread;
|
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
@ -49,13 +49,6 @@ public class CurrentPlatformTraceDisassembleAction extends DockingAction {
|
|||||||
setHelpLocation(new HelpLocation(plugin.getName(), "disassemble"));
|
setHelpLocation(new HelpLocation(plugin.getName(), "disassemble"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TraceObject getObject(TraceThread thread) {
|
|
||||||
if (!(thread instanceof TraceObjectThread)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ((TraceObjectThread) thread).getObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Reqs getReqs(ActionContext context) {
|
protected Reqs getReqs(ActionContext context) {
|
||||||
if (plugin.platformService == null) {
|
if (plugin.platformService == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -70,9 +63,10 @@ public class CurrentPlatformTraceDisassembleAction extends DockingAction {
|
|||||||
}
|
}
|
||||||
TraceProgramView view = (TraceProgramView) program;
|
TraceProgramView view = (TraceProgramView) program;
|
||||||
Trace trace = view.getTrace();
|
Trace trace = view.getTrace();
|
||||||
TraceThread thread = plugin.traceManager == null ? null
|
DebuggerCoordinates current = plugin.traceManager == null ? DebuggerCoordinates.NOWHERE
|
||||||
: plugin.traceManager.getCurrentThreadFor(trace);
|
: plugin.traceManager.getCurrentFor(trace);
|
||||||
TraceObject object = getObject(thread);
|
TraceThread thread = current.getThread();
|
||||||
|
TraceObject object = current.getObject();
|
||||||
DebuggerPlatformMapper mapper =
|
DebuggerPlatformMapper mapper =
|
||||||
plugin.platformService.getMapper(trace, object, view.getSnap());
|
plugin.platformService.getMapper(trace, object, view.getSnap());
|
||||||
return new Reqs(mapper, thread, object, view);
|
return new Reqs(mapper, thread, object, view);
|
||||||
|
@ -497,7 +497,7 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
|
|||||||
if (recorder == null) {
|
if (recorder == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!DebuggerCoordinates.view(source).withRecorder(recorder).isAliveAndReadsPresent()) {
|
if (!DebuggerCoordinates.NOWHERE.view(source).recorder(recorder).isAliveAndReadsPresent()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return recorder;
|
return recorder;
|
||||||
|
@ -229,15 +229,14 @@ public class DebuggerTraceViewDiffPlugin extends AbstractDebuggerPlugin {
|
|||||||
actionCompare.setSelected(true);
|
actionCompare.setSelected(true);
|
||||||
|
|
||||||
DebuggerCoordinates current = traceManager.getCurrent();
|
DebuggerCoordinates current = traceManager.getCurrent();
|
||||||
DebuggerCoordinates alternate =
|
DebuggerCoordinates alternate = traceManager.resolveTime(time);
|
||||||
traceManager.resolveCoordinates(DebuggerCoordinates.time(time));
|
|
||||||
PluginToolExecutorService toolExecutorService =
|
PluginToolExecutorService toolExecutorService =
|
||||||
new PluginToolExecutorService(tool, "Computing diff", true, true, false, 500);
|
new PluginToolExecutorService(tool, "Computing diff", true, true, false, 500);
|
||||||
return traceManager.materialize(alternate).thenApplyAsync(snap -> {
|
return traceManager.materialize(alternate).thenApplyAsync(snap -> {
|
||||||
clearMarkers();
|
clearMarkers();
|
||||||
TraceProgramView altView = alternate.getTrace().getFixedProgramView(snap);
|
TraceProgramView altView = alternate.getTrace().getFixedProgramView(snap);
|
||||||
altListingPanel.setProgram(altView);
|
altListingPanel.setProgram(altView);
|
||||||
trackingTrait.goToCoordinates(alternate.withView(altView));
|
trackingTrait.goToCoordinates(alternate.view(altView));
|
||||||
listingService.setListingPanel(altListingPanel);
|
listingService.setListingPanel(altListingPanel);
|
||||||
return altView;
|
return altView;
|
||||||
}, AsyncUtils.SWING_EXECUTOR).thenApplyAsync(altView -> {
|
}, AsyncUtils.SWING_EXECUTOR).thenApplyAsync(altView -> {
|
||||||
|
@ -408,7 +408,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
|||||||
public void readDataState(SaveState saveState) {
|
public void readDataState(SaveState saveState) {
|
||||||
if (!isMainListing()) {
|
if (!isMainListing()) {
|
||||||
DebuggerCoordinates coordinates =
|
DebuggerCoordinates coordinates =
|
||||||
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES, true);
|
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES);
|
||||||
coordinatesActivated(coordinates);
|
coordinatesActivated(coordinates);
|
||||||
}
|
}
|
||||||
super.readDataState(saveState);
|
super.readDataState(saveState);
|
||||||
@ -1041,7 +1041,8 @@ public class DebuggerListingProvider extends CodeViewerProvider {
|
|||||||
return coordinates;
|
return coordinates;
|
||||||
}
|
}
|
||||||
// Because the view's snap is changing with or without us.... So go with.
|
// Because the view's snap is changing with or without us.... So go with.
|
||||||
return current.withTime(coordinates.getTime());
|
// i.e., take the time, but not the thread
|
||||||
|
return current.time(coordinates.getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void goToCoordinates(DebuggerCoordinates coordinates) {
|
public void goToCoordinates(DebuggerCoordinates coordinates) {
|
||||||
|
@ -309,7 +309,8 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
|
|||||||
return coordinates;
|
return coordinates;
|
||||||
}
|
}
|
||||||
// Because the view's snap is changing with or without us.... So go with.
|
// Because the view's snap is changing with or without us.... So go with.
|
||||||
return current.withTime(coordinates.getTime());
|
// i.e., take the time, but not the thread
|
||||||
|
return current.time(coordinates.getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void goToCoordinates(DebuggerCoordinates coordinates) {
|
public void goToCoordinates(DebuggerCoordinates coordinates) {
|
||||||
@ -439,7 +440,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
|
|||||||
protected void readDataState(SaveState saveState) {
|
protected void readDataState(SaveState saveState) {
|
||||||
if (!isMainViewer()) {
|
if (!isMainViewer()) {
|
||||||
DebuggerCoordinates coordinates =
|
DebuggerCoordinates coordinates =
|
||||||
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES, true);
|
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES);
|
||||||
coordinatesActivated(coordinates);
|
coordinatesActivated(coordinates);
|
||||||
}
|
}
|
||||||
super.readDataState(saveState);
|
super.readDataState(saveState);
|
||||||
|
@ -199,7 +199,8 @@ public class DebuggerMemviewTraceListener extends TraceDomainObjectListener {
|
|||||||
|
|
||||||
protected DebuggerCoordinates adjustCoordinates(DebuggerCoordinates coordinates) {
|
protected DebuggerCoordinates adjustCoordinates(DebuggerCoordinates coordinates) {
|
||||||
// Because the view's snap is changing with or without us.... So go with.
|
// Because the view's snap is changing with or without us.... So go with.
|
||||||
return current.withSnap(coordinates.getSnap());
|
// i.e., take the time, but not the thread
|
||||||
|
return current.time(coordinates.getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
public void coordinatesActivated(DebuggerCoordinates coordinates) {
|
||||||
|
@ -28,6 +28,7 @@ import ghidra.trace.model.Trace;
|
|||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
import ghidra.trace.model.Trace.TraceSnapshotChangeType;
|
||||||
import ghidra.trace.model.TraceDomainObjectListener;
|
import ghidra.trace.model.TraceDomainObjectListener;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.TraceObjectValue;
|
import ghidra.trace.model.target.TraceObjectValue;
|
||||||
import ghidra.util.datastruct.Accumulator;
|
import ghidra.util.datastruct.Accumulator;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
@ -306,4 +307,6 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
|||||||
public abstract void setDiffColor(Color diffColor);
|
public abstract void setDiffColor(Color diffColor);
|
||||||
|
|
||||||
public abstract void setDiffColorSel(Color diffColorSel);
|
public abstract void setDiffColorSel(Color diffColorSel);
|
||||||
|
|
||||||
|
public abstract T findTraceObject(TraceObject object);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import com.google.common.collect.Range;
|
|||||||
|
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.util.table.GhidraTable;
|
import ghidra.util.table.GhidraTable;
|
||||||
import ghidra.util.table.GhidraTableFilterPanel;
|
import ghidra.util.table.GhidraTableFilterPanel;
|
||||||
|
|
||||||
@ -36,7 +37,7 @@ public abstract class AbstractQueryTablePanel<T> extends JPanel {
|
|||||||
|
|
||||||
protected final AbstractQueryTableModel<T> tableModel;
|
protected final AbstractQueryTableModel<T> tableModel;
|
||||||
protected final GhidraTable table;
|
protected final GhidraTable table;
|
||||||
private final GhidraTableFilterPanel<T> filterPanel;
|
protected final GhidraTableFilterPanel<T> filterPanel;
|
||||||
|
|
||||||
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||||
protected boolean limitToSnap = false;
|
protected boolean limitToSnap = false;
|
||||||
@ -60,6 +61,10 @@ public abstract class AbstractQueryTablePanel<T> extends JPanel {
|
|||||||
}
|
}
|
||||||
DebuggerCoordinates previous = current;
|
DebuggerCoordinates previous = current;
|
||||||
this.current = coords;
|
this.current = coords;
|
||||||
|
if (previous.getSnap() == current.getSnap() &&
|
||||||
|
previous.getTrace() == current.getTrace()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
tableModel.setDiffTrace(previous.getTrace());
|
tableModel.setDiffTrace(previous.getTrace());
|
||||||
tableModel.setTrace(current.getTrace());
|
tableModel.setTrace(current.getTrace());
|
||||||
tableModel.setDiffSnap(previous.getSnap());
|
tableModel.setDiffSnap(previous.getSnap());
|
||||||
@ -158,6 +163,15 @@ public abstract class AbstractQueryTablePanel<T> extends JPanel {
|
|||||||
filterPanel.setSelectedItem(item);
|
filterPanel.setSelectedItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean trySelect(TraceObject object) {
|
||||||
|
T t = tableModel.findTraceObject(object);
|
||||||
|
if (t == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setSelectedItem(t);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public List<T> getSelectedItems() {
|
public List<T> getSelectedItems() {
|
||||||
return filterPanel.getSelectedItems();
|
return filterPanel.getSelectedItems();
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import javax.swing.*;
|
|||||||
import docking.*;
|
import docking.*;
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
import docking.action.ToggleDockingAction;
|
import docking.action.ToggleDockingAction;
|
||||||
|
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
@ -156,7 +157,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
@Override
|
@Override
|
||||||
public boolean verify(JComponent input) {
|
public boolean verify(JComponent input) {
|
||||||
try {
|
try {
|
||||||
setPath(TraceObjectKeyPath.parse(pathField.getText()), pathField);
|
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
|
||||||
|
setPath(path, pathField, EventOrigin.USER_GENERATED);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
@ -168,7 +170,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
goButton = new JButton("Go");
|
goButton = new JButton("Go");
|
||||||
ActionListener gotoPath = evt -> {
|
ActionListener gotoPath = evt -> {
|
||||||
try {
|
try {
|
||||||
setPath(TraceObjectKeyPath.parse(pathField.getText()), pathField);
|
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
|
||||||
|
setPath(path, pathField, EventOrigin.USER_GENERATED);
|
||||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
|
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
@ -245,15 +248,12 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TraceObjectValue value = sel.get(0).getValue();
|
TraceObjectValue value = sel.get(0).getValue();
|
||||||
TraceObject parent = value.getParent();
|
TraceObjectKeyPath path = value.getCanonicalPath();
|
||||||
TraceObjectKeyPath path;
|
|
||||||
if (parent == null) {
|
// Prevent activation when selecting a link
|
||||||
path = TraceObjectKeyPath.of();
|
EventOrigin origin =
|
||||||
}
|
value.isCanonical() ? evt.getEventOrigin() : EventOrigin.API_GENERATED;
|
||||||
else {
|
setPath(path, objectsTreePanel, origin);
|
||||||
path = parent.getCanonicalPath().key(value.getEntryKey());
|
|
||||||
}
|
|
||||||
setPath(path, objectsTreePanel);
|
|
||||||
});
|
});
|
||||||
elementsTablePanel.addSelectionListener(evt -> {
|
elementsTablePanel.addSelectionListener(evt -> {
|
||||||
if (evt.getValueIsAdjusting()) {
|
if (evt.getValueIsAdjusting()) {
|
||||||
@ -279,8 +279,11 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
if (!value.isObject()) {
|
if (!value.isObject()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attributesTablePanel
|
TraceObject object = value.getChild();
|
||||||
.setQuery(ModelQuery.attributesOf(value.getChild().getCanonicalPath()));
|
attributesTablePanel.setQuery(ModelQuery.attributesOf(object.getCanonicalPath()));
|
||||||
|
if (value.isCanonical()) {
|
||||||
|
activatePath(object.getCanonicalPath());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
attributesTablePanel.addSelectionListener(evt -> {
|
attributesTablePanel.addSelectionListener(evt -> {
|
||||||
if (evt.getValueIsAdjusting()) {
|
if (evt.getValueIsAdjusting()) {
|
||||||
@ -297,6 +300,15 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
myActionContext = null;
|
myActionContext = null;
|
||||||
}
|
}
|
||||||
contextChanged();
|
contextChanged();
|
||||||
|
|
||||||
|
if (sel.size() != 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceObjectValue value = sel.get(0).getPath().getLastEntry();
|
||||||
|
// "canonical" implies "object"
|
||||||
|
if (value != null && value.isCanonical()) {
|
||||||
|
activatePath(value.getCanonicalPath());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
elementsTablePanel.addMouseListener(new MouseAdapter() {
|
elementsTablePanel.addMouseListener(new MouseAdapter() {
|
||||||
@ -453,7 +465,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
if (values.size() != 1) {
|
if (values.size() != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setPath(values.get(0).getChild().getCanonicalPath(), null);
|
setPath(values.get(0).getChild().getCanonicalPath(), null, EventOrigin.USER_GENERATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isStepBackwardEnabled(ActionContext ignored) {
|
private boolean isStepBackwardEnabled(ActionContext ignored) {
|
||||||
@ -499,13 +511,79 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
return mainPanel;
|
return mainPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TraceObjectKeyPath findAsSibling(TraceObject object) {
|
||||||
|
Trace trace = current.getTrace();
|
||||||
|
if (trace == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TraceObjectKeyPath parentPath = path.parent();
|
||||||
|
if (parentPath == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TraceObject parent = trace.getObjectManager().getObjectByCanonicalPath(parentPath);
|
||||||
|
// TODO: Require parent to be a canonical container?
|
||||||
|
if (parent == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (TraceObjectValue value : parent.getValues()) {
|
||||||
|
if (Objects.equals(object, value.getValue())) {
|
||||||
|
return value.getCanonicalPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceObjectKeyPath findAsParent(TraceObject object) {
|
||||||
|
Trace trace = current.getTrace();
|
||||||
|
if (trace == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TraceObjectManager objectManager = trace.getObjectManager();
|
||||||
|
if (objectManager.getRootObject() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TraceObjectValue sel = getTreeSelection();
|
||||||
|
if (sel == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (TraceObjectKeyPath p = sel.getCanonicalPath(); p != null; p = p.parent()) {
|
||||||
|
if (objectManager.getObjectByCanonicalPath(p) == object) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void coordinatesActivated(DebuggerCoordinates coords) {
|
public void coordinatesActivated(DebuggerCoordinates coords) {
|
||||||
this.current = coords;
|
this.current = coords;
|
||||||
objectsTreePanel.goToCoordinates(coords);
|
objectsTreePanel.goToCoordinates(coords);
|
||||||
elementsTablePanel.goToCoordinates(coords);
|
elementsTablePanel.goToCoordinates(coords);
|
||||||
attributesTablePanel.goToCoordinates(coords);
|
attributesTablePanel.goToCoordinates(coords);
|
||||||
|
|
||||||
checkPath();
|
// NOTE: The plugin only calls this on the connected provider
|
||||||
|
// When cloning or restoring state, we MUST still consider the object
|
||||||
|
TraceObject object = coords.getObject();
|
||||||
|
if (object == null) {
|
||||||
|
checkPath();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (attributesTablePanel.trySelect(object)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (elementsTablePanel.trySelect(object)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (findAsParent(object) != null) {
|
||||||
|
checkPath();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TraceObjectKeyPath sibling = findAsSibling(object);
|
||||||
|
if (sibling != null) {
|
||||||
|
setPath(sibling);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setPath(object.getCanonicalPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void traceClosed(Trace trace) {
|
public void traceClosed(Trace trace) {
|
||||||
@ -514,8 +592,21 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setPath(TraceObjectKeyPath path, JComponent source) {
|
protected void activatePath(TraceObjectKeyPath path) {
|
||||||
if (Objects.equals(this.path, path)) {
|
if (isClone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Trace trace = current.getTrace();
|
||||||
|
if (trace != null) {
|
||||||
|
TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path);
|
||||||
|
if (object != null) {
|
||||||
|
traceManager.activateObject(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setPath(TraceObjectKeyPath path, JComponent source, EventOrigin origin) {
|
||||||
|
if (Objects.equals(this.path, path) && getTreeSelection() != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@ -523,7 +614,10 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
pathField.setText(path.toString());
|
pathField.setText(path.toString());
|
||||||
}
|
}
|
||||||
if (source != objectsTreePanel) {
|
if (source != objectsTreePanel) {
|
||||||
selectInTree(path);
|
setTreeSelection(path);
|
||||||
|
}
|
||||||
|
if (origin == EventOrigin.USER_GENERATED) {
|
||||||
|
activatePath(path);
|
||||||
}
|
}
|
||||||
elementsTablePanel.setQuery(ModelQuery.elementsOf(path));
|
elementsTablePanel.setQuery(ModelQuery.elementsOf(path));
|
||||||
attributesTablePanel.setQuery(ModelQuery.attributesOf(path));
|
attributesTablePanel.setQuery(ModelQuery.attributesOf(path));
|
||||||
@ -538,7 +632,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setPath(TraceObjectKeyPath path) {
|
public void setPath(TraceObjectKeyPath path) {
|
||||||
setPath(path, null);
|
setPath(path, null, EventOrigin.API_GENERATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TraceObjectKeyPath getPath() {
|
public TraceObjectKeyPath getPath() {
|
||||||
@ -639,8 +733,17 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
attributesTablePanel.setDiffColorSel(diffColorSel);
|
attributesTablePanel.setDiffColorSel(diffColorSel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void selectInTree(TraceObjectKeyPath path) {
|
protected void setTreeSelection(TraceObjectKeyPath path, EventOrigin origin) {
|
||||||
objectsTreePanel.setSelectedKeyPaths(List.of(path));
|
objectsTreePanel.setSelectedKeyPaths(List.of(path), origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setTreeSelection(TraceObjectKeyPath path) {
|
||||||
|
setTreeSelection(path, EventOrigin.API_GENERATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceObjectValue getTreeSelection() {
|
||||||
|
AbstractNode sel = objectsTreePanel.getSelectedItem();
|
||||||
|
return sel == null ? null : sel.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -671,7 +774,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||||||
public void readDataState(SaveState saveState) {
|
public void readDataState(SaveState saveState) {
|
||||||
if (isClone) {
|
if (isClone) {
|
||||||
DebuggerCoordinates coords = DebuggerCoordinates.readDataState(plugin.getTool(),
|
DebuggerCoordinates coords = DebuggerCoordinates.readDataState(plugin.getTool(),
|
||||||
saveState, KEY_DEBUGGER_COORDINATES, true);
|
saveState, KEY_DEBUGGER_COORDINATES);
|
||||||
if (coords != DebuggerCoordinates.NOWHERE) {
|
if (coords != DebuggerCoordinates.NOWHERE) {
|
||||||
coordinatesActivated(coords);
|
coordinatesActivated(coords);
|
||||||
}
|
}
|
||||||
|
@ -394,6 +394,16 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueRow findTraceObject(TraceObject object) {
|
||||||
|
for (ValueRow row : getModelData()) {
|
||||||
|
if (row.getValue().getValue() == object && row.getValue().isCanonical()) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDiffColor(Color diffColor) {
|
public void setDiffColor(Color diffColor) {
|
||||||
valueColumn.setDiffColor(diffColor);
|
valueColumn.setDiffColor(diffColor);
|
||||||
|
@ -160,10 +160,12 @@ public class ObjectTreeModel implements DisplaysModified {
|
|||||||
|
|
||||||
protected AbstractNode getOrCreateNode(TraceObjectValue value) {
|
protected AbstractNode getOrCreateNode(TraceObjectValue value) {
|
||||||
if (value.getParent() == null) {
|
if (value.getParent() == null) {
|
||||||
|
root.unloadChildren();
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
AbstractNode node =
|
AbstractNode node =
|
||||||
byValue.computeIfAbsent(new IDKeyed<>(value), k -> createNode(value));
|
byValue.computeIfAbsent(new IDKeyed<>(value), k -> createNode(value));
|
||||||
|
node.unloadChildren();
|
||||||
//AbstractNode node = createNode(value);
|
//AbstractNode node = createNode(value);
|
||||||
if (value.isCanonical()) {
|
if (value.isCanonical()) {
|
||||||
byObject.put(new IDKeyed<>(value.getChild()), node);
|
byObject.put(new IDKeyed<>(value.getChild()), node);
|
||||||
@ -303,6 +305,13 @@ public class ObjectTreeModel implements DisplaysModified {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void childCreated(TraceObjectValue value) {
|
protected void childCreated(TraceObjectValue value) {
|
||||||
|
if (!isValueVisible(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nodeCache.getByValue(value) != null) {
|
||||||
|
super.childCreated(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try (KeepTreeState keep = KeepTreeState.ifNotNull(getTree())) {
|
try (KeepTreeState keep = KeepTreeState.ifNotNull(getTree())) {
|
||||||
unloadChildren();
|
unloadChildren();
|
||||||
}
|
}
|
||||||
|
@ -28,8 +28,8 @@ import javax.swing.tree.TreePath;
|
|||||||
import com.google.common.collect.Range;
|
import com.google.common.collect.Range;
|
||||||
|
|
||||||
import docking.widgets.tree.GTree;
|
import docking.widgets.tree.GTree;
|
||||||
import docking.widgets.tree.GTreeNode;
|
|
||||||
import docking.widgets.tree.support.GTreeRenderer;
|
import docking.widgets.tree.support.GTreeRenderer;
|
||||||
|
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||||
import docking.widgets.tree.support.GTreeSelectionListener;
|
import docking.widgets.tree.support.GTreeSelectionListener;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
@ -115,6 +115,10 @@ public class ObjectsTreePanel extends JPanel {
|
|||||||
}
|
}
|
||||||
DebuggerCoordinates previous = current;
|
DebuggerCoordinates previous = current;
|
||||||
this.current = coords;
|
this.current = coords;
|
||||||
|
if (previous.getSnap() == current.getSnap() &&
|
||||||
|
previous.getTrace() == current.getTrace()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try (KeepTreeState keep = keepTreeState()) {
|
try (KeepTreeState keep = keepTreeState()) {
|
||||||
treeModel.setDiffTrace(computeDiffTrace(current.getTrace(), previous.getTrace()));
|
treeModel.setDiffTrace(computeDiffTrace(current.getTrace(), previous.getTrace()));
|
||||||
treeModel.setTrace(current.getTrace());
|
treeModel.setTrace(current.getTrace());
|
||||||
@ -255,14 +259,18 @@ public class ObjectsTreePanel extends JPanel {
|
|||||||
return treeModel.getNode(path);
|
return treeModel.getNode(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths) {
|
public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths, EventOrigin origin) {
|
||||||
List<GTreeNode> nodes = new ArrayList<>();
|
List<TreePath> treePaths = new ArrayList<>();
|
||||||
for (TraceObjectKeyPath path : keyPaths) {
|
for (TraceObjectKeyPath path : keyPaths) {
|
||||||
AbstractNode node = getNode(path);
|
AbstractNode node = getNode(path);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
nodes.add(node);
|
treePaths.add(node.getTreePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tree.setSelectedNodes(nodes);
|
tree.setSelectionPaths(treePaths.toArray(TreePath[]::new), origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths) {
|
||||||
|
setSelectedKeyPaths(keyPaths, EventOrigin.API_GENERATED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
|||||||
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
import ghidra.app.plugin.core.debug.gui.model.columns.*;
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.target.TraceObjectValPath;
|
import ghidra.trace.model.target.*;
|
||||||
|
|
||||||
public class PathTableModel extends AbstractQueryTableModel<PathRow> {
|
public class PathTableModel extends AbstractQueryTableModel<PathRow> {
|
||||||
/** Initialized in {@link #createTableColumnDescriptor()}, which precedes this. */
|
/** Initialized in {@link #createTableColumnDescriptor()}, which precedes this. */
|
||||||
@ -89,6 +89,12 @@ public class PathTableModel extends AbstractQueryTableModel<PathRow> {
|
|||||||
public boolean isModified() {
|
public boolean isModified() {
|
||||||
return isValueModified(path.getLastEntry());
|
return isValueModified(path.getLastEntry());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLastCanonical() {
|
||||||
|
TraceObjectValue last = path.getLastEntry();
|
||||||
|
// Root is canonical
|
||||||
|
return last == null || last.isCanonical();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PathTableModel(Plugin plugin) {
|
public PathTableModel(Plugin plugin) {
|
||||||
@ -143,6 +149,16 @@ public class PathTableModel extends AbstractQueryTableModel<PathRow> {
|
|||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PathRow findTraceObject(TraceObject object) {
|
||||||
|
for (PathRow row : getModelData()) {
|
||||||
|
if (row.getValue() == object && row.isLastCanonical()) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDiffColor(Color diffColor) {
|
public void setDiffColor(Color diffColor) {
|
||||||
valueColumn.setDiffColor(diffColor);
|
valueColumn.setDiffColor(diffColor);
|
||||||
|
@ -279,7 +279,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void objectRestored(DomainObjectChangeRecord rec) {
|
private void objectRestored(DomainObjectChangeRecord rec) {
|
||||||
coordinatesActivated(current.withReFoundThread());
|
coordinatesActivated(current.reFindThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerValueChanged(TraceAddressSpace space, TraceAddressSnapRange range,
|
private void registerValueChanged(TraceAddressSpace space, TraceAddressSnapRange range,
|
||||||
@ -1384,7 +1384,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||||||
public void readDataState(SaveState saveState) {
|
public void readDataState(SaveState saveState) {
|
||||||
if (isClone) {
|
if (isClone) {
|
||||||
coordinatesActivated(
|
coordinatesActivated(
|
||||||
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES, true));
|
DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
|||||||
myActionContext = null;
|
myActionContext = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (snap.longValue() == current.getSnap().longValue()) {
|
if (snap.longValue() == current.getSnap()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
||||||
|
@ -35,8 +35,7 @@ import ghidra.program.model.mem.*;
|
|||||||
import ghidra.trace.model.Trace;
|
import ghidra.trace.model.Trace;
|
||||||
import ghidra.trace.model.Trace.TraceProgramViewListener;
|
import ghidra.trace.model.Trace.TraceProgramViewListener;
|
||||||
import ghidra.trace.model.memory.TraceMemoryOperations;
|
import ghidra.trace.model.memory.TraceMemoryOperations;
|
||||||
import ghidra.trace.model.program.TraceProgramView;
|
import ghidra.trace.model.program.*;
|
||||||
import ghidra.trace.model.program.TraceProgramViewMemory;
|
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.schedule.PatchStep;
|
import ghidra.trace.model.time.schedule.PatchStep;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
@ -121,7 +120,8 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
|
|||||||
DebuggerCoordinates coordinates = getCoordinates();
|
DebuggerCoordinates coordinates = getCoordinates();
|
||||||
Trace trace = coordinates.getTrace();
|
Trace trace = coordinates.getTrace();
|
||||||
|
|
||||||
switch (getCurrentMode(trace)) {
|
StateEditingMode mode = getCurrentMode(trace);
|
||||||
|
switch (mode) {
|
||||||
case READ_ONLY:
|
case READ_ONLY:
|
||||||
return CompletableFuture
|
return CompletableFuture
|
||||||
.failedFuture(new MemoryAccessException("Read-only mode"));
|
.failedFuture(new MemoryAccessException("Read-only mode"));
|
||||||
@ -178,6 +178,9 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
|
|||||||
|
|
||||||
protected CompletableFuture<Void> writeEmulatorVariable(DebuggerCoordinates coordinates,
|
protected CompletableFuture<Void> writeEmulatorVariable(DebuggerCoordinates coordinates,
|
||||||
Address address, byte[] data) {
|
Address address, byte[] data) {
|
||||||
|
if (!(coordinates.getView() instanceof TraceVariableSnapProgramView)) {
|
||||||
|
throw new IllegalArgumentException("Cannot emulate using a Fixed Program View");
|
||||||
|
}
|
||||||
TraceThread thread = coordinates.getThread();
|
TraceThread thread = coordinates.getThread();
|
||||||
if (thread == null) {
|
if (thread == null) {
|
||||||
// TODO: Well, technically, only for register edits
|
// TODO: Well, technically, only for register edits
|
||||||
@ -187,7 +190,7 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
|
|||||||
.patched(thread, PatchStep.generateSleigh(
|
.patched(thread, PatchStep.generateSleigh(
|
||||||
coordinates.getTrace().getBaseLanguage(), address, data));
|
coordinates.getTrace().getBaseLanguage(), address, data));
|
||||||
|
|
||||||
DebuggerCoordinates withTime = coordinates.withTime(time);
|
DebuggerCoordinates withTime = coordinates.time(time);
|
||||||
Long found = traceManager.findSnapshot(withTime);
|
Long found = traceManager.findSnapshot(withTime);
|
||||||
// Materialize it on the same thread (even if swing)
|
// Materialize it on the same thread (even if swing)
|
||||||
// It shouldn't take long, since we're only appending one step.
|
// It shouldn't take long, since we're only appending one step.
|
||||||
@ -237,17 +240,11 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DebuggerCoordinates getCoordinates() {
|
public DebuggerCoordinates getCoordinates() {
|
||||||
DebuggerCoordinates current = traceManager.getCurrentFor(trace);
|
if (!traceManager.getOpenTraces().contains(trace)) {
|
||||||
if (current != null) {
|
throw new IllegalStateException(
|
||||||
return current;
|
"Trace " + trace + " is not opened in the trace manager.");
|
||||||
}
|
}
|
||||||
DebuggerCoordinates resolved =
|
return traceManager.resolveTrace(trace);
|
||||||
traceManager.resolveCoordinates(DebuggerCoordinates.trace(trace));
|
|
||||||
if (resolved != null) {
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Trace " + trace + " is not opened in the trace manager.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +263,7 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DebuggerCoordinates getCoordinates() {
|
public DebuggerCoordinates getCoordinates() {
|
||||||
return traceManager.resolveCoordinates(DebuggerCoordinates.view(view));
|
return traceManager.resolveView(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -268,6 +268,9 @@ class ObjectRecorder {
|
|||||||
|
|
||||||
protected <T extends TargetObject> T getTargetFrameInterface(TraceThread thread, int frameLevel,
|
protected <T extends TargetObject> T getTargetFrameInterface(TraceThread thread, int frameLevel,
|
||||||
Class<T> targetObjectIf) {
|
Class<T> targetObjectIf) {
|
||||||
|
if (thread == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
TraceObject object = ((TraceObjectThread) thread).getObject();
|
TraceObject object = ((TraceObjectThread) thread).getObject();
|
||||||
PathMatcher matcher = object.getTargetSchema().searchFor(targetObjectIf, false);
|
PathMatcher matcher = object.getTargetSchema().searchFor(targetObjectIf, false);
|
||||||
PathPattern pattern = matcher.getSingletonPattern();
|
PathPattern pattern = matcher.getSingletonPattern();
|
||||||
|
@ -53,7 +53,6 @@ import ghidra.trace.model.program.TraceVariableSnapProgramView;
|
|||||||
import ghidra.trace.model.stack.TraceStackFrame;
|
import ghidra.trace.model.stack.TraceStackFrame;
|
||||||
import ghidra.trace.model.target.TraceObject;
|
import ghidra.trace.model.target.TraceObject;
|
||||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||||
import ghidra.trace.model.thread.TraceObjectThread;
|
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.trace.model.time.schedule.TraceSchedule;
|
import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||||
@ -105,7 +104,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
if (supportsFocus(recorder)) {
|
if (supportsFocus(recorder)) {
|
||||||
// TODO: Same for stack frame? I can't imagine it's as common as this....
|
// TODO: Same for stack frame? I can't imagine it's as common as this....
|
||||||
if (thread == recorder.getTraceThreadForSuccessor(recorder.getFocus())) {
|
if (thread == recorder.getTraceThreadForSuccessor(recorder.getFocus())) {
|
||||||
activate(DebuggerCoordinates.thread(thread));
|
activate(current.thread(thread));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -115,7 +114,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
if (current.getThread() != null) {
|
if (current.getThread() != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
activate(DebuggerCoordinates.thread(thread));
|
activate(current.thread(thread));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void threadDeleted(TraceThread thread) {
|
private void threadDeleted(TraceThread thread) {
|
||||||
@ -124,7 +123,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
lastCoordsByTrace.remove(trace);
|
lastCoordsByTrace.remove(trace);
|
||||||
}
|
}
|
||||||
if (current.getThread() == thread) {
|
if (current.getThread() == thread) {
|
||||||
activate(DebuggerCoordinates.trace(trace));
|
activate(current.thread(null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -422,127 +421,24 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
return recorder != null && recorder.isSupportsFocus();
|
return recorder != null && recorder.isSupportsFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected DebuggerCoordinates fillInRecorder(Trace trace, DebuggerCoordinates coordinates) {
|
||||||
public DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coordinates) {
|
|
||||||
if (coordinates == DebuggerCoordinates.NOWHERE) {
|
|
||||||
return DebuggerCoordinates.NOWHERE;
|
|
||||||
}
|
|
||||||
Trace trace = coordinates.getTrace();
|
|
||||||
if (trace == null) {
|
|
||||||
trace = current.getTrace();
|
|
||||||
}
|
|
||||||
if (trace == null) {
|
if (trace == null) {
|
||||||
return DebuggerCoordinates.NOWHERE;
|
return DebuggerCoordinates.NOWHERE;
|
||||||
}
|
}
|
||||||
DebuggerCoordinates lastForTrace = lastCoordsByTrace.get(trace);
|
if (coordinates.getRecorder() != null) {
|
||||||
// Note: override recorder with that known to service
|
return coordinates;
|
||||||
|
}
|
||||||
TraceRecorder recorder = computeRecorder(trace);
|
TraceRecorder recorder = computeRecorder(trace);
|
||||||
TargetObject focus = recorder == null ? null : recorder.getFocus();
|
if (recorder == null) {
|
||||||
TraceThread thread = coordinates.getThread();
|
return coordinates;
|
||||||
if (thread == null) {
|
|
||||||
if (supportsFocus(recorder)) {
|
|
||||||
thread = threadFromTargetFocus(recorder, focus);
|
|
||||||
}
|
|
||||||
if (thread /*still*/ == null) { // either no focus support, or focus is not a thread
|
|
||||||
thread = lastForTrace == null ? null : lastForTrace.getThread();
|
|
||||||
}
|
|
||||||
// NOTE, if still null without focus support,
|
|
||||||
// we will take the eldest live thread at the resolved snap
|
|
||||||
}
|
}
|
||||||
TraceObject object = coordinates.getObject();
|
return coordinates.recorder(recorder);
|
||||||
if (object == null) {
|
|
||||||
if (supportsFocus(recorder)) {
|
|
||||||
object = objectFromTargetFocus(recorder, focus);
|
|
||||||
}
|
|
||||||
if (object /*still*/ == null) { // either no focus support, or focus is not recorded
|
|
||||||
object = lastForTrace == null ? null : lastForTrace.getObject();
|
|
||||||
if (object != null) {
|
|
||||||
TraceObjectThread objThread =
|
|
||||||
object.queryCanonicalAncestorsInterface(TraceObjectThread.class)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
if (objThread != thread) {
|
|
||||||
object = null; // Abandon remembered object
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (object /*still*/ == null && thread instanceof TraceObjectThread objThread) {
|
|
||||||
// TODO: Seek the frame out?
|
|
||||||
object = objThread.getObject();
|
|
||||||
}
|
|
||||||
if (object /*still*/ == null) {
|
|
||||||
object = trace.getObjectManager().getRootObject();
|
|
||||||
// Could still be null, but that means no objects in the trace at all
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Only select a default thread if the trace is not live. If it is live, and the model
|
|
||||||
* supports focus, then we should expect the debugger to control thread/frame focus.
|
|
||||||
*
|
|
||||||
* Note: If recorder has current thread, it should already be in the threadFocusByTrace map
|
|
||||||
*/
|
|
||||||
// Note: override view. May not agree on snap now, but will upon activation
|
|
||||||
TraceProgramView view = trace.getProgramView();
|
|
||||||
TraceSchedule time = coordinates.getTime();
|
|
||||||
if (time == null) {
|
|
||||||
if (recorder != null && autoActivatePresent.get() && trace != current.getTrace()) {
|
|
||||||
time = TraceSchedule.snap(recorder.getSnap());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
time = lastForTrace == null ? TraceSchedule.snap(0) : lastForTrace.getTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!supportsFocus(recorder)) {
|
|
||||||
if (thread /*still*/ == null) {
|
|
||||||
Iterator<? extends TraceThread> it =
|
|
||||||
trace.getThreadManager().getLiveThreads(time.getSnap()).iterator();
|
|
||||||
// docs say eldest come first
|
|
||||||
if (it.hasNext()) {
|
|
||||||
thread = it.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (thread /*STILL!?*/ == null) {
|
|
||||||
Iterator<? extends TraceThread> it =
|
|
||||||
trace.getThreadManager().getAllThreads().iterator();
|
|
||||||
if (it.hasNext()) {
|
|
||||||
thread = it.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer frame = coordinates.getFrame();
|
|
||||||
if (frame == null) {
|
|
||||||
if (supportsFocus(recorder)) {
|
|
||||||
TraceStackFrame traceFrame = frameFromTargetFocus(recorder, focus);
|
|
||||||
if (traceFrame == null) {
|
|
||||||
Msg.warn(this,
|
|
||||||
"Focus-capable model has not reported frame focus. Imposing a default");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
frame = traceFrame.getLevel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 || !time.isSnapOnly() ||
|
|
||||||
!Objects.equals(time.getSnap(), current.getSnap())) {
|
|
||||||
frame = 0;
|
|
||||||
}
|
|
||||||
return DebuggerCoordinates.all(trace, recorder, thread, view, Objects.requireNonNull(time),
|
|
||||||
Objects.requireNonNull(frame), object);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DebuggerCoordinates doSetCurrent(DebuggerCoordinates newCurrent) {
|
protected DebuggerCoordinates doSetCurrent(DebuggerCoordinates newCurrent) {
|
||||||
newCurrent = newCurrent == null ? DebuggerCoordinates.NOWHERE : newCurrent;
|
newCurrent = newCurrent == null ? DebuggerCoordinates.NOWHERE : newCurrent;
|
||||||
synchronized (listenersByTrace) {
|
synchronized (listenersByTrace) {
|
||||||
DebuggerCoordinates resolved = resolveCoordinates(newCurrent);
|
DebuggerCoordinates resolved = fillInRecorder(newCurrent.getTrace(), newCurrent);
|
||||||
if (current.equals(resolved)) {
|
if (current.equals(resolved)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -592,13 +488,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TraceThread thread = threadFromTargetFocus(recorder, obj);
|
activateNoFocus(getCurrentFor(trace).object(obj));
|
||||||
TraceObject object = objectFromTargetFocus(recorder, obj);
|
|
||||||
long snap = recorder.getSnap();
|
|
||||||
TraceStackFrame traceFrame = frameFromTargetFocus(recorder, obj);
|
|
||||||
Integer frame = traceFrame == null ? null : traceFrame.getLevel();
|
|
||||||
activateNoFocus(DebuggerCoordinates.all(trace, recorder, thread, null,
|
|
||||||
TraceSchedule.snap(snap), frame, object));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,11 +516,15 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
|
|
||||||
protected void updateCurrentRecorder() {
|
protected void updateCurrentRecorder() {
|
||||||
TraceRecorder recorder = computeRecorder(current.getTrace());
|
TraceRecorder recorder = computeRecorder(current.getTrace());
|
||||||
|
if (recorder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DebuggerCoordinates toActivate = current.recorder(recorder);
|
||||||
if (autoActivatePresent.get()) {
|
if (autoActivatePresent.get()) {
|
||||||
activate(DebuggerCoordinates.recorder(recorder));
|
activate(toActivate.snap(recorder.getSnap()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
activate(current.withRecorder(recorder));
|
activate(toActivate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -670,7 +564,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DebuggerCoordinates getCurrentFor(Trace trace) {
|
public DebuggerCoordinates getCurrentFor(Trace trace) {
|
||||||
return lastCoordsByTrace.get(trace);
|
synchronized (listenersByTrace) {
|
||||||
|
// If known, fill in recorder ASAP, so it determines the time
|
||||||
|
return fillInRecorder(trace,
|
||||||
|
lastCoordsByTrace.getOrDefault(trace, DebuggerCoordinates.NOWHERE));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -688,12 +586,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
return current.getThread();
|
return current.getThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public TraceThread getCurrentThreadFor(Trace trace) {
|
|
||||||
DebuggerCoordinates coords = lastCoordsByTrace.get(trace);
|
|
||||||
return coords == null ? null : coords.getThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getCurrentSnap() {
|
public long getCurrentSnap() {
|
||||||
return current.getSnap();
|
return current.getSnap();
|
||||||
@ -709,6 +601,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
return current.getObject();
|
return current.getObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Long findSnapshot(DebuggerCoordinates coordinates) {
|
public Long findSnapshot(DebuggerCoordinates coordinates) {
|
||||||
if (coordinates.getTime().isSnapOnly()) {
|
if (coordinates.getTime().isSnapOnly()) {
|
||||||
return coordinates.getSnap();
|
return coordinates.getSnap();
|
||||||
@ -1021,34 +914,47 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
prepareViewAndFireEvent(resolved);
|
prepareViewAndFireEvent(resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) {
|
||||||
|
if (!Objects.equals(prev.getObject(), resolved.getObject())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(prev.getFrame(), resolved.getFrame())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(prev.getThread(), resolved.getThread())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(prev.getTrace(), resolved.getTrace())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected static TargetObject translateToFocus(DebuggerCoordinates prev,
|
protected static TargetObject translateToFocus(DebuggerCoordinates prev,
|
||||||
DebuggerCoordinates resolved) {
|
DebuggerCoordinates resolved) {
|
||||||
if (!resolved.isAliveAndPresent()) {
|
if (!resolved.isAliveAndPresent()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (isSameFocus(prev, resolved)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
TraceRecorder recorder = resolved.getRecorder();
|
TraceRecorder recorder = resolved.getRecorder();
|
||||||
if (!Objects.equals(prev.getObject(), resolved.getObject())) {
|
TraceObject obj = resolved.getObject();
|
||||||
TraceObject obj = resolved.getObject();
|
if (obj != null) {
|
||||||
if (obj != null) {
|
TargetObject object =
|
||||||
TargetObject object =
|
recorder.getTarget().getSuccessor(obj.getCanonicalPath().getKeyList());
|
||||||
recorder.getTarget().getSuccessor(obj.getCanonicalPath().getKeyList());
|
if (object != null) {
|
||||||
if (object != null) {
|
return object;
|
||||||
return object;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!Objects.equals(prev.getFrame(), resolved.getFrame())) {
|
TargetStackFrame frame =
|
||||||
TargetStackFrame frame =
|
recorder.getTargetStackFrame(resolved.getThread(), resolved.getFrame());
|
||||||
recorder.getTargetStackFrame(resolved.getThread(), resolved.getFrame());
|
if (frame != null) {
|
||||||
if (frame != null) {
|
return frame;
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!Objects.equals(prev.getThread(), resolved.getThread())) {
|
TargetThread thread = recorder.getTargetThread(resolved.getThread());
|
||||||
TargetThread thread = recorder.getTargetThread(resolved.getThread());
|
if (thread != null) {
|
||||||
if (thread != null) {
|
return thread;
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return recorder.getTarget();
|
return recorder.getTarget();
|
||||||
}
|
}
|
||||||
@ -1094,33 +1000,40 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activateTrace(Trace trace) {
|
public DebuggerCoordinates resolveTrace(Trace trace) {
|
||||||
activate(DebuggerCoordinates.trace(trace));
|
return getCurrentFor(trace).trace(trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activateThread(TraceThread thread) {
|
public DebuggerCoordinates resolveThread(TraceThread thread) {
|
||||||
activate(DebuggerCoordinates.thread(thread));
|
Trace trace = thread == null ? null : thread.getTrace();
|
||||||
|
return getCurrentFor(trace).thread(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activateSnap(long snap) {
|
public DebuggerCoordinates resolveSnap(long snap) {
|
||||||
activateNoFocusChange(DebuggerCoordinates.snap(snap));
|
return current.snap(snap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activateTime(TraceSchedule time) {
|
public DebuggerCoordinates resolveTime(TraceSchedule time) {
|
||||||
activate(DebuggerCoordinates.time(time));
|
return current.time(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activateFrame(int frameLevel) {
|
public DebuggerCoordinates resolveView(TraceProgramView view) {
|
||||||
activate(DebuggerCoordinates.frame(frameLevel));
|
Trace trace = view == null ? null : view.getTrace();
|
||||||
|
return getCurrentFor(trace).view(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void activateObject(TraceObject object) {
|
public DebuggerCoordinates resolveFrame(int frameLevel) {
|
||||||
activate(DebuggerCoordinates.object(object));
|
return current.frame(frameLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DebuggerCoordinates resolveObject(TraceObject object) {
|
||||||
|
return current.object(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1135,7 +1048,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
if (curRecorder != null) {
|
if (curRecorder != null) {
|
||||||
activateNoFocus(DebuggerCoordinates.snap(curRecorder.getSnap()));
|
activateNoFocus(current.snap(curRecorder.getSnap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1274,12 +1187,12 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||||||
String stateName = PREFIX_OPEN_TRACE + index;
|
String stateName = PREFIX_OPEN_TRACE + index;
|
||||||
// Trace will be opened by readDataState, resolve causes update to focus and view
|
// Trace will be opened by readDataState, resolve causes update to focus and view
|
||||||
DebuggerCoordinates coords =
|
DebuggerCoordinates coords =
|
||||||
DebuggerCoordinates.readDataState(tool, saveState, stateName, true);
|
DebuggerCoordinates.readDataState(tool, saveState, stateName);
|
||||||
if (coords.getTrace() != null) {
|
if (coords.getTrace() != null) {
|
||||||
lastCoordsByTrace.put(coords.getTrace(), coords);
|
lastCoordsByTrace.put(coords.getTrace(), coords);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activate(DebuggerCoordinates.readDataState(tool, saveState, KEY_CURRENT_COORDS, false));
|
activate(DebuggerCoordinates.readDataState(tool, saveState, KEY_CURRENT_COORDS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,10 @@ public class DefaultTransactionCoalescer<T extends UndoableDomainObject, U exten
|
|||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
|
if (tx == null) {
|
||||||
|
// TODO: This smells really bad
|
||||||
|
return;
|
||||||
|
}
|
||||||
tx.exit();
|
tx.exit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,19 +111,6 @@ public interface DebuggerTraceManagerService {
|
|||||||
*/
|
*/
|
||||||
TraceThread getCurrentThread();
|
TraceThread getCurrentThread();
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the active thread for a given trace
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* The manager remembers the last active thread for every open trace. If the trace has never
|
|
||||||
* been active, then the last active thread is null. If trace is the active trace, then this
|
|
||||||
* will return the currently active thread.
|
|
||||||
*
|
|
||||||
* @param trace the trace
|
|
||||||
* @return the thread, or null
|
|
||||||
*/
|
|
||||||
TraceThread getCurrentThreadFor(Trace trace);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the active snap
|
* Get the active snap
|
||||||
*
|
*
|
||||||
@ -255,47 +242,125 @@ public interface DebuggerTraceManagerService {
|
|||||||
*/
|
*/
|
||||||
void activate(DebuggerCoordinates coordinates);
|
void activate(DebuggerCoordinates coordinates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve coordinates for the given trace using the manager's "best judgment"
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The manager may use a variety of sources of context including the current trace, the last
|
||||||
|
* coordinates for a trace, the target's last/current focus, the list of active threads, etc.
|
||||||
|
*
|
||||||
|
* @param trace the trace
|
||||||
|
* @return the best coordinates
|
||||||
|
*/
|
||||||
|
DebuggerCoordinates resolveTrace(Trace trace);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the given trace
|
* Activate the given trace
|
||||||
*
|
*
|
||||||
* @param trace the desired trace
|
* @param trace the desired trace
|
||||||
*/
|
*/
|
||||||
void activateTrace(Trace trace);
|
default void activateTrace(Trace trace) {
|
||||||
|
activate(resolveTrace(trace));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve coordinates for the given thread using the manager's "best judgment"
|
||||||
|
*
|
||||||
|
* @see #resolveTrace(Trace)
|
||||||
|
* @param thread the thread
|
||||||
|
* @return the best coordinates
|
||||||
|
*/
|
||||||
|
DebuggerCoordinates resolveThread(TraceThread thread);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the given thread
|
* Activate the given thread
|
||||||
*
|
*
|
||||||
* @param thread the desired thread
|
* @param thread the desired thread
|
||||||
*/
|
*/
|
||||||
void activateThread(TraceThread thread);
|
default void activateThread(TraceThread thread) {
|
||||||
|
activate(resolveThread(thread));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve coordinates for the given snap using the manager's "best judgment"
|
||||||
|
*
|
||||||
|
* @see #resolveTrace(Trace)
|
||||||
|
* @param snap the snapshot key
|
||||||
|
* @return the best coordinates
|
||||||
|
*/
|
||||||
|
DebuggerCoordinates resolveSnap(long snap);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the given snapshot key
|
* Activate the given snapshot key
|
||||||
*
|
*
|
||||||
* @param snap the desired snapshot key
|
* @param snap the desired snapshot key
|
||||||
*/
|
*/
|
||||||
void activateSnap(long snap);
|
default void activateSnap(long snap) {
|
||||||
|
activate(resolveSnap(snap));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve coordinates for the given time using the manager's "best judgment"
|
||||||
|
*
|
||||||
|
* @see #resolveTrace(Trace)
|
||||||
|
* @param time the time
|
||||||
|
* @return the best coordinates
|
||||||
|
*/
|
||||||
|
DebuggerCoordinates resolveTime(TraceSchedule time);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the given point in time, possibly invoking emulation
|
* Activate the given point in time, possibly invoking emulation
|
||||||
*
|
*
|
||||||
* @param time the desired schedule
|
* @param time the desired schedule
|
||||||
*/
|
*/
|
||||||
void activateTime(TraceSchedule time);
|
default void activateTime(TraceSchedule time) {
|
||||||
|
activate(resolveTime(time));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve coordinates for the given view using the manager's "best judgment"
|
||||||
|
*
|
||||||
|
* @see #resolveTrace(Trace)
|
||||||
|
* @param view the view
|
||||||
|
* @return the best coordinates
|
||||||
|
*/
|
||||||
|
DebuggerCoordinates resolveView(TraceProgramView view);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve coordinates for the given frame level using the manager's "best judgment"
|
||||||
|
*
|
||||||
|
* @see #resolveTrace(Trace)
|
||||||
|
* @param frameLevel the frame level, 0 being the innermost
|
||||||
|
* @return the best coordinates
|
||||||
|
*/
|
||||||
|
DebuggerCoordinates resolveFrame(int frameLevel);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the given stack frame
|
* Activate the given stack frame
|
||||||
*
|
*
|
||||||
* @param frameLevel the level of the desired frame, 0 being innermost
|
* @param frameLevel the level of the desired frame, 0 being innermost
|
||||||
*/
|
*/
|
||||||
void activateFrame(int frameLevel);
|
default void activateFrame(int frameLevel) {
|
||||||
|
activate(resolveFrame(frameLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve coordinates for the given object using the manager's "best judgment"
|
||||||
|
*
|
||||||
|
* @see #resolveTrace(Trace)
|
||||||
|
* @param object the object
|
||||||
|
* @return the best coordinates
|
||||||
|
*/
|
||||||
|
DebuggerCoordinates resolveObject(TraceObject object);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the given object
|
* Activate the given object
|
||||||
*
|
*
|
||||||
* @param object the desired object
|
* @param object the desired object
|
||||||
*/
|
*/
|
||||||
void activateObject(TraceObject object);
|
default void activateObject(TraceObject object) {
|
||||||
|
activate(resolveObject(object));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control whether the trace manager automatically activates the "present snapshot"
|
* Control whether the trace manager automatically activates the "present snapshot"
|
||||||
@ -413,14 +478,6 @@ public interface DebuggerTraceManagerService {
|
|||||||
*/
|
*/
|
||||||
void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener);
|
||||||
|
|
||||||
/**
|
|
||||||
* Fill in an incomplete coordinate specification, using the manager's "best judgment"
|
|
||||||
*
|
|
||||||
* @param coords the possibly-incomplete coordinates
|
|
||||||
* @return the complete resolved coordinates
|
|
||||||
*/
|
|
||||||
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coordinates);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the given coordinates are already materialized, get the snapshot
|
* If the given coordinates are already materialized, get the snapshot
|
||||||
*
|
*
|
||||||
|
@ -1138,8 +1138,7 @@ public interface FlatDebuggerAPI {
|
|||||||
* @return the editor
|
* @return the editor
|
||||||
*/
|
*/
|
||||||
default StateEditor createStateEditor(DebuggerCoordinates coordinates) {
|
default StateEditor createStateEditor(DebuggerCoordinates coordinates) {
|
||||||
return getEditingService()
|
return getEditingService().createStateEditor(coordinates);
|
||||||
.createStateEditor(getTraceManager().resolveCoordinates(coordinates));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1150,9 +1149,9 @@ public interface FlatDebuggerAPI {
|
|||||||
* @return the editor
|
* @return the editor
|
||||||
*/
|
*/
|
||||||
default StateEditor createStateEditor(Trace trace, long snap) {
|
default StateEditor createStateEditor(Trace trace, long snap) {
|
||||||
return getEditingService().createStateEditor(DebuggerCoordinates
|
return getEditingService().createStateEditor(getTraceManager()
|
||||||
.trace(trace)
|
.resolveTrace(trace)
|
||||||
.withSnap(snap));
|
.snap(snap));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1164,10 +1163,10 @@ public interface FlatDebuggerAPI {
|
|||||||
* @return the editor
|
* @return the editor
|
||||||
*/
|
*/
|
||||||
default StateEditor createStateEditor(TraceThread thread, int frame, long snap) {
|
default StateEditor createStateEditor(TraceThread thread, int frame, long snap) {
|
||||||
return getEditingService().createStateEditor(DebuggerCoordinates
|
return getEditingService().createStateEditor(getTraceManager()
|
||||||
.thread(thread)
|
.resolveThread(thread)
|
||||||
.withSnap(snap)
|
.snap(snap)
|
||||||
.withFrame(frame));
|
.frame(frame));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -600,7 +600,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||||||
modelService.addModel(mb.testModel);
|
modelService.addModel(mb.testModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected TraceRecorder recordAndWaitSync() throws Exception {
|
protected TraceRecorder recordAndWaitSync() throws Throwable {
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
mb.createTestThreadRegisterBanks();
|
mb.createTestThreadRegisterBanks();
|
||||||
@ -613,22 +613,7 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
|
|||||||
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
|
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
|
||||||
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
|
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
|
||||||
|
|
||||||
waitFor(() -> {
|
waitRecorder(recorder);
|
||||||
TraceThread thread = recorder.getTraceThread(mb.testThread1);
|
|
||||||
if (thread == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
DebuggerRegisterMapper mapper = recorder.getRegisterMapper(thread);
|
|
||||||
if (mapper == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!mapper.getRegistersOnTarget().containsAll(baseRegs)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return recorder;
|
return recorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1343,7 +1343,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||||||
regs.setValue(1, new RegisterValue(pc, new BigInteger("00404321", 16)));
|
regs.setValue(1, new RegisterValue(pc, new BigInteger("00404321", 16)));
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
traceManager.activate(DebuggerCoordinates.threadSnap(thread, 0));
|
traceManager.activate(DebuggerCoordinates.NOWHERE.thread(thread).snap(0));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
||||||
@ -1398,7 +1398,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||||||
regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16)));
|
regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16)));
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
traceManager.activate(DebuggerCoordinates.threadSnap(thread, 0));
|
traceManager.activate(DebuggerCoordinates.NOWHERE.thread(thread).snap(0));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
||||||
@ -1432,7 +1432,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||||||
stack.getFrame(0, true);
|
stack.getFrame(0, true);
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
traceManager.activate(DebuggerCoordinates.threadSnap(thread, 0));
|
traceManager.activate(DebuggerCoordinates.NOWHERE.thread(thread).snap(0));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress());
|
||||||
|
@ -945,7 +945,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||||||
regs.setValue(1, new RegisterValue(pc, new BigInteger("00404321", 16)));
|
regs.setValue(1, new RegisterValue(pc, new BigInteger("00404321", 16)));
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
traceManager.activate(DebuggerCoordinates.threadSnap(thread, 0));
|
traceManager.activate(DebuggerCoordinates.NOWHERE.thread(thread).snap(0));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
||||||
@ -1000,7 +1000,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||||||
regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16)));
|
regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16)));
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
traceManager.activate(DebuggerCoordinates.threadSnap(thread, 0));
|
traceManager.activate(DebuggerCoordinates.NOWHERE.thread(thread).snap(0));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
||||||
@ -1034,7 +1034,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
|
|||||||
stack.getFrame(0, true);
|
stack.getFrame(0, true);
|
||||||
}
|
}
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
traceManager.activate(DebuggerCoordinates.threadSnap(thread, 0));
|
traceManager.activate(DebuggerCoordinates.NOWHERE.thread(thread).snap(0));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress());
|
||||||
|
@ -27,6 +27,7 @@ import com.google.common.collect.Range;
|
|||||||
|
|
||||||
import docking.widgets.table.DynamicTableColumn;
|
import docking.widgets.table.DynamicTableColumn;
|
||||||
import docking.widgets.table.GDynamicColumnTableModel;
|
import docking.widgets.table.GDynamicColumnTableModel;
|
||||||
|
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||||
import generic.Unique;
|
import generic.Unique;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
@ -40,10 +41,10 @@ import ghidra.dbg.target.TargetObject;
|
|||||||
import ghidra.dbg.target.schema.SchemaContext;
|
import ghidra.dbg.target.schema.SchemaContext;
|
||||||
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
|
||||||
import ghidra.dbg.target.schema.XmlSchemaContext;
|
import ghidra.dbg.target.schema.XmlSchemaContext;
|
||||||
import ghidra.trace.database.target.DBTraceObject;
|
|
||||||
import ghidra.trace.database.target.DBTraceObjectManager;
|
|
||||||
import ghidra.trace.model.target.*;
|
import ghidra.trace.model.target.*;
|
||||||
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
import ghidra.trace.model.target.TraceObject.ConflictResolution;
|
||||||
|
import ghidra.trace.model.thread.TraceObjectThread;
|
||||||
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
|
||||||
public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||||
@ -52,34 +53,45 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
CTX = XmlSchemaContext.deserialize("" + //
|
CTX = XmlSchemaContext.deserialize(
|
||||||
"<context>" + //
|
"""
|
||||||
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
|
<context>
|
||||||
" <attribute name='Processes' schema='ProcessContainer' />" + //
|
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
||||||
" <interface name='EventScope' />" + //
|
<attribute name='Processes' schema='ProcessContainer' />
|
||||||
" </schema>" + //
|
<interface name='EventScope' />
|
||||||
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + //
|
</schema>
|
||||||
" attributeResync='ONCE'>" + //
|
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
|
||||||
" <element schema='Process' />" + //
|
attributeResync='ONCE'>
|
||||||
" </schema>" + //
|
<element schema='Process' />
|
||||||
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + //
|
</schema>
|
||||||
" <attribute name='Threads' schema='ThreadContainer' />" + //
|
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
|
||||||
" <attribute name='Handles' schema='HandleContainer' />" + //
|
<attribute name='Threads' schema='ThreadContainer' />
|
||||||
" </schema>" + //
|
<attribute name='Handles' schema='HandleContainer' />
|
||||||
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
|
</schema>
|
||||||
" attributeResync='ONCE'>" + //
|
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
||||||
" <element schema='Thread' />" + //
|
attributeResync='ONCE'>
|
||||||
" </schema>" + //
|
<element schema='Thread' />
|
||||||
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
|
</schema>
|
||||||
" <interface name='Thread' />" + //
|
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
" <attribute name='_display' schema='STRING' />" + //
|
<interface name='Thread' />
|
||||||
" <attribute name='_self' schema='Thread' />" + //
|
<attribute name='_display' schema='STRING' />
|
||||||
" </schema>" + //
|
<attribute name='_self' schema='Thread' />
|
||||||
" <schema name='HandleContainer' canonical='yes' elementResync='NEVER' " + //
|
<attribute name='Stack' schema='Stack' />
|
||||||
" attributeResync='ONCE'>" + //
|
</schema>
|
||||||
" <element schema='INT' />" + //
|
<schema name='Stack' canonical='yes' elementResync='NEVER'
|
||||||
" </schema>" + //
|
attributeResync='ONCE'>
|
||||||
"</context>");
|
<interface name='Stack' />
|
||||||
|
<element schema='Frame' />
|
||||||
|
</schema>
|
||||||
|
<schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>
|
||||||
|
<interface name='StackFrame' />
|
||||||
|
</schema>
|
||||||
|
<schema name='HandleContainer' canonical='yes' elementResync='NEVER'
|
||||||
|
attributeResync='ONCE'>
|
||||||
|
<element schema='INT' />
|
||||||
|
</schema>
|
||||||
|
</context>
|
||||||
|
""");
|
||||||
}
|
}
|
||||||
catch (JDOMException e) {
|
catch (JDOMException e) {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
@ -104,9 +116,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
public void setUpModelProviderTest() throws Exception {
|
public void setUpModelProviderTest() throws Exception {
|
||||||
modelPlugin = addPlugin(tool, DebuggerModelPlugin.class);
|
modelPlugin = addPlugin(tool, DebuggerModelPlugin.class);
|
||||||
modelProvider = waitForComponentProvider(DebuggerModelProvider.class);
|
modelProvider = waitForComponentProvider(DebuggerModelProvider.class);
|
||||||
|
|
||||||
// So I can manipulate the coordinates
|
|
||||||
//addPlugin(tool, DebuggerThreadsPlugin.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -126,16 +135,16 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected TraceObjectValue createSessionObject() throws Throwable {
|
protected TraceObjectValue createSessionObject() throws Throwable {
|
||||||
DBTraceObjectManager objects = tb.trace.getObjectManager();
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
return objects.createRootObject(CTX.getSchema(new SchemaName("Session")));
|
return objects.createRootObject(CTX.getSchema(new SchemaName("Session")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected DBTraceObject createThread(long i, DBTraceObject prevThread) {
|
protected TraceObject createThread(long i, TraceObject prevThread) {
|
||||||
DBTraceObjectManager objects = tb.trace.getObjectManager();
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
TraceObjectKeyPath threadContainerPath = TraceObjectKeyPath.parse("Processes[0].Threads");
|
TraceObjectKeyPath threadContainerPath = TraceObjectKeyPath.parse("Processes[0].Threads");
|
||||||
DBTraceObject thread = objects.createObject(threadContainerPath.index(i));
|
TraceObject thread = objects.createObject(threadContainerPath.index(i));
|
||||||
thread.insert(Range.closed(i, 10L), ConflictResolution.DENY);
|
thread.insert(Range.closed(i, 10L), ConflictResolution.DENY);
|
||||||
thread.insert(Range.atLeast(10 + i), ConflictResolution.DENY);
|
thread.insert(Range.atLeast(10 + i), ConflictResolution.DENY);
|
||||||
thread.setAttribute(Range.atLeast(i), "Attribute " + i, "Some value");
|
thread.setAttribute(Range.atLeast(i), "Attribute " + i, "Some value");
|
||||||
@ -151,18 +160,31 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TraceObject createStack(TraceObject thread) {
|
||||||
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
TraceObjectKeyPath stackPath = thread.getCanonicalPath().key("Stack");
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject stack = objects.createObject(stackPath);
|
||||||
|
objects.createObject(stackPath.index(0))
|
||||||
|
.insert(thread.getLife().span(), ConflictResolution.TRUNCATE);
|
||||||
|
objects.createObject(stackPath.index(1))
|
||||||
|
.insert(thread.getLife().span(), ConflictResolution.TRUNCATE);
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void populateThreads() throws Throwable {
|
protected void populateThreads() throws Throwable {
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
DBTraceObject prevThread = null;
|
TraceObject prevThread = null;
|
||||||
for (long i = 0; i < 10; i++) {
|
for (long i = 0; i < 10; i++) {
|
||||||
DBTraceObject thread = createThread(i, prevThread);
|
TraceObject thread = createThread(i, prevThread);
|
||||||
prevThread = thread;
|
prevThread = thread;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addThread10() throws Throwable {
|
protected void addThread10() throws Throwable {
|
||||||
DBTraceObjectManager objects = tb.trace.getObjectManager();
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
createThread(10, objects.getObjectByCanonicalPath(
|
createThread(10, objects.getObjectByCanonicalPath(
|
||||||
TraceObjectKeyPath.parse("Processes[0].Threads[9]")));
|
TraceObjectKeyPath.parse("Processes[0].Threads[9]")));
|
||||||
@ -170,9 +192,9 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void populateHandles() throws Throwable {
|
protected void populateHandles() throws Throwable {
|
||||||
DBTraceObjectManager objects = tb.trace.getObjectManager();
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
DBTraceObject handleContainer =
|
TraceObject handleContainer =
|
||||||
objects.createObject(TraceObjectKeyPath.parse("Processes[0].Handles"));
|
objects.createObject(TraceObjectKeyPath.parse("Processes[0].Handles"));
|
||||||
handleContainer.insert(Range.atLeast(0L), ConflictResolution.DENY);
|
handleContainer.insert(Range.atLeast(0L), ConflictResolution.DENY);
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
@ -183,10 +205,10 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void populateLinks() throws Throwable {
|
protected void populateLinks() throws Throwable {
|
||||||
DBTraceObjectManager objects = tb.trace.getObjectManager();
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
TraceObjectKeyPath threadContainerPath = TraceObjectKeyPath.parse("Processes[0].Threads");
|
TraceObjectKeyPath threadContainerPath = TraceObjectKeyPath.parse("Processes[0].Threads");
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
DBTraceObject linkContainer =
|
TraceObject linkContainer =
|
||||||
objects.createObject(TraceObjectKeyPath.parse("Processes[0].Links"));
|
objects.createObject(TraceObjectKeyPath.parse("Processes[0].Links"));
|
||||||
linkContainer.insert(Range.atLeast(0L), ConflictResolution.DENY);
|
linkContainer.insert(Range.atLeast(0L), ConflictResolution.DENY);
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
@ -197,7 +219,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void populateBoxedPrimitive() throws Throwable {
|
protected void populateBoxedPrimitive() throws Throwable {
|
||||||
DBTraceObjectManager objects = tb.trace.getObjectManager();
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
TraceObject boxed =
|
TraceObject boxed =
|
||||||
objects.createObject(TraceObjectKeyPath.parse("Processes[0].Boxed"));
|
objects.createObject(TraceObjectKeyPath.parse("Processes[0].Boxed"));
|
||||||
@ -291,7 +313,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
createTraceAndPopulateObjects();
|
createTraceAndPopulateObjects();
|
||||||
|
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
modelProvider.objectsTreePanel
|
modelProvider.objectsTreePanel
|
||||||
.setSelectedKeyPaths(List.of(TraceObjectKeyPath.parse("Processes[0].Threads")));
|
.setSelectedKeyPaths(List.of(TraceObjectKeyPath.parse("Processes[0].Threads")));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
@ -306,7 +328,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
ValueRow selElem = waitForValue(() -> {
|
ValueRow selElem = waitForValue(() -> {
|
||||||
List<ValueRow> rows = modelProvider.elementsTablePanel.tableModel.getModelData();
|
List<ValueRow> rows = modelProvider.elementsTablePanel.tableModel.getModelData();
|
||||||
@ -316,7 +338,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
return rows.get(2);
|
return rows.get(2);
|
||||||
});
|
});
|
||||||
modelProvider.elementsTablePanel.setSelectedItem(selElem);
|
modelProvider.elementsTablePanel.setSelectedItem(selElem);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
waitForPass(() -> assertEquals(3,
|
waitForPass(() -> assertEquals(3,
|
||||||
modelProvider.attributesTablePanel.tableModel.getModelData().size()));
|
modelProvider.attributesTablePanel.tableModel.getModelData().size()));
|
||||||
@ -329,7 +351,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].NoSuch"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].NoSuch"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertEquals("No such object at path Processes[0].NoSuch", tool.getStatusInfo());
|
assertEquals("No such object at path Processes[0].NoSuch", tool.getStatusInfo());
|
||||||
}
|
}
|
||||||
@ -341,7 +363,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Handles"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Handles"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
int valColIndex =
|
int valColIndex =
|
||||||
waitForValue(() -> findColumnOfClass(modelProvider.elementsTablePanel.tableModel,
|
waitForValue(() -> findColumnOfClass(modelProvider.elementsTablePanel.tableModel,
|
||||||
@ -364,7 +386,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
modelProvider.pathField.setText("SomeNonsenseToBeCancelled");
|
modelProvider.pathField.setText("SomeNonsenseToBeCancelled");
|
||||||
triggerEscapeKey(modelProvider.pathField);
|
triggerEscapeKey(modelProvider.pathField);
|
||||||
@ -380,7 +402,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Links"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Links"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
ValueRow row2 = waitForValue(() -> {
|
ValueRow row2 = waitForValue(() -> {
|
||||||
return modelProvider.elementsTablePanel.tableModel.getModelData()
|
return modelProvider.elementsTablePanel.tableModel.getModelData()
|
||||||
@ -390,7 +412,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
});
|
});
|
||||||
modelProvider.elementsTablePanel.setSelectedItem(row2);
|
modelProvider.elementsTablePanel.setSelectedItem(row2);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
int rowIndex = waitForValue(() -> {
|
int rowIndex = waitForValue(() -> {
|
||||||
int index = modelProvider.elementsTablePanel.table.getSelectedRow();
|
int index = modelProvider.elementsTablePanel.table.getSelectedRow();
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
@ -410,7 +432,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
ValueRow row2 = waitForValue(() -> {
|
ValueRow row2 = waitForValue(() -> {
|
||||||
return modelProvider.elementsTablePanel.tableModel.getModelData()
|
return modelProvider.elementsTablePanel.tableModel.getModelData()
|
||||||
@ -420,7 +442,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
});
|
});
|
||||||
modelProvider.elementsTablePanel.setSelectedItem(row2);
|
modelProvider.elementsTablePanel.setSelectedItem(row2);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
int rowIndex = waitForValue(() -> {
|
int rowIndex = waitForValue(() -> {
|
||||||
int index = modelProvider.elementsTablePanel.table.getSelectedRow();
|
int index = modelProvider.elementsTablePanel.table.getSelectedRow();
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
@ -458,9 +480,9 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
selectAttribute("_next");
|
selectAttribute("_next");
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
int rowIndex = waitForValue(() -> {
|
int rowIndex = waitForValue(() -> {
|
||||||
int index = modelProvider.attributesTablePanel.table.getSelectedRow();
|
int index = modelProvider.attributesTablePanel.table.getSelectedRow();
|
||||||
@ -481,7 +503,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0]"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0]"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
PathRow rowNext = waitForValue(() -> {
|
PathRow rowNext = waitForValue(() -> {
|
||||||
return modelProvider.attributesTablePanel.tableModel.getModelData()
|
return modelProvider.attributesTablePanel.tableModel.getModelData()
|
||||||
@ -497,7 +519,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
});
|
});
|
||||||
modelProvider.attributesTablePanel.setSelectedItem(rowNext);
|
modelProvider.attributesTablePanel.setSelectedItem(rowNext);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
int rowIndex = waitForValue(() -> {
|
int rowIndex = waitForValue(() -> {
|
||||||
int index = modelProvider.attributesTablePanel.table.getSelectedRow();
|
int index = modelProvider.attributesTablePanel.table.getSelectedRow();
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
@ -519,7 +541,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(TraceObjectKeyPath.parse("Processes[0].Threads"), 10, 0);
|
assertPathIs(TraceObjectKeyPath.parse("Processes[0].Threads"), 10, 0);
|
||||||
|
|
||||||
@ -545,7 +567,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
AbstractNode nodeThread2 =
|
AbstractNode nodeThread2 =
|
||||||
waitForValue(() -> modelProvider.objectsTreePanel.getSelectedItem());
|
waitForValue(() -> modelProvider.objectsTreePanel.getSelectedItem());
|
||||||
@ -573,14 +595,16 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
selectAttribute("_next");
|
selectAttribute("_next");
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEnabled(modelProvider, modelProvider.actionFollowLink);
|
assertEnabled(modelProvider, modelProvider.actionFollowLink);
|
||||||
performAction(modelProvider.actionFollowLink, modelProvider, true);
|
performAction(modelProvider.actionFollowLink, modelProvider, true);
|
||||||
|
|
||||||
assertPathIs(TraceObjectKeyPath.parse("Processes[0].Threads[3]"), 0, 5);
|
TraceObjectKeyPath thread3Path = TraceObjectKeyPath.parse("Processes[0].Threads[3]");
|
||||||
|
assertPathIs(thread3Path, 0, 5);
|
||||||
|
assertEquals(thread3Path, traceManager.getCurrentObject().getCanonicalPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -590,7 +614,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
performAction(modelProvider.actionCloneWindow);
|
performAction(modelProvider.actionCloneWindow);
|
||||||
|
|
||||||
@ -608,12 +632,12 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(path);
|
modelProvider.setPath(path);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIsThreadsContainer();
|
assertPathIsThreadsContainer();
|
||||||
|
|
||||||
addThread10();
|
addThread10();
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 11, 0);
|
assertPathIs(path, 11, 0);
|
||||||
}
|
}
|
||||||
@ -626,15 +650,15 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(path);
|
modelProvider.setPath(path);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 0, 3);
|
assertPathIs(path, 0, 3);
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
DBTraceObject thread = tb.trace.getObjectManager().getObjectByCanonicalPath(path);
|
TraceObject thread = tb.trace.getObjectManager().getObjectByCanonicalPath(path);
|
||||||
thread.setAttribute(Range.atLeast(0L), "NewAttribute", 11);
|
thread.setAttribute(Range.atLeast(0L), "NewAttribute", 11);
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 0, 4);
|
assertPathIs(path, 0, 4);
|
||||||
}
|
}
|
||||||
@ -647,15 +671,15 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(path);
|
modelProvider.setPath(path);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIsThreadsContainer();
|
assertPathIsThreadsContainer();
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
DBTraceObject threads = tb.trace.getObjectManager().getObjectByCanonicalPath(path);
|
TraceObject threads = tb.trace.getObjectManager().getObjectByCanonicalPath(path);
|
||||||
threads.setElement(Range.all(), 2, null);
|
threads.setElement(Range.all(), 2, null);
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 9, 0);
|
assertPathIs(path, 9, 0);
|
||||||
}
|
}
|
||||||
@ -668,15 +692,15 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(path);
|
modelProvider.setPath(path);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 0, 3);
|
assertPathIs(path, 0, 3);
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
DBTraceObject thread = tb.trace.getObjectManager().getObjectByCanonicalPath(path);
|
TraceObject thread = tb.trace.getObjectManager().getObjectByCanonicalPath(path);
|
||||||
thread.setAttribute(Range.all(), "_self", null);
|
thread.setAttribute(Range.all(), "_self", null);
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 0, 2);
|
assertPathIs(path, 0, 2);
|
||||||
}
|
}
|
||||||
@ -693,21 +717,21 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateSnap(2);
|
traceManager.activateSnap(2);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(path);
|
modelProvider.setPath(path);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 3, 0);
|
assertPathIs(path, 3, 0);
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
element2.setLifespan(Range.atLeast(10L), ConflictResolution.DENY);
|
element2.setLifespan(Range.atLeast(10L), ConflictResolution.DENY);
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 2, 0);
|
assertPathIs(path, 2, 0);
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
element2.setLifespan(Range.atLeast(2L), ConflictResolution.DENY);
|
element2.setLifespan(Range.atLeast(2L), ConflictResolution.DENY);
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 3, 0);
|
assertPathIs(path, 3, 0);
|
||||||
}
|
}
|
||||||
@ -725,21 +749,21 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateSnap(2);
|
traceManager.activateSnap(2);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(path);
|
modelProvider.setPath(path);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 0, 4); // _next created at snap 3
|
assertPathIs(path, 0, 4); // _next created at snap 3
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
attrSelf.setLifespan(Range.atLeast(10L), ConflictResolution.DENY);
|
attrSelf.setLifespan(Range.atLeast(10L), ConflictResolution.DENY);
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 0, 3);
|
assertPathIs(path, 0, 3);
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
attrSelf.setLifespan(Range.atLeast(2L), ConflictResolution.DENY);
|
attrSelf.setLifespan(Range.atLeast(2L), ConflictResolution.DENY);
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
assertPathIs(path, 0, 4);
|
assertPathIs(path, 0, 4);
|
||||||
}
|
}
|
||||||
@ -753,7 +777,7 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
modelProvider.setPath(path);
|
modelProvider.setPath(path);
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
AbstractNode node =
|
AbstractNode node =
|
||||||
waitForValue(() -> modelProvider.objectsTreePanel.treeModel.getNode(path));
|
waitForValue(() -> modelProvider.objectsTreePanel.treeModel.getNode(path));
|
||||||
@ -762,8 +786,253 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
thread.setAttribute(Range.atLeast(0L), "_display", "Renamed Thread");
|
thread.setAttribute(Range.atLeast(0L), "_display", "Renamed Thread");
|
||||||
}
|
}
|
||||||
waitForSwing();
|
waitForTasks();
|
||||||
|
|
||||||
waitForPass(() -> assertEquals("<html>Renamed Thread", node.getDisplayText()));
|
waitForPass(() -> assertEquals("<html>Renamed Thread", node.getDisplayText()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTreeSelectionActivatesObject() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject root = objects.getRootObject();
|
||||||
|
TraceObjectKeyPath processesPath = TraceObjectKeyPath.parse("Processes");
|
||||||
|
TraceObject processes = objects.getObjectByCanonicalPath(processesPath);
|
||||||
|
traceManager.activateObject(root);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
modelProvider.setTreeSelection(processesPath, EventOrigin.USER_GENERATED);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(processes, traceManager.getCurrentObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testElementSelectionActivatesObject() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject processes =
|
||||||
|
objects.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Processes"));
|
||||||
|
TraceObject process0 = processes.getElement(0, 0).getChild();
|
||||||
|
traceManager.activateObject(processes);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
assertTrue(modelProvider.elementsTablePanel.trySelect(process0));
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(process0, traceManager.getCurrentObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAttributeSelectionActivatesObject() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject root = objects.getRootObject();
|
||||||
|
TraceObject processes = root.getAttribute(0, "Processes").getChild();
|
||||||
|
traceManager.activateObject(root);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
assertTrue(modelProvider.attributesTablePanel.trySelect(processes));
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(processes, traceManager.getCurrentObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObjectActivationSelectsTree() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject root = objects.getRootObject();
|
||||||
|
TraceObject process0 =
|
||||||
|
objects.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Processes[0]"));
|
||||||
|
|
||||||
|
traceManager.activateObject(root);
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals(root, modelProvider.getTreeSelection().getChild());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: Have to skip a level, lest is select the child in the attributes pane instead
|
||||||
|
*/
|
||||||
|
traceManager.activateObject(process0);
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals(process0, modelProvider.getTreeSelection().getChild());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObjectActivationParentDoesNothing() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject root = objects.getRootObject();
|
||||||
|
TraceObject processes = root.getAttribute(0, "Processes").getChild();
|
||||||
|
|
||||||
|
traceManager.activateObject(processes);
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals(processes, modelProvider.getTreeSelection().getChild());
|
||||||
|
|
||||||
|
// TODO: Is this the desired behavior?
|
||||||
|
traceManager.activateObject(root);
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals(processes, modelProvider.getTreeSelection().getChild());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObjectActivationSiblingSelectsTree() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject thread0 =
|
||||||
|
objects.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Processes[0].Threads[0]"));
|
||||||
|
TraceObject thread1 =
|
||||||
|
objects.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Processes[0].Threads[1]"));
|
||||||
|
|
||||||
|
modelProvider.setShowHidden(true);
|
||||||
|
traceManager.activateObject(thread0);
|
||||||
|
traceManager.activateSnap(1);
|
||||||
|
waitForTasks();
|
||||||
|
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[0]._self"));
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
traceManager.activateObject(thread1);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[0]._next"),
|
||||||
|
modelProvider.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObjectActivationSelectsElement() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObjectKeyPath processesPath = TraceObjectKeyPath.parse("Processes");
|
||||||
|
TraceObject processes = objects.getObjectByCanonicalPath(processesPath);
|
||||||
|
TraceObject process0 = processes.getElement(0, 0).getChild();
|
||||||
|
traceManager.activateObject(processes);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: It's interesting that activating a parent then a child produces a different end
|
||||||
|
* result than activating the child directly.
|
||||||
|
*/
|
||||||
|
traceManager.activateObject(process0);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
assertEquals(processesPath, modelProvider.getPath());
|
||||||
|
assertEquals(process0,
|
||||||
|
modelProvider.elementsTablePanel.getSelectedItem().getValue().getChild());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testObjectActivationSelectsAttribute() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject root = objects.getRootObject();
|
||||||
|
TraceObject processes = root.getAttribute(0, "Processes").getChild();
|
||||||
|
traceManager.activateObject(root);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: It's interesting that activating a parent then a child produces a different end
|
||||||
|
* result than activating the child directly.
|
||||||
|
*/
|
||||||
|
traceManager.activateObject(processes);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
assertEquals(TraceObjectKeyPath.of(), modelProvider.getPath());
|
||||||
|
assertEquals(processes, modelProvider.attributesTablePanel.getSelectedItem().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TraceThread populateThread0Stack() {
|
||||||
|
TraceObjectManager objects = tb.trace.getObjectManager();
|
||||||
|
TraceObject threadObj0 =
|
||||||
|
objects.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Processes[0].Threads[0]"));
|
||||||
|
TraceThread thread0 = threadObj0.queryInterface(TraceObjectThread.class);
|
||||||
|
createStack(threadObj0);
|
||||||
|
return thread0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFrameActivationSelectsSibling() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceThread thread0 = populateThread0Stack();
|
||||||
|
|
||||||
|
traceManager.activate(DebuggerCoordinates.NOWHERE.thread(thread0).frame(0));
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[0].Stack[0]"),
|
||||||
|
modelProvider.getPath());
|
||||||
|
|
||||||
|
traceManager.activateFrame(1);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[0].Stack[1]"),
|
||||||
|
modelProvider.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFrameActivationSelectsElement() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceThread thread0 = populateThread0Stack();
|
||||||
|
TraceObjectKeyPath stackPath = TraceObjectKeyPath.parse("Processes[0].Threads[0].Stack");
|
||||||
|
|
||||||
|
traceManager.activateThread(thread0);
|
||||||
|
waitForSwing();
|
||||||
|
modelProvider.setPath(stackPath);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
// Test 1 then 0, because 0 is default
|
||||||
|
traceManager.activateFrame(1);
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals(stackPath, modelProvider.getPath());
|
||||||
|
assertEquals(stackPath.index(1),
|
||||||
|
modelProvider.elementsTablePanel.getSelectedItem().getValue().getCanonicalPath());
|
||||||
|
|
||||||
|
traceManager.activateFrame(0);
|
||||||
|
waitForTasks();
|
||||||
|
assertEquals(stackPath, modelProvider.getPath());
|
||||||
|
assertEquals(stackPath.index(0),
|
||||||
|
modelProvider.elementsTablePanel.getSelectedItem().getValue().getCanonicalPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThreadActivationSelectsSibling() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceThread thread0 =
|
||||||
|
tb.trace.getThreadManager().getLiveThreadByPath(1, "Processes[0].Threads[0]");
|
||||||
|
TraceThread thread1 =
|
||||||
|
tb.trace.getThreadManager().getLiveThreadByPath(1, "Processes[0].Threads[1]");
|
||||||
|
|
||||||
|
traceManager.activateThread(thread0);
|
||||||
|
traceManager.activateSnap(1);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[0]"), modelProvider.getPath());
|
||||||
|
|
||||||
|
traceManager.activateThread(thread1);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[1]"), modelProvider.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThreadActivationSelectsElement() throws Throwable {
|
||||||
|
createTraceAndPopulateObjects();
|
||||||
|
TraceThread thread0 =
|
||||||
|
tb.trace.getThreadManager().getLiveThreadByPath(1, "Processes[0].Threads[0]");
|
||||||
|
TraceThread thread1 =
|
||||||
|
tb.trace.getThreadManager().getLiveThreadByPath(1, "Processes[0].Threads[1]");
|
||||||
|
TraceObjectKeyPath threadsPath = TraceObjectKeyPath.parse("Processes[0].Threads");
|
||||||
|
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
traceManager.activateSnap(1);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
modelProvider.setPath(threadsPath);
|
||||||
|
waitForTasks();
|
||||||
|
|
||||||
|
// Testing 1 then 0, because 0 is default
|
||||||
|
traceManager.activateThread(thread1);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(threadsPath, modelProvider.getPath());
|
||||||
|
assertEquals(threadsPath.index(1),
|
||||||
|
modelProvider.elementsTablePanel.getSelectedItem().getValue().getCanonicalPath());
|
||||||
|
|
||||||
|
traceManager.activateThread(thread0);
|
||||||
|
waitForSwing();
|
||||||
|
assertEquals(threadsPath, modelProvider.getPath());
|
||||||
|
assertEquals(threadsPath.index(0),
|
||||||
|
modelProvider.elementsTablePanel.getSelectedItem().getValue().getCanonicalPath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ public class DebuggerStaticMappingProviderTest extends AbstractGhidraHeadedDebug
|
|||||||
// Select and remove the first 2 via the action
|
// Select and remove the first 2 via the action
|
||||||
// NOTE: I'm not responsible for making the transaction here. The UI should do it.
|
// NOTE: I'm not responsible for making the transaction here. The UI should do it.
|
||||||
mappingsProvider.mappingTable.getSelectionModel().setSelectionInterval(0, 1);
|
mappingsProvider.mappingTable.getSelectionModel().setSelectionInterval(0, 1);
|
||||||
performAction(mappingsProvider.actionRemove);
|
performEnabledAction(mappingsProvider, mappingsProvider.actionRemove, true);
|
||||||
waitForDomainObject(tb.trace);
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
// Now, check that only the final one remains
|
// Now, check that only the final one remains
|
||||||
|
@ -291,7 +291,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLiveAddValuesThenActivatePopulatesPanel() throws Exception {
|
public void testLiveAddValuesThenActivatePopulatesPanel() throws Throwable {
|
||||||
TraceRecorder recorder = recordAndWaitSync();
|
TraceRecorder recorder = recordAndWaitSync();
|
||||||
traceManager.openTrace(recorder.getTrace());
|
traceManager.openTrace(recorder.getTrace());
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
@ -725,8 +725,8 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
|||||||
traceManager.activateSnap(1);
|
traceManager.activateSnap(1);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertEquals(1, registersProvider.current.getSnap().longValue());
|
assertEquals(1, registersProvider.current.getSnap());
|
||||||
assertEquals(0, cloned.current.getSnap().longValue()); // TODO: Action to toggle snap tracking?
|
assertEquals(0, cloned.current.getSnap()); // TODO: Action to toggle snap tracking?
|
||||||
|
|
||||||
// NB, can't activate "null" trace. Manager ignores it.
|
// NB, can't activate "null" trace. Manager ignores it.
|
||||||
traceManager.closeTrace(tb.trace);
|
traceManager.closeTrace(tb.trace);
|
||||||
|
@ -566,6 +566,8 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||||||
@Test
|
@Test
|
||||||
public void testEditMemoryTarget() throws Throwable {
|
public void testEditMemoryTarget() throws Throwable {
|
||||||
WatchRow row = prepareTestEditTarget("*:8 r0");
|
WatchRow row = prepareTestEditTarget("*:8 r0");
|
||||||
|
// Wait for the async reads to settle.
|
||||||
|
waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress()));
|
||||||
|
|
||||||
row.setRawValueString("0x1234");
|
row.setRawValueString("0x1234");
|
||||||
retryVoid(() -> {
|
retryVoid(() -> {
|
||||||
|
@ -1293,11 +1293,17 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe
|
|||||||
}
|
}
|
||||||
waitForDomainObject(trace);
|
waitForDomainObject(trace);
|
||||||
|
|
||||||
|
waitOn(mappingService.changesSettled());
|
||||||
|
waitOn(breakpointService.changesSettled());
|
||||||
|
|
||||||
// Sanity
|
// Sanity
|
||||||
assertLogicalBreakpointForMappedSoftwareBreakpoint(trace);
|
assertLogicalBreakpointForMappedSoftwareBreakpoint(trace);
|
||||||
|
|
||||||
expectMappingChange(() -> undo(trace));
|
expectMappingChange(() -> undo(trace));
|
||||||
|
|
||||||
|
waitOn(mappingService.changesSettled());
|
||||||
|
waitOn(breakpointService.changesSettled());
|
||||||
|
|
||||||
// NB. The bookmark remains
|
// NB. The bookmark remains
|
||||||
LogicalBreakpoint one = Unique.assertOne(breakpointService.getAllBreakpoints());
|
LogicalBreakpoint one = Unique.assertOne(breakpointService.getAllBreakpoints());
|
||||||
assertTrue(one.getTraceBreakpoints().isEmpty());
|
assertTrue(one.getTraceBreakpoints().isEmpty());
|
||||||
|
@ -138,20 +138,20 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
|
|||||||
@Test
|
@Test
|
||||||
public void testWriteEmuMemoryAfterStep() throws Throwable {
|
public void testWriteEmuMemoryAfterStep() throws Throwable {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
|
||||||
|
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
// NB. TraceManager should automatically activate the first thread
|
// NB. TraceManager should automatically activate the first thread
|
||||||
TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
|
TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
|
||||||
AsyncPcodeExecutor<byte[]> executor =
|
AsyncPcodeExecutor<byte[]> executor =
|
||||||
TracePcodeUtils.executorForCoordinates(
|
TracePcodeUtils.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
|
||||||
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0,
|
|
||||||
null));
|
|
||||||
|
|
||||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||||
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
||||||
executor.executeSleighLine("pc = 0x00400000");
|
executor.executeSleighLine("pc = 0x00400000");
|
||||||
}
|
}
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
|
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
|
||||||
@ -163,7 +163,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
|
|||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
DebuggerCoordinates current = traceManager.getCurrent();
|
DebuggerCoordinates current = traceManager.getCurrent();
|
||||||
assertEquals(0, current.getSnap().longValue()); // Chain edits, don't source from scratch
|
assertEquals(0, current.getSnap()); // Chain edits, don't source from scratch
|
||||||
long snap = current.getViewSnap();
|
long snap = current.getViewSnap();
|
||||||
assertTrue(DBTraceUtils.isScratch(snap));
|
assertTrue(DBTraceUtils.isScratch(snap));
|
||||||
|
|
||||||
@ -175,21 +175,21 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
|
|||||||
@Test
|
@Test
|
||||||
public void testWriteEmuRegisterAfterStep() throws Throwable {
|
public void testWriteEmuRegisterAfterStep() throws Throwable {
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE);
|
||||||
|
|
||||||
TraceThread thread;
|
TraceThread thread;
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
// NB. TraceManager should automatically activate the first thread
|
// NB. TraceManager should automatically activate the first thread
|
||||||
thread = tb.getOrAddThread("Threads[0]", 0);
|
thread = tb.getOrAddThread("Threads[0]", 0);
|
||||||
AsyncPcodeExecutor<byte[]> executor =
|
AsyncPcodeExecutor<byte[]> executor =
|
||||||
TracePcodeUtils.executorForCoordinates(
|
TracePcodeUtils.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
|
||||||
DebuggerCoordinates.all(tb.trace, null, thread, null, TraceSchedule.ZERO, 0,
|
|
||||||
null));
|
|
||||||
|
|
||||||
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
|
||||||
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
asm.assemble(tb.addr(0x00400000), "imm r0,#123");
|
||||||
executor.executeSleighLine("pc = 0x00400000");
|
executor.executeSleighLine("pc = 0x00400000");
|
||||||
}
|
}
|
||||||
|
traceManager.activateTrace(tb.trace);
|
||||||
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
|
TraceSchedule step1 = TraceSchedule.parse("0:t0-1");
|
||||||
@ -201,7 +201,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
|
|||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
DebuggerCoordinates current = traceManager.getCurrent();
|
DebuggerCoordinates current = traceManager.getCurrent();
|
||||||
assertEquals(0, current.getSnap().longValue()); // Chain edits, don't source from scratch
|
assertEquals(0, current.getSnap()); // Chain edits, don't source from scratch
|
||||||
long snap = current.getViewSnap();
|
long snap = current.getViewSnap();
|
||||||
assertTrue(DBTraceUtils.isScratch(snap));
|
assertTrue(DBTraceUtils.isScratch(snap));
|
||||||
|
|
||||||
|
@ -133,12 +133,12 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
|||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertNull(traceManager.getCurrentTrace());
|
assertNull(traceManager.getCurrentTrace());
|
||||||
assertEquals(thread, traceManager.getCurrentThreadFor(tb.trace));
|
assertEquals(thread, traceManager.getCurrentFor(tb.trace).getThread());
|
||||||
|
|
||||||
traceManager.closeTrace(tb.trace);
|
traceManager.closeTrace(tb.trace);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertNull(traceManager.getCurrentThreadFor(tb.trace));
|
assertNull(traceManager.getCurrentFor(tb.trace).getThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -159,13 +159,13 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetCurrentDebuggerCoordinates() throws Throwable {
|
public void testGetCurrentDebuggerCoordinates() throws Throwable {
|
||||||
assertEquals(DebuggerCoordinates.NOWHERE, flat.getCurrentDebuggerCoordinates());
|
assertSame(DebuggerCoordinates.NOWHERE, flat.getCurrentDebuggerCoordinates());
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
traceManager.activateTrace(tb.trace);
|
traceManager.activateTrace(tb.trace);
|
||||||
|
|
||||||
assertEquals(DebuggerCoordinates.all(tb.trace, null, null, tb.trace.getProgramView(),
|
assertEquals(DebuggerCoordinates.NOWHERE.trace(tb.trace),
|
||||||
TraceSchedule.parse("0"), 0, null), flat.getCurrentDebuggerCoordinates());
|
flat.getCurrentDebuggerCoordinates());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -318,8 +318,8 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertEquals(thread, traceManager.getCurrentThread());
|
assertEquals(thread, traceManager.getCurrentThread());
|
||||||
|
|
||||||
flat.activateThread(null); // Ignored by manager
|
flat.activateThread(null);
|
||||||
assertEquals(thread, traceManager.getCurrentThread());
|
assertNull(traceManager.getCurrentThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -820,7 +820,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
TraceRecorder recorder = record(mb.testProcess1);
|
TraceRecorder recorder = record(mb.testProcess1);
|
||||||
waitRecorder(recorder);
|
waitRecorder(recorder);
|
||||||
|
|
||||||
mb.testModel.requestFocus(mb.testThread2);
|
waitOn(mb.testModel.requestFocus(mb.testThread2));
|
||||||
waitRecorder(recorder);
|
waitRecorder(recorder);
|
||||||
|
|
||||||
assertEquals(mb.testThread2, flat.getTargetFocus(recorder.getTrace()));
|
assertEquals(mb.testThread2, flat.getTargetFocus(recorder.getTrace()));
|
||||||
@ -849,7 +849,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
TraceRecorder recorder = record(mb.testProcess1);
|
TraceRecorder recorder = record(mb.testProcess1);
|
||||||
recorder.requestFocus(mb.testThread2);
|
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||||
waitRecorder(recorder);
|
waitRecorder(recorder);
|
||||||
|
|
||||||
assertTrue(step.apply(flat));
|
assertTrue(step.apply(flat));
|
||||||
@ -899,7 +899,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
TraceRecorder recorder = record(mb.testProcess1);
|
TraceRecorder recorder = record(mb.testProcess1);
|
||||||
recorder.requestFocus(mb.testThread2);
|
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||||
waitRecorder(recorder);
|
waitRecorder(recorder);
|
||||||
|
|
||||||
assertTrue(resume.apply(flat));
|
assertTrue(resume.apply(flat));
|
||||||
@ -933,7 +933,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> interrupt() {
|
public CompletableFuture<Void> interrupt() {
|
||||||
observedThread = this;
|
observedThread = this;
|
||||||
return super.resume();
|
return super.interrupt();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -942,7 +942,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
TraceRecorder recorder = record(mb.testProcess1);
|
TraceRecorder recorder = record(mb.testProcess1);
|
||||||
recorder.requestFocus(mb.testThread2);
|
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||||
waitRecorder(recorder);
|
waitRecorder(recorder);
|
||||||
|
|
||||||
assertTrue(interrupt.apply(flat));
|
assertTrue(interrupt.apply(flat));
|
||||||
@ -976,7 +976,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> kill() {
|
public CompletableFuture<Void> kill() {
|
||||||
observedThread = this;
|
observedThread = this;
|
||||||
return super.resume();
|
return super.kill();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -985,7 +985,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
TraceRecorder recorder = record(mb.testProcess1);
|
TraceRecorder recorder = record(mb.testProcess1);
|
||||||
recorder.requestFocus(mb.testThread2);
|
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||||
waitRecorder(recorder);
|
waitRecorder(recorder);
|
||||||
|
|
||||||
assertTrue(kill.apply(flat));
|
assertTrue(kill.apply(flat));
|
||||||
@ -1026,7 +1026,7 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerGUITest {
|
|||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
TraceRecorder recorder = record(mb.testProcess1);
|
TraceRecorder recorder = record(mb.testProcess1);
|
||||||
recorder.requestFocus(mb.testThread2);
|
assertTrue(waitOn(recorder.requestFocus(mb.testThread2)));
|
||||||
waitRecorder(recorder);
|
waitRecorder(recorder);
|
||||||
|
|
||||||
assertEquals("Response to cmd", executeCapture.apply(flat, "cmd"));
|
assertEquals("Response to cmd", executeCapture.apply(flat, "cmd"));
|
||||||
|
@ -68,8 +68,8 @@ public class TestTargetSession extends DefaultTargetModelRoot
|
|||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> requestFocus(TargetObject obj) {
|
public CompletableFuture<Void> requestFocus(TargetObject obj) {
|
||||||
return model.gateFuture(getModel().future(null).thenAccept(__ -> {
|
return model.gateFuture(getModel().future(null).thenAccept(__ -> {
|
||||||
changeAttributes(List.of(), List.of(), Map.of(FOCUS_ATTRIBUTE_NAME, obj //
|
changeAttributes(List.of(), List.of(), Map.of(FOCUS_ATTRIBUTE_NAME, obj),
|
||||||
), "Focus requested");
|
"Focus requested");
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,10 +205,8 @@ public class DBTraceObjectStack implements TraceObjectStack, DBTraceObjectInterf
|
|||||||
return doGetFrame(level);
|
return doGetFrame(level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
try (LockHold hold = object.getTrace().lockRead()) {
|
||||||
try (LockHold hold = object.getTrace().lockRead()) {
|
return doGetFrame(level);
|
||||||
return doGetFrame(level);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,6 +327,9 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected TraceObjectKeyPath doGetCanonicalPath() {
|
protected TraceObjectKeyPath doGetCanonicalPath() {
|
||||||
|
if (triple == null || triple.parent == null) {
|
||||||
|
return TraceObjectKeyPath.of();
|
||||||
|
}
|
||||||
return triple.parent.getCanonicalPath().extend(triple.key);
|
return triple.parent.getCanonicalPath().extend(triple.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user