From a1cfeebcc93cb1e2e94e44a8e881649bc72452aa Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 5 May 2021 09:35:35 -0400 Subject: [PATCH] GP-406: Central Debug Console for problems and actions --- Ghidra/Debug/Debugger/certification.manifest | 3 +- .../src/main/help/help/TOC_Source.xml | 4 + .../help/topics/Debugger/Troubleshooting.html | 10 +- .../DebuggerConsolePlugin.html | 68 ++ .../images/DebuggerConsolePlugin.png | Bin 0 -> 16762 bytes .../DebuggerInterpreterPlugin.html | 4 +- .../DebuggerListingPlugin.html | 27 +- .../images/DebuggerModuleImportDialog.png | Bin 11360 -> 0 bytes .../DebuggerModulesPlugin.html | 10 + .../core/debug/gui/DebuggerResources.java | 96 +- .../gui/console/ConsoleActionsCellEditor.java | 67 ++ .../console/ConsoleActionsCellRenderer.java | 89 ++ .../gui/console/DebuggerConsolePlugin.java | 130 +++ .../gui/console/DebuggerConsoleProvider.java | 487 +++++++++ .../console/LogRowConsoleActionContext.java | 22 + .../DebuggerInterpreterPlugin.java | 23 +- .../gui/listing/DebuggerListingPlugin.java | 2 +- .../gui/listing/DebuggerListingProvider.java | 55 +- .../listing/DebuggerModuleImportDialog.java | 251 ----- .../DebuggerMissingModuleActionContext.java | 55 ++ .../gui/modules/DebuggerModulesPlugin.java | 35 +- .../gui/modules/DebuggerModulesProvider.java | 72 +- .../model/DebuggerModelServicePlugin.java | 4 +- .../service/model/DefaultThreadRecorder.java | 4 +- .../service/model/TraceEventListener.java | 6 +- .../DebuggerTraceManagerServicePlugin.java | 2 +- .../app/services/DebuggerConsoleService.java | 87 ++ .../main/resources/defaultTools/Debugger.tool | 921 +++++++++++------- .../DebuggerConsolePluginScreenShots.java | 77 ++ .../DebuggerListingPluginScreenShots.java | 33 - .../console/DebuggerConsoleProviderTest.java | 78 ++ .../listing/DebuggerListingProviderTest.java | 23 +- .../ghidra/dbg/DebuggerConsoleLogger.java | 20 + .../model/TraceDomainObjectListener.java | 2 +- .../trace/util/DefaultTraceTimeViewport.java | 14 +- .../table/CustomToStringCellRenderer.java | 35 +- .../DefaultEnumeratedColumnTableModel.java | 102 +- .../RowWrappedEnumeratedColumnTableModel.java | 16 +- .../src/main/java/ghidra/util/TimedMsg.java | 4 +- 39 files changed, 2101 insertions(+), 837 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerConsolePlugin/images/DebuggerConsolePlugin.png delete mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerModuleImportDialog.png create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/ConsoleActionsCellEditor.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/ConsoleActionsCellRenderer.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePlugin.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/LogRowConsoleActionContext.java delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerModuleImportDialog.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerMissingModuleActionContext.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerConsoleService.java create mode 100644 Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePluginScreenShots.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProviderTest.java create mode 100644 Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerConsoleLogger.java diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 1e12776852..9f50cd3e65 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -38,11 +38,12 @@ src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoint-mixed-ed.p src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-clear-all.png||GHIDRA||||END| src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-disable-all.png||GHIDRA||||END| src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-enable-all.png||GHIDRA||||END| +src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html||GHIDRA||||END| +src/main/help/help/topics/DebuggerConsolePlugin/images/DebuggerConsolePlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerInterpreterPlugin/DebuggerInterpreterPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerGoToDialog.png||GHIDRA||||END| src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.png||GHIDRA||||END| -src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerModuleImportDialog.png||GHIDRA||||END| src/main/help/help/topics/DebuggerMemviewPlugin/DebuggerMemviewPlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin_old.png||GHIDRA||||END| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml index a9902c2ef8..d21c44c30c 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -74,6 +74,10 @@ target="help/topics/DebuggerModelServicePlugin/DebuggerModelServicePlugin.html" /> + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/Debugger/Troubleshooting.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/Debugger/Troubleshooting.html index c851714c51..0ed4ff62bc 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/Debugger/Troubleshooting.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/Debugger/Troubleshooting.html @@ -15,9 +15,10 @@

Error Console

-

The first place to look when you're having trouble is the error console. In Eclipse, this is - just the "Console" window. In Ghidra, it can be accessed from the main application window. - Sometimes it reports known issues; sometimes it reports unexpected behavior; etc., which may be +

The first place to look when you're having trouble is the Debug Console. Second, if you're + in Eclipse, you can check its "Console" window. Often, Ghidra's Debug Console will offer + actions to help you resolve a well-known issue or configuration problem. It also duplicates the + error log, when those messages are emitted from a debugger-related class. These typically offer clues to exactly what has gone wrong.

Settings and Toggles

@@ -38,9 +39,6 @@

In the Dynamic Listing:

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html new file mode 100644 index 0000000000..30e30a3393 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html @@ -0,0 +1,68 @@ + + + + + + + Debugger: Memory Regions + + + + + +

Debugger: Console

+ + + + + + + +
+ +

The console logs messages from Ghidra related to the debugger. Depending on the exact + configuration, this can comprise a wide range of components, including all GUI views, active + connectors, and running agents. Currently, it implements an appender to gather all Log4J + messages emitted by Ghidra and filters for debugger-related packages and a level in the range + INFO through and including FATAL. That feature will likely be removed as more components are + programmed to work directly with the console. Soon, it may also provide a command-line + interface to control Ghidra's debugging sessions and interact with traces.

+ +

Some log messages include an action context, allowing plug-ins to offer actions on that + message. These are said to be "actionable" messages. A noteworthy example is when navigating to + a module that could not be automatically mapped from the current project. Instead of displaying + a prompt, it will log a message and suggest actions to resolve the issue. A successful + resolution typically removes the message from the log. Note that additional actions may be + available from the context menu.

+ +

By default, the log is sorted so that actionable messages appear at the top. Then, it is + sorted by descending date, so that the most recent messages appear at the top. Like any other + Ghidra table, it can customized and filtered. Note that the filter box is at the top, because + we anticipate a command-line input in the future, which we'd like to place at the bottom.

+ +

Table Columns

+ +

The table has the following columns:

+ + + +

Actions

+ +

Not considering extension actions from other plugins, the console provides the + following:

+ +

Clear

+ +

Removes all messages, including actionable messages, from the log.

+ + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerConsolePlugin/images/DebuggerConsolePlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerConsolePlugin/images/DebuggerConsolePlugin.png new file mode 100644 index 0000000000000000000000000000000000000000..79f8089b97ad217e4216160befedfbc24b61ebd8 GIT binary patch literal 16762 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 - +

Interrupt

- +

This action is always available. It interrupts the current target's execution.

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html index ad4215d60c..9769c18d68 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html @@ -131,7 +131,9 @@ computed using information about loaded modules reported by the debugger. For the finer details, see the Static Mappings - window.

+ window. When you navigate to a location contained by a module, but there is no corresponding + static location, the listing logs a "missing module" to the console, offering either to import + the module or map it to an existing program.

Capture Memory

@@ -162,29 +164,6 @@ neglect to capture read-only ranges that have been captured previously. -

Auto-Import Current Module

- -

This toggle is available whenever Sync to Static Listing is enabled. It causes Ghidra to - prompt the user to import unknown modules. Specifically, when Ghidra cannot map the dynamic - listing's location to a static location, but the debugger reports a module containing the - dynamic address, it prompts the user to import that module:

- - - - - - - -
- -

This non-modal dialog collects those prompts and appears whenever a new module is suggested. - To import a module, click the import icon to the suggestion's right. To remove an entry, click - the delete icon to the suggestions's left. To ignore an entry, check the box to the left of the - suggestion and dismiss the dialog. Note that a removed suggestion is not ignored. Ghidra may - suggest that module again. To import modules manually, see the Modules window.

-

Tool Options: Colors

The memory-state and tracked-location background colors can all be configured here.

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerModuleImportDialog.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerModuleImportDialog.png deleted file mode 100644 index 0cab69707e802f1f284a192c707ebd0f3db75fb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11360 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 diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html index d7a3eee373..445e0c380b 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html @@ -145,6 +145,16 @@ It behaves like Map Sections, except that it will propose the selected section be mapped to the block containing the cursor in the static listing.

+

Import Missing Module

+ +

This action is offered to resolve a "Missing Module" console message. It is equivalent to Import From File System on the missing module.

+ +

Map Missing Module

+ +

This action is offered to resolve a "Missing Module" console message. It is equivalent to Map Module To on the missing module.

+

Filter Sections by Module

