GP-3018: Navigate/activate by double-click. Various related fixes.

This commit is contained in:
Dan 2023-01-20 14:04:42 -05:00
parent 3073268203
commit d0c8c5a77a
63 changed files with 1182 additions and 713 deletions

View file

@ -71,7 +71,7 @@ public class DebugSystemObjectsImpl1 implements DebugSystemObjectsInternal {
@Override @Override
public void setCurrentThreadId(DebugThreadId id) { public void setCurrentThreadId(DebugThreadId id) {
HRESULT hr = jnaSysobj.SetCurrentThreadId(new ULONG(id.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); COMUtils.checkRC(hr);
} }
} }

View file

@ -49,7 +49,7 @@ public interface DbgModelSelectableObject extends DbgModelTargetObject {
} }
if (this instanceof DbgModelTargetStackFrame) { if (this instanceof DbgModelTargetStackFrame) {
DbgModelTargetStackFrame tf = (DbgModelTargetStackFrame) this; DbgModelTargetStackFrame tf = (DbgModelTargetStackFrame) this;
TargetObject ref = tf.getThread(); TargetObject ref = tf.getParentThread();
if (ref instanceof DbgModelTargetThread) { if (ref instanceof DbgModelTargetThread) {
DbgModelTargetThread tt = (DbgModelTargetThread) ref; DbgModelTargetThread tt = (DbgModelTargetThread) ref;
DbgThread thread = tt.getThread(); DbgThread thread = tt.getThread();

View file

@ -18,13 +18,10 @@ package agent.dbgeng.model.iface2;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import agent.dbgeng.manager.DbgEventsListenerAdapter; import agent.dbgeng.manager.*;
import agent.dbgeng.manager.DbgStackFrame;
import agent.dbgeng.manager.impl.DbgManagerImpl; import agent.dbgeng.manager.impl.DbgManagerImpl;
import agent.dbgeng.manager.impl.DbgThreadImpl;
import agent.dbgeng.model.iface1.DbgModelSelectableObject; import agent.dbgeng.model.iface1.DbgModelSelectableObject;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetStackFrame; import ghidra.dbg.target.TargetStackFrame;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
@ -51,7 +48,8 @@ public interface DbgModelTargetStackFrame extends //
@Override @Override
public default CompletableFuture<Void> setActive() { public default CompletableFuture<Void> setActive() {
DbgManagerImpl manager = getManager(); DbgManagerImpl manager = getManager();
DbgThreadImpl thread = manager.getCurrentThread(); DbgModelTargetThread targetThread = getParentThread();
DbgThread thread = targetThread.getThread();
String name = this.getName(); String name = this.getName();
String stripped = name.substring(1, name.length() - 1); String stripped = name.substring(1, name.length() - 1);
int index = Integer.decode(stripped); int index = Integer.decode(stripped);
@ -111,10 +109,6 @@ public interface DbgModelTargetStackFrame extends //
public void setFrame(DbgStackFrame frame); public void setFrame(DbgStackFrame frame);
public TargetObject getThread();
public Address getPC(); public Address getPC();
public DbgModelTargetProcess getProcess();
} }

View file

@ -23,7 +23,6 @@ import agent.dbgeng.manager.*;
import agent.dbgeng.model.iface1.DbgModelTargetFocusScope; import agent.dbgeng.model.iface1.DbgModelTargetFocusScope;
import agent.dbgeng.model.iface2.*; import agent.dbgeng.model.iface2.*;
import ghidra.dbg.target.TargetFocusScope; import ghidra.dbg.target.TargetFocusScope;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -79,8 +78,6 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
return PathUtils.makeKey(indexFrame(frame)); return PathUtils.makeKey(indexFrame(frame));
} }
protected final DbgModelTargetThread thread;
protected DbgStackFrame frame; protected DbgStackFrame frame;
protected Address pc; protected Address pc;
protected String func; protected String func;
@ -97,7 +94,6 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
DbgStackFrame frame) { DbgStackFrame frame) {
super(stack.getModel(), stack, keyFrame(frame), "StackFrame"); super(stack.getModel(), stack, keyFrame(frame), "StackFrame");
this.getModel().addModelObject(frame, this); this.getModel().addModelObject(frame, this);
this.thread = thread;
this.pc = getModel().getAddressSpace("ram").getAddress(-1); this.pc = getModel().getAddressSpace("ram").getAddress(-1);
changeAttributes(List.of(), List.of(), Map.of( // changeAttributes(List.of(), List.of(), Map.of( //
@ -162,21 +158,11 @@ public class DbgModelTargetStackFrameImpl extends DbgModelTargetObjectImpl
), "Refreshed"); ), "Refreshed");
} }
@Override
public TargetObject getThread() {
return thread.getParent();
}
@Override @Override
public Address getPC() { public Address getPC() {
return pc; return pc;
} }
@Override
public DbgModelTargetProcess getProcess() {
return ((DbgModelTargetThreadImpl) thread).getProcess();
}
/* /*
public void invalidateRegisterCaches() { public void invalidateRegisterCaches() {
listeners.fire.invalidateCacheRequested(this); listeners.fire.invalidateCacheRequested(this);

View file

@ -264,7 +264,8 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
* or be used as an example for other implementations. * or be used as an example for other implementations.
*/ */
if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) { 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; TargetObject cur = obj;
while (cur != null) { while (cur != null) {

View file

@ -75,8 +75,11 @@ public class GdbModelTargetStackFrame
this.registers = new GdbModelTargetStackFrameRegisterContainer(this); this.registers = new GdbModelTargetStackFrameRegisterContainer(this);
changeAttributes(List.of(), List.of(registers), Map.of( // changeAttributes(List.of(),
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame)), List.of(
registers),
Map.of(
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame)),
"Initialized"); "Initialized");
setFrame(frame); setFrame(frame);
} }
@ -99,14 +102,14 @@ public class GdbModelTargetStackFrame
this.func = frame.getFunction(); this.func = frame.getFunction();
// TODO: module? "from" // TODO: module? "from"
changeAttributes(List.of(), List.of( // changeAttributes(List.of(),
registers // List.of(
), Map.of( // registers),
PC_ATTRIBUTE_NAME, pc, // Map.of(
FUNC_ATTRIBUTE_NAME, func, // PC_ATTRIBUTE_NAME, pc,
DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame), // FUNC_ATTRIBUTE_NAME, func,
VALUE_ATTRIBUTE_NAME, pc // DISPLAY_ATTRIBUTE_NAME, display = computeDisplay(frame)),
), "Refreshed"); "Refreshed");
} }
protected void invalidateRegisterCaches() { protected void invalidateRegisterCaches() {

View file

@ -35,6 +35,18 @@
because it descends from an inferior or process. In almost every case, the selection directly 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> 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 <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 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 powerful implications. All objects may be represented in many ways, and one representation may
@ -170,6 +182,12 @@
"help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html">Breakpoint "help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html">Breakpoint
Marker</A> actions from the disassembly listings.</P> 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> <H2>Actions for Trace Management</H2>
<P>The following actions manage target tracing. Note that many other windows are not usable <P>The following actions manage target tracing. Note that many other windows are not usable

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

View file

@ -25,13 +25,15 @@
<P>The stack window displays the current trace's execution stack, as unwound and reported by <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 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 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 single synthetic frame is shown. See the <A href="#unwind_stack">Unwind Stack</A> action for an
incremental level refers to the next caller in the chain &mdash; most of the time. The current alternative mechanism that unwinds using Ghidra's program databases and works with emulation.
frame comprises one element of the tool's current "coordinates." Selecting a frame changes Level 0 always refers to the innermost frame, and each incremental level refers to the next
those coordinates, potentially causing other windows to display different information. Namely, caller in the chain &mdash; most of the time. The current frame comprises one element of the
the <A href="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> tool's current "coordinates." Double-clicking a frame changes those coordinates, potentially
window will show registers for the current frame, assuming they can be retrieved. The Listings causing other windows to display different information. Namely, the <A href=
may also navigate to the current frame's program counter.</P> "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> <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>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 <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. non-0 frames. Different platforms may have different subtleties in how they report PC.</LI>
Double-clicking this field will navigate to the address.</LI>
<LI>Function - the name of the containing function, if Ghidra has the corresponding module <LI>Function - the name of the containing function, if Ghidra has the corresponding module
image imported and analyzed.</LI> 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 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 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, 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%"> <TABLE width="100%">
<TBODY> <TBODY>
@ -101,8 +103,8 @@
<CODE>posOff_</CODE> for positive offsets). They represent unused or unknown entries.</LI> <CODE>posOff_</CODE> for positive offsets). They represent unused or unknown entries.</LI>
</UL> </UL>
<P>The frame entries are not automatically updated when a function's frame changes in a program <P>The frame entries are <EM>not</EM> automatically updated when a function's frame changes in
database. To update the unwind after changing a function's stack frame, you must unwind a program database. To update the unwind after changing a function's stack frame, you must
again.</P> unwind again.</P>
</BODY> </BODY>
</HTML> </HTML>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Before After
Before After

View file

@ -53,8 +53,8 @@
<H2>Navigating Threads</H2> <H2>Navigating Threads</H2>
<P>Selecting a thread in the table will navigate to (or "activate" or "focus") that thread. <P>Double-clicking a thread in the table will navigate to (or "activate" or "focus") that
Windows which are sensitive to the current thread will update. Notably, the <A href= thread. Windows which are sensitive to the current thread will update. Notably, the <A href=
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will "help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
display the activated thread's register values. <A href= display the activated thread's register values. <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Listing</A> windows with "help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Listing</A> windows with
@ -88,15 +88,15 @@
<H2>Actions</H2> <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 <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 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 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 to GDB so that commands typed into its CLI will refer to the same thread and frame as is active
focused in Ghidra. Conversely, any debugger events which indicate a change in focus are in Ghidra. Conversely, any target events which indicate a change in activation are translated,
translated, to the extent possible, into navigation coordinates and activated in Ghidra. For to the extent possible, into navigation coordinates and activated in Ghidra. For example, if
example, if the user issues a <CODE>frame</CODE> command to the CLI of a GDB connection, then the user issues a <CODE>frame</CODE> command to the CLI of a GDB connection, then Ghidra will
Ghidra will navigate to that same frame.</P> navigate to that same frame.</P>
</BODY> </BODY>
</HTML> </HTML>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

View file

@ -26,9 +26,9 @@
snapshot per event recorded. Other windows often display the times of various events or use 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 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 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 target's state, usually while suspended, along with any mark up. Double-clicking a snapshot
to the selected point in time. Note that browsing the past may prevent other windows from navigates to the selected point in time. Note that navigating to the past may change your <A
interacting with a live target.</P> href="help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control mode</A>.</P>
<H2>Table Columns</H2> <H2>Table Columns</H2>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

View file

