Merge remote-tracking branch 'origin/GP-4293_Dan_doubleClickModelActions--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-04-03 13:02:31 -04:00
commit 611aae64ae
30 changed files with 470 additions and 144 deletions

View File

@ -29,6 +29,7 @@
<attribute schema="ANY" hidden="yes" />
</schema>
<schema name="Session" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Access" />
<interface name="Attacher" />
<interface name="Interpreter" />
@ -163,6 +164,7 @@
<attribute schema="VOID" />
</schema>
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Process" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
@ -290,6 +292,7 @@
<attribute schema="VOID" fixed="yes" hidden="yes" />
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
@ -416,6 +419,7 @@
<attribute schema="VOID" />
</schema>
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="StackFrame" />
<interface name="Aggregate" />
<element schema="VOID" />

View File

@ -120,6 +120,7 @@
<attribute schema="VOID" />
</schema>
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Process" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
@ -245,6 +246,7 @@
<attribute schema="VOID" fixed="yes" hidden="yes" />
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
@ -355,6 +357,7 @@
<attribute schema="VOID" />
</schema>
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="StackFrame" />
<interface name="Aggregate" />
<element schema="VOID" />

View File

@ -117,6 +117,7 @@
<attribute schema="VOID" />
</schema>
<schema name="Inferior" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Process" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
@ -241,6 +242,7 @@
<attribute schema="VOID" fixed="yes" hidden="yes" />
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
@ -349,6 +351,7 @@
<attribute schema="VOID" />
</schema>
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="StackFrame" />
<interface name="Aggregate" />
<element schema="VOID" />

View File

@ -153,6 +153,7 @@
<attribute schema="VOID" />
</schema>
<schema name="Process" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Process" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
@ -280,6 +281,7 @@
<attribute schema="VOID" fixed="yes" hidden="yes" />
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="Thread" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
@ -389,6 +391,7 @@
<attribute schema="VOID" />
</schema>
<schema name="StackFrame" elementResync="NEVER" attributeResync="NEVER">
<interface name="Activatable" />
<interface name="StackFrame" />
<interface name="Aggregate" />
<element schema="VOID" />

View File

