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) {
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() {

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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<Trace> getOpenTraces() {
return tracesView;
public synchronized Collection<Trace> 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

View File

@ -88,7 +88,7 @@ public interface DebuggerTraceManagerService {
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>
* 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
*
* <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 activateTrace(Trace trace);
@ -179,5 +192,4 @@ public interface DebuggerTraceManagerService {
* @return the complete resolved coordinates
*/
DebuggerCoordinates resolveCoordinates(DebuggerCoordinates coords);
}

View File

@ -233,7 +233,7 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
waitForSwing();
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
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 {