mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-02-18 16:40:08 +00:00
GP-506: Implemented trace management actions for "Debugger" menu.
This commit is contained in:
parent
8201baef2b
commit
077192c301
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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;
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||
waitForSwing();
|
||||
|
||||
waitForPass(() -> {
|
||||
assertEquals(Set.of(), Set.copyOf(traceManager.getOpenTraces()));
|
||||
assertEquals(Set.of(), traceManager.getOpenTraces());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user