diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpTcpDebuggerModelFactory.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpTcpDebuggerModelFactory.java index e0dcd3957d..a57d52298d 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpTcpDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/client/GadpTcpDebuggerModelFactory.java @@ -17,8 +17,7 @@ package ghidra.dbg.gadp.client; import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.channels.AsynchronousChannelGroup; -import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; @@ -53,7 +52,7 @@ public class GadpTcpDebuggerModelFactory implements DebuggerModelFactory { CompletableFuture connect = AsyncUtils.completable(TypeSpec.VOID, channel::connect, new InetSocketAddress(host, port)); return connect.thenCompose(__ -> { - GadpClient client = new GadpClient(host + ":" + port, channel); + GadpClient client = createClient(host + ":" + port, channel); return client.connect().thenApply(___ -> client); }); } @@ -62,6 +61,10 @@ public class GadpTcpDebuggerModelFactory implements DebuggerModelFactory { } } + protected GadpClient createClient(String description, AsynchronousByteChannel channel) { + return new GadpClient(description, channel); + } + public String getAgentAddress() { return host; } diff --git a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/AbstractGadpLocalDebuggerModelFactory.java b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/AbstractGadpLocalDebuggerModelFactory.java index 15c373f836..a08e91aa51 100644 --- a/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/AbstractGadpLocalDebuggerModelFactory.java +++ b/Ghidra/Debug/Debugger-gadp/src/main/java/ghidra/dbg/gadp/server/AbstractGadpLocalDebuggerModelFactory.java @@ -18,6 +18,7 @@ package ghidra.dbg.gadp.server; import java.io.BufferedReader; import java.io.InputStreamReader; import java.lang.ProcessBuilder.Redirect; +import java.nio.channels.AsynchronousByteChannel; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -84,10 +85,17 @@ public abstract class AbstractGadpLocalDebuggerModelFactory implements LocalDebu this.jdwpPort = jdwpPort; } - @Override - public CompletableFuture build() { - CompletableFuture findPort = new CompletableFuture<>(); - new Thread(() -> { + class AgentThread extends Thread { + int port; + Process process; + CompletableFuture ready = new CompletableFuture<>(); + + public AgentThread() { + super(getThreadName()); + } + + @Override + public void run() { try { ProcessBuilder builder = new ProcessBuilder(); List cmd = new ArrayList<>(); @@ -101,9 +109,9 @@ public abstract class AbstractGadpLocalDebuggerModelFactory implements LocalDebu builder.command(cmd); builder.redirectError(Redirect.INHERIT); - Process agent = builder.start(); + process = builder.start(); BufferedReader reader = - new BufferedReader(new InputStreamReader(agent.getInputStream())); + new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while (null != (line = reader.readLine())) { if (LOG_AGENT_STDOUT) { @@ -111,22 +119,61 @@ public abstract class AbstractGadpLocalDebuggerModelFactory implements LocalDebu } if (line.startsWith(AbstractGadpServer.LISTENING_ON)) { String[] parts = line.split(":"); // Separates address from port - findPort.complete(Integer.parseInt(parts[parts.length - 1])); + port = Integer.parseInt(parts[parts.length - 1]); + ready.complete(null); } } - if (!findPort.isDone()) { - findPort.completeExceptionally( + if (!ready.isDone()) { + ready.completeExceptionally( new RuntimeException("Agent terminated unexpectedly")); } } catch (Throwable e) { - findPort.completeExceptionally(e); + ready.completeExceptionally(e); } - }, getThreadName()).start(); - return findPort.thenCompose(selectedPort -> { - GadpTcpDebuggerModelFactory factory = new GadpTcpDebuggerModelFactory(); + } + } + + static class AgentOwningGadpClient extends GadpClient { + private final AgentThread agentThread; + + public AgentOwningGadpClient(String description, AsynchronousByteChannel channel, + AgentThread agentThread) { + super(description, channel); + this.agentThread = agentThread; + } + + @Override + public CompletableFuture close() { + return super.close().thenRun(() -> { + agentThread.process.destroy(); + agentThread.interrupt(); + }); + } + } + + static class AgentOwningGadpTcpDebuggerModelFactory extends GadpTcpDebuggerModelFactory { + private final AgentThread agentThread; + + public AgentOwningGadpTcpDebuggerModelFactory(AgentThread agentThread) { + this.agentThread = agentThread; + } + + @Override + protected GadpClient createClient(String description, AsynchronousByteChannel channel) { + return new AgentOwningGadpClient(description, channel, agentThread); + } + } + + @Override + public CompletableFuture build() { + AgentThread thread = new AgentThread(); + thread.start(); + return thread.ready.thenCompose(__ -> { + GadpTcpDebuggerModelFactory factory = + new AgentOwningGadpTcpDebuggerModelFactory(thread); // selectedPort may differ from port option, particularly if port == 0 - factory.setAgentPort(selectedPort); + factory.setAgentPort(thread.port); return factory.build(); }); } diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index fb03a3e939..65c19f4f55 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -99,6 +99,7 @@ src/main/help/help/topics/DebuggerThreadsPlugin/images/stepback.png||GHIDRA||||E src/main/help/help/topics/DebuggerThreadsPlugin/images/stepinto.png||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END| src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END| src/main/resources/define_info_proc_mappings||GHIDRA||||END| src/main/resources/images/add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml index 9cd0c3ccf4..833620d610 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -82,20 +82,24 @@ sortgroup="e" target="help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html" /> + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.png index 4aee497608..b353abef39 100644 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html index da02d1eac7..de4de4c2a5 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html @@ -130,17 +130,5 @@ translated, to the extent possible, into navigation coordinates and activated in Ghidra. For example, if the user issues a frame command to the CLI of a GDB connection, then Ghidra will navigate to that same frame.

- -

Save Trace

- -

This action is available whenever at least one trace is open and active. It saves the - current trace. If the current trace is not in any project, it saves it under "New Traces" of - the current project.

- -

Save Traces by Default

- -

This toggle is always available. If the tool is closed with this toggle enabled, all open - traces are immediately saved. Note that if Ghidra is abruptly terminated (a rare occurrence - under normal use), traces may not be saved.

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png index cbad0796b6..2b60416b65 100644 Binary files a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png and b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerThreadsPlugin/images/DebuggerThreadsPlugin.png differ diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html new file mode 100644 index 0000000000..109323e406 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html @@ -0,0 +1,75 @@ + + + + + + + Debugger: Trace Service + + + + + +

Debugger: Trace Management

+ +

This service plugin manages the collection of open traces, and it controlled primarily via + the Threads window. + It maintains a list of open traces, the active trace coordinates (trace, time, thread, frame), + and permits saving, opening, and closing traces. To some extent, it also tracks which traces + are being actively recorded.

+ +

Actions

+ +

The plugin provides the following actions and toggles:

+ +

Open Trace

+ +

This action is always available. It prompts for a trace in the current project and opens + that trace in the tool.

+ +

Save Trace

+ +

This action is available whenever at least one trace is open and active. It saves the + current trace. If the current trace is not in any project, it saves it under "New Traces" of + the current project.

+ +

Close Trace

+ +

This action is available whenever at least one trace is open and active. It closes the + current trace. WARNING: If the trace has not been saved, it will be + lost, even when Save by Default is active.

+ +

Close All Traces

+ +

This action is available whenever at least one trace is open. It closes all traces in this + tool. WARNING: Any trace that has not been saved will be lost, even + when Save by Default is active.

+ +

Close Other Traces

+ +

This action is available whenever there is an open trace other than the active one -- + usually two or more open traces. It closes all traces in this tool, except the active trace. + WARNING: Any closed trace that has not been saved will be lost, even + when Save by Default is active.

+ +

Close Dead Traces

+ +

This action is available whenever at least one trace is open. It closes all dead traces in + this tool. WARNING: Any closed trace that has not been saved will be + lost, even when Save by Default is active.

+ +

Save by Default

+ +

This toggle is always available. If the tool is closed with this toggle enabled, all open + traces are immediately saved. Note that if Ghidra is abruptly terminated (a rare occurrence + under normal use), traces may not be saved. When the tool is re-opened, the open traces are + also restored.

+ +

Close Traces on Termination

+ +

This toggle is always available. If a target terminates with this toggle enabled, and it was + being recorded into a trace, that trace is automatically closed. If Save by Default is active, + the trace is saved.

+ + 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 01fa6cafb0..608130991f 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 @@ -41,6 +41,7 @@ import ghidra.app.plugin.core.debug.gui.target.DebuggerTargetsPlugin; import ghidra.app.plugin.core.debug.gui.thread.DebuggerThreadsPlugin; import ghidra.app.plugin.core.debug.gui.time.DebuggerTimePlugin; import ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesPlugin; +import ghidra.app.services.DebuggerTraceManagerService.BooleanChangeAdapter; import ghidra.app.services.MarkerService; import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.util.PluginUtils; @@ -258,6 +259,8 @@ public interface DebuggerResources { String GROUP_TARGET = "Dbg5. Target"; String GROUP_BREAKPOINTS = "Dbg6. Breakpoints"; String GROUP_TRACE = "Dbg7. Trace"; + String GROUP_TRACE_TOGGLES = "Dbg7.a. Trace Toggles"; + String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close"; String GROUP_MAINTENANCE = "Dbg8. Maintenance"; String GROUP_MAPPING = "Dbg9. Map Modules/Sections"; @@ -296,7 +299,7 @@ public interface DebuggerResources { } interface SaveTraceAction { - String NAME = "Save Trace"; + String NAME_PREFIX = "Save "; String DESCRIPTION = "Save the selected trace"; Icon ICON = ICON_SAVE; String GROUP = GROUP_TRACE; @@ -304,9 +307,10 @@ public interface DebuggerResources { static ActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ActionBuilder(NAME, ownerName).description(DESCRIPTION) - .toolBarIcon(ICON) - .toolBarGroup(GROUP) + return new ActionBuilder(NAME_PREFIX, ownerName).description(DESCRIPTION) + .menuPath(DebuggerPluginPackage.NAME, NAME_PREFIX + "...") + .menuIcon(ICON) + .menuGroup(GROUP) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -376,6 +380,9 @@ public interface DebuggerResources { return new ActionBuilder(NAME, owner.getName()).description(DESCRIPTION_PREFIX) .toolBarIcon(ICON) .toolBarGroup(GROUP) + .menuPath(DebuggerPluginPackage.NAME, DESCRIPTION_PREFIX) + .menuIcon(ICON) + .menuGroup(GROUP) .helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR)); } } @@ -549,11 +556,10 @@ public interface DebuggerResources { Icon ICON = ICON_DISCONNECT; String HELP_ANCHOR = "disconnect_all"; - public static ActionBuilder builder(Plugin owner) { - String ownerName = owner.getName(); - return new ActionBuilder(ownerName, NAME).description(DESCRIPTION) + public static ActionBuilder builder(Plugin owner, Plugin helpOwner) { + return new ActionBuilder(owner.getName(), NAME).description(DESCRIPTION) .menuIcon(ICON) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + .helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR)); } } @@ -1166,9 +1172,9 @@ public interface DebuggerResources { } interface SaveByDefaultAction { - String NAME = "Save Trace By Default"; + String NAME = "Save Traces By Default"; String DESCRIPTION = "Automatically save traces to the project"; - String GROUP = GROUP_TRACE; + String GROUP = GROUP_TRACE_TOGGLES; Icon ICON = ICON_SAVE; String HELP_ANCHOR = "save_by_default"; @@ -1176,8 +1182,27 @@ public interface DebuggerResources { String ownerName = owner.getName(); return new ToggleActionBuilder(NAME, ownerName) .description(DESCRIPTION) - .toolBarGroup(GROUP) - .toolBarIcon(ICON) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .menuGroup(GROUP) + .menuIcon(ICON) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface CloseOnTerminateAction { + String NAME = "Close Traces Upon Termination"; + String DESCRIPTION = "Close any live trace whose recording terminates"; + String GROUP = GROUP_TRACE_TOGGLES; + Icon ICON = ICON_CLOSE; + String HELP_ANCHOR = "auto_close_terminated"; + + static ToggleActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .menuGroup(GROUP) + .menuIcon(ICON) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -1204,7 +1229,7 @@ public interface DebuggerResources { interface CloseTraceAction { String NAME_PREFIX = "Close "; String DESCRIPTION = "Close the current trace"; - String GROUP = GROUP_TRACE; + String GROUP = GROUP_TRACE_CLOSE; Icon ICON = ICON_CLOSE; String HELP_ANCHOR = "close_trace"; @@ -1329,4 +1354,20 @@ public interface DebuggerResources { } table.scrollToSelectedRow(); } + + public static class ToToggleSelectionListener implements BooleanChangeAdapter { + private final ToggleDockingAction action; + + public ToToggleSelectionListener(ToggleDockingAction action) { + this.action = action; + } + + @Override + public void changed(Boolean value) { + if (action.isSelected() == value) { + return; + } + action.setSelected(value); + } + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/target/DebuggerTargetsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/target/DebuggerTargetsProvider.java index 1bf726c4b2..052756ef9a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/target/DebuggerTargetsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/target/DebuggerTargetsProvider.java @@ -199,7 +199,7 @@ public class DebuggerTargetsProvider extends ComponentProviderAdapter { private void createActions() { actionConnect = new ConnectAction(); actionDisconnect = new DisconnectAction(); - actionDisconnectAll = DisconnectAllAction.builder(plugin) + actionDisconnectAll = DisconnectAllAction.builder(plugin, plugin) .menuPath(DisconnectAllAction.NAME) .onAction(this::activatedDisconnectAll) .buildAndInstallLocal(this); 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 2f0cab8a11..8ac4195077 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 @@ -137,22 +137,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { } } - protected static class ToToggleSelectionListener implements BooleanChangeAdapter { - private final ToggleDockingAction action; - - public ToToggleSelectionListener(ToggleDockingAction action) { - this.action = action; - } - - @Override - public void changed(Boolean value) { - if (action.isSelected() == value) { - return; - } - action.setSelected(value); - } - } - protected class SeekTracePresentAction extends AbstractSeekTracePresentAction implements BooleanChangeAdapter { public static final String GROUP = DebuggerResources.GROUP_GENERAL; @@ -289,7 +273,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { StepTraceForwardAction actionStepTraceForward; SeekTracePresentAction actionSeekTracePresent; ToggleDockingAction actionSyncFocus; - ToggleDockingAction actionSaveByDefault; Set strongRefs = new HashSet<>(); // Eww public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) { @@ -584,39 +567,17 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { } protected void createActions() { - // TODO: Make other actions like this one - actionSaveTrace = DebuggerResources.SaveTraceAction.builder(plugin) - .enabledWhen(c -> current.getTrace() != null && traceManager != null) - .onAction(c -> saveTrace()) - .buildAndInstallLocal(this); + // TODO: Make other actions use builder? actionStepTraceBackward = new StepTraceBackwardAction(); actionStepTraceForward = new StepTraceForwardAction(); actionSeekTracePresent = new SeekTracePresentAction(); - actionSyncFocus = DebuggerResources.SynchronizeFocusAction.builder(plugin) + actionSyncFocus = SynchronizeFocusAction.builder(plugin) .selected(traceManager != null && traceManager.isSynchronizeFocus()) .enabledWhen(c -> traceManager != null) .onAction(c -> toggleSyncFocus(actionSyncFocus.isSelected())) .buildAndInstallLocal(this); traceManager.addSynchronizeFocusChangeListener( strongRef(new ToToggleSelectionListener(actionSyncFocus))); - actionSaveByDefault = DebuggerResources.SaveByDefaultAction.builder(plugin) - .selected(traceManager != null && traceManager.isSaveTracesByDefault()) - .enabledWhen(c -> traceManager != null) - .onAction(c -> toggleSaveByDefault(actionSaveByDefault.isSelected())) - .buildAndInstallLocal(this); - traceManager.addSaveTracesByDefaultChangeListener( - strongRef(new ToToggleSelectionListener(actionSaveByDefault))); - } - - private void saveTrace() { - Trace curTrace = current.getTrace(); - if (curTrace == null) { - return; - } - if (traceManager == null) { - return; - } - traceManager.saveTrace(curTrace); } private void toggleSyncFocus(boolean enabled) { @@ -626,13 +587,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { traceManager.setSynchronizeFocus(enabled); } - private void toggleSaveByDefault(boolean enabled) { - if (traceManager == null) { - return; - } - traceManager.setSaveTracesByDefault(enabled); - } - private void traceTabSelected(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java index 8a9f5ea8ab..73d951588e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java @@ -217,7 +217,7 @@ public class DebuggerModelServicePlugin extends Plugin } protected void createActions() { - actionDisconnectAll = DisconnectAllAction.builder(this) + actionDisconnectAll = DisconnectAllAction.builder(this, this) .menuPath("Debugger", DisconnectAllAction.NAME) .onAction(this::activatedDisconnectAll) .buildAndInstall(tool); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java index 95464157ce..3ae77cbe5a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java @@ -26,8 +26,8 @@ import ghidra.app.events.ProgramActivatedPluginEvent; import ghidra.app.events.ProgramClosedPluginEvent; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; -import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.DebugProgramAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction; import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper; import ghidra.app.plugin.core.debug.utils.BackgroundUtils; import ghidra.app.services.*; @@ -164,6 +164,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin new ProxiedRecorderChangeListener(); DockingAction actionDebugProgram; + DockingAction actionDisconnectAll; protected final ListenerSet> factoryListeners = new ListenerSet<>(CollectionChangeListener.of(DebuggerModelFactory.class)); @@ -190,10 +191,15 @@ public class DebuggerModelServiceProxyPlugin extends Plugin protected void createActions() { // Note, I have to give an enabledWhen, otherwise any context change re-enables it - actionDebugProgram = DebuggerResources.DebugProgramAction.builder(this, delegate) + actionDebugProgram = DebugProgramAction.builder(this, delegate) .enabledWhen(ctx -> currentProgramPath != null) .onAction(this::debugProgramActivated) .buildAndInstall(tool); + actionDisconnectAll = DisconnectAllAction.builder(this, delegate) + .menuPath("Debugger", DisconnectAllAction.NAME) + .onAction(this::activatedDisconnectAll) + .buildAndInstall(tool); + updateActionDebugProgram(); } @@ -209,6 +215,10 @@ public class DebuggerModelServiceProxyPlugin extends Plugin true, this::debugProgram); } + private void activatedDisconnectAll(ActionContext context) { + closeAllModels(); + } + private CompletableFuture debugProgram(Program __, TaskMonitor monitor) { monitor.initialize(3); monitor.setMessage("Starting local session"); @@ -244,6 +254,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin String desc = currentProgramPath == null ? DebugProgramAction.DESCRIPTION_PREFIX.trim() : DebugProgramAction.DESCRIPTION_PREFIX + currentProgramPath; actionDebugProgram.setDescription(desc); + actionDebugProgram.getMenuBarData().setMenuItemName(desc); } @Override 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 18daadcd84..3d7a5babe1 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 @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import docking.ActionContext; import docking.action.DockingAction; +import docking.action.ToggleDockingAction; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; @@ -125,12 +126,21 @@ public class DebuggerTraceManagerServicePlugin extends Plugin // TODO: This is a bit out of this manager's bounds, but acceptable for now. class ForRecordersListener implements CollectionChangeListener { @Override - public void elementAdded(TraceRecorder element) { + public void elementAdded(TraceRecorder recorder) { updateCurrentRecorder(); } @Override - public void elementRemoved(TraceRecorder element) { + public void elementRemoved(TraceRecorder recorder) { + if (isAutoCloseOnTerminate()) { + Trace trace = recorder.getTrace(); + if (getOpenTraces().contains(trace)) { + if (isSaveTracesByDefault()) { + saveTrace(trace); + } + closeTrace(trace); + } + } updateCurrentRecorder(); } } @@ -149,6 +159,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin protected final AsyncReference saveTracesByDefault = new AsyncReference<>(true); @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class) protected final AsyncReference synchronizeFocus = new AsyncReference<>(true); + @AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class) + protected final AsyncReference autoCloseOnTerminate = new AsyncReference<>(true); // @AutoServiceConsumed via method private DebuggerModelService modelService; @@ -161,7 +173,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin DockingAction actionCloseAllTraces; DockingAction actionCloseOtherTraces; DockingAction actionCloseDeadTraces; + DockingAction actionSaveTrace; DockingAction actionOpenTrace; + ToggleDockingAction actionSaveByDefault; + ToggleDockingAction actionCloseOnTerminate; + Set strongRefs = new HashSet<>(); // Eww public DebuggerTraceManagerServicePlugin(PluginTool plugintool) { super(plugintool); @@ -169,6 +185,11 @@ public class DebuggerTraceManagerServicePlugin extends Plugin autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); } + private T strongRef(T t) { + strongRefs.add(t); + return t; + } + @Override protected void init() { super.init(); @@ -176,6 +197,10 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } protected void createActions() { + actionSaveTrace = SaveTraceAction.builder(this) + .enabledWhen(c -> current.getTrace() != null) + .onAction(this::activatedSaveTrace) + .buildAndInstall(tool); actionOpenTrace = OpenTraceAction.builder(this) .enabledWhen(ctx -> true) .onAction(this::activatedOpenTrace) @@ -196,6 +221,28 @@ public class DebuggerTraceManagerServicePlugin extends Plugin .enabledWhen(ctx -> !tracesView.isEmpty() && modelService != null) .onAction(this::activatedCloseDeadTraces) .buildAndInstall(tool); + + actionSaveByDefault = SaveByDefaultAction.builder(this) + .selected(isSaveTracesByDefault()) + .onAction(c -> setSaveTracesByDefault(actionSaveByDefault.isSelected())) + .buildAndInstall(tool); + addSaveTracesByDefaultChangeListener( + strongRef(new ToToggleSelectionListener(actionSaveByDefault))); + + actionCloseOnTerminate = CloseOnTerminateAction.builder(this) + .selected(isAutoCloseOnTerminate()) + .onAction(c -> setAutoCloseOnTerminate(actionCloseOnTerminate.isSelected())) + .buildAndInstall(tool); + addAutoCloseOnTerminateChangeListener( + strongRef(new ToToggleSelectionListener(actionCloseOnTerminate))); + } + + private void activatedSaveTrace(ActionContext ctx) { + Trace trace = current.getTrace(); + if (trace == null) { + return; + } + saveTrace(trace); } private void activatedOpenTrace(ActionContext ctx) { @@ -445,6 +492,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin Trace trace = current.getTrace(); String name = trace == null ? "..." : trace.getName(); actionCloseTrace.getMenuBarData().setMenuItemName(CloseTraceAction.NAME_PREFIX + name); + actionSaveTrace.getMenuBarData().setMenuItemName(SaveTraceAction.NAME_PREFIX + name); tool.contextChanged(null); } @@ -741,6 +789,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin public void run(TaskMonitor monitor) throws CancelledException { try { traces.createFile(finalFilename, trace, monitor); + trace.save("Initial save", monitor); } catch (CancelledException e) { // Done @@ -762,7 +811,6 @@ public class DebuggerTraceManagerServicePlugin extends Plugin "Save New Trace Error", e.getMessage(), e); } } - }); } } @@ -973,6 +1021,26 @@ public class DebuggerTraceManagerServicePlugin extends Plugin saveTracesByDefault.removeChangeListener(listener); } + @Override + public void setAutoCloseOnTerminate(boolean enabled) { + autoCloseOnTerminate.set(enabled, null); + } + + @Override + public boolean isAutoCloseOnTerminate() { + return autoCloseOnTerminate.get(); + } + + @Override + public void addAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener) { + autoCloseOnTerminate.addChangeListener(listener); + } + + @Override + public void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener) { + autoCloseOnTerminate.removeChangeListener(listener); + } + @Override // TODO: Move this into some static util, now that canonical view is a trace concept public ProgramLocation fixLocation(ProgramLocation location, boolean matchSnap) { 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 c67bf940b7..fb4df12f24 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 @@ -162,7 +162,7 @@ public interface DebuggerTraceManagerService { /** * Check whether traces should by saved by default * - * @return true if save by default, false otherwise + * @return true if saved by default, false otherwise */ boolean isSaveTracesByDefault(); @@ -170,6 +170,24 @@ public interface DebuggerTraceManagerService { void removeSaveTracesByDefaultChangeListener(BooleanChangeAdapter listener); + /** + * Control whether live traces are automatically closed upon target termination + * + * @param enabled true to automatically close, false to leave open + */ + void setAutoCloseOnTerminate(boolean enabled); + + /** + * Check whether live traces are automatically closed upon target termination + * + * @return true if automatically closed, false if left open + */ + boolean isAutoCloseOnTerminate(); + + void addAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener); + + void removeAutoCloseOnTerminateChangeListener(BooleanChangeAdapter listener); + /** * Swap out the trace view of a {@link ProgramLocation} if it is not the debugger's view * diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPluginScreenShots.java index a6dc4ce8f4..6772bd1052 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsPluginScreenShots.java @@ -101,6 +101,8 @@ public class DebuggerThreadsPluginScreenShots extends GhidraScreenShotGenerator TraceRecorder recDummy2 = modelService.recordTarget(dummy2, new TestDebuggerTargetTraceMapper(dummy2)); + traceManager.setAutoCloseOnTerminate(false); + traceManager.openTrace(recDummy1.getTrace()); traceManager.openTrace(recDummy2.getTrace()); recDummy1.stopRecording();