GP-506: Implemented trace management actions for "Debugger" menu.

This commit is contained in:
Dan 2020-12-11 12:51:56 -05:00
parent 8201baef2b
commit 077192c301
8 changed files with 257 additions and 30 deletions

View file

@ -1174,13 +1174,100 @@ public interface DebuggerResources {
static ToggleActionBuilder builder(Plugin owner) { static ToggleActionBuilder builder(Plugin owner) {
String ownerName = owner.getName(); String ownerName = owner.getName();
return new ToggleActionBuilder(NAME, ownerName).description(DESCRIPTION) return new ToggleActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.toolBarGroup(GROUP) .toolBarGroup(GROUP)
.toolBarIcon(ICON) .toolBarIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); .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 { public abstract class AbstractDebuggerConnectionsNode extends GTreeNode {
@Override @Override
public String getName() { public String getName() {

View file

@ -221,7 +221,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
ExportAsFactsAction exportAsFactsAction; ExportAsFactsAction exportAsFactsAction;
ImportFromXMLAction importFromXMLAction; ImportFromXMLAction importFromXMLAction;
ImportFromFactsAction importFromFactsAction; ImportFromFactsAction importFromFactsAction;
OpenTraceAction openTraceAction; OpenWinDbgTraceAction openTraceAction;
private ToggleDockingAction actionToggleSubscribe; private ToggleDockingAction actionToggleSubscribe;
private ToggleDockingAction actionToggleAutoRecord; private ToggleDockingAction actionToggleAutoRecord;
@ -1166,7 +1166,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter implements
exportAsFactsAction = new ExportAsFactsAction(tool, plugin.getName(), this); exportAsFactsAction = new ExportAsFactsAction(tool, plugin.getName(), this);
importFromXMLAction = new ImportFromXMLAction(tool, plugin.getName(), this); importFromXMLAction = new ImportFromXMLAction(tool, plugin.getName(), this);
importFromFactsAction = new ImportFromFactsAction(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 //@formatter:on

View file

@ -40,12 +40,12 @@ import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import resources.ResourceManager; import resources.ResourceManager;
public class OpenTraceAction extends ImportExportAsAction { public class OpenWinDbgTraceAction extends ImportExportAsAction {
protected ImageIcon ICON_TRACE = ResourceManager.loadImage("images/text-xml.png"); protected ImageIcon ICON_TRACE = ResourceManager.loadImage("images/text-xml.png");
private ActionContext context; private ActionContext context;
public OpenTraceAction(PluginTool tool, String owner, DebuggerObjectsProvider provider) { public OpenWinDbgTraceAction(PluginTool tool, String owner, DebuggerObjectsProvider provider) {
super("OpenTrace", tool, owner, provider); super("OpenTrace", tool, owner, provider);
fileExt = ".run"; fileExt = ".run";
fileMode = GhidraFileChooserMode.FILES_AND_DIRECTORIES; fileMode = GhidraFileChooserMode.FILES_AND_DIRECTORIES;

View file

@ -561,23 +561,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
JMenuItem closeOthers = JMenuItem closeOthers =
new JMenuItem("Close Others", DebuggerResources.ICON_CLOSE); new JMenuItem("Close Others", DebuggerResources.ICON_CLOSE);
closeOthers.addActionListener(evt -> { closeOthers.addActionListener(evt -> {
for (Trace t : List.copyOf(traceManager.getOpenTraces())) { traceManager.closeOtherTraces(trace);
if (trace == t) {
continue;
}
traceManager.closeTrace(t);
}
}); });
JMenuItem closeDead = JMenuItem closeDead =
new JMenuItem("Close Dead", DebuggerResources.ICON_CLOSE); new JMenuItem("Close Dead", DebuggerResources.ICON_CLOSE);
closeDead.addActionListener(evt -> { closeDead.addActionListener(evt -> {
for (Trace t : List.copyOf(traceManager.getOpenTraces())) { traceManager.closeDeadTraces();
TraceRecorder recorder = modelService.getRecorder(t);
if (recorder != null) {
continue;
}
traceManager.closeTrace(t);
}
}); });
JMenuItem closeAll = JMenuItem closeAll =
new JMenuItem("Close All", DebuggerResources.ICON_CLOSE); new JMenuItem("Close All", DebuggerResources.ICON_CLOSE);

View file

@ -21,10 +21,13 @@ import java.net.ConnectException;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import docking.ActionContext;
import docking.action.DockingAction;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.event.*; 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.plugin.core.debug.utils.ProgramLocationUtils;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec; import ghidra.async.AsyncConfigFieldCodec.BooleanAsyncConfigFieldCodec;
@ -34,6 +37,7 @@ import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.target.TargetThread; import ghidra.dbg.target.TargetThread;
import ghidra.framework.client.ClientUtil; import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.NotConnectedException; import ghidra.framework.client.NotConnectedException;
import ghidra.framework.main.DataTreeDialog;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
@ -151,12 +155,133 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring; private final AutoService.Wiring autoServiceWiring;
private DataTreeDialog traceChooserDialog;
DockingAction actionCloseTrace;
DockingAction actionCloseAllTraces;
DockingAction actionCloseOtherTraces;
DockingAction actionCloseDeadTraces;
DockingAction actionOpenTrace;
public DebuggerTraceManagerServicePlugin(PluginTool plugintool) { public DebuggerTraceManagerServicePlugin(PluginTool plugintool) {
super(plugintool); super(plugintool);
// NOTE: Plugin should be recognized as its own service provider // NOTE: Plugin should be recognized as its own service provider
autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); 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 @AutoServiceConsumed
private void setModelService(DebuggerModelService modelService) { private void setModelService(DebuggerModelService modelService) {
if (this.modelService != null) { if (this.modelService != null) {
@ -308,6 +433,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
return null; return null;
} }
current = resolved; current = resolved;
contextChanged();
if (current.getTrace() != null && current.getThread() != null) { if (current.getTrace() != null && current.getThread() != null) {
threadFocusByTrace.put(current.getTrace(), current.getThread()); 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) { protected boolean doModelObjectFocused(TargetObjectRef ref, boolean requirePresent) {
curRef = ref; curRef = ref;
if (!synchronizeFocus.get()) { if (!synchronizeFocus.get()) {
@ -410,8 +543,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
} }
@Override @Override
public Collection<Trace> getOpenTraces() { public synchronized Collection<Trace> getOpenTraces() {
return tracesView; return Set.copyOf(tracesView);
} }
@Override @Override
@ -471,6 +604,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
listenersByTrace.put(trace, listener); listenersByTrace.put(trace, listener);
trace.addListener(listener); trace.addListener(listener);
} }
contextChanged();
firePluginEvent(new TraceOpenedPluginEvent(getName(), trace)); firePluginEvent(new TraceOpenedPluginEvent(getName(), trace));
} }
@ -648,6 +782,9 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (current.getTrace() == trace) { if (current.getTrace() == trace) {
activate(DebuggerCoordinates.NOWHERE); activate(DebuggerCoordinates.NOWHERE);
} }
else {
contextChanged();
}
} }
@Override @Override

View file

@ -88,7 +88,7 @@ public interface DebuggerTraceManagerService {
Collection<Trace> openTraces(Collection<DomainFile> files); Collection<Trace> openTraces(Collection<DomainFile> files);
/** /**
* Save the trace to the root folder of the project * Save the trace to the "New Traces" folder of the project
* *
* <p> * <p>
* If a different domain file of the trace's name already exists, an incrementing integer is * 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 closeTrace(Trace trace);
void closeAllTraces();
void closeOtherTraces(Trace keep);
/**
* Close all traces which are not the destination of a live recording
*
* <p>
* 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 activate(DebuggerCoordinates coordinates);
void activateTrace(Trace trace); void activateTrace(Trace trace);
@ -179,5 +192,4 @@ public interface DebuggerTraceManagerService {
* @return the complete resolved coordinates * @return the complete resolved coordinates
*/ */
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coords); DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coords);
} }

View file

@ -233,7 +233,7 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
waitForSwing(); waitForSwing();
waitForPass(() -> { waitForPass(() -> {
assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); assertEquals(Set.of(), traceManager.getOpenTraces());
}); });
} }

View file

@ -46,17 +46,17 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
@Test @Test
public void testGetOpenTraces() throws Exception { public void testGetOpenTraces() throws Exception {
assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); assertEquals(Set.of(), traceManager.getOpenTraces());
createAndOpenTrace(); createAndOpenTrace();
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
assertEquals(Set.of(tb.trace), Set.copyOf(traceManager.getOpenTraces())); assertEquals(Set.of(tb.trace), traceManager.getOpenTraces());
traceManager.closeTrace(tb.trace); traceManager.closeTrace(tb.trace);
waitForSwing(); waitForSwing();
assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); assertEquals(Set.of(), traceManager.getOpenTraces());
} }
@Test @Test
@ -204,8 +204,8 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
createTrace(); createTrace();
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); assertEquals(Set.of(), traceManager.getOpenTraces());
assertEquals(Set.of(tb), Set.copyOf(tb.trace.getConsumerList())); assertEquals(Set.of(tb), tb.trace.getConsumerList());
traceManager.openTrace(tb.trace); traceManager.openTrace(tb.trace);
waitForSwing(); waitForSwing();
@ -213,12 +213,14 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
assertEquals(Set.of(tb, traceManager), Set.copyOf(tb.trace.getConsumerList())); assertEquals(Set.of(tb, traceManager), Set.copyOf(tb.trace.getConsumerList()));
} }
// TODO: Test the other close methods: all, others, dead
@Test @Test
public void testOpenTraceDomainFile() throws Exception { public void testOpenTraceDomainFile() throws Exception {
createTrace(); createTrace();
waitForDomainObject(tb.trace); waitForDomainObject(tb.trace);
assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); assertEquals(Set.of(), traceManager.getOpenTraces());
assertEquals(Set.of(tb), Set.copyOf(tb.trace.getConsumerList())); assertEquals(Set.of(tb), Set.copyOf(tb.trace.getConsumerList()));
traceManager.openTrace(tb.trace.getDomainFile(), DomainFile.DEFAULT_VERSION); traceManager.openTrace(tb.trace.getDomainFile(), DomainFile.DEFAULT_VERSION);
@ -232,7 +234,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
createProgram(); createProgram();
waitForDomainObject(program); waitForDomainObject(program);
assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces())); assertEquals(Set.of(), traceManager.getOpenTraces());
assertEquals(Set.of(this), Set.copyOf(program.getConsumerList())); assertEquals(Set.of(this), Set.copyOf(program.getConsumerList()));
try { try {