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
public void setCurrentThreadId(DebugThreadId id) {
HRESULT hr = jnaSysobj.SetCurrentThreadId(new ULONG(id.id));
if (!hr.equals(COMUtilsExtra.E_UNEXPECTED)) {
if (!hr.equals(COMUtilsExtra.E_UNEXPECTED) && !hr.equals(COMUtilsExtra.E_NOINTERFACE)) {
COMUtils.checkRC(hr);
}
}

View file

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

View file

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

View file

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

View file

@ -264,7 +264,8 @@ public class GdbModelTargetSession extends DefaultTargetModelRoot
* or be used as an example for other implementations.
*/
if (!PathUtils.isAncestor(this.getPath(), obj.getPath())) {
throw new DebuggerIllegalArgumentException("Can only focus a successor of the scope");
throw new DebuggerIllegalArgumentException(
"Can only activate a successor of the scope");
}
TargetObject cur = obj;
while (cur != null) {

View file

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

View file

@ -35,6 +35,18 @@
because it descends from an inferior or process. In almost every case, the selection directly
or indirectly determines the set of valid or enabled actions.</P>
<P>The tree will display the active object, e.g., the current thread, and its ancestors in
<B>bold</B> font. By default, this is also the selected object. To activate a different object,
double-click it or select it and press ENTER. This will command the debugger and the rest of
the UI to make that object the current object. For example, double-clicking the 2nd thread
would be equivalent to <CODE>thread 2</CODE> in GDB, or <CODE>~2s</CODE> in WinDbg. The actions
apply to the <EM>selected</EM> object, which may differ from the <EM>active</EM> object. For
example, suppose Thread 1 is active. If you click "Step Into," it will command the debugger to
step Thread 1. If you select (but do not activate) Thread 2 and click "Step Into," it will
command the debugger to step Thread 2, even though Thread 1 is still the active thread. Likely,
Thread 2 will become active during the Step Into command, but that behavior is determined by
the model implementation.</P>
<P>The application of these special properties to each object to determine its behavior and
relevant actions allows all objects to be treated generically. This feature has several
powerful implications. All objects may be represented in many ways, and one representation may
@ -170,6 +182,12 @@
"help/topics/DebuggerBreakpointMarkerPlugin/DebuggerBreakpointMarkerPlugin.html">Breakpoint
Marker</A> actions from the disassembly listings.</P>
<H3><A name="toggle_option"></A><IMG alt="" src="images/system-switch-user.png"> Toggle
Option</H3>
<P>Toggle an object. This may apply to a breakpoint or to configuration options provided in the
model tree.</P>
<H2>Actions for Trace Management</H2>
<P>The following actions manage target tracing. Note that many other windows are not usable

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
the target. Not all debuggers will unwind the stack, in which case, this window displays only
the innermost frame. When emulation is used to generate the current machine state, only a
single synthetic frame is shown. Level 0 always refers to the innermost frame, and each
incremental level refers to the next caller in the chain &mdash; most of the time. The current
frame comprises one element of the tool's current "coordinates." Selecting a frame changes
those coordinates, potentially causing other windows to display different information. Namely,
the <A href="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A>
window will show registers for the current frame, assuming they can be retrieved. The Listings
may also navigate to the current frame's program counter.</P>
single synthetic frame is shown. See the <A href="#unwind_stack">Unwind Stack</A> action for an
alternative mechanism that unwinds using Ghidra's program databases and works with emulation.
Level 0 always refers to the innermost frame, and each incremental level refers to the next
caller in the chain &mdash; most of the time. The current frame comprises one element of the
tool's current "coordinates." Double-clicking a frame changes those coordinates, potentially
causing other windows to display different information. Namely, the <A href=
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
show registers for the current frame, assuming they can be retrieved. The Listings may also
navigate to the current frame's program counter.</P>
<H2>Table Columns</H2>
@ -41,8 +43,7 @@
<LI>Level - the level of the frame, counting 0-up starting at the innermost frame.</LI>
<LI>PC - the address of the instruction to execute next, or upon return of the callee for
non-0 frames. Different platforms may have different subtleties in how they report PC.
Double-clicking this field will navigate to the address.</LI>
non-0 frames. Different platforms may have different subtleties in how they report PC.</LI>
<LI>Function - the name of the containing function, if Ghidra has the corresponding module
image imported and analyzed.</LI>
@ -66,7 +67,8 @@
and unwind it in the same manner. This proceeds until analysis fails or the stack segment is
exhausted. For best results, ensure you have imported and opened the Ghidra program database
for every module, or at least the subset you expect to see in your stack. To view the results,
navigate to or follow the stack pointer in a Dynamic Listing.</P>
navigate to or follow the stack pointer in a Dynamic Listing. The Stack window <EM>does
not</EM> display Ghidra's unwinding results.</P>
<TABLE width="100%">
<TBODY>
@ -101,8 +103,8 @@
<CODE>posOff_</CODE> for positive offsets). They represent unused or unknown entries.</LI>
</UL>
<P>The frame entries are not automatically updated when a function's frame changes in a program
database. To update the unwind after changing a function's stack frame, you must unwind
again.</P>
<P>The frame entries are <EM>not</EM> automatically updated when a function's frame changes in
a program database. To update the unwind after changing a function's stack frame, you must
unwind again.</P>
</BODY>
</HTML>

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>
<P>Selecting a thread in the table will navigate to (or "activate" or "focus") that thread.
Windows which are sensitive to the current thread will update. Notably, the <A href=
<P>Double-clicking a thread in the table will navigate to (or "activate" or "focus") that
thread. Windows which are sensitive to the current thread will update. Notably, the <A href=
"help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html">Registers</A> window will
display the activated thread's register values. <A href=
"help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html">Listing</A> windows with
@ -88,15 +88,15 @@
<H2>Actions</H2>
<H3><A name="sync_focus"></A>Synchronize Trace and Target Focus</H3>
<H3><A name="sync_target"></A>Synchronize Trace and Target Activation</H3>
<P>This toggle is always available and is enabled by default. While enabled, any changes in
navigation coordinates are translated, to the extent possible, and sent to the connected
debugger. This may, for example, issue <CODE>thread</CODE> and/or <CODE>frame</CODE> commands
to GDB so that commands typed into its CLI will refer to the same thread and frame as is
focused in Ghidra. Conversely, any debugger events which indicate a change in focus are
translated, to the extent possible, into navigation coordinates and activated in Ghidra. For
example, if the user issues a <CODE>frame</CODE> command to the CLI of a GDB connection, then
Ghidra will navigate to that same frame.</P>
to GDB so that commands typed into its CLI will refer to the same thread and frame as is active
in Ghidra. Conversely, any target events which indicate a change in activation are translated,
to the extent possible, into navigation coordinates and activated in Ghidra. For example, if
the user issues a <CODE>frame</CODE> command to the CLI of a GDB connection, then Ghidra will
navigate to that same frame.</P>
</BODY>
</HTML>

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
time ranges to describe lifespans of various records. Those times refer to the "snap," which is
a 0-up counter of snapshot records. Thus, a snapshot is a collection of observations of a
target's state, usually while suspended, along with any mark up. Selecting a snapshot navigates
to the selected point in time. Note that browsing the past may prevent other windows from
interacting with a live target.</P>
target's state, usually while suspended, along with any mark up. Double-clicking a snapshot
navigates to the selected point in time. Note that navigating to the past may change your <A
href="help/topics/DebuggerControlPlugin/DebuggerControlPlugin.html">Control mode</A>.</P>
<H2>Table Columns</H2>

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 {
String NAME = "Synchronize Focus";
interface SynchronizeTargetAction {
String NAME = "Synchronize Target Activation";
String DESCRIPTION = "Synchronize trace activation with debugger focus/select";
Icon ICON = ICON_SYNC;
String HELP_ANCHOR = "sync_focus";
String HELP_ANCHOR = "sync_target";
static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();

View file

@ -101,6 +101,7 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
private Trace trace;
private long snap;
private TraceObject curObject;
private Trace diffTrace;
private long diffSnap;
private ModelQuery query;
@ -173,6 +174,23 @@ public abstract class AbstractQueryTableModel<T> extends ThreadedTableModel<T, T
return snap;
}
protected void currentObjectChanged() {
refresh();
}
public void setCurrentObject(TraceObject curObject) {
if (this.curObject == curObject) {
return;
}
this.curObject = curObject;
currentObjectChanged();
}
public TraceObject getCurrentObject() {
return curObject;
}
protected void diffTraceChanged() {
refresh();
}

View file

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

View file

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

View file

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

View file

@ -181,6 +181,8 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
public interface ValueRow {
String getKey();
TraceObject currentObject();
long currentSnap();
long previousSnap();
@ -212,6 +214,8 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
*/
boolean isModified();
boolean isCurrent();
default <T> ValueAttribute<T> getAttribute(String attributeName, Class<T> type) {
return new ValueAttribute<>(this, attributeName, type);
}
@ -250,6 +254,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
return getSnap();
}
@Override
public TraceObject currentObject() {
return getCurrentObject();
}
@Override
public long previousSnap() {
return getTrace() == getDiffTrace() ? getDiffSnap() : getSnap();
@ -313,6 +322,11 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
public boolean isAttributeModified(String attributeName) {
return false;
}
@Override
public boolean isCurrent() {
return false;
}
}
protected class ObjectRow extends AbstractValueRow {
@ -366,6 +380,15 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
public boolean isAttributeModified(String attributeName) {
return isValueModified(getAttributeEntry(attributeName));
}
@Override
public boolean isCurrent() {
TraceObject current = getCurrentObject();
if (current == null) {
return false;
}
return object.getCanonicalPath().isAncestor(current.getCanonicalPath());
}
}
protected ValueRow rowForValue(TraceObjectValue value) {
@ -375,46 +398,13 @@ public class ObjectTableModel extends AbstractQueryTableModel<ValueRow> {
return new PrimitiveRow(value);
}
protected static class ColKey {
protected record ColKey(String name, Class<?> type) {
public static ColKey fromSchema(SchemaContext ctx, AttributeSchema attributeSchema) {
String name = attributeSchema.getName();
Class<?> type =
TraceValueObjectAttributeColumn.computeAttributeType(ctx, attributeSchema);
return new ColKey(name, type);
}
private final String name;
private final Class<?> type;
private final int hash;
public ColKey(String name, Class<?> type) {
this.name = name;
this.type = type;
this.hash = Objects.hash(name, type);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ColKey)) {
return false;
}
ColKey that = (ColKey) obj;
if (!Objects.equals(this.name, that.name)) {
return false;
}
if (this.type != that.type) {
return false;
}
return true;
}
@Override
public int hashCode() {
return hash;
}
}
static class AutoAttributeColumn<T> extends TraceValueObjectAttributeColumn<T> {

View file

@ -16,7 +16,6 @@
package ghidra.app.plugin.core.debug.gui.model;
import java.awt.*;
import java.awt.event.MouseListener;
import java.util.*;
import java.util.List;
import java.util.stream.*;
@ -26,6 +25,7 @@ import javax.swing.JTree;
import javax.swing.tree.TreePath;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import docking.widgets.tree.support.GTreeRenderer;
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
import docking.widgets.tree.support.GTreeSelectionListener;
@ -34,7 +34,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.target.*;
public class ObjectsTreePanel extends JPanel {
@ -43,6 +43,21 @@ public class ObjectsTreePanel extends JPanel {
setHTMLRenderingEnabled(true);
}
private boolean isOnCurrentPath(TraceObjectValue value) {
if (value == null) {
return false;
}
return (value.getValue() instanceof TraceObject child && isOnCurrentPath(child));
}
private boolean isOnCurrentPath(TraceObject object) {
TraceObject cur = current.getObject();
if (cur == null) {
return false;
}
return object.getCanonicalPath().isAncestor(cur.getCanonicalPath());
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
@ -54,6 +69,7 @@ public class ObjectsTreePanel extends JPanel {
AbstractNode node = (AbstractNode) value;
setForeground(getForegroundFor(tree, node.isModified(), selected));
setFont(getFont(isOnCurrentPath(node.getValue())));
return this;
}
@ -68,8 +84,19 @@ public class ObjectsTreePanel extends JPanel {
}
}
static class ObjectGTree extends GTree {
public ObjectGTree(GTreeNode root) {
super(root);
getJTree().setToggleClickCount(0);
}
JTree tree() {
return getJTree();
}
}
protected final ObjectTreeModel treeModel;
protected final GTree tree;
protected final ObjectGTree tree;
protected DebuggerCoordinates current = DebuggerCoordinates.NOWHERE;
protected boolean limitToSnap = true;
@ -83,7 +110,7 @@ public class ObjectsTreePanel extends JPanel {
public ObjectsTreePanel() {
super(new BorderLayout());
treeModel = createModel();
tree = new GTree(treeModel.getRoot());
tree = new ObjectGTree(treeModel.getRoot());
tree.setCellRenderer(new ObjectsTreeRenderer());
add(tree, BorderLayout.CENTER);
@ -108,14 +135,14 @@ public class ObjectsTreePanel extends JPanel {
}
public void goToCoordinates(DebuggerCoordinates coords) {
// TODO: thread should probably become a TraceObject once we transition
if (DebuggerCoordinates.equalsIgnoreRecorderAndView(current, coords)) {
return;
}
DebuggerCoordinates previous = current;
this.current = coords;
if (previous.getSnap() == current.getSnap() &&
previous.getTrace() == current.getTrace()) {
previous.getTrace() == current.getTrace() &&
previous.getObject() == current.getObject()) {
return;
}
try (KeepTreeState keep = keepTreeState()) {
@ -127,6 +154,7 @@ public class ObjectsTreePanel extends JPanel {
treeModel.setSpan(Lifespan.at(current.getSnap()));
}
tree.filterChanged();
// Repaint for bold current path is already going to happen
}
}
@ -210,20 +238,6 @@ public class ObjectsTreePanel extends JPanel {
tree.removeGTreeSelectionListener(listener);
}
@Override
public synchronized void addMouseListener(MouseListener l) {
super.addMouseListener(l);
// Is this a HACK?
tree.addMouseListener(l);
}
@Override
public synchronized void removeMouseListener(MouseListener l) {
super.removeMouseListener(l);
// HACK?
tree.removeMouseListener(l);
}
public void setSelectionMode(int selectionMode) {
tree.getSelectionModel().setSelectionMode(selectionMode);
}
@ -272,4 +286,30 @@ public class ObjectsTreePanel extends JPanel {
public void setSelectedKeyPaths(Collection<TraceObjectKeyPath> keyPaths) {
setSelectedKeyPaths(keyPaths, EventOrigin.API_GENERATED);
}
public void expandCurrent() {
TraceObject object = current.getObject();
if (object == null) {
return;
}
AbstractNode node = getNode(object.getCanonicalPath());
TreePath parentPath = node.getTreePath().getParentPath();
if (parentPath != null) {
tree.expandPath(parentPath);
}
}
public void setSelectedObject(TraceObject object) {
if (object == null) {
tree.clearSelectionPaths();
return;
}
AbstractNode node = getNode(object.getCanonicalPath());
tree.addSelectionPath(node.getTreePath());
}
public void selectCurrent() {
setSelectedObject(current.getObject());
}
}

View file

@ -95,6 +95,17 @@ public class PathTableModel extends AbstractQueryTableModel<PathRow> {
// Root is canonical
return last == null || last.isCanonical();
}
public boolean isCurrent() {
TraceObject current = getCurrentObject();
if (current == null) {
return false;
}
if (!(getValue() instanceof TraceObject child)) {
return false;
}
return child.getCanonicalPath().isAncestor(current.getCanonicalPath());
}
}
public PathTableModel(Plugin plugin) {

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

View file

@ -15,22 +15,51 @@
*/
package ghidra.app.plugin.core.debug.gui.model.columns;
import java.awt.Component;
import docking.widgets.table.AbstractDynamicTableColumn;
import docking.widgets.table.GTableCellRenderingData;
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectValue;
import ghidra.util.table.column.AbstractGColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
public class TracePathLastLifespanColumn
extends AbstractDynamicTableColumn<PathRow, Lifespan, Trace> {
private final class LastLifespanRenderer extends AbstractGColumnRenderer<Lifespan> {
@Override
public String getFilterString(Lifespan t, Settings settings) {
return t == null ? "<null>" : t.toString();
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
PathRow row = (PathRow) data.getRowObject();
if (row.isCurrent()) {
setBold();
}
return this;
}
}
private final LastLifespanRenderer renderer = new LastLifespanRenderer();
@Override
public String getColumnName() {
return "Life";
}
@Override
public GColumnRenderer<Lifespan> getColumnRenderer() {
return renderer;
}
@Override
public Lifespan getValue(PathRow rowObject, Settings settings, Trace data,
ServiceProvider serviceProvider) throws IllegalArgumentException {

View file

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

View file

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

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

View file

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

View file

@ -26,10 +26,6 @@ import ghidra.dbg.target.TargetSteppable.TargetStepKindSet;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.target.schema.TargetObjectSchema.AttributeSchema;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
/**

View file

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

View file

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

View file

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

View file

@ -545,6 +545,17 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
return isLink;
}
public boolean isFocused() {
if (provider == null || targetObject == null) {
return false;
}
TargetObject focus = provider.getFocus();
if (focus == null) {
return false;
}
return PathUtils.isAncestor(targetObject.getPath(), focus.getPath());
}
public String getOrder() {
Integer order = (Integer) attributeMap.get(TargetObject.ORDER_ATTRIBUTE_NAME);
return order == null ? getName() : Integer.toString(order);
@ -578,5 +589,4 @@ public class ObjectContainer implements Comparable<ObjectContainer> {
}
return this.hashCode() - that.hashCode();
}
}

View file

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

View file

@ -23,7 +23,6 @@ import ghidra.app.plugin.core.debug.gui.objects.ObjectContainer;
import ghidra.dbg.target.TargetObject;
public interface ObjectPane {
public ObjectContainer getContainer();
public TargetObject getTargetObject();
@ -46,6 +45,7 @@ public interface ObjectPane {
public void setFocus(TargetObject object, TargetObject focused);
public void setRoot(ObjectContainer root, TargetObject targetObject);
public void setSelectedObject(TargetObject object);
public void setRoot(ObjectContainer root, TargetObject targetObject);
}

View file

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

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;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.TreePath;
@ -49,44 +47,38 @@ public class ObjectTree implements ObjectPane {
public static final Icon ICON_TREE = new GIcon("icon.debugger.tree.object");
private ObjectNode root;
private GTree tree;
private static class MyGTree extends GTree {
public MyGTree(GTreeNode root) {
super(root);
getJTree().setToggleClickCount(0);
}
private JTree tree() {
return getJTree();
}
}
private final ObjectNode root;
private final MyGTree tree;
private final Map<String, ObjectNode> nodeMap = new LinkedHashMap<>();
private final SwingUpdateManager restoreTreeStateManager =
new SwingUpdateManager(this::restoreTreeState);
private TreePath[] currentSelectionPaths;
private List<TreePath> currentExpandedPaths;
private Point currentViewPosition;
private Map<String, ObjectNode> nodeMap = new LinkedHashMap<>();
private SwingUpdateManager restoreTreeStateManager =
new SwingUpdateManager(this::restoreTreeState);
public ObjectTree(ObjectContainer container) {
this.root = new ObjectNode(this, null, container);
addToMap(null, container, root);
this.tree = new GTree(root);
this.tree = new MyGTree(root);
tree.addGTreeSelectionListener(new GTreeSelectionListener() {
@Override
public void valueChanged(GTreeSelectionEvent e) {
DebuggerObjectsProvider provider = container.getProvider();
provider.updateActions(container);
TreePath path = e.getPath();
Object last = path.getLastPathComponent();
if (!(last instanceof ObjectNode)) {
throw new RuntimeException("Path terminating in non-ObjectNode");
}
ObjectNode node = (ObjectNode) last;
TargetObject targetObject = node.getTargetObject();
if (targetObject != null && !(targetObject instanceof DummyTargetObject) &&
e.getEventOrigin().equals(EventOrigin.USER_GENERATED)) {
DebugModelConventions.requestActivation(targetObject).exceptionally(ex -> {
Msg.error(this, "Could not activate " + targetObject, ex);
return null;
});
DebugModelConventions.requestFocus(targetObject).exceptionally(ex -> {
Msg.error(this, "Could not focus " + targetObject, ex);
return null;
});
}
provider.getTool().contextChanged(provider);
if (e.getEventOrigin() == EventOrigin.INTERNAL_GENERATED) {
restoreTreeStateManager.updateLater();
@ -119,8 +111,7 @@ public class ObjectTree implements ObjectPane {
});
tree.setCellRenderer(new ObjectTreeCellRenderer(root.getProvider()));
tree.setDataTransformer(t -> {
if (t instanceof ObjectNode) {
ObjectNode node = (ObjectNode) t;
if (t instanceof ObjectNode node) {
return List.of(node.getContainer().getDecoratedName());
}
return null;
@ -131,8 +122,7 @@ public class ObjectTree implements ObjectPane {
public void treeExpanded(TreeExpansionEvent event) {
TreePath expandedPath = event.getPath();
Object last = expandedPath.getLastPathComponent();
if (last instanceof ObjectNode) {
ObjectNode node = (ObjectNode) last;
if (last instanceof ObjectNode node) {
node.markExpanded();
currentExpandedPaths = tree.getExpandedPaths();
}
@ -142,8 +132,7 @@ public class ObjectTree implements ObjectPane {
public void treeCollapsed(TreeExpansionEvent event) {
TreePath collapsedPath = event.getPath();
Object last = collapsedPath.getLastPathComponent();
if (last instanceof ObjectNode) {
ObjectNode node = (ObjectNode) last;
if (last instanceof ObjectNode node) {
node.markCollapsed();
currentExpandedPaths = tree.getExpandedPaths();
}
@ -153,11 +142,17 @@ public class ObjectTree implements ObjectPane {
tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
TargetObject selectedObject = getSelectedObject();
if (selectedObject != null) {
container.getProvider().navigateToSelectedObject(selectedObject, null);
}
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
activateOrNavigateSelectedObject();
}
}
});
tree.tree().addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
activateOrNavigateSelectedObject();
e.consume();
}
}
});
@ -166,6 +161,27 @@ public class ObjectTree implements ObjectPane {
tree.setSelectedNode(root);
}
private void activateOrNavigateSelectedObject() {
TargetObject object = getSelectedObject();
if (object == null) {
return;
}
if (getProvider().navigateToSelectedObject(object, null) != null) {
return;
}
if (object instanceof DummyTargetObject) {
return;
}
DebugModelConventions.requestActivation(object).exceptionally(ex -> {
Msg.error(this, "Could not activate " + object, ex);
return null;
});
/*DebugModelConventions.requestFocus(object).exceptionally(ex -> {
Msg.error(this, "Could not focus " + object, ex);
return null;
});*/
}
@Override
public ObjectContainer getContainer() {
return root.getContainer();
@ -188,8 +204,8 @@ public class ObjectTree implements ObjectPane {
}
if (path != null) {
Object last = path.getLastPathComponent();
if (last instanceof ObjectNode) {
return ((ObjectNode) last).getContainer().getTargetObject();
if (last instanceof ObjectNode node) {
return node.getContainer().getTargetObject();
}
}
return null;
@ -303,8 +319,7 @@ public class ObjectTree implements ObjectPane {
for (TreePath path : expandedPaths) {
Object[] objs = path.getPath();
for (int i = 0; i < objs.length - 2; i++) {
if (objs[i] instanceof ObjectNode) {
ObjectNode node = (ObjectNode) objs[i];
if (objs[i] instanceof ObjectNode node) {
if (!node.isLoaded()) {
return false;
}
@ -364,6 +379,14 @@ public class ObjectTree implements ObjectPane {
});
}
@Override
public void setSelectedObject(TargetObject object) {
Swing.runIfSwingOrRunLater(() -> {
List<String> path = object.getPath();
tree.setSelectedNodeByNamePath(addRootNameToPath(path));
});
}
private String[] addRootNameToPath(List<String> path) {
String[] fullPath = new String[path.size() + 1];
fullPath[0] = tree.getModelRoot().getName();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,19 +16,20 @@
package ghidra.app.plugin.core.debug.gui.time;
import java.awt.BorderLayout;
import java.awt.Component;
import java.util.Collection;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.*;
import com.google.common.collect.Collections2;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.docking.settings.Settings;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
@ -37,6 +38,7 @@ import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.util.table.GhidraTableFilterPanel;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class DebuggerSnapshotTablePanel extends JPanel {
@ -131,6 +133,23 @@ public class DebuggerSnapshotTablePanel extends JPanel {
}
}
final TableCellRenderer boldCurrentRenderer = new AbstractGColumnRenderer<Object>() {
@Override
public String getFilterString(Object t, Settings settings) {
return t == null ? "<null>" : t.toString();
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
SnapshotRow row = (SnapshotRow) data.getRowObject();
if (row != null && row.getSnap() == currentSnap) {
setBold();
}
return this;
}
};
protected final EnumeratedColumnTableModel<SnapshotRow> snapshotTableModel;
protected final GTable snapshotTable;
protected final GhidraTableFilterPanel<SnapshotRow> snapshotFilterPanel;
@ -155,14 +174,19 @@ public class DebuggerSnapshotTablePanel extends JPanel {
TableColumnModel columnModel = snapshotTable.getColumnModel();
TableColumn snapCol = columnModel.getColumn(SnapshotTableColumns.SNAP.ordinal());
snapCol.setPreferredWidth(40);
snapCol.setCellRenderer(boldCurrentRenderer);
TableColumn timeCol = columnModel.getColumn(SnapshotTableColumns.TIMESTAMP.ordinal());
timeCol.setPreferredWidth(200);
timeCol.setCellRenderer(boldCurrentRenderer);
TableColumn etCol = columnModel.getColumn(SnapshotTableColumns.EVENT_THREAD.ordinal());
etCol.setPreferredWidth(40);
etCol.setCellRenderer(boldCurrentRenderer);
TableColumn schdCol = columnModel.getColumn(SnapshotTableColumns.SCHEDULE.ordinal());
schdCol.setPreferredWidth(60);
schdCol.setCellRenderer(boldCurrentRenderer);
TableColumn descCol = columnModel.getColumn(SnapshotTableColumns.DESCRIPTION.ordinal());
descCol.setPreferredWidth(200);
descCol.setCellRenderer(boldCurrentRenderer);
}
private void addNewListeners() {
@ -260,5 +284,6 @@ public class DebuggerSnapshotTablePanel extends JPanel {
return;
}
snapshotFilterPanel.setSelectedItem(row);
snapshotTableModel.fireTableDataChanged();
}
}

View file

@ -17,7 +17,7 @@ package ghidra.app.plugin.core.debug.gui.time;
import static ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import java.awt.event.MouseEvent;
import java.awt.event.*;
import java.lang.invoke.MethodHandles;
import java.util.Objects;
@ -146,9 +146,32 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter {
return;
}
myActionContext = new DebuggerSnapActionContext(current.getTrace(), snap);
traceManager.activateSnap(snap);
contextChanged();
});
mainPanel.snapshotTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
activateSelectedSnapshot();
}
}
});
mainPanel.snapshotTable.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
activateSelectedSnapshot();
e.consume(); // lest it select the next row down
}
}
});
}
private void activateSelectedSnapshot() {
Long snap = mainPanel.getSelectedSnapshot();
if (snap != null && traceManager != null) {
traceManager.activateSnap(snap);
}
}
protected void createActions() {

View file

@ -387,15 +387,33 @@ public class DefaultTraceRecorder implements TraceRecorder {
return findFocusScope() != null;
}
@Override
public boolean isSupportsActivation() {
return findActiveScope() != null;
}
// NOTE: This may require the scope to be an ancestor of the target
// That should be fine
protected TargetFocusScope findFocusScope() {
List<String> path = target.getModel()
.getRootSchema()
.searchForSuitable(TargetFocusScope.class, target.getPath());
if (path == null) {
return null;
}
return (TargetFocusScope) target.getModel().getModelObject(path);
}
protected TargetActiveScope findActiveScope() {
List<String> path = target.getModel()
.getRootSchema()
.searchForSuitable(TargetActiveScope.class, target.getPath());
if (path == null) {
return null;
}
return (TargetActiveScope) target.getModel().getModelObject(path);
}
@Override
public TargetObject getFocus() {
if (curFocus == null) {
@ -419,8 +437,8 @@ public class DefaultTraceRecorder implements TraceRecorder {
@Override
public CompletableFuture<Boolean> requestFocus(TargetObject focus) {
if (!isSupportsFocus()) {
return CompletableFuture
.failedFuture(new IllegalArgumentException("Target does not support focus"));
return CompletableFuture.failedFuture(
new IllegalArgumentException("Target does not support focus"));
}
if (!PathUtils.isAncestor(getTarget().getPath(), focus.getPath())) {
return CompletableFuture.failedFuture(new IllegalArgumentException(
@ -446,6 +464,36 @@ public class DefaultTraceRecorder implements TraceRecorder {
});
}
@Override
public CompletableFuture<Boolean> requestActivation(TargetObject active) {
if (!isSupportsActivation()) {
return CompletableFuture.failedFuture(
new IllegalArgumentException("Target does not support activation"));
}
if (!PathUtils.isAncestor(getTarget().getPath(), active.getPath())) {
return CompletableFuture.failedFuture(new IllegalArgumentException(
"Requested activation path is not a successor of the target"));
}
TargetActiveScope activeScope = findActiveScope();
if (!PathUtils.isAncestor(activeScope.getPath(), active.getPath())) {
// This should be rare, if not forbidden
return CompletableFuture.failedFuture(new IllegalArgumentException(
"Requested activation path is not a successor of the focus scope"));
}
return activeScope.requestActivation(active).thenApply(__ -> true).exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
String msg = "Could not activate " + active + ": " + ex.getMessage();
plugin.getTool().setStatusInfo(msg);
if (ex instanceof DebuggerModelAccessException) {
Msg.info(this, msg);
}
else {
Msg.error(this, "Could not activate " + active, ex);
}
return false;
});
}
/*---------------- ACCESSOR METHODS -------------------*/
@Override

View file

@ -737,6 +737,11 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
return objectRecorder.isSupportsFocus;
}
@Override
public boolean isSupportsActivation() {
return objectRecorder.isSupportsActivation;
}
@Override
public TargetObject getFocus() {
return curFocus;
@ -764,6 +769,28 @@ public class ObjectBasedTraceRecorder implements TraceRecorder {
return CompletableFuture.completedFuture(false);
}
@Override
public CompletableFuture<Boolean> requestActivation(TargetObject active) {
for (TargetActiveScope scope : objectRecorder.collectTargetSuccessors(target,
TargetActiveScope.class)) {
if (PathUtils.isAncestor(scope.getPath(), active.getPath())) {
return scope.requestActivation(active).thenApply(__ -> true).exceptionally(ex -> {
ex = AsyncUtils.unwrapThrowable(ex);
String msg = "Could not activate " + active + ": " + ex.getMessage();
if (ex instanceof DebuggerModelAccessException) {
Msg.info(this, msg);
}
else {
Msg.error(this, msg, ex);
}
return false;
});
}
}
Msg.info(this, "Could not find suitable active scope for " + active);
return CompletableFuture.completedFuture(false);
}
// UNUSED?
@Override
public CompletableFuture<Void> flushTransactions() {

View file

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

View file

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

View file

@ -54,7 +54,7 @@ public interface DebuggerTraceManagerService {
*/
START_RECORDING,
/**
* The change was driven by the model focus, possibly indirectly by the user
* The change was driven by the model activation, possibly indirectly by the user
*/
SYNC_MODEL,
/**
@ -281,11 +281,11 @@ public interface DebuggerTraceManagerService {
*
* @param coordinates the desired coordinates
* @param cause the cause of the activation
* @param syncTargetFocus true synchronize the current target to the same coordinates
* @param syncTarget true synchronize the current target to the same coordinates
* @return a future which completes when emulation and navigation is complete
*/
CompletableFuture<Void> activateAndNotify(DebuggerCoordinates coordinates,
ActivationCause cause, boolean syncTargetFocus);
ActivationCause cause, boolean syncTarget);
/**
* Activate the given coordinates, caused by the user
@ -314,7 +314,7 @@ public interface DebuggerTraceManagerService {
*
* <p>
* The manager may use a variety of sources of context including the current trace, the last
* coordinates for a trace, the target's last/current focus, the list of active threads, etc.
* coordinates for a trace, the target's last/current activation, the list of live threads, etc.
*
* @param trace the trace
* @return the best coordinates
@ -448,32 +448,32 @@ public interface DebuggerTraceManagerService {
}
/**
* Control whether trace activation is synchronized with debugger focus/select
* Control whether trace activation is synchronized with debugger activation
*
* @param enabled true to synchronize, false otherwise
*/
void setSynchronizeFocus(boolean enabled);
void setSynchronizeActive(boolean enabled);
/**
* Check whether trace activation is synchronized with debugger focus/select
* Check whether trace activation is synchronized with debugger activation
*
* @return true if synchronized, false otherwise
*/
boolean isSynchronizeFocus();
boolean isSynchronizeActive();
/**
* Add a listener for changes to focus synchronization enablement
* Add a listener for changes to activation synchronization enablement
*
* @param listener the listener to receive change notifications
*/
void addSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
void addSynchronizeActiveChangeListener(BooleanChangeAdapter listener);
/**
* Remove a listener for changes to focus synchronization enablement
* Remove a listener for changes to activation synchronization enablement
*
* @param listener the listener receiving change notifications
*/
void removeSynchronizeFocusChangeListener(BooleanChangeAdapter listener);
void removeSynchronizeActiveChangeListener(BooleanChangeAdapter listener);
/**
* Control whether traces should be saved by default

View file

@ -722,6 +722,13 @@ public interface TraceRecorder {
*/
boolean isSupportsFocus();
/**
* Check if the target is subject to a {@link TargetActiveScope}.
*
* @return true if an applicable scope is found, false otherwise.
*/
boolean isSupportsActivation();
/**
* Get the last-focused object as observed by this recorder
*
@ -748,6 +755,14 @@ public interface TraceRecorder {
*/
CompletableFuture<Boolean> requestFocus(TargetObject focus);
/**
* Request activation of a successor of the target
*
* @param active the object to activate
* @return a future which completes with true if the operation was successful, false otherwise.
*/
CompletableFuture<Boolean> requestActivation(TargetObject active);
/**
* Wait for pending transactions finish execution.
*

View file

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

View file

@ -17,11 +17,11 @@ package ghidra.app.plugin.core.debug.gui.stack;
import static org.junit.Assert.*;
import java.awt.event.MouseEvent;
import java.math.BigInteger;
import java.util.List;
import org.junit.*;
import org.junit.Before;
import org.junit.Test;
import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
@ -404,32 +404,6 @@ public class DebuggerStackProviderLegacyTest extends AbstractGhidraHeadedDebugge
assertProviderEmpty();
}
@Test
@Ignore("TODO") // Not sure why this fails under Gradle but not my IDE
public void testSelectRowActivatesFrame() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
clickTableCellWithButton(stackProvider.legacyPanel.stackTable, 0, 0, MouseEvent.BUTTON1);
waitForSwing();
assertEquals(0, traceManager.getCurrentFrame());
clickTableCellWithButton(stackProvider.legacyPanel.stackTable, 1, 0, MouseEvent.BUTTON1);
waitForSwing();
assertEquals(1, traceManager.getCurrentFrame());
}
@Test
public void testActivateFrameSelectsRow() throws Exception {
createAndOpenTrace();
@ -455,6 +429,27 @@ public class DebuggerStackProviderLegacyTest extends AbstractGhidraHeadedDebugge
assertEquals(1, stackProvider.legacyPanel.stackTable.getSelectedRow());
}
@Test
public void testDoubleClickRowActivatesFrame() throws Exception {
createAndOpenTrace();
TraceThread thread = addThread("Processes[1].Threads[1]");
TraceStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertProviderPopulated();
clickTableCell(stackProvider.legacyPanel.stackTable, 0, 0, 2);
assertEquals(0, traceManager.getCurrentFrame());
clickTableCell(stackProvider.legacyPanel.stackTable, 1, 0, 2);
assertEquals(1, traceManager.getCurrentFrame());
}
@Test
public void testActivateThenAddMappingPopulatesFunctionColumn() throws Exception {
createTrace();

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.model.ObjectTableModel.ValueProperty;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.QueryPanelTestHelper;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils;
import ghidra.app.services.DebuggerStaticMappingService;
@ -487,33 +488,6 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
waitForPass(() -> assertProviderEmpty());
}
@Test
public void testSelectRowActivateFrame() throws Exception {
createAndOpenTrace();
TraceObjectThread thread = addThread(1);
TraceObjectStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateObject(thread.getObject());
waitForTasks();
waitForPass(() -> assertProviderPopulated());
TraceObject frame0 = stack.getObject().getElement(0, 0).getChild();
TraceObject frame1 = stack.getObject().getElement(0, 1).getChild();
List<ValueRow> allItems = stackProvider.panel.getAllItems();
stackProvider.panel.setSelectedItem(allItems.get(1));
waitForTasks();
waitForPass(() -> assertEquals(frame1, traceManager.getCurrentObject()));
stackProvider.panel.setSelectedItem(allItems.get(0));
waitForTasks();
waitForPass(() -> assertEquals(frame0, traceManager.getCurrentObject()));
}
@Test
public void testActivateFrameSelectsRow() throws Exception {
createAndOpenTrace();
@ -541,6 +515,32 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe
waitForPass(() -> assertEquals(allItems.get(0), stackProvider.panel.getSelectedItem()));
}
@Test
public void testDoubleClickRowActivateFrame() throws Exception {
createAndOpenTrace();
TraceObjectThread thread = addThread(1);
TraceObjectStack stack = addStack(thread);
addStackFrames(stack);
waitForDomainObject(tb.trace);
traceManager.activateObject(thread.getObject());
waitForTasks();
waitForPass(() -> assertProviderPopulated());
TraceObject frame0 = stack.getObject().getElement(0, 0).getChild();
TraceObject frame1 = stack.getObject().getElement(0, 1).getChild();
clickTableCell(QueryPanelTestHelper.getTable(stackProvider.panel), 1, 0, 2);
waitForTasks();
waitForPass(() -> assertEquals(frame1, traceManager.getCurrentObject()));
clickTableCell(QueryPanelTestHelper.getTable(stackProvider.panel), 0, 0, 2);
waitForTasks();
waitForPass(() -> assertEquals(frame0, traceManager.getCurrentObject()));
}
@Test
public void testActivateTheAddMappingPopulatesFunctionColumn() throws Exception {
createTrace();

View file

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

View file

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

View file

@ -286,31 +286,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
tb.trace.getTimeManager().getSnapshot(0, false).getDescription());
}
@Test
public void testSelectRowActivatesSnap() throws Exception {
createSnaplessTrace();
traceManager.openTrace(tb.trace);
addSnapshots();
waitForDomainObject(tb.trace);
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
List<SnapshotRow> data = timeProvider.mainPanel.snapshotTableModel.getModelData();
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(0));
waitForSwing();
assertEquals(0, traceManager.getCurrentSnap());
timeProvider.mainPanel.snapshotFilterPanel.setSelectedItem(data.get(1));
waitForSwing();
assertEquals(10, traceManager.getCurrentSnap());
}
@Test
public void testActivateSnapSelectsRow() throws Exception {
createSnaplessTrace();
@ -341,6 +316,25 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
assertNull(timeProvider.mainPanel.snapshotFilterPanel.getSelectedItem());
}
@Test
public void testDoubleClickRowActivatesSnap() throws Exception {
createSnaplessTrace();
traceManager.openTrace(tb.trace);
addSnapshots();
waitForDomainObject(tb.trace);
assertProviderEmpty();
traceManager.activateTrace(tb.trace);
waitForSwing();
clickTableCell(timeProvider.mainPanel.snapshotTable, 0, 0, 2);
assertEquals(0, traceManager.getCurrentSnap());
clickTableCell(timeProvider.mainPanel.snapshotTable, 1, 0, 2);
assertEquals(10, traceManager.getCurrentSnap());
}
@Test
public void testAddScratchThenActivateIsHidden() throws Exception {
createSnaplessTrace();

View file

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

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