@ -1669,11 +1669,11 @@ public interface DebuggerResources {
} }
} }
interface SynchronizeFocusAction { interface SynchronizeTargetAction {
String NAME = "Synchronize Focus"; String NAME = "Synchronize Target Activation";
String DESCRIPTION = "Synchronize trace activation with debugger focus/select"; String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
Icon ICON = ICON_SYNC; Icon ICON = ICON_SYNC;
String HELP_ANCHOR = "sync_focus"; String HELP_ANCHOR = "sync_target";
static ToggleActionBuilder builder(Plugin owner) { static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName(); String ownerName = owner.getName();

View file

@ -101,6 +101,7 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
private Trace trace; private Trace trace;
private long snap; private long snap;
private TraceObject curObject;
private Trace diffTrace; private Trace diffTrace;
private long diffSnap; private long diffSnap;
private ModelQuery query; private ModelQuery query;
@ -173,6 +174,23 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
return snap; 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() { protected void diffTraceChanged() {
refresh(); refresh();
} }

View file

@ -65,7 +65,7 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
table.addMouseListener(new MouseAdapter() { table.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) { if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
fireCellActivated(); fireCellActivated();
} }
} }
@ -90,13 +90,16 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
DebuggerCoordinates previous = current; DebuggerCoordinates previous = current;
this.current = coords; this.current = coords;
if (previous.getSnap() == current.getSnap() && if (previous.getSnap() == current.getSnap() &&
previous.getTrace() == current.getTrace()) { previous.getTrace() == current.getTrace() &&
previous.getObject() == current.getObject()) {
return; return;
} }
tableModel.setDiffTrace(previous.getTrace()); tableModel.setDiffTrace(previous.getTrace());
tableModel.setTrace(current.getTrace()); tableModel.setTrace(current.getTrace());
tableModel.setDiffSnap(previous.getSnap()); tableModel.setDiffSnap(previous.getSnap());
tableModel.setSnap(current.getSnap()); tableModel.setSnap(current.getSnap());
// current object is used only for bolding
tableModel.setCurrentObject(current.getObject());
if (limitToSnap) { if (limitToSnap) {
tableModel.setSpan(Lifespan.at(current.getSnap())); tableModel.setSpan(Lifespan.at(current.getSnap()));
} }

View file

@ -18,6 +18,7 @@ package ghidra.app.plugin.core.debug.gui.model;
import java.util.*; import java.util.*;
import ghidra.app.plugin.PluginCategoryNames; 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.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent;
import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent;
@ -112,12 +113,20 @@ public class DebuggerModelPlugin extends Plugin {
return Collections.unmodifiableList(disconnectedProviders); return Collections.unmodifiableList(disconnectedProviders);
} }
private void coordinatesActivated(DebuggerCoordinates current) {
connectedProvider.coordinatesActivated(current);
synchronized (disconnectedProviders) {
for (DebuggerModelProvider p : disconnectedProviders) {
p.coordinatesActivated(current);
}
}
}
@Override @Override
public void processEvent(PluginEvent event) { public void processEvent(PluginEvent event) {
super.processEvent(event); super.processEvent(event);
if (event instanceof TraceActivatedPluginEvent) { if (event instanceof TraceActivatedPluginEvent ev) {
TraceActivatedPluginEvent ev = (TraceActivatedPluginEvent) event; coordinatesActivated(ev.getActiveCoordinates());
connectedProvider.coordinatesActivated(ev.getActiveCoordinates());
} }
if (event instanceof TraceClosedPluginEvent) { if (event instanceof TraceClosedPluginEvent) {
TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event; TraceClosedPluginEvent ev = (TraceClosedPluginEvent) event;

View file

@ -47,6 +47,7 @@ import ghidra.framework.plugintool.AutoConfigState;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.annotation.AutoConfigStateField; import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.*; import ghidra.trace.model.target.*;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -155,7 +156,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
public boolean verify(JComponent input) { public boolean verify(JComponent input) {
try { try {
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText()); TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
setPath(path, pathField, EventOrigin.USER_GENERATED); setPath(path);
objectsTreePanel.setSelectedKeyPaths(List.of(path));
return true; return true;
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
@ -168,7 +170,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
ActionListener gotoPath = evt -> { ActionListener gotoPath = evt -> {
try { try {
TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText()); TraceObjectKeyPath path = TraceObjectKeyPath.parse(pathField.getText());
setPath(path, pathField, EventOrigin.USER_GENERATED); activatePath(path);
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner(); KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
@ -238,20 +240,30 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
myActionContext = null; myActionContext = null;
} }
contextChanged(); contextChanged();
if (sel.size() != 1) { if (sel.size() != 1) {
// TODO: Multiple paths? PathMatcher can do it, just have to parse
// Just leave whatever was there.
return; return;
} }
TraceObjectValue value = sel.get(0).getValue(); TraceObjectValue value = sel.get(0).getValue();
TraceObjectKeyPath path = value.getCanonicalPath(); setPath(value.getCanonicalPath(), objectsTreePanel);
// Prevent activation when selecting a link
EventOrigin origin =
value.isCanonical() ? evt.getEventOrigin() : EventOrigin.API_GENERATED;
setPath(path, objectsTreePanel, origin);
}); });
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 -> { elementsTablePanel.addSelectionListener(evt -> {
if (evt.getValueIsAdjusting()) { if (evt.getValueIsAdjusting()) {
return; return;
@ -278,9 +290,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
} }
TraceObject object = value.getChild(); TraceObject object = value.getChild();
attributesTablePanel.setQuery(ModelQuery.attributesOf(object.getCanonicalPath())); attributesTablePanel.setQuery(ModelQuery.attributesOf(object.getCanonicalPath()));
if (value.isCanonical()) {
activatePath(object.getCanonicalPath());
}
}); });
attributesTablePanel.addSelectionListener(evt -> { attributesTablePanel.addSelectionListener(evt -> {
if (evt.getValueIsAdjusting()) { if (evt.getValueIsAdjusting()) {
@ -297,15 +306,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
myActionContext = null; myActionContext = null;
} }
contextChanged(); contextChanged();
if (sel.size() != 1) {
return;
}
TraceObjectValue value = sel.get(0).getPath().getLastEntry();
// "canonical" implies "object"
if (value != null && value.isCanonical()) {
activatePath(value.getCanonicalPath());
}
}); });
elementsTablePanel.addCellActivationListener(elementActivationListener); elementsTablePanel.addCellActivationListener(elementActivationListener);
@ -315,6 +315,19 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
attributesTablePanel.addSeekListener(seekListener); 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 @Override
public ActionContext getActionContext(MouseEvent event) { public ActionContext getActionContext(MouseEvent event) {
if (myActionContext != null) { if (myActionContext != null) {
@ -352,11 +365,10 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
if (row == null) { if (row == null) {
return; return;
} }
if (!(row instanceof ObjectRow)) { if (!(row instanceof ObjectRow objectRow)) {
return; return;
} }
ObjectRow objectRow = (ObjectRow) row; activatePath(objectRow.getTraceObject().getCanonicalPath());
setPath(objectRow.getTraceObject().getCanonicalPath());
} }
private void activatedAttributesTable() { private void activatedAttributesTable() {
@ -365,11 +377,10 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
return; return;
} }
Object value = row.getValue(); Object value = row.getValue();
if (!(value instanceof TraceObject)) { if (!(value instanceof TraceObject object)) {
return; return;
} }
TraceObject object = (TraceObject) value; activatePath(object.getCanonicalPath());
setPath(object.getCanonicalPath());
} }
private void activatedCloneWindow() { private void activatedCloneWindow() {
@ -419,7 +430,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
if (values.size() != 1) { if (values.size() != 1) {
return; return;
} }
setPath(values.get(0).getChild().getCanonicalPath(), null, EventOrigin.USER_GENERATED); TraceObjectKeyPath canonicalPath = values.get(0).getChild().getCanonicalPath();
setPath(canonicalPath);
} }
@Override @Override
@ -476,11 +488,15 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
elementsTablePanel.goToCoordinates(coords); elementsTablePanel.goToCoordinates(coords);
attributesTablePanel.goToCoordinates(coords); attributesTablePanel.goToCoordinates(coords);
checkPath();
if (isClone) {
return;
}
// NOTE: The plugin only calls this on the connected provider // NOTE: The plugin only calls this on the connected provider
// When cloning or restoring state, we MUST still consider the object // When cloning or restoring state, we MUST still consider the object
TraceObject object = coords.getObject(); TraceObject object = coords.getObject();
if (object == null) { if (object == null) {
checkPath();
return; return;
} }
if (attributesTablePanel.trySelect(object)) { if (attributesTablePanel.trySelect(object)) {
@ -490,14 +506,15 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
return; return;
} }
if (findAsParent(object) != null) { if (findAsParent(object) != null) {
checkPath();
return; return;
} }
TraceObjectKeyPath sibling = findAsSibling(object); TraceObjectKeyPath sibling = findAsSibling(object);
if (sibling != null) { if (sibling != null) {
objectsTreePanel.setSelectedKeyPaths(List.of(sibling));
setPath(sibling); setPath(sibling);
} }
else { else {
objectsTreePanel.setSelectedObject(object);
setPath(object.getCanonicalPath()); setPath(object.getCanonicalPath());
} }
} }
@ -509,48 +526,52 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
} }
protected void activatePath(TraceObjectKeyPath path) { protected void activatePath(TraceObjectKeyPath path) {
if (isClone) {
return;
}
Trace trace = current.getTrace(); Trace trace = current.getTrace();
if (trace != null) { if (trace != null) {
TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path); TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path);
if (object != null) { if (object != null) {
traceManager.activateObject(object); 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) { if (Objects.equals(this.path, path) && getTreeSelection() != null) {
return; return;
} }
this.path = path; this.path = path;
if (source != pathField) {
pathField.setText(path.toString());
}
if (source != objectsTreePanel) { if (source != objectsTreePanel) {
setTreeSelection(path); setTreeSelection(path);
} }
if (origin == EventOrigin.USER_GENERATED) {
activatePath(path); pathField.setText(path.toString());
} objectsTreePanel.repaint();
elementsTablePanel.setQuery(ModelQuery.elementsOf(path)); elementsTablePanel.setQuery(ModelQuery.elementsOf(path));
attributesTablePanel.setQuery(ModelQuery.attributesOf(path)); attributesTablePanel.setQuery(ModelQuery.attributesOf(path));
checkPath(); checkPath();
} }
public void setPath(TraceObjectKeyPath path) {
setPath(path, null);
}
protected void checkPath() { protected void checkPath() {
if (objectsTreePanel.getNode(path) == null) { if (objectsTreePanel.getNode(path) == null) {
plugin.getTool().setStatusInfo("No such object at path " + path, true); plugin.getTool().setStatusInfo("No such object at path " + path, true);
} }
} }
public void setPath(TraceObjectKeyPath path) {
setPath(path, null, EventOrigin.API_GENERATED);
}
public TraceObjectKeyPath getPath() { public TraceObjectKeyPath getPath() {
return path; return path;
} }

View file

@ -181,6 +181,8 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
public interface ValueRow { public interface ValueRow {
String getKey(); String getKey();
TraceObject currentObject();
long currentSnap(); long currentSnap();
long previousSnap(); long previousSnap();
@ -212,6 +214,8 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
*/ */
boolean isModified(); boolean isModified();
boolean isCurrent();
default <T> ValueAttribute<T> getAttribute(String attributeName, Class<T> type) { default <T> ValueAttribute<T> getAttribute(String attributeName, Class<T> type) {
return new ValueAttribute<>(this, attributeName, type); return new ValueAttribute<>(this, attributeName, type);
} }
@ -250,6 +254,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
return getSnap(); return getSnap();
} }
@Override
public TraceObject currentObject() {
return getCurrentObject();
}
@Override @Override
public long previousSnap() { public long previousSnap() {
return getTrace() == getDiffTrace() ? getDiffSnap() : getSnap(); return getTrace() == getDiffTrace() ? getDiffSnap() : getSnap();
@ -313,6 +322,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
public boolean isAttributeModified(String attributeName) { public boolean isAttributeModified(String attributeName) {
return false; return false;
} }
@Override
public boolean isCurrent() {
return false;
}
} }
protected class ObjectRow extends AbstractValueRow { protected class ObjectRow extends AbstractValueRow {
@ -366,6 +380,15 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
public boolean isAttributeModified(String attributeName) { public boolean isAttributeModified(String attributeName) {
return isValueModified(getAttributeEntry(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) { protected ValueRow rowForValue(TraceObjectValue value) {
@ -375,46 +398,13 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
return new PrimitiveRow(value); return new PrimitiveRow(value);
} }
protected static class ColKey { protected record ColKey(String name, Class<?> type) {
public static ColKey fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) { public static ColKey fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) {
String name = attributeSchema.getName(); String name = attributeSchema.getName();
Class<?> type = Class<?> type =
TraceValueObjectAttributeColumn.computeAttributeType(ctx, attributeSchema); TraceValueObjectAttributeColumn.computeAttributeType(ctx, attributeSchema);
return new ColKey(name, type); 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> { static class AutoAttributeColumn<T> extends TraceValueObjectAttributeColumn<T> {

View file

@ -16,7 +16,6 @@
package ghidra.app.plugin.core.debug.gui.model; package ghidra.app.plugin.core.debug.gui.model;
import java.awt.*; import java.awt.*;
import java.awt.event.MouseListener;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.stream.*; import java.util.stream.*;
@ -26,6 +25,7 @@ import javax.swing.JTree;
import javax.swing.tree.TreePath; import javax.swing.tree.TreePath;
import docking.widgets.tree.GTree; import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeRenderer; import docking.widgets.tree.support.GTreeRenderer;
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin; import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
import docking.widgets.tree.support.GTreeSelectionListener; 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.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.target.*;
public class ObjectsTreePanel extends JPanel { public class ObjectsTreePanel extends JPanel {
@ -43,6 +43,21 @@ public class ObjectsTreePanel extends JPanel {
setHTMLRenderingEnabled(true); 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 @Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus) { boolean expanded, boolean leaf, int row, boolean hasFocus) {
@ -54,6 +69,7 @@ public class ObjectsTreePanel extends JPanel {
AbstractNode node = (AbstractNode) value; AbstractNode node = (AbstractNode) value;
setForeground(getForegroundFor(tree, node.isModified(), selected)); setForeground(getForegroundFor(tree, node.isModified(), selected));
setFont(getFont(isOnCurrentPath(node.getValue())));
return this; 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 ObjectTreeModel treeModel;
protected final GTree tree; protected final ObjectGTree tree;
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected boolean limitToSnap = true; protected boolean limitToSnap = true;
@ -83,7 +110,7 @@ public class ObjectsTreePanel extends JPanel {
public ObjectsTreePanel() { public ObjectsTreePanel() {
super(new BorderLayout()); super(new BorderLayout());
treeModel = createModel(); treeModel = createModel();
tree = new GTree(treeModel.getRoot()); tree = new ObjectGTree(treeModel.getRoot());
tree.setCellRenderer(new ObjectsTreeRenderer()); tree.setCellRenderer(new ObjectsTreeRenderer());
add(tree, BorderLayout.CENTER); add(tree, BorderLayout.CENTER);
@ -108,14 +135,14 @@ public class ObjectsTreePanel extends JPanel {
} }
public void goToCoordinates(DebuggerCoordinates coords) { public void goToCoordinates(DebuggerCoordinates coords) {
// TODO: thread should probably become a TraceObject once we transition
if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) { if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) {
return; return;
} }
DebuggerCoordinates previous = current; DebuggerCoordinates previous = current;
this.current = coords; this.current = coords;
if (previous.getSnap() == current.getSnap() && if (previous.getSnap() == current.getSnap() &&
previous.getTrace() == current.getTrace()) { previous.getTrace() == current.getTrace() &&
previous.getObject() == current.getObject()) {
return; return;
} }
try (KeepTreeState keep = keepTreeState()) { try (KeepTreeState keep = keepTreeState()) {
@ -127,6 +154,7 @@ public class ObjectsTreePanel extends JPanel {
treeModel.setSpan(Lifespan.at(current.getSnap())); treeModel.setSpan(Lifespan.at(current.getSnap()));
} }
tree.filterChanged(); tree.filterChanged();
// Repaint for bold current path is already going to happen
} }
} }
@ -210,20 +238,6 @@ public class ObjectsTreePanel extends JPanel {
tree.removeGTreeSelectionListener(listener); 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) { public void setSelectionMode(int selectionMode) {
tree.getSelectionModel().setSelectionMode(selectionMode); tree.getSelectionModel().setSelectionMode(selectionMode);
} }
@ -272,4 +286,30 @@ public class ObjectsTreePanel extends JPanel {
public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths) { public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths) {
setSelectedKeyPaths(keyPaths, EventOrigin.API_GENERATED); 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());
}
} }

View file

@ -95,6 +95,17 @@ public class PathTableModel extends AbstractQueryTableModel<PathRow> {
// Root is canonical // Root is canonical
return last == null || last.isCanonical(); 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) { public PathTableModel(Plugin plugin) {

View file

@ -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;
}
}

View file

@ -22,13 +22,21 @@ import ghidra.framework.plugintool.ServiceProvider;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectValPath; import ghidra.trace.model.target.TraceObjectValPath;
import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.model.target.TraceObjectValue;
import ghidra.util.table.column.GColumnRenderer;
public class TracePathLastKeyColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> { public class TracePathLastKeyColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
private final TracePathColumnRenderer<String> renderer = new TracePathColumnRenderer<>();
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Key"; return "Key";
} }
@Override
public GColumnRenderer<String> getColumnRenderer() {
return renderer;
}
@Override @Override
public String getValue(PathRow rowObject, Settings settings, Trace data, public String getValue(PathRow rowObject, Settings settings, Trace data,
ServiceProvider serviceProvider) throws IllegalArgumentException { ServiceProvider serviceProvider) throws IllegalArgumentException {

View file

@ -15,22 +15,51 @@
*/ */
package ghidra.app.plugin.core.debug.gui.model.columns; package ghidra.app.plugin.core.debug.gui.model.columns;
import java.awt.Component;
import docking.widgets.table.AbstractDynamicTableColumn; import docking.widgets.table.AbstractDynamicTableColumn;
import docking.widgets.table.GTableCellRenderingData;
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow; import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.model.target.TraceObjectValue;
import ghidra.util.table.column.AbstractGColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
public class TracePathLastLifespanColumn public class TracePathLastLifespanColumn
extends AbstractDynamicTableColumn<PathRow, Lifespan, Trace> { 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 @Override
public String getColumnName() { public String getColumnName() {
return "Life"; return "Life";
} }
@Override
public GColumnRenderer<Lifespan> getColumnRenderer() {
return renderer;
}
@Override @Override
public Lifespan getValue(PathRow rowObject, Settings settings, Trace data, public Lifespan getValue(PathRow rowObject, Settings settings, Trace data,
ServiceProvider serviceProvider) throws IllegalArgumentException { ServiceProvider serviceProvider) throws IllegalArgumentException {

View file

@ -21,13 +21,21 @@ import ghidra.dbg.util.PathUtils;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.util.table.column.GColumnRenderer;
public class TracePathStringColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> { public class TracePathStringColumn extends AbstractDynamicTableColumn<PathRow, String, Trace> {
private final TracePathColumnRenderer<String> renderer = new TracePathColumnRenderer<>();
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Path"; return "Path";
} }
@Override
public GColumnRenderer<String> getColumnRenderer() {
return renderer;
}
@Override @Override
public String getValue(PathRow rowObject, Settings settings, Trace data, public String getValue(PathRow rowObject, Settings settings, Trace data,
ServiceProvider serviceProvider) throws IllegalArgumentException { ServiceProvider serviceProvider) throws IllegalArgumentException {

View file

@ -50,6 +50,9 @@ public class TracePathValueColumn extends AbstractDynamicTableColumn<PathRow, Pa
setText(row.getHtmlDisplay()); setText(row.getHtmlDisplay());
setToolTipText(row.getToolTip()); setToolTipText(row.getToolTip());
setForeground(getForegroundFor(data.getTable(), row.isModified(), data.isSelected())); setForeground(getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
if (row.isCurrent()) {
setBold();
}
return this; return this;
} }

View file

@ -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;
}
}

View file

@ -23,8 +23,11 @@ import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.util.table.column.GColumnRenderer;
public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, String, Trace> { public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, String, Trace> {
private final TraceValueColumnRenderer<String> renderer = new TraceValueColumnRenderer<>();
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Key"; return "Key";
@ -36,6 +39,11 @@ public class TraceValueKeyColumn extends AbstractDynamicTableColumn<ValueRow, St
return rowObject.getKey(); return rowObject.getKey();
} }
@Override
public GColumnRenderer<String> getColumnRenderer() {
return renderer;
}
@Override @Override
public Comparator<String> getComparator() { public Comparator<String> getComparator() {
return TargetObjectKeyComparator.CHILD; return TargetObjectKeyComparator.CHILD;

View file

@ -21,15 +21,22 @@ import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.trace.model.Lifespan.LifeSet; import ghidra.trace.model.Lifespan.LifeSet;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.util.table.column.GColumnRenderer;
public class TraceValueLifeColumn public class TraceValueLifeColumn
extends AbstractDynamicTableColumn<ValueRow, LifeSet, Trace> { extends AbstractDynamicTableColumn<ValueRow, LifeSet, Trace> {
private final TraceValueColumnRenderer<LifeSet> renderer = new TraceValueColumnRenderer<>();
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Life"; return "Life";
} }
@Override
public GColumnRenderer<LifeSet> getColumnRenderer() {
return renderer;
}
@Override @Override
public LifeSet getValue(ValueRow rowObject, Settings settings, Trace data, public LifeSet getValue(ValueRow rowObject, Settings settings, Trace data,
ServiceProvider serviceProvider) throws IllegalArgumentException { ServiceProvider serviceProvider) throws IllegalArgumentException {

View file

@ -26,10 +26,6 @@ import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
import ghidra.dbg.target.schema.SchemaContext; import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema; 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; import ghidra.trace.model.target.TraceObject;
/** /**

View file

@ -56,8 +56,10 @@ public abstract class TraceValueObjectPropertyColumn<T>
ValueProperty<T> p = (ValueProperty<T>) data.getValue(); ValueProperty<T> p = (ValueProperty<T>) data.getValue();
setText(p.getHtmlDisplay()); setText(p.getHtmlDisplay());
setToolTipText(p.getToolTip()); setToolTipText(p.getToolTip());
setForeground(getForegroundFor(data.getTable(), p.isModified(), data.isSelected())); setForeground(getForegroundFor(data.getTable(), p.isModified(), data.isSelected()));
if (p.getRow().isCurrent()) {
setBold();
}
return this; return this;
} }

View file

@ -54,6 +54,9 @@ public class TraceValueValColumn extends AbstractDynamicTableColumn<ValueRow, Va
setToolTipText(row.getToolTip()); setToolTipText(row.getToolTip());
setForeground( setForeground(
getForegroundFor(data.getTable(), row.isModified(), data.isSelected())); getForegroundFor(data.getTable(), row.isModified(), data.isSelected()));
if (row.isCurrent()) {
setBold();
}
} }
catch (TraceClosedException e) { catch (TraceClosedException e) {
setText("ERROR: Trace Closed"); setText("ERROR: Trace Closed");

View file

@ -150,6 +150,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
protected Map<Long, Trace> traces = new HashMap<>(); protected Map<Long, Trace> traces = new HashMap<>();
protected Trace currentTrace; protected Trace currentTrace;
protected DebuggerObjectModel currentModel; protected DebuggerObjectModel currentModel;
private TargetObject targetFocus;
// NB: We're getting rid of this because the ObjectsProvider is beating the trace // NB: We're getting rid of this because the ObjectsProvider is beating the trace
// to the punch and causing the pattern-matcher to fail // to the punch and causing the pattern-matcher to fail
// private TraceRecorder recorder; // private TraceRecorder recorder;
@ -562,17 +563,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
synchronized (targetMap) { synchronized (targetMap) {
targetMap.put(key, container); targetMap.put(key, container);
refSet.add(targetObject); refSet.add(targetObject);
if (targetObject instanceof TargetConfigurable) {
configurables.add((TargetConfigurable) targetObject);
}
} }
if (targetObject instanceof TargetInterpreter) { if (targetObject instanceof TargetInterpreter) {
TargetInterpreter interpreter = (TargetInterpreter) targetObject; TargetInterpreter interpreter = (TargetInterpreter) targetObject;
getPlugin().showConsole(interpreter); getPlugin().showConsole(interpreter);
DebugModelConventions.findSuitable(TargetFocusScope.class, targetObject) pane.setSelectedObject(targetObject);
.thenAccept(f -> {
setFocus(f, targetObject);
});
}
if (targetObject instanceof TargetConfigurable) {
configurables.add((TargetConfigurable) targetObject);
} }
} }
} }
@ -591,7 +589,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
} }
public ObjectContainer getContainerByPath(List<String> path) { 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, static List<ObjectContainer> getContainersFromObjects(Map<String, ?> objectMap,
@ -1724,13 +1722,13 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
@AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME) @AttributeCallback(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)
public void executionStateChanged(TargetObject object, TargetExecutionState state) { public void executionStateChanged(TargetObject object, TargetExecutionState state) {
//this.state = state; //this.state = state;
plugin.getTool().contextChanged(DebuggerObjectsProvider.this); contextChanged();
} }
@AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME) @AttributeCallback(TargetFocusScope.FOCUS_ATTRIBUTE_NAME)
public void focusChanged(TargetObject object, TargetObject focused) { public void focusChanged(TargetObject object, TargetObject focused) {
plugin.setFocus(object, focused); plugin.setFocus(object, focused);
plugin.getTool().contextChanged(DebuggerObjectsProvider.this); contextChanged();
} }
@Override @Override
@ -1824,8 +1822,11 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
if (focused.getModel() != currentModel) { if (focused.getModel() != currentModel) {
return; return;
} }
this.targetFocus = focused;
if (isStopped(focused) || isUpdateWhileRunning()) { 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; return listener.queue.in;
} }
public void navigateToSelectedObject(TargetObject object, Object value) { public Address navigateToSelectedObject(TargetObject object, Object value) {
if (listingService == null || listingService == null) { if (listingService == null || modelService == null) {
return; return null;
} }
// TODO: Could probably inspect schema for any attribute of type Address[Range], or String // TODO: Could probably inspect schema for any attribute of type Address[Range], or String
if (value == null) { if (value == null) {
@ -1926,7 +1927,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
value = object.getCachedAttribute(TargetObject.VALUE_ATTRIBUTE_NAME); value = object.getCachedAttribute(TargetObject.VALUE_ATTRIBUTE_NAME);
} }
if (value == null) { if (value == null) {
return; return null;
} }
Address addr = null; Address addr = null;
@ -1947,13 +1948,14 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
if (recorder == null) { if (recorder == null) {
recorder = modelService.getRecorder(currentTrace); recorder = modelService.getRecorder(currentTrace);
if (recorder == null) { if (recorder == null) {
return; return addr;
} }
} }
DebuggerMemoryMapper memoryMapper = recorder.getMemoryMapper(); DebuggerMemoryMapper memoryMapper = recorder.getMemoryMapper();
Address traceAddr = memoryMapper.targetToTrace(addr); Address traceAddr = memoryMapper.targetToTrace(addr);
listingService.goTo(traceAddr, true); listingService.goTo(traceAddr, true);
} }
return addr;
} }
private Address stringToAddress(TargetObject selectedObject, Address addr, String sval) { private Address stringToAddress(TargetObject selectedObject, Address addr, String sval) {
@ -1976,4 +1978,8 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
public boolean isUpdateWhileRunning() { public boolean isUpdateWhileRunning() {
return updateWhileRunning; return updateWhileRunning;
} }
public TargetObject getFocus() {
return targetFocus;
}
} }

View file

@ -545,6 +545,17 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
return isLink; 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() { public String getOrder() {
Integer order = (Integer) attributeMap.get(TargetObject.ORDER_ATTRIBUTE_NAME); Integer order = (Integer) attributeMap.get(TargetObject.ORDER_ATTRIBUTE_NAME);
return order == null ? getName() : Integer.toString(order); return order == null ? getName() : Integer.toString(order);
@ -578,5 +589,4 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
} }
return this.hashCode() - that.hashCode(); return this.hashCode() - that.hashCode();
} }
} }

View file

@ -88,6 +88,7 @@ public abstract class DisplayAsAction extends DockingAction {
provider.getModel(), container, isTree); provider.getModel(), container, isTree);
container.propagateProvider(p); container.propagateProvider(p);
p.update(container); p.update(container);
p.setFocus(null, provider.getFocus());
} }
catch (Exception e) { catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View file

@ -23,7 +23,6 @@ import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
public interface ObjectPane { public interface ObjectPane {
public ObjectContainer getContainer(); public ObjectContainer getContainer();
public TargetObject getTargetObject(); public TargetObject getTargetObject();
@ -46,6 +45,7 @@ public interface ObjectPane {
public void setFocus(TargetObject object, TargetObject focused); 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);
} }

View file

@ -15,8 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.gui.objects.components; package ghidra.app.plugin.core.debug.gui.objects.components;
import java.awt.event.MouseAdapter; import java.awt.event.*;
import java.awt.event.MouseEvent;
import java.util.*; import java.util.*;
import javax.swing.*; import javax.swing.*;
@ -26,7 +25,9 @@ import docking.widgets.table.EnumeratedColumnTableModel;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider; import ghidra.app.plugin.core.debug.gui.objects.DebuggerObjectsProvider;
import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer; import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
import ghidra.dbg.DebugModelConventions;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.util.Msg;
import ghidra.util.Swing; import ghidra.util.Swing;
import ghidra.util.table.GhidraTable; 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"); public static final Icon ICON_TABLE = new GIcon("icon.debugger.table.object");
private ObjectContainer container; private final ObjectContainer container;
private Class<R> clazz; private final Class<R> clazz;
private AbstractSortedTableModel<R> model; private final AbstractSortedTableModel<R> model;
private GhidraTable table; private final GhidraTable table;
private JScrollPane component; private final JScrollPane component;
public ObjectTable(ObjectContainer container, Class<R> clazz, public ObjectTable(ObjectContainer container, Class<R> clazz,
AbstractSortedTableModel<R> model) { AbstractSortedTableModel<R> model) {
@ -55,15 +56,24 @@ public class ObjectTable<R> implements ObjectPane {
DebuggerObjectsProvider provider = container.getProvider(); DebuggerObjectsProvider provider = container.getProvider();
provider.getTool().contextChanged(provider); provider.getTool().contextChanged(provider);
}); });
table.setDefaultRenderer(String.class,
new ObjectTableCellRenderer(container.getProvider()));
table.setDefaultRenderer(Object.class,
new ObjectTableCellRenderer(container.getProvider()));
table.addMouseListener(new MouseAdapter() { table.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) { if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
int selectedRow = table.getSelectedRow(); activateOrNavigateSelectedObject();
int selectedColumn = table.getSelectedColumn(); }
Object value = table.getValueAt(selectedRow, selectedColumn); }
container.getProvider() });
.navigateToSelectedObject(container.getTargetObject(), value); 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); 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 @Override
public ObjectContainer getContainer() { public ObjectContainer getContainer() {
return container; return container;
@ -171,8 +213,7 @@ public class ObjectTable<R> implements ObjectPane {
ObjectElementRow match = null; ObjectElementRow match = null;
for (int i = 0; i < model.getRowCount(); i++) { for (int i = 0; i < model.getRowCount(); i++) {
R r = model.getRowObject(i); R r = model.getRowObject(i);
if (r instanceof ObjectElementRow) { if (r instanceof ObjectElementRow row) {
ObjectElementRow row = (ObjectElementRow) r;
if (row.getTargetObject().equals(changedTarget)) { if (row.getTargetObject().equals(changedTarget)) {
row.setAttributes(changed.getAttributeMap()); row.setAttributes(changed.getAttributeMap());
match = row; match = row;
@ -184,7 +225,6 @@ public class ObjectTable<R> implements ObjectPane {
} }
private List<R> updateMatch(ObjectElementRow match) { private List<R> updateMatch(ObjectElementRow match) {
@SuppressWarnings("unchecked")
ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model; ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model;
m.updateColumns(match); m.updateColumns(match);
m.fireTableDataChanged(); m.fireTableDataChanged();
@ -198,12 +238,11 @@ public class ObjectTable<R> implements ObjectPane {
} }
public void setColumns() { public void setColumns() {
@SuppressWarnings("unchecked")
ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model; ObjectEnumeratedColumnTableModel<?, R> m = (ObjectEnumeratedColumnTableModel<?, R>) model;
for (int i = 0; i < model.getRowCount(); i++) { for (int i = 0; i < model.getRowCount(); i++) {
R r = model.getRowObject(i); R r = model.getRowObject(i);
if (r instanceof ObjectElementRow) { if (r instanceof ObjectElementRow row) {
m.updateColumns((ObjectElementRow) r); m.updateColumns(row);
break; break;
} }
} }
@ -214,12 +253,10 @@ public class ObjectTable<R> implements ObjectPane {
public TargetObject getSelectedObject() { public TargetObject getSelectedObject() {
int selectedColumn = table.getSelectedColumn(); int selectedColumn = table.getSelectedColumn();
R r = model.getRowObject(table.getSelectedRow()); R r = model.getRowObject(table.getSelectedRow());
if (r instanceof ObjectAttributeRow) { if (r instanceof ObjectAttributeRow row) {
ObjectAttributeRow row = (ObjectAttributeRow) r;
return row.getTargetObject(); return row.getTargetObject();
} }
if (r instanceof ObjectElementRow) { if (r instanceof ObjectElementRow row) {
ObjectElementRow row = (ObjectElementRow) r;
TargetObject targetObject = row.getTargetObject(); TargetObject targetObject = row.getTargetObject();
if (selectedColumn > 0) { if (selectedColumn > 0) {
List<String> keys = row.getKeys(); List<String> keys = row.getKeys();
@ -229,8 +266,8 @@ public class ObjectTable<R> implements ObjectPane {
String key = keys.get(selectedColumn); String key = keys.get(selectedColumn);
Map<String, ?> attributes = targetObject.getCachedAttributes(); Map<String, ?> attributes = targetObject.getCachedAttributes();
Object object = attributes.get(key); Object object = attributes.get(key);
if (object instanceof TargetObject) { if (object instanceof TargetObject to) {
return (TargetObject) object; return to;
} }
} }
return targetObject; return targetObject;
@ -238,18 +275,17 @@ public class ObjectTable<R> implements ObjectPane {
return null; return null;
} }
@Override
public void setSelectedObject(TargetObject selection) { public void setSelectedObject(TargetObject selection) {
for (int i = 0; i < model.getRowCount(); i++) { for (int i = 0; i < model.getRowCount(); i++) {
R r = model.getRowObject(i); R r = model.getRowObject(i);
if (r instanceof ObjectAttributeRow) { if (r instanceof ObjectAttributeRow row) {
ObjectAttributeRow row = (ObjectAttributeRow) r;
if (row.getTargetObject().equals(selection)) { if (row.getTargetObject().equals(selection)) {
table.selectRow(i); table.selectRow(i);
break; break;
} }
} }
if (r instanceof ObjectElementRow) { if (r instanceof ObjectElementRow row) {
ObjectElementRow row = (ObjectElementRow) r;
if (row.getTargetObject().equals(selection)) { if (row.getTargetObject().equals(selection)) {
table.selectRow(i); table.selectRow(i);
break; break;
@ -260,8 +296,9 @@ public class ObjectTable<R> implements ObjectPane {
@Override @Override
public void setFocus(TargetObject object, TargetObject focused) { public void setFocus(TargetObject object, TargetObject focused) {
// Should this setSelectedObject, too?
Swing.runIfSwingOrRunLater(() -> { Swing.runIfSwingOrRunLater(() -> {
setSelectedObject(focused); table.repaint();
}); });
} }

View file

@ -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;
}
}

View file

@ -16,13 +16,11 @@
package ghidra.app.plugin.core.debug.gui.objects.components; package ghidra.app.plugin.core.debug.gui.objects.components;
import java.awt.Point; import java.awt.Point;
import java.awt.event.MouseAdapter; import java.awt.event.*;
import java.awt.event.MouseEvent;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.swing.Icon; import javax.swing.*;
import javax.swing.JComponent;
import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.TreePath; 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"); public static final Icon ICON_TREE = new GIcon("icon.debugger.tree.object");
private ObjectNode root; private static class MyGTree extends GTree {
private GTree tree; 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 TreePath[] currentSelectionPaths;
private List<TreePath> currentExpandedPaths; private List<TreePath> currentExpandedPaths;
private Point currentViewPosition; private Point currentViewPosition;
private Map<String, ObjectNode> nodeMap = new LinkedHashMap<>();
private SwingUpdateManager restoreTreeStateManager =
new SwingUpdateManager(this::restoreTreeState);
public ObjectTree(ObjectContainer container) { public ObjectTree(ObjectContainer container) {
this.root = new ObjectNode(this, null, container); this.root = new ObjectNode(this, null, container);
addToMap(null, container, root); addToMap(null, container, root);
this.tree = new GTree(root); this.tree = new MyGTree(root);
tree.addGTreeSelectionListener(new GTreeSelectionListener() { tree.addGTreeSelectionListener(new GTreeSelectionListener() {
@Override @Override
public void valueChanged(GTreeSelectionEvent e) { public void valueChanged(GTreeSelectionEvent e) {
DebuggerObjectsProvider provider = container.getProvider(); DebuggerObjectsProvider provider = container.getProvider();
provider.updateActions(container); 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); provider.getTool().contextChanged(provider);
if (e.getEventOrigin() == EventOrigin.INTERNAL_GENERATED) { if (e.getEventOrigin() == EventOrigin.INTERNAL_GENERATED) {
restoreTreeStateManager.updateLater(); restoreTreeStateManager.updateLater();
@ -119,8 +111,7 @@ public class ObjectTree implements ObjectPane {
}); });
tree.setCellRenderer(new ObjectTreeCellRenderer(root.getProvider())); tree.setCellRenderer(new ObjectTreeCellRenderer(root.getProvider()));
tree.setDataTransformer(t -> { tree.setDataTransformer(t -> {
if (t instanceof ObjectNode) { if (t instanceof ObjectNode node) {
ObjectNode node = (ObjectNode) t;
return List.of(node.getContainer().getDecoratedName()); return List.of(node.getContainer().getDecoratedName());
} }
return null; return null;
@ -131,8 +122,7 @@ public class ObjectTree implements ObjectPane {
public void treeExpanded(TreeExpansionEvent event) { public void treeExpanded(TreeExpansionEvent event) {
TreePath expandedPath = event.getPath(); TreePath expandedPath = event.getPath();
Object last = expandedPath.getLastPathComponent(); Object last = expandedPath.getLastPathComponent();
if (last instanceof ObjectNode) { if (last instanceof ObjectNode node) {
ObjectNode node = (ObjectNode) last;
node.markExpanded(); node.markExpanded();
currentExpandedPaths = tree.getExpandedPaths(); currentExpandedPaths = tree.getExpandedPaths();
} }
@ -142,8 +132,7 @@ public class ObjectTree implements ObjectPane {
public void treeCollapsed(TreeExpansionEvent event) { public void treeCollapsed(TreeExpansionEvent event) {
TreePath collapsedPath = event.getPath(); TreePath collapsedPath = event.getPath();
Object last = collapsedPath.getLastPathComponent(); Object last = collapsedPath.getLastPathComponent();
if (last instanceof ObjectNode) { if (last instanceof ObjectNode node) {
ObjectNode node = (ObjectNode) last;
node.markCollapsed(); node.markCollapsed();
currentExpandedPaths = tree.getExpandedPaths(); currentExpandedPaths = tree.getExpandedPaths();
} }
@ -153,11 +142,17 @@ public class ObjectTree implements ObjectPane {
tree.addMouseListener(new MouseAdapter() { tree.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) { if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
TargetObject selectedObject = getSelectedObject(); activateOrNavigateSelectedObject();
if (selectedObject != null) { }
container.getProvider().navigateToSelectedObject(selectedObject, null); }
} });
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); 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 @Override
public ObjectContainer getContainer() { public ObjectContainer getContainer() {
return root.getContainer(); return root.getContainer();
@ -188,8 +204,8 @@ public class ObjectTree implements ObjectPane {
} }
if (path != null) { if (path != null) {
Object last = path.getLastPathComponent(); Object last = path.getLastPathComponent();
if (last instanceof ObjectNode) { if (last instanceof ObjectNode node) {
return ((ObjectNode) last).getContainer().getTargetObject(); return node.getContainer().getTargetObject();
} }
} }
return null; return null;
@ -303,8 +319,7 @@ public class ObjectTree implements ObjectPane {
for (TreePath path : expandedPaths) { for (TreePath path : expandedPaths) {
Object[] objs = path.getPath(); Object[] objs = path.getPath();
for (int i = 0; i < objs.length - 2; i++) { for (int i = 0; i < objs.length - 2; i++) {
if (objs[i] instanceof ObjectNode) { if (objs[i] instanceof ObjectNode node) {
ObjectNode node = (ObjectNode) objs[i];
if (!node.isLoaded()) { if (!node.isLoaded()) {
return false; 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) { private String[] addRootNameToPath(List<String> path) {
String[] fullPath = new String[path.size() + 1]; String[] fullPath = new String[path.size() + 1];
fullPath[0] = tree.getModelRoot().getName(); fullPath[0] = tree.getModelRoot().getName();

View file

@ -30,7 +30,6 @@ import ghidra.dbg.target.TargetExecutionStateful;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
// TODO: In the new scheme, I'm not sure this is applicable anymore.
class ObjectTreeCellRenderer extends GTreeRenderer { class ObjectTreeCellRenderer extends GTreeRenderer {
private static final String FONT_ID = "font.debugger.object.tree.renderer"; 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) { boolean leaf, int row, boolean focus) {
Component component = Component component =
super.getTreeCellRendererComponent(t, value, sel, exp, leaf, row, focus); super.getTreeCellRendererComponent(t, value, sel, exp, leaf, row, focus);
if (value instanceof ObjectNode) { if (value instanceof ObjectNode node) {
ObjectNode node = (ObjectNode) value;
ObjectContainer container = node.getContainer(); ObjectContainer container = node.getContainer();
setText(container.getDecoratedName()); setText(container.getDecoratedName());
component.setForeground(provider.COLOR_FOREGROUND); component.setForeground(provider.COLOR_FOREGROUND);
TargetObject targetObject = container.getTargetObject(); 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) { if (targetObject != null) {
Map<String, ?> attrs = targetObject.getCachedAttributes(); Map<String, ?> attrs = targetObject.getCachedAttributes();
String kind = (String) attrs.get(TargetObject.KIND_ATTRIBUTE_NAME); String kind = (String) attrs.get(TargetObject.KIND_ATTRIBUTE_NAME);
@ -76,12 +80,6 @@ class ObjectTreeCellRenderer extends GTreeRenderer {
if (container.isModified()) { if (container.isModified()) {
component.setForeground(provider.COLOR_FOREGROUND_MODIFIED); 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(); TreePath path = t.getSelectionPath();
if (path != null) { if (path != null) {
Object last = path.getLastPathComponent(); Object last = path.getLastPathComponent();
@ -93,8 +91,8 @@ class ObjectTreeCellRenderer extends GTreeRenderer {
} }
} }
Font font = Gui.getFont(FONT_ID); Font font = Gui.getFont(FONT_ID);
if (container.isSubscribed()) { if (container.isFocused()) {
font = font.deriveFont(Font.ITALIC); font = font.deriveFont(Font.BOLD);
} }
component.setFont(font); component.setFont(font);
} }

View file

@ -1296,7 +1296,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
return AsyncUtils.NIL; return AsyncUtils.NIL;
} }
toRead.retainAll(regMapper.getRegistersOnTarget()); 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()) { if (banks == null || banks.isEmpty()) {
Msg.error(this, "Current frame's bank does not exist"); Msg.error(this, "Current frame's bank does not exist");
return AsyncUtils.NIL; return AsyncUtils.NIL;
@ -1341,16 +1342,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
} }
return future.exceptionally(ex -> { return future.exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex); ex = AsyncUtils.unwrapThrowable(ex);
if (ex instanceof DebuggerModelAccessException) { String msg = "Could not read target registers for selected thread: " + ex.getMessage();
String msg = Msg.info(this, msg);
"Could not read target registers for selected thread: " + ex.getMessage(); plugin.getTool().setStatusInfo(msg);
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);
}
return ExceptionUtils.rethrow(ex); return ExceptionUtils.rethrow(ex);
}).thenApply(__ -> null); }).thenApply(__ -> null);
} }

View file

@ -16,23 +16,20 @@
package ghidra.app.plugin.core.debug.gui.stack; package ghidra.app.plugin.core.debug.gui.stack;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.event.MouseAdapter; import java.awt.Component;
import java.awt.event.MouseEvent; import java.awt.event.*;
import java.util.*; import java.util.*;
import java.util.function.*; import java.util.function.*;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.table.TableColumn; import javax.swing.table.*;
import javax.swing.table.TableColumnModel;
import docking.widgets.table.CustomToStringCellRenderer; import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.dbg.DebugModelConventions; import ghidra.docking.settings.Settings;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
@ -52,8 +49,7 @@ import ghidra.trace.util.TraceRegisterUtils;
import ghidra.util.Swing; import ghidra.util.Swing;
import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel; import ghidra.util.table.GhidraTableFilterPanel;
import utilities.util.SuppressableCallback; import ghidra.util.table.column.AbstractGColumnRenderer;
import utilities.util.SuppressableCallback.Suppression;
public class DebuggerLegacyStackPanel extends JPanel { public class DebuggerLegacyStackPanel extends JPanel {
@ -202,8 +198,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
@AutoServiceConsumed @AutoServiceConsumed
private DebuggerTraceManagerService traceManager; private DebuggerTraceManagerService traceManager;
// @AutoServiceConsumed by method
private DebuggerModelService modelService;
// @AutoServiceConsumed via method // @AutoServiceConsumed via method
DebuggerStaticMappingService mappingService; DebuggerStaticMappingService mappingService;
@AutoServiceConsumed @AutoServiceConsumed
@ -213,6 +207,23 @@ public class DebuggerLegacyStackPanel extends JPanel {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring; 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 // Table rows access this for function name resolution
DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
private Trace currentTrace; // Copy for transition private Trace currentTrace; // Copy for transition
@ -222,8 +233,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
private ForStackListener forStackListener = new ForStackListener(); private ForStackListener forStackListener = new ForStackListener();
private ForFunctionsListener forFunctionsListener = new ForFunctionsListener(); private ForFunctionsListener forFunctionsListener = new ForFunctionsListener();
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
protected final StackTableModel stackTableModel; protected final StackTableModel stackTableModel;
protected GhidraTable stackTable; protected GhidraTable stackTable;
protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel; protected GhidraTableFilterPanel<StackFrameRow> stackFilterPanel;
@ -246,33 +255,23 @@ public class DebuggerLegacyStackPanel extends JPanel {
return; return;
} }
contextChanged(); contextChanged();
activateSelectedFrame();
}); });
stackTable.addMouseListener(new MouseAdapter() { stackTable.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
if (e.getClickCount() < 2 || e.getButton() != MouseEvent.BUTTON1) { if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
return; 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 @Override
public void mouseReleased(MouseEvent e) { public void keyPressed(KeyEvent e) {
int selectedRow = stackTable.getSelectedRow(); if (e.getKeyCode() == KeyEvent.VK_ENTER) {
StackFrameRow row = stackTableModel.getRowObject(selectedRow); activateSelectedFrame();
rowActivated(row); 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()); TableColumn levelCol = columnModel.getColumn(StackTableColumns.LEVEL.ordinal());
levelCol.setPreferredWidth(25); levelCol.setPreferredWidth(25);
TableColumn baseCol = columnModel.getColumn(StackTableColumns.PC.ordinal()); levelCol.setCellRenderer(boldCurrentRenderer);
baseCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT); 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() { protected void contextChanged() {
@ -293,37 +298,13 @@ public class DebuggerLegacyStackPanel extends JPanel {
} }
protected void activateSelectedFrame() { protected void activateSelectedFrame() {
// TODO: Action to toggle sync?
if (myActionContext == null) { if (myActionContext == null) {
return; return;
} }
if (traceManager == null) { if (traceManager == null) {
return; return;
} }
cbFrameSelected.invoke(() -> { traceManager.activateFrame(myActionContext.getFrame().getFrameLevel());
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);
} }
protected void updateStack() { protected void updateStack() {
@ -344,8 +325,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
} }
stackTableModel.fireTableDataChanged(); stackTableModel.fireTableDataChanged();
selectCurrentFrame();
} }
protected void doSetCurrentStack(TraceStack stack) { protected void doSetCurrentStack(TraceStack stack) {
@ -411,7 +390,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
else { else {
doSetCurrentStack(stack); doSetCurrentStack(stack);
} }
selectCurrentFrame();
} }
private void removeOldListeners() { private void removeOldListeners() {
@ -441,19 +419,17 @@ public class DebuggerLegacyStackPanel extends JPanel {
current = coordinates; current = coordinates;
doSetTrace(current.getTrace()); doSetTrace(current.getTrace());
loadStack(); loadStack();
selectCurrentFrame();
} }
protected void selectCurrentFrame() { protected void selectCurrentFrame() {
try (Suppression supp = cbFrameSelected.suppress(null)) { StackFrameRow row = stackTableModel.findFirst(r -> r.getFrameLevel() == current.getFrame());
StackFrameRow row = if (row == null) {
stackTableModel.findFirst(r -> r.getFrameLevel() == current.getFrame()); // Strange
if (row == null) { stackTable.clearSelection();
// Strange }
stackTable.clearSelection(); else {
} stackFilterPanel.setSelectedItem(row);
else {
stackFilterPanel.setSelectedItem(row);
}
} }
} }
@ -461,11 +437,6 @@ public class DebuggerLegacyStackPanel extends JPanel {
return myActionContext; return myActionContext;
} }
@AutoServiceConsumed
public void setModelService(DebuggerModelService modelService) {
this.modelService = modelService;
}
@AutoServiceConsumed @AutoServiceConsumed
private void setMappingService(DebuggerStaticMappingService mappingService) { private void setMappingService(DebuggerStaticMappingService mappingService) {
if (this.mappingService != null) { if (this.mappingService != null) {

View file

@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.stack;
import java.util.List; import java.util.List;
import javax.swing.event.ListSelectionEvent; import javax.swing.JTable;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import docking.widgets.table.AbstractDynamicTableColumn; import docking.widgets.table.AbstractDynamicTableColumn;
@ -43,8 +43,6 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.stack.TraceObjectStackFrame; import ghidra.trace.model.stack.TraceObjectStackFrame;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.model.target.TraceObjectValue;
import utilities.util.SuppressableCallback;
import utilities.util.SuppressableCallback.Suppression;
public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObjectStackFrame> public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObjectStackFrame>
implements ListSelectionListener, CellActivationListener { implements ListSelectionListener, CellActivationListener {
@ -109,8 +107,6 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
@AutoServiceConsumed @AutoServiceConsumed
protected DebuggerTraceManagerService traceManager; protected DebuggerTraceManagerService traceManager;
private final SuppressableCallback<Void> cbFrameSelected = new SuppressableCallback<>();
public DebuggerStackPanel(DebuggerStackProvider provider) { public DebuggerStackPanel(DebuggerStackProvider provider) {
super(provider.plugin, provider, TraceObjectStackFrame.class); super(provider.plugin, provider, TraceObjectStackFrame.class);
this.provider = provider; this.provider = provider;
@ -139,21 +135,16 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
super.coordinatesActivated(coordinates); super.coordinatesActivated(coordinates);
TraceObject object = coordinates.getObject(); TraceObject object = coordinates.getObject();
if (object != null) { if (object != null) {
try (Suppression supp = cbFrameSelected.suppress(null)) { trySelectAncestor(object);
trySelectAncestor(object);
}
} }
} }
@Override @Override
public void valueChanged(ListSelectionEvent e) { public void cellActivated(JTable table) {
super.valueChanged(e); // No super
if (e.getValueIsAdjusting()) {
return;
}
ValueRow item = getSelectedItem(); ValueRow item = getSelectedItem();
if (item != null) { if (item != null) {
cbFrameSelected.invoke(() -> traceManager.activateObject(item.getValue().getChild())); traceManager.activateObject(item.getValue().getChild());
} }
} }
} }

View file

@ -16,14 +16,14 @@
package ghidra.app.plugin.core.debug.gui.thread; package ghidra.app.plugin.core.debug.gui.thread;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.*; import java.awt.event.*;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import javax.swing.table.TableColumn; import javax.swing.table.*;
import javax.swing.table.TableColumnModel;
import docking.ActionContext; import docking.ActionContext;
import docking.widgets.table.*; 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.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext; import ghidra.app.plugin.core.debug.gui.DebuggerSnapActionContext;
import ghidra.app.services.DebuggerTraceManagerService; import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.docking.settings.Settings;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.model.DomainObjectChangeRecord; import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.AutoService;
@ -45,8 +46,7 @@ import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.database.ObjectKey; import ghidra.util.database.ObjectKey;
import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel; import ghidra.util.table.GhidraTableFilterPanel;
import utilities.util.SuppressableCallback; import ghidra.util.table.column.AbstractGColumnRenderer;
import utilities.util.SuppressableCallback.Suppression;
public class DebuggerLegacyThreadsPanel extends JPanel { public class DebuggerLegacyThreadsPanel extends JPanel {
@ -175,13 +175,28 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
private final ForThreadsListener forThreadsListener = new ForThreadsListener(); private final ForThreadsListener forThreadsListener = new ForThreadsListener();
private final SuppressableCallback<Void> cbCoordinateActivation = new SuppressableCallback<>();
/* package access for testing */ /* package access for testing */
final SpanTableCellRenderer<Long> spanRenderer = new SpanTableCellRenderer<>(); final SpanTableCellRenderer<Long> spanRenderer = new SpanTableCellRenderer<>();
final RangeCursorTableHeaderRenderer<Long> headerRenderer = final RangeCursorTableHeaderRenderer<Long> headerRenderer =
new RangeCursorTableHeaderRenderer<>(0L); 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 ThreadTableModel threadTableModel;
final GTable threadTable; final GTable threadTable;
final GhidraTableFilterPanel<ThreadRow> threadFilterPanel; final GhidraTableFilterPanel<ThreadRow> threadFilterPanel;
@ -208,6 +223,23 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap()); myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getViewSnap());
threadTable.getSelectionModel().addListSelectionListener(this::threadRowSelected); 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() { threadTable.addFocusListener(new FocusAdapter() {
@Override @Override
public void focusGained(FocusEvent e) { public void focusGained(FocusEvent e) {
@ -224,14 +256,19 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
TableColumnModel columnModel = threadTable.getColumnModel(); TableColumnModel columnModel = threadTable.getColumnModel();
TableColumn colName = columnModel.getColumn(ThreadTableColumns.NAME.ordinal()); TableColumn colName = columnModel.getColumn(ThreadTableColumns.NAME.ordinal());
colName.setPreferredWidth(100); colName.setPreferredWidth(100);
colName.setCellRenderer(boldCurrentRenderer);
TableColumn colCreated = columnModel.getColumn(ThreadTableColumns.CREATED.ordinal()); TableColumn colCreated = columnModel.getColumn(ThreadTableColumns.CREATED.ordinal());
colCreated.setPreferredWidth(10); colCreated.setPreferredWidth(10);
colCreated.setCellRenderer(boldCurrentRenderer);
TableColumn colDestroyed = columnModel.getColumn(ThreadTableColumns.DESTROYED.ordinal()); TableColumn colDestroyed = columnModel.getColumn(ThreadTableColumns.DESTROYED.ordinal());
colDestroyed.setPreferredWidth(10); colDestroyed.setPreferredWidth(10);
colDestroyed.setCellRenderer(boldCurrentRenderer);
TableColumn colState = columnModel.getColumn(ThreadTableColumns.STATE.ordinal()); TableColumn colState = columnModel.getColumn(ThreadTableColumns.STATE.ordinal());
colState.setPreferredWidth(20); colState.setPreferredWidth(20);
colState.setCellRenderer(boldCurrentRenderer);
TableColumn colComment = columnModel.getColumn(ThreadTableColumns.COMMENT.ordinal()); TableColumn colComment = columnModel.getColumn(ThreadTableColumns.COMMENT.ordinal());
colComment.setPreferredWidth(100); colComment.setPreferredWidth(100);
colComment.setCellRenderer(boldCurrentRenderer);
TableColumn colPlot = columnModel.getColumn(ThreadTableColumns.PLOT.ordinal()); TableColumn colPlot = columnModel.getColumn(ThreadTableColumns.PLOT.ordinal());
colPlot.setPreferredWidth(200); colPlot.setPreferredWidth(200);
colPlot.setCellRenderer(spanRenderer); colPlot.setCellRenderer(spanRenderer);
@ -281,19 +318,13 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
} }
private void doSetThread(TraceThread thread) { private void doSetThread(TraceThread thread) {
ThreadRow row = threadFilterPanel.getSelectedItem(); if (thread != null) {
TraceThread curThread = row == null ? null : row.getThread(); threadFilterPanel.setSelectedItem(threadTableModel.getRow(thread));
if (curThread == thread) {
return;
} }
try (Suppression supp = cbCoordinateActivation.suppress(null)) { else {
if (thread != null) { threadTable.clearSelection();
threadFilterPanel.setSelectedItem(threadTableModel.getRow(thread));
}
else {
threadTable.clearSelection();
}
} }
threadTableModel.fireTableDataChanged();
} }
private void doSetSnap(long snap) { private void doSetSnap(long snap) {
@ -324,9 +355,13 @@ public class DebuggerLegacyThreadsPanel extends JPanel {
if (e.getValueIsAdjusting()) { if (e.getValueIsAdjusting()) {
return; return;
} }
setThreadRowActionContext();
}
private void activateSelectedThread() {
ThreadRow row = setThreadRowActionContext(); ThreadRow row = setThreadRowActionContext();
if (row != null && traceManager != null) { if (row != null && traceManager != null) {
cbCoordinateActivation.invoke(() -> traceManager.activateThread(row.getThread())); traceManager.activateThread(row.getThread());
} }
} }

View file

@ -17,9 +17,10 @@ package ghidra.app.plugin.core.debug.gui.thread;
import java.util.List; import java.util.List;
import javax.swing.event.ListSelectionEvent; import javax.swing.JTable;
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener; import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
import docking.widgets.table.threaded.ThreadedTableModelListener;
import docking.widgets.table.TableColumnDescriptor; import docking.widgets.table.TableColumnDescriptor;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.model.*; 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.TraceObject;
import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.model.target.TraceObjectValue;
import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceObjectThread;
import utilities.util.SuppressableCallback;
import utilities.util.SuppressableCallback.Suppression;
public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceObjectThread> { public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceObjectThread> {
@ -166,8 +165,6 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
@AutoServiceConsumed @AutoServiceConsumed
protected DebuggerTraceManagerService traceManager; protected DebuggerTraceManagerService traceManager;
private final SuppressableCallback<Void> cbThreadSelected = new SuppressableCallback<>();
private final SeekListener seekListener = pos -> { private final SeekListener seekListener = pos -> {
long snap = Math.round(pos); long snap = Math.round(pos);
if (current.getTrace() == null || snap < 0) { if (current.getTrace() == null || snap < 0) {
@ -180,11 +177,22 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
super(provider.plugin, provider, TraceObjectThread.class); super(provider.plugin, provider, TraceObjectThread.class);
setLimitToSnap(false); // TODO: Toggle for this? setLimitToSnap(false); // TODO: Toggle for this?
tableModel.addTableModelListener(e -> {
// This seems a bit heavy handed
trySelectCurrentThread();
});
addSeekListener(seekListener); addSeekListener(seekListener);
tableModel.addThreadedTableModelListener(new ThreadedTableModelListener() {
@Override
public void loadingStarted() {
}
@Override
public void loadingFinished(boolean wasCancelled) {
trySelectCurrentThread();
}
@Override
public void loadPending() {
}
});
} }
@Override @Override
@ -211,11 +219,10 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
private void trySelectCurrentThread() { private void trySelectCurrentThread() {
TraceObject object = current.getObject(); TraceObject object = current.getObject();
if (object != null) { if (object == null) {
try (Suppression supp = cbThreadSelected.suppress(null)) { return;
trySelectAncestor(object);
}
} }
trySelectAncestor(object);
} }
@Override @Override
@ -225,21 +232,11 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
} }
@Override @Override
public void valueChanged(ListSelectionEvent e) { public void cellActivated(JTable table) {
super.valueChanged(e); // No super
if (e.getValueIsAdjusting()) {
return;
}
ValueRow item = getSelectedItem(); ValueRow item = getSelectedItem();
if (item != null) { if (item != null) {
cbThreadSelected.invoke(() -> { traceManager.activateObject(item.getValue().getChild());
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());
});
} }
} }
} }

View file

@ -29,7 +29,7 @@ import docking.action.*;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources;
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.plugin.core.debug.gui.DebuggerResources.ToToggleSelectionListener;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter; import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter;
@ -106,8 +106,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring; private final AutoService.Wiring autoServiceWiring;
private final BooleanChangeAdapter synchronizeFocusChangeListener = private final BooleanChangeAdapter synchronizeTargetChangeListener =
this::changedSynchronizeFocus; this::changedSynchronizeTarget;
private final ForSnapsListener forSnapsListener = new ForSnapsListener(); private final ForSnapsListener forSnapsListener = new ForSnapsListener();
@ -119,7 +119,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
DebuggerLegacyThreadsPanel legacyPanel; DebuggerLegacyThreadsPanel legacyPanel;
DockingAction actionSaveTrace; DockingAction actionSaveTrace;
ToggleDockingAction actionSyncFocus; ToggleDockingAction actionSyncTarget;
ActionContext myActionContext; ActionContext myActionContext;
@ -149,13 +149,13 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
@AutoServiceConsumed @AutoServiceConsumed
public void setTraceManager(DebuggerTraceManagerService traceManager) { public void setTraceManager(DebuggerTraceManagerService traceManager) {
if (this.traceManager != null) { if (this.traceManager != null) {
this.traceManager.removeSynchronizeFocusChangeListener(synchronizeFocusChangeListener); this.traceManager.removeSynchronizeActiveChangeListener(synchronizeTargetChangeListener);
} }
this.traceManager = traceManager; this.traceManager = traceManager;
if (traceManager != null) { if (traceManager != null) {
traceManager.addSynchronizeFocusChangeListener(synchronizeFocusChangeListener); traceManager.addSynchronizeActiveChangeListener(synchronizeTargetChangeListener);
if (actionSyncFocus != null) { if (actionSyncTarget != null) {
actionSyncFocus.setSelected(traceManager.isSynchronizeFocus()); actionSyncTarget.setSelected(traceManager.isSynchronizeActive());
} }
} }
contextChanged(); contextChanged();
@ -236,27 +236,27 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
} }
protected void createActions() { protected void createActions() {
actionSyncFocus = SynchronizeFocusAction.builder(plugin) actionSyncTarget = SynchronizeTargetAction.builder(plugin)
.selected(traceManager != null && traceManager.isSynchronizeFocus()) .selected(traceManager != null && traceManager.isSynchronizeActive())
.enabledWhen(c -> traceManager != null) .enabledWhen(c -> traceManager != null)
.onAction(c -> toggleSyncFocus(actionSyncFocus.isSelected())) .onAction(c -> toggleSyncFocus(actionSyncTarget.isSelected()))
.buildAndInstallLocal(this); .buildAndInstallLocal(this);
traceManager.addSynchronizeFocusChangeListener(toToggleSelectionListener = traceManager.addSynchronizeActiveChangeListener(toToggleSelectionListener =
new ToToggleSelectionListener(actionSyncFocus)); new ToToggleSelectionListener(actionSyncTarget));
} }
private void changedSynchronizeFocus(boolean value) { private void changedSynchronizeTarget(boolean value) {
if (actionSyncFocus == null || actionSyncFocus.isSelected()) { if (actionSyncTarget == null || actionSyncTarget.isSelected()) {
return; return;
} }
actionSyncFocus.setSelected(value); actionSyncTarget.setSelected(value);
} }
private void toggleSyncFocus(boolean enabled) { private void toggleSyncFocus(boolean enabled) {
if (traceManager == null) { if (traceManager == null) {
return; return;
} }
traceManager.setSynchronizeFocus(enabled); traceManager.setSynchronizeActive(enabled);
} }
@Override @Override

View file

@ -16,19 +16,20 @@
package ghidra.app.plugin.core.debug.gui.time; package ghidra.app.plugin.core.debug.gui.time;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Component;
import java.util.Collection; import java.util.Collection;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.TableColumn; import javax.swing.table.*;
import javax.swing.table.TableColumnModel;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import docking.widgets.table.*; import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.docking.settings.Settings;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace; 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.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager; import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.table.GhidraTableFilterPanel; import ghidra.util.table.GhidraTableFilterPanel;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class DebuggerSnapshotTablePanel extends JPanel { 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 EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel;
protected final GTable snapshotTable; protected final GTable snapshotTable;
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel; protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
@ -155,14 +174,19 @@ public class DebuggerSnapshotTablePanel extends JPanel {
TableColumnModel columnModel = snapshotTable.getColumnModel(); TableColumnModel columnModel = snapshotTable.getColumnModel();
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal()); TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
snapCol.setPreferredWidth(40); snapCol.setPreferredWidth(40);
snapCol.setCellRenderer(boldCurrentRenderer);
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal()); TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
timeCol.setPreferredWidth(200); timeCol.setPreferredWidth(200);
timeCol.setCellRenderer(boldCurrentRenderer);
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal()); TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
etCol.setPreferredWidth(40); etCol.setPreferredWidth(40);
etCol.setCellRenderer(boldCurrentRenderer);
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal()); TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
schdCol.setPreferredWidth(60); schdCol.setPreferredWidth(60);
schdCol.setCellRenderer(boldCurrentRenderer);
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal()); TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
descCol.setPreferredWidth(200); descCol.setPreferredWidth(200);
descCol.setCellRenderer(boldCurrentRenderer);
} }
private void addNewListeners() { private void addNewListeners() {
@ -260,5 +284,6 @@ public class DebuggerSnapshotTablePanel extends JPanel {
return; return;
} }
snapshotFilterPanel.setSelectedItem(row); snapshotFilterPanel.setSelectedItem(row);
snapshotTableModel.fireTableDataChanged();
} }
} }

View file

@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.time;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*; 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.lang.invoke.MethodHandles;
import java.util.Objects; import java.util.Objects;
@ -146,9 +146,32 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
return; return;
} }
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap); myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
traceManager.activateSnap(snap);
contextChanged(); 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() { protected void createActions() {

View file

@ -387,15 +387,33 @@ public class DefaultTraceRecorder implements TraceRecorder {
return findFocusScope() != null; return findFocusScope() != null;
} }
@Override
public boolean isSupportsActivation() {
return findActiveScope() != null;
}
// NOTE: This may require the scope to be an ancestor of the target // NOTE: This may require the scope to be an ancestor of the target
// That should be fine // That should be fine
protected TargetFocusScope findFocusScope() { protected TargetFocusScope findFocusScope() {
List<String> path = target.getModel() List<String> path = target.getModel()
.getRootSchema() .getRootSchema()
.searchForSuitable(TargetFocusScope.class, target.getPath()); .searchForSuitable(TargetFocusScope.class, target.getPath());
if (path == null) {
return null;
}
return (TargetFocusScope) target.getModel().getModelObject(path); 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 @Override
public TargetObject getFocus() { public TargetObject getFocus() {
if (curFocus == null) { if (curFocus == null) {
@ -419,8 +437,8 @@ public class DefaultTraceRecorder implements TraceRecorder {
@Override @Override
public CompletableFuture<Boolean> requestFocus(TargetObject focus) { public CompletableFuture<Boolean> requestFocus(TargetObject focus) {
if (!isSupportsFocus()) { if (!isSupportsFocus()) {
return CompletableFuture return CompletableFuture.failedFuture(
.failedFuture(new IllegalArgumentException("Target does not support focus")); new IllegalArgumentException("Target does not support focus"));
} }
if (!PathUtils.isAncestor(getTarget().getPath(), focus.getPath())) { if (!PathUtils.isAncestor(getTarget().getPath(), focus.getPath())) {
return CompletableFuture.failedFuture(new IllegalArgumentException( 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 -------------------*/ /*---------------- ACCESSOR METHODS -------------------*/
@Override @Override

View file

@ -737,6 +737,11 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
return objectRecorder.isSupportsFocus; return objectRecorder.isSupportsFocus;
} }
@Override
public boolean isSupportsActivation() {
return objectRecorder.isSupportsActivation;
}
@Override @Override
public TargetObject getFocus() { public TargetObject getFocus() {
return curFocus; return curFocus;
@ -764,6 +769,28 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
return CompletableFuture.completedFuture(false); 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? // UNUSED?
@Override @Override
public CompletableFuture<Void> flushTransactions() { public CompletableFuture<Void> flushTransactions() {

View file

@ -26,14 +26,13 @@ import com.google.gson.JsonElement;
import ghidra.dbg.DebuggerObjectModel; import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.attributes.TargetDataType; import ghidra.dbg.attributes.TargetDataType;
import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetAttacher.TargetAttachKind; import ghidra.dbg.target.TargetAttacher.TargetAttachKind;
import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet; import ghidra.dbg.target.TargetAttacher.TargetAttachKindSet;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind; import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet; import ghidra.dbg.target.TargetBreakpointSpecContainer.TargetBreakpointKindSet;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetFocusScope;
import ghidra.dbg.target.TargetMethod.TargetParameterMap; import ghidra.dbg.target.TargetMethod.TargetParameterMap;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetSteppable.TargetStepKind; import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.target.TargetSteppable.TargetStepKindSet; import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.target.schema.TargetObjectSchema;
@ -53,6 +52,7 @@ class ObjectRecorder {
protected final ObjectBasedTraceRecorder recorder; protected final ObjectBasedTraceRecorder recorder;
protected final TraceObjectManager objectManager; protected final TraceObjectManager objectManager;
protected final boolean isSupportsFocus; protected final boolean isSupportsFocus;
protected final boolean isSupportsActivation;
private final BidiMap<IDKeyed<TargetObject>, IDKeyed<TraceObject>> objectMap = private final BidiMap<IDKeyed<TargetObject>, IDKeyed<TraceObject>> objectMap =
new DualHashBidiMap<>(); new DualHashBidiMap<>();
@ -62,6 +62,7 @@ class ObjectRecorder {
this.objectManager = recorder.trace.getObjectManager(); this.objectManager = recorder.trace.getObjectManager();
TargetObjectSchema schema = recorder.target.getSchema(); TargetObjectSchema schema = recorder.target.getSchema();
this.isSupportsFocus = !schema.searchFor(TargetFocusScope.class, false).isEmpty(); this.isSupportsFocus = !schema.searchFor(TargetFocusScope.class, false).isEmpty();
this.isSupportsActivation = !schema.searchFor(TargetActiveScope.class, false).isEmpty();
try (UndoableTransaction tid = try (UndoableTransaction tid =
UndoableTransaction.start(recorder.trace, "Create root")) { UndoableTransaction.start(recorder.trace, "Create root")) {

View file

@ -67,8 +67,8 @@ import ghidra.util.exception.*;
import ghidra.util.task.*; import ghidra.util.task.*;
@PluginInfo( @PluginInfo(
shortDescription = "Debugger Trace View Management Plugin", shortDescription = "Debugger Trace Management Plugin",
description = "Manages UI Components, Wrappers, Focus, etc.", description = "Manages the set of open traces, current views, etc.",
category = PluginCategoryNames.DEBUGGER, category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME, packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.RELEASED, status = PluginStatus.RELEASED,
@ -244,8 +244,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (curRecorder == null) { if (curRecorder == null) {
return; return;
} }
// TODO: Also re-sync focused thread/frame? DebuggerCoordinates coords = current;
activateNoFocus(current.snap(curRecorder.getSnap()), ActivationCause.FOLLOW_PRESENT); 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) @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true); protected final AsyncReference<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class) @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) @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> autoCloseOnTerminate = new AsyncReference<>(true); 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) { protected boolean doModelObjectFocused(TargetObject obj, boolean requirePresent) {
curObj = obj; curObj = obj;
if (!synchronizeFocus.get()) { if (!synchronizeActive.get()) {
return false; return false;
} }
if (requirePresent && !current.isDeadOrPresent()) { if (requirePresent && !current.isDeadOrPresent()) {
@ -612,7 +617,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return false; return false;
} }
} }
activateNoFocus(getCurrentFor(trace).object(obj), ActivationCause.SYNC_MODEL); activateAndNotify(getCurrentFor(trace).object(obj), ActivationCause.SYNC_MODEL, false);
return true; return true;
} }
@ -1086,14 +1091,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return elem.toString(); 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) { protected static boolean isSameFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) {
if (!Objects.equals(prev.getObject(), resolved.getObject())) { if (!Objects.equals(prev.getObject(), resolved.getObject())) {
return false; return false;
@ -1110,7 +1107,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return true; return true;
} }
protected static TargetObject translateToFocus(DebuggerCoordinates prev, protected static TargetObject translateToTarget(DebuggerCoordinates prev,
DebuggerCoordinates resolved) { DebuggerCoordinates resolved) {
if (!resolved.isAliveAndPresent()) { if (!resolved.isAliveAndPresent()) {
return null; return null;
@ -1141,7 +1138,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@Override @Override
public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates, public CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
ActivationCause cause, boolean syncTargetFocus) { ActivationCause cause, boolean syncTarget) {
DebuggerCoordinates prev; DebuggerCoordinates prev;
DebuggerCoordinates resolved; DebuggerCoordinates resolved;
@ -1158,21 +1155,21 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return AsyncUtils.NIL; return AsyncUtils.NIL;
} }
CompletableFuture<Void> future = prepareViewAndFireEvent(resolved, cause); CompletableFuture<Void> future = prepareViewAndFireEvent(resolved, cause);
if (!syncTargetFocus) { if (!syncTarget) {
return future; return future;
} }
if (!synchronizeFocus.get()) { if (!synchronizeActive.get()) {
return future; return future;
} }
TraceRecorder recorder = resolved.getRecorder(); TraceRecorder recorder = resolved.getRecorder();
if (recorder == null) { if (recorder == null) {
return future; return future;
} }
TargetObject focus = translateToFocus(prev, resolved); TargetObject activate = translateToTarget(prev, resolved);
if (focus == null || !focus.isValid()) { if (activate == null || !activate.isValid()) {
return future; return future;
} }
recorder.requestFocus(focus); recorder.requestActivation(activate);
return future; return future;
} }
@ -1225,24 +1222,24 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
} }
@Override @Override
public void setSynchronizeFocus(boolean enabled) { public void setSynchronizeActive(boolean enabled) {
synchronizeFocus.set(enabled, null); synchronizeActive.set(enabled, null);
// TODO: Which action to take here, if any? // TODO: Which action to take here, if any?
} }
@Override @Override
public boolean isSynchronizeFocus() { public boolean isSynchronizeActive() {
return synchronizeFocus.get(); return synchronizeActive.get();
} }
@Override @Override
public void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener) { public void addSynchronizeActiveChangeListener(BooleanChangeAdapter listener) {
synchronizeFocus.addChangeListener(listener); synchronizeActive.addChangeListener(listener);
} }
@Override @Override
public void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener) { public void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener) {
synchronizeFocus.removeChangeListener(listener); synchronizeActive.removeChangeListener(listener);
} }
@Override @Override

View file

@ -54,7 +54,7 @@ public interface DebuggerTraceManagerService {
*/ */
START_RECORDING, 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, SYNC_MODEL,
/** /**
@ -281,11 +281,11 @@ public interface DebuggerTraceManagerService {
* *
* @param coordinates the desired coordinates * @param coordinates the desired coordinates
* @param cause the cause of the activation * @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 * @return a future which completes when emulation and navigation is complete
*/ */
CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates, CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
ActivationCause cause, boolean syncTargetFocus); ActivationCause cause, boolean syncTarget);
/** /**
* Activate the given coordinates, caused by the user * Activate the given coordinates, caused by the user
@ -314,7 +314,7 @@ public interface DebuggerTraceManagerService {
* *
* <p> * <p>
* The manager may use a variety of sources of context including the current trace, the last * 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 * @param trace the trace
* @return the best coordinates * @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 * @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 * @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 * @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 * @param listener the listener receiving change notifications
*/ */
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener); void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener);
/** /**
* Control whether traces should be saved by default * Control whether traces should be saved by default

View file

@ -722,6 +722,13 @@ public interface TraceRecorder {
*/ */
boolean isSupportsFocus(); 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 * Get the last-focused object as observed by this recorder
* *
@ -748,6 +755,14 @@ public interface TraceRecorder {
*/ */
CompletableFuture<Boolean> requestFocus(TargetObject focus); 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. * Wait for pending transactions finish execution.
* *

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.model;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.event.MouseEvent;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -25,6 +26,8 @@ import org.junit.*;
import docking.widgets.table.DynamicTableColumn; import docking.widgets.table.DynamicTableColumn;
import docking.widgets.table.GDynamicColumnTableModel; import docking.widgets.table.GDynamicColumnTableModel;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin; import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
import generic.Unique; import generic.Unique;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
@ -52,45 +55,44 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
static { static {
try { try {
CTX = XmlSchemaContext.deserialize( CTX = XmlSchemaContext.deserialize("""
""" <context>
<context> <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'> <attribute name='Processes' schema='ProcessContainer' />
<attribute name='Processes' schema='ProcessContainer' /> <interface name='EventScope' />
<interface name='EventScope' /> </schema>
</schema> <schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER' attributeResync='ONCE'>
attributeResync='ONCE'> <element schema='Process' />
<element schema='Process' /> </schema>
</schema> <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'> <attribute name='Threads' schema='ThreadContainer' />
<attribute name='Threads' schema='ThreadContainer' /> <attribute name='Handles' schema='HandleContainer' />
<attribute name='Handles' schema='HandleContainer' /> </schema>
</schema> <schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER' attributeResync='ONCE'>
attributeResync='ONCE'> <element schema='Thread' />
<element schema='Thread' /> </schema>
</schema> <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'> <interface name='Thread' />
<interface name='Thread' /> <attribute name='_display' schema='STRING' />
<attribute name='_display' schema='STRING' /> <attribute name='_self' schema='Thread' />
<attribute name='_self' schema='Thread' /> <attribute name='Stack' schema='Stack' />
<attribute name='Stack' schema='Stack' /> </schema>
</schema> <schema name='Stack' canonical='yes' elementResync='NEVER'
<schema name='Stack' canonical='yes' elementResync='NEVER' attributeResync='ONCE'>
attributeResync='ONCE'> <interface name='Stack' />
<interface name='Stack' /> <element schema='Frame' />
<element schema='Frame' /> </schema>
</schema> <schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>
<schema name='Frame' elementResync='NEVER' attributeResync='NEVER'> <interface name='StackFrame' />
<interface name='StackFrame' /> </schema>
</schema> <schema name='HandleContainer' canonical='yes' elementResync='NEVER'
<schema name='HandleContainer' canonical='yes' elementResync='NEVER' attributeResync='ONCE'>
attributeResync='ONCE'> <element schema='INT' />
<element schema='INT' /> </schema>
</schema> </context>
</context> """);
""");
} }
catch (JDOMException e) { catch (JDOMException e) {
throw new AssertionError(); throw new AssertionError();
@ -241,9 +243,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
protected void assertPathIs(TraceObjectKeyPath path, int elemCount, int attrCount) { protected void assertPathIs(TraceObjectKeyPath path, int elemCount, int attrCount) {
assertEquals(path, modelProvider.getPath()); assertEquals(path, modelProvider.getPath());
assertEquals(path.toString(), modelProvider.pathField.getText()); assertEquals(path.toString(), modelProvider.pathField.getText());
AbstractNode item = modelProvider.objectsTreePanel.getSelectedItem();
assertNotNull(item);
assertEquals(path, item.getValue().getChild().getCanonicalPath());
// Table model is threaded // Table model is threaded
waitForPass(() -> assertEquals(elemCount, waitForPass(() -> assertEquals(elemCount,
modelProvider.elementsTablePanel.tableModel.getModelData().size())); modelProvider.elementsTablePanel.tableModel.getModelData().size()));
@ -394,13 +393,40 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
assertPathIsThreadsContainer(); 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 @Test
public void testDoubleClickLinkInElementsTable() throws Throwable { public void testDoubleClickLinkInElementsTable() throws Throwable {
createTraceAndPopulateObjects(); createTraceAndPopulateObjects();
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForSwing();
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Links")); TraceObjectKeyPath pathLinks = TraceObjectKeyPath.parse("Processes[0].Links");
modelProvider.setPath(pathLinks);
waitForTasks(); waitForTasks();
ValueRow row2 = waitForValue(() -> { ValueRow row2 = waitForValue(() -> {
@ -421,7 +447,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
}); });
clickTableCell(modelProvider.elementsTablePanel.table, rowIndex, 0, 2); 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 @Test
@ -451,7 +478,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
}); });
clickTableCell(modelProvider.elementsTablePanel.table, rowIndex, 0, 2); 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) { protected void selectAttribute(String key) {
@ -492,7 +520,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
}); });
clickTableCell(modelProvider.attributesTablePanel.table, rowIndex, 0, 2); 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 @Test
@ -528,7 +557,8 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
}); });
clickTableCell(modelProvider.attributesTablePanel.table, rowIndex, 0, 2); clickTableCell(modelProvider.attributesTablePanel.table, rowIndex, 0, 2);
assertPathIsThreadsContainer(); assertEquals(TraceObjectKeyPath.parse("Processes[0].Threads"),
traceManager.getCurrentObject().getCanonicalPath());
} }
@Test @Test
@ -565,7 +595,9 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForSwing(); waitForSwing();
modelProvider.setPath(TraceObjectKeyPath.parse("Processes[0].Threads[2]")); TraceObjectKeyPath thread2Path = TraceObjectKeyPath.parse("Processes[0].Threads[2]");
modelProvider.setPath(thread2Path);
modelProvider.setTreeSelection(thread2Path);
waitForTasks(); waitForTasks();
AbstractNode nodeThread2 = AbstractNode nodeThread2 =
@ -603,7 +635,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
TraceObjectKeyPath thread3Path = TraceObjectKeyPath.parse("Processes[0].Threads[3]"); TraceObjectKeyPath thread3Path = TraceObjectKeyPath.parse("Processes[0].Threads[3]");
assertPathIs(thread3Path, 0, 5); assertPathIs(thread3Path, 0, 5);
assertEquals(thread3Path, traceManager.getCurrentObject().getCanonicalPath());
} }
@Test @Test
@ -790,52 +821,6 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
waitForPass(() -> assertEquals("<html>Renamed Thread", node.getDisplayText())); waitForPass(() -> assertEquals("<html>Renamed Thread", node.getDisplayText()));
} }
@Test
public void testTreeSelectionActivatesObject() throws Throwable {
createTraceAndPopulateObjects();
TraceObjectManager objects = tb.trace.getObjectManager();
TraceObject root = objects.getRootObject();
TraceObjectKeyPath processesPath = TraceObjectKeyPath.parse("Processes");
TraceObject processes = objects.getObjectByCanonicalPath(processesPath);
traceManager.activateObject(root);
waitForTasks();
modelProvider.setTreeSelection(processesPath, EventOrigin.USER_GENERATED);
waitForSwing();
assertEquals(processes, traceManager.getCurrentObject());
}
@Test
public void testElementSelectionActivatesObject() throws Throwable {
createTraceAndPopulateObjects();
TraceObjectManager objects = tb.trace.getObjectManager();
TraceObject processes =
objects.getObjectByCanonicalPath(TraceObjectKeyPath.parse("Processes"));
TraceObject process0 = processes.getElement(0, 0).getChild();
traceManager.activateObject(processes);
waitForTasks();
assertTrue(modelProvider.elementsTablePanel.trySelect(process0));
waitForSwing();
assertEquals(process0, traceManager.getCurrentObject());
}
@Test
public void testAttributeSelectionActivatesObject() throws Throwable {
createTraceAndPopulateObjects();
TraceObjectManager objects = tb.trace.getObjectManager();
TraceObject root = objects.getRootObject();
TraceObject processes = root.getAttribute(0, "Processes").getChild();
traceManager.activateObject(root);
waitForTasks();
assertTrue(modelProvider.attributesTablePanel.trySelect(processes));
waitForSwing();
assertEquals(processes, traceManager.getCurrentObject());
}
@Test @Test
public void testObjectActivationSelectsTree() throws Throwable { public void testObjectActivationSelectsTree() throws Throwable {
createTraceAndPopulateObjects(); createTraceAndPopulateObjects();
@ -865,11 +850,12 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
traceManager.activateObject(processes); traceManager.activateObject(processes);
waitForTasks(); waitForTasks();
assertEquals(processes, modelProvider.getTreeSelection().getChild()); modelProvider.setTreeSelection(processes.getCanonicalPath());
waitForSwing();
// TODO: Is this the desired behavior?
traceManager.activateObject(root); traceManager.activateObject(root);
waitForTasks(); waitForTasks();
// TODO: Is this the desired behavior?
assertEquals(processes, modelProvider.getTreeSelection().getChild()); assertEquals(processes, modelProvider.getTreeSelection().getChild());
} }
@ -925,6 +911,12 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerGUITe
TraceObject processes = root.getAttribute(0, "Processes").getChild(); TraceObject processes = root.getAttribute(0, "Processes").getChild();
traceManager.activateObject(root); traceManager.activateObject(root);
waitForTasks(); 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 * TODO: It's interesting that activating a parent then a child produces a different end

View file

@ -17,11 +17,11 @@ package ghidra.app.plugin.core.debug.gui.stack;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.event.MouseEvent;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.List; import java.util.List;
import org.junit.*; import org.junit.Before;
import org.junit.Test;
import generic.Unique; import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
@ -404,32 +404,6 @@ public class DebuggerStackProviderLegacyTest extends AbstractGhidraHeadedDebugge
assertProviderEmpty(); 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 @Test
public void testActivateFrameSelectsRow() throws Exception { public void testActivateFrameSelectsRow() throws Exception {
createAndOpenTrace(); createAndOpenTrace();
@ -455,6 +429,27 @@ public class DebuggerStackProviderLegacyTest extends AbstractGhidraHeadedDebugge
assertEquals(1, stackProvider.legacyPanel.stackTable.getSelectedRow()); 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 @Test
public void testActivateThenAddMappingPopulatesFunctionColumn() throws Exception { public void testActivateThenAddMappingPopulatesFunctionColumn() throws Exception {
createTrace(); createTrace();

View file

@ -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.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty; 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.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.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerStaticMappingService;
@ -487,33 +488,6 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
waitForPass(() -> assertProviderEmpty()); 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 @Test
public void testActivateFrameSelectsRow() throws Exception { public void testActivateFrameSelectsRow() throws Exception {
createAndOpenTrace(); createAndOpenTrace();
@ -541,6 +515,32 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
waitForPass(() -> assertEquals(allItems.get(0), stackProvider.panel.getSelectedItem())); 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 @Test
public void testActivateTheAddMappingPopulatesFunctionColumn() throws Exception { public void testActivateTheAddMappingPopulatesFunctionColumn() throws Exception {
createTrace(); createTrace();

View file

@ -364,21 +364,16 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
} }
@Test @Test
public void testSelectThreadInTableActivatesThread() throws Exception { public void testDoubleClickThreadInTableActivatesThread() throws Exception {
createAndOpenTrace(); createAndOpenTrace();
addThreads(); addThreads();
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
assertThreadsPopulated(); assertThreadsPopulated();
assertThreadSelected(thread1); // Manager selects default if not live
clickTableCellWithButton(threadsProvider.legacyPanel.threadTable, 1, 0, MouseEvent.BUTTON1); clickTableCell(threadsProvider.legacyPanel.threadTable, 1, 0, 2);
assertEquals(thread2, traceManager.getCurrentThread());
waitForPass(() -> {
assertThreadSelected(thread2);
assertEquals(thread2, traceManager.getCurrentThread());
});
} }
@Test @Test
@ -397,5 +392,4 @@ public class DebuggerThreadsProviderLegacyTest extends AbstractGhidraHeadedDebug
assertEquals(6, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue()); assertEquals(6, threadsProvider.legacyPanel.headerRenderer.getCursorPosition().longValue());
} }
} }

View file

@ -112,26 +112,26 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
public void activateObjectsMode() throws Exception { public void activateObjectsMode() throws Exception {
// NOTE the use of index='1' allowing object-based managers to ID unique path // NOTE the use of index='1' allowing object-based managers to ID unique path
ctx = XmlSchemaContext.deserialize("" + // ctx = XmlSchemaContext.deserialize("""
"<context>" + // <context>
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + // <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
" <attribute name='Processes' schema='ProcessContainer' />" + // <attribute name='Processes' schema='ProcessContainer' />
" </schema>" + // </schema>
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + // <schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
" attributeResync='ONCE'>" + // attributeResync='ONCE'>
" <element index='1' schema='Process' />" + // <---- NOTE HERE <element index='1' schema='Process' />
" </schema>" + // </schema>
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + // <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
" <attribute name='Threads' schema='ThreadContainer' />" + // <attribute name='Threads' schema='ThreadContainer' />
" </schema>" + // </schema>
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + // <schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
" attributeResync='ONCE'>" + // attributeResync='ONCE'>
" <element schema='Thread' />" + // <element schema='Thread' />
" </schema>" + // </schema>
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + // <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
" <interface name='Thread' />" + // <interface name='Thread' />
" </schema>" + // </schema>
"</context>"); </context>""");
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
@ -521,25 +521,18 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
} }
@Test @Test
public void testSelectThreadInTableActivatesThread() throws Exception { public void testDoubleClickThreadInTableActivatesThread() throws Exception {
createAndOpenTrace(); createAndOpenTrace();
addThreads(); addThreads();
traceManager.activateTrace(tb.trace); traceManager.activateTrace(tb.trace);
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
waitForTasks(); waitForTasks();
waitForPass(() -> { waitForPass(() -> assertThreadsPopulated());
assertThreadsPopulated();
assertThreadSelected(thread1); // Manager selects default if not live
});
GhidraTable table = QueryPanelTestHelper.getTable(provider.panel); GhidraTable table = QueryPanelTestHelper.getTable(provider.panel);
clickTableCellWithButton(table, 1, 0, MouseEvent.BUTTON1); clickTableCell(table, 1, 0, 2);
assertEquals(thread2, traceManager.getCurrentThread());
waitForPass(() -> {
assertThreadSelected(thread2);
assertEquals(thread2, traceManager.getCurrentThread());
});
} }
@Test @Test

View file

@ -286,31 +286,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
tb.trace.getTimeManager().getSnapshot(0, false).getDescription()); 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 @Test
public void testActivateSnapSelectsRow() throws Exception { public void testActivateSnapSelectsRow() throws Exception {
createSnaplessTrace(); createSnaplessTrace();
@ -341,6 +316,25 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem()); 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 @Test
public void testAddScratchThenActivateIsHidden() throws Exception { public void testAddScratchThenActivateIsHidden() throws Exception {
createSnaplessTrace(); createSnaplessTrace();

View file

@ -392,7 +392,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
@Test @Test
public void testSynchronizeFocusTraceToModel() throws Throwable { public void testSynchronizeFocusTraceToModel() throws Throwable {
assertTrue(traceManager.isSynchronizeFocus()); assertTrue(traceManager.isSynchronizeActive());
createTestModel(); createTestModel();
mb.createTestProcessesAndThreads(); mb.createTestProcessesAndThreads();
@ -444,7 +444,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus())); waitForPass(() -> assertEquals(frame0, mb.testModel.session.getFocus()));
traceManager.setSynchronizeFocus(false); traceManager.setSynchronizeActive(false);
traceManager.activateFrame(1); traceManager.activateFrame(1);
waitForSwing(); waitForSwing();
@ -453,7 +453,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
@Test @Test
public void testSynchronizeFocusModelToTrace() throws Throwable { public void testSynchronizeFocusModelToTrace() throws Throwable {
assertTrue(traceManager.isSynchronizeFocus()); assertTrue(traceManager.isSynchronizeActive());
createTestModel(); createTestModel();
mb.createTestProcessesAndThreads(); mb.createTestProcessesAndThreads();
@ -499,7 +499,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
waitForPass(() -> assertEquals(0, traceManager.getCurrentFrame())); waitForPass(() -> assertEquals(0, traceManager.getCurrentFrame()));
traceManager.setSynchronizeFocus(false); traceManager.setSynchronizeActive(false);
waitOn(mb.testModel.session.requestFocus(frame1)); waitOn(mb.testModel.session.requestFocus(frame1));
// Not super reliable, but at least wait for it to change in case it does // Not super reliable, but at least wait for it to change in case it does
Thread.sleep(200); Thread.sleep(200);

View file

@ -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 // 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(); Font font = getFont();
// check if someone set a new font on the renderer // check if someone set a new font on the renderer
if (font != cachedDefaultFont && font != cachedBoldFont) { if (font != cachedDefaultFont && font != cachedBoldFont) {