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 c0e0f69b3b..4c2cfc02a2 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 @@ -41,12 +41,15 @@ dead or terminated. A "dead" trace can still be manipulated and marked up, but it will not record any new target information.

+

Close Trace / All / Other / Dead + Traces

+

In most cases, a trace is ephemeral, but occasionally, interesting behavior is observed that - is difficult to store as static mark-up. When a trace is no longer needed, it can be closed by - right-clicking the tab and selecting "Close Trace." Warning: closing a - trace that has not been saved cannot be undone. If you accumulate many unwanted - traces, use one of the "Close Others," "Close Dead," or "Close All" actions from the pop-up - menu.

+ is difficult to store as static mark-up. When traces are no longer needed, they can be closed + by right-clicking a tab and selecting one of the actions. See Trace + Management for details of each action.

Navigating Threads

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 dba8c1420d..f6637fb2c7 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 @@ -1748,26 +1748,36 @@ public interface DebuggerResources { .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 DESCRIPTION = "Close the current or selected trace"; String GROUP = GROUP_TRACE_CLOSE; + String SUB_GROUP = "a"; Icon ICON = ICON_CLOSE; String HELP_ANCHOR = "close_trace"; - static ActionBuilder builder(Plugin owner) { + static ActionBuilder builderCommon(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)); + } + static ActionBuilder builder(Plugin owner) { + return builderCommon(owner) + .menuGroup(GROUP, SUB_GROUP) + .menuIcon(ICON) + .menuPath(DebuggerPluginPackage.NAME, NAME_PREFIX + "..."); + } + + static ActionBuilder builderPopup(Plugin owner) { + return builderCommon(owner) + .popupMenuGroup(GROUP, SUB_GROUP) + .popupMenuIcon(ICON) + .popupMenuPath(NAME_PREFIX + "..."); } } @@ -1776,14 +1786,25 @@ public interface DebuggerResources { String DESCRIPTION = "Close all traces"; String HELP_ANCHOR = "close_all_traces"; - static ActionBuilder builder(Plugin owner) { + static ActionBuilder builderCommon(Plugin owner) { String ownerName = owner.getName(); return new ActionBuilder(NAME, ownerName) .description(DESCRIPTION) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + + static ActionBuilder builder(Plugin owner) { + return builderCommon(owner) .menuGroup(GROUP) .menuIcon(ICON) - .menuPath(DebuggerPluginPackage.NAME, NAME) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + .menuPath(DebuggerPluginPackage.NAME, NAME); + } + + static ActionBuilder builderPopup(Plugin owner) { + return builderCommon(owner) + .popupMenuGroup(GROUP) + .popupMenuIcon(ICON) + .popupMenuPath(NAME); } } @@ -1792,14 +1813,25 @@ public interface DebuggerResources { String DESCRIPTION = "Close all traces except the current one"; String HELP_ANCHOR = "close_other_traces"; - static ActionBuilder builder(Plugin owner) { + static ActionBuilder builderCommon(Plugin owner) { String ownerName = owner.getName(); return new ActionBuilder(NAME, ownerName) .description(DESCRIPTION) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + + static ActionBuilder builder(Plugin owner) { + return builderCommon(owner) .menuGroup(GROUP) .menuIcon(ICON) - .menuPath(DebuggerPluginPackage.NAME, NAME) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + .menuPath(DebuggerPluginPackage.NAME, NAME); + } + + static ActionBuilder builderPopup(Plugin owner) { + return builderCommon(owner) + .popupMenuGroup(GROUP) + .popupMenuIcon(ICON) + .popupMenuPath(NAME); } } @@ -1808,14 +1840,25 @@ public interface DebuggerResources { String DESCRIPTION = "Close all traces not being recorded"; String HELP_ANCHOR = "close_dead_traces"; - static ActionBuilder builder(Plugin owner) { + static ActionBuilder builderCommon(Plugin owner) { String ownerName = owner.getName(); return new ActionBuilder(NAME, ownerName) .description(DESCRIPTION) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + + static ActionBuilder builder(Plugin owner) { + return builderCommon(owner) .menuGroup(GROUP) .menuIcon(ICON) - .menuPath(DebuggerPluginPackage.NAME, NAME) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + .menuPath(DebuggerPluginPackage.NAME, NAME); + } + + static ActionBuilder builderPopup(Plugin owner) { + return builderCommon(owner) + .popupMenuGroup(GROUP) + .popupMenuIcon(ICON) + .popupMenuPath(NAME); } } 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 177e2caa2b..4c9cb5fd0f 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 @@ -15,11 +15,11 @@ */ package ghidra.app.plugin.core.debug.gui.thread; -import java.awt.*; +import java.awt.BorderLayout; +import java.awt.Rectangle; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.*; -import java.util.List; import javax.swing.*; import javax.swing.event.ListSelectionEvent; @@ -361,6 +361,12 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { SeekTracePresentAction actionSeekTracePresent; ToggleDockingAction actionSyncFocus; DockingAction actionGoToTime; + + DockingAction actionCloseTrace; + DockingAction actionCloseOtherTraces; + DockingAction actionCloseDeadTraces; + DockingAction actionCloseAllTraces; + Set strongRefs = new HashSet<>(); // Eww public DebuggerThreadsProvider(final DebuggerThreadsPlugin plugin) { @@ -554,6 +560,11 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { threadTable.getSelectionModel().addListSelectionListener(this::threadRowSelected); threadTable.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + setThreadRowActionContext(); + } + @Override public void mouseReleased(MouseEvent e) { int selectedRow = threadTable.getSelectedRow(); @@ -584,15 +595,13 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { list.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { - checkTraceTabPopupViaMouse(e); + setTraceTabActionContext(e); } @Override public void mouseReleased(MouseEvent e) { - checkTraceTabPopupViaMouse(e); } }); - // TODO: The popup key? Only seems to have rawCode=0x93 (147) in Swing mainPanel.add(traceTabs, BorderLayout.NORTH); TableColumnModel columnModel = threadTable.getColumnModel(); @@ -622,53 +631,6 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { }); } - private void checkTraceTabPopupViaMouse(MouseEvent e) { - if (!e.isPopupTrigger()) { - return; - } - JList list = traceTabs.getList(); - int i = list.locationToIndex(e.getPoint()); - if (i < 0) { - return; - } - Rectangle cell = list.getCellBounds(i, i); - if (!cell.contains(e.getPoint())) { - return; - } - showTraceTabPopup(e.getComponent(), e.getPoint(), i); - } - - private void showTraceTabPopup(Component comp, Point p, int i) { - // TODO: Make this use action contexts and docking actions instead - traceTabPopupMenu.removeAll(); - final Trace trace = traceTabs.getItem(i); - JMenuItem closeItem = - new JMenuItem("Close " + trace.getName(), DebuggerResources.ICON_CLOSE); - closeItem.addActionListener(evt -> { - traceManager.closeTrace(trace); - }); - JMenuItem closeOthers = new JMenuItem("Close Others", DebuggerResources.ICON_CLOSE); - closeOthers.addActionListener(evt -> { - traceManager.closeOtherTraces(trace); - }); - JMenuItem closeDead = new JMenuItem("Close Dead", DebuggerResources.ICON_CLOSE); - closeDead.addActionListener(evt -> { - traceManager.closeDeadTraces(); - }); - JMenuItem closeAll = new JMenuItem("Close All", DebuggerResources.ICON_CLOSE); - closeAll.addActionListener(evt -> { - for (Trace t : List.copyOf(traceManager.getOpenTraces())) { - traceManager.closeTrace(t); - } - }); - traceTabPopupMenu.add(closeItem); - traceTabPopupMenu.addSeparator(); - traceTabPopupMenu.add(closeOthers); - traceTabPopupMenu.add(closeDead); - traceTabPopupMenu.add(closeAll); - traceTabPopupMenu.show(comp, p.x, p.y); - } - protected void createActions() { // TODO: Make other actions use builder? actionStepSnapBackward = new StepSnapBackwardAction(); @@ -687,6 +649,27 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { .buildAndInstallLocal(this); traceManager.addSynchronizeFocusChangeListener( strongRef(new ToToggleSelectionListener(actionSyncFocus))); + + actionCloseTrace = CloseTraceAction.builderPopup(plugin) + .withContext(DebuggerTraceFileActionContext.class) + .popupWhen(c -> c.getTrace() != null) + .onAction(c -> traceManager.closeTrace(c.getTrace())) + .buildAndInstallLocal(this); + actionCloseAllTraces = CloseAllTracesAction.builderPopup(plugin) + .withContext(DebuggerTraceFileActionContext.class) + .popupWhen(c -> !traceManager.getOpenTraces().isEmpty()) + .onAction(c -> traceManager.closeAllTraces()) + .buildAndInstallLocal(this); + actionCloseOtherTraces = CloseOtherTracesAction.builderPopup(plugin) + .withContext(DebuggerTraceFileActionContext.class) + .popupWhen(c -> traceManager.getOpenTraces().size() > 1 && c.getTrace() != null) + .onAction(c -> traceManager.closeOtherTraces(c.getTrace())) + .buildAndInstallLocal(this); + actionCloseDeadTraces = CloseDeadTracesAction.builderPopup(plugin) + .withContext(DebuggerTraceFileActionContext.class) + .popupWhen(c -> !traceManager.getOpenTraces().isEmpty() && modelService != null) + .onAction(c -> traceManager.closeDeadTraces()) + .buildAndInstallLocal(this); } private void toggleSyncFocus(boolean enabled) { @@ -712,24 +695,50 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { } } + private Trace computeClickedTraceTab(MouseEvent e) { + JList list = traceTabs.getList(); + int i = list.locationToIndex(e.getPoint()); + if (i < 0) { + return null; + } + Rectangle cell = list.getCellBounds(i, i); + if (!cell.contains(e.getPoint())) { + return null; + } + return traceTabs.getItem(i); + } + + private Trace setTraceTabActionContext(MouseEvent e) { + Trace newTrace = e == null ? traceTabs.getSelectedItem() : computeClickedTraceTab(e); + actionCloseTrace.getPopupMenuData() + .setMenuItemName( + CloseTraceAction.NAME_PREFIX + (newTrace == null ? "..." : newTrace.getName())); + myActionContext = new DebuggerTraceFileActionContext(newTrace); + contextChanged(); + return newTrace; + } + private void traceTabSelected(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } - Trace newTrace = traceTabs.getSelectedItem(); - myActionContext = new DebuggerThreadActionContext(newTrace, null); - contextChanged(); + Trace newTrace = setTraceTabActionContext(null); cbCoordinateActivation.invoke(() -> traceManager.activateTrace(newTrace)); } + private ThreadRow setThreadRowActionContext() { + ThreadRow row = threadFilterPanel.getSelectedItem(); + myActionContext = new DebuggerThreadActionContext(current.getTrace(), + row == null ? null : row.getThread()); + contextChanged(); + return row; + } + private void threadRowSelected(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } - ThreadRow row = threadFilterPanel.getSelectedItem(); - myActionContext = new DebuggerThreadActionContext(current.getTrace(), - row == null ? null : row.getThread()); - contextChanged(); + ThreadRow row = setThreadRowActionContext(); if (row != null && traceManager != null) { cbCoordinateActivation.invoke(() -> traceManager.activateThread(row.getThread())); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceFileActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceFileActionContext.java new file mode 100644 index 0000000000..4a926a3b79 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerTraceFileActionContext.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.thread; + +import docking.ActionContext; +import ghidra.trace.model.Trace; + +public class DebuggerTraceFileActionContext extends ActionContext { + private final Trace trace; + + public DebuggerTraceFileActionContext(Trace trace) { + this.trace = trace; + } + + public Trace getTrace() { + return trace; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramURLUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramURLUtils.java index 639117d7a5..ea5c0df68d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramURLUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/ProgramURLUtils.java @@ -23,6 +23,7 @@ import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.model.*; import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.program.model.listing.Program; +import ghidra.util.Msg; public enum ProgramURLUtils { ; @@ -33,7 +34,9 @@ public enum ProgramURLUtils { if (projectLocator == null) { return null; } - RepositoryAdapter repository = file.getParent().getProjectData().getRepository(); + DomainFolder parent = file.getParent(); + ProjectData projectData = parent == null ? null : parent.getProjectData(); + RepositoryAdapter repository = projectData == null ? null : projectData.getRepository(); if (repository != null) { // There is an associated remote repo if (file.isVersioned()) { // The domain file exists there ServerInfo server = repository.getServerInfo(); @@ -66,6 +69,11 @@ public enum ProgramURLUtils { } URL localProjURL = new URL(asString.substring(0, bangLoc)); ProjectData projectData = project.getProjectData(localProjURL); + if (projectData == null) { + Msg.error(ProgramURLUtils.class, "The repository containing " + ghidraURL + + " is not open in the Project manager."); + return null; + } return projectData.getFile(asString.substring(bangLoc + 1)); } catch (MalformedURLException e) {