This action is always available. By default the bottom table displays all sections in the 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 a254a77e34..13a613fac6 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 @@ -32,6 +32,7 @@ import docking.widgets.table.*; import docking.widgets.tree.GTreeNode; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsPlugin; +import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsPlugin; import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesPlugin; @@ -118,6 +119,7 @@ public interface DebuggerResources { ImageIcon ICON_CLOSE = ResourceManager.loadImage("images/x.gif"); ImageIcon ICON_ADD = ResourceManager.loadImage("images/add.png"); ImageIcon ICON_DELETE = ResourceManager.loadImage("images/delete.png"); + ImageIcon ICON_CLEAR = ResourceManager.loadImage("images/erase16.png"); ImageIcon ICON_REFRESH = ResourceManager.loadImage("images/view-refresh.png"); ImageIcon ICON_FILTER = ResourceManager.loadImage("images/filter_off.png"); // Eww. ImageIcon ICON_SELECT_ROWS = ResourceManager.loadImage("images/table_go.png"); @@ -128,7 +130,7 @@ public interface DebuggerResources { //ResourceManager.loadImage("images/capture-memory.png"); // TODO: Draw an icon - ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/map-modules.png"); + ImageIcon ICON_MAP_MODULES = ResourceManager.loadImage("images/modules.png"); ImageIcon ICON_MAP_SECTIONS = ICON_MAP_MODULES; // TODO ImageIcon ICON_BLOCK = ICON_MAP_SECTIONS; // TODO // TODO: Draw an icon @@ -138,6 +140,10 @@ public interface DebuggerResources { // TODO: Draw an icon? ImageIcon ICON_CAPTURE_SYMBOLS = ResourceManager.loadImage("images/closedFolderLabels.png"); + ImageIcon ICON_LOG_FATAL = ResourceManager.loadImage("images/edit-bomg.png"); + ImageIcon ICON_LOG_ERROR = ResourceManager.loadImage("images/dialog-warning_red.png"); + ImageIcon ICON_LOG_WARN = ResourceManager.loadImage("images/dialog-warning.png"); + ImageIcon ICON_SYNC = ResourceManager.loadImage("images/sync_enabled.png"); ImageIcon ICON_VISIBILITY = ResourceManager.loadImage("images/format-text-bold.png"); @@ -156,6 +162,11 @@ public interface DebuggerResources { HelpLocation HELP_PROVIDER_BREAKPOINTS = new HelpLocation( PluginUtils.getPluginNameFromClass(DebuggerBreakpointsPlugin.class), HELP_ANCHOR_PLUGIN); + String TITLE_PROVIDER_CONSOLE = "Debug Console"; + ImageIcon ICON_PROVIDER_CONSOLE = ICON_CONSOLE; + HelpLocation HELP_PROVIDER_CONSOLE = new HelpLocation( + PluginUtils.getPluginNameFromClass(DebuggerConsolePlugin.class), HELP_ANCHOR_PLUGIN); + String TITLE_PROVIDER_LISTING = "Dynamic"; ImageIcon ICON_PROVIDER_LISTING = ICON_LISTING; HelpLocation HELP_PROVIDER_LISTING = new HelpLocation( @@ -310,6 +321,9 @@ public interface DebuggerResources { "Colors.Ineffective Disabled Breakpoint Markers Have Background"; boolean DEFAULT_COLOR_INEFFECTIVE_D_BREAKPOINT_COLORING_BACKGROUND = false; + String OPTION_NAME_LOG_BUFFER_LIMIT = "Log Buffer Size"; + int DEFAULT_LOG_BUFFER_LIMIT = 100; + // TODO: Re-assign/name groups String GROUP_GENERAL = "Dbg1. General"; String GROUP_CONNECTION = "Dbg2. Connection"; @@ -645,7 +659,7 @@ public interface DebuggerResources { .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } - + interface InterpreterInterruptAction { String NAME = "Interpreter Interrupt"; String DESCRIPTION = "Send an interrupt through this Interpreter"; @@ -733,17 +747,36 @@ public interface DebuggerResources { } } - interface AutoImportCurrentModuleAction { - String NAME = "Auto-Import Current Module"; - String DESCRIPTION = "Import missing module at the cursor"; + interface ImportMissingModuleAction { + String NAME = "Import Missing Module"; + String DESCRIPTION = "Import the missing module from disk"; Icon ICON = ICON_IMPORT; - String HELP_ANCHOR = "auto_import_module"; + String HELP_ANCHOR = "import_missing_module"; - static ToggleActionBuilder builder(Plugin owner) { + static ActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ToggleActionBuilder(NAME, ownerName).description(DESCRIPTION) - .menuIcon(ICON) - .menuPath(NAME) + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarIcon(ICON) + .popupMenuIcon(ICON) + .popupMenuPath(NAME) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface MapMissingModuleAction { + String NAME = "Map Missing Module"; + String DESCRIPTION = "Map the missing module to an existing import"; + Icon ICON = ICON_MAP_MODULES; + String HELP_ANCHOR = "map_missing_module"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarIcon(ICON) + .popupMenuIcon(ICON) + .popupMenuPath(NAME) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } } @@ -862,7 +895,8 @@ public interface DebuggerResources { static ActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ActionBuilder(NAME, ownerName).toolBarGroup(GROUP) + return new ActionBuilder(NAME, ownerName) + .toolBarGroup(GROUP) .toolBarIcon(ICON) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } @@ -879,7 +913,26 @@ public interface DebuggerResources { } static ActionBuilder builder(String ownerName) { - return new ActionBuilder(NAME, ownerName).toolBarGroup(GROUP) + return new ActionBuilder(NAME, ownerName) + .toolBarGroup(GROUP) + .toolBarIcon(ICON) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface ClearAction { + String NAME = "Clear"; + String GROUP = "yyyy"; + Icon ICON = ICON_CLEAR; + String HELP_ANCHOR = "clear"; + + static ActionBuilder builder(Plugin owner) { + return builder(owner.getName()); + } + + static ActionBuilder builder(String ownerName) { + return new ActionBuilder(NAME, ownerName) + .toolBarGroup(GROUP) .toolBarIcon(ICON) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); } @@ -892,7 +945,21 @@ public interface DebuggerResources { static ToggleActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ToggleActionBuilder(NAME, ownerName).toolBarGroup(GROUP).toolBarIcon(ICON); + return new ToggleActionBuilder(NAME, ownerName) + .toolBarGroup(GROUP) + .toolBarIcon(ICON); + } + } + + interface SelectNoneAction { + String NAME = "Select None"; + String GROUP = "Select"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .popupMenuGroup(GROUP) + .popupMenuPath(NAME); } } @@ -904,7 +971,8 @@ public interface DebuggerResources { static ActionBuilder builder(Plugin owner) { String ownerName = owner.getName(); - return new ActionBuilder(NAME, ownerName).toolBarGroup(GROUP) + return new ActionBuilder(NAME, ownerName) + .toolBarGroup(GROUP) .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)) .toolBarIcon(ICON); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/ConsoleActionsCellEditor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/ConsoleActionsCellEditor.java new file mode 100644 index 0000000000..866fb4f53b --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/ConsoleActionsCellEditor.java @@ -0,0 +1,67 @@ +/* ### + * 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.console; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.*; +import javax.swing.table.TableCellEditor; + +import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.ActionList; +import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction; + +public class ConsoleActionsCellEditor extends AbstractCellEditor + implements TableCellEditor, ActionListener { + private static final ActionList EMPTY_ACTION_LIST = new ActionList(); + + protected final JPanel box = new JPanel(); + protected final List buttonCache = new ArrayList<>(); + + protected ActionList value; + + public ConsoleActionsCellEditor() { + ConsoleActionsCellRenderer.configureBox(box); + } + + @Override + public Object getCellEditorValue() { + return EMPTY_ACTION_LIST; + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object v, boolean isSelected, + int row, int column) { + // I can't think of when you'd be "editing" a non-selected cell. + box.setBackground(table.getSelectionBackground()); + + value = (ActionList) v; + ConsoleActionsCellRenderer.populateBox(box, buttonCache, value, + button -> button.addActionListener(this)); + return box; + } + + @Override + public void actionPerformed(ActionEvent e) { + int index = buttonCache.indexOf(e.getSource()); + BoundAction action = value.get(index); + stopCellEditing(); + action.perform(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/ConsoleActionsCellRenderer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/ConsoleActionsCellRenderer.java new file mode 100644 index 0000000000..dba02d6ef0 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/ConsoleActionsCellRenderer.java @@ -0,0 +1,89 @@ +/* ### + * 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.console; + +import java.awt.Component; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import javax.swing.*; + +import docking.widgets.table.GTableCellRenderingData; +import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.ActionList; +import ghidra.app.plugin.core.debug.gui.console.DebuggerConsoleProvider.BoundAction; +import ghidra.docking.settings.Settings; +import ghidra.util.table.column.AbstractGhidraColumnRenderer; + +public class ConsoleActionsCellRenderer extends AbstractGhidraColumnRenderer { + + static void configureBox(JPanel box) { + box.setLayout(new BoxLayout(box, BoxLayout.X_AXIS)); + box.setOpaque(true); + box.setAlignmentX(0.5f); + } + + static void ensureCacheSize(List buttonCache, int size, + Consumer extraConfig) { + int diff = size - buttonCache.size(); + for (int i = 0; i < diff; i++) { + JButton button = new JButton(); + button.setMinimumSize(DebuggerConsoleProvider.ACTION_BUTTON_DIM); + button.setMaximumSize(DebuggerConsoleProvider.ACTION_BUTTON_DIM); + extraConfig.accept(button); + buttonCache.add(button); + } + } + + static void populateBox(JPanel box, List buttonCache, ActionList value, + Consumer extraConfig) { + box.removeAll(); + ensureCacheSize(buttonCache, value.size(), extraConfig); + int i = 0; + for (BoundAction a : value) { + JButton button = buttonCache.get(i); + button.setToolTipText(a.getTooltipText()); + button.setIcon(a.getIcon()); + button.setEnabled(a.isEnabled()); + box.add(button); + i++; + } + } + + protected final JPanel box = new JPanel(); + protected final List buttonCache = new ArrayList<>(); + + public ConsoleActionsCellRenderer() { + configureBox(box); + } + + @Override + public String getFilterString(ActionList t, Settings settings) { + return t.stream().map(a -> a.getName()).collect(Collectors.joining(" ")); + } + + @Override + public Component getTableCellRendererComponent(GTableCellRenderingData data) { + super.getTableCellRendererComponent(data); // A bit of a waste, but sets the background + box.setBackground(getBackground()); + + ActionList value = (ActionList) data.getValue(); + populateBox(box, buttonCache, value, button -> { + }); + return box; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePlugin.java new file mode 100644 index 0000000000..5fd781ca61 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePlugin.java @@ -0,0 +1,130 @@ +/* ### + * 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.console; + +import javax.swing.Icon; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.filter.LevelRangeFilter; + +import docking.ActionContext; +import docking.action.DockingActionIf; +import ghidra.app.plugin.PluginCategoryNames; +import ghidra.app.plugin.core.debug.DebuggerPluginPackage; +import ghidra.app.services.DebuggerConsoleService; +import ghidra.framework.plugintool.*; +import ghidra.framework.plugintool.util.PluginStatus; + +@PluginInfo( + shortDescription = "Debugger console panel plugin", + description = "A tool-global console for controlling a debug/trace session", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.RELEASED, + servicesRequired = {}, + servicesProvided = { + DebuggerConsoleService.class, + }) +public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleService { + protected static final String APPENDER_NAME = "debuggerAppender"; + + protected class ConsolePluginAppender extends AbstractAppender { + + public ConsolePluginAppender() { + super(APPENDER_NAME, null, null, true, Property.EMPTY_ARRAY); + + addFilter(LevelRangeFilter.createFilter(Level.FATAL, Level.INFO, null, null)); + } + + @Override + public void append(LogEvent event) { + String loggerName = event.getLoggerName(); + if (loggerName.contains(".debug") || + loggerName.contains(".dbg.") || + loggerName.contains("agent.")) { + provider.logEvent(event); + } + } + } + + protected DebuggerConsoleProvider provider; + + protected final ConsolePluginAppender appender; + + protected Logger rootLogger; + + public DebuggerConsolePlugin(PluginTool tool) { + super(tool); + + appender = new ConsolePluginAppender(); + } + + @Override + protected void init() { + super.init(); + provider = new DebuggerConsoleProvider(this); + + rootLogger = (Logger) LogManager.getRootLogger(); + appender.start(); + rootLogger.addAppender(appender); + } + + @Override + protected void dispose() { + if (rootLogger != null) { + rootLogger.removeAppender(appender); + appender.stop(); + + provider.dispose(); + tool.removeComponentProvider(provider); + } + super.dispose(); + } + + @Override + public void log(Icon icon, String message, ActionContext context) { + provider.log(icon, message, context); + } + + @Override + public void remove(ActionContext context) { + provider.remove(context); + } + + @Override + public void addResolutionAction(DockingActionIf action) { + provider.addResolutionAction(action); + } + + @Override + public void removeResolutionAction(DockingActionIf action) { + provider.removeResolutionAction(action); + } + + /** + * For testing: get the number of rows having a given class of action context + * + * @param ctxCls the context class + */ + public long getRowCount(Class ctxCls) { + return provider.getRowCount(ctxCls); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java new file mode 100644 index 0000000000..6c0828b153 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java @@ -0,0 +1,487 @@ +/* ### + * 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.console; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.TableModelEvent; +import javax.swing.table.*; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; + +import docking.*; +import docking.action.DockingAction; +import docking.action.DockingActionIf; +import docking.actions.PopupActionProvider; +import docking.widgets.table.ColumnSortState.SortDirection; +import docking.widgets.table.CustomToStringCellRenderer; +import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; +import ghidra.app.plugin.core.debug.DebuggerPluginPackage; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.ClearAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectNoneAction; +import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel; +import ghidra.framework.options.AutoOptions; +import ghidra.framework.options.annotation.*; +import ghidra.framework.plugintool.AutoService; +import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.util.*; +import ghidra.util.table.GhidraTable; +import ghidra.util.table.GhidraTableFilterPanel; + +public class DebuggerConsoleProvider extends ComponentProviderAdapter + implements PopupActionProvider { + static final int ACTION_BUTTON_SIZE = 32; + static final Dimension ACTION_BUTTON_DIM = + new Dimension(ACTION_BUTTON_SIZE, ACTION_BUTTON_SIZE); + static final int MAX_ROW_HEIGHT = 300; + + protected enum LogTableColumns implements EnumeratedTableColumn { + LEVEL("Level", Icon.class, LogRow::getIcon, SortDirection.ASCENDING, false), + MESSAGE("Message", String.class, LogRow::getMessage, SortDirection.ASCENDING, false), + ACTIONS("Actions", ActionList.class, LogRow::getActions, SortDirection.DESCENDING, true), + TIME("Time", Date.class, LogRow::getDate, SortDirection.DESCENDING, false); + + private final String header; + private final Function getter; + private final Class cls; + private final SortDirection defaultSortDirection; + private final boolean editable; + + LogTableColumns(String header, Class cls, Function getter, + SortDirection defaultSortDirection, boolean editable) { + this.header = header; + this.cls = cls; + this.getter = getter; + this.defaultSortDirection = defaultSortDirection; + this.editable = editable; + } + + @Override + public String getHeader() { + return header; + } + + @Override + public Class getValueClass() { + return cls; + } + + @Override + public Object getValueOf(LogRow row) { + return getter.apply(row); + } + + @Override + public boolean isEditable(LogRow row) { + return editable; + } + + @Override + public void setValueOf(LogRow row, Object value) { + } + + @Override + public SortDirection defaultSortDirection() { + return defaultSortDirection; + } + } + + protected static class BoundAction { + protected final DockingActionIf action; + protected final ActionContext context; + + public BoundAction(DockingActionIf action, ActionContext context) { + this.action = action; + this.context = context; + } + + @Override + public String toString() { + return getName(); + } + + public String getName() { + return action.getName(); + } + + public Icon getIcon() { + return action.getToolBarData().getIcon(); + } + + public boolean isEnabled() { + return action.isEnabledForContext(context); + } + + public String getTooltipText() { + return action.getDescription(); + } + + public void perform() { + action.actionPerformed(context); + } + } + + protected static class ActionList extends ArrayList { + } + + protected static class LogRow { + private final Icon icon; + private final String message; + private final Date date; + private final ActionContext context; + private final ActionList actions; + + public LogRow(Icon icon, String message, Date date, ActionContext context, + ActionList actions) { + this.icon = icon; + this.message = message; + this.date = date; + this.context = context; + this.actions = actions; + } + + public Icon getIcon() { + return icon; + } + + public String getMessage() { + return message; + } + + public Date getDate() { + return date; + } + + public ActionContext getActionContext() { + return context; + } + + public ActionList getActions() { + return actions; + } + } + + protected static class LogTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< // + LogTableColumns, ActionContext, LogRow, LogRow> { + + public LogTableModel() { + super("Log", LogTableColumns.class, r -> r.getActionContext(), r -> r); + } + + @Override + public java.util.List defaultSortOrder() { + return java.util.List.of(LogTableColumns.ACTIONS, LogTableColumns.TIME); + } + } + + protected static class LogTable extends GhidraTable { + + public LogTable(LogTableModel model) { + super(model); + } + + @Override + public void tableChanged(TableModelEvent e) { + super.tableChanged(e); + Swing.runIfSwingOrRunLater(() -> updateRowHeights()); + } + + @Override + public void columnMarginChanged(ChangeEvent e) { + super.columnMarginChanged(e); + // TODO: Debounce or otherwise delay this + Swing.runIfSwingOrRunLater(() -> updateRowHeights()); + } + + protected void updateRowHeights() { + // TODO: Be more selective in which rows + // Those changed + // Those visible? + TableModel model = getModel(); + int rows = model.getRowCount(); + int cols = getColumnCount(); + for (int r = 0; r < rows; r++) { + int height = 0; + for (int c = 0; c < cols; c++) { + height = Math.max(height, computePreferredHeight(r, c)); + } + setRowHeight(r, height); + } + } + + protected int computePreferredHeight(int r, int c) { + TableCellRenderer renderer = getCellRenderer(r, c); + if (renderer instanceof ConsoleActionsCellRenderer) { + ActionList actions = (ActionList) getModel().getValueAt(r, c); + if (!actions.isEmpty()) { + return ACTION_BUTTON_SIZE; + } + return 0; + } + if (renderer instanceof CustomToStringCellRenderer) { + CustomToStringCellRenderer custom = (CustomToStringCellRenderer) renderer; + int colWidth = getColumnModel().getColumn(c).getWidth(); + prepareRenderer(renderer, r, c); + return custom.getRowHeight(colWidth); + } + return 0; + } + } + + private final DebuggerConsolePlugin plugin; + + @SuppressWarnings("unused") + private final AutoService.Wiring autoServiceWiring; + + @AutoOptionDefined( + name = DebuggerResources.OPTION_NAME_LOG_BUFFER_LIMIT, + description = "The maximum number of entries in the console log (0 or less for unlimited)", + help = @HelpInfo(anchor = "buffer_limit")) + private int logBufferLimit = DebuggerResources.DEFAULT_LOG_BUFFER_LIMIT; + @SuppressWarnings("unused") + private final AutoOptions.Wiring autoOptionsWiring; + + protected final Map> actionsByOwnerThenName = + new LinkedHashMap<>(); + + protected final LogTableModel logTableModel = new LogTableModel(); + protected GhidraTable logTable; + private GhidraTableFilterPanel logFilterPanel; + + private Deque buffer = new ArrayDeque<>(); + + private final JPanel mainPanel = new JPanel(new BorderLayout()); + + DockingAction actionClear; + DockingAction actionSelectNone; + + public DebuggerConsoleProvider(DebuggerConsolePlugin plugin) { + super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName()); + this.plugin = plugin; + + tool.addPopupActionProvider(this); + + setIcon(DebuggerResources.ICON_PROVIDER_CONSOLE); + setHelpLocation(DebuggerResources.HELP_PROVIDER_CONSOLE); + setWindowMenuGroup(DebuggerPluginPackage.NAME); + + buildMainPanel(); + + this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); + this.autoOptionsWiring = AutoOptions.wireOptions(plugin, this); + + setDefaultWindowPosition(WindowPosition.BOTTOM); + setVisible(true); + createActions(); + } + + protected void dispose() { + tool.removePopupActionProvider(this); + } + + protected void buildMainPanel() { + logTable = new LogTable(logTableModel); + logTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + mainPanel.add(new JScrollPane(logTable)); + logFilterPanel = new GhidraTableFilterPanel<>(logTable, logTableModel); + mainPanel.add(logFilterPanel, BorderLayout.NORTH); + + logTable.setRowHeight(ACTION_BUTTON_SIZE); + TableColumnModel columnModel = logTable.getColumnModel(); + + TableColumn levelCol = columnModel.getColumn(LogTableColumns.LEVEL.ordinal()); + levelCol.setMaxWidth(24); + levelCol.setMinWidth(24); + + TableColumn msgCol = columnModel.getColumn(LogTableColumns.MESSAGE.ordinal()); + msgCol.setPreferredWidth(150); + msgCol.setCellRenderer(CustomToStringCellRenderer.HTML); + + TableColumn actCol = columnModel.getColumn(LogTableColumns.ACTIONS.ordinal()); + actCol.setPreferredWidth(50); + actCol.setCellRenderer(new ConsoleActionsCellRenderer()); + actCol.setCellEditor(new ConsoleActionsCellEditor()); + + TableColumn timeCol = columnModel.getColumn(LogTableColumns.TIME.ordinal()); + timeCol.setCellRenderer(CustomToStringCellRenderer.TIME_24HMSms); + timeCol.setPreferredWidth(15); + } + + protected void createActions() { + actionClear = ClearAction.builder(plugin) + .onAction(this::activatedClear) + .buildAndInstallLocal(this); + actionSelectNone = SelectNoneAction.builder(plugin) + .popupWhen(ctx -> ctx.getSourceComponent() == logTable) + .onAction(this::activatedSelectNone) + .buildAndInstallLocal(this); + } + + private void activatedClear(ActionContext ctx) { + synchronized (buffer) { + logTableModel.clear(); + buffer.clear(); + } + } + + private void activatedSelectNone(ActionContext ctx) { + logTable.clearSelection(); + } + + @Override + public ActionContext getActionContext(MouseEvent event) { + if (logTable.getSelectedRowCount() != 1) { + return super.getActionContext(event); + } + LogRow sel = logFilterPanel.getSelectedItem(); + if (sel == null) { + // I guess this can happen because of timing? + return super.getActionContext(event); + } + return sel.getActionContext(); + } + + @AutoOptionConsumed(name = DebuggerResources.OPTION_NAME_LOG_BUFFER_LIMIT) + private void setLogBufferLimit(int logBufferLimit) { + truncateLog(); + } + + @Override + public JComponent getComponent() { + return mainPanel; + } + + protected void truncateLog() { + synchronized (buffer) { + while (logBufferLimit > 0 && buffer.size() > logBufferLimit) { + logTableModel.deleteItem(buffer.removeFirst()); + } + } + } + + protected void log(Icon icon, String message, ActionContext context) { + logRow(new LogRow(icon, message, new Date(), context, computeToolbarActions(context))); + } + + protected void logRow(LogRow row) { + synchronized (buffer) { + LogRow old = logTableModel.deleteKey(row.getActionContext()); + if (old != null) { + buffer.remove(old); + } + logTableModel.addItem(row); + buffer.addLast(row); + truncateLog(); + } + //logTable.scrollRectToVisible(new Rectangle(0, Integer.MAX_VALUE - 1, 1, 1)); + } + + protected Icon iconForLevel(Level level) { + if (level == Level.FATAL) { + return DebuggerResources.ICON_LOG_FATAL; + } + else if (level == Level.ERROR) { + return DebuggerResources.ICON_LOG_ERROR; + } + else if (level == Level.WARN) { + return DebuggerResources.ICON_LOG_WARN; + } + return null; + } + + protected void logEvent(LogEvent event) { + ActionContext context = new LogRowConsoleActionContext(); + logRow(new LogRow(iconForLevel(event.getLevel()), + "" + HTMLUtilities.escapeHTML(event.getMessage().getFormattedMessage()), + new Date(event.getTimeMillis()), context, computeToolbarActions(context))); + } + + protected void remove(ActionContext context) { + synchronized (buffer) { + LogRow r = logTableModel.deleteKey(context); + buffer.remove(r); + } + } + + protected void addResolutionAction(DockingActionIf action) { + DockingActionIf replaced = + actionsByOwnerThenName.computeIfAbsent(action.getOwner(), o -> new LinkedHashMap<>()) + .put(action.getName(), action); + if (replaced != null) { + Msg.warn(this, "Duplicate resolution action registered: " + action.getFullName()); + } + } + + protected void removeResolutionAction(DockingActionIf action) { + Map byName = actionsByOwnerThenName.get(action.getOwner()); + if (byName == null) { + Msg.warn(this, "Action to remove was never added: " + action.getFullName()); + return; + } + DockingActionIf removed = byName.get(action.getName()); + if (removed != action) { + if (removed != null) { + Msg.warn(this, + "Action to remove did not match that added: " + action.getFullName()); + } + else { + Msg.warn(this, "Action to removed was never added: " + action.getFullName()); + } + return; + } + if (byName.isEmpty()) { + actionsByOwnerThenName.remove(action.getOwner()); + } + } + + protected Stream streamActions(ActionContext context) { + return actionsByOwnerThenName.values() + .stream() + .flatMap(m -> m.values().stream()) + .filter(a -> a.isValidContext(context)); + } + + protected ActionList computeToolbarActions(ActionContext context) { + return streamActions(context) + .filter(a -> a.getToolBarData() != null) + .map(a -> new BoundAction(a, context)) + .collect(Collectors.toCollection(ActionList::new)); + } + + @Override + public java.util.List getPopupActions(Tool tool, ActionContext context) { + return streamActions(context) + .filter(a -> a.isAddToPopup(context)) + .collect(Collectors.toList()); + } + + protected long getRowCount(Class ctxCls) { + return logTableModel.getModelData() + .stream() + .filter(r -> ctxCls.isInstance(r.context)) + .count(); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/LogRowConsoleActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/LogRowConsoleActionContext.java new file mode 100644 index 0000000000..db0658b1c0 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/LogRowConsoleActionContext.java @@ -0,0 +1,22 @@ +/* ### + * 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.console; + +import docking.ActionContext; + +public class LogRowConsoleActionContext extends ActionContext { + +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/interpreters/DebuggerInterpreterPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/interpreters/DebuggerInterpreterPlugin.java index 21fa362e84..3e09ff8168 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/interpreters/DebuggerInterpreterPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/interpreters/DebuggerInterpreterPlugin.java @@ -31,19 +31,18 @@ import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; -@PluginInfo( // - shortDescription = "Debugger interpreter panel service", // - description = "Manage interpreter panels within debug sessions", // - category = PluginCategoryNames.DEBUGGER, // - packageName = DebuggerPluginPackage.NAME, // - status = PluginStatus.RELEASED, // - servicesRequired = { // - InterpreterPanelService.class // - }, // +@PluginInfo( + shortDescription = "Debugger interpreter panel service", + description = "Manage interpreter panels within debug sessions", + category = PluginCategoryNames.DEBUGGER, + packageName = DebuggerPluginPackage.NAME, + status = PluginStatus.RELEASED, + servicesRequired = { + InterpreterPanelService.class, + }, servicesProvided = { - DebuggerInterpreterService.class // - } // -) + DebuggerInterpreterService.class, + }) public class DebuggerInterpreterPlugin extends AbstractDebuggerPlugin implements DebuggerInterpreterService { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java index f4ff8ca1b3..39dd501520 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java @@ -342,7 +342,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger //cbGoTo.invoke(() -> { DebuggerListingProvider provider = getConnectedProvider(); provider.doSyncToStatic(location); - provider.doAutoImportCurrentModule(); + provider.doCheckCurrentModuleMissing(); //}); return true; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java index 932fc64903..cdc6637163 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProvider.java @@ -19,7 +19,6 @@ import static ghidra.app.plugin.core.debug.gui.DebuggerResources.ICON_REGISTER_M import static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS; import java.awt.Color; -import java.io.File; import java.lang.invoke.MethodHandles; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -50,6 +49,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec; import ghidra.app.plugin.core.debug.gui.action.LocationTrackingSpec.TrackingSpecConfigFieldCodec; +import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.plugin.core.debug.utils.BackgroundUtils; import ghidra.app.plugin.core.debug.utils.ProgramURLUtils; import ghidra.app.plugin.core.exporter.ExporterDialog; @@ -85,8 +85,7 @@ import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.util.TraceAddressSpace; -import ghidra.util.Msg; -import ghidra.util.Swing; +import ghidra.util.*; import utilities.util.SuppressableCallback; import utilities.util.SuppressableCallback.Suppression; @@ -323,6 +322,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi //@AutoServiceConsumed via method private DebuggerStaticMappingService mappingService; @AutoServiceConsumed + private DebuggerConsoleService consoleService; + @AutoServiceConsumed private ProgramManager programManager; @AutoServiceConsumed private FileImporterService importerService; @@ -349,12 +350,10 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi protected MultiStateDockingAction actionTrackLocation; protected DockingAction actionGoTo; protected SyncToStaticListingAction actionSyncToStaticListing; - protected ToggleDockingAction actionAutoImportCurrentModule; protected FollowsCurrentThreadAction actionFollowsCurrentThread; protected MultiStateDockingAction actionAutoReadMemory; protected DockingAction actionExportView; - protected final DebuggerModuleImportDialog importDialog; protected final DebuggerGoToDialog goToDialog; @AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class) @@ -362,8 +361,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi @AutoConfigStateField protected boolean syncToStaticListing; @AutoConfigStateField - protected boolean autoImportCurrentModule; - @AutoConfigStateField protected boolean followsCurrentThread = true; @AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class) protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec; @@ -389,7 +386,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi super(plugin, formatManager, isConnected); this.plugin = plugin; - importDialog = new DebuggerModuleImportDialog(tool); goToDialog = new DebuggerGoToDialog(this); ListingPanel listingPanel = getListingPanel(); @@ -403,7 +399,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi autoOptionsWiring = AutoOptions.wireOptionsConsumed(plugin, this); syncToStaticListing = isConnected; - autoImportCurrentModule = isConnected; setVisible(true); createActions(); @@ -473,12 +468,10 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi actionTrackLocation.setCurrentActionStateByUserData(trackingSpec); if (isConnected()) { actionSyncToStaticListing.setSelected(syncToStaticListing); - actionAutoImportCurrentModule.setSelected(autoImportCurrentModule); followsCurrentThread = true; } else { syncToStaticListing = false; - autoImportCurrentModule = false; actionFollowsCurrentThread.setSelected(followsCurrentThread); updateBorder(); } @@ -744,11 +737,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi if (isConnected()) { actionSyncToStaticListing = new SyncToStaticListingAction(); - actionAutoImportCurrentModule = AutoImportCurrentModuleAction.builder(plugin) - .enabledWhen(ctx -> syncToStaticListing) - .onAction(this::autoImportCurrentModuleActionToggled) - .selected(autoImportCurrentModule) - .buildAndInstallLocal(this); } else { actionFollowsCurrentThread = new FollowsCurrentThreadAction(); @@ -803,10 +791,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi doSetTrackingSpec(newState.getUserData()); } - protected void autoImportCurrentModuleActionToggled(ActionContext ctx) { - doSetAutoImportCurrentModule(actionAutoImportCurrentModule.isSelected()); - } - protected void activatedAutoReadMemory(ActionContext ctx) { doAutoReadMemory(); } @@ -891,7 +875,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi super.programLocationChanged(location, trigger); if (trigger == EventTrigger.GUI_ACTION) { doSyncToStatic(location); - doAutoImportCurrentModule(); + doCheckCurrentModuleMissing(); } } @@ -928,11 +912,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } - protected void doAutoImportCurrentModule() { - if (!autoImportCurrentModule) { - return; - } - if (importerService == null) { + protected void doCheckCurrentModuleMissing() { + if (importerService == null || consoleService == null) { return; } Trace trace = current.getTrace(); @@ -962,8 +943,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi } } - Map missing = new LinkedHashMap<>(); - Set toOpen = new LinkedHashSet<>(); + Set missing = new HashSet<>(); + Set toOpen = new HashSet<>(); TraceModuleManager modMan = trace.getModuleManager(); Collection modules = Stream.concat( modMan.getModulesAt(snap, address).stream().filter(m -> m.getSections().isEmpty()), @@ -975,7 +956,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi for (TraceModule mod : modules) { Set matches = mappingService.findProbableModulePrograms(mod); if (matches.isEmpty()) { - missing.put(mod, new File(mod.getName())); + missing.add(mod); } else { toOpen.addAll(matches); @@ -988,7 +969,12 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi ProgramManager.OPEN_VISIBLE); } } - importDialog.addFiles(missing.values()); + for (TraceModule mod : missing) { + consoleService.log(DebuggerResources.ICON_LOG_ERROR, + "The module " + HTMLUtilities.escapeHTML(mod.getName()) + + " was not found in the project", + new DebuggerMissingModuleActionContext(mod)); + } /** * Once the programs are opened, including those which are successfully imported, the * section mapper should take over, eventually invoking callbacks to our mapping change @@ -1027,11 +1013,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi doSyncToStatic(getLocation()); } - protected void doSetAutoImportCurrentModule(boolean autoImport) { - this.autoImportCurrentModule = autoImport; - doAutoImportCurrentModule(); - } - public boolean isSyncToStaticListing() { return syncToStaticListing; } @@ -1117,13 +1098,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi if (!syncToStaticListing || trackedStatic == null) { Swing.runIfSwingOrRunLater(() -> { goTo(curView, loc); - doAutoImportCurrentModule(); + doCheckCurrentModuleMissing(); }); } else { Swing.runIfSwingOrRunLater(() -> { goTo(curView, loc); - doAutoImportCurrentModule(); + doCheckCurrentModuleMissing(); plugin.fireStaticLocationEvent(trackedStatic); }); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerModuleImportDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerModuleImportDialog.java deleted file mode 100644 index 870984f473..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerModuleImportDialog.java +++ /dev/null @@ -1,251 +0,0 @@ -/* ### - * 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.listing; - -import java.awt.BorderLayout; -import java.io.File; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Function; - -import javax.swing.*; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; - -import docking.DialogComponentProvider; -import docking.widgets.filechooser.GhidraFileChooser; -import docking.widgets.table.CellEditorUtils; -import docking.widgets.table.DefaultEnumeratedColumnTableModel; -import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; -import ghidra.app.plugin.core.debug.gui.DebuggerResources; -import ghidra.app.services.FileImporterService; -import ghidra.framework.main.AppInfo; -import ghidra.framework.model.DomainFolder; -import ghidra.framework.model.Project; -import ghidra.framework.plugintool.PluginTool; -import ghidra.util.MessageType; -import ghidra.util.table.GhidraTable; -import ghidra.util.table.GhidraTableFilterPanel; - -public class DebuggerModuleImportDialog extends DialogComponentProvider { - static final String BLANK = ""; - static final int BUTTON_SIZE = 32; - - protected static class FileRow { - private final File file; - private boolean isIgnored; - - public FileRow(File file) { - this.file = file; - } - - public File getFile() { - return file; - } - - public boolean isIgnored() { - return isIgnored; - } - - public void setIgnored(boolean isIgnored) { - this.isIgnored = isIgnored; - } - } - - protected static enum FileTableColumns - implements EnumeratedTableColumn { - REMOVE("Remove", String.class, m -> BLANK, (m, v) -> nop()), - IGNORE("Ignore", Boolean.class, FileRow::isIgnored, FileRow::setIgnored), - PATH("Path", File.class, FileRow::getFile), - IMPORT("Import", String.class, m -> BLANK, (m, v) -> nop()); - - private String header; - private Class cls; - private Function getter; - private BiConsumer setter; - - private static void nop() { - } - - @SuppressWarnings("unchecked") - FileTableColumns(String header, Class cls, - Function getter, BiConsumer setter) { - this.header = header; - this.cls = cls; - this.getter = getter; - this.setter = (BiConsumer) setter; - } - - FileTableColumns(String header, Class cls, - Function getter) { - this(header, cls, getter, null); - } - - @Override - public String getHeader() { - return header; - } - - @Override - public Class getValueClass() { - return cls; - } - - @Override - public Object getValueOf(FileRow row) { - return getter.apply(row); - } - - @Override - public boolean isEditable(FileRow row) { - return setter != null; - } - - @Override - public void setValueOf(FileRow row, Object value) { - setter.accept(row, value); - } - } - - protected static class FileTableModel - extends DefaultEnumeratedColumnTableModel { - public FileTableModel() { - super("Suggested Files to Import", FileTableColumns.class); - } - } - - private final PluginTool tool; - - final FileTableModel fileTableModel = new FileTableModel(); - private final Map map = new HashMap<>(); - - private GhidraTable fileTable; - private GhidraTableFilterPanel fileFilterPanel; - - protected DebuggerModuleImportDialog(PluginTool tool) { - super("Suggested Modules to Import", false, true, false, false); - this.tool = tool; - - populateComponents(); - } - - protected void populateComponents() { - JPanel panel = new JPanel(new BorderLayout()); - - fileTable = new GhidraTable(fileTableModel); - fileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - panel.add(new JScrollPane(fileTable)); - - fileFilterPanel = new GhidraTableFilterPanel<>(fileTable, fileTableModel); - panel.add(fileFilterPanel, BorderLayout.SOUTH); - - TableColumnModel columnModel = fileTable.getColumnModel(); - - TableColumn removeCol = columnModel.getColumn(FileTableColumns.REMOVE.ordinal()); - CellEditorUtils.installButton(fileTable, fileFilterPanel, removeCol, - DebuggerResources.ICON_DELETE, BUTTON_SIZE, this::removeFile); - - TableColumn ignoreCol = columnModel.getColumn(FileTableColumns.IGNORE.ordinal()); - ignoreCol.setPreferredWidth(30); - - TableColumn importCol = columnModel.getColumn(FileTableColumns.IMPORT.ordinal()); - CellEditorUtils.installButton(fileTable, fileFilterPanel, importCol, - DebuggerResources.ICON_IMPORT, BUTTON_SIZE, this::importFile); - - addWorkPanel(panel); - } - - private void importFile(FileRow mod) { - FileImporterService importerService = tool.getService(FileImporterService.class); - if (importerService == null) { - setStatusText("No FileImporterService!", MessageType.ERROR); - return; - } - GhidraFileChooser chooser = new GhidraFileChooser(getComponent()); - chooser.setSelectedFile(mod.getFile()); - File file = chooser.getSelectedFile(); // Shows modal - if (file == null) { // Includes cancelled case - return; - } - Project activeProject = Objects.requireNonNull(AppInfo.getActiveProject()); - DomainFolder root = activeProject.getProjectData().getRootFolder(); - importerService.importFile(root, file); - removeFile(mod); - } - - private void removeFile(FileRow mod) { - removeFiles(Set.of(mod.getFile())); - } - - public void show() { - tool.showDialog(this); - } - - /** - * Suggest files to import. - * - *

- * If this causes a change to the suggested file list, or the list is not currently showing, the - * dialog will be shown. The user may leave the list in the background to avoid being pestered - * again. - * - * @param files the collection of files to suggest importing - */ - public void addFiles(Collection files) { - synchronized (map) { - List mods = new ArrayList<>(); - for (File file : files) { - map.computeIfAbsent(file, f -> { - FileRow mod = new FileRow(f); - mods.add(mod); - return mod; - }); - } - fileTableModel.addAll(mods); - // Do not steal focus if suggested files are already on screen, or ignored - boolean anyNotIgnored = - fileTableModel.getModelData().stream().anyMatch(r -> !r.isIgnored()); - if (!mods.isEmpty() || (!isShowing() && anyNotIgnored)) { - show(); - } - } - } - - /** - * Remove suggested files from the dialog. - * - *

- * If this causes the list to become empty, the dialog is automatically hidden. - * - * @param files the collection of files to no longer suggest - */ - public void removeFiles(Collection files) { - synchronized (map) { - Set mods = new HashSet<>(); - for (File file : files) { - FileRow mod = map.remove(file); - if (mod != null) { - mods.add(mod); - } - } - fileTableModel.deleteWith(mods::contains); - - if (fileTableModel.getModelData().isEmpty()) { - close(); - } - } - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerMissingModuleActionContext.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerMissingModuleActionContext.java new file mode 100644 index 0000000000..86c31cc0fb --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerMissingModuleActionContext.java @@ -0,0 +1,55 @@ +/* ### + * 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.modules; + +import java.util.Objects; + +import docking.ActionContext; +import ghidra.trace.model.modules.TraceModule; + +public class DebuggerMissingModuleActionContext extends ActionContext { + private final TraceModule module; + private final int hashCode; + + public DebuggerMissingModuleActionContext(TraceModule module) { + this.module = Objects.requireNonNull(module); + this.hashCode = Objects.hash(getClass(), module); + } + + public TraceModule getModule() { + return module; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DebuggerMissingModuleActionContext)) { + return false; + } + DebuggerMissingModuleActionContext that = (DebuggerMissingModuleActionContext) obj; + if (!this.module.equals(that.module)) { + return false; + } + return true; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java index cdf0674a13..4bd84e7aea 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesPlugin.java @@ -25,23 +25,23 @@ import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @PluginInfo( // - shortDescription = "Debugger module and section manager", // - description = "GUI to manage modules and sections", // - category = PluginCategoryNames.DEBUGGER, // - packageName = DebuggerPluginPackage.NAME, // - status = PluginStatus.RELEASED, // - eventsConsumed = { - ProgramActivatedPluginEvent.class, // - ProgramLocationPluginEvent.class, // - ProgramClosedPluginEvent.class, // - TraceActivatedPluginEvent.class, // - }, // - servicesRequired = { // - DebuggerModelService.class, // - DebuggerStaticMappingService.class, // - DebuggerTraceManagerService.class, // - ProgramManager.class, // - } // + shortDescription = "Debugger module and section manager", // + description = "GUI to manage modules and sections", // + category = PluginCategoryNames.DEBUGGER, // + packageName = DebuggerPluginPackage.NAME, // + status = PluginStatus.RELEASED, // + eventsConsumed = { + ProgramActivatedPluginEvent.class, // + ProgramLocationPluginEvent.class, // + ProgramClosedPluginEvent.class, // + TraceActivatedPluginEvent.class, // + }, // + servicesRequired = { // + DebuggerModelService.class, // + DebuggerStaticMappingService.class, // + DebuggerTraceManagerService.class, // + ProgramManager.class, // + } // ) public class DebuggerModulesPlugin extends AbstractDebuggerPlugin { protected DebuggerModulesProvider provider; @@ -58,6 +58,7 @@ public class DebuggerModulesPlugin extends AbstractDebuggerPlugin { @Override protected void dispose() { + provider.dispose(); tool.removeComponentProvider(provider); super.dispose(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java index 146041c632..a7cfc0bd23 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java @@ -458,15 +458,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { return; } TraceModule mod = modules.iterator().next(); - GhidraFileChooser chooser = new GhidraFileChooser(getComponent()); - chooser.setSelectedFile(new File(mod.getName())); - File file = chooser.getSelectedFile(); - if (file == null) { // Perhaps cancelled - return; - } - Project activeProject = Objects.requireNonNull(AppInfo.getActiveProject()); - DomainFolder root = activeProject.getProjectData().getRootFolder(); - importerService.importFile(root, file); + importModuleFromFileSystem(mod); } @Override @@ -508,6 +500,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { @AutoServiceConsumed private DebuggerListingService listingService; @AutoServiceConsumed + private DebuggerConsoleService consoleService; + @AutoServiceConsumed ProgramManager programManager; @AutoServiceConsumed private GoToService goToService; @@ -551,6 +545,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { DockingAction actionMapSectionTo; DockingAction actionMapSectionsTo; + DockingAction actionImportMissingModule; + DockingAction actionMapMissingModule; + SelectAddressesAction actionSelectAddresses; CaptureTypesAction actionCaptureTypes; CaptureSymbolsAction actionCaptureSymbols; @@ -580,6 +577,18 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { createActions(); } + private void importModuleFromFileSystem(TraceModule module) { + GhidraFileChooser chooser = new GhidraFileChooser(getComponent()); + chooser.setSelectedFile(new File(module.getName())); + File file = chooser.getSelectedFile(); + if (file == null) { // Perhaps cancelled + return; + } + Project activeProject = Objects.requireNonNull(AppInfo.getActiveProject()); + DomainFolder root = activeProject.getProjectData().getRootFolder(); + importerService.importFile(root, file); + } + @AutoServiceConsumed private void setModelService(DebuggerModelService modelService) { if (this.modelService != null) { @@ -592,6 +601,29 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { contextChanged(); } + @AutoServiceConsumed + private void setConsoleService(DebuggerConsoleService consoleService) { + if (consoleService != null) { + if (actionImportMissingModule != null) { + consoleService.addResolutionAction(actionImportMissingModule); + } + if (actionMapMissingModule != null) { + consoleService.addResolutionAction(actionMapMissingModule); + } + } + } + + protected void dispose() { + if (consoleService != null) { + if (actionImportMissingModule != null) { + consoleService.removeResolutionAction(actionImportMissingModule); + } + if (actionMapMissingModule != null) { + consoleService.removeResolutionAction(actionMapMissingModule); + } + } + } + @Override public ActionContext getActionContext(MouseEvent event) { if (myActionContext == null) { @@ -743,6 +775,15 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { .onAction(this::activatedMapSectionsTo) .buildAndInstallLocal(this); + actionImportMissingModule = ImportMissingModuleAction.builder(plugin) + .withContext(DebuggerMissingModuleActionContext.class) + .onAction(this::activatedImportMissingModule) + .build(); + actionMapMissingModule = MapMissingModuleAction.builder(plugin) + .withContext(DebuggerMissingModuleActionContext.class) + .onAction(this::activatedMapMissingModule) + .build(); + actionSelectAddresses = new SelectAddressesAction(); actionCaptureTypes = new CaptureTypesAction(); actionCaptureSymbols = new CaptureSymbolsAction(); @@ -818,6 +859,19 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { mapSectionTo(sel.iterator().next()); } + private void activatedImportMissingModule(DebuggerMissingModuleActionContext context) { + if (importerService == null) { + Msg.error(this, "Import service is not present"); + } + importModuleFromFileSystem(context.getModule()); + consoleService.remove(context); // TODO: Should remove when mapping is created + } + + private void activatedMapMissingModule(DebuggerMissingModuleActionContext context) { + mapModuleTo(context.getModule()); + consoleService.remove(context); // TODO: Should remove when mapping is created + } + private void toggledFilter(ActionContext ignored) { if (actionFilterSectionsByModules.isSelected()) { sectionFilterPanel.setSecondaryFilter(filterSectionsBySelectedModules); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java index 4243d8b94c..38fe49708d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java @@ -159,14 +159,14 @@ public class DebuggerModelServicePlugin extends Plugin protected class ListenerOnRecorders implements TraceRecorderListener { @Override public void snapAdvanced(TraceRecorder recorder, long snap) { - TimedMsg.info(this, "Got snapAdvanced callback"); + TimedMsg.debug(this, "Got snapAdvanced callback"); fireSnapEvent(recorder, snap); List copy; synchronized (proxies) { copy = List.copyOf(proxies); } for (DebuggerModelServiceProxyPlugin proxy : copy) { - TimedMsg.info(this, "Firing SnapEvent on " + proxy); + TimedMsg.debug(this, "Firing SnapEvent on " + proxy); proxy.fireSnapEvent(recorder, snap); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java index 7adfeef18f..cfc9d349f8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultThreadRecorder.java @@ -264,7 +264,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { int frameLevel = stackRecorder.getSuccessorFrameLevel(bank); long snap = recorder.getSnap(); String path = bank.getJoinedPath("."); - TimedMsg.info(this, "Reg values changed: " + updates.keySet()); + TimedMsg.debug(this, "Reg values changed: " + updates.keySet()); recorder.parTx.execute("Registers " + path + " changed", () -> { TraceCodeManager codeManager = trace.getCodeManager(); TraceCodeRegisterSpace codeRegisterSpace = @@ -377,7 +377,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder { if (targetRange == null) { return AsyncUtils.NIL; } - TimedMsg.info(this, + TimedMsg.debug(this, " Reading memory at " + name + " (" + targetAddress + " -> " + targetRange + ")"); // NOTE: Recorder takes data via memoryUpdated callback // TODO: In that callback, sort out process memory from thread memory? diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java index a589345113..1d2f5ffd4d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceEventListener.java @@ -119,7 +119,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener { if (!valid) { return; } - TimedMsg.info(this, "Event: " + type + " thread=" + eventThread + " description=" + + TimedMsg.debug(this, "Event: " + type + " thread=" + eventThread + " description=" + description + " params=" + parameters); // Just use this to step the snaps. Creation/destruction still handled in add/remove if (eventThread == null) { @@ -162,7 +162,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener { if (!valid) { return; } - TimedMsg.info(this, "State " + state + " for " + stateful); + TimedMsg.debug(this, "State " + state + " for " + stateful); TargetObject x = recorder.objectManager.findThreadOrProcess(stateful); if (x != null) { if (x == target && state == TargetExecutionState.TERMINATED) { @@ -204,7 +204,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener { } Address traceAddr = recorder.getMemoryMapper().targetToTrace(address); long snap = recorder.getSnap(); - TimedMsg.info(this, "Memory updated: " + address + " (" + data.length + ")"); + TimedMsg.debug(this, "Memory updated: " + address + " (" + data.length + ")"); String path = memory.getJoinedPath("."); recorder.parTx.execute("Memory observed: " + path, () -> { memoryManager.putBytes(snap, traceAddr, ByteBuffer.wrap(data)); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 9347b14967..f8f2a03f07 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -618,7 +618,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin } else if (event instanceof TraceRecorderAdvancedPluginEvent) { TraceRecorderAdvancedPluginEvent ev = (TraceRecorderAdvancedPluginEvent) event; - TimedMsg.info(this, "Processing trace-advanced event"); + TimedMsg.debug(this, "Processing trace-advanced event"); doTraceRecorderAdvanced(ev.getRecorder(), ev.getSnap()); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerConsoleService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerConsoleService.java new file mode 100644 index 0000000000..ad1654599b --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerConsoleService.java @@ -0,0 +1,87 @@ +/* ### + * 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.services; + +import javax.swing.Icon; + +import docking.ActionContext; +import docking.action.DockingActionIf; +import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin; +import ghidra.dbg.DebuggerConsoleLogger; +import ghidra.framework.plugintool.ServiceInfo; +import ghidra.util.HTMLUtilities; + +@ServiceInfo(defaultProvider = DebuggerConsolePlugin.class) +public interface DebuggerConsoleService extends DebuggerConsoleLogger { + + /** + * Log an actionable message to the console + * + *

+ * WARNING: The log accepts and will interpret HTML in its messages, allowing a rich and + * flexible display; however, you MUST sanitize any content derived from the user or target. We + * recommend using {@link HTMLUtilities#escapeHTML(String)}. + * + * @param icon an icon for the message + * @param message the HTML-formatted message + * @param context an (immutable) context for actions + */ + void log(Icon icon, String message, ActionContext context); + + /** + * Remove an actionable message from the console + * + *

+ * It is common courtesy to remove the entry when the user has resolved the issue, whether via + * the presented actions, or some other means. The framework does not do this automatically, + * because simply activating an action does not imply the issue will be resolved. + * + * @param context the context of the entry to remove + */ + void remove(ActionContext context); + + /** + * Add an action which might be applied to an actionable log message + * + *

+ * Please invoke this method from the Swing thread. Only toolbar and pop-up menu placement is + * considered. Toolbar actions are placed as icon-only buttons in the "Actions" column for any + * log message where the action is applicable to the context given for that message. Pop-up + * actions are placed in the context menu when a single message is selected and the action is + * applicable to its given context. In most cases, the action should be presented both as a + * button and as a pop-up menu. Less commonly, an action may be presented only as a pop-up, + * likely because it is an uncommon resolution, or because you don't want the user to activated + * it accidentally. Rarely, if ever, should an action be a button, but not in the menu. The user + * may expect the menu to give more complete descriptions of actions presented as buttons. + * + *

+ * IMPORTANT: Unlike other action managers, you are required to remove your actions upon + * plugin disposal. + * + * @param action the action + */ + void addResolutionAction(DockingActionIf action); + + /** + * Remove an action + * + *

+ * Please invoke this method from the Swing thread. + * + * @param action the action + */ + void removeResolutionAction(DockingActionIf action); +} diff --git a/Ghidra/Debug/Debugger/src/main/resources/defaultTools/Debugger.tool b/Ghidra/Debug/Debugger/src/main/resources/defaultTools/Debugger.tool index d24e0a8845..37a33dd7a4 100644 --- a/Ghidra/Debug/Debugger/src/main/resources/defaultTools/Debugger.tool +++ b/Ghidra/Debug/Debugger/src/main/resources/defaultTools/Debugger.tool @@ -7,145 +7,154 @@ + - + - - - + + + - + - + - - - - + + + + + - - + + - + - + - - - - - - - - - - - - + + + + + + + + + + + + + + - + - + - + - + - - - - - + + + + - - + + + - + - + - + - + - + - + - + - + - + - + - + + + + + + - + @@ -155,54 +164,26 @@ - + + + + + + - + - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -233,32 +214,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -311,52 +266,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -369,56 +278,15 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + @@ -439,19 +307,6 @@ - - - - - - - - - - - - - @@ -467,15 +322,15 @@ - - + + - - - - - - + + + + + + @@ -483,6 +338,24 @@ + + + + + + + + + + + + + + + + + + @@ -496,38 +369,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -629,12 +470,12 @@ - - - - - - + + + + + + @@ -642,6 +483,430 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -670,41 +935,15 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + @@ -727,23 +966,6 @@ - - - - - - - - - - - - - - - - - @@ -795,54 +1017,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -879,6 +1053,21 @@ + + + + + + + + + + + + + + + diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePluginScreenShots.java new file mode 100644 index 0000000000..e4a8165e28 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsolePluginScreenShots.java @@ -0,0 +1,77 @@ +/* ### + * 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.console; + +import static org.junit.Assert.assertEquals; + +import org.junit.*; +import org.junit.rules.TestName; + +import docking.ActionContext; +import docking.action.builder.ActionBuilder; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.util.Msg; +import help.screenshot.GhidraScreenShotGenerator; + +public class DebuggerConsolePluginScreenShots extends GhidraScreenShotGenerator { + + public static class ScreenShotActionContext extends ActionContext { + } + + DebuggerConsolePlugin consolePlugin; + DebuggerConsoleProvider consoleProvider; + + @Rule + public TestName name = new TestName(); + + @Before + public void setUpMine() throws Throwable { + consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); + consoleProvider = waitForComponentProvider(DebuggerConsoleProvider.class); + + consolePlugin.addResolutionAction(new ActionBuilder("Import", name.getMethodName()) + .toolBarIcon(DebuggerResources.ICON_IMPORT) + .popupMenuIcon(DebuggerResources.ICON_IMPORT) + .popupMenuPath("Map") + .description("Import") + .withContext(ScreenShotActionContext.class) + .onAction(ctx -> Msg.info(this, "Import clicked")) + .build()); + consolePlugin.addResolutionAction(new ActionBuilder("Map", name.getMethodName()) + .toolBarIcon(DebuggerResources.ICON_MODULES) + .popupMenuIcon(DebuggerResources.ICON_MODULES) + .popupMenuPath("Map") + .description("Map") + .withContext(ScreenShotActionContext.class) + .onAction(ctx -> Msg.info(this, "Map clicked")) + .build()); + } + + @Test + public void testCaptureDebuggerConsolePlugin() throws Throwable { + Msg.warn(this, "This is a warning message"); + Msg.error(this, "This is an error message"); + consolePlugin.log(DebuggerResources.ICON_DEBUGGER, + "You can take action to resolve this message", + new ScreenShotActionContext()); + + AbstractGhidraHeadedDebuggerGUITest + .waitForPass(() -> assertEquals(3, consolePlugin.getRowCount(ActionContext.class))); + + captureIsolatedProvider(consoleProvider, 600, 300); + } +} diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java index f70f4dea2c..91a77409ab 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPluginScreenShots.java @@ -31,7 +31,6 @@ import ghidra.test.ToyProgramBuilder; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.memory.TraceMemoryRegisterSpace; -import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.symbol.*; import ghidra.trace.model.thread.TraceThread; import ghidra.util.database.UndoableTransaction; @@ -140,36 +139,4 @@ public class DebuggerListingPluginScreenShots extends GhidraScreenShotGenerator captureDialog(dialog); } - - @Test - public void testCaptureDebuggerModuleImportDialog() throws Throwable { - try (UndoableTransaction tid = tb.startTransaction()) { - long snap = tb.trace.getTimeManager().createSnapshot("First").getKey(); - tb.trace.getMemoryManager() - .addRegion("bash:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), - Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); - tb.trace.getMemoryManager() - .addRegion("libc:.text", Range.atLeast(0L), tb.range(0x7fac0000, 0x7facffff), - Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); - - TraceModule bin = tb.trace.getModuleManager() - .addLoadedModule("/bin/bash", "/bin/bash", - tb.range(0x00400000, 0x0040ffff), snap); - bin.addSection("bash[.text]", tb.range(0x00400000, 0x0040ffff)); - TraceModule lib = tb.trace.getModuleManager() - .addLoadedModule("/lib/libc.so.6", "/lib/libc.so.6", - tb.range(0x7fac0000, 0x7facffff), snap); - lib.addSection("libc[.text]", tb.range(0x7fac0000, 0x7facffff)); - - traceManager.openTrace(tb.trace); - traceManager.activateTrace(tb.trace); - - listingPlugin.goTo(tb.addr(0x7fac1234), true); - waitForSwing(); - listingPlugin.goTo(tb.addr(0x00401234), true); - waitForSwing(); - - captureDialog(DebuggerModuleImportDialog.class); - } - } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProviderTest.java new file mode 100644 index 0000000000..055bebd24d --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProviderTest.java @@ -0,0 +1,78 @@ +/* ### + * 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.console; + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +import docking.ActionContext; +import docking.action.builder.ActionBuilder; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.util.Msg; + +public class DebuggerConsoleProviderTest extends AbstractGhidraHeadedDebuggerGUITest { + DebuggerConsolePlugin consolePlugin; + DebuggerConsoleProvider consoleProvider; + + @Before + public void setUpConsoleProviderTest() throws Exception { + consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); + consoleProvider = waitForComponentProvider(DebuggerConsoleProvider.class); + } + + public static class TestConsoleActionContext extends ActionContext { + + } + + @Test + public void testActions() throws Exception { + consolePlugin.addResolutionAction(new ActionBuilder("Add", name.getMethodName()) + .toolBarIcon(DebuggerResources.ICON_ADD) + .description("Add") + .withContext(TestConsoleActionContext.class) + .onAction(ctx -> Msg.info(this, "Add clicked")) + .build()); + consolePlugin.addResolutionAction(new ActionBuilder("Delete", name.getMethodName()) + .popupMenuIcon(DebuggerResources.ICON_DELETE) + .popupMenuPath("Delete") + .description("Delete") + .withContext(TestConsoleActionContext.class) + .onAction(ctx -> Msg.info(this, "Delete clicked")) + .build()); + + consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "Test message", + new TestConsoleActionContext()); + consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "Test message 2", + new TestConsoleActionContext()); + + waitForPass(() -> assertEquals(2, consoleProvider.logTable.getRowCount())); + } + + @Test + public void testHTMLLabel() throws Exception { + consolePlugin.log(DebuggerResources.ICON_DEBUGGER, + "A rather lengthy test message. " + + "Here's some more text just to prove it!", + new TestConsoleActionContext()); + consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "Test message 2", + new TestConsoleActionContext()); + + waitForPass(() -> assertEquals(2, consoleProvider.logTable.getRowCount())); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index 32f4ff4dbb..902cc2fadd 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -36,6 +36,8 @@ import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction; import ghidra.app.plugin.core.debug.gui.action.*; +import ghidra.app.plugin.core.debug.gui.console.DebuggerConsolePlugin; +import ghidra.app.plugin.core.debug.gui.modules.DebuggerMissingModuleActionContext; import ghidra.app.services.*; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.async.SwingExecutorService; @@ -1078,9 +1080,9 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI } @Test - public void testActionAutoImportCurrentModuleWithSections() throws Exception { + public void testPromptImportCurrentModuleWithSections() throws Exception { addPlugin(tool, ImporterPlugin.class); - assertTrue(listingProvider.actionAutoImportCurrentModule.isEnabled()); + DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); createAndOpenTrace(); try (UndoableTransaction tid = tb.startTransaction()) { @@ -1099,16 +1101,19 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI // In the module, but not in its section listingPlugin.goTo(tb.addr(0x00411234), true); waitForSwing(); - assertFalse(listingProvider.importDialog.isVisible()); + waitForPass(() -> assertEquals(0, + consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class))); listingPlugin.goTo(tb.addr(0x00401234), true); - waitForDialogComponent(DebuggerModuleImportDialog.class); + waitForSwing(); + waitForPass(() -> assertEquals(1, + consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class))); } @Test - public void testActionAutoImportCurrentModuleWithoutSections() throws Exception { + public void testPromptImportCurrentModuleWithoutSections() throws Exception { addPlugin(tool, ImporterPlugin.class); - assertTrue(listingProvider.actionAutoImportCurrentModule.isEnabled()); + DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class); createAndOpenTrace(); try (UndoableTransaction tid = tb.startTransaction()) { @@ -1116,7 +1121,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI .addRegion("bash:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0041ffff), Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); - TraceModule bin = tb.trace.getModuleManager() + tb.trace.getModuleManager() .addLoadedModule("/bin/bash", "/bin/bash", tb.range(0x00400000, 0x0041ffff), 0); @@ -1125,7 +1130,9 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI // In the module, but not in its section listingPlugin.goTo(tb.addr(0x00411234), true); - waitForDialogComponent(DebuggerModuleImportDialog.class); + waitForSwing(); + waitForPass(() -> assertEquals(1, + consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class))); } @Test diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerConsoleLogger.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerConsoleLogger.java new file mode 100644 index 0000000000..95b53b58e4 --- /dev/null +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/DebuggerConsoleLogger.java @@ -0,0 +1,20 @@ +/* ### + * 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.dbg; + +public interface DebuggerConsoleLogger { + +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java index 6f91f6447c..c79a4fea13 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java @@ -165,7 +165,7 @@ public class TraceDomainObjectListener implements DomainObjectListener { for (DomainObjectChangeRecord rec : ev) { if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) { restoredHandler.accept(rec); - TimedMsg.info(this, " Done: OBJECT_RESTORED"); + TimedMsg.debug(this, " Done: OBJECT_RESTORED"); return; } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceTimeViewport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceTimeViewport.java index d91720a553..8a23e06dfb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceTimeViewport.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceTimeViewport.java @@ -328,14 +328,16 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport { @Override public T getTop(Function func) { - synchronized (ordered) { - for (Range rng : ordered) { - T t = func.apply(rng.upperEndpoint()); - if (t != null) { - return t; + try (LockHold hold = trace.lockRead()) { + synchronized (ordered) { + for (Range rng : ordered) { + T t = func.apply(rng.upperEndpoint()); + if (t != null) { + return t; + } } + return null; } - return null; } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/CustomToStringCellRenderer.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/CustomToStringCellRenderer.java index 2691408886..e0fadc6477 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/CustomToStringCellRenderer.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/CustomToStringCellRenderer.java @@ -18,10 +18,15 @@ package docking.widgets.table; import java.awt.Component; import java.awt.Font; import java.math.BigInteger; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.function.BiFunction; -import javax.swing.JTable; +import javax.swing.*; +import javax.swing.plaf.basic.BasicHTML; import javax.swing.table.TableModel; +import javax.swing.text.View; import ghidra.docking.settings.Settings; import ghidra.util.table.column.AbstractGColumnRenderer; @@ -40,6 +45,14 @@ public class CustomToStringCellRenderer extends AbstractGColumnRenderer { return v.signum() < 0 ? "-0x" + v.negate().toString(16) : "0x" + v.toString(16); } + public static final DateFormat TIME_FORMAT_24HMSms = new SimpleDateFormat("HH:mm:ss.SSS"); + + public static final CustomToStringCellRenderer TIME_24HMSms = + new CustomToStringCellRenderer<>(CustomFont.DEFAULT, Date.class, + (v, s) -> v == null ? "" : TIME_FORMAT_24HMSms.format(v), false); + public static final CustomToStringCellRenderer HTML = + new CustomToStringCellRenderer(CustomFont.DEFAULT, String.class, + (v, s) -> v == null ? "" : v, true); public static final CustomToStringCellRenderer MONO_OBJECT = new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Object.class, (v, s) -> v == null ? "" : v.toString(), false); @@ -60,6 +73,9 @@ public class CustomToStringCellRenderer extends AbstractGColumnRenderer { private final Class cls; private final BiFunction toString; + private final JPanel panelForSize = new JPanel(); + private final BoxLayout layoutForSize = new BoxLayout(panelForSize, BoxLayout.Y_AXIS); + public CustomToStringCellRenderer(Class cls, BiFunction toString, boolean enableHtml) { this(null, cls, toString, enableHtml); @@ -71,6 +87,8 @@ public class CustomToStringCellRenderer extends AbstractGColumnRenderer { this.customFont = font; this.cls = cls; this.toString = toString; + + panelForSize.setLayout(layoutForSize); } @Override @@ -100,6 +118,21 @@ public class CustomToStringCellRenderer extends AbstractGColumnRenderer { public Component getTableCellRendererComponent(GTableCellRenderingData data) { super.getTableCellRendererComponent(data); setText(toString.apply(cls.cast(data.getValue()), data.getColumnSettings())); + if (getHTMLRenderingEnabled()) { + setVerticalAlignment(SwingConstants.TOP); + } + else { + setVerticalAlignment(SwingConstants.CENTER); + } return this; } + + public int getRowHeight(int colWidth) { + View v = (View) getClientProperty(BasicHTML.propertyKey); + if (v == null) { + return 0; + } + v.setSize(colWidth, Short.MAX_VALUE); + return (int) v.getPreferredSpan(View.Y_AXIS); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java index a78eac755c..02408c4c2f 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java @@ -163,18 +163,22 @@ public class DefaultEnumeratedColumnTableModel & EnumeratedTab @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { - R row = modelData.get(rowIndex); - C col = cols[columnIndex]; - Class cls = col.getValueClass(); - col.setValueOf(row, cls.cast(aValue)); + synchronized (modelData) { + R row = modelData.get(rowIndex); + C col = cols[columnIndex]; + Class cls = col.getValueClass(); + col.setValueOf(row, cls.cast(aValue)); + } fireTableCellUpdated(rowIndex, columnIndex); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { - R row = modelData.get(rowIndex); - C col = cols[columnIndex]; - return col.isEditable(row); + synchronized (modelData) { + R row = modelData.get(rowIndex); + C col = cols[columnIndex]; + return col.isEditable(row); + } } @Override @@ -200,35 +204,47 @@ public class DefaultEnumeratedColumnTableModel & EnumeratedTab @Override public void add(R row) { - int rowIndex = modelData.size(); - modelData.add(row); + int rowIndex; + synchronized (modelData) { + rowIndex = modelData.size(); + modelData.add(row); + } fireTableRowsInserted(rowIndex, rowIndex); } @Override public void addAll(Collection c) { - int startIndex = modelData.size(); - modelData.addAll(c); - int endIndex = modelData.size() - 1; + int startIndex; + int endIndex; + synchronized (modelData) { + startIndex = modelData.size(); + modelData.addAll(c); + endIndex = modelData.size() - 1; + } fireTableRowsInserted(startIndex, endIndex); } @Override public void notifyUpdated(R row) { - int rowIndex = modelData.indexOf(row); + int rowIndex; + synchronized (modelData) { + rowIndex = modelData.indexOf(row); + } fireTableRowsUpdated(rowIndex, rowIndex); } @Override public List notifyUpdatedWith(Predicate predicate) { int lastIndexUpdated = 0; - ListIterator rit = modelData.listIterator(); List updated = new ArrayList<>(); - while (rit.hasNext()) { - R row = rit.next(); - if (predicate.test(row)) { - lastIndexUpdated = rit.previousIndex(); - updated.add(row); + ListIterator rit = modelData.listIterator(); + synchronized (modelData) { + while (rit.hasNext()) { + R row = rit.next(); + if (predicate.test(row)) { + lastIndexUpdated = rit.previousIndex(); + updated.add(row); + } } } int size = updated.size(); @@ -245,25 +261,30 @@ public class DefaultEnumeratedColumnTableModel & EnumeratedTab @Override public void delete(R row) { - int rowIndex = modelData.indexOf(row); - if (rowIndex == -1) { - return; + int rowIndex; + synchronized (modelData) { + rowIndex = modelData.indexOf(row); + if (rowIndex == -1) { + return; + } + modelData.remove(rowIndex); } - modelData.remove(rowIndex); fireTableRowsDeleted(rowIndex, rowIndex); } @Override public List deleteWith(Predicate predicate) { int lastIndexRemoved = 0; - ListIterator rit = modelData.listIterator(); List removed = new ArrayList<>(); - while (rit.hasNext()) { - R row = rit.next(); - if (predicate.test(row)) { - lastIndexRemoved = rit.previousIndex(); - rit.remove(); - removed.add(row); + synchronized (modelData) { + ListIterator rit = modelData.listIterator(); + while (rit.hasNext()) { + R row = rit.next(); + if (predicate.test(row)) { + lastIndexRemoved = rit.previousIndex(); + rit.remove(); + removed.add(row); + } } } int size = removed.size(); @@ -280,17 +301,28 @@ public class DefaultEnumeratedColumnTableModel & EnumeratedTab @Override public R findFirst(Predicate predicate) { - for (R row : modelData) { - if (predicate.test(row)) { - return row; + synchronized (modelData) { + for (R row : modelData) { + if (predicate.test(row)) { + return row; + } } + return null; } - return null; } @Override public void clear() { - modelData.clear(); + synchronized (modelData) { + modelData.clear(); + } fireTableDataChanged(); } + + @Override + protected void sort(List data, TableSortingContext sortingContext) { + synchronized (data) { + super.sort(data, sortingContext); + } + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java index 9eb08aa349..42359588fb 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java @@ -47,8 +47,12 @@ public class RowWrappedEnumeratedColumnTableModel & Enumerated return map.computeIfAbsent(keyFunc.apply(t), k -> wrapper.apply(t)); } - protected synchronized R delFor(T t) { - return map.remove(keyFunc.apply(t)); + protected R delFor(T t) { + return delKey(keyFunc.apply(t)); + } + + protected synchronized R delKey(K k) { + return map.remove(k); } protected synchronized List rowsFor(Collection c) { @@ -79,9 +83,15 @@ public class RowWrappedEnumeratedColumnTableModel & Enumerated delete(delFor(t)); } + public R deleteKey(K k) { + R r = delKey(k); + delete(r); + return r; + } + public synchronized void deleteAllItems(Collection c) { deleteWith(rowsFor(c)::contains); - map.keySet().removeAll(c); + map.keySet().removeAll(c.stream().map(keyFunc).collect(Collectors.toList())); } public synchronized Map getMap() { diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/TimedMsg.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/TimedMsg.java index a4a9f18f18..cb97b77ab7 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/TimedMsg.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/TimedMsg.java @@ -34,7 +34,7 @@ public class TimedMsg { } } - public static void info(Object originator, String message) { - doMsg(Msg::info, originator, message); + public static void debug(Object originator, String message) { + doMsg(Msg::debug, originator, message); } }