GP-506: Added more actions to Debugger menu, and writing help.

This commit is contained in:
Dan
2020-12-16 11:19:46 -05:00
parent 077192c301
commit 9dccae344e
16 changed files with 318 additions and 106 deletions
@@ -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<Void> 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;
}
@@ -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<GadpClient> build() {
CompletableFuture<Integer> findPort = new CompletableFuture<>();
new Thread(() -> {
class AgentThread extends Thread {
int port;
Process process;
CompletableFuture<Void> ready = new CompletableFuture<>();
public AgentThread() {
super(getThreadName());
}
@Override
public void run() {
try {
ProcessBuilder builder = new ProcessBuilder();
List<String> 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<Void> 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<GadpClient> 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();
});
}
@@ -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|
@@ -82,20 +82,24 @@
sortgroup="e"
target="help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html" />
<tocdef id="DebuggerTraceManagerServicePlugin" text="Trace Management"
sortgroup="f"
target="help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html" />
<tocdef id="DebuggerRegistersPlugin" text="Registers"
sortgroup="f"
sortgroup="g"
target="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html" />
<tocdef id="DebuggerListingPlugin" text="Dynamic Listing"
sortgroup="g"
sortgroup="h"
target="help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html" />
<tocdef id="DebuggerStackPlugin" text="Stack"
sortgroup="h"
sortgroup="i"
target="help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html" />
<tocdef id="DebuggerBreakpointsPlugin" text="Breakpoints"
sortgroup="i"
sortgroup="j"
target="help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html" >
<tocdef id="DebuggerBreakpointMarkerPlugin" text="In the Listings"
@@ -104,15 +108,15 @@
</tocdef>
<tocdef id="DebuggerRegionsPlugin" text="Memory Regions"
sortgroup="j"
sortgroup="k"
target="help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html" />
<tocdef id="DebuggerTimePlugin" text="Time"
sortgroup="k"
sortgroup="l"
target="help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html" />
<tocdef id="DebuggerModulesPlugin" text="Modules and Sections"
sortgroup="l"
sortgroup="m"
target="help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html" >
<tocdef id="DebuggerStaticMappingPlugin" text="Static Mappings"
@@ -121,7 +125,7 @@
</tocdef>
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
sortgroup="m"
sortgroup="n"
target="help/topics/DebuggerBots/DebuggerBots.html" />
</tocdef>
</tocref>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

@@ -130,17 +130,5 @@
translated, to the extent possible, into navigation coordinates and activated in Ghidra. For
example, if the user issues a <CODE>frame</CODE> command to the CLI of a GDB connection, then
Ghidra will navigate to that same frame.</P>
<H3><A name="save_trace"></A>Save Trace</H3>
<P>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.</P>
<H3><A name="save_by_default"></A>Save Traces by Default</H3>
<P>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.</P>
</BODY>
</HTML>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 21 KiB

@@ -0,0 +1,75 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Debugger: Trace Service</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="plugin"></A>Debugger: Trace Management</H1>
<P>This service plugin manages the collection of open traces, and it controlled primarily via
the <A href="help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html">Threads</A> 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.</P>
<H2>Actions</H2>
<P>The plugin provides the following actions and toggles:</P>
<H3><A name="open_trace"></A>Open Trace</H3>
<P>This action is always available. It prompts for a trace in the current project and opens
that trace in the tool.</P>
<H3><A name="save_trace"></A>Save Trace</H3>
<P>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.</P>
<H3><A name="close_trace"></A>Close Trace</H3>
<P>This action is available whenever at least one trace is open and active. It closes the
current trace. <FONT color="red">WARNING:</FONT> If the trace has not been saved, it will be
lost, even when Save by Default is active.</P>
<H3><A name="close_all_traces"></A>Close All Traces</H3>
<P>This action is available whenever at least one trace is open. It closes all traces in this
tool. <FONT color="red">WARNING:</FONT> Any trace that has not been saved will be lost, even
when Save by Default is active.</P>
<H3><A name="close_other_traces"></A>Close Other Traces</H3>
<P>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.
<FONT color="red">WARNING:</FONT> Any closed trace that has not been saved will be lost, even
when Save by Default is active.</P>
<H3><A name="close_dead_traces"></A>Close Dead Traces</H3>
<P>This action is available whenever at least one trace is open. It closes all dead traces in
this tool. <FONT color="red">WARNING:</FONT> Any closed trace that has not been saved will be
lost, even when Save by Default is active.</P>
<H3><A name="save_by_default"></A>Save by Default</H3>
<P>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.</P>
<H3><A name="auto_close_terminated"></A>Close Traces on Termination</H3>
<P>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.</P>
</BODY>
</HTML>
@@ -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);
}
}
}
@@ -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);
@@ -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<Object> 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;
@@ -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);
@@ -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<CollectionChangeListener<DebuggerModelFactory>> 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<Void> 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
@@ -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<TraceRecorder> {
@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<Boolean, Void> saveTracesByDefault = new AsyncReference<>(true);
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> synchronizeFocus = new AsyncReference<>(true);
@AutoConfigStateField(codec = BooleanAsyncConfigFieldCodec.class)
protected final AsyncReference<Boolean, Void> 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<Object> 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> 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) {
@@ -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
*
@@ -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();