diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 9807398a7a..01fa6cafb0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -1174,13 +1174,100 @@ public interface DebuggerResources { static ToggleActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ToggleActionBuilder(NAME, ownerName).description(DESCRIPTION) + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) .toolBarGroup(GROUP) .toolBarIcon(ICON) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } + interface OpenTraceAction { + String NAME = "Open Trace"; + String DESCRIPTION = "Open a trace from the project"; + String GROUP = GROUP_TRACE; + Icon ICON = ICON_TRACE; + String HELP_ANCHOR = "open_trace"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .menuIcon(ICON) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + + } + } + + interface CloseTraceAction { + String NAME_PREFIX = "Close "; + String DESCRIPTION = "Close the current trace"; + String GROUP = GROUP_TRACE; + Icon ICON = ICON_CLOSE; + String HELP_ANCHOR = "close_trace"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME_PREFIX, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .menuIcon(ICON) + .menuPath(DebuggerPluginPackage.NAME, NAME_PREFIX + "...") + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + + } + } + + interface CloseAllTracesAction extends CloseTraceAction { + String NAME = NAME_PREFIX + " All Traces"; + String DESCRIPTION = "Close all traces"; + String HELP_ANCHOR = "close_all_traces"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .menuIcon(ICON) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface CloseOtherTracesAction extends CloseTraceAction { + String NAME = NAME_PREFIX + " Other Traces"; + String DESCRIPTION = "Close all traces except the current one"; + String HELP_ANCHOR = "close_other_traces"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .menuIcon(ICON) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface CloseDeadTracesAction extends CloseTraceAction { + String NAME = NAME_PREFIX + " Dead Traces"; + String DESCRIPTION = "Close all traces not being recorded"; + String HELP_ANCHOR = "close_dead_traces"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .menuIcon(ICON) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + public abstract class AbstractDebuggerConnectionsNode extends GTreeNode { @Override public String getName() { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java index f7e8533cd9..fd9009f33b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java @@ -221,7 +221,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements ExportAsFactsAction exportAsFactsAction; ImportFromXMLAction importFromXMLAction; ImportFromFactsAction importFromFactsAction; - OpenTraceAction openTraceAction; + OpenWinDbgTraceAction openTraceAction; private ToggleDockingAction actionToggleSubscribe; private ToggleDockingAction actionToggleAutoRecord; @@ -1166,7 +1166,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements exportAsFactsAction = new ExportAsFactsAction(tool, plugin.getName(), this); importFromXMLAction = new ImportFromXMLAction(tool, plugin.getName(), this); importFromFactsAction = new ImportFromFactsAction(tool, plugin.getName(), this); - openTraceAction = new OpenTraceAction(tool, plugin.getName(), this); + openTraceAction = new OpenWinDbgTraceAction(tool, plugin.getName(), this); } //@formatter:on diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/OpenTraceAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/OpenWinDbgTraceAction.java similarity index 94% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/OpenTraceAction.java rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/OpenWinDbgTraceAction.java index c269c1d322..92a5ab7c0b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/OpenTraceAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/OpenWinDbgTraceAction.java @@ -40,12 +40,12 @@ import ghidra.framework.plugintool.PluginTool; import ghidra.util.HelpLocation; import resources.ResourceManager; -public class OpenTraceAction extends ImportExportAsAction { +public class OpenWinDbgTraceAction extends ImportExportAsAction { protected ImageIcon ICON_TRACE = ResourceManager.loadImage("images/text-xml.png"); private ActionContext context; - public OpenTraceAction(PluginTool tool, String owner, DebuggerObjectsProvider provider) { + public OpenWinDbgTraceAction(PluginTool tool, String owner, DebuggerObjectsProvider provider) { super("OpenTrace", tool, owner, provider); fileExt = ".run"; fileMode = GhidraFileChooserMode.FILES_AND_DIRECTORIES; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java index da50c4afaa..2f0cab8a11 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java @@ -561,23 +561,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { JMenuItem closeOthers = new JMenuItem("Close Others", DebuggerResources.ICON_CLOSE); closeOthers.addActionListener(evt -> { - for (Trace t : List.copyOf(traceManager.getOpenTraces())) { - if (trace == t) { - continue; - } - traceManager.closeTrace(t); - } + traceManager.closeOtherTraces(trace); }); JMenuItem closeDead = new JMenuItem("Close Dead", DebuggerResources.ICON_CLOSE); closeDead.addActionListener(evt -> { - for (Trace t : List.copyOf(traceManager.getOpenTraces())) { - TraceRecorder recorder = modelService.getRecorder(t); - if (recorder != null) { - continue; - } - traceManager.closeTrace(t); - } + traceManager.closeDeadTraces(); }); JMenuItem closeAll = new JMenuItem("Close All", DebuggerResources.ICON_CLOSE); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 84a4a53736..18daadcd84 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -21,10 +21,13 @@ import java.net.ConnectException; import java.util.*; import java.util.stream.Collectors; +import docking.ActionContext; +import docking.action.DockingAction; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.*; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.utils.ProgramLocationUtils; import ghidra.app.services.*; import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec; @@ -34,6 +37,7 @@ import ghidra.dbg.target.TargetStackFrame; import ghidra.dbg.target.TargetThread; import ghidra.framework.client.ClientUtil; import ghidra.framework.client.NotConnectedException; +import ghidra.framework.main.DataTreeDialog; import ghidra.framework.model.*; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; @@ -151,12 +155,133 @@ public class DebuggerTraceManagerServicePlugin extends Plugin @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; + private DataTreeDialog traceChooserDialog; + + DockingAction actionCloseTrace; + DockingAction actionCloseAllTraces; + DockingAction actionCloseOtherTraces; + DockingAction actionCloseDeadTraces; + DockingAction actionOpenTrace; + public DebuggerTraceManagerServicePlugin(PluginTool plugintool) { super(plugintool); // NOTE: Plugin should be recognized as its own service provider autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); } + @Override + protected void init() { + super.init(); + createActions(); + } + + protected void createActions() { + actionOpenTrace = OpenTraceAction.builder(this) + .enabledWhen(ctx -> true) + .onAction(this::activatedOpenTrace) + .buildAndInstall(tool); + actionCloseTrace = CloseTraceAction.builder(this) + .enabledWhen(ctx -> current.getTrace() != null) + .onAction(this::activatedCloseTrace) + .buildAndInstall(tool); + actionCloseAllTraces = CloseAllTracesAction.builder(this) + .enabledWhen(ctx -> !tracesView.isEmpty()) + .onAction(this::activatedCloseAllTraces) + .buildAndInstall(tool); + actionCloseOtherTraces = CloseOtherTracesAction.builder(this) + .enabledWhen(ctx -> tracesView.size() > 1 && current.getTrace() != null) + .onAction(this::activatedCloseOtherTraces) + .buildAndInstall(tool); + actionCloseDeadTraces = CloseDeadTracesAction.builder(this) + .enabledWhen(ctx -> !tracesView.isEmpty() && modelService != null) + .onAction(this::activatedCloseDeadTraces) + .buildAndInstall(tool); + } + + private void activatedOpenTrace(ActionContext ctx) { + DomainFile df = askTrace(current.getTrace()); + if (df != null) { + Trace trace = openTrace(df, DomainFile.DEFAULT_VERSION); // TODO: Permit opening a previous revision? + activateTrace(trace); + } + } + + private void activatedCloseTrace(ActionContext ctx) { + Trace trace = current.getTrace(); + if (trace == null) { + return; + } + closeTrace(trace); + } + + private void activatedCloseAllTraces(ActionContext ctx) { + closeAllTraces(); + } + + private void activatedCloseOtherTraces(ActionContext ctx) { + Trace trace = current.getTrace(); + if (trace == null) { + return; + } + closeOtherTraces(trace); + } + + private void activatedCloseDeadTraces(ActionContext ctx) { + closeDeadTraces(); + } + + protected DataTreeDialog getTraceChooserDialog() { + if (traceChooserDialog != null) { + return traceChooserDialog; + } + DomainFileFilter filter = df -> Trace.class.isAssignableFrom(df.getDomainObjectClass()); + return traceChooserDialog = + new DataTreeDialog(null, OpenTraceAction.NAME, DataTreeDialog.OPEN, filter) { + { // TODO/HACK: Why the NPE if I don't do this? + dialogShown(); + } + }; + } + + public DomainFile askTrace(Trace trace) { + getTraceChooserDialog(); + if (trace != null) { + traceChooserDialog.selectDomainFile(trace.getDomainFile()); + } + tool.showDialog(traceChooserDialog); + return traceChooserDialog.getDomainFile(); + } + + @Override + public void closeAllTraces() { + for (Trace trace : getOpenTraces()) { + closeTrace(trace); + } + } + + @Override + public void closeOtherTraces(Trace keep) { + for (Trace trace : getOpenTraces()) { + if (trace != keep) { + closeTrace(trace); + } + } + + } + + @Override + public void closeDeadTraces() { + if (modelService == null) { + return; + } + for (Trace trace : getOpenTraces()) { + TraceRecorder recorder = modelService.getRecorder(trace); + if (recorder == null) { + closeTrace(trace); + } + } + } + @AutoServiceConsumed private void setModelService(DebuggerModelService modelService) { if (this.modelService != null) { @@ -308,6 +433,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin return null; } current = resolved; + contextChanged(); if (current.getTrace() != null && current.getThread() != null) { threadFocusByTrace.put(current.getTrace(), current.getThread()); } @@ -315,6 +441,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } } + protected void contextChanged() { + Trace trace = current.getTrace(); + String name = trace == null ? "..." : trace.getName(); + actionCloseTrace.getMenuBarData().setMenuItemName(CloseTraceAction.NAME_PREFIX + name); + tool.contextChanged(null); + } + protected boolean doModelObjectFocused(TargetObjectRef ref, boolean requirePresent) { curRef = ref; if (!synchronizeFocus.get()) { @@ -410,8 +543,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } @Override - public Collection getOpenTraces() { - return tracesView; + public synchronized Collection getOpenTraces() { + return Set.copyOf(tracesView); } @Override @@ -471,6 +604,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin listenersByTrace.put(trace, listener); trace.addListener(listener); } + contextChanged(); firePluginEvent(new TraceOpenedPluginEvent(getName(), trace)); } @@ -648,6 +782,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin if (current.getTrace() == trace) { activate(DebuggerCoordinates.NOWHERE); } + else { + contextChanged(); + } } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java index 3138d79b59..c67bf940b7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerTraceManagerService.java @@ -88,7 +88,7 @@ public interface DebuggerTraceManagerService { Collection openTraces(Collection files); /** - * Save the trace to the root folder of the project + * Save the trace to the "New Traces" folder of the project * *

* If a different domain file of the trace's name already exists, an incrementing integer is @@ -103,6 +103,19 @@ public interface DebuggerTraceManagerService { void closeTrace(Trace trace); + void closeAllTraces(); + + void closeOtherTraces(Trace keep); + + /** + * Close all traces which are not the destination of a live recording + * + *

+ * Operation of this method depends on the model service. If that service is not present, this + * method performs no operation at all. + */ + void closeDeadTraces(); + void activate(DebuggerCoordinates coordinates); void activateTrace(Trace trace); @@ -179,5 +192,4 @@ public interface DebuggerTraceManagerService { * @return the complete resolved coordinates */ DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coords); - } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java index c1a26c1cd6..3fdec87aa7 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java @@ -233,7 +233,7 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI waitForSwing(); waitForPass(() -> { - assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); + assertEquals(Set.of(), traceManager.getOpenTraces()); }); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java index b6ef30e74b..3f55015dce 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java @@ -46,17 +46,17 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge @Test public void testGetOpenTraces() throws Exception { - assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); + assertEquals(Set.of(), traceManager.getOpenTraces()); createAndOpenTrace(); waitForDomainObject(tb.trace); - assertEquals(Set.of(tb.trace), Set.copyOf(traceManager.getOpenTraces())); + assertEquals(Set.of(tb.trace), traceManager.getOpenTraces()); traceManager.closeTrace(tb.trace); waitForSwing(); - assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); + assertEquals(Set.of(), traceManager.getOpenTraces()); } @Test @@ -204,8 +204,8 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge createTrace(); waitForDomainObject(tb.trace); - assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); - assertEquals(Set.of(tb), Set.copyOf(tb.trace.getConsumerList())); + assertEquals(Set.of(), traceManager.getOpenTraces()); + assertEquals(Set.of(tb), tb.trace.getConsumerList()); traceManager.openTrace(tb.trace); waitForSwing(); @@ -213,12 +213,14 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge assertEquals(Set.of(tb, traceManager), Set.copyOf(tb.trace.getConsumerList())); } + // TODO: Test the other close methods: all, others, dead + @Test public void testOpenTraceDomainFile() throws Exception { createTrace(); waitForDomainObject(tb.trace); - assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); + assertEquals(Set.of(), traceManager.getOpenTraces()); assertEquals(Set.of(tb), Set.copyOf(tb.trace.getConsumerList())); traceManager.openTrace(tb.trace.getDomainFile(), DomainFile.DEFAULT_VERSION); @@ -232,7 +234,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge createProgram(); waitForDomainObject(program); - assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); + assertEquals(Set.of(), traceManager.getOpenTraces()); assertEquals(Set.of(this), Set.copyOf(program.getConsumerList())); try {