@ -458,7 +458,7 @@ public interface DebuggerTraceManagerService {
DebuggerCoordinates resolvePath(TraceObjectKeyPath path);
/**
* Activate the given object path
* Activate the given canonical object path
*
* @param path the desired path
*/

View File

@ -169,6 +169,11 @@ public interface Target {
*/
Map<String, ActionEntry> collectActions(ActionName name, ActionContext context);
/**
* @see #execute(String, boolean)
*/
CompletableFuture<String> executeAsync(String command, boolean toString);
/**
* Execute a command as if in the CLI
*
@ -176,11 +181,6 @@ public interface Target {
* @param toString true to capture the output and return it, false to print to the terminal
* @return the captured output, or null if {@code toString} is false
*/
CompletableFuture<String> executeAsync(String command, boolean toString);
/**
* @see #executeAsync(String, boolean)
*/
String execute(String command, boolean toString);
/**

View File

@ -499,12 +499,10 @@ public class DebuggerCoordinates {
else if (trace == null) {
throw new IllegalArgumentException("No trace");
}
else {
if (newPath == null) {
else if (newPath == null) {
return new DebuggerCoordinates(trace, platform, target, thread, view, time, frame,
newPath);
}
}
TraceThread newThread = target != null
? resolveThread(target, newPath)
: resolveThread(trace, newPath);
@ -516,6 +514,31 @@ public class DebuggerCoordinates {
newFrame, newPath);
}
public DebuggerCoordinates pathNonCanonical(TraceObjectKeyPath newPath) {
if (trace == null && newPath == null) {
return NOWHERE;
}
else if (trace == null) {
throw new IllegalArgumentException("No trace");
}
else if (newPath == null) {
return new DebuggerCoordinates(trace, platform, target, thread, view, time, frame,
newPath);
}
TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(newPath);
if (object != null) {
return path(newPath);
}
object = trace.getObjectManager()
.getObjectsByPath(Lifespan.at(getSnap()), newPath)
.findAny()
.orElse(null);
if (object != null) {
return path(object.getCanonicalPath());
}
throw new IllegalArgumentException("No such object at path" + path);
}
protected static TraceThread resolveThread(Target target, TraceObjectKeyPath objectPath) {
return target.getThreadForSuccessor(objectPath);
}

View File

@ -38,6 +38,7 @@ import ghidra.dbg.util.PathMatcher;
import ghidra.dbg.util.PathPredicates;
import ghidra.dbg.util.PathPredicates.Align;
import ghidra.debug.api.model.DebuggerObjectActionContext;
import ghidra.debug.api.model.DebuggerSingleObjectPathActionContext;
import ghidra.debug.api.target.ActionName;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.debug.api.tracermi.*;
@ -144,6 +145,20 @@ public class TraceRmiTarget extends AbstractTarget {
}
}
}
else if (context instanceof DebuggerSingleObjectPathActionContext ctx) {
TraceObject object =
trace.getObjectManager().getObjectByCanonicalPath(ctx.getPath());
if (object != null) {
return object;
}
object = trace.getObjectManager()
.getObjectsByPath(Lifespan.at(getSnap()), ctx.getPath())
.findAny()
.orElse(null);
if (object != null) {
return object;
}
}
}
if (allowCoordsObject) {
DebuggerTraceManagerService traceManager =
@ -156,13 +171,45 @@ public class TraceRmiTarget extends AbstractTarget {
return null;
}
protected Object findArgumentForSchema(ActionContext context, TargetObjectSchema schema,
boolean allowContextObject, boolean allowCoordsObject, boolean allowSuitableObject) {
/**
* "Find" a boolean value for the given context.
*
* <p>
* At the moment, this is only used for toggle actions, where the "found" parameter is the
* opposite of the context object's current state. That object is presumed the object argument
* of the "toggle" method.
*
* @param action the action name, so this is only applied to {@link ActionName#TOGGLE}
* @param context the context in which to find the object whose current state is to be
* considered
* @param allowContextObject true to allow the object to come from context
* @param allowCoordsObject true to allow the object to come from the current coordinates
* @return a value if found, null if not
*/
protected Boolean findBool(ActionName action, ActionContext context, boolean allowContextObject,
boolean allowCoordsObject) {
if (!Objects.equals(action, ActionName.TOGGLE)) {
return null;
}
TraceObject object = findObject(context, allowContextObject, allowCoordsObject);
if (object == null) {
return null;
}
TraceObjectValue attrEnabled =
object.getAttribute(getSnap(), TargetTogglable.ENABLED_ATTRIBUTE_NAME);
boolean enabled = attrEnabled != null && attrEnabled.getValue() instanceof Boolean b && b;
return !enabled;
}
protected Object findArgumentForSchema(ActionName action, ActionContext context,
TargetObjectSchema schema, boolean allowContextObject, boolean allowCoordsObject,
boolean allowSuitableObject) {
if (schema instanceof EnumerableTargetObjectSchema prim) {
return switch (prim) {
case OBJECT -> findObject(context, allowContextObject, allowCoordsObject);
case ADDRESS -> findAddress(context);
case RANGE -> findRange(context);
case BOOL -> findBool(action, context, allowContextObject, allowCoordsObject);
default -> null;
};
}
@ -183,8 +230,9 @@ public class TraceRmiTarget extends AbstractTarget {
MISSING; // The argument requires a prompt
}
protected Object findArgument(RemoteParameter parameter, ActionContext context,
boolean allowContextObject, boolean allowCoordsObject, boolean allowSuitableObject) {
protected Object findArgument(ActionName action, RemoteParameter parameter,
ActionContext context, boolean allowContextObject, boolean allowCoordsObject,
boolean allowSuitableObject) {
SchemaName type = parameter.type();
SchemaContext ctx = getSchemaContext();
if (ctx == null) {
@ -196,8 +244,8 @@ public class TraceRmiTarget extends AbstractTarget {
Msg.error(this, "Schema " + type + " not in trace! " + trace);
return null;
}
Object arg = findArgumentForSchema(context, schema, allowContextObject, allowCoordsObject,
allowSuitableObject);
Object arg = findArgumentForSchema(action, context, schema, allowContextObject,
allowCoordsObject, allowSuitableObject);
if (arg != null) {
return arg;
}
@ -211,8 +259,8 @@ public class TraceRmiTarget extends AbstractTarget {
boolean allowContextObject, boolean allowCoordsObject, boolean allowSuitableObject) {
Map<String, Object> args = new HashMap<>();
for (RemoteParameter param : method.parameters().values()) {
Object found = findArgument(param, context, allowContextObject, allowCoordsObject,
allowSuitableObject);
Object found = findArgument(method.action(), param, context, allowContextObject,
allowCoordsObject, allowSuitableObject);
if (found != null) {
args.put(param.name(), found);
}
@ -433,6 +481,12 @@ public class TraceRmiTarget extends AbstractTarget {
true, false, false);
}
@Override
protected Map<String, ActionEntry> collectToggleActions(ActionContext context) {
return collectFromMethods(connection.getMethods().getByAction(ActionName.TOGGLE), context,
true, false, false);
}
@Override
public boolean isSupportsFocus() {
TargetObjectSchema schema = trace.getObjectManager().getRootSchema();
@ -1219,9 +1273,8 @@ public class TraceRmiTarget extends AbstractTarget {
String condition, String commands) {
RemoteParameter paramProc = brk.params.get("process");
if (paramProc != null) {
Object proc =
findArgumentForSchema(null, getSchemaContext().getSchema(paramProc.type()), true,
true, true);
Object proc = findArgumentForSchema(null, null,
getSchemaContext().getSchema(paramProc.type()), true, true, true);
if (proc == null) {
Msg.error(this, "Cannot find required process argument for " + brk.method);
}

View File

@ -23,6 +23,7 @@ import ghidra.debug.api.breakpoint.LogicalBreakpoint;
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.thread.TraceThread;
@ -82,6 +83,10 @@ public class BreakpointLocationRow {
return loc.getMinAddress();
}
public ProgramLocation getProgramLocation() {
return new ProgramLocation(loc.getTrace().getProgramView(), getAddress());
}
public String getTraceName() {
return loc.getTrace().getName();
}

View File

@ -1153,7 +1153,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
traceManager.activateTrace(trace);
}
listingService.goTo(row.getAddress(), true);
listingService.goTo(row.getProgramLocation(), true);
}
protected void createActions() {

View File

@ -267,8 +267,8 @@ public class DebuggerLegacyRegionsPanel extends JPanel {
int selectedRow = regionTable.getSelectedRow();
int selectedColumn = regionTable.getSelectedColumn();
Object value = regionTable.getValueAt(selectedRow, selectedColumn);
if (value instanceof Address) {
listingService.goTo((Address) value, true);
if (value instanceof Address address) {
listingService.goTo(address, true);
}
}
}

View File

@ -187,7 +187,7 @@ public class DebuggerRegionsPanel extends AbstractObjectsTableBasedPanel<TraceOb
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new RegionTableModel(plugin);
}

View File

@ -26,27 +26,25 @@ import javax.swing.event.ListSelectionListener;
import docking.ComponentProvider;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.model.DebuggerObjectActionContext;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectInterface;
import ghidra.trace.model.target.*;
public abstract class AbstractObjectsTableBasedPanel<U extends TraceObjectInterface>
extends ObjectsTablePanel implements ListSelectionListener, CellActivationListener {
extends ObjectsTablePanel
implements ListSelectionListener, CellActivationListener, ObjectDefaultActionsMixin {
private final ComponentProvider provider;
private final Class<U> objType;
@AutoServiceConsumed
protected DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
protected DebuggerListingService listingService;
@SuppressWarnings("unused")
@ -121,23 +119,32 @@ public abstract class AbstractObjectsTableBasedPanel<U extends TraceObjectInterf
@Override
public void cellActivated(JTable table) {
if (listingService == null) {
if (performElementCellDefaultAction(table)) {
return;
}
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
Object value = table.getValueAt(row, col);
if (!(value instanceof ValueProperty<?> property)) {
performValueRowDefaultAction(getSelectedItem());
}
@Override
public DebuggerCoordinates getCurrent() {
return current;
}
@Override
public PluginTool getTool() {
return plugin.getTool();
}
@Override
public void activatePath(TraceObjectKeyPath path) {
if (current.getTrace() == null) {
return;
}
Object propVal = property.getValue();
if (propVal instanceof Address address) {
listingService.goTo(address, true);
try {
traceManager.activate(current.pathNonCanonical(path));
}
else if (propVal instanceof AddressRange range) {
listingService.setCurrentSelection(
new ProgramSelection(range.getMinAddress(), range.getMaxAddress()));
listingService.goTo(range.getMinAddress(), true);
catch (IllegalArgumentException e) {
plugin.getTool().setStatusInfo(e.getMessage(), true);
}
}
}

View File

@ -41,6 +41,7 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
void cellActivated(JTable table);
}
protected final Plugin plugin;
protected final M tableModel;
protected final GhidraTable table;
protected final GhidraTableFilterPanel<T> filterPanel;
@ -54,7 +55,9 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
public AbstractQueryTablePanel(Plugin plugin) {
super(new BorderLayout());
tableModel = createModel(plugin);
this.plugin = plugin;
tableModel = createModel();
table = new GhidraTable(tableModel);
filterPanel = new GhidraTableFilterPanel<>(table, tableModel);
@ -80,7 +83,7 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
});
}
protected abstract M createModel(Plugin plugin);
protected abstract M createModel();
protected void coordinatesChanged() {
// Extension point

View File

@ -45,11 +45,11 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.CloneWindowAction;
import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider;
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ObjectRow;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.*;
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.RootNode;
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.model.DebuggerObjectActionContext;
import ghidra.debug.api.target.ActionName;
@ -233,6 +233,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
@AutoServiceConsumed
protected DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
protected DebuggerListingService listingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@ -431,8 +433,49 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
mainPanel.revalidate();
}
protected class ObjectsTreeListener implements Adapters.FocusListener,
protected void activatePath(TraceObjectKeyPath path) {
if (current.getTrace() == null) {
return;
}
try {
traceManager.activate(current.pathNonCanonical(path));
}
catch (IllegalArgumentException e) {
plugin.getTool().setStatusInfo(e.getMessage(), true);
}
}
protected class MyMixin implements ObjectDefaultActionsMixin {
@Override
public DebuggerCoordinates getCurrent() {
return current;
}
@Override
public PluginTool getTool() {
return plugin.getTool();
}
@Override
public void activatePath(TraceObjectKeyPath path) {
DebuggerModelProvider.this.activatePath(path);
}
}
protected class ObjectsTreeListener extends MyMixin implements Adapters.FocusListener,
Adapters.TreeExpansionListener, Adapters.MouseListener, Adapters.KeyListener {
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();
performDefaultAction(value);
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
@ -501,18 +544,14 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
}
}
protected class ElementsTableListener
protected class ElementsTableListener extends MyMixin
implements Adapters.FocusListener, CellActivationListener {
@Override
public void cellActivated(JTable table) {
ValueRow row = elementsTablePanel.getSelectedItem();
if (row == null) {
if (performElementCellDefaultAction(table)) {
return;
}
if (!(row instanceof ObjectRow objectRow)) {
return;
}
activatePath(objectRow.getTraceObject().getCanonicalPath());
performValueRowDefaultAction(elementsTablePanel.getSelectedItem());
}
@Override
@ -567,19 +606,11 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
}
}
protected class AttributesTableListener
protected class AttributesTableListener extends MyMixin
implements Adapters.FocusListener, CellActivationListener {
@Override
public void cellActivated(JTable table) {
PathRow row = attributesTablePanel.getSelectedItem();
if (row == null) {
return;
}
Object value = row.getValue();
if (!(value instanceof TraceObject object)) {
return;
}
activatePath(object.getCanonicalPath());
performPathRowDefaultAction(attributesTablePanel.getSelectedItem());
}
@Override
@ -746,19 +777,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
}
}
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 != null && value.getValue() instanceof TraceObject child) {
activatePath(child.getCanonicalPath());
}
}
@Override
public ActionContext getActionContext(MouseEvent event) {
if (event != null) {
@ -970,26 +988,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
}
}
protected void activatePath(TraceObjectKeyPath path) {
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;
}
plugin.getTool().setStatusInfo("No such object at path " + path, true);
}
}
protected void setPath(TraceObjectKeyPath path, JComponent source, EventOrigin origin) {
if (Objects.equals(this.path, path) && getTreeSelection() != null) {
return;

View File

@ -0,0 +1,169 @@
/* ###
* 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;
import java.util.*;
import javax.swing.JTable;
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
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.PathTableModel.PathRow;
import ghidra.app.services.DebuggerListingService;
import ghidra.dbg.target.*;
import ghidra.debug.api.model.DebuggerSingleObjectPathActionContext;
import ghidra.debug.api.target.ActionName;
import ghidra.debug.api.target.Target;
import ghidra.debug.api.target.Target.ActionEntry;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.target.*;
import ghidra.util.Msg;
public interface ObjectDefaultActionsMixin {
PluginTool getTool();
DebuggerCoordinates getCurrent();
void activatePath(TraceObjectKeyPath path);
default void toggleObject(TraceObject object) {
if (!getCurrent().isAliveAndPresent()) {
return;
}
Target target = getCurrent().getTarget();
Map<String, ActionEntry> actions = target.collectActions(ActionName.TOGGLE,
new DebuggerSingleObjectPathActionContext(object.getCanonicalPath()));
ActionEntry action = actions.values()
.stream()
.filter(e -> !e.requiresPrompt())
.sorted(Comparator.comparing(e -> -e.specificity()))
.findFirst()
.orElse(null);
if (action == null) {
Msg.error(this, "No suitable toggle action for " + object);
return;
}
TargetActionTask.runAction(getTool(), "Toggle", action);
}
default void goToAddress(DebuggerListingService listingService, Address address) {
ProgramLocation loc = new ProgramLocation(getCurrent().getView(), address);
listingService.goTo(loc, true);
}
default void goToAddress(Address address) {
DebuggerListingService listingService = getTool().getService(DebuggerListingService.class);
if (listingService == null) {
return;
}
goToAddress(listingService, address);
}
default void goToRange(AddressRange range) {
DebuggerListingService listingService = getTool().getService(DebuggerListingService.class);
if (listingService == null) {
return;
}
listingService.setCurrentSelection(
new ProgramSelection(range.getMinAddress(), range.getMaxAddress()));
goToAddress(listingService, range.getMinAddress());
}
default boolean performElementCellDefaultAction(JTable table) {
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
Object cellValue = table.getValueAt(row, col);
if (cellValue instanceof ValueProperty<?> property) {
Object propValue = property.getValue();
if (performDefaultAction(propValue)) {
return true;
}
}
return false;
}
default boolean performValueRowDefaultAction(ValueRow row) {
if (row == null) {
return false;
}
return performDefaultAction(row.getValue());
}
default boolean performPathRowDefaultAction(PathRow row) {
if (row == null) {
return false;
}
return performDefaultAction(row.getValue());
}
default boolean performDefaultAction(TraceObjectValue value) {
if (value == null) {
return false;
}
return performDefaultAction(value.getValue());
}
default boolean performDefaultAction(TraceObject object) {
Set<Class<? extends TargetObject>> interfaces = object.getTargetSchema().getInterfaces();
if (interfaces.contains(TargetActivatable.class)) {
activatePath(object.getCanonicalPath());
return true;
}
/**
* Should I check aliveAndPresent() here? If I do, behavior changes when target is dead,
* which might be unexpected.
*/
if (interfaces.contains(TargetTogglable.class)) {
toggleObject(object);
return true;
}
long snap = getCurrent().getSnap();
TraceObjectValue valAddress = object.getAttribute(snap, "_address");
if (valAddress != null && valAddress.getValue() instanceof Address address) {
goToAddress(address);
return true;
}
TraceObjectValue valRange = object.getAttribute(snap, "_range");
if (valRange != null && valRange.getValue() instanceof AddressRange range) {
goToRange(range);
return true;
}
return false;
}
default boolean performDefaultAction(Object value) {
if (value instanceof Address address) {
goToAddress(address);
return true;
}
if (value instanceof AddressRange range) {
goToRange(range);
return true;
}
if (value instanceof TraceObject object) {
return performDefaultAction(object);
}
return false;
}
}

View File

@ -69,7 +69,7 @@ public class ObjectsTablePanel extends AbstractQueryTablePanel<ValueRow, ObjectT
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new ObjectTableModel(plugin);
}

View File

@ -24,7 +24,7 @@ public class PathsTablePanel extends AbstractQueryTablePanel<PathRow, PathTableM
}
@Override
protected PathTableModel createModel(Plugin plugin) {
protected PathTableModel createModel() {
return new PathTableModel(plugin);
}
}

View File

@ -229,8 +229,8 @@ public class DebuggerLegacyModulesPanel extends JPanel {
int selectedRow = moduleTable.getSelectedRow();
int selectedColumn = moduleTable.getSelectedColumn();
Object value = moduleTable.getValueAt(selectedRow, selectedColumn);
if (value instanceof Address) {
provider.listingService.goTo((Address) value, true);
if (value instanceof Address address) {
provider.listingService.goTo(address, true);
}
}
}

View File

@ -273,8 +273,8 @@ public class DebuggerLegacySectionsPanel extends JPanel {
int selectedRow = sectionTable.getSelectedRow();
int selectedColumn = sectionTable.getSelectedColumn();
Object value = sectionTable.getValueAt(selectedRow, selectedColumn);
if (value instanceof Address) {
provider.listingService.goTo((Address) value, true);
if (value instanceof Address address) {
provider.listingService.goTo(address, true);
}
}
}

View File

@ -232,7 +232,7 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceOb
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new ModuleTableModel(plugin);
}

View File

@ -211,7 +211,7 @@ public class DebuggerSectionsPanel extends AbstractObjectsTableBasedPanel<TraceO
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new SectionTableModel(plugin);
}

View File

@ -63,6 +63,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.*;
@ -639,7 +640,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (listingService == null) {
return;
}
listingService.goTo(address, true);
ProgramLocation loc = new ProgramLocation(current.getView(), address);
listingService.goTo(loc, true);
}).build());
}
return result;
@ -658,7 +660,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (address == null) {
return;
}
listingService.goTo(address, true);
ProgramLocation loc = new ProgramLocation(current.getView(), address);
listingService.goTo(loc, true);
}
@Override

View File

@ -23,7 +23,6 @@ import javax.swing.event.ListSelectionListener;
import docking.widgets.table.AbstractDynamicTableColumn;
import docking.widgets.table.TableColumnDescriptor;
import ghidra.app.plugin.core.debug.gui.model.*;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueKeyColumn;
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectAttributeColumn;
@ -46,7 +45,7 @@ import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObjectStackFrame>
implements ListSelectionListener, CellActivationListener {
implements ListSelectionListener {
private static class FrameLevelColumn extends TraceValueKeyColumn {
@Override
@ -137,7 +136,7 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new StackTableModel(plugin);
}
@ -165,7 +164,11 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
@Override
public void cellActivated(JTable table) {
// No super
/**
* Override, because PC columns is fairly wide and representative of the stack frame.
* Likely, when the user double-clicks, they mean to activate the frame, even if it happens
* to be in that column. Simply going to the address will confuse and/or disappoint.
*/
ValueRow item = getSelectedItem();
if (item != null) {
traceManager.activateObject(item.getValue().getChild());

View File

@ -17,7 +17,6 @@ package ghidra.app.plugin.core.debug.gui.thread;
import java.util.List;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import docking.widgets.table.AbstractDynamicTableColumn;
@ -258,7 +257,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new ThreadTableModel(plugin);
}
@ -293,15 +292,6 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
trySelectCurrentThread();
}
@Override
public void cellActivated(JTable table) {
// No super
ValueRow item = getSelectedItem();
if (item != null) {
traceManager.activateObject(item.getValue().getChild());
}
}
@Override
public void valueChanged(ListSelectionEvent e) {
super.valueChanged(e);

View File

@ -483,7 +483,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter
return;
}
if (address.isMemoryAddress()) {
listingService.goTo(address, true);
ProgramLocation loc = new ProgramLocation(current.getView(), address);
listingService.goTo(loc, true);
return;
}
}

View File

@ -370,6 +370,14 @@ public class TraceRecorderTarget extends AbstractTarget {
return Map.of();
}
@Override
protected Map<String, ActionEntry> collectToggleActions(ActionContext context) {
return collectIfaceActions(context, TargetTogglable.class, "Toggle",
ActionName.TOGGLE, "Toggle the object",
togglable -> true,
togglable -> togglable.toggle(!togglable.isEnabled()));
}
@Override
public Trace getTrace() {
return recorder.getTrace();

View File

@ -198,7 +198,8 @@ public abstract class AbstractTarget implements Target {
collectStepOverActions(context),
collectStepOutActions(context),
collectStepExtActions(context),
collectRefreshActions(context))
collectRefreshActions(context),
collectToggleActions(context))
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
@ -219,6 +220,8 @@ public abstract class AbstractTarget implements Target {
protected abstract Map<String, ActionEntry> collectRefreshActions(ActionContext context);
protected abstract Map<String, ActionEntry> collectToggleActions(ActionContext context);
@Override
public Map<String, ActionEntry> collectActions(ActionName name, ActionContext context) {
if (name == null) {
@ -251,6 +254,9 @@ public abstract class AbstractTarget implements Target {
else if (ActionName.REFRESH.equals(name)) {
return collectRefreshActions(context);
}
else if (ActionName.TOGGLE.equals(name)) {
return collectToggleActions(context);
}
Msg.warn(this, "Unrecognized action name: " + name);
return Map.of();
}

View File

@ -0,0 +1,43 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.dbg.target;
import ghidra.dbg.DebuggerTargetObjectIface;
/**
* An object which can be activated
*
* <p>
* Activation generally means to become the active, selected or focused object. Subsequent commands
* to the debugger implicitly apply to this object. For example, if a user activates a thread, then
* subsequent register read/write commands ought to affect the active thread's context.
*
* <p>
* This interface is only used by RMI targets. The back end must register a suitable method so that
* the front end can notify it when the user has activated this object. Generally, a user activates
* the object by double-clicking it in the appropriate table or tree. If it is <em>not</em> marked
* with this interface, the UI will ignore the action. If it is, the UI will mark it the active
* object and invoke the appropriate target method. If this interface is present, but a suitable
* method is not, an error is logged upon attempted activation.
*
* <p>
* We cannot just use the presence or absence of a suitable activation method as a proxy for this
* interface, because the registry is only available when the back end is alive.
*/
@DebuggerTargetObjectIface("Activatable")
public interface TargetActivatable extends TargetObject {
// No methods
}

View File

@ -105,8 +105,7 @@ import ghidra.lifecycle.Internal;
* <ul>
* <li>"Threads" : {@link TargetObject}</li>
* <ul>
* <li>"Thread 1" : {@link TargetExecutionStateful}, {@link TargetSingleSteppable},
* {@link TargetMultiSteppable}</li>
* <li>"Thread 1" : {@link TargetExecutionStateful}, {@link TargetSteppable}</li>
* <ul>
* <li>"Registers" : {@link TargetRegisterBank}</li>
* <ul>
@ -167,15 +166,16 @@ import ghidra.lifecycle.Internal;
public interface TargetObject extends Comparable<TargetObject> {
Set<Class<? extends TargetObject>> ALL_INTERFACES =
Set.of(TargetAccessConditioned.class, TargetActiveScope.class, TargetAggregate.class,
TargetAttachable.class, TargetAttacher.class, TargetBreakpointLocation.class,
TargetBreakpointLocationContainer.class, TargetBreakpointSpec.class,
TargetBreakpointSpecContainer.class, TargetConfigurable.class, TargetConsole.class,
TargetDataTypeMember.class, TargetDataTypeNamespace.class, TargetDeletable.class,
TargetDetachable.class, TargetEnvironment.class, TargetEventScope.class,
TargetExecutionStateful.class, TargetFocusScope.class, TargetInterpreter.class,
TargetInterruptible.class, TargetKillable.class, TargetLauncher.class,
TargetMemory.class, TargetMemoryRegion.class, TargetMethod.class, TargetModule.class,
Set.of(TargetAccessConditioned.class, TargetActivatable.class, TargetActiveScope.class,
TargetAggregate.class, TargetAttachable.class, TargetAttacher.class,
TargetBreakpointLocation.class, TargetBreakpointLocationContainer.class,
TargetBreakpointSpec.class, TargetBreakpointSpecContainer.class,
TargetConfigurable.class, TargetConsole.class, TargetDataTypeMember.class,
TargetDataTypeNamespace.class, TargetDeletable.class, TargetDetachable.class,
TargetEnvironment.class, TargetEventScope.class, TargetExecutionStateful.class,
TargetFocusScope.class, TargetInterpreter.class, TargetInterruptible.class,
TargetKillable.class, TargetLauncher.class, TargetMemory.class,
TargetMemoryRegion.class, TargetMethod.class, TargetModule.class,
TargetModuleContainer.class, TargetNamedDataType.class, TargetProcess.class,
TargetRegister.class, TargetRegisterBank.class, TargetRegisterContainer.class,
TargetResumable.class, TargetSection.class, TargetSectionContainer.class,
@ -472,7 +472,7 @@ public interface TargetObject extends Comparable<TargetObject> {
* <p>
* This is an informal notion of type and may only be used for visual styling, logging, or other
* informational purposes. Scripts should not rely on this to predict behavior, but instead on
* {@link #getAs(Class)}, {@link #getInterfaces()}, or {@link #getSchema()}.
* {@link #as(Class)}, {@link #getInterfaces()}, or {@link #getSchema()}.
*
* @return an informal name of this object's type
*/
@ -516,10 +516,11 @@ public interface TargetObject extends Comparable<TargetObject> {
*
* <p>
* In general, an invalid object should be disposed by the user immediately on discovering it is
* invalid. See {@link DebuggerModelListener#invalidated(TargetObject)} for a means of reacting
* to object invalidation. Nevertheless, it is acceptable to access stale attributes and element
* keys, for informational purposes only. Implementors must reject all commands, including
* fetches, on an invalid object by throwing an {@link IllegalStateException}.
* invalid. See {@link DebuggerModelListener#invalidated(TargetObject, TargetObject, String)}
* for a means of reacting to object invalidation. Nevertheless, it is acceptable to access
* stale attributes and element keys, for informational purposes only. Implementors must reject
* all commands, including fetches, on an invalid object by throwing an
* {@link IllegalStateException}.
*
* @return true if valid, false if invalid
*/