GP-3018: Navigate/activate by double-click. Various related fixes.
|
@ -71,7 +71,7 @@ public class DebugSystemObjectsImpl1 implements DebugSystemObjectsInternal {
|
|||
@Override
|
||||
public void setCurrentThreadId(DebugThreadId id) {
|
||||
HRESULT hr = jnaSysobj.SetCurrentThreadId(new ULONG(id.id));
|
||||
if (!hr.equals(COMUtilsExtra.E_UNEXPECTED)) {
|
||||
if (!hr.equals(COMUtilsExtra.E_UNEXPECTED) && !hr.equals(COMUtilsExtra.E_NOINTERFACE)) {
|
||||
COMUtils.checkRC(hr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ public interface DbgModelSelectableObject extends DbgModelTargetObject {
|
|||
}
|
||||
if (this instanceof DbgModelTargetStackFrame) {
|
||||
DbgModelTargetStackFrame tf = (DbgModelTargetStackFrame) this;
|
||||
TargetObject ref = tf.getThread();
|
||||
TargetObject ref = tf.getParentThread();
|
||||
if (ref instanceof DbgModelTargetThread) {
|
||||
DbgModelTargetThread tt = (DbgModelTargetThread) ref;
|
||||
DbgThread thread = tt.getThread();
|
||||
|
|
|
@ -18,13 +18,10 @@ package agent.dbgeng.model.iface2;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import agent.dbgeng.manager.DbgEventsListenerAdapter;
|
||||
import agent.dbgeng.manager.DbgStackFrame;
|
||||
import agent.dbgeng.manager.*;
|
||||
import agent.dbgeng.manager.impl.DbgManagerImpl;
|
||||
import agent.dbgeng.manager.impl.DbgThreadImpl;
|
||||
import agent.dbgeng.model.iface1.DbgModelSelectableObject;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
|
@ -51,7 +48,8 @@ public interface DbgModelTargetStackFrame extends //
|
|||
@Override
|
||||
public default CompletableFuture<Void> setActive() {
|
||||
DbgManagerImpl manager = getManager();
|
||||
DbgThreadImpl thread = manager.getCurrentThread();
|
||||
DbgModelTargetThread targetThread = getParentThread();
|
||||
DbgThread thread = targetThread.getThread();
|
||||
String name = this.getName();
|
||||
String stripped = name.substring(1, name.length() - 1);
|
||||
int index = Integer.decode(stripped);
|
||||
|
@ -111,10 +109,6 @@ public interface DbgModelTargetStackFrame extends //
|
|||
|
||||
public void setFrame(DbgStackFrame frame);
|
||||
|
||||
public TargetObject getThread();
|
||||
|
||||
public Address getPC();
|
||||
|
||||
public DbgModelTargetProcess getProcess();
|
||||
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import agent.dbgeng.manager.*;
|
|||
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
|
||||
import agent.dbgeng.model.iface2.*;
|
||||
import ghidra.dbg.target.TargetFocusScope;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.schema.*;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
@ -79,8 +78,6 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
|
|||
return PathUtils.makeKey(indexFrame(frame));
|
||||
}
|
||||
|
||||
protected final DbgModelTargetThread thread;
|
||||
|
||||
protected DbgStackFrame frame;
|
||||
protected Address pc;
|
||||
protected String func;
|
||||
|
@ -97,7 +94,6 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
|
|||
DbgStackFrame frame) {
|
||||
super(stack.getModel(), stack, keyFrame(frame), "StackFrame");
|
||||
this.getModel().addModelObject(frame, this);
|
||||
this.thread = thread;
|
||||
this.pc = getModel().getAddressSpace("ram").getAddress(-1);
|
||||
|
||||
changeAttributes(List.of(), List.of(), Map.of( //
|
||||
|
@ -162,21 +158,11 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
|
|||
), "Refreshed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetObject getThread() {
|
||||
return thread.getParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getPC() {
|
||||
return pc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DbgModelTargetProcess getProcess() {
|
||||
return ((DbgModelTargetThreadImpl) thread).getProcess();
|
||||
}
|
||||
|
||||
/*
|
||||
public void invalidateRegisterCaches() {
|
||||
listeners.fire.invalidateCacheRequested(this);
|
||||
|
|
|
@ -264,7 +264,8 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
|
|||
* or be used as an example for other implementations.
|
||||
*/
|
||||
if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) {
|
||||
throw new DebuggerIllegalArgumentException("Can only focus a successor of the scope");
|
||||
throw new DebuggerIllegalArgumentException(
|
||||
"Can only activate a successor of the scope");
|
||||
}
|
||||
TargetObject cur = obj;
|
||||
while (cur != null) {
|
||||
|
|
|
@ -75,8 +75,11 @@ public class GdbModelTargetStackFrame
|
|||
|
||||
this.registers = new GdbModelTargetStackFrameRegisterContainer(this);
|
||||
|
||||
changeAttributes(List.of(), List.of(registers), Map.of( //
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame)),
|
||||
changeAttributes(List.of(),
|
||||
List.of(
|
||||
registers),
|
||||
Map.of(
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame)),
|
||||
"Initialized");
|
||||
setFrame(frame);
|
||||
}
|
||||
|
@ -99,14 +102,14 @@ public class GdbModelTargetStackFrame
|
|||
this.func = frame.getFunction();
|
||||
// TODO: module? "from"
|
||||
|
||||
changeAttributes(List.of(), List.of( //
|
||||
registers //
|
||||
), Map.of( //
|
||||
PC_ATTRIBUTE_NAME, pc, //
|
||||
FUNC_ATTRIBUTE_NAME, func, //
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame), //
|
||||
VALUE_ATTRIBUTE_NAME, pc //
|
||||
), "Refreshed");
|
||||
changeAttributes(List.of(),
|
||||
List.of(
|
||||
registers),
|
||||
Map.of(
|
||||
PC_ATTRIBUTE_NAME, pc,
|
||||
FUNC_ATTRIBUTE_NAME, func,
|
||||
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame)),
|
||||
"Refreshed");
|
||||
}
|
||||
|
||||
protected void invalidateRegisterCaches() {
|
||||
|
|
|
@ -35,6 +35,18 @@
|
|||
because it descends from an inferior or process. In almost every case, the selection directly
|
||||
or indirectly determines the set of valid or enabled actions.</P>
|
||||
|
||||
<P>The tree will display the active object, e.g., the current thread, and its ancestors in
|
||||
<B>bold</B> font. By default, this is also the selected object. To activate a different object,
|
||||
double-click it or select it and press ENTER. This will command the debugger and the rest of
|
||||
the UI to make that object the current object. For example, double-clicking the 2nd thread
|
||||
would be equivalent to <CODE>thread 2</CODE> in GDB, or <CODE>~2s</CODE> in WinDbg. The actions
|
||||
apply to the <EM>selected</EM> object, which may differ from the <EM>active</EM> object. For
|
||||
example, suppose Thread 1 is active. If you click "Step Into," it will command the debugger to
|
||||
step Thread 1. If you select (but do not activate) Thread 2 and click "Step Into," it will
|
||||
command the debugger to step Thread 2, even though Thread 1 is still the active thread. Likely,
|
||||
Thread 2 will become active during the Step Into command, but that behavior is determined by
|
||||
the model implementation.</P>
|
||||
|
||||
<P>The application of these special properties to each object to determine its behavior and
|
||||
relevant actions allows all objects to be treated generically. This feature has several
|
||||
powerful implications. All objects may be represented in many ways, and one representation may
|
||||
|
@ -170,6 +182,12 @@
|
|||
"help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html">Breakpoint
|
||||
Marker</A> actions from the disassembly listings.</P>
|
||||
|
||||
<H3><A name="toggle_option"></A><IMG alt="" src="images/system-switch-user.png"> Toggle
|
||||
Option</H3>
|
||||
|
||||
<P>Toggle an object. This may apply to a breakpoint or to configuration options provided in the
|
||||
model tree.</P>
|
||||
|
||||
<H2>Actions for Trace Management</H2>
|
||||
|
||||
<P>The following actions manage target tracing. Note that many other windows are not usable
|
||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
|
@ -25,13 +25,15 @@
|
|||
<P>The stack window displays the current trace's execution stack, as unwound and reported by
|
||||
the target. Not all debuggers will unwind the stack, in which case, this window displays only
|
||||
the innermost frame. When emulation is used to generate the current machine state, only a
|
||||
single synthetic frame is shown. Level 0 always refers to the innermost frame, and each
|
||||
incremental level refers to the next caller in the chain — most of the time. The current
|
||||
frame comprises one element of the tool's current "coordinates." Selecting a frame changes
|
||||
those coordinates, potentially causing other windows to display different information. Namely,
|
||||
the <A href="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A>
|
||||
window will show registers for the current frame, assuming they can be retrieved. The Listings
|
||||
may also navigate to the current frame's program counter.</P>
|
||||
single synthetic frame is shown. See the <A href="#unwind_stack">Unwind Stack</A> action for an
|
||||
alternative mechanism that unwinds using Ghidra's program databases and works with emulation.
|
||||
Level 0 always refers to the innermost frame, and each incremental level refers to the next
|
||||
caller in the chain — most of the time. The current frame comprises one element of the
|
||||
tool's current "coordinates." Double-clicking a frame changes those coordinates, potentially
|
||||
causing other windows to display different information. Namely, the <A href=
|
||||
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
|
||||
show registers for the current frame, assuming they can be retrieved. The Listings may also
|
||||
navigate to the current frame's program counter.</P>
|
||||
|
||||
<H2>Table Columns</H2>
|
||||
|
||||
|
@ -41,8 +43,7 @@
|
|||
<LI>Level - the level of the frame, counting 0-up starting at the innermost frame.</LI>
|
||||
|
||||
<LI>PC - the address of the instruction to execute next, or upon return of the callee for
|
||||
non-0 frames. Different platforms may have different subtleties in how they report PC.
|
||||
Double-clicking this field will navigate to the address.</LI>
|
||||
non-0 frames. Different platforms may have different subtleties in how they report PC.</LI>
|
||||
|
||||
<LI>Function - the name of the containing function, if Ghidra has the corresponding module
|
||||
image imported and analyzed.</LI>
|
||||
|
@ -66,7 +67,8 @@
|
|||
and unwind it in the same manner. This proceeds until analysis fails or the stack segment is
|
||||
exhausted. For best results, ensure you have imported and opened the Ghidra program database
|
||||
for every module, or at least the subset you expect to see in your stack. To view the results,
|
||||
navigate to or follow the stack pointer in a Dynamic Listing.</P>
|
||||
navigate to or follow the stack pointer in a Dynamic Listing. The Stack window <EM>does
|
||||
not</EM> display Ghidra's unwinding results.</P>
|
||||
|
||||
<TABLE width="100%">
|
||||
<TBODY>
|
||||
|
@ -101,8 +103,8 @@
|
|||
<CODE>posOff_</CODE> for positive offsets). They represent unused or unknown entries.</LI>
|
||||
</UL>
|
||||
|
||||
<P>The frame entries are not automatically updated when a function's frame changes in a program
|
||||
database. To update the unwind after changing a function's stack frame, you must unwind
|
||||
again.</P>
|
||||
<P>The frame entries are <EM>not</EM> automatically updated when a function's frame changes in
|
||||
a program database. To update the unwind after changing a function's stack frame, you must
|
||||
unwind again.</P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 8.8 KiB |
|
@ -53,8 +53,8 @@
|
|||
|
||||
<H2>Navigating Threads</H2>
|
||||
|
||||
<P>Selecting a thread in the table will navigate to (or "activate" or "focus") that thread.
|
||||
Windows which are sensitive to the current thread will update. Notably, the <A href=
|
||||
<P>Double-clicking a thread in the table will navigate to (or "activate" or "focus") that
|
||||
thread. Windows which are sensitive to the current thread will update. Notably, the <A href=
|
||||
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
|
||||
display the activated thread's register values. <A href=
|
||||
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Listing</A> windows with
|
||||
|
@ -88,15 +88,15 @@
|
|||
|
||||
<H2>Actions</H2>
|
||||
|
||||
<H3><A name="sync_focus"></A>Synchronize Trace and Target Focus</H3>
|
||||
<H3><A name="sync_target"></A>Synchronize Trace and Target Activation</H3>
|
||||
|
||||
<P>This toggle is always available and is enabled by default. While enabled, any changes in
|
||||
navigation coordinates are translated, to the extent possible, and sent to the connected
|
||||
debugger. This may, for example, issue <CODE>thread</CODE> and/or <CODE>frame</CODE> commands
|
||||
to GDB so that commands typed into its CLI will refer to the same thread and frame as is
|
||||
focused in Ghidra. Conversely, any debugger events which indicate a change in focus are
|
||||
translated, to the extent possible, into navigation coordinates and activated in Ghidra. For
|
||||
example, if the user issues a <CODE>frame</CODE> command to the CLI of a GDB connection, then
|
||||
Ghidra will navigate to that same frame.</P>
|
||||
to GDB so that commands typed into its CLI will refer to the same thread and frame as is active
|
||||
in Ghidra. Conversely, any target events which indicate a change in activation are translated,
|
||||
to the extent possible, into navigation coordinates and activated in Ghidra. For example, if
|
||||
the user issues a <CODE>frame</CODE> command to the CLI of a GDB connection, then Ghidra will
|
||||
navigate to that same frame.</P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
|
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
@ -26,9 +26,9 @@
|
|||
snapshot per event recorded. Other windows often display the times of various events or use
|
||||
time ranges to describe lifespans of various records. Those times refer to the "snap," which is
|
||||
a 0-up counter of snapshot records. Thus, a snapshot is a collection of observations of a
|
||||
target's state, usually while suspended, along with any mark up. Selecting a snapshot navigates
|
||||
to the selected point in time. Note that browsing the past may prevent other windows from
|
||||
interacting with a live target.</P>
|
||||
target's state, usually while suspended, along with any mark up. Double-clicking a snapshot
|
||||
navigates to the selected point in time. Note that navigating to the past may change your <A
|
||||
href="help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control mode</A>.</P>
|
||||
|
||||
<H2>Table Columns</H2>
|
||||
|
||||
|
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
@ -1669,11 +1669,11 @@ public interface DebuggerResources {
|
|||
}
|
||||
}
|
||||
|
||||
interface SynchronizeFocusAction {
|
||||
String NAME = "Synchronize Focus";
|
||||
interface SynchronizeTargetAction {
|
||||
String NAME = "Synchronize Target Activation";
|
||||
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
|
||||
Icon ICON = ICON_SYNC;
|
||||
String HELP_ANCHOR = "sync_focus";
|
||||
String HELP_ANCHOR = "sync_target";
|
||||
|
||||
static ToggleActionBuilder builder(Plugin owner) {
|
||||
String ownerName = owner.getName();
|
||||
|
|
|
@ -101,6 +101,7 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
|||
|
||||
private Trace trace;
|
||||
private long snap;
|
||||
private TraceObject curObject;
|
||||
private Trace diffTrace;
|
||||
private long diffSnap;
|
||||
private ModelQuery query;
|
||||
|
@ -173,6 +174,23 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
|
|||
return snap;
|
||||
}
|
||||
|
||||
protected void currentObjectChanged() {
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void setCurrentObject(TraceObject curObject) {
|
||||
if (this.curObject == curObject) {
|
||||
return;
|
||||
}
|
||||
this.curObject = curObject;
|
||||
|
||||
currentObjectChanged();
|
||||
}
|
||||
|
||||
public TraceObject getCurrentObject() {
|
||||
return curObject;
|
||||
}
|
||||
|
||||
protected void diffTraceChanged() {
|
||||
refresh();
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
|||
table.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
fireCellActivated();
|
||||
}
|
||||
}
|
||||
|
@ -90,13 +90,16 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
|
|||
DebuggerCoordinates previous = current;
|
||||
this.current = coords;
|
||||
if (previous.getSnap() == current.getSnap() &&
|
||||
previous.getTrace() == current.getTrace()) {
|
||||
previous.getTrace() == current.getTrace() &&
|
||||
previous.getObject() == current.getObject()) {
|
||||
return;
|
||||
}
|
||||
tableModel.setDiffTrace(previous.getTrace());
|
||||
tableModel.setTrace(current.getTrace());
|
||||
tableModel.setDiffSnap(previous.getSnap());
|
||||
tableModel.setSnap(current.getSnap());
|
||||
// current object is used only for bolding
|
||||
tableModel.setCurrentObject(current.getObject());
|
||||
if (limitToSnap) {
|
||||
tableModel.setSpan(Lifespan.at(current.getSnap()));
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.gui.model;
|
|||
import java.util.*;
|
||||
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
|
||||
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
|
||||
|
@ -112,12 +113,20 @@ public class DebuggerModelPlugin extends Plugin {
|
|||
return Collections.unmodifiableList(disconnectedProviders);
|
||||
}
|
||||
|
||||
private void coordinatesActivated(DebuggerCoordinates current) {
|
||||
connectedProvider.coordinatesActivated(current);
|
||||
synchronized (disconnectedProviders) {
|
||||
for (DebuggerModelProvider p : disconnectedProviders) {
|
||||
p.coordinatesActivated(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof TraceActivatedPluginEvent) {
|
||||
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event;
|
||||
connectedProvider.coordinatesActivated(ev.getActiveCoordinates());
|
||||
if (event instanceof TraceActivatedPluginEvent ev) {
|
||||
coordinatesActivated(ev.getActiveCoordinates());
|
||||
}
|
||||
if (event instanceof TraceClosedPluginEvent) {
|
||||
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;
|
||||
|
|
|
@ -47,6 +47,7 @@ import ghidra.framework.plugintool.AutoConfigState;
|
|||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.*;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -155,7 +156,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
public boolean verify(JComponent input) {
|
||||
try {
|
||||
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
|
||||
setPath(path, pathField, EventOrigin.USER_GENERATED);
|
||||
setPath(path);
|
||||
objectsTreePanel.setSelectedKeyPaths(List.of(path));
|
||||
return true;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
|
@ -168,7 +170,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
ActionListener gotoPath = evt -> {
|
||||
try {
|
||||
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
|
||||
setPath(path, pathField, EventOrigin.USER_GENERATED);
|
||||
activatePath(path);
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
|
@ -238,20 +240,30 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
myActionContext = null;
|
||||
}
|
||||
contextChanged();
|
||||
|
||||
if (sel.size() != 1) {
|
||||
// TODO: Multiple paths? PathMatcher can do it, just have to parse
|
||||
// Just leave whatever was there.
|
||||
return;
|
||||
}
|
||||
TraceObjectValue value = sel.get(0).getValue();
|
||||
TraceObjectKeyPath path = value.getCanonicalPath();
|
||||
|
||||
// Prevent activation when selecting a link
|
||||
EventOrigin origin =
|
||||
value.isCanonical() ? evt.getEventOrigin() : EventOrigin.API_GENERATED;
|
||||
setPath(path, objectsTreePanel, origin);
|
||||
setPath(value.getCanonicalPath(), objectsTreePanel);
|
||||
});
|
||||
objectsTreePanel.tree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
activateObjectSelectedInTree();
|
||||
}
|
||||
}
|
||||
});
|
||||
objectsTreePanel.tree.tree().addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
activateObjectSelectedInTree();
|
||||
e.consume(); // lest is select the next row down
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
elementsTablePanel.addSelectionListener(evt -> {
|
||||
if (evt.getValueIsAdjusting()) {
|
||||
return;
|
||||
|
@ -278,9 +290,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
}
|
||||
TraceObject object = value.getChild();
|
||||
attributesTablePanel.setQuery(ModelQuery.attributesOf(object.getCanonicalPath()));
|
||||
if (value.isCanonical()) {
|
||||
activatePath(object.getCanonicalPath());
|
||||
}
|
||||
});
|
||||
attributesTablePanel.addSelectionListener(evt -> {
|
||||
if (evt.getValueIsAdjusting()) {
|
||||
|
@ -297,15 +306,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
myActionContext = null;
|
||||
}
|
||||
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.addCellActivationListener(elementActivationListener);
|
||||
|
@ -315,6 +315,19 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
attributesTablePanel.addSeekListener(seekListener);
|
||||
}
|
||||
|
||||
private void activateObjectSelectedInTree() {
|
||||
List<AbstractNode> sel = objectsTreePanel.getSelectedItems();
|
||||
if (sel.size() != 1) {
|
||||
// TODO: Multiple paths? PathMatcher can do it, just have to parse
|
||||
// Just leave whatever was there.
|
||||
return;
|
||||
}
|
||||
TraceObjectValue value = sel.get(0).getValue();
|
||||
if (value.getValue() instanceof TraceObject child) {
|
||||
activatePath(child.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
if (myActionContext != null) {
|
||||
|
@ -352,11 +365,10 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
if (!(row instanceof ObjectRow)) {
|
||||
if (!(row instanceof ObjectRow objectRow)) {
|
||||
return;
|
||||
}
|
||||
ObjectRow objectRow = (ObjectRow) row;
|
||||
setPath(objectRow.getTraceObject().getCanonicalPath());
|
||||
activatePath(objectRow.getTraceObject().getCanonicalPath());
|
||||
}
|
||||
|
||||
private void activatedAttributesTable() {
|
||||
|
@ -365,11 +377,10 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
return;
|
||||
}
|
||||
Object value = row.getValue();
|
||||
if (!(value instanceof TraceObject)) {
|
||||
if (!(value instanceof TraceObject object)) {
|
||||
return;
|
||||
}
|
||||
TraceObject object = (TraceObject) value;
|
||||
setPath(object.getCanonicalPath());
|
||||
activatePath(object.getCanonicalPath());
|
||||
}
|
||||
|
||||
private void activatedCloneWindow() {
|
||||
|
@ -419,7 +430,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
if (values.size() != 1) {
|
||||
return;
|
||||
}
|
||||
setPath(values.get(0).getChild().getCanonicalPath(), null, EventOrigin.USER_GENERATED);
|
||||
TraceObjectKeyPath canonicalPath = values.get(0).getChild().getCanonicalPath();
|
||||
setPath(canonicalPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -476,11 +488,15 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
elementsTablePanel.goToCoordinates(coords);
|
||||
attributesTablePanel.goToCoordinates(coords);
|
||||
|
||||
checkPath();
|
||||
|
||||
if (isClone) {
|
||||
return;
|
||||
}
|
||||
// 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)) {
|
||||
|
@ -490,14 +506,15 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
return;
|
||||
}
|
||||
if (findAsParent(object) != null) {
|
||||
checkPath();
|
||||
return;
|
||||
}
|
||||
TraceObjectKeyPath sibling = findAsSibling(object);
|
||||
if (sibling != null) {
|
||||
objectsTreePanel.setSelectedKeyPaths(List.of(sibling));
|
||||
setPath(sibling);
|
||||
}
|
||||
else {
|
||||
objectsTreePanel.setSelectedObject(object);
|
||||
setPath(object.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
|
@ -509,48 +526,52 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
|||
}
|
||||
|
||||
protected void activatePath(TraceObjectKeyPath path) {
|
||||
if (isClone) {
|
||||
return;
|
||||
}
|
||||
Trace trace = current.getTrace();
|
||||
if (trace != null) {
|
||||
TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path);
|
||||
if (object != null) {
|
||||
traceManager.activateObject(object);
|
||||
return;
|
||||
}
|
||||
object = trace.getObjectManager()
|
||||
.getObjectsByPath(Lifespan.at(current.getSnap()), path)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (object != null) {
|
||||
traceManager.activateObject(object);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setPath(TraceObjectKeyPath path, JComponent source, EventOrigin origin) {
|
||||
protected void setPath(TraceObjectKeyPath path, JComponent source) {
|
||||
if (Objects.equals(this.path, path) && getTreeSelection() != null) {
|
||||
return;
|
||||
}
|
||||
this.path = path;
|
||||
if (source != pathField) {
|
||||
pathField.setText(path.toString());
|
||||
}
|
||||
|
||||
if (source != objectsTreePanel) {
|
||||
setTreeSelection(path);
|
||||
}
|
||||
if (origin == EventOrigin.USER_GENERATED) {
|
||||
activatePath(path);
|
||||
}
|
||||
|
||||
pathField.setText(path.toString());
|
||||
objectsTreePanel.repaint();
|
||||
elementsTablePanel.setQuery(ModelQuery.elementsOf(path));
|
||||
attributesTablePanel.setQuery(ModelQuery.attributesOf(path));
|
||||
|
||||
checkPath();
|
||||
}
|
||||
|
||||
public void setPath(TraceObjectKeyPath path) {
|
||||
setPath(path, null);
|
||||
}
|
||||
|
||||
protected void checkPath() {
|
||||
if (objectsTreePanel.getNode(path) == null) {
|
||||
plugin.getTool().setStatusInfo("No such object at path " + path, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPath(TraceObjectKeyPath path) {
|
||||
setPath(path, null, EventOrigin.API_GENERATED);
|
||||
}
|
||||
|
||||
public TraceObjectKeyPath getPath() {
|
||||
return path;
|
||||
}
|
||||
|
|
|
@ -181,6 +181,8 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||
public interface ValueRow {
|
||||
String getKey();
|
||||
|
||||
TraceObject currentObject();
|
||||
|
||||
long currentSnap();
|
||||
|
||||
long previousSnap();
|
||||
|
@ -212,6 +214,8 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||
*/
|
||||
boolean isModified();
|
||||
|
||||
boolean isCurrent();
|
||||
|
||||
default <T> ValueAttribute<T> getAttribute(String attributeName, Class<T> type) {
|
||||
return new ValueAttribute<>(this, attributeName, type);
|
||||
}
|
||||
|
@ -250,6 +254,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||
return getSnap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraceObject currentObject() {
|
||||
return getCurrentObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long previousSnap() {
|
||||
return getTrace() == getDiffTrace() ? getDiffSnap() : getSnap();
|
||||
|
@ -313,6 +322,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||
public boolean isAttributeModified(String attributeName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected class ObjectRow extends AbstractValueRow {
|
||||
|
@ -366,6 +380,15 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||
public boolean isAttributeModified(String attributeName) {
|
||||
return isValueModified(getAttributeEntry(attributeName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrent() {
|
||||
TraceObject current = getCurrentObject();
|
||||
if (current == null) {
|
||||
return false;
|
||||
}
|
||||
return object.getCanonicalPath().isAncestor(current.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
|
||||
protected ValueRow rowForValue(TraceObjectValue value) {
|
||||
|
@ -375,46 +398,13 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
|
|||
return new PrimitiveRow(value);
|
||||
}
|
||||
|
||||
protected static class ColKey {
|
||||
protected record ColKey(String name, Class<?> type) {
|
||||
public static ColKey fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) {
|
||||
String name = attributeSchema.getName();
|
||||
Class<?> type =
|
||||
TraceValueObjectAttributeColumn.computeAttributeType(ctx, attributeSchema);
|
||||
return new ColKey(name, type);
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final Class<?> type;
|
||||
private final int hash;
|
||||
|
||||
public ColKey(String name, Class<?> type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.hash = Objects.hash(name, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ColKey)) {
|
||||
return false;
|
||||
}
|
||||
ColKey that = (ColKey) obj;
|
||||
if (!Objects.equals(this.name, that.name)) {
|
||||
return false;
|
||||
}
|
||||
if (this.type != that.type) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
static class AutoAttributeColumn<T> extends TraceValueObjectAttributeColumn<T> {
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package ghidra.app.plugin.core.debug.gui.model;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.*;
|
||||
|
@ -26,6 +25,7 @@ import javax.swing.JTree;
|
|||
import javax.swing.tree.TreePath;
|
||||
|
||||
import docking.widgets.tree.GTree;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.support.GTreeRenderer;
|
||||
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||
import docking.widgets.tree.support.GTreeSelectionListener;
|
||||
|
@ -34,7 +34,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
|||
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObjectKeyPath;
|
||||
import ghidra.trace.model.target.*;
|
||||
|
||||
public class ObjectsTreePanel extends JPanel {
|
||||
|
||||
|
@ -43,6 +43,21 @@ public class ObjectsTreePanel extends JPanel {
|
|||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
private boolean isOnCurrentPath(TraceObjectValue value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
return (value.getValue() instanceof TraceObject child && isOnCurrentPath(child));
|
||||
}
|
||||
|
||||
private boolean isOnCurrentPath(TraceObject object) {
|
||||
TraceObject cur = current.getObject();
|
||||
if (cur == null) {
|
||||
return false;
|
||||
}
|
||||
return object.getCanonicalPath().isAncestor(cur.getCanonicalPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
|
||||
boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||
|
@ -54,6 +69,7 @@ public class ObjectsTreePanel extends JPanel {
|
|||
|
||||
AbstractNode node = (AbstractNode) value;
|
||||
setForeground(getForegroundFor(tree, node.isModified(), selected));
|
||||
setFont(getFont(isOnCurrentPath(node.getValue())));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -68,8 +84,19 @@ public class ObjectsTreePanel extends JPanel {
|
|||
}
|
||||
}
|
||||
|
||||
static class ObjectGTree extends GTree {
|
||||
public ObjectGTree(GTreeNode root) {
|
||||
super(root);
|
||||
getJTree().setToggleClickCount(0);
|
||||
}
|
||||
|
||||
JTree tree() {
|
||||
return getJTree();
|
||||
}
|
||||
}
|
||||
|
||||
protected final ObjectTreeModel treeModel;
|
||||
protected final GTree tree;
|
||||
protected final ObjectGTree tree;
|
||||
|
||||
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
protected boolean limitToSnap = true;
|
||||
|
@ -83,7 +110,7 @@ public class ObjectsTreePanel extends JPanel {
|
|||
public ObjectsTreePanel() {
|
||||
super(new BorderLayout());
|
||||
treeModel = createModel();
|
||||
tree = new GTree(treeModel.getRoot());
|
||||
tree = new ObjectGTree(treeModel.getRoot());
|
||||
|
||||
tree.setCellRenderer(new ObjectsTreeRenderer());
|
||||
add(tree, BorderLayout.CENTER);
|
||||
|
@ -108,14 +135,14 @@ public class ObjectsTreePanel extends JPanel {
|
|||
}
|
||||
|
||||
public void goToCoordinates(DebuggerCoordinates coords) {
|
||||
// TODO: thread should probably become a TraceObject once we transition
|
||||
if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) {
|
||||
return;
|
||||
}
|
||||
DebuggerCoordinates previous = current;
|
||||
this.current = coords;
|
||||
if (previous.getSnap() == current.getSnap() &&
|
||||
previous.getTrace() == current.getTrace()) {
|
||||
previous.getTrace() == current.getTrace() &&
|
||||
previous.getObject() == current.getObject()) {
|
||||
return;
|
||||
}
|
||||
try (KeepTreeState keep = keepTreeState()) {
|
||||
|
@ -127,6 +154,7 @@ public class ObjectsTreePanel extends JPanel {
|
|||
treeModel.setSpan(Lifespan.at(current.getSnap()));
|
||||
}
|
||||
tree.filterChanged();
|
||||
// Repaint for bold current path is already going to happen
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,20 +238,6 @@ public class ObjectsTreePanel extends JPanel {
|
|||
tree.removeGTreeSelectionListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void addMouseListener(MouseListener l) {
|
||||
super.addMouseListener(l);
|
||||
// Is this a HACK?
|
||||
tree.addMouseListener(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removeMouseListener(MouseListener l) {
|
||||
super.removeMouseListener(l);
|
||||
// HACK?
|
||||
tree.removeMouseListener(l);
|
||||
}
|
||||
|
||||
public void setSelectionMode(int selectionMode) {
|
||||
tree.getSelectionModel().setSelectionMode(selectionMode);
|
||||
}
|
||||
|
@ -272,4 +286,30 @@ public class ObjectsTreePanel extends JPanel {
|
|||
public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths) {
|
||||
setSelectedKeyPaths(keyPaths, EventOrigin.API_GENERATED);
|
||||
}
|
||||
|
||||
public void expandCurrent() {
|
||||
TraceObject object = current.getObject();
|
||||
if (object == null) {
|
||||
return;
|
||||
}
|
||||
AbstractNode node = getNode(object.getCanonicalPath());
|
||||
TreePath parentPath = node.getTreePath().getParentPath();
|
||||
if (parentPath != null) {
|
||||
tree.expandPath(parentPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setSelectedObject(TraceObject object) {
|
||||
if (object == null) {
|
||||
tree.clearSelectionPaths();
|
||||
return;
|
||||
}
|
||||
AbstractNode node = getNode(object.getCanonicalPath());
|
||||
tree.addSelectionPath(node.getTreePath());
|
||||
}
|
||||
|
||||
public void selectCurrent() {
|
||||
setSelectedObject(current.getObject());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,17 @@ public class PathTableModel extends AbstractQueryTableModel<PathRow> {
|
|||
// Root is canonical
|
||||
return last == null || last.isCanonical();
|
||||
}
|
||||
|
||||
public boolean isCurrent() {
|
||||
TraceObject current = getCurrentObject();
|
||||
if (current == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(getValue() instanceof TraceObject child)) {
|
||||
return false;
|
||||
}
|
||||
return child.getCanonicalPath().isAncestor(current.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
|
||||
public PathTableModel(Plugin plugin) {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import docking.widgets.table.GTableCellRenderingData;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
|
||||
public class TracePathColumnRenderer<T> extends AbstractGColumnRenderer<T> {
|
||||
@Override
|
||||
public String getFilterString(T t, Settings settings) {
|
||||
return t == null ? "<null>" : t.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
PathRow row = (PathRow) data.getRowObject();
|
||||
if (row.isCurrent()) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -22,13 +22,21 @@ import ghidra.framework.plugintool.ServiceProvider;
|
|||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObjectValPath;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TracePathLastKeyColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
|
||||
private final TracePathColumnRenderer<String> renderer = new TracePathColumnRenderer<>();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Key";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<String> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(PathRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
|
|
|
@ -15,22 +15,51 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
import docking.widgets.table.GTableCellRenderingData;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TracePathLastLifespanColumn
|
||||
extends AbstractDynamicTableColumn<PathRow, Lifespan, Trace> {
|
||||
|
||||
private final class LastLifespanRenderer extends AbstractGColumnRenderer<Lifespan> {
|
||||
@Override
|
||||
public String getFilterString(Lifespan t, Settings settings) {
|
||||
return t == null ? "<null>" : t.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
PathRow row = (PathRow) data.getRowObject();
|
||||
if (row.isCurrent()) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private final LastLifespanRenderer renderer = new LastLifespanRenderer();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Life";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<Lifespan> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lifespan getValue(PathRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
|
|
|
@ -21,13 +21,21 @@ import ghidra.dbg.util.PathUtils;
|
|||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TracePathStringColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
|
||||
private final TracePathColumnRenderer<String> renderer = new TracePathColumnRenderer<>();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Path";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<String> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(PathRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
|
|
|
@ -50,6 +50,9 @@ public class TracePathValueColumn extends AbstractDynamicTableColumn<PathRow, Pa
|
|||
setText(row.getHtmlDisplay());
|
||||
setToolTipText(row.getToolTip());
|
||||
setForeground(getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
||||
if (row.isCurrent()) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.model.columns;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import docking.widgets.table.GTableCellRenderingData;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
|
||||
public class TraceValueColumnRenderer<T> extends AbstractGColumnRenderer<T> {
|
||||
@Override
|
||||
public String getFilterString(T t, Settings settings) {
|
||||
return t == null ? "<null>" : t.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
ValueRow row = (ValueRow) data.getRowObject();
|
||||
if (row.isCurrent()) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -23,8 +23,11 @@ import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
|||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, String, Trace> {
|
||||
private final TraceValueColumnRenderer<String> renderer = new TraceValueColumnRenderer<>();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Key";
|
||||
|
@ -36,6 +39,11 @@ public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, St
|
|||
return rowObject.getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<String> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<String> getComparator() {
|
||||
return TargetObjectKeyComparator.CHILD;
|
||||
|
|
|
@ -21,15 +21,22 @@ import ghidra.docking.settings.Settings;
|
|||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Lifespan.LifeSet;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
|
||||
public class TraceValueLifeColumn
|
||||
extends AbstractDynamicTableColumn<ValueRow, LifeSet, Trace> {
|
||||
private final TraceValueColumnRenderer<LifeSet> renderer = new TraceValueColumnRenderer<>();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Life";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<LifeSet> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifeSet getValue(ValueRow rowObject, Settings settings, Trace data,
|
||||
ServiceProvider serviceProvider) throws IllegalArgumentException {
|
||||
|
|
|
@ -26,10 +26,6 @@ import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
|||
import ghidra.dbg.target.schema.SchemaContext;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
|
||||
/**
|
||||
|
|
|
@ -56,8 +56,10 @@ public abstract class TraceValueObjectPropertyColumn<T>
|
|||
ValueProperty<T> p = (ValueProperty<T>) data.getValue();
|
||||
setText(p.getHtmlDisplay());
|
||||
setToolTipText(p.getToolTip());
|
||||
|
||||
setForeground(getForegroundFor(data.getTable(), p.isModified(), data.isSelected()));
|
||||
if (p.getRow().isCurrent()) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@ public class TraceValueValColumn extends AbstractDynamicTableColumn<ValueRow, Va
|
|||
setToolTipText(row.getToolTip());
|
||||
setForeground(
|
||||
getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
|
||||
if (row.isCurrent()) {
|
||||
setBold();
|
||||
}
|
||||
}
|
||||
catch (TraceClosedException e) {
|
||||
setText("ERROR: Trace Closed");
|
||||
|
|
|
@ -150,6 +150,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
protected Map<Long, Trace> traces = new HashMap<>();
|
||||
protected Trace currentTrace;
|
||||
protected DebuggerObjectModel currentModel;
|
||||
private TargetObject targetFocus;
|
||||
// NB: We're getting rid of this because the ObjectsProvider is beating the trace
|
||||
// to the punch and causing the pattern-matcher to fail
|
||||
// private TraceRecorder recorder;
|
||||
|
@ -562,17 +563,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
synchronized (targetMap) {
|
||||
targetMap.put(key, container);
|
||||
refSet.add(targetObject);
|
||||
if (targetObject instanceof TargetConfigurable) {
|
||||
configurables.add((TargetConfigurable) targetObject);
|
||||
}
|
||||
}
|
||||
if (targetObject instanceof TargetInterpreter) {
|
||||
TargetInterpreter interpreter = (TargetInterpreter) targetObject;
|
||||
getPlugin().showConsole(interpreter);
|
||||
DebugModelConventions.findSuitable(TargetFocusScope.class, targetObject)
|
||||
.thenAccept(f -> {
|
||||
setFocus(f, targetObject);
|
||||
});
|
||||
}
|
||||
if (targetObject instanceof TargetConfigurable) {
|
||||
configurables.add((TargetConfigurable) targetObject);
|
||||
pane.setSelectedObject(targetObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -591,7 +589,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
public ObjectContainer getContainerByPath(List<String> path) {
|
||||
return targetMap.get(PathUtils.toString(path));
|
||||
return targetMap.get(PathUtils.toString(path, PATH_JOIN_CHAR));
|
||||
}
|
||||
|
||||
static List<ObjectContainer> getContainersFromObjects(Map<String, ?> objectMap,
|
||||
|
@ -1724,13 +1722,13 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
|
||||
public void executionStateChanged(TargetObject object, TargetExecutionState state) {
|
||||
//this.state = state;
|
||||
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
|
||||
public void focusChanged(TargetObject object, TargetObject focused) {
|
||||
plugin.setFocus(object, focused);
|
||||
plugin.getTool().contextChanged(DebuggerObjectsProvider.this);
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1824,8 +1822,11 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
if (focused.getModel() != currentModel) {
|
||||
return;
|
||||
}
|
||||
this.targetFocus = focused;
|
||||
if (isStopped(focused) || isUpdateWhileRunning()) {
|
||||
pane.setFocus(object, focused);
|
||||
if (pane != null) {
|
||||
pane.setFocus(object, focused);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1911,9 +1912,9 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
return listener.queue.in;
|
||||
}
|
||||
|
||||
public void navigateToSelectedObject(TargetObject object, Object value) {
|
||||
if (listingService == null || listingService == null) {
|
||||
return;
|
||||
public Address navigateToSelectedObject(TargetObject object, Object value) {
|
||||
if (listingService == null || modelService == null) {
|
||||
return null;
|
||||
}
|
||||
// TODO: Could probably inspect schema for any attribute of type Address[Range], or String
|
||||
if (value == null) {
|
||||
|
@ -1926,7 +1927,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
value = object.getCachedAttribute(TargetObject.VALUE_ATTRIBUTE_NAME);
|
||||
}
|
||||
if (value == null) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
Address addr = null;
|
||||
|
@ -1947,13 +1948,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
if (recorder == null) {
|
||||
recorder = modelService.getRecorder(currentTrace);
|
||||
if (recorder == null) {
|
||||
return;
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
DebuggerMemoryMapper memoryMapper = recorder.getMemoryMapper();
|
||||
Address traceAddr = memoryMapper.targetToTrace(addr);
|
||||
listingService.goTo(traceAddr, true);
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
private Address stringToAddress(TargetObject selectedObject, Address addr, String sval) {
|
||||
|
@ -1976,4 +1978,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
|
|||
public boolean isUpdateWhileRunning() {
|
||||
return updateWhileRunning;
|
||||
}
|
||||
|
||||
public TargetObject getFocus() {
|
||||
return targetFocus;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -545,6 +545,17 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
|
|||
return isLink;
|
||||
}
|
||||
|
||||
public boolean isFocused() {
|
||||
if (provider == null || targetObject == null) {
|
||||
return false;
|
||||
}
|
||||
TargetObject focus = provider.getFocus();
|
||||
if (focus == null) {
|
||||
return false;
|
||||
}
|
||||
return PathUtils.isAncestor(targetObject.getPath(), focus.getPath());
|
||||
}
|
||||
|
||||
public String getOrder() {
|
||||
Integer order = (Integer) attributeMap.get(TargetObject.ORDER_ATTRIBUTE_NAME);
|
||||
return order == null ? getName() : Integer.toString(order);
|
||||
|
@ -578,5 +589,4 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
|
|||
}
|
||||
return this.hashCode() - that.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ public abstract class DisplayAsAction extends DockingAction {
|
|||
provider.getModel(), container, isTree);
|
||||
container.propagateProvider(p);
|
||||
p.update(container);
|
||||
p.setFocus(null, provider.getFocus());
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
|
|
@ -23,7 +23,6 @@ import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
|
|||
import ghidra.dbg.target.TargetObject;
|
||||
|
||||
public interface ObjectPane {
|
||||
|
||||
public ObjectContainer getContainer();
|
||||
|
||||
public TargetObject getTargetObject();
|
||||
|
@ -46,6 +45,7 @@ public interface ObjectPane {
|
|||
|
||||
public void setFocus(TargetObject object, TargetObject focused);
|
||||
|
||||
public void setRoot(ObjectContainer root, TargetObject targetObject);
|
||||
public void setSelectedObject(TargetObject object);
|
||||
|
||||
public void setRoot(ObjectContainer root, TargetObject targetObject);
|
||||
}
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
@ -26,7 +25,9 @@ import docking.widgets.table.EnumeratedColumnTableModel;
|
|||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
|
||||
|
@ -34,11 +35,11 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
|
||||
public static final Icon ICON_TABLE = new GIcon("icon.debugger.table.object");
|
||||
|
||||
private ObjectContainer container;
|
||||
private Class<R> clazz;
|
||||
private AbstractSortedTableModel<R> model;
|
||||
private GhidraTable table;
|
||||
private JScrollPane component;
|
||||
private final ObjectContainer container;
|
||||
private final Class<R> clazz;
|
||||
private final AbstractSortedTableModel<R> model;
|
||||
private final GhidraTable table;
|
||||
private final JScrollPane component;
|
||||
|
||||
public ObjectTable(ObjectContainer container, Class<R> clazz,
|
||||
AbstractSortedTableModel<R> model) {
|
||||
|
@ -55,15 +56,24 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
DebuggerObjectsProvider provider = container.getProvider();
|
||||
provider.getTool().contextChanged(provider);
|
||||
});
|
||||
table.setDefaultRenderer(String.class,
|
||||
new ObjectTableCellRenderer(container.getProvider()));
|
||||
table.setDefaultRenderer(Object.class,
|
||||
new ObjectTableCellRenderer(container.getProvider()));
|
||||
table.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
int selectedRow = table.getSelectedRow();
|
||||
int selectedColumn = table.getSelectedColumn();
|
||||
Object value = table.getValueAt(selectedRow, selectedColumn);
|
||||
container.getProvider()
|
||||
.navigateToSelectedObject(container.getTargetObject(), value);
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
activateOrNavigateSelectedObject();
|
||||
}
|
||||
}
|
||||
});
|
||||
table.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
activateOrNavigateSelectedObject();
|
||||
e.consume(); // lest it select the next row down
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -71,6 +81,38 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
signalUpdate(container);
|
||||
}
|
||||
|
||||
private void activateOrNavigateSelectedObject() {
|
||||
int selectedRow = table.getSelectedRow();
|
||||
int selectedColumn = table.getSelectedColumn();
|
||||
Object value = table.getValueAt(selectedRow, selectedColumn);
|
||||
if (container.getProvider()
|
||||
.navigateToSelectedObject(container.getTargetObject(), value) != null) {
|
||||
return;
|
||||
}
|
||||
R row = model.getModelData().get(selectedRow); // No filter?
|
||||
TargetObject object;
|
||||
if (row instanceof ObjectElementRow eRow) {
|
||||
object = eRow.getTargetObject();
|
||||
}
|
||||
else if (row instanceof ObjectAttributeRow aRow) {
|
||||
object = aRow.getTargetObject();
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
if (object instanceof DummyTargetObject) {
|
||||
return;
|
||||
}
|
||||
DebugModelConventions.requestActivation(object).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not activate " + object, ex);
|
||||
return null;
|
||||
});
|
||||
/*DebugModelConventions.requestFocus(object).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not focus " + object, ex);
|
||||
return null;
|
||||
});*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectContainer getContainer() {
|
||||
return container;
|
||||
|
@ -171,8 +213,7 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
ObjectElementRow match = null;
|
||||
for (int i = 0; i < model.getRowCount(); i++) {
|
||||
R r = model.getRowObject(i);
|
||||
if (r instanceof ObjectElementRow) {
|
||||
ObjectElementRow row = (ObjectElementRow) r;
|
||||
if (r instanceof ObjectElementRow row) {
|
||||
if (row.getTargetObject().equals(changedTarget)) {
|
||||
row.setAttributes(changed.getAttributeMap());
|
||||
match = row;
|
||||
|
@ -184,7 +225,6 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
}
|
||||
|
||||
private List<R> updateMatch(ObjectElementRow match) {
|
||||
@SuppressWarnings("unchecked")
|
||||
ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model;
|
||||
m.updateColumns(match);
|
||||
m.fireTableDataChanged();
|
||||
|
@ -198,12 +238,11 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
}
|
||||
|
||||
public void setColumns() {
|
||||
@SuppressWarnings("unchecked")
|
||||
ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model;
|
||||
for (int i = 0; i < model.getRowCount(); i++) {
|
||||
R r = model.getRowObject(i);
|
||||
if (r instanceof ObjectElementRow) {
|
||||
m.updateColumns((ObjectElementRow) r);
|
||||
if (r instanceof ObjectElementRow row) {
|
||||
m.updateColumns(row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -214,12 +253,10 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
public TargetObject getSelectedObject() {
|
||||
int selectedColumn = table.getSelectedColumn();
|
||||
R r = model.getRowObject(table.getSelectedRow());
|
||||
if (r instanceof ObjectAttributeRow) {
|
||||
ObjectAttributeRow row = (ObjectAttributeRow) r;
|
||||
if (r instanceof ObjectAttributeRow row) {
|
||||
return row.getTargetObject();
|
||||
}
|
||||
if (r instanceof ObjectElementRow) {
|
||||
ObjectElementRow row = (ObjectElementRow) r;
|
||||
if (r instanceof ObjectElementRow row) {
|
||||
TargetObject targetObject = row.getTargetObject();
|
||||
if (selectedColumn > 0) {
|
||||
List<String> keys = row.getKeys();
|
||||
|
@ -229,8 +266,8 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
String key = keys.get(selectedColumn);
|
||||
Map<String, ?> attributes = targetObject.getCachedAttributes();
|
||||
Object object = attributes.get(key);
|
||||
if (object instanceof TargetObject) {
|
||||
return (TargetObject) object;
|
||||
if (object instanceof TargetObject to) {
|
||||
return to;
|
||||
}
|
||||
}
|
||||
return targetObject;
|
||||
|
@ -238,18 +275,17 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectedObject(TargetObject selection) {
|
||||
for (int i = 0; i < model.getRowCount(); i++) {
|
||||
R r = model.getRowObject(i);
|
||||
if (r instanceof ObjectAttributeRow) {
|
||||
ObjectAttributeRow row = (ObjectAttributeRow) r;
|
||||
if (r instanceof ObjectAttributeRow row) {
|
||||
if (row.getTargetObject().equals(selection)) {
|
||||
table.selectRow(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r instanceof ObjectElementRow) {
|
||||
ObjectElementRow row = (ObjectElementRow) r;
|
||||
if (r instanceof ObjectElementRow row) {
|
||||
if (row.getTargetObject().equals(selection)) {
|
||||
table.selectRow(i);
|
||||
break;
|
||||
|
@ -260,8 +296,9 @@ public class ObjectTable<R> implements ObjectPane {
|
|||
|
||||
@Override
|
||||
public void setFocus(TargetObject object, TargetObject focused) {
|
||||
// Should this setSelectedObject, too?
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
setSelectedObject(focused);
|
||||
table.repaint();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import docking.widgets.table.GTableCellRenderingData;
|
||||
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.util.PathUtils;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
|
||||
|
||||
public class ObjectTableCellRenderer extends AbstractGhidraColumnRenderer<Object> {
|
||||
private final DebuggerObjectsProvider provider;
|
||||
|
||||
public ObjectTableCellRenderer(DebuggerObjectsProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(Object t, Settings settings) {
|
||||
return t == null ? "<null>" : t.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
TargetObject focus = provider.getFocus();
|
||||
if (focus == null) {
|
||||
return this;
|
||||
}
|
||||
Object rowObject = data.getRowObject();
|
||||
TargetObject object;
|
||||
if (rowObject instanceof ObjectElementRow eRow) {
|
||||
object = eRow.getTargetObject();
|
||||
}
|
||||
else if (rowObject instanceof ObjectAttributeRow aRow) {
|
||||
object = aRow.getTargetObject();
|
||||
}
|
||||
else {
|
||||
return this;
|
||||
}
|
||||
if (PathUtils.isAncestor(object.getPath(), focus.getPath())) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -16,13 +16,11 @@
|
|||
package ghidra.app.plugin.core.debug.gui.objects.components;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.TreeExpansionEvent;
|
||||
import javax.swing.event.TreeExpansionListener;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
@ -49,44 +47,38 @@ public class ObjectTree implements ObjectPane {
|
|||
|
||||
public static final Icon ICON_TREE = new GIcon("icon.debugger.tree.object");
|
||||
|
||||
private ObjectNode root;
|
||||
private GTree tree;
|
||||
private static class MyGTree extends GTree {
|
||||
public MyGTree(GTreeNode root) {
|
||||
super(root);
|
||||
getJTree().setToggleClickCount(0);
|
||||
}
|
||||
|
||||
private JTree tree() {
|
||||
return getJTree();
|
||||
}
|
||||
}
|
||||
|
||||
private final ObjectNode root;
|
||||
private final MyGTree tree;
|
||||
|
||||
private final Map<String, ObjectNode> nodeMap = new LinkedHashMap<>();
|
||||
private final SwingUpdateManager restoreTreeStateManager =
|
||||
new SwingUpdateManager(this::restoreTreeState);
|
||||
|
||||
private TreePath[] currentSelectionPaths;
|
||||
private List<TreePath> currentExpandedPaths;
|
||||
private Point currentViewPosition;
|
||||
|
||||
private Map<String, ObjectNode> nodeMap = new LinkedHashMap<>();
|
||||
private SwingUpdateManager restoreTreeStateManager =
|
||||
new SwingUpdateManager(this::restoreTreeState);
|
||||
|
||||
public ObjectTree(ObjectContainer container) {
|
||||
this.root = new ObjectNode(this, null, container);
|
||||
addToMap(null, container, root);
|
||||
this.tree = new GTree(root);
|
||||
this.tree = new MyGTree(root);
|
||||
|
||||
tree.addGTreeSelectionListener(new GTreeSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(GTreeSelectionEvent e) {
|
||||
DebuggerObjectsProvider provider = container.getProvider();
|
||||
provider.updateActions(container);
|
||||
TreePath path = e.getPath();
|
||||
Object last = path.getLastPathComponent();
|
||||
if (!(last instanceof ObjectNode)) {
|
||||
throw new RuntimeException("Path terminating in non-ObjectNode");
|
||||
}
|
||||
ObjectNode node = (ObjectNode) last;
|
||||
TargetObject targetObject = node.getTargetObject();
|
||||
if (targetObject != null && !(targetObject instanceof DummyTargetObject) &&
|
||||
e.getEventOrigin().equals(EventOrigin.USER_GENERATED)) {
|
||||
DebugModelConventions.requestActivation(targetObject).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not activate " + targetObject, ex);
|
||||
return null;
|
||||
});
|
||||
DebugModelConventions.requestFocus(targetObject).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not focus " + targetObject, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
provider.getTool().contextChanged(provider);
|
||||
if (e.getEventOrigin() == EventOrigin.INTERNAL_GENERATED) {
|
||||
restoreTreeStateManager.updateLater();
|
||||
|
@ -119,8 +111,7 @@ public class ObjectTree implements ObjectPane {
|
|||
});
|
||||
tree.setCellRenderer(new ObjectTreeCellRenderer(root.getProvider()));
|
||||
tree.setDataTransformer(t -> {
|
||||
if (t instanceof ObjectNode) {
|
||||
ObjectNode node = (ObjectNode) t;
|
||||
if (t instanceof ObjectNode node) {
|
||||
return List.of(node.getContainer().getDecoratedName());
|
||||
}
|
||||
return null;
|
||||
|
@ -131,8 +122,7 @@ public class ObjectTree implements ObjectPane {
|
|||
public void treeExpanded(TreeExpansionEvent event) {
|
||||
TreePath expandedPath = event.getPath();
|
||||
Object last = expandedPath.getLastPathComponent();
|
||||
if (last instanceof ObjectNode) {
|
||||
ObjectNode node = (ObjectNode) last;
|
||||
if (last instanceof ObjectNode node) {
|
||||
node.markExpanded();
|
||||
currentExpandedPaths = tree.getExpandedPaths();
|
||||
}
|
||||
|
@ -142,8 +132,7 @@ public class ObjectTree implements ObjectPane {
|
|||
public void treeCollapsed(TreeExpansionEvent event) {
|
||||
TreePath collapsedPath = event.getPath();
|
||||
Object last = collapsedPath.getLastPathComponent();
|
||||
if (last instanceof ObjectNode) {
|
||||
ObjectNode node = (ObjectNode) last;
|
||||
if (last instanceof ObjectNode node) {
|
||||
node.markCollapsed();
|
||||
currentExpandedPaths = tree.getExpandedPaths();
|
||||
}
|
||||
|
@ -153,11 +142,17 @@ public class ObjectTree implements ObjectPane {
|
|||
tree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
TargetObject selectedObject = getSelectedObject();
|
||||
if (selectedObject != null) {
|
||||
container.getProvider().navigateToSelectedObject(selectedObject, null);
|
||||
}
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
activateOrNavigateSelectedObject();
|
||||
}
|
||||
}
|
||||
});
|
||||
tree.tree().addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
activateOrNavigateSelectedObject();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -166,6 +161,27 @@ public class ObjectTree implements ObjectPane {
|
|||
tree.setSelectedNode(root);
|
||||
}
|
||||
|
||||
private void activateOrNavigateSelectedObject() {
|
||||
TargetObject object = getSelectedObject();
|
||||
if (object == null) {
|
||||
return;
|
||||
}
|
||||
if (getProvider().navigateToSelectedObject(object, null) != null) {
|
||||
return;
|
||||
}
|
||||
if (object instanceof DummyTargetObject) {
|
||||
return;
|
||||
}
|
||||
DebugModelConventions.requestActivation(object).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not activate " + object, ex);
|
||||
return null;
|
||||
});
|
||||
/*DebugModelConventions.requestFocus(object).exceptionally(ex -> {
|
||||
Msg.error(this, "Could not focus " + object, ex);
|
||||
return null;
|
||||
});*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectContainer getContainer() {
|
||||
return root.getContainer();
|
||||
|
@ -188,8 +204,8 @@ public class ObjectTree implements ObjectPane {
|
|||
}
|
||||
if (path != null) {
|
||||
Object last = path.getLastPathComponent();
|
||||
if (last instanceof ObjectNode) {
|
||||
return ((ObjectNode) last).getContainer().getTargetObject();
|
||||
if (last instanceof ObjectNode node) {
|
||||
return node.getContainer().getTargetObject();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -303,8 +319,7 @@ public class ObjectTree implements ObjectPane {
|
|||
for (TreePath path : expandedPaths) {
|
||||
Object[] objs = path.getPath();
|
||||
for (int i = 0; i < objs.length - 2; i++) {
|
||||
if (objs[i] instanceof ObjectNode) {
|
||||
ObjectNode node = (ObjectNode) objs[i];
|
||||
if (objs[i] instanceof ObjectNode node) {
|
||||
if (!node.isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -364,6 +379,14 @@ public class ObjectTree implements ObjectPane {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectedObject(TargetObject object) {
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
List<String> path = object.getPath();
|
||||
tree.setSelectedNodeByNamePath(addRootNameToPath(path));
|
||||
});
|
||||
}
|
||||
|
||||
private String[] addRootNameToPath(List<String> path) {
|
||||
String[] fullPath = new String[path.size() + 1];
|
||||
fullPath[0] = tree.getModelRoot().getName();
|
||||
|
|
|
@ -30,7 +30,6 @@ import ghidra.dbg.target.TargetExecutionStateful;
|
|||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
|
||||
// TODO: In the new scheme, I'm not sure this is applicable anymore.
|
||||
class ObjectTreeCellRenderer extends GTreeRenderer {
|
||||
private static final String FONT_ID = "font.debugger.object.tree.renderer";
|
||||
|
||||
|
@ -45,12 +44,17 @@ class ObjectTreeCellRenderer extends GTreeRenderer {
|
|||
boolean leaf, int row, boolean focus) {
|
||||
Component component =
|
||||
super.getTreeCellRendererComponent(t, value, sel, exp, leaf, row, focus);
|
||||
if (value instanceof ObjectNode) {
|
||||
ObjectNode node = (ObjectNode) value;
|
||||
if (value instanceof ObjectNode node) {
|
||||
ObjectContainer container = node.getContainer();
|
||||
setText(container.getDecoratedName());
|
||||
component.setForeground(provider.COLOR_FOREGROUND);
|
||||
TargetObject targetObject = container.getTargetObject();
|
||||
if (container.isSubscribed()) {
|
||||
Color color = provider.COLOR_FOREGROUND_SUBSCRIBED;
|
||||
if (!color.equals(Tables.FG_UNSELECTED)) {
|
||||
component.setForeground(color);
|
||||
}
|
||||
}
|
||||
if (targetObject != null) {
|
||||
Map<String, ?> attrs = targetObject.getCachedAttributes();
|
||||
String kind = (String) attrs.get(TargetObject.KIND_ATTRIBUTE_NAME);
|
||||
|
@ -76,12 +80,6 @@ class ObjectTreeCellRenderer extends GTreeRenderer {
|
|||
if (container.isModified()) {
|
||||
component.setForeground(provider.COLOR_FOREGROUND_MODIFIED);
|
||||
}
|
||||
if (container.isSubscribed()) {
|
||||
Color color = provider.COLOR_FOREGROUND_SUBSCRIBED;
|
||||
if (!color.equals(Tables.FG_UNSELECTED)) {
|
||||
component.setForeground(color);
|
||||
}
|
||||
}
|
||||
TreePath path = t.getSelectionPath();
|
||||
if (path != null) {
|
||||
Object last = path.getLastPathComponent();
|
||||
|
@ -93,8 +91,8 @@ class ObjectTreeCellRenderer extends GTreeRenderer {
|
|||
}
|
||||
}
|
||||
Font font = Gui.getFont(FONT_ID);
|
||||
if (container.isSubscribed()) {
|
||||
font = font.deriveFont(Font.ITALIC);
|
||||
if (container.isFocused()) {
|
||||
font = font.deriveFont(Font.BOLD);
|
||||
}
|
||||
component.setFont(font);
|
||||
}
|
||||
|
|
|
@ -1296,7 +1296,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
return AsyncUtils.NIL;
|
||||
}
|
||||
toRead.retainAll(regMapper.getRegistersOnTarget());
|
||||
Set<TargetRegisterBank> banks = recorder.getTargetRegisterBanks(traceThread, current.getFrame());
|
||||
Set<TargetRegisterBank> banks =
|
||||
recorder.getTargetRegisterBanks(traceThread, current.getFrame());
|
||||
if (banks == null || banks.isEmpty()) {
|
||||
Msg.error(this, "Current frame's bank does not exist");
|
||||
return AsyncUtils.NIL;
|
||||
|
@ -1341,16 +1342,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
}
|
||||
return future.exceptionally(ex -> {
|
||||
ex = AsyncUtils.unwrapThrowable(ex);
|
||||
if (ex instanceof DebuggerModelAccessException) {
|
||||
String msg =
|
||||
"Could not read target registers for selected thread: " + ex.getMessage();
|
||||
Msg.info(this, msg);
|
||||
plugin.getTool().setStatusInfo(msg);
|
||||
}
|
||||
else {
|
||||
Msg.showError(this, getComponent(), "Read Target Registers",
|
||||
"Could not read target registers for selected thread", ex);
|
||||
}
|
||||
String msg = "Could not read target registers for selected thread: " + ex.getMessage();
|
||||
Msg.info(this, msg);
|
||||
plugin.getTool().setStatusInfo(msg);
|
||||
return ExceptionUtils.rethrow(ex);
|
||||
}).thenApply(__ -> null);
|
||||
}
|
||||
|
|
|
@ -16,23 +16,20 @@
|
|||
package ghidra.app.plugin.core.debug.gui.stack;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import javax.swing.table.*;
|
||||
|
||||
import docking.widgets.table.CustomToStringCellRenderer;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.dbg.DebugModelConventions;
|
||||
import ghidra.dbg.target.TargetStackFrame;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||
|
@ -52,8 +49,7 @@ import ghidra.trace.util.TraceRegisterUtils;
|
|||
import ghidra.util.Swing;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
import utilities.util.SuppressableCallback;
|
||||
import utilities.util.SuppressableCallback.Suppression;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
|
||||
public class DebuggerLegacyStackPanel extends JPanel {
|
||||
|
||||
|
@ -202,8 +198,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
|
||||
@AutoServiceConsumed
|
||||
private DebuggerTraceManagerService traceManager;
|
||||
// @AutoServiceConsumed by method
|
||||
private DebuggerModelService modelService;
|
||||
// @AutoServiceConsumed via method
|
||||
DebuggerStaticMappingService mappingService;
|
||||
@AutoServiceConsumed
|
||||
|
@ -213,6 +207,23 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
final TableCellRenderer boldCurrentRenderer = new AbstractGColumnRenderer<Object>() {
|
||||
@Override
|
||||
public String getFilterString(Object t, Settings settings) {
|
||||
return t == null ? "<null>" : t.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
StackFrameRow row = (StackFrameRow) data.getRowObject();
|
||||
if (row != null && row.getFrameLevel() == current.getFrame()) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
// Table rows access this for function name resolution
|
||||
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
|
||||
private Trace currentTrace; // Copy for transition
|
||||
|
@ -222,8 +233,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
private ForStackListener forStackListener = new ForStackListener();
|
||||
private ForFunctionsListener forFunctionsListener = new ForFunctionsListener();
|
||||
|
||||
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
|
||||
|
||||
protected final StackTableModel stackTableModel;
|
||||
protected GhidraTable stackTable;
|
||||
protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel;
|
||||
|
@ -246,33 +255,23 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
return;
|
||||
}
|
||||
contextChanged();
|
||||
activateSelectedFrame();
|
||||
});
|
||||
|
||||
stackTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() < 2 || e.getButton() != MouseEvent.BUTTON1) {
|
||||
return;
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
activateSelectedFrame();
|
||||
}
|
||||
if (listingService == null) {
|
||||
return;
|
||||
}
|
||||
if (myActionContext == null) {
|
||||
return;
|
||||
}
|
||||
Address pc = myActionContext.getFrame().getProgramCounter();
|
||||
if (pc == null) {
|
||||
return;
|
||||
}
|
||||
listingService.goTo(pc, true);
|
||||
}
|
||||
|
||||
});
|
||||
stackTable.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
int selectedRow = stackTable.getSelectedRow();
|
||||
StackFrameRow row = stackTableModel.getRowObject(selectedRow);
|
||||
rowActivated(row);
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
activateSelectedFrame();
|
||||
e.consume(); // lest it select the next row down
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -281,8 +280,14 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
|
||||
TableColumn levelCol = columnModel.getColumn(StackTableColumns.LEVEL.ordinal());
|
||||
levelCol.setPreferredWidth(25);
|
||||
TableColumn baseCol = columnModel.getColumn(StackTableColumns.PC.ordinal());
|
||||
baseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
levelCol.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn pcCol = columnModel.getColumn(StackTableColumns.PC.ordinal());
|
||||
pcCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT);
|
||||
pcCol.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn funcCol = columnModel.getColumn(StackTableColumns.FUNCTION.ordinal());
|
||||
funcCol.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn commCol = columnModel.getColumn(StackTableColumns.COMMENT.ordinal());
|
||||
commCol.setCellRenderer(boldCurrentRenderer);
|
||||
}
|
||||
|
||||
protected void contextChanged() {
|
||||
|
@ -293,37 +298,13 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
}
|
||||
|
||||
protected void activateSelectedFrame() {
|
||||
// TODO: Action to toggle sync?
|
||||
if (myActionContext == null) {
|
||||
return;
|
||||
}
|
||||
if (traceManager == null) {
|
||||
return;
|
||||
}
|
||||
cbFrameSelected.invoke(() -> {
|
||||
traceManager.activateFrame(myActionContext.getFrame().getFrameLevel());
|
||||
});
|
||||
}
|
||||
|
||||
private void rowActivated(StackFrameRow row) {
|
||||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
TraceStackFrame frame = row.frame;
|
||||
if (frame == null) {
|
||||
return;
|
||||
}
|
||||
TraceThread thread = frame.getStack().getThread();
|
||||
Trace trace = thread.getTrace();
|
||||
TraceRecorder recorder = modelService.getRecorder(trace);
|
||||
if (recorder == null) {
|
||||
return;
|
||||
}
|
||||
TargetStackFrame targetFrame = recorder.getTargetStackFrame(thread, frame.getLevel());
|
||||
if (targetFrame == null || !targetFrame.isValid()) {
|
||||
return;
|
||||
}
|
||||
DebugModelConventions.requestActivation(targetFrame);
|
||||
traceManager.activateFrame(myActionContext.getFrame().getFrameLevel());
|
||||
}
|
||||
|
||||
protected void updateStack() {
|
||||
|
@ -344,8 +325,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
}
|
||||
|
||||
stackTableModel.fireTableDataChanged();
|
||||
|
||||
selectCurrentFrame();
|
||||
}
|
||||
|
||||
protected void doSetCurrentStack(TraceStack stack) {
|
||||
|
@ -411,7 +390,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
else {
|
||||
doSetCurrentStack(stack);
|
||||
}
|
||||
selectCurrentFrame();
|
||||
}
|
||||
|
||||
private void removeOldListeners() {
|
||||
|
@ -441,19 +419,17 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
current = coordinates;
|
||||
doSetTrace(current.getTrace());
|
||||
loadStack();
|
||||
selectCurrentFrame();
|
||||
}
|
||||
|
||||
protected void selectCurrentFrame() {
|
||||
try (Suppression supp = cbFrameSelected.suppress(null)) {
|
||||
StackFrameRow row =
|
||||
stackTableModel.findFirst(r -> r.getFrameLevel() == current.getFrame());
|
||||
if (row == null) {
|
||||
// Strange
|
||||
stackTable.clearSelection();
|
||||
}
|
||||
else {
|
||||
stackFilterPanel.setSelectedItem(row);
|
||||
}
|
||||
StackFrameRow row = stackTableModel.findFirst(r -> r.getFrameLevel() == current.getFrame());
|
||||
if (row == null) {
|
||||
// Strange
|
||||
stackTable.clearSelection();
|
||||
}
|
||||
else {
|
||||
stackFilterPanel.setSelectedItem(row);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -461,11 +437,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
|
|||
return myActionContext;
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
public void setModelService(DebuggerModelService modelService) {
|
||||
this.modelService = modelService;
|
||||
}
|
||||
|
||||
@AutoServiceConsumed
|
||||
private void setMappingService(DebuggerStaticMappingService mappingService) {
|
||||
if (this.mappingService != null) {
|
||||
|
|
|
@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.stack;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import docking.widgets.table.AbstractDynamicTableColumn;
|
||||
|
@ -43,8 +43,6 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.stack.TraceObjectStackFrame;
|
||||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import utilities.util.SuppressableCallback;
|
||||
import utilities.util.SuppressableCallback.Suppression;
|
||||
|
||||
public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObjectStackFrame>
|
||||
implements ListSelectionListener, CellActivationListener {
|
||||
|
@ -109,8 +107,6 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
|
|||
@AutoServiceConsumed
|
||||
protected DebuggerTraceManagerService traceManager;
|
||||
|
||||
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
|
||||
|
||||
public DebuggerStackPanel(DebuggerStackProvider provider) {
|
||||
super(provider.plugin, provider, TraceObjectStackFrame.class);
|
||||
this.provider = provider;
|
||||
|
@ -139,21 +135,16 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
|
|||
super.coordinatesActivated(coordinates);
|
||||
TraceObject object = coordinates.getObject();
|
||||
if (object != null) {
|
||||
try (Suppression supp = cbFrameSelected.suppress(null)) {
|
||||
trySelectAncestor(object);
|
||||
}
|
||||
trySelectAncestor(object);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
super.valueChanged(e);
|
||||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
public void cellActivated(JTable table) {
|
||||
// No super
|
||||
ValueRow item = getSelectedItem();
|
||||
if (item != null) {
|
||||
cbFrameSelected.invoke(() -> traceManager.activateObject(item.getValue().getChild()));
|
||||
traceManager.activateObject(item.getValue().getChild());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
package ghidra.app.plugin.core.debug.gui.thread;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import javax.swing.table.*;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.widgets.table.*;
|
||||
|
@ -32,6 +32,7 @@ import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
|||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||
import ghidra.framework.plugintool.AutoService;
|
||||
|
@ -45,8 +46,7 @@ import ghidra.trace.model.time.TraceSnapshot;
|
|||
import ghidra.util.database.ObjectKey;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
import utilities.util.SuppressableCallback;
|
||||
import utilities.util.SuppressableCallback.Suppression;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
|
||||
public class DebuggerLegacyThreadsPanel extends JPanel {
|
||||
|
||||
|
@ -175,13 +175,28 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
|||
|
||||
private final ForThreadsListener forThreadsListener = new ForThreadsListener();
|
||||
|
||||
private final SuppressableCallback<Void> cbCoordinateActivation = new SuppressableCallback<>();
|
||||
|
||||
/* package access for testing */
|
||||
final SpanTableCellRenderer<Long> spanRenderer = new SpanTableCellRenderer<>();
|
||||
final RangeCursorTableHeaderRenderer<Long> headerRenderer =
|
||||
new RangeCursorTableHeaderRenderer<>(0L);
|
||||
|
||||
final TableCellRenderer boldCurrentRenderer = new AbstractGColumnRenderer<Object>() {
|
||||
@Override
|
||||
public String getFilterString(Object t, Settings settings) {
|
||||
return t == null ? "<null>" : t.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
ThreadRow row = (ThreadRow) data.getRowObject();
|
||||
if (row != null && row.getThread() == current.getThread()) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
final ThreadTableModel threadTableModel;
|
||||
final GTable threadTable;
|
||||
final GhidraTableFilterPanel<ThreadRow> threadFilterPanel;
|
||||
|
@ -208,6 +223,23 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
|||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
|
||||
|
||||
threadTable.getSelectionModel().addListSelectionListener(this::threadRowSelected);
|
||||
threadTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
activateSelectedThread();
|
||||
}
|
||||
}
|
||||
});
|
||||
threadTable.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
activateSelectedThread();
|
||||
e.consume(); // lest it select the next row down
|
||||
}
|
||||
}
|
||||
});
|
||||
threadTable.addFocusListener(new FocusAdapter() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
|
@ -224,14 +256,19 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
|||
TableColumnModel columnModel = threadTable.getColumnModel();
|
||||
TableColumn colName = columnModel.getColumn(ThreadTableColumns.NAME.ordinal());
|
||||
colName.setPreferredWidth(100);
|
||||
colName.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn colCreated = columnModel.getColumn(ThreadTableColumns.CREATED.ordinal());
|
||||
colCreated.setPreferredWidth(10);
|
||||
colCreated.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn colDestroyed = columnModel.getColumn(ThreadTableColumns.DESTROYED.ordinal());
|
||||
colDestroyed.setPreferredWidth(10);
|
||||
colDestroyed.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn colState = columnModel.getColumn(ThreadTableColumns.STATE.ordinal());
|
||||
colState.setPreferredWidth(20);
|
||||
colState.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn colComment = columnModel.getColumn(ThreadTableColumns.COMMENT.ordinal());
|
||||
colComment.setPreferredWidth(100);
|
||||
colComment.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn colPlot = columnModel.getColumn(ThreadTableColumns.PLOT.ordinal());
|
||||
colPlot.setPreferredWidth(200);
|
||||
colPlot.setCellRenderer(spanRenderer);
|
||||
|
@ -281,19 +318,13 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
|||
}
|
||||
|
||||
private void doSetThread(TraceThread thread) {
|
||||
ThreadRow row = threadFilterPanel.getSelectedItem();
|
||||
TraceThread curThread = row == null ? null : row.getThread();
|
||||
if (curThread == thread) {
|
||||
return;
|
||||
if (thread != null) {
|
||||
threadFilterPanel.setSelectedItem(threadTableModel.getRow(thread));
|
||||
}
|
||||
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
|
||||
if (thread != null) {
|
||||
threadFilterPanel.setSelectedItem(threadTableModel.getRow(thread));
|
||||
}
|
||||
else {
|
||||
threadTable.clearSelection();
|
||||
}
|
||||
else {
|
||||
threadTable.clearSelection();
|
||||
}
|
||||
threadTableModel.fireTableDataChanged();
|
||||
}
|
||||
|
||||
private void doSetSnap(long snap) {
|
||||
|
@ -324,9 +355,13 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
|
|||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
setThreadRowActionContext();
|
||||
}
|
||||
|
||||
private void activateSelectedThread() {
|
||||
ThreadRow row = setThreadRowActionContext();
|
||||
if (row != null && traceManager != null) {
|
||||
cbCoordinateActivation.invoke(() -> traceManager.activateThread(row.getThread()));
|
||||
traceManager.activateThread(row.getThread());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,10 @@ package ghidra.app.plugin.core.debug.gui.thread;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.JTable;
|
||||
|
||||
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
|
||||
import docking.widgets.table.threaded.ThreadedTableModelListener;
|
||||
import docking.widgets.table.TableColumnDescriptor;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.gui.model.*;
|
||||
|
@ -37,8 +38,6 @@ import ghidra.trace.model.Trace;
|
|||
import ghidra.trace.model.target.TraceObject;
|
||||
import ghidra.trace.model.target.TraceObjectValue;
|
||||
import ghidra.trace.model.thread.TraceObjectThread;
|
||||
import utilities.util.SuppressableCallback;
|
||||
import utilities.util.SuppressableCallback.Suppression;
|
||||
|
||||
public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceObjectThread> {
|
||||
|
||||
|
@ -166,8 +165,6 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
|||
@AutoServiceConsumed
|
||||
protected DebuggerTraceManagerService traceManager;
|
||||
|
||||
private final SuppressableCallback<Void> cbThreadSelected = new SuppressableCallback<>();
|
||||
|
||||
private final SeekListener seekListener = pos -> {
|
||||
long snap = Math.round(pos);
|
||||
if (current.getTrace() == null || snap < 0) {
|
||||
|
@ -180,11 +177,22 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
|||
super(provider.plugin, provider, TraceObjectThread.class);
|
||||
setLimitToSnap(false); // TODO: Toggle for this?
|
||||
|
||||
tableModel.addTableModelListener(e -> {
|
||||
// This seems a bit heavy handed
|
||||
trySelectCurrentThread();
|
||||
});
|
||||
addSeekListener(seekListener);
|
||||
|
||||
tableModel.addThreadedTableModelListener(new ThreadedTableModelListener() {
|
||||
@Override
|
||||
public void loadingStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadingFinished(boolean wasCancelled) {
|
||||
trySelectCurrentThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPending() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -211,11 +219,10 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
|||
|
||||
private void trySelectCurrentThread() {
|
||||
TraceObject object = current.getObject();
|
||||
if (object != null) {
|
||||
try (Suppression supp = cbThreadSelected.suppress(null)) {
|
||||
trySelectAncestor(object);
|
||||
}
|
||||
if (object == null) {
|
||||
return;
|
||||
}
|
||||
trySelectAncestor(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -225,21 +232,11 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
|
|||
}
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
super.valueChanged(e);
|
||||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
public void cellActivated(JTable table) {
|
||||
// No super
|
||||
ValueRow item = getSelectedItem();
|
||||
if (item != null) {
|
||||
cbThreadSelected.invoke(() -> {
|
||||
if (current.getTrace() != item.getValue().getTrace()) {
|
||||
// Prevent timing issues during navigation from causing trace changes
|
||||
// Thread table should never cause trace change anyway
|
||||
return;
|
||||
}
|
||||
traceManager.activateObject(item.getValue().getChild());
|
||||
});
|
||||
traceManager.activateObject(item.getValue().getChild());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ import docking.action.*;
|
|||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SynchronizeFocusAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.SynchronizeTargetAction;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.ToToggleSelectionListener;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
|
||||
|
@ -106,8 +106,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
@SuppressWarnings("unused")
|
||||
private final AutoService.Wiring autoServiceWiring;
|
||||
|
||||
private final BooleanChangeAdapter synchronizeFocusChangeListener =
|
||||
this::changedSynchronizeFocus;
|
||||
private final BooleanChangeAdapter synchronizeTargetChangeListener =
|
||||
this::changedSynchronizeTarget;
|
||||
|
||||
private final ForSnapsListener forSnapsListener = new ForSnapsListener();
|
||||
|
||||
|
@ -119,7 +119,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
DebuggerLegacyThreadsPanel legacyPanel;
|
||||
|
||||
DockingAction actionSaveTrace;
|
||||
ToggleDockingAction actionSyncFocus;
|
||||
ToggleDockingAction actionSyncTarget;
|
||||
|
||||
ActionContext myActionContext;
|
||||
|
||||
|
@ -149,13 +149,13 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
@AutoServiceConsumed
|
||||
public void setTraceManager(DebuggerTraceManagerService traceManager) {
|
||||
if (this.traceManager != null) {
|
||||
this.traceManager.removeSynchronizeFocusChangeListener(synchronizeFocusChangeListener);
|
||||
this.traceManager.removeSynchronizeActiveChangeListener(synchronizeTargetChangeListener);
|
||||
}
|
||||
this.traceManager = traceManager;
|
||||
if (traceManager != null) {
|
||||
traceManager.addSynchronizeFocusChangeListener(synchronizeFocusChangeListener);
|
||||
if (actionSyncFocus != null) {
|
||||
actionSyncFocus.setSelected(traceManager.isSynchronizeFocus());
|
||||
traceManager.addSynchronizeActiveChangeListener(synchronizeTargetChangeListener);
|
||||
if (actionSyncTarget != null) {
|
||||
actionSyncTarget.setSelected(traceManager.isSynchronizeActive());
|
||||
}
|
||||
}
|
||||
contextChanged();
|
||||
|
@ -236,27 +236,27 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
|
||||
protected void createActions() {
|
||||
actionSyncFocus = SynchronizeFocusAction.builder(plugin)
|
||||
.selected(traceManager != null && traceManager.isSynchronizeFocus())
|
||||
actionSyncTarget = SynchronizeTargetAction.builder(plugin)
|
||||
.selected(traceManager != null && traceManager.isSynchronizeActive())
|
||||
.enabledWhen(c -> traceManager != null)
|
||||
.onAction(c -> toggleSyncFocus(actionSyncFocus.isSelected()))
|
||||
.onAction(c -> toggleSyncFocus(actionSyncTarget.isSelected()))
|
||||
.buildAndInstallLocal(this);
|
||||
traceManager.addSynchronizeFocusChangeListener(toToggleSelectionListener =
|
||||
new ToToggleSelectionListener(actionSyncFocus));
|
||||
traceManager.addSynchronizeActiveChangeListener(toToggleSelectionListener =
|
||||
new ToToggleSelectionListener(actionSyncTarget));
|
||||
}
|
||||
|
||||
private void changedSynchronizeFocus(boolean value) {
|
||||
if (actionSyncFocus == null || actionSyncFocus.isSelected()) {
|
||||
private void changedSynchronizeTarget(boolean value) {
|
||||
if (actionSyncTarget == null || actionSyncTarget.isSelected()) {
|
||||
return;
|
||||
}
|
||||
actionSyncFocus.setSelected(value);
|
||||
actionSyncTarget.setSelected(value);
|
||||
}
|
||||
|
||||
private void toggleSyncFocus(boolean enabled) {
|
||||
if (traceManager == null) {
|
||||
return;
|
||||
}
|
||||
traceManager.setSynchronizeFocus(enabled);
|
||||
traceManager.setSynchronizeActive(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,19 +16,20 @@
|
|||
package ghidra.app.plugin.core.debug.gui.time;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import javax.swing.table.*;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.trace.model.Trace;
|
||||
|
@ -37,6 +38,7 @@ import ghidra.trace.model.TraceDomainObjectListener;
|
|||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.model.time.TraceTimeManager;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
|
||||
public class DebuggerSnapshotTablePanel extends JPanel {
|
||||
|
||||
|
@ -131,6 +133,23 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
|||
}
|
||||
}
|
||||
|
||||
final TableCellRenderer boldCurrentRenderer = new AbstractGColumnRenderer<Object>() {
|
||||
@Override
|
||||
public String getFilterString(Object t, Settings settings) {
|
||||
return t == null ? "<null>" : t.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
SnapshotRow row = (SnapshotRow) data.getRowObject();
|
||||
if (row != null && row.getSnap() == currentSnap) {
|
||||
setBold();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel;
|
||||
protected final GTable snapshotTable;
|
||||
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
|
||||
|
@ -155,14 +174,19 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
|||
TableColumnModel columnModel = snapshotTable.getColumnModel();
|
||||
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
|
||||
snapCol.setPreferredWidth(40);
|
||||
snapCol.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
|
||||
timeCol.setPreferredWidth(200);
|
||||
timeCol.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
|
||||
etCol.setPreferredWidth(40);
|
||||
etCol.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
|
||||
schdCol.setPreferredWidth(60);
|
||||
schdCol.setCellRenderer(boldCurrentRenderer);
|
||||
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
|
||||
descCol.setPreferredWidth(200);
|
||||
descCol.setCellRenderer(boldCurrentRenderer);
|
||||
}
|
||||
|
||||
private void addNewListeners() {
|
||||
|
@ -260,5 +284,6 @@ public class DebuggerSnapshotTablePanel extends JPanel {
|
|||
return;
|
||||
}
|
||||
snapshotFilterPanel.setSelectedItem(row);
|
||||
snapshotTableModel.fireTableDataChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.time;
|
|||
|
||||
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.*;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Objects;
|
||||
|
||||
|
@ -146,9 +146,32 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
|
|||
return;
|
||||
}
|
||||
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
|
||||
traceManager.activateSnap(snap);
|
||||
contextChanged();
|
||||
});
|
||||
mainPanel.snapshotTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
activateSelectedSnapshot();
|
||||
}
|
||||
}
|
||||
});
|
||||
mainPanel.snapshotTable.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
activateSelectedSnapshot();
|
||||
e.consume(); // lest it select the next row down
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void activateSelectedSnapshot() {
|
||||
Long snap = mainPanel.getSelectedSnapshot();
|
||||
if (snap != null && traceManager != null) {
|
||||
traceManager.activateSnap(snap);
|
||||
}
|
||||
}
|
||||
|
||||
protected void createActions() {
|
||||
|
|
|
@ -387,15 +387,33 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
|||
return findFocusScope() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportsActivation() {
|
||||
return findActiveScope() != null;
|
||||
}
|
||||
|
||||
// NOTE: This may require the scope to be an ancestor of the target
|
||||
// That should be fine
|
||||
protected TargetFocusScope findFocusScope() {
|
||||
List<String> path = target.getModel()
|
||||
.getRootSchema()
|
||||
.searchForSuitable(TargetFocusScope.class, target.getPath());
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
return (TargetFocusScope) target.getModel().getModelObject(path);
|
||||
}
|
||||
|
||||
protected TargetActiveScope findActiveScope() {
|
||||
List<String> path = target.getModel()
|
||||
.getRootSchema()
|
||||
.searchForSuitable(TargetActiveScope.class, target.getPath());
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
return (TargetActiveScope) target.getModel().getModelObject(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetObject getFocus() {
|
||||
if (curFocus == null) {
|
||||
|
@ -419,8 +437,8 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
|||
@Override
|
||||
public CompletableFuture<Boolean> requestFocus(TargetObject focus) {
|
||||
if (!isSupportsFocus()) {
|
||||
return CompletableFuture
|
||||
.failedFuture(new IllegalArgumentException("Target does not support focus"));
|
||||
return CompletableFuture.failedFuture(
|
||||
new IllegalArgumentException("Target does not support focus"));
|
||||
}
|
||||
if (!PathUtils.isAncestor(getTarget().getPath(), focus.getPath())) {
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException(
|
||||
|
@ -446,6 +464,36 @@ public class DefaultTraceRecorder implements TraceRecorder {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> requestActivation(TargetObject active) {
|
||||
if (!isSupportsActivation()) {
|
||||
return CompletableFuture.failedFuture(
|
||||
new IllegalArgumentException("Target does not support activation"));
|
||||
}
|
||||
if (!PathUtils.isAncestor(getTarget().getPath(), active.getPath())) {
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException(
|
||||
"Requested activation path is not a successor of the target"));
|
||||
}
|
||||
TargetActiveScope activeScope = findActiveScope();
|
||||
if (!PathUtils.isAncestor(activeScope.getPath(), active.getPath())) {
|
||||
// This should be rare, if not forbidden
|
||||
return CompletableFuture.failedFuture(new IllegalArgumentException(
|
||||
"Requested activation path is not a successor of the focus scope"));
|
||||
}
|
||||
return activeScope.requestActivation(active).thenApply(__ -> true).exceptionally(ex -> {
|
||||
ex = AsyncUtils.unwrapThrowable(ex);
|
||||
String msg = "Could not activate " + active + ": " + ex.getMessage();
|
||||
plugin.getTool().setStatusInfo(msg);
|
||||
if (ex instanceof DebuggerModelAccessException) {
|
||||
Msg.info(this, msg);
|
||||
}
|
||||
else {
|
||||
Msg.error(this, "Could not activate " + active, ex);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/*---------------- ACCESSOR METHODS -------------------*/
|
||||
|
||||
@Override
|
||||
|
|
|
@ -737,6 +737,11 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
|||
return objectRecorder.isSupportsFocus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportsActivation() {
|
||||
return objectRecorder.isSupportsActivation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TargetObject getFocus() {
|
||||
return curFocus;
|
||||
|
@ -764,6 +769,28 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
|
|||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> requestActivation(TargetObject active) {
|
||||
for (TargetActiveScope scope : objectRecorder.collectTargetSuccessors(target,
|
||||
TargetActiveScope.class)) {
|
||||
if (PathUtils.isAncestor(scope.getPath(), active.getPath())) {
|
||||
return scope.requestActivation(active).thenApply(__ -> true).exceptionally(ex -> {
|
||||
ex = AsyncUtils.unwrapThrowable(ex);
|
||||
String msg = "Could not activate " + active + ": " + ex.getMessage();
|
||||
if (ex instanceof DebuggerModelAccessException) {
|
||||
Msg.info(this, msg);
|
||||
}
|
||||
else {
|
||||
Msg.error(this, msg, ex);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
Msg.info(this, "Could not find suitable active scope for " + active);
|
||||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
// UNUSED?
|
||||
@Override
|
||||
public CompletableFuture<Void> flushTransactions() {
|
||||
|
|
|
@ -26,14 +26,13 @@ import com.google.gson.JsonElement;
|
|||
|
||||
import ghidra.dbg.DebuggerObjectModel;
|
||||
import ghidra.dbg.attributes.TargetDataType;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKind;
|
||||
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
|
||||
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
|
||||
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
|
||||
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
|
||||
import ghidra.dbg.target.TargetFocusScope;
|
||||
import ghidra.dbg.target.TargetMethod.TargetParameterMap;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKind;
|
||||
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
|
||||
import ghidra.dbg.target.schema.TargetObjectSchema;
|
||||
|
@ -53,6 +52,7 @@ class ObjectRecorder {
|
|||
protected final ObjectBasedTraceRecorder recorder;
|
||||
protected final TraceObjectManager objectManager;
|
||||
protected final boolean isSupportsFocus;
|
||||
protected final boolean isSupportsActivation;
|
||||
|
||||
private final BidiMap<IDKeyed<TargetObject>, IDKeyed<TraceObject>> objectMap =
|
||||
new DualHashBidiMap<>();
|
||||
|
@ -62,6 +62,7 @@ class ObjectRecorder {
|
|||
this.objectManager = recorder.trace.getObjectManager();
|
||||
TargetObjectSchema schema = recorder.target.getSchema();
|
||||
this.isSupportsFocus = !schema.searchFor(TargetFocusScope.class, false).isEmpty();
|
||||
this.isSupportsActivation = !schema.searchFor(TargetActiveScope.class, false).isEmpty();
|
||||
|
||||
try (UndoableTransaction tid =
|
||||
UndoableTransaction.start(recorder.trace, "Create root")) {
|
||||
|
|
|
@ -67,8 +67,8 @@ import ghidra.util.exception.*;
|
|||
import ghidra.util.task.*;
|
||||
|
||||
@PluginInfo(
|
||||
shortDescription = "Debugger Trace View Management Plugin",
|
||||
description = "Manages UI Components, Wrappers, Focus, etc.",
|
||||
shortDescription = "Debugger Trace Management Plugin",
|
||||
description = "Manages the set of open traces, current views, etc.",
|
||||
category = PluginCategoryNames.DEBUGGER,
|
||||
packageName = DebuggerPluginPackage.NAME,
|
||||
status = PluginStatus.RELEASED,
|
||||
|
@ -244,8 +244,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
if (curRecorder == null) {
|
||||
return;
|
||||
}
|
||||
// TODO: Also re-sync focused thread/frame?
|
||||
activateNoFocus(current.snap(curRecorder.getSnap()), ActivationCause.FOLLOW_PRESENT);
|
||||
DebuggerCoordinates coords = current;
|
||||
TargetObject focus = curRecorder.getFocus();
|
||||
if (focus != null && synchronizeActive.get()) {
|
||||
coords = coords.object(focus);
|
||||
}
|
||||
coords = coords.snap(curRecorder.getSnap());
|
||||
activateAndNotify(coords, ActivationCause.FOLLOW_PRESENT, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,7 +267,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
||||
protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
|
||||
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
||||
protected final AsyncReference<Boolean, Void> synchronizeFocus = new AsyncReference<>(true);
|
||||
protected final AsyncReference<Boolean, Void> synchronizeActive = new AsyncReference<>(true);
|
||||
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
|
||||
protected final AsyncReference<Boolean, Void> autoCloseOnTerminate = new AsyncReference<>(true);
|
||||
|
||||
|
@ -585,7 +590,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
|
||||
protected boolean doModelObjectFocused(TargetObject obj, boolean requirePresent) {
|
||||
curObj = obj;
|
||||
if (!synchronizeFocus.get()) {
|
||||
if (!synchronizeActive.get()) {
|
||||
return false;
|
||||
}
|
||||
if (requirePresent && !current.isDeadOrPresent()) {
|
||||
|
@ -612,7 +617,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
return false;
|
||||
}
|
||||
}
|
||||
activateNoFocus(getCurrentFor(trace).object(obj), ActivationCause.SYNC_MODEL);
|
||||
activateAndNotify(getCurrentFor(trace).object(obj), ActivationCause.SYNC_MODEL, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1086,14 +1091,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
return elem.toString();
|
||||
}
|
||||
|
||||
protected void activateNoFocus(DebuggerCoordinates coordinates, ActivationCause cause) {
|
||||
DebuggerCoordinates resolved = doSetCurrent(coordinates, cause);
|
||||
if (resolved == null) {
|
||||
return;
|
||||
}
|
||||
prepareViewAndFireEvent(resolved, cause);
|
||||
}
|
||||
|
||||
protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) {
|
||||
if (!Objects.equals(prev.getObject(), resolved.getObject())) {
|
||||
return false;
|
||||
|
@ -1110,7 +1107,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
return true;
|
||||
}
|
||||
|
||||
protected static TargetObject translateToFocus(DebuggerCoordinates prev,
|
||||
protected static TargetObject translateToTarget(DebuggerCoordinates prev,
|
||||
DebuggerCoordinates resolved) {
|
||||
if (!resolved.isAliveAndPresent()) {
|
||||
return null;
|
||||
|
@ -1141,7 +1138,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
|
||||
@Override
|
||||
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
||||
ActivationCause cause, boolean syncTargetFocus) {
|
||||
ActivationCause cause, boolean syncTarget) {
|
||||
DebuggerCoordinates prev;
|
||||
DebuggerCoordinates resolved;
|
||||
|
||||
|
@ -1158,21 +1155,21 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
return AsyncUtils.NIL;
|
||||
}
|
||||
CompletableFuture<Void> future = prepareViewAndFireEvent(resolved, cause);
|
||||
if (!syncTargetFocus) {
|
||||
if (!syncTarget) {
|
||||
return future;
|
||||
}
|
||||
if (!synchronizeFocus.get()) {
|
||||
if (!synchronizeActive.get()) {
|
||||
return future;
|
||||
}
|
||||
TraceRecorder recorder = resolved.getRecorder();
|
||||
if (recorder == null) {
|
||||
return future;
|
||||
}
|
||||
TargetObject focus = translateToFocus(prev, resolved);
|
||||
if (focus == null || !focus.isValid()) {
|
||||
TargetObject activate = translateToTarget(prev, resolved);
|
||||
if (activate == null || !activate.isValid()) {
|
||||
return future;
|
||||
}
|
||||
recorder.requestFocus(focus);
|
||||
recorder.requestActivation(activate);
|
||||
return future;
|
||||
}
|
||||
|
||||
|
@ -1225,24 +1222,24 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setSynchronizeFocus(boolean enabled) {
|
||||
synchronizeFocus.set(enabled, null);
|
||||
public void setSynchronizeActive(boolean enabled) {
|
||||
synchronizeActive.set(enabled, null);
|
||||
// TODO: Which action to take here, if any?
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSynchronizeFocus() {
|
||||
return synchronizeFocus.get();
|
||||
public boolean isSynchronizeActive() {
|
||||
return synchronizeActive.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener) {
|
||||
synchronizeFocus.addChangeListener(listener);
|
||||
public void addSynchronizeActiveChangeListener(BooleanChangeAdapter listener) {
|
||||
synchronizeActive.addChangeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener) {
|
||||
synchronizeFocus.removeChangeListener(listener);
|
||||
public void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener) {
|
||||
synchronizeActive.removeChangeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -54,7 +54,7 @@ public interface DebuggerTraceManagerService {
|
|||
*/
|
||||
START_RECORDING,
|
||||
/**
|
||||
* The change was driven by the model focus, possibly indirectly by the user
|
||||
* The change was driven by the model activation, possibly indirectly by the user
|
||||
*/
|
||||
SYNC_MODEL,
|
||||
/**
|
||||
|
@ -281,11 +281,11 @@ public interface DebuggerTraceManagerService {
|
|||
*
|
||||
* @param coordinates the desired coordinates
|
||||
* @param cause the cause of the activation
|
||||
* @param syncTargetFocus true synchronize the current target to the same coordinates
|
||||
* @param syncTarget true synchronize the current target to the same coordinates
|
||||
* @return a future which completes when emulation and navigation is complete
|
||||
*/
|
||||
CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
|
||||
ActivationCause cause, boolean syncTargetFocus);
|
||||
ActivationCause cause, boolean syncTarget);
|
||||
|
||||
/**
|
||||
* Activate the given coordinates, caused by the user
|
||||
|
@ -314,7 +314,7 @@ public interface DebuggerTraceManagerService {
|
|||
*
|
||||
* <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.
|
||||
* coordinates for a trace, the target's last/current activation, the list of live threads, etc.
|
||||
*
|
||||
* @param trace the trace
|
||||
* @return the best coordinates
|
||||
|
@ -448,32 +448,32 @@ public interface DebuggerTraceManagerService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Control whether trace activation is synchronized with debugger focus/select
|
||||
* Control whether trace activation is synchronized with debugger activation
|
||||
*
|
||||
* @param enabled true to synchronize, false otherwise
|
||||
*/
|
||||
void setSynchronizeFocus(boolean enabled);
|
||||
void setSynchronizeActive(boolean enabled);
|
||||
|
||||
/**
|
||||
* Check whether trace activation is synchronized with debugger focus/select
|
||||
* Check whether trace activation is synchronized with debugger activation
|
||||
*
|
||||
* @return true if synchronized, false otherwise
|
||||
*/
|
||||
boolean isSynchronizeFocus();
|
||||
boolean isSynchronizeActive();
|
||||
|
||||
/**
|
||||
* Add a listener for changes to focus synchronization enablement
|
||||
* Add a listener for changes to activation synchronization enablement
|
||||
*
|
||||
* @param listener the listener to receive change notifications
|
||||
*/
|
||||
void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
||||
void addSynchronizeActiveChangeListener(BooleanChangeAdapter listener);
|
||||
|
||||
/**
|
||||
* Remove a listener for changes to focus synchronization enablement
|
||||
* Remove a listener for changes to activation synchronization enablement
|
||||
*
|
||||
* @param listener the listener receiving change notifications
|
||||
*/
|
||||
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
|
||||
void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener);
|
||||
|
||||
/**
|
||||
* Control whether traces should be saved by default
|
||||
|
|
|
@ -722,6 +722,13 @@ public interface TraceRecorder {
|
|||
*/
|
||||
boolean isSupportsFocus();
|
||||
|
||||
/**
|
||||
* Check if the target is subject to a {@link TargetActiveScope}.
|
||||
*
|
||||
* @return true if an applicable scope is found, false otherwise.
|
||||
*/
|
||||
boolean isSupportsActivation();
|
||||
|
||||
/**
|
||||
* Get the last-focused object as observed by this recorder
|
||||
*
|
||||
|
@ -748,6 +755,14 @@ public interface TraceRecorder {
|
|||
*/
|
||||
CompletableFuture<Boolean> requestFocus(TargetObject focus);
|
||||
|
||||
/**
|
||||
* Request activation of a successor of the target
|
||||
*
|
||||
* @param active the object to activate
|
||||
* @return a future which completes with true if the operation was successful, false otherwise.
|
||||
*/
|
||||
CompletableFuture<Boolean> requestActivation(TargetObject active);
|
||||
|
||||
/**
|
||||
* Wait for pending transactions finish execution.
|
||||
*
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.model;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -25,6 +26,8 @@ import org.junit.*;
|
|||
|
||||
import docking.widgets.table.DynamicTableColumn;
|
||||
import docking.widgets.table.GDynamicColumnTableModel;
|
||||
import docking.widgets.tree.GTree;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
||||
|
@ -52,45 +55,44 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
|
||||
static {
|
||||
try {
|
||||
CTX = XmlSchemaContext.deserialize(
|
||||
"""
|
||||
<context>
|
||||
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
||||
<attribute name='Processes' schema='ProcessContainer' />
|
||||
<interface name='EventScope' />
|
||||
</schema>
|
||||
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='ONCE'>
|
||||
<element schema='Process' />
|
||||
</schema>
|
||||
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
|
||||
<attribute name='Threads' schema='ThreadContainer' />
|
||||
<attribute name='Handles' schema='HandleContainer' />
|
||||
</schema>
|
||||
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='ONCE'>
|
||||
<element schema='Thread' />
|
||||
</schema>
|
||||
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
||||
<interface name='Thread' />
|
||||
<attribute name='_display' schema='STRING' />
|
||||
<attribute name='_self' schema='Thread' />
|
||||
<attribute name='Stack' schema='Stack' />
|
||||
</schema>
|
||||
<schema name='Stack' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='ONCE'>
|
||||
<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>
|
||||
""");
|
||||
CTX = XmlSchemaContext.deserialize("""
|
||||
<context>
|
||||
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
||||
<attribute name='Processes' schema='ProcessContainer' />
|
||||
<interface name='EventScope' />
|
||||
</schema>
|
||||
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='ONCE'>
|
||||
<element schema='Process' />
|
||||
</schema>
|
||||
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
|
||||
<attribute name='Threads' schema='ThreadContainer' />
|
||||
<attribute name='Handles' schema='HandleContainer' />
|
||||
</schema>
|
||||
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='ONCE'>
|
||||
<element schema='Thread' />
|
||||
</schema>
|
||||
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
||||
<interface name='Thread' />
|
||||
<attribute name='_display' schema='STRING' />
|
||||
<attribute name='_self' schema='Thread' />
|
||||
<attribute name='Stack' schema='Stack' />
|
||||
</schema>
|
||||
<schema name='Stack' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='ONCE'>
|
||||
<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) {
|
||||
throw new AssertionError();
|
||||
|
@ -241,9 +243,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
protected void assertPathIs(TraceObjectKeyPath path, int elemCount, int attrCount) {
|
||||
assertEquals(path, modelProvider.getPath());
|
||||
assertEquals(path.toString(), modelProvider.pathField.getText());
|
||||
AbstractNode item = modelProvider.objectsTreePanel.getSelectedItem();
|
||||
assertNotNull(item);
|
||||
assertEquals(path, item.getValue().getChild().getCanonicalPath());
|
||||
// Table model is threaded
|
||||
waitForPass(() -> assertEquals(elemCount,
|
||||
modelProvider.elementsTablePanel.tableModel.getModelData().size()));
|
||||
|
@ -394,13 +393,40 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
assertPathIsThreadsContainer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleClickObjectInObjectsTree() 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();
|
||||
|
||||
GTree tree = modelProvider.objectsTreePanel.tree;
|
||||
GTreeNode node = waitForPass(() -> {
|
||||
GTreeNode n = Unique.assertOne(tree.getSelectedNodes());
|
||||
assertEquals("Processes", n.getName());
|
||||
return n;
|
||||
});
|
||||
clickTreeNode(tree, node, MouseEvent.BUTTON1);
|
||||
clickTreeNode(tree, node, MouseEvent.BUTTON1);
|
||||
waitForSwing();
|
||||
waitForPass(() -> assertEquals(processes, traceManager.getCurrentObject()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleClickLinkInElementsTable() throws Throwable {
|
||||
createTraceAndPopulateObjects();
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Links"));
|
||||
TraceObjectKeyPath pathLinks = TraceObjectKeyPath.parse("Processes[0].Links");
|
||||
modelProvider.setPath(pathLinks);
|
||||
waitForTasks();
|
||||
|
||||
ValueRow row2 = waitForValue(() -> {
|
||||
|
@ -421,7 +447,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
});
|
||||
clickTableCell(modelProvider.elementsTablePanel.table, rowIndex, 0, 2);
|
||||
|
||||
assertPathIs(TraceObjectKeyPath.parse("Processes[0].Threads[7]"), 0, 3);
|
||||
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[7]"),
|
||||
traceManager.getCurrentObject().getCanonicalPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -451,7 +478,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
});
|
||||
clickTableCell(modelProvider.elementsTablePanel.table, rowIndex, 0, 2);
|
||||
|
||||
assertPathIs(TraceObjectKeyPath.parse("Processes[0].Threads[2]"), 0, 3);
|
||||
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[2]"),
|
||||
traceManager.getCurrentObject().getCanonicalPath());
|
||||
}
|
||||
|
||||
protected void selectAttribute(String key) {
|
||||
|
@ -492,7 +520,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
});
|
||||
clickTableCell(modelProvider.attributesTablePanel.table, rowIndex, 0, 2);
|
||||
|
||||
assertPathIs(TraceObjectKeyPath.parse("Processes[0].Threads[3]"), 0, 5);
|
||||
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads[3]"),
|
||||
traceManager.getCurrentObject().getCanonicalPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -528,7 +557,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
});
|
||||
clickTableCell(modelProvider.attributesTablePanel.table, rowIndex, 0, 2);
|
||||
|
||||
assertPathIsThreadsContainer();
|
||||
assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads"),
|
||||
traceManager.getCurrentObject().getCanonicalPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -565,7 +595,9 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]"));
|
||||
TraceObjectKeyPath thread2Path = TraceObjectKeyPath.parse("Processes[0].Threads[2]");
|
||||
modelProvider.setPath(thread2Path);
|
||||
modelProvider.setTreeSelection(thread2Path);
|
||||
waitForTasks();
|
||||
|
||||
AbstractNode nodeThread2 =
|
||||
|
@ -603,7 +635,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
|
||||
TraceObjectKeyPath thread3Path = TraceObjectKeyPath.parse("Processes[0].Threads[3]");
|
||||
assertPathIs(thread3Path, 0, 5);
|
||||
assertEquals(thread3Path, traceManager.getCurrentObject().getCanonicalPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -790,52 +821,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
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();
|
||||
|
@ -865,11 +850,12 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
|
||||
traceManager.activateObject(processes);
|
||||
waitForTasks();
|
||||
assertEquals(processes, modelProvider.getTreeSelection().getChild());
|
||||
modelProvider.setTreeSelection(processes.getCanonicalPath());
|
||||
waitForSwing();
|
||||
|
||||
// TODO: Is this the desired behavior?
|
||||
traceManager.activateObject(root);
|
||||
waitForTasks();
|
||||
// TODO: Is this the desired behavior?
|
||||
assertEquals(processes, modelProvider.getTreeSelection().getChild());
|
||||
}
|
||||
|
||||
|
@ -925,6 +911,12 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
TraceObject processes = root.getAttribute(0, "Processes").getChild();
|
||||
traceManager.activateObject(root);
|
||||
waitForTasks();
|
||||
// Warm it up a bit. TODO: This is kind of cheating.
|
||||
traceManager.activateObject(processes);
|
||||
waitForTasks();
|
||||
traceManager.activateObject(root);
|
||||
modelProvider.setPath(root.getCanonicalPath());
|
||||
waitForTasks();
|
||||
|
||||
/**
|
||||
* TODO: It's interesting that activating a parent then a child produces a different end
|
||||
|
|
|
@ -17,11 +17,11 @@ package ghidra.app.plugin.core.debug.gui.stack;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.Unique;
|
||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
|
@ -404,32 +404,6 @@ public class DebuggerStackProviderLegacyTest extends AbstractGhidraHeadedDebugge
|
|||
assertProviderEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("TODO") // Not sure why this fails under Gradle but not my IDE
|
||||
public void testSelectRowActivatesFrame() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
TraceThread thread = addThread("Processes[1].Threads[1]");
|
||||
TraceStack stack = addStack(thread);
|
||||
addStackFrames(stack);
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
assertProviderPopulated();
|
||||
|
||||
clickTableCellWithButton(stackProvider.legacyPanel.stackTable, 0, 0, MouseEvent.BUTTON1);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(0, traceManager.getCurrentFrame());
|
||||
|
||||
clickTableCellWithButton(stackProvider.legacyPanel.stackTable, 1, 0, MouseEvent.BUTTON1);
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(1, traceManager.getCurrentFrame());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateFrameSelectsRow() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
@ -455,6 +429,27 @@ public class DebuggerStackProviderLegacyTest extends AbstractGhidraHeadedDebugge
|
|||
assertEquals(1, stackProvider.legacyPanel.stackTable.getSelectedRow());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleClickRowActivatesFrame() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
TraceThread thread = addThread("Processes[1].Threads[1]");
|
||||
TraceStack stack = addStack(thread);
|
||||
addStackFrames(stack);
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
traceManager.activateThread(thread);
|
||||
waitForSwing();
|
||||
|
||||
assertProviderPopulated();
|
||||
|
||||
clickTableCell(stackProvider.legacyPanel.stackTable, 0, 0, 2);
|
||||
assertEquals(0, traceManager.getCurrentFrame());
|
||||
|
||||
clickTableCell(stackProvider.legacyPanel.stackTable, 1, 0, 2);
|
||||
assertEquals(1, traceManager.getCurrentFrame());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateThenAddMappingPopulatesFunctionColumn() throws Exception {
|
||||
createTrace();
|
||||
|
|
|
@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates;
|
|||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
|
||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
|
||||
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
|
||||
import ghidra.app.services.DebuggerStaticMappingService;
|
||||
|
@ -487,33 +488,6 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
waitForPass(() -> assertProviderEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectRowActivateFrame() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
TraceObjectThread thread = addThread(1);
|
||||
TraceObjectStack stack = addStack(thread);
|
||||
addStackFrames(stack);
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
traceManager.activateObject(thread.getObject());
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
|
||||
TraceObject frame0 = stack.getObject().getElement(0, 0).getChild();
|
||||
TraceObject frame1 = stack.getObject().getElement(0, 1).getChild();
|
||||
List<ValueRow> allItems = stackProvider.panel.getAllItems();
|
||||
|
||||
stackProvider.panel.setSelectedItem(allItems.get(1));
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertEquals(frame1, traceManager.getCurrentObject()));
|
||||
|
||||
stackProvider.panel.setSelectedItem(allItems.get(0));
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertEquals(frame0, traceManager.getCurrentObject()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateFrameSelectsRow() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
@ -541,6 +515,32 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
|
|||
waitForPass(() -> assertEquals(allItems.get(0), stackProvider.panel.getSelectedItem()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleClickRowActivateFrame() throws Exception {
|
||||
createAndOpenTrace();
|
||||
|
||||
TraceObjectThread thread = addThread(1);
|
||||
TraceObjectStack stack = addStack(thread);
|
||||
addStackFrames(stack);
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
traceManager.activateObject(thread.getObject());
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> assertProviderPopulated());
|
||||
|
||||
TraceObject frame0 = stack.getObject().getElement(0, 0).getChild();
|
||||
TraceObject frame1 = stack.getObject().getElement(0, 1).getChild();
|
||||
|
||||
clickTableCell(QueryPanelTestHelper.getTable(stackProvider.panel), 1, 0, 2);
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertEquals(frame1, traceManager.getCurrentObject()));
|
||||
|
||||
clickTableCell(QueryPanelTestHelper.getTable(stackProvider.panel), 0, 0, 2);
|
||||
waitForTasks();
|
||||
waitForPass(() -> assertEquals(frame0, traceManager.getCurrentObject()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateTheAddMappingPopulatesFunctionColumn() throws Exception {
|
||||
createTrace();
|
||||
|
|
|
@ -364,21 +364,16 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSelectThreadInTableActivatesThread() throws Exception {
|
||||
public void testDoubleClickThreadInTableActivatesThread() throws Exception {
|
||||
createAndOpenTrace();
|
||||
addThreads();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
assertThreadsPopulated();
|
||||
assertThreadSelected(thread1); // Manager selects default if not live
|
||||
|
||||
clickTableCellWithButton(threadsProvider.legacyPanel.threadTable, 1, 0, MouseEvent.BUTTON1);
|
||||
|
||||
waitForPass(() -> {
|
||||
assertThreadSelected(thread2);
|
||||
assertEquals(thread2, traceManager.getCurrentThread());
|
||||
});
|
||||
clickTableCell(threadsProvider.legacyPanel.threadTable, 1, 0, 2);
|
||||
assertEquals(thread2, traceManager.getCurrentThread());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -397,5 +392,4 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
|
|||
|
||||
assertEquals(6, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -112,26 +112,26 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
|
||||
public void activateObjectsMode() throws Exception {
|
||||
// NOTE the use of index='1' allowing object-based managers to ID unique path
|
||||
ctx = XmlSchemaContext.deserialize("" + //
|
||||
"<context>" + //
|
||||
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
|
||||
" <attribute name='Processes' schema='ProcessContainer' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='ONCE'>" + //
|
||||
" <element index='1' schema='Process' />" + // <---- NOTE HERE
|
||||
" </schema>" + //
|
||||
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + //
|
||||
" <attribute name='Threads' schema='ThreadContainer' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
|
||||
" attributeResync='ONCE'>" + //
|
||||
" <element schema='Thread' />" + //
|
||||
" </schema>" + //
|
||||
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
|
||||
" <interface name='Thread' />" + //
|
||||
" </schema>" + //
|
||||
"</context>");
|
||||
ctx = XmlSchemaContext.deserialize("""
|
||||
<context>
|
||||
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
|
||||
<attribute name='Processes' schema='ProcessContainer' />
|
||||
</schema>
|
||||
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='ONCE'>
|
||||
<element index='1' schema='Process' />
|
||||
</schema>
|
||||
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
|
||||
<attribute name='Threads' schema='ThreadContainer' />
|
||||
</schema>
|
||||
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
|
||||
attributeResync='ONCE'>
|
||||
<element schema='Thread' />
|
||||
</schema>
|
||||
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
|
||||
<interface name='Thread' />
|
||||
</schema>
|
||||
</context>""");
|
||||
|
||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
|
||||
|
@ -521,25 +521,18 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSelectThreadInTableActivatesThread() throws Exception {
|
||||
public void testDoubleClickThreadInTableActivatesThread() throws Exception {
|
||||
createAndOpenTrace();
|
||||
addThreads();
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForDomainObject(tb.trace);
|
||||
waitForTasks();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertThreadsPopulated();
|
||||
assertThreadSelected(thread1); // Manager selects default if not live
|
||||
});
|
||||
waitForPass(() -> assertThreadsPopulated());
|
||||
|
||||
GhidraTable table = QueryPanelTestHelper.getTable(provider.panel);
|
||||
clickTableCellWithButton(table, 1, 0, MouseEvent.BUTTON1);
|
||||
|
||||
waitForPass(() -> {
|
||||
assertThreadSelected(thread2);
|
||||
assertEquals(thread2, traceManager.getCurrentThread());
|
||||
});
|
||||
clickTableCell(table, 1, 0, 2);
|
||||
assertEquals(thread2, traceManager.getCurrentThread());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -286,31 +286,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
tb.trace.getTimeManager().getSnapshot(0, false).getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectRowActivatesSnap() throws Exception {
|
||||
createSnaplessTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
addSnapshots();
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
assertProviderEmpty();
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
|
||||
|
||||
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(0));
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(0, traceManager.getCurrentSnap());
|
||||
|
||||
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(1));
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(10, traceManager.getCurrentSnap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivateSnapSelectsRow() throws Exception {
|
||||
createSnaplessTrace();
|
||||
|
@ -341,6 +316,25 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
|
|||
assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleClickRowActivatesSnap() throws Exception {
|
||||
createSnaplessTrace();
|
||||
traceManager.openTrace(tb.trace);
|
||||
addSnapshots();
|
||||
waitForDomainObject(tb.trace);
|
||||
|
||||
assertProviderEmpty();
|
||||
|
||||
traceManager.activateTrace(tb.trace);
|
||||
waitForSwing();
|
||||
|
||||
clickTableCell(timeProvider.mainPanel.snapshotTable, 0, 0, 2);
|
||||
assertEquals(0, traceManager.getCurrentSnap());
|
||||
|
||||
clickTableCell(timeProvider.mainPanel.snapshotTable, 1, 0, 2);
|
||||
assertEquals(10, traceManager.getCurrentSnap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddScratchThenActivateIsHidden() throws Exception {
|
||||
createSnaplessTrace();
|
||||
|
|
|
@ -392,7 +392,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
|||
|
||||
@Test
|
||||
public void testSynchronizeFocusTraceToModel() throws Throwable {
|
||||
assertTrue(traceManager.isSynchronizeFocus());
|
||||
assertTrue(traceManager.isSynchronizeActive());
|
||||
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
@ -444,7 +444,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
|||
|
||||
waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus()));
|
||||
|
||||
traceManager.setSynchronizeFocus(false);
|
||||
traceManager.setSynchronizeActive(false);
|
||||
traceManager.activateFrame(1);
|
||||
waitForSwing();
|
||||
|
||||
|
@ -453,7 +453,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
|||
|
||||
@Test
|
||||
public void testSynchronizeFocusModelToTrace() throws Throwable {
|
||||
assertTrue(traceManager.isSynchronizeFocus());
|
||||
assertTrue(traceManager.isSynchronizeActive());
|
||||
|
||||
createTestModel();
|
||||
mb.createTestProcessesAndThreads();
|
||||
|
@ -499,7 +499,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
|
|||
|
||||
waitForPass(() -> assertEquals(0, traceManager.getCurrentFrame()));
|
||||
|
||||
traceManager.setSynchronizeFocus(false);
|
||||
traceManager.setSynchronizeActive(false);
|
||||
waitOn(mb.testModel.session.requestFocus(frame1));
|
||||
// Not super reliable, but at least wait for it to change in case it does
|
||||
Thread.sleep(200);
|
||||
|
|
|
@ -152,7 +152,7 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
|
|||
}
|
||||
|
||||
// allows us to change the font to bold as needed without erasing the original font
|
||||
private Font getFont(boolean bold) {
|
||||
protected Font getFont(boolean bold) {
|
||||
Font font = getFont();
|
||||
// check if someone set a new font on the renderer
|
||||
if (font != cachedDefaultFont && font != cachedBoldFont) {
|
||||
|
|