mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-406: Central Debug Console for problems and actions
This commit is contained in:
parent
2466f85ea8
commit
a1cfeebcc9
39 changed files with 2101 additions and 837 deletions
|
@ -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-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-disable-all.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerBreakpointsPlugin/images/breakpoints-enable-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/DebuggerInterpreterPlugin/DebuggerInterpreterPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerListingPlugin/DebuggerListingPlugin.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/DebuggerGoToDialog.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerListingPlugin/images/DebuggerListingPlugin.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/DebuggerMemviewPlugin.html||GHIDRA||||END|
|
||||||
src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin.png||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|
|
src/main/help/help/topics/DebuggerMemviewPlugin/images/DebuggerMemviewPlugin_old.png||GHIDRA||||END|
|
||||||
|
|
|
@ -74,6 +74,10 @@
|
||||||
target="help/topics/DebuggerModelServicePlugin/DebuggerModelServicePlugin.html" />
|
target="help/topics/DebuggerModelServicePlugin/DebuggerModelServicePlugin.html" />
|
||||||
</tocdef>
|
</tocdef>
|
||||||
|
|
||||||
|
<tocdef id="DebuggerConsolePlugin" text="Debug Console"
|
||||||
|
sortgroup="c1"
|
||||||
|
target="help/topics/DebuggerConsolePlugin/DebuggerConsolePlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerObjectsPlugin" text="Commands and Objects"
|
<tocdef id="DebuggerObjectsPlugin" text="Commands and Objects"
|
||||||
sortgroup="d"
|
sortgroup="d"
|
||||||
target="help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html" />
|
target="help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html" />
|
||||||
|
|
|
@ -15,9 +15,10 @@
|
||||||
|
|
||||||
<H2>Error Console</H2>
|
<H2>Error Console</H2>
|
||||||
|
|
||||||
<P>The first place to look when you're having trouble is the error console. In Eclipse, this is
|
<P>The first place to look when you're having trouble is the Debug Console. Second, if you're
|
||||||
just the "Console" window. In Ghidra, it can be accessed from the main application window.
|
in Eclipse, you can check its "Console" window. Often, Ghidra's Debug Console will offer
|
||||||
Sometimes it reports known issues; sometimes it reports unexpected behavior; etc., which may be
|
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.</P>
|
clues to exactly what has gone wrong.</P>
|
||||||
|
|
||||||
<H2>Settings and Toggles</H2>
|
<H2>Settings and Toggles</H2>
|
||||||
|
@ -38,9 +39,6 @@
|
||||||
<P>In the Dynamic Listing:</P>
|
<P>In the Dynamic Listing:</P>
|
||||||
|
|
||||||
<UL>
|
<UL>
|
||||||
<LI>"Auto-Import Current Module" will cause the user to be prompted for information to sync
|
|
||||||
static and dynamic listings.</LI>
|
|
||||||
|
|
||||||
<LI>"Sync to Static Listing" controls the tracking of the Static listing.</LI>
|
<LI>"Sync to Static Listing" controls the tracking of the Static listing.</LI>
|
||||||
</UL>
|
</UL>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||||
|
|
||||||
|
<HTML>
|
||||||
|
<HEAD>
|
||||||
|
<META name="generator" content=
|
||||||
|
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||||
|
|
||||||
|
<TITLE>Debugger: Memory Regions</TITLE>
|
||||||
|
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||||
|
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
|
||||||
|
</HEAD>
|
||||||
|
|
||||||
|
<BODY lang="EN-US">
|
||||||
|
<H1><A name="plugin"></A>Debugger: Console</H1>
|
||||||
|
|
||||||
|
<TABLE width="100%">
|
||||||
|
<TBODY>
|
||||||
|
<TR>
|
||||||
|
<TD align="center" width="100%"><IMG alt="" border="1" src=
|
||||||
|
"images/DebuggerConsolePlugin.png"></TD>
|
||||||
|
</TR>
|
||||||
|
</TBODY>
|
||||||
|
</TABLE>
|
||||||
|
|
||||||
|
<P>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.</P>
|
||||||
|
|
||||||
|
<P>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.</P>
|
||||||
|
|
||||||
|
<P>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.</P>
|
||||||
|
|
||||||
|
<H2>Table Columns</H2>
|
||||||
|
|
||||||
|
<P>The table has the following columns:</P>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
<LI>Icon - an icon to identify the type, topic, or source of a message.</LI>
|
||||||
|
|
||||||
|
<LI>Message - the message itself.</LI>
|
||||||
|
|
||||||
|
<LI>Actions - if actionable, a row of buttons for available actions.</LI>
|
||||||
|
|
||||||
|
<LI>Time - the time the message was generated in 24-hour HH:mm:ss.SSS format.</LI>
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
<H2>Actions</H2>
|
||||||
|
|
||||||
|
<P>Not considering extension actions from other plugins, the console provides the
|
||||||
|
following:</P>
|
||||||
|
|
||||||
|
<H3><A name="clear"></A>Clear</H3>
|
||||||
|
|
||||||
|
<P>Removes all messages, including actionable messages, from the log.</P>
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
|
@ -131,7 +131,9 @@
|
||||||
computed using information about loaded modules reported by the debugger. For the finer
|
computed using information about loaded modules reported by the debugger. For the finer
|
||||||
details, see the <A href=
|
details, see the <A href=
|
||||||
"help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static Mappings</A>
|
"help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html">Static Mappings</A>
|
||||||
window.</P>
|
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.</P>
|
||||||
|
|
||||||
<H3><A name="capture_memory"></A>Capture Memory</H3>
|
<H3><A name="capture_memory"></A>Capture Memory</H3>
|
||||||
|
|
||||||
|
@ -162,29 +164,6 @@
|
||||||
neglect to capture read-only ranges that have been captured previously.</LI>
|
neglect to capture read-only ranges that have been captured previously.</LI>
|
||||||
</UL>
|
</UL>
|
||||||
|
|
||||||
<H3><A name="auto_import_module"></A>Auto-Import Current Module</H3>
|
|
||||||
|
|
||||||
<P>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:</P>
|
|
||||||
|
|
||||||
<TABLE width="100%">
|
|
||||||
<TBODY>
|
|
||||||
<TR>
|
|
||||||
<TD align="center" width="100%"><IMG alt="" src=
|
|
||||||
"images/DebuggerModuleImportDialog.png"></TD>
|
|
||||||
</TR>
|
|
||||||
</TBODY>
|
|
||||||
</TABLE>
|
|
||||||
|
|
||||||
<P>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 <A href=
|
|
||||||
"help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html">Modules</A> window.</P>
|
|
||||||
|
|
||||||
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
<H2><A name="colors"></A>Tool Options: Colors</H2>
|
||||||
|
|
||||||
<P>The memory-state and tracked-location background colors can all be configured here.</P>
|
<P>The memory-state and tracked-location background colors can all be configured here.</P>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
|
@ -145,6 +145,16 @@
|
||||||
It behaves like Map Sections, except that it will propose the selected section be mapped to the
|
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.</P>
|
block containing the cursor in the static listing.</P>
|
||||||
|
|
||||||
|
<H3><A name="import_missing_module"></A>Import Missing Module</H3>
|
||||||
|
|
||||||
|
<P>This action is offered to resolve a "Missing Module" console message. It is equivalent to <A
|
||||||
|
href="#import_from_fs">Import From File System</A> on the missing module.</P>
|
||||||
|
|
||||||
|
<H3><A name="map_missing_module"></A>Map Missing Module</H3>
|
||||||
|
|
||||||
|
<P>This action is offered to resolve a "Missing Module" console message. It is equivalent to <A
|
||||||
|
href="#map_module_to">Map Module To</A> on the missing module.</P>
|
||||||
|
|
||||||
<H3><A name="filter_by_module"></A>Filter Sections by Module</H3>
|
<H3><A name="filter_by_module"></A>Filter Sections by Module</H3>
|
||||||
|
|
||||||
<P>This action is always available. By default the bottom table displays all sections in the
|
<P>This action is always available. By default the bottom table displays all sections in the
|
||||||
|
|
|
@ -32,6 +32,7 @@ import docking.widgets.table.*;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||||
import ghidra.app.plugin.core.debug.gui.breakpoint.DebuggerBreakpointsPlugin;
|
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.listing.DebuggerListingPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsPlugin;
|
import ghidra.app.plugin.core.debug.gui.memory.DebuggerRegionsPlugin;
|
||||||
import ghidra.app.plugin.core.debug.gui.modules.DebuggerModulesPlugin;
|
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_CLOSE = ResourceManager.loadImage("images/x.gif");
|
||||||
ImageIcon ICON_ADD = ResourceManager.loadImage("images/add.png");
|
ImageIcon ICON_ADD = ResourceManager.loadImage("images/add.png");
|
||||||
ImageIcon ICON_DELETE = ResourceManager.loadImage("images/delete.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_REFRESH = ResourceManager.loadImage("images/view-refresh.png");
|
||||||
ImageIcon ICON_FILTER = ResourceManager.loadImage("images/filter_off.png"); // Eww.
|
ImageIcon ICON_FILTER = ResourceManager.loadImage("images/filter_off.png"); // Eww.
|
||||||
ImageIcon ICON_SELECT_ROWS = ResourceManager.loadImage("images/table_go.png");
|
ImageIcon ICON_SELECT_ROWS = ResourceManager.loadImage("images/table_go.png");
|
||||||
|
@ -128,7 +130,7 @@ public interface DebuggerResources {
|
||||||
//ResourceManager.loadImage("images/capture-memory.png");
|
//ResourceManager.loadImage("images/capture-memory.png");
|
||||||
|
|
||||||
// TODO: Draw an icon
|
// 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_MAP_SECTIONS = ICON_MAP_MODULES; // TODO
|
||||||
ImageIcon ICON_BLOCK = ICON_MAP_SECTIONS; // TODO
|
ImageIcon ICON_BLOCK = ICON_MAP_SECTIONS; // TODO
|
||||||
// TODO: Draw an icon
|
// TODO: Draw an icon
|
||||||
|
@ -138,6 +140,10 @@ public interface DebuggerResources {
|
||||||
// TODO: Draw an icon?
|
// TODO: Draw an icon?
|
||||||
ImageIcon ICON_CAPTURE_SYMBOLS = ResourceManager.loadImage("images/closedFolderLabels.png");
|
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_SYNC = ResourceManager.loadImage("images/sync_enabled.png");
|
||||||
ImageIcon ICON_VISIBILITY = ResourceManager.loadImage("images/format-text-bold.png");
|
ImageIcon ICON_VISIBILITY = ResourceManager.loadImage("images/format-text-bold.png");
|
||||||
|
|
||||||
|
@ -156,6 +162,11 @@ public interface DebuggerResources {
|
||||||
HelpLocation HELP_PROVIDER_BREAKPOINTS = new HelpLocation(
|
HelpLocation HELP_PROVIDER_BREAKPOINTS = new HelpLocation(
|
||||||
PluginUtils.getPluginNameFromClass(DebuggerBreakpointsPlugin.class), HELP_ANCHOR_PLUGIN);
|
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";
|
String TITLE_PROVIDER_LISTING = "Dynamic";
|
||||||
ImageIcon ICON_PROVIDER_LISTING = ICON_LISTING;
|
ImageIcon ICON_PROVIDER_LISTING = ICON_LISTING;
|
||||||
HelpLocation HELP_PROVIDER_LISTING = new HelpLocation(
|
HelpLocation HELP_PROVIDER_LISTING = new HelpLocation(
|
||||||
|
@ -310,6 +321,9 @@ public interface DebuggerResources {
|
||||||
"Colors.Ineffective Disabled Breakpoint Markers Have Background";
|
"Colors.Ineffective Disabled Breakpoint Markers Have Background";
|
||||||
boolean DEFAULT_COLOR_INEFFECTIVE_D_BREAKPOINT_COLORING_BACKGROUND = false;
|
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
|
// TODO: Re-assign/name groups
|
||||||
String GROUP_GENERAL = "Dbg1. General";
|
String GROUP_GENERAL = "Dbg1. General";
|
||||||
String GROUP_CONNECTION = "Dbg2. Connection";
|
String GROUP_CONNECTION = "Dbg2. Connection";
|
||||||
|
@ -733,17 +747,36 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AutoImportCurrentModuleAction {
|
interface ImportMissingModuleAction {
|
||||||
String NAME = "Auto-Import Current Module";
|
String NAME = "Import Missing Module";
|
||||||
String DESCRIPTION = "Import missing module at the cursor";
|
String DESCRIPTION = "Import the missing module from disk";
|
||||||
Icon ICON = ICON_IMPORT;
|
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();
|
String ownerName = owner.getName();
|
||||||
return new ToggleActionBuilder(NAME, ownerName).description(DESCRIPTION)
|
return new ActionBuilder(NAME, ownerName)
|
||||||
.menuIcon(ICON)
|
.description(DESCRIPTION)
|
||||||
.menuPath(NAME)
|
.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));
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -862,7 +895,8 @@ public interface DebuggerResources {
|
||||||
|
|
||||||
static ActionBuilder builder(Plugin owner) {
|
static ActionBuilder builder(Plugin owner) {
|
||||||
String ownerName = owner.getName();
|
String ownerName = owner.getName();
|
||||||
return new ActionBuilder(NAME, ownerName).toolBarGroup(GROUP)
|
return new ActionBuilder(NAME, ownerName)
|
||||||
|
.toolBarGroup(GROUP)
|
||||||
.toolBarIcon(ICON)
|
.toolBarIcon(ICON)
|
||||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
}
|
}
|
||||||
|
@ -879,7 +913,26 @@ public interface DebuggerResources {
|
||||||
}
|
}
|
||||||
|
|
||||||
static ActionBuilder builder(String ownerName) {
|
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)
|
.toolBarIcon(ICON)
|
||||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
}
|
}
|
||||||
|
@ -892,7 +945,21 @@ public interface DebuggerResources {
|
||||||
|
|
||||||
static ToggleActionBuilder builder(Plugin owner) {
|
static ToggleActionBuilder builder(Plugin owner) {
|
||||||
String ownerName = owner.getName();
|
String ownerName = owner.getName();
|
||||||
return new ToggleActionBuilder(NAME, ownerName).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) {
|
static ActionBuilder builder(Plugin owner) {
|
||||||
String ownerName = owner.getName();
|
String ownerName = owner.getName();
|
||||||
return new ActionBuilder(NAME, ownerName).toolBarGroup(GROUP)
|
return new ActionBuilder(NAME, ownerName)
|
||||||
|
.toolBarGroup(GROUP)
|
||||||
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR))
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR))
|
||||||
.toolBarIcon(ICON);
|
.toolBarIcon(ICON);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<JButton> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ActionList> {
|
||||||
|
|
||||||
|
static void configureBox(JPanel box) {
|
||||||
|
box.setLayout(new BoxLayout(box, BoxLayout.X_AXIS));
|
||||||
|
box.setOpaque(true);
|
||||||
|
box.setAlignmentX(0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ensureCacheSize(List<JButton> buttonCache, int size,
|
||||||
|
Consumer<JButton> 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<JButton> buttonCache, ActionList value,
|
||||||
|
Consumer<JButton> 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<JButton> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<? extends ActionContext> ctxCls) {
|
||||||
|
return provider.getRowCount(ctxCls);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<LogTableColumns, LogRow> {
|
||||||
|
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<LogRow, ?> getter;
|
||||||
|
private final Class<?> cls;
|
||||||
|
private final SortDirection defaultSortDirection;
|
||||||
|
private final boolean editable;
|
||||||
|
|
||||||
|
<T> LogTableColumns(String header, Class<T> cls, Function<LogRow, T> 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<BoundAction> {
|
||||||
|
}
|
||||||
|
|
||||||
|
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<LogTableColumns> 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<String, Map<String, DockingActionIf>> actionsByOwnerThenName =
|
||||||
|
new LinkedHashMap<>();
|
||||||
|
|
||||||
|
protected final LogTableModel logTableModel = new LogTableModel();
|
||||||
|
protected GhidraTable logTable;
|
||||||
|
private GhidraTableFilterPanel<LogRow> logFilterPanel;
|
||||||
|
|
||||||
|
private Deque<LogRow> 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()),
|
||||||
|
"<html>" + 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<String, DockingActionIf> 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<DockingActionIf> 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<DockingActionIf> getPopupActions(Tool tool, ActionContext context) {
|
||||||
|
return streamActions(context)
|
||||||
|
.filter(a -> a.isAddToPopup(context))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected long getRowCount(Class<? extends ActionContext> ctxCls) {
|
||||||
|
return logTableModel.getModelData()
|
||||||
|
.stream()
|
||||||
|
.filter(r -> ctxCls.isInstance(r.context))
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -31,19 +31,18 @@ import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
|
||||||
@PluginInfo( //
|
@PluginInfo(
|
||||||
shortDescription = "Debugger interpreter panel service", //
|
shortDescription = "Debugger interpreter panel service",
|
||||||
description = "Manage interpreter panels within debug sessions", //
|
description = "Manage interpreter panels within debug sessions",
|
||||||
category = PluginCategoryNames.DEBUGGER, //
|
category = PluginCategoryNames.DEBUGGER,
|
||||||
packageName = DebuggerPluginPackage.NAME, //
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.RELEASED, //
|
status = PluginStatus.RELEASED,
|
||||||
servicesRequired = { //
|
servicesRequired = {
|
||||||
InterpreterPanelService.class //
|
InterpreterPanelService.class,
|
||||||
}, //
|
},
|
||||||
servicesProvided = {
|
servicesProvided = {
|
||||||
DebuggerInterpreterService.class //
|
DebuggerInterpreterService.class,
|
||||||
} //
|
})
|
||||||
)
|
|
||||||
public class DebuggerInterpreterPlugin extends AbstractDebuggerPlugin
|
public class DebuggerInterpreterPlugin extends AbstractDebuggerPlugin
|
||||||
implements DebuggerInterpreterService {
|
implements DebuggerInterpreterService {
|
||||||
|
|
||||||
|
|
|
@ -342,7 +342,7 @@ public class DebuggerListingPlugin extends CodeBrowserPlugin implements Debugger
|
||||||
//cbGoTo.invoke(() -> {
|
//cbGoTo.invoke(() -> {
|
||||||
DebuggerListingProvider provider = getConnectedProvider();
|
DebuggerListingProvider provider = getConnectedProvider();
|
||||||
provider.doSyncToStatic(location);
|
provider.doSyncToStatic(location);
|
||||||
provider.doAutoImportCurrentModule();
|
provider.doCheckCurrentModuleMissing();
|
||||||
//});
|
//});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 static ghidra.app.plugin.core.debug.gui.DebuggerResources.OPTION_NAME_COLORS_REGISTER_MARKERS;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.File;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
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.*;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
|
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.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.BackgroundUtils;
|
||||||
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
||||||
import ghidra.app.plugin.core.exporter.ExporterDialog;
|
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.thread.TraceThread;
|
||||||
import ghidra.trace.model.time.TraceSnapshot;
|
import ghidra.trace.model.time.TraceSnapshot;
|
||||||
import ghidra.trace.util.TraceAddressSpace;
|
import ghidra.trace.util.TraceAddressSpace;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.*;
|
||||||
import ghidra.util.Swing;
|
|
||||||
import utilities.util.SuppressableCallback;
|
import utilities.util.SuppressableCallback;
|
||||||
import utilities.util.SuppressableCallback.Suppression;
|
import utilities.util.SuppressableCallback.Suppression;
|
||||||
|
|
||||||
|
@ -323,6 +322,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
//@AutoServiceConsumed via method
|
//@AutoServiceConsumed via method
|
||||||
private DebuggerStaticMappingService mappingService;
|
private DebuggerStaticMappingService mappingService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
private DebuggerConsoleService consoleService;
|
||||||
|
@AutoServiceConsumed
|
||||||
private ProgramManager programManager;
|
private ProgramManager programManager;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private FileImporterService importerService;
|
private FileImporterService importerService;
|
||||||
|
@ -349,12 +350,10 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
|
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
|
||||||
protected DockingAction actionGoTo;
|
protected DockingAction actionGoTo;
|
||||||
protected SyncToStaticListingAction actionSyncToStaticListing;
|
protected SyncToStaticListingAction actionSyncToStaticListing;
|
||||||
protected ToggleDockingAction actionAutoImportCurrentModule;
|
|
||||||
protected FollowsCurrentThreadAction actionFollowsCurrentThread;
|
protected FollowsCurrentThreadAction actionFollowsCurrentThread;
|
||||||
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
|
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
|
||||||
protected DockingAction actionExportView;
|
protected DockingAction actionExportView;
|
||||||
|
|
||||||
protected final DebuggerModuleImportDialog importDialog;
|
|
||||||
protected final DebuggerGoToDialog goToDialog;
|
protected final DebuggerGoToDialog goToDialog;
|
||||||
|
|
||||||
@AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class)
|
@AutoConfigStateField(codec = TrackingSpecConfigFieldCodec.class)
|
||||||
|
@ -362,8 +361,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
@AutoConfigStateField
|
@AutoConfigStateField
|
||||||
protected boolean syncToStaticListing;
|
protected boolean syncToStaticListing;
|
||||||
@AutoConfigStateField
|
@AutoConfigStateField
|
||||||
protected boolean autoImportCurrentModule;
|
|
||||||
@AutoConfigStateField
|
|
||||||
protected boolean followsCurrentThread = true;
|
protected boolean followsCurrentThread = true;
|
||||||
@AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class)
|
@AutoConfigStateField(codec = AutoReadMemorySpecConfigFieldCodec.class)
|
||||||
protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec;
|
protected AutoReadMemorySpec autoReadMemorySpec = defaultReadMemorySpec;
|
||||||
|
@ -389,7 +386,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
super(plugin, formatManager, isConnected);
|
super(plugin, formatManager, isConnected);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
||||||
importDialog = new DebuggerModuleImportDialog(tool);
|
|
||||||
goToDialog = new DebuggerGoToDialog(this);
|
goToDialog = new DebuggerGoToDialog(this);
|
||||||
|
|
||||||
ListingPanel listingPanel = getListingPanel();
|
ListingPanel listingPanel = getListingPanel();
|
||||||
|
@ -403,7 +399,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
autoOptionsWiring = AutoOptions.wireOptionsConsumed(plugin, this);
|
autoOptionsWiring = AutoOptions.wireOptionsConsumed(plugin, this);
|
||||||
|
|
||||||
syncToStaticListing = isConnected;
|
syncToStaticListing = isConnected;
|
||||||
autoImportCurrentModule = isConnected;
|
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
createActions();
|
createActions();
|
||||||
|
|
||||||
|
@ -473,12 +468,10 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
actionTrackLocation.setCurrentActionStateByUserData(trackingSpec);
|
actionTrackLocation.setCurrentActionStateByUserData(trackingSpec);
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
actionSyncToStaticListing.setSelected(syncToStaticListing);
|
actionSyncToStaticListing.setSelected(syncToStaticListing);
|
||||||
actionAutoImportCurrentModule.setSelected(autoImportCurrentModule);
|
|
||||||
followsCurrentThread = true;
|
followsCurrentThread = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
syncToStaticListing = false;
|
syncToStaticListing = false;
|
||||||
autoImportCurrentModule = false;
|
|
||||||
actionFollowsCurrentThread.setSelected(followsCurrentThread);
|
actionFollowsCurrentThread.setSelected(followsCurrentThread);
|
||||||
updateBorder();
|
updateBorder();
|
||||||
}
|
}
|
||||||
|
@ -744,11 +737,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
|
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
actionSyncToStaticListing = new SyncToStaticListingAction();
|
actionSyncToStaticListing = new SyncToStaticListingAction();
|
||||||
actionAutoImportCurrentModule = AutoImportCurrentModuleAction.builder(plugin)
|
|
||||||
.enabledWhen(ctx -> syncToStaticListing)
|
|
||||||
.onAction(this::autoImportCurrentModuleActionToggled)
|
|
||||||
.selected(autoImportCurrentModule)
|
|
||||||
.buildAndInstallLocal(this);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
actionFollowsCurrentThread = new FollowsCurrentThreadAction();
|
actionFollowsCurrentThread = new FollowsCurrentThreadAction();
|
||||||
|
@ -803,10 +791,6 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
doSetTrackingSpec(newState.getUserData());
|
doSetTrackingSpec(newState.getUserData());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void autoImportCurrentModuleActionToggled(ActionContext ctx) {
|
|
||||||
doSetAutoImportCurrentModule(actionAutoImportCurrentModule.isSelected());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void activatedAutoReadMemory(ActionContext ctx) {
|
protected void activatedAutoReadMemory(ActionContext ctx) {
|
||||||
doAutoReadMemory();
|
doAutoReadMemory();
|
||||||
}
|
}
|
||||||
|
@ -891,7 +875,7 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
super.programLocationChanged(location, trigger);
|
super.programLocationChanged(location, trigger);
|
||||||
if (trigger == EventTrigger.GUI_ACTION) {
|
if (trigger == EventTrigger.GUI_ACTION) {
|
||||||
doSyncToStatic(location);
|
doSyncToStatic(location);
|
||||||
doAutoImportCurrentModule();
|
doCheckCurrentModuleMissing();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -928,11 +912,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doAutoImportCurrentModule() {
|
protected void doCheckCurrentModuleMissing() {
|
||||||
if (!autoImportCurrentModule) {
|
if (importerService == null || consoleService == null) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (importerService == null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Trace trace = current.getTrace();
|
Trace trace = current.getTrace();
|
||||||
|
@ -962,8 +943,8 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<TraceModule, File> missing = new LinkedHashMap<>();
|
Set<TraceModule> missing = new HashSet<>();
|
||||||
Set<DomainFile> toOpen = new LinkedHashSet<>();
|
Set<DomainFile> toOpen = new HashSet<>();
|
||||||
TraceModuleManager modMan = trace.getModuleManager();
|
TraceModuleManager modMan = trace.getModuleManager();
|
||||||
Collection<TraceModule> modules = Stream.concat(
|
Collection<TraceModule> modules = Stream.concat(
|
||||||
modMan.getModulesAt(snap, address).stream().filter(m -> m.getSections().isEmpty()),
|
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) {
|
for (TraceModule mod : modules) {
|
||||||
Set<DomainFile> matches = mappingService.findProbableModulePrograms(mod);
|
Set<DomainFile> matches = mappingService.findProbableModulePrograms(mod);
|
||||||
if (matches.isEmpty()) {
|
if (matches.isEmpty()) {
|
||||||
missing.put(mod, new File(mod.getName()));
|
missing.add(mod);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
toOpen.addAll(matches);
|
toOpen.addAll(matches);
|
||||||
|
@ -988,7 +969,12 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
ProgramManager.OPEN_VISIBLE);
|
ProgramManager.OPEN_VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
importDialog.addFiles(missing.values());
|
for (TraceModule mod : missing) {
|
||||||
|
consoleService.log(DebuggerResources.ICON_LOG_ERROR,
|
||||||
|
"<html>The module <b><tt>" + HTMLUtilities.escapeHTML(mod.getName()) +
|
||||||
|
"</tt></b> was not found in the project",
|
||||||
|
new DebuggerMissingModuleActionContext(mod));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Once the programs are opened, including those which are successfully imported, the
|
* Once the programs are opened, including those which are successfully imported, the
|
||||||
* section mapper should take over, eventually invoking callbacks to our mapping change
|
* 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());
|
doSyncToStatic(getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doSetAutoImportCurrentModule(boolean autoImport) {
|
|
||||||
this.autoImportCurrentModule = autoImport;
|
|
||||||
doAutoImportCurrentModule();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSyncToStaticListing() {
|
public boolean isSyncToStaticListing() {
|
||||||
return syncToStaticListing;
|
return syncToStaticListing;
|
||||||
}
|
}
|
||||||
|
@ -1117,13 +1098,13 @@ public class DebuggerListingProvider extends CodeViewerProvider implements Listi
|
||||||
if (!syncToStaticListing || trackedStatic == null) {
|
if (!syncToStaticListing || trackedStatic == null) {
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
goTo(curView, loc);
|
goTo(curView, loc);
|
||||||
doAutoImportCurrentModule();
|
doCheckCurrentModuleMissing();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Swing.runIfSwingOrRunLater(() -> {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
goTo(curView, loc);
|
goTo(curView, loc);
|
||||||
doAutoImportCurrentModule();
|
doCheckCurrentModuleMissing();
|
||||||
plugin.fireStaticLocationEvent(trackedStatic);
|
plugin.fireStaticLocationEvent(trackedStatic);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<FileTableColumns, FileRow> {
|
|
||||||
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<FileRow, ?> getter;
|
|
||||||
private BiConsumer<FileRow, Object> setter;
|
|
||||||
|
|
||||||
private static void nop() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
<T> FileTableColumns(String header, Class<T> cls,
|
|
||||||
Function<FileRow, T> getter, BiConsumer<FileRow, T> setter) {
|
|
||||||
this.header = header;
|
|
||||||
this.cls = cls;
|
|
||||||
this.getter = getter;
|
|
||||||
this.setter = (BiConsumer<FileRow, Object>) setter;
|
|
||||||
}
|
|
||||||
|
|
||||||
<T> FileTableColumns(String header, Class<T> cls,
|
|
||||||
Function<FileRow, T> 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<FileTableColumns, FileRow> {
|
|
||||||
public FileTableModel() {
|
|
||||||
super("Suggested Files to Import", FileTableColumns.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final PluginTool tool;
|
|
||||||
|
|
||||||
final FileTableModel fileTableModel = new FileTableModel();
|
|
||||||
private final Map<File, FileRow> map = new HashMap<>();
|
|
||||||
|
|
||||||
private GhidraTable fileTable;
|
|
||||||
private GhidraTableFilterPanel<FileRow> 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.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 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<File> files) {
|
|
||||||
synchronized (map) {
|
|
||||||
List<FileRow> 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.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 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<File> files) {
|
|
||||||
synchronized (map) {
|
|
||||||
Set<FileRow> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,6 +58,7 @@ public class DebuggerModulesPlugin extends AbstractDebuggerPlugin {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void dispose() {
|
protected void dispose() {
|
||||||
|
provider.dispose();
|
||||||
tool.removeComponentProvider(provider);
|
tool.removeComponentProvider(provider);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -458,15 +458,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TraceModule mod = modules.iterator().next();
|
TraceModule mod = modules.iterator().next();
|
||||||
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
|
importModuleFromFileSystem(mod);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -508,6 +500,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerListingService listingService;
|
private DebuggerListingService listingService;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
private DebuggerConsoleService consoleService;
|
||||||
|
@AutoServiceConsumed
|
||||||
ProgramManager programManager;
|
ProgramManager programManager;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private GoToService goToService;
|
private GoToService goToService;
|
||||||
|
@ -551,6 +545,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
DockingAction actionMapSectionTo;
|
DockingAction actionMapSectionTo;
|
||||||
DockingAction actionMapSectionsTo;
|
DockingAction actionMapSectionsTo;
|
||||||
|
|
||||||
|
DockingAction actionImportMissingModule;
|
||||||
|
DockingAction actionMapMissingModule;
|
||||||
|
|
||||||
SelectAddressesAction actionSelectAddresses;
|
SelectAddressesAction actionSelectAddresses;
|
||||||
CaptureTypesAction actionCaptureTypes;
|
CaptureTypesAction actionCaptureTypes;
|
||||||
CaptureSymbolsAction actionCaptureSymbols;
|
CaptureSymbolsAction actionCaptureSymbols;
|
||||||
|
@ -580,6 +577,18 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
createActions();
|
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
|
@AutoServiceConsumed
|
||||||
private void setModelService(DebuggerModelService modelService) {
|
private void setModelService(DebuggerModelService modelService) {
|
||||||
if (this.modelService != null) {
|
if (this.modelService != null) {
|
||||||
|
@ -592,6 +601,29 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
contextChanged();
|
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
|
@Override
|
||||||
public ActionContext getActionContext(MouseEvent event) {
|
public ActionContext getActionContext(MouseEvent event) {
|
||||||
if (myActionContext == null) {
|
if (myActionContext == null) {
|
||||||
|
@ -743,6 +775,15 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
.onAction(this::activatedMapSectionsTo)
|
.onAction(this::activatedMapSectionsTo)
|
||||||
.buildAndInstallLocal(this);
|
.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();
|
actionSelectAddresses = new SelectAddressesAction();
|
||||||
actionCaptureTypes = new CaptureTypesAction();
|
actionCaptureTypes = new CaptureTypesAction();
|
||||||
actionCaptureSymbols = new CaptureSymbolsAction();
|
actionCaptureSymbols = new CaptureSymbolsAction();
|
||||||
|
@ -818,6 +859,19 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
|
||||||
mapSectionTo(sel.iterator().next());
|
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) {
|
private void toggledFilter(ActionContext ignored) {
|
||||||
if (actionFilterSectionsByModules.isSelected()) {
|
if (actionFilterSectionsByModules.isSelected()) {
|
||||||
sectionFilterPanel.setSecondaryFilter(filterSectionsBySelectedModules);
|
sectionFilterPanel.setSecondaryFilter(filterSectionsBySelectedModules);
|
||||||
|
|
|
@ -159,14 +159,14 @@ public class DebuggerModelServicePlugin extends Plugin
|
||||||
protected class ListenerOnRecorders implements TraceRecorderListener {
|
protected class ListenerOnRecorders implements TraceRecorderListener {
|
||||||
@Override
|
@Override
|
||||||
public void snapAdvanced(TraceRecorder recorder, long snap) {
|
public void snapAdvanced(TraceRecorder recorder, long snap) {
|
||||||
TimedMsg.info(this, "Got snapAdvanced callback");
|
TimedMsg.debug(this, "Got snapAdvanced callback");
|
||||||
fireSnapEvent(recorder, snap);
|
fireSnapEvent(recorder, snap);
|
||||||
List<DebuggerModelServiceProxyPlugin> copy;
|
List<DebuggerModelServiceProxyPlugin> copy;
|
||||||
synchronized (proxies) {
|
synchronized (proxies) {
|
||||||
copy = List.copyOf(proxies);
|
copy = List.copyOf(proxies);
|
||||||
}
|
}
|
||||||
for (DebuggerModelServiceProxyPlugin proxy : copy) {
|
for (DebuggerModelServiceProxyPlugin proxy : copy) {
|
||||||
TimedMsg.info(this, "Firing SnapEvent on " + proxy);
|
TimedMsg.debug(this, "Firing SnapEvent on " + proxy);
|
||||||
proxy.fireSnapEvent(recorder, snap);
|
proxy.fireSnapEvent(recorder, snap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -264,7 +264,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
||||||
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
int frameLevel = stackRecorder.getSuccessorFrameLevel(bank);
|
||||||
long snap = recorder.getSnap();
|
long snap = recorder.getSnap();
|
||||||
String path = bank.getJoinedPath(".");
|
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", () -> {
|
recorder.parTx.execute("Registers " + path + " changed", () -> {
|
||||||
TraceCodeManager codeManager = trace.getCodeManager();
|
TraceCodeManager codeManager = trace.getCodeManager();
|
||||||
TraceCodeRegisterSpace codeRegisterSpace =
|
TraceCodeRegisterSpace codeRegisterSpace =
|
||||||
|
@ -377,7 +377,7 @@ public class DefaultThreadRecorder implements ManagedThreadRecorder {
|
||||||
if (targetRange == null) {
|
if (targetRange == null) {
|
||||||
return AsyncUtils.NIL;
|
return AsyncUtils.NIL;
|
||||||
}
|
}
|
||||||
TimedMsg.info(this,
|
TimedMsg.debug(this,
|
||||||
" Reading memory at " + name + " (" + targetAddress + " -> " + targetRange + ")");
|
" Reading memory at " + name + " (" + targetAddress + " -> " + targetRange + ")");
|
||||||
// NOTE: Recorder takes data via memoryUpdated callback
|
// NOTE: Recorder takes data via memoryUpdated callback
|
||||||
// TODO: In that callback, sort out process memory from thread memory?
|
// TODO: In that callback, sort out process memory from thread memory?
|
||||||
|
|
|
@ -119,7 +119,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TimedMsg.info(this, "Event: " + type + " thread=" + eventThread + " description=" +
|
TimedMsg.debug(this, "Event: " + type + " thread=" + eventThread + " description=" +
|
||||||
description + " params=" + parameters);
|
description + " params=" + parameters);
|
||||||
// Just use this to step the snaps. Creation/destruction still handled in add/remove
|
// Just use this to step the snaps. Creation/destruction still handled in add/remove
|
||||||
if (eventThread == null) {
|
if (eventThread == null) {
|
||||||
|
@ -162,7 +162,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TimedMsg.info(this, "State " + state + " for " + stateful);
|
TimedMsg.debug(this, "State " + state + " for " + stateful);
|
||||||
TargetObject x = recorder.objectManager.findThreadOrProcess(stateful);
|
TargetObject x = recorder.objectManager.findThreadOrProcess(stateful);
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
if (x == target && state == TargetExecutionState.TERMINATED) {
|
if (x == target && state == TargetExecutionState.TERMINATED) {
|
||||||
|
@ -204,7 +204,7 @@ public class TraceEventListener extends AnnotatedDebuggerAttributeListener {
|
||||||
}
|
}
|
||||||
Address traceAddr = recorder.getMemoryMapper().targetToTrace(address);
|
Address traceAddr = recorder.getMemoryMapper().targetToTrace(address);
|
||||||
long snap = recorder.getSnap();
|
long snap = recorder.getSnap();
|
||||||
TimedMsg.info(this, "Memory updated: " + address + " (" + data.length + ")");
|
TimedMsg.debug(this, "Memory updated: " + address + " (" + data.length + ")");
|
||||||
String path = memory.getJoinedPath(".");
|
String path = memory.getJoinedPath(".");
|
||||||
recorder.parTx.execute("Memory observed: " + path, () -> {
|
recorder.parTx.execute("Memory observed: " + path, () -> {
|
||||||
memoryManager.putBytes(snap, traceAddr, ByteBuffer.wrap(data));
|
memoryManager.putBytes(snap, traceAddr, ByteBuffer.wrap(data));
|
||||||
|
|
|
@ -618,7 +618,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
}
|
}
|
||||||
else if (event instanceof TraceRecorderAdvancedPluginEvent) {
|
else if (event instanceof TraceRecorderAdvancedPluginEvent) {
|
||||||
TraceRecorderAdvancedPluginEvent ev = (TraceRecorderAdvancedPluginEvent) event;
|
TraceRecorderAdvancedPluginEvent ev = (TraceRecorderAdvancedPluginEvent) event;
|
||||||
TimedMsg.info(this, "Processing trace-advanced event");
|
TimedMsg.debug(this, "Processing trace-advanced event");
|
||||||
doTraceRecorderAdvanced(ev.getRecorder(), ev.getSnap());
|
doTraceRecorderAdvanced(ev.getRecorder(), ev.getSnap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>WARNING:</b> 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
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* <b>IMPORTANT:</b> 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
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Please invoke this method from the Swing thread.
|
||||||
|
*
|
||||||
|
* @param action the action
|
||||||
|
*/
|
||||||
|
void removeResolutionAction(DockingActionIf action);
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -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,
|
||||||
|
"<html>You can take <b>action</b> to resolve this message</html>",
|
||||||
|
new ScreenShotActionContext());
|
||||||
|
|
||||||
|
AbstractGhidraHeadedDebuggerGUITest
|
||||||
|
.waitForPass(() -> assertEquals(3, consolePlugin.getRowCount(ActionContext.class)));
|
||||||
|
|
||||||
|
captureIsolatedProvider(consoleProvider, 600, 300);
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,6 @@ import ghidra.test.ToyProgramBuilder;
|
||||||
import ghidra.trace.database.ToyDBTraceBuilder;
|
import ghidra.trace.database.ToyDBTraceBuilder;
|
||||||
import ghidra.trace.model.memory.TraceMemoryFlag;
|
import ghidra.trace.model.memory.TraceMemoryFlag;
|
||||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||||
import ghidra.trace.model.modules.TraceModule;
|
|
||||||
import ghidra.trace.model.symbol.*;
|
import ghidra.trace.model.symbol.*;
|
||||||
import ghidra.trace.model.thread.TraceThread;
|
import ghidra.trace.model.thread.TraceThread;
|
||||||
import ghidra.util.database.UndoableTransaction;
|
import ghidra.util.database.UndoableTransaction;
|
||||||
|
@ -140,36 +139,4 @@ public class DebuggerListingPluginScreenShots extends GhidraScreenShotGenerator
|
||||||
|
|
||||||
captureDialog(dialog);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, "<html><b>Test message</b></html>",
|
||||||
|
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,
|
||||||
|
"<html><b>A rather lengthy test message. " +
|
||||||
|
"Here's some more text just to prove it!</b></html>",
|
||||||
|
new TestConsoleActionContext());
|
||||||
|
consolePlugin.log(DebuggerResources.ICON_DEBUGGER, "Test message 2",
|
||||||
|
new TestConsoleActionContext());
|
||||||
|
|
||||||
|
waitForPass(() -> assertEquals(2, consoleProvider.logTable.getRowCount()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractFollowsCurrentThreadAction;
|
||||||
import ghidra.app.plugin.core.debug.gui.action.*;
|
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.services.*;
|
||||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||||
import ghidra.async.SwingExecutorService;
|
import ghidra.async.SwingExecutorService;
|
||||||
|
@ -1078,9 +1080,9 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActionAutoImportCurrentModuleWithSections() throws Exception {
|
public void testPromptImportCurrentModuleWithSections() throws Exception {
|
||||||
addPlugin(tool, ImporterPlugin.class);
|
addPlugin(tool, ImporterPlugin.class);
|
||||||
assertTrue(listingProvider.actionAutoImportCurrentModule.isEnabled());
|
DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class);
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
@ -1099,16 +1101,19 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
// In the module, but not in its section
|
// In the module, but not in its section
|
||||||
listingPlugin.goTo(tb.addr(0x00411234), true);
|
listingPlugin.goTo(tb.addr(0x00411234), true);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
assertFalse(listingProvider.importDialog.isVisible());
|
waitForPass(() -> assertEquals(0,
|
||||||
|
consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class)));
|
||||||
|
|
||||||
listingPlugin.goTo(tb.addr(0x00401234), true);
|
listingPlugin.goTo(tb.addr(0x00401234), true);
|
||||||
waitForDialogComponent(DebuggerModuleImportDialog.class);
|
waitForSwing();
|
||||||
|
waitForPass(() -> assertEquals(1,
|
||||||
|
consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActionAutoImportCurrentModuleWithoutSections() throws Exception {
|
public void testPromptImportCurrentModuleWithoutSections() throws Exception {
|
||||||
addPlugin(tool, ImporterPlugin.class);
|
addPlugin(tool, ImporterPlugin.class);
|
||||||
assertTrue(listingProvider.actionAutoImportCurrentModule.isEnabled());
|
DebuggerConsolePlugin consolePlugin = addPlugin(tool, DebuggerConsolePlugin.class);
|
||||||
|
|
||||||
createAndOpenTrace();
|
createAndOpenTrace();
|
||||||
try (UndoableTransaction tid = tb.startTransaction()) {
|
try (UndoableTransaction tid = tb.startTransaction()) {
|
||||||
|
@ -1116,7 +1121,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
.addRegion("bash:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0041ffff),
|
.addRegion("bash:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0041ffff),
|
||||||
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE));
|
||||||
|
|
||||||
TraceModule bin = tb.trace.getModuleManager()
|
tb.trace.getModuleManager()
|
||||||
.addLoadedModule("/bin/bash", "/bin/bash",
|
.addLoadedModule("/bin/bash", "/bin/bash",
|
||||||
tb.range(0x00400000, 0x0041ffff), 0);
|
tb.range(0x00400000, 0x0041ffff), 0);
|
||||||
|
|
||||||
|
@ -1125,7 +1130,9 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
|
|
||||||
// In the module, but not in its section
|
// In the module, but not in its section
|
||||||
listingPlugin.goTo(tb.addr(0x00411234), true);
|
listingPlugin.goTo(tb.addr(0x00411234), true);
|
||||||
waitForDialogComponent(DebuggerModuleImportDialog.class);
|
waitForSwing();
|
||||||
|
waitForPass(() -> assertEquals(1,
|
||||||
|
consolePlugin.getRowCount(DebuggerMissingModuleActionContext.class)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
|
@ -165,7 +165,7 @@ public class TraceDomainObjectListener implements DomainObjectListener {
|
||||||
for (DomainObjectChangeRecord rec : ev) {
|
for (DomainObjectChangeRecord rec : ev) {
|
||||||
if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) {
|
if (rec.getEventType() == DomainObject.DO_OBJECT_RESTORED) {
|
||||||
restoredHandler.accept(rec);
|
restoredHandler.accept(rec);
|
||||||
TimedMsg.info(this, " Done: OBJECT_RESTORED");
|
TimedMsg.debug(this, " Done: OBJECT_RESTORED");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -328,6 +328,7 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T getTop(Function<Long, T> func) {
|
public <T> T getTop(Function<Long, T> func) {
|
||||||
|
try (LockHold hold = trace.lockRead()) {
|
||||||
synchronized (ordered) {
|
synchronized (ordered) {
|
||||||
for (Range<Long> rng : ordered) {
|
for (Range<Long> rng : ordered) {
|
||||||
T t = func.apply(rng.upperEndpoint());
|
T t = func.apply(rng.upperEndpoint());
|
||||||
|
@ -338,6 +339,7 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc,
|
public <T> Iterator<T> mergedIterator(Function<Long, Iterator<T>> iterFunc,
|
||||||
|
|
|
@ -18,10 +18,15 @@ package docking.widgets.table;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.function.BiFunction;
|
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.table.TableModel;
|
||||||
|
import javax.swing.text.View;
|
||||||
|
|
||||||
import ghidra.docking.settings.Settings;
|
import ghidra.docking.settings.Settings;
|
||||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||||
|
@ -40,6 +45,14 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
|
||||||
return v.signum() < 0 ? "-0x" + v.negate().toString(16) : "0x" + v.toString(16);
|
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<Date> TIME_24HMSms =
|
||||||
|
new CustomToStringCellRenderer<>(CustomFont.DEFAULT, Date.class,
|
||||||
|
(v, s) -> v == null ? "<null>" : TIME_FORMAT_24HMSms.format(v), false);
|
||||||
|
public static final CustomToStringCellRenderer<String> HTML =
|
||||||
|
new CustomToStringCellRenderer<String>(CustomFont.DEFAULT, String.class,
|
||||||
|
(v, s) -> v == null ? "<null>" : v, true);
|
||||||
public static final CustomToStringCellRenderer<Object> MONO_OBJECT =
|
public static final CustomToStringCellRenderer<Object> MONO_OBJECT =
|
||||||
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Object.class,
|
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Object.class,
|
||||||
(v, s) -> v == null ? "<null>" : v.toString(), false);
|
(v, s) -> v == null ? "<null>" : v.toString(), false);
|
||||||
|
@ -60,6 +73,9 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
|
||||||
private final Class<T> cls;
|
private final Class<T> cls;
|
||||||
private final BiFunction<T, Settings, String> toString;
|
private final BiFunction<T, Settings, String> toString;
|
||||||
|
|
||||||
|
private final JPanel panelForSize = new JPanel();
|
||||||
|
private final BoxLayout layoutForSize = new BoxLayout(panelForSize, BoxLayout.Y_AXIS);
|
||||||
|
|
||||||
public CustomToStringCellRenderer(Class<T> cls, BiFunction<T, Settings, String> toString,
|
public CustomToStringCellRenderer(Class<T> cls, BiFunction<T, Settings, String> toString,
|
||||||
boolean enableHtml) {
|
boolean enableHtml) {
|
||||||
this(null, cls, toString, enableHtml);
|
this(null, cls, toString, enableHtml);
|
||||||
|
@ -71,6 +87,8 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
|
||||||
this.customFont = font;
|
this.customFont = font;
|
||||||
this.cls = cls;
|
this.cls = cls;
|
||||||
this.toString = toString;
|
this.toString = toString;
|
||||||
|
|
||||||
|
panelForSize.setLayout(layoutForSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -100,6 +118,21 @@ public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
|
||||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||||
super.getTableCellRendererComponent(data);
|
super.getTableCellRendererComponent(data);
|
||||||
setText(toString.apply(cls.cast(data.getValue()), data.getColumnSettings()));
|
setText(toString.apply(cls.cast(data.getValue()), data.getColumnSettings()));
|
||||||
|
if (getHTMLRenderingEnabled()) {
|
||||||
|
setVerticalAlignment(SwingConstants.TOP);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setVerticalAlignment(SwingConstants.CENTER);
|
||||||
|
}
|
||||||
return this;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,19 +163,23 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
|
||||||
|
synchronized (modelData) {
|
||||||
R row = modelData.get(rowIndex);
|
R row = modelData.get(rowIndex);
|
||||||
C col = cols[columnIndex];
|
C col = cols[columnIndex];
|
||||||
Class<?> cls = col.getValueClass();
|
Class<?> cls = col.getValueClass();
|
||||||
col.setValueOf(row, cls.cast(aValue));
|
col.setValueOf(row, cls.cast(aValue));
|
||||||
|
}
|
||||||
fireTableCellUpdated(rowIndex, columnIndex);
|
fireTableCellUpdated(rowIndex, columnIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
public boolean isCellEditable(int rowIndex, int columnIndex) {
|
||||||
|
synchronized (modelData) {
|
||||||
R row = modelData.get(rowIndex);
|
R row = modelData.get(rowIndex);
|
||||||
C col = cols[columnIndex];
|
C col = cols[columnIndex];
|
||||||
return col.isEditable(row);
|
return col.isEditable(row);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSortable(int columnIndex) {
|
public boolean isSortable(int columnIndex) {
|
||||||
|
@ -200,30 +204,41 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(R row) {
|
public void add(R row) {
|
||||||
int rowIndex = modelData.size();
|
int rowIndex;
|
||||||
|
synchronized (modelData) {
|
||||||
|
rowIndex = modelData.size();
|
||||||
modelData.add(row);
|
modelData.add(row);
|
||||||
|
}
|
||||||
fireTableRowsInserted(rowIndex, rowIndex);
|
fireTableRowsInserted(rowIndex, rowIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addAll(Collection<R> c) {
|
public void addAll(Collection<R> c) {
|
||||||
int startIndex = modelData.size();
|
int startIndex;
|
||||||
|
int endIndex;
|
||||||
|
synchronized (modelData) {
|
||||||
|
startIndex = modelData.size();
|
||||||
modelData.addAll(c);
|
modelData.addAll(c);
|
||||||
int endIndex = modelData.size() - 1;
|
endIndex = modelData.size() - 1;
|
||||||
|
}
|
||||||
fireTableRowsInserted(startIndex, endIndex);
|
fireTableRowsInserted(startIndex, endIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void notifyUpdated(R row) {
|
public void notifyUpdated(R row) {
|
||||||
int rowIndex = modelData.indexOf(row);
|
int rowIndex;
|
||||||
|
synchronized (modelData) {
|
||||||
|
rowIndex = modelData.indexOf(row);
|
||||||
|
}
|
||||||
fireTableRowsUpdated(rowIndex, rowIndex);
|
fireTableRowsUpdated(rowIndex, rowIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<R> notifyUpdatedWith(Predicate<R> predicate) {
|
public List<R> notifyUpdatedWith(Predicate<R> predicate) {
|
||||||
int lastIndexUpdated = 0;
|
int lastIndexUpdated = 0;
|
||||||
ListIterator<R> rit = modelData.listIterator();
|
|
||||||
List<R> updated = new ArrayList<>();
|
List<R> updated = new ArrayList<>();
|
||||||
|
ListIterator<R> rit = modelData.listIterator();
|
||||||
|
synchronized (modelData) {
|
||||||
while (rit.hasNext()) {
|
while (rit.hasNext()) {
|
||||||
R row = rit.next();
|
R row = rit.next();
|
||||||
if (predicate.test(row)) {
|
if (predicate.test(row)) {
|
||||||
|
@ -231,6 +246,7 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
|
||||||
updated.add(row);
|
updated.add(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
int size = updated.size();
|
int size = updated.size();
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
}
|
}
|
||||||
|
@ -245,19 +261,23 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(R row) {
|
public void delete(R row) {
|
||||||
int rowIndex = modelData.indexOf(row);
|
int rowIndex;
|
||||||
|
synchronized (modelData) {
|
||||||
|
rowIndex = modelData.indexOf(row);
|
||||||
if (rowIndex == -1) {
|
if (rowIndex == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
modelData.remove(rowIndex);
|
modelData.remove(rowIndex);
|
||||||
|
}
|
||||||
fireTableRowsDeleted(rowIndex, rowIndex);
|
fireTableRowsDeleted(rowIndex, rowIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<R> deleteWith(Predicate<R> predicate) {
|
public List<R> deleteWith(Predicate<R> predicate) {
|
||||||
int lastIndexRemoved = 0;
|
int lastIndexRemoved = 0;
|
||||||
ListIterator<R> rit = modelData.listIterator();
|
|
||||||
List<R> removed = new ArrayList<>();
|
List<R> removed = new ArrayList<>();
|
||||||
|
synchronized (modelData) {
|
||||||
|
ListIterator<R> rit = modelData.listIterator();
|
||||||
while (rit.hasNext()) {
|
while (rit.hasNext()) {
|
||||||
R row = rit.next();
|
R row = rit.next();
|
||||||
if (predicate.test(row)) {
|
if (predicate.test(row)) {
|
||||||
|
@ -266,6 +286,7 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
|
||||||
removed.add(row);
|
removed.add(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
int size = removed.size();
|
int size = removed.size();
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
}
|
}
|
||||||
|
@ -280,6 +301,7 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public R findFirst(Predicate<R> predicate) {
|
public R findFirst(Predicate<R> predicate) {
|
||||||
|
synchronized (modelData) {
|
||||||
for (R row : modelData) {
|
for (R row : modelData) {
|
||||||
if (predicate.test(row)) {
|
if (predicate.test(row)) {
|
||||||
return row;
|
return row;
|
||||||
|
@ -287,10 +309,20 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
synchronized (modelData) {
|
||||||
modelData.clear();
|
modelData.clear();
|
||||||
|
}
|
||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sort(List<R> data, TableSortingContext<R> sortingContext) {
|
||||||
|
synchronized (data) {
|
||||||
|
super.sort(data, sortingContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,12 @@ public class RowWrappedEnumeratedColumnTableModel<C extends Enum<C> & Enumerated
|
||||||
return map.computeIfAbsent(keyFunc.apply(t), k -> wrapper.apply(t));
|
return map.computeIfAbsent(keyFunc.apply(t), k -> wrapper.apply(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected synchronized R delFor(T t) {
|
protected R delFor(T t) {
|
||||||
return map.remove(keyFunc.apply(t));
|
return delKey(keyFunc.apply(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized R delKey(K k) {
|
||||||
|
return map.remove(k);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected synchronized List<R> rowsFor(Collection<? extends T> c) {
|
protected synchronized List<R> rowsFor(Collection<? extends T> c) {
|
||||||
|
@ -79,9 +83,15 @@ public class RowWrappedEnumeratedColumnTableModel<C extends Enum<C> & Enumerated
|
||||||
delete(delFor(t));
|
delete(delFor(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public R deleteKey(K k) {
|
||||||
|
R r = delKey(k);
|
||||||
|
delete(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void deleteAllItems(Collection<T> c) {
|
public synchronized void deleteAllItems(Collection<T> c) {
|
||||||
deleteWith(rowsFor(c)::contains);
|
deleteWith(rowsFor(c)::contains);
|
||||||
map.keySet().removeAll(c);
|
map.keySet().removeAll(c.stream().map(keyFunc).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Map<K, R> getMap() {
|
public synchronized Map<K, R> getMap() {
|
||||||
|
|
|
@ -34,7 +34,7 @@ public class TimedMsg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void info(Object originator, String message) {
|
public static void debug(Object originator, String message) {
|
||||||
doMsg(Msg::info, originator, message);
|
doMsg(Msg::debug, originator, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue