executeAsync(String command, boolean toString);
-
- /**
- * @see #executeAsync(String, boolean)
- */
String execute(String command, boolean toString);
/**
diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java
index 76b2e8bef2..97ef5c8090 100644
--- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java
+++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java
@@ -499,11 +499,9 @@ public class DebuggerCoordinates {
else if (trace == null) {
throw new IllegalArgumentException("No trace");
}
- else {
- if (newPath == null) {
- return new DebuggerCoordinates(trace, platform, target, thread, view, time, frame,
- newPath);
- }
+ else if (newPath == null) {
+ return new DebuggerCoordinates(trace, platform, target, thread, view, time, frame,
+ newPath);
}
TraceThread newThread = target != null
? resolveThread(target, 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);
}
diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java
index cf79ddc269..d30660b4a5 100644
--- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java
+++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiTarget.java
@@ -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.
+ *
+ *
+ * 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 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 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);
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java
index 07c4272ee6..386874642b 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java
@@ -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();
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java
index 35a77da536..4fb7b6559b 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java
@@ -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() {
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerLegacyRegionsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerLegacyRegionsPanel.java
index 2ac39195d3..91246709d8 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerLegacyRegionsPanel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerLegacyRegionsPanel.java
@@ -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);
}
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java
index 9c2a698cd5..3399ccde50 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsPanel.java
@@ -187,7 +187,7 @@ public class DebuggerRegionsPanel extends AbstractObjectsTableBasedPanel
- extends ObjectsTablePanel implements ListSelectionListener, CellActivationListener {
+ extends ObjectsTablePanel
+ implements ListSelectionListener, CellActivationListener, ObjectDefaultActionsMixin {
private final ComponentProvider provider;
private final Class objType;
+ @AutoServiceConsumed
+ protected DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
protected DebuggerListingService listingService;
@SuppressWarnings("unused")
@@ -121,23 +119,32 @@ public abstract class AbstractObjectsTableBasedPanel 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);
}
}
}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java
index bc1eb784e1..249aab470a 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/AbstractQueryTablePanel.java
@@ -41,6 +41,7 @@ public abstract class AbstractQueryTablePanel filterPanel;
@@ -54,7 +55,9 @@ public abstract class AbstractQueryTablePanel(table, tableModel);
@@ -80,7 +83,7 @@ public abstract class AbstractQueryTablePanel 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 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;
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectDefaultActionsMixin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectDefaultActionsMixin.java
new file mode 100644
index 0000000000..81f4279316
--- /dev/null
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectDefaultActionsMixin.java
@@ -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 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> 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;
+ }
+
+}
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTablePanel.java
index 18321eda17..55a447497a 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTablePanel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/ObjectsTablePanel.java
@@ -69,7 +69,7 @@ public class ObjectsTablePanel extends AbstractQueryTablePanel
- implements ListSelectionListener, CellActivationListener {
+ implements ListSelectionListener {
private static class FrameLevelColumn extends TraceValueKeyColumn {
@Override
@@ -137,7 +136,7 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel 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();
diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java
index 8117ffae85..e44852ec96 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/target/AbstractTarget.java
@@ -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 collectRefreshActions(ActionContext context);
+ protected abstract Map collectToggleActions(ActionContext context);
+
@Override
public Map 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();
}
diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetActivatable.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetActivatable.java
new file mode 100644
index 0000000000..873d7c07e0
--- /dev/null
+++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetActivatable.java
@@ -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
+ *
+ *
+ * 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.
+ *
+ *
+ * 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 not 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.
+ *
+ *
+ * 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
+}
diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java
index 7f5c5fe733..d44d896f14 100644
--- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java
+++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetObject.java
@@ -105,8 +105,7 @@ import ghidra.lifecycle.Internal;
*
* - "Threads" : {@link TargetObject}
*
- * - "Thread 1" : {@link TargetExecutionStateful}, {@link TargetSingleSteppable},
- * {@link TargetMultiSteppable}
+ * - "Thread 1" : {@link TargetExecutionStateful}, {@link TargetSteppable}
*
* - "Registers" : {@link TargetRegisterBank}
*
@@ -167,15 +166,16 @@ import ghidra.lifecycle.Internal;
public interface TargetObject extends Comparable {
Set> 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 {
*
* 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 {
*
*
* 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
*/