diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java index 214c4631e1..c7df85f76e 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/ActionName.java @@ -183,6 +183,7 @@ public record ActionName(String name, Show show, Enabler enabler, String display public static final ActionName REFRESH = create("refresh", Show.EXTENDED, Enabler.ALWAYS, "Refresh", ICON_REFRESH, "Refresh"); + /** * Activate a given object and optionally a time * @@ -191,6 +192,7 @@ public record ActionName(String name, Show show, Enabler enabler, String display */ public static final ActionName ACTIVATE = create("activate", Show.BUILTIN, Enabler.ALWAYS, "Activate", null, "Activate"); + /** * A weaker form of activate. * @@ -262,6 +264,7 @@ public record ActionName(String name, Show show, Enabler enabler, String display create("step_over", Show.BUILTIN, Enabler.NOT_RUNNING, "Step Over", ICON_STEP_OVER, "Step"); public static final ActionName STEP_OUT = create("step_out", Show.BUILTIN, Enabler.NOT_RUNNING, "Step Out", ICON_STEP_OUT, "Step"); + /** * Skip is not typically available, except in emulators. If the back-end debugger does not have * a command for this action out-of-the-box, we do not recommend trying to implement it @@ -270,12 +273,14 @@ public record ActionName(String name, Show show, Enabler enabler, String display */ public static final ActionName STEP_SKIP = create("step_skip", Show.BUILTIN, Enabler.NOT_RUNNING, "Skip Over", ICON_SKIP_OVER, "Skip"); + /** * Step back is not typically available, except in emulators and timeless (or time-travel) * debuggers. */ public static final ActionName STEP_BACK = create("step_back", Show.BUILTIN, Enabler.NOT_RUNNING, "Step Back", ICON_STEP_BACK, "Back"); + /** * The action for steps that don't fit one of the common stepping actions. */ @@ -315,6 +320,7 @@ public record ActionName(String name, Show show, Enabler enabler, String display */ public static final ActionName READ_MEM = create("read_mem", Show.BUILTIN, Enabler.ALWAYS, "Read Memory", null, "Read"); + /** * Forms: (addr:ADDRESS,data:BYTES) */ diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java index 4f6eb5e6cd..cca206f874 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/target/Target.java @@ -181,6 +181,78 @@ public interface Target { } } + /** + * Specifies how object arguments are derived + */ + public enum ObjectArgumentPolicy { + /** + * The object should be taken exactly from the action context, if applicable, present, and + * matching in schema. + */ + CONTEXT_ONLY { + @Override + public boolean allowContextObject() { + return true; + } + + @Override + public boolean allowCoordsObject() { + return false; + } + + @Override + public boolean allowSuitableRelative() { + return false; + } + }, + /** + * The object should be taken from the current (active) object in the tool, or a suitable + * relative having the correct schema. + */ + CURRENT_AND_RELATED { + @Override + public boolean allowContextObject() { + return false; + } + + @Override + public boolean allowCoordsObject() { + return true; + } + + @Override + public boolean allowSuitableRelative() { + return true; + } + }, + /** + * The object can be taken from the given context, or the current (active) object in the + * tool, or a suitable relative having the correct schema. + */ + EITHER_AND_RELATED { + @Override + public boolean allowContextObject() { + return true; + } + + @Override + public boolean allowCoordsObject() { + return true; + } + + @Override + public boolean allowSuitableRelative() { + return true; + } + }; + + public abstract boolean allowContextObject(); + + public abstract boolean allowCoordsObject(); + + public abstract boolean allowSuitableRelative(); + } + /** * Describe the target for display in the UI * @@ -216,11 +288,17 @@ public interface Target { /** * Collect all actions that implement the given common debugger command * + *

+ * Note that if the context provides a program location (i.e., address), the object policy is + * ignored. It will use current and related objects. + * * @param name the action name * @param context applicable context from the UI + * @param policy determines how objects may be found * @return the collected actions */ - Map collectActions(ActionName name, ActionContext context); + Map collectActions(ActionName name, ActionContext context, + ObjectArgumentPolicy policy); /** * @see #execute(String, boolean) diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java index 2f8a88eff0..06e7c2821f 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java @@ -34,6 +34,7 @@ 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.target.Target.ObjectArgumentPolicy; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectListener; @@ -1473,7 +1474,7 @@ public interface FlatDebuggerAPI { } default ActionEntry findAction(Target target, ActionName action, ActionContext context) { - return target.collectActions(action, context) + return target.collectActions(action, context, ObjectArgumentPolicy.EITHER_AND_RELATED) .values() .stream() .filter(e -> !e.requiresPrompt()) 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 0254bf5b13..60179b73b6 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 @@ -56,6 +56,7 @@ import ghidra.trace.model.target.info.TraceObjectInterfaceUtils; import ghidra.trace.model.target.path.*; import ghidra.trace.model.target.path.PathFilter.Align; import ghidra.trace.model.target.schema.*; +import ghidra.trace.model.target.schema.PrimitiveTraceObjectSchema.MinimalSchemaContext; import ghidra.trace.model.target.schema.TraceObjectSchema.SchemaName; import ghidra.trace.model.thread.*; import ghidra.util.Msg; @@ -75,7 +76,7 @@ public class TraceRmiTarget extends AbstractTarget { this.args = args; this.requiresPrompt = args.values().contains(Missing.MISSING); - this.specificity = computeSpecificity(args); + this.specificity = computeSpecificity(method, args); this.first = getFirstObjectArgument(method, args); } @@ -197,9 +198,8 @@ public class TraceRmiTarget extends AbstractTarget { .orElse(null); } - protected TraceObject findObject(ActionContext context, boolean allowContextObject, - boolean allowCoordsObject) { - if (allowContextObject) { + protected TraceObject findObject(ActionContext context, ObjectArgumentPolicy policy) { + if (policy.allowContextObject()) { if (context instanceof DebuggerObjectActionContext ctx) { List values = ctx.getObjectValues(); if (values.size() == 1) { @@ -224,7 +224,7 @@ public class TraceRmiTarget extends AbstractTarget { } } } - if (allowCoordsObject) { + if (policy.allowCoordsObject()) { DebuggerTraceManagerService traceManager = tool.getService(DebuggerTraceManagerService.class); if (traceManager == null) { @@ -246,16 +246,15 @@ public class TraceRmiTarget extends AbstractTarget { * @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 + * @param policy how object arguments can be found * @return a value if found, null if not */ - protected Boolean findBool(ActionName action, ActionContext context, boolean allowContextObject, - boolean allowCoordsObject) { + protected Boolean findBool(ActionName action, ActionContext context, + ObjectArgumentPolicy policy) { if (!Objects.equals(action, ActionName.TOGGLE)) { return null; } - TraceObject object = findObject(context, allowContextObject, allowCoordsObject); + TraceObject object = findObject(context, policy); if (object == null) { return null; } @@ -266,22 +265,21 @@ public class TraceRmiTarget extends AbstractTarget { } protected Object findArgumentForSchema(ActionName action, ActionContext context, - TraceObjectSchema schema, boolean allowContextObject, boolean allowCoordsObject, - boolean allowSuitableObject) { + TraceObjectSchema schema, ObjectArgumentPolicy policy) { if (schema instanceof PrimitiveTraceObjectSchema prim) { return switch (prim) { - case OBJECT -> findObject(context, allowContextObject, allowCoordsObject); + case OBJECT -> findObject(context, policy); case ADDRESS -> findAddress(context); case RANGE -> findRange(context); - case BOOL -> findBool(action, context, allowContextObject, allowCoordsObject); + case BOOL -> findBool(action, context, policy); default -> null; }; } - TraceObject object = findObject(context, allowContextObject, allowCoordsObject); + TraceObject object = findObject(context, policy); if (object == null) { return null; } - if (allowSuitableObject) { + if (policy.allowSuitableRelative()) { return object.findSuitableSchema(schema); } if (object.getSchema() == schema) { @@ -299,8 +297,7 @@ public class TraceRmiTarget extends AbstractTarget { } protected Object findArgument(ActionName action, RemoteParameter parameter, - ActionContext context, boolean allowContextObject, boolean allowCoordsObject, - boolean allowSuitableObject) { + ActionContext context, ObjectArgumentPolicy policy) { SchemaName type = parameter.type(); SchemaContext ctx = getSchemaContext(); if (ctx == null) { @@ -312,8 +309,7 @@ public class TraceRmiTarget extends AbstractTarget { Msg.error(this, "Schema " + type + " not in trace! " + trace); return null; } - Object arg = findArgumentForSchema(action, context, schema, allowContextObject, - allowCoordsObject, allowSuitableObject); + Object arg = findArgumentForSchema(action, context, schema, policy); if (arg != null) { return arg; } @@ -324,11 +320,10 @@ public class TraceRmiTarget extends AbstractTarget { } protected Map collectArguments(RemoteMethod method, ActionContext context, - boolean allowContextObject, boolean allowCoordsObject, boolean allowSuitableObject) { + ObjectArgumentPolicy policy) { Map args = new HashMap<>(); for (RemoteParameter param : method.parameters().values()) { - Object found = findArgument(method.action(), param, context, allowContextObject, - allowCoordsObject, allowSuitableObject); + Object found = findArgument(method.action(), param, context, policy); if (found != null) { args.put(param.name(), found); } @@ -336,8 +331,36 @@ public class TraceRmiTarget extends AbstractTarget { return args; } - protected static long computeSpecificity(Map args) { + /** + * Compute the specificity of the entry. + * + * More specific is generally preferred. There are two sorts of specificity here. 1) The + * specificity of the methods formal parameters. A parameter having a non-primitive schema is + * more specific than one having an ANY or OBJECT schema. 2) The specificity of the objects + * selected as arguments. This is crudely computed as the length of the canonical path. + * + * @param method the method + * @param args the arguments + * @return the specificity + */ + protected static long computeSpecificity(RemoteMethod method, Map args) { long score = 0; + for (RemoteParameter param : method.parameters().values()) { + score += switch (MinimalSchemaContext.INSTANCE.getSchemaOrNull(param.type())) { + case PrimitiveTraceObjectSchema prim -> switch (prim) { + case ANY -> 0; // Absolutely not specific + case OBJECT -> 1; // well, it is better than ANY + default -> 2; // real primitives + }; + /** + * Because we're using the "minimal" schema, not the actual one, anything + * user-defined will be null. + */ + case null -> 100; + default -> 100; + }; + } + score *= 1000; for (Object o : args.values()) { if (o instanceof TraceObject obj) { score += obj.getCanonicalPath().size(); @@ -408,19 +431,16 @@ public class TraceRmiTarget extends AbstractTarget { } protected ActionEntry createEntry(RemoteMethod method, ActionContext context, - boolean allowContextObject, boolean allowCoordsObject, boolean allowSuitableObject) { - Map args = collectArguments(method, context, allowContextObject, - allowCoordsObject, allowSuitableObject); + ObjectArgumentPolicy policy) { + Map args = collectArguments(method, context, policy); return new TraceRmiActionEntry(method, args); } protected Map collectFromMethods(Collection methods, - ActionContext context, boolean allowContextObject, boolean allowCoordsObject, - boolean allowSuitableObject) { + ActionContext context, ObjectArgumentPolicy policy) { Map result = new HashMap<>(); for (RemoteMethod m : methods) { - ActionEntry entry = createEntry(m, context, allowContextObject, allowCoordsObject, - allowSuitableObject); + ActionEntry entry = createEntry(m, context, policy); result.put(m.name(), entry); } return result; @@ -442,7 +462,6 @@ public class TraceRmiTarget extends AbstractTarget { .count() == 1; } - @Override protected Map collectAddressActions(ProgramLocationActionContext context) { SchemaContext ctx = getSchemaContext(); Map result = new HashMap<>(); @@ -450,78 +469,31 @@ public class TraceRmiTarget extends AbstractTarget { if (!isAddressMethod(m, ctx)) { continue; } - result.put(m.name(), createEntry(m, context, true, true, true)); + result.put(m.name(), createEntry(m, context, ObjectArgumentPolicy.CURRENT_AND_RELATED)); } return result; } - @Override - protected Map collectAllActions(ActionContext context) { - return collectFromMethods(connection.getMethods().all().values(), context, true, false, - false); + protected Map collectAllActions(ActionContext context, + ObjectArgumentPolicy policy) { + return collectFromMethods(connection.getMethods().all().values(), context, policy); } - protected Map collectByName(ActionName name, ActionContext context) { - return collectFromMethods(connection.getMethods().getByAction(name), context, false, true, - true); + protected Map collectByName(ActionName name, ActionContext context, + ObjectArgumentPolicy policy) { + return collectFromMethods(connection.getMethods().getByAction(name), context, policy); } @Override - public Map collectActions(ActionName name, ActionContext context) { + public Map collectActions(ActionName name, ActionContext context, + ObjectArgumentPolicy policy) { if (name == null) { if (context instanceof ProgramLocationActionContext ctx) { return collectAddressActions(ctx); } - return collectAllActions(context); + return collectAllActions(context, policy); } - return collectByName(name, context); - } - - @Override - protected Map collectResumeActions(ActionContext context) { - return collectByName(ActionName.RESUME, context); - } - - @Override - protected Map collectInterruptActions(ActionContext context) { - return collectByName(ActionName.INTERRUPT, context); - } - - @Override - protected Map collectKillActions(ActionContext context) { - return collectByName(ActionName.KILL, context); - } - - @Override - protected Map collectStepIntoActions(ActionContext context) { - return collectByName(ActionName.STEP_INTO, context); - } - - @Override - protected Map collectStepOverActions(ActionContext context) { - return collectByName(ActionName.STEP_OVER, context); - } - - @Override - protected Map collectStepOutActions(ActionContext context) { - return collectByName(ActionName.STEP_OUT, context); - } - - @Override - protected Map collectStepExtActions(ActionContext context) { - return collectByName(ActionName.STEP_EXT, context); - } - - @Override - protected Map collectRefreshActions(ActionContext context) { - return collectFromMethods(connection.getMethods().getByAction(ActionName.REFRESH), context, - true, false, false); - } - - @Override - protected Map collectToggleActions(ActionContext context) { - return collectFromMethods(connection.getMethods().getByAction(ActionName.TOGGLE), context, - true, false, false); + return collectByName(name, context, policy); } @Override @@ -1420,7 +1392,8 @@ public class TraceRmiTarget extends AbstractTarget { RemoteParameter paramProc = brk.params.get("process"); if (paramProc != null) { Object proc = findArgumentForSchema(null, null, - getSchemaContext().getSchema(paramProc.type()), true, true, true); + getSchemaContext().getSchema(paramProc.type()), + ObjectArgumentPolicy.CURRENT_AND_RELATED); if (proc == null) { Msg.error(this, "Cannot find required process argument for " + brk.method); } @@ -1675,7 +1648,8 @@ public class TraceRmiTarget extends AbstractTarget { @Override public CompletableFuture forceTerminateAsync() { - Map kills = collectKillActions(null); + Map kills = + collectByName(ActionName.KILL, null, ObjectArgumentPolicy.CURRENT_AND_RELATED); for (ActionEntry kill : kills.values()) { if (kill.requiresPrompt()) { continue; 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 c478f6606e..7a14b7309f 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 @@ -46,6 +46,7 @@ import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.target.ActionName; import ghidra.debug.api.target.Target; import ghidra.debug.api.target.Target.ActionEntry; +import ghidra.debug.api.target.Target.ObjectArgumentPolicy; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.model.DomainObjectEvent; import ghidra.framework.plugintool.*; @@ -327,7 +328,9 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter return stub; } List result = new ArrayList<>(); - for (ActionEntry entry : target.collectActions(ActionName.BREAK_EXT, context) + for (ActionEntry entry : target + .collectActions(ActionName.BREAK_EXT, context, + ObjectArgumentPolicy.CURRENT_AND_RELATED) .values()) { result.add(new GenericSetBreakpointAction(entry)); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java index 68db4d689e..6be2c952c7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java @@ -42,6 +42,7 @@ import ghidra.debug.api.model.DebuggerObjectActionContext; import ghidra.debug.api.target.ActionName; import ghidra.debug.api.target.Target; import ghidra.debug.api.target.Target.ActionEntry; +import ghidra.debug.api.target.Target.ObjectArgumentPolicy; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; @@ -282,7 +283,10 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin } protected void addTargetStepExtActions(Target target) { - for (ActionEntry entry : target.collectActions(ActionName.STEP_EXT, context).values()) { + for (ActionEntry entry : target + .collectActions(ActionName.STEP_EXT, context, + ObjectArgumentPolicy.CURRENT_AND_RELATED) + .values()) { if (entry.requiresPrompt()) { continue; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java index a3074a91c3..5384bfbfa1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java @@ -33,6 +33,7 @@ import ghidra.debug.api.control.ControlMode; import ghidra.debug.api.model.DebuggerObjectActionContext; import ghidra.debug.api.target.Target; import ghidra.debug.api.target.Target.ActionEntry; +import ghidra.debug.api.target.Target.ObjectArgumentPolicy; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; @@ -119,7 +120,9 @@ public class DebuggerMethodActionsPlugin extends Plugin implements PopupActionPr } List result = new ArrayList<>(); - for (ActionEntry entry : target.collectActions(null, context).values()) { + for (ActionEntry entry : target + .collectActions(null, context, ObjectArgumentPolicy.CONTEXT_ONLY) + .values()) { //if (entry.requiresPrompt() || entry.builtIn()) { if (!entry.isEnabled() || !entry.getShow().isShowing(context)) { continue; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetDockingAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetDockingAction.java index 72efd87cf7..5e0e45eebb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetDockingAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetDockingAction.java @@ -24,6 +24,7 @@ import ghidra.app.services.DebuggerTraceManagerService; import ghidra.debug.api.target.ActionName; import ghidra.debug.api.target.Target; import ghidra.debug.api.target.Target.ActionEntry; +import ghidra.debug.api.target.Target.ObjectArgumentPolicy; import ghidra.framework.plugintool.PluginTool; class TargetDockingAction extends DockingAction { @@ -51,7 +52,7 @@ class TargetDockingAction extends DockingAction { if (target == null) { return null; } - return target.collectActions(action, context) + return target.collectActions(action, context, ObjectArgumentPolicy.CURRENT_AND_RELATED) .values() .stream() .filter(e -> !e.requiresPrompt()) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java index 1882b11d66..755f69934b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java @@ -55,6 +55,7 @@ import ghidra.debug.api.model.DebuggerObjectActionContext; import ghidra.debug.api.target.ActionName; import ghidra.debug.api.target.Target; import ghidra.debug.api.target.Target.ActionEntry; +import ghidra.debug.api.target.Target.ObjectArgumentPolicy; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; @@ -767,30 +768,34 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable if (target == null) { return; } - Map actions = target.collectActions(ActionName.REFRESH, + ActionEntry ent = target.collectActions(ActionName.REFRESH, new DebuggerObjectActionContext(List.of(value), this, objectsTreePanel, - current.getSnap())); - for (ActionEntry ent : actions.values()) { - if (ent.requiresPrompt()) { - continue; - } - if (path.getLastPathComponent() instanceof AbstractNode node) { - /** - * This pending node does not duplicate what the lazy node already does. For all - * it's concerned, once it has loaded the entries from the database, it is done. - * This task asks the target to update that database, so it needs its own indicator. - */ - PendingNode pending = new PendingNode(); - node.addNode(0, pending); - CompletableFuture future = - TargetActionTask.runAction(plugin.getTool(), ent.display(), ent); - future.handle((__, ex) -> { - node.removeNode(pending); - return null; - }); - return; - } + current.getSnap()), + ObjectArgumentPolicy.CONTEXT_ONLY) + .values() + .stream() + .filter(e -> !e.requiresPrompt()) + .sorted(Comparator.comparing(e -> -e.specificity())) + .findFirst() + .orElse(null); + if (ent == null) { + // Fail silently. It's common for nodes to not have a refresh action. + return; } + AbstractNode node = (AbstractNode) path.getLastPathComponent(); + /** + * This pending node does not duplicate what the lazy node already does. For all it's + * concerned, once it has loaded the entries from the database, it is done. This task asks + * the target to update that database, so it needs its own indicator. + */ + PendingNode pending = new PendingNode(); + node.addNode(0, pending); + CompletableFuture future = + TargetActionTask.runAction(plugin.getTool(), ent.display(), ent); + future.handle((__, ex) -> { + node.removeNode(pending); + return null; + }); } @Override 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 index d30c495253..41e5b5ff49 100644 --- 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 @@ -28,13 +28,15 @@ 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.target.Target.ObjectArgumentPolicy; 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.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectValue; import ghidra.trace.model.target.iface.*; import ghidra.trace.model.target.path.KeyPath; import ghidra.util.Msg; @@ -53,7 +55,8 @@ public interface ObjectDefaultActionsMixin { } Target target = getCurrent().getTarget(); Map actions = target.collectActions(ActionName.TOGGLE, - new DebuggerSingleObjectPathActionContext(object.getCanonicalPath())); + new DebuggerSingleObjectPathActionContext(object.getCanonicalPath()), + ObjectArgumentPolicy.CONTEXT_ONLY); ActionEntry action = actions.values() .stream() .filter(e -> !e.requiresPrompt()) 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 e44852ec96..60b9c2e449 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 @@ -4,9 +4,9 @@ * 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. @@ -19,17 +19,13 @@ import java.util.*; import java.util.Map.Entry; import java.util.concurrent.*; import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; import docking.ActionContext; import ghidra.app.context.NavigatableActionContext; -import ghidra.app.context.ProgramLocationActionContext; import ghidra.app.nav.Navigatable; import ghidra.app.services.*; import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.async.AsyncUtils; -import ghidra.debug.api.target.ActionName; import ghidra.debug.api.target.Target; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.plugintool.PluginTool; @@ -46,7 +42,6 @@ import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceRegisterUtils; -import ghidra.util.Msg; import ghidra.util.Swing; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -186,81 +181,6 @@ public abstract class AbstractTarget implements Target { return null; } - protected abstract Map collectAddressActions( - ProgramLocationActionContext context); - - protected Map collectAllActions(ActionContext context) { - return Stream.of( - collectResumeActions(context), - collectInterruptActions(context), - collectKillActions(context), - collectStepIntoActions(context), - collectStepOverActions(context), - collectStepOutActions(context), - collectStepExtActions(context), - collectRefreshActions(context), - collectToggleActions(context)) - .flatMap(m -> m.entrySet().stream()) - .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - } - - protected abstract Map collectResumeActions(ActionContext context); - - protected abstract Map collectInterruptActions(ActionContext context); - - protected abstract Map collectKillActions(ActionContext context); - - protected abstract Map collectStepIntoActions(ActionContext context); - - protected abstract Map collectStepOverActions(ActionContext context); - - protected abstract Map collectStepOutActions(ActionContext context); - - protected abstract Map collectStepExtActions(ActionContext context); - - protected abstract Map collectRefreshActions(ActionContext context); - - protected abstract Map collectToggleActions(ActionContext context); - - @Override - public Map collectActions(ActionName name, ActionContext context) { - if (name == null) { - if (context instanceof ProgramLocationActionContext ctx) { - return collectAddressActions(ctx); - } - return collectAllActions(context); - } - else if (ActionName.RESUME.equals(name)) { - return collectResumeActions(context); - } - else if (ActionName.INTERRUPT.equals(name)) { - return collectInterruptActions(context); - } - else if (ActionName.KILL.equals(name)) { - return collectKillActions(context); - } - else if (ActionName.STEP_INTO.equals(name)) { - return collectStepIntoActions(context); - } - else if (ActionName.STEP_OVER.equals(name)) { - return collectStepOverActions(context); - } - else if (ActionName.STEP_OUT.equals(name)) { - return collectStepOutActions(context); - } - else if (ActionName.STEP_EXT.equals(name)) { - return collectStepExtActions(context); - } - 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(); - } - protected static T doSync(String name, Supplier> supplier) throws InterruptedException, ExecutionException { if (Swing.isSwingThread()) { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/MockTarget.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/MockTarget.java index ffc8d50c0a..a37e6479b2 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/MockTarget.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/MockTarget.java @@ -27,8 +27,8 @@ import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; -import ghidra.trace.model.TraceExecutionState; import ghidra.trace.model.Trace; +import ghidra.trace.model.TraceExecutionState; import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.trace.model.guest.TracePlatform; @@ -71,7 +71,8 @@ public class MockTarget implements Target { } @Override - public Map collectActions(ActionName name, ActionContext context) { + public Map collectActions(ActionName name, ActionContext context, + ObjectArgumentPolicy policy) { return Map.of(); }