mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-511: Added "Pin" toggle to interpreters.
This commit is contained in:
parent
5ea7996b7d
commit
593e9b4a22
10 changed files with 266 additions and 111 deletions
|
@ -37,6 +37,7 @@ 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/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|
|
||||||
|
|
|
@ -78,28 +78,32 @@
|
||||||
sortgroup="d"
|
sortgroup="d"
|
||||||
target="help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html" />
|
target="help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerThreadsPlugin" text="Threads and Traces"
|
<tocdef id="DebuggerInterpreterPlugin" text="Interpreters"
|
||||||
sortgroup="e"
|
sortgroup="e"
|
||||||
|
target="help/topics/DebuggerInterpreterPlugin/DebuggerInterpreterPlugin.html" />
|
||||||
|
|
||||||
|
<tocdef id="DebuggerThreadsPlugin" text="Threads and Traces"
|
||||||
|
sortgroup="f"
|
||||||
target="help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html" />
|
target="help/topics/DebuggerThreadsPlugin/DebuggerThreadsPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerTraceManagerServicePlugin" text="Trace Management"
|
<tocdef id="DebuggerTraceManagerServicePlugin" text="Trace Management"
|
||||||
sortgroup="f"
|
sortgroup="g"
|
||||||
target="help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html" />
|
target="help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerRegistersPlugin" text="Registers"
|
<tocdef id="DebuggerRegistersPlugin" text="Registers"
|
||||||
sortgroup="g"
|
sortgroup="h"
|
||||||
target="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html" />
|
target="help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerListingPlugin" text="Dynamic Listing"
|
<tocdef id="DebuggerListingPlugin" text="Dynamic Listing"
|
||||||
sortgroup="h"
|
sortgroup="i"
|
||||||
target="help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html" />
|
target="help/topics/DebuggerListingPlugin/DebuggerListingPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerStackPlugin" text="Stack"
|
<tocdef id="DebuggerStackPlugin" text="Stack"
|
||||||
sortgroup="i"
|
sortgroup="j"
|
||||||
target="help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html" />
|
target="help/topics/DebuggerStackPlugin/DebuggerStackPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerBreakpointsPlugin" text="Breakpoints"
|
<tocdef id="DebuggerBreakpointsPlugin" text="Breakpoints"
|
||||||
sortgroup="j"
|
sortgroup="k"
|
||||||
target="help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html" >
|
target="help/topics/DebuggerBreakpointsPlugin/DebuggerBreakpointsPlugin.html" >
|
||||||
|
|
||||||
<tocdef id="DebuggerBreakpointMarkerPlugin" text="In the Listings"
|
<tocdef id="DebuggerBreakpointMarkerPlugin" text="In the Listings"
|
||||||
|
@ -108,15 +112,15 @@
|
||||||
</tocdef>
|
</tocdef>
|
||||||
|
|
||||||
<tocdef id="DebuggerRegionsPlugin" text="Memory Regions"
|
<tocdef id="DebuggerRegionsPlugin" text="Memory Regions"
|
||||||
sortgroup="k"
|
sortgroup="l"
|
||||||
target="help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html" />
|
target="help/topics/DebuggerRegionsPlugin/DebuggerRegionsPlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerTimePlugin" text="Time"
|
<tocdef id="DebuggerTimePlugin" text="Time"
|
||||||
sortgroup="l"
|
sortgroup="m"
|
||||||
target="help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html" />
|
target="help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html" />
|
||||||
|
|
||||||
<tocdef id="DebuggerModulesPlugin" text="Modules and Sections"
|
<tocdef id="DebuggerModulesPlugin" text="Modules and Sections"
|
||||||
sortgroup="m"
|
sortgroup="n"
|
||||||
target="help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html" >
|
target="help/topics/DebuggerModulesPlugin/DebuggerModulesPlugin.html" >
|
||||||
|
|
||||||
<tocdef id="DebuggerStaticMappingPlugin" text="Static Mappings"
|
<tocdef id="DebuggerStaticMappingPlugin" text="Static Mappings"
|
||||||
|
@ -125,7 +129,7 @@
|
||||||
</tocdef>
|
</tocdef>
|
||||||
|
|
||||||
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
|
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
|
||||||
sortgroup="n"
|
sortgroup="o"
|
||||||
target="help/topics/DebuggerBots/DebuggerBots.html" />
|
target="help/topics/DebuggerBots/DebuggerBots.html" />
|
||||||
</tocdef>
|
</tocdef>
|
||||||
</tocref>
|
</tocref>
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<!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: Interpreters</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><A name="interpreter"></A>Debugger: Interpreters</H1>
|
||||||
|
|
||||||
|
<P>For debuggers which have built-in interpreters (many do), and whose connectors expose that
|
||||||
|
interpreter in the model, the interpreters plugin can provide user access to it via a graphical
|
||||||
|
console emulator. The plugin leverages the existing interpreter console framework in Ghidra, so
|
||||||
|
the interface should be relatively familiar. Typically, the console is accessed via the Objects
|
||||||
|
window's <A href=
|
||||||
|
"help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html#console">Console</A> action.
|
||||||
|
Output is displayed in a large text field, and user input is taken via a small text field at
|
||||||
|
the bottom. The prompt, commands, and outputs are all defined by the connector.</P>
|
||||||
|
|
||||||
|
<H2>Actions</H2>
|
||||||
|
|
||||||
|
<P>Each interpreter console has the following actions:</P>
|
||||||
|
|
||||||
|
<H3><A name="Clear_Interpreter"></A>Clear Interpreter</H3>
|
||||||
|
|
||||||
|
<P>This action is always available. It clears the console's ouput buffer.</P>
|
||||||
|
|
||||||
|
<H3><A name="Remove_Interpreter"></A>Remove Interpreter</H3>
|
||||||
|
|
||||||
|
<P>This action appears when the target's interpreter is not longer valid, i.e., the connection
|
||||||
|
was closed. It permanently closes the console. Note this action can only appear if this console
|
||||||
|
was pinned.</P>
|
||||||
|
|
||||||
|
<H3><A name="pin"></A>Pin Interpreter</H3>
|
||||||
|
|
||||||
|
<P>This action is always available. Normally interpreter consoles are permanently closed
|
||||||
|
immediately upon the associated target interpreter becoming invalid, i.e., the connection was
|
||||||
|
closed. Pinning an interpreter keeps it open, but in a disabled state, so that the buffer can
|
||||||
|
be examined after invalidation.</P>
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
|
@ -128,12 +128,12 @@ public interface DebuggerResources {
|
||||||
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");
|
||||||
|
|
||||||
|
ImageIcon ICON_PIN = ResourceManager.loadImage("images/pin.png");
|
||||||
// TODO: Find better icon?
|
// TODO: Find better icon?
|
||||||
ImageIcon ICON_IMPORT = ResourceManager.loadImage("images/imported_bookmark.gif");
|
ImageIcon ICON_IMPORT = ResourceManager.loadImage("images/imported_bookmark.gif");
|
||||||
|
|
||||||
ImageIcon ICON_BLANK = ResourceManager.loadImage("images/blank.png");
|
ImageIcon ICON_BLANK = ResourceManager.loadImage("images/blank.png");
|
||||||
|
|
||||||
ImageIcon ICON_PACKAGE = ResourceManager.loadImage("images/debugger32.png");
|
ImageIcon ICON_PACKAGE = ResourceManager.loadImage("images/debugger32.png");
|
||||||
|
|
||||||
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
|
HelpLocation HELP_PACKAGE = new HelpLocation("Debugger", "package");
|
||||||
|
|
||||||
String HELP_ANCHOR_PLUGIN = "plugin";
|
String HELP_ANCHOR_PLUGIN = "plugin";
|
||||||
|
@ -557,12 +557,28 @@ public interface DebuggerResources {
|
||||||
String HELP_ANCHOR = "disconnect_all";
|
String HELP_ANCHOR = "disconnect_all";
|
||||||
|
|
||||||
public static ActionBuilder builder(Plugin owner, Plugin helpOwner) {
|
public static ActionBuilder builder(Plugin owner, Plugin helpOwner) {
|
||||||
return new ActionBuilder(owner.getName(), NAME).description(DESCRIPTION)
|
return new ActionBuilder(owner.getName(), NAME)
|
||||||
|
.description(DESCRIPTION)
|
||||||
.menuIcon(ICON)
|
.menuIcon(ICON)
|
||||||
.helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR));
|
.helpLocation(new HelpLocation(helpOwner.getName(), HELP_ANCHOR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PinInterpreterAction {
|
||||||
|
String NAME = "Pin Interpreter";
|
||||||
|
String DESCRIPTION = "Prevent this Interpreter from closing automatically";
|
||||||
|
Icon ICON = ICON_PIN;
|
||||||
|
String HELP_ANCHOR = "pin";
|
||||||
|
|
||||||
|
public static ToggleActionBuilder builder(Plugin owner) {
|
||||||
|
String ownerName = owner.getName();
|
||||||
|
return new ToggleActionBuilder(ownerName, NAME)
|
||||||
|
.description(DESCRIPTION)
|
||||||
|
.toolBarIcon(ICON)
|
||||||
|
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract class AbstractRecordAction extends DockingAction {
|
abstract class AbstractRecordAction extends DockingAction {
|
||||||
public static final String NAME = "Record";
|
public static final String NAME = "Record";
|
||||||
public static final Icon ICON = ICON_TRACE;
|
public static final Icon ICON = ICON_TRACE;
|
||||||
|
|
|
@ -23,9 +23,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
|
||||||
|
import docking.ActionContext;
|
||||||
|
import docking.action.ToggleDockingAction;
|
||||||
import ghidra.app.plugin.core.console.CodeCompletion;
|
import ghidra.app.plugin.core.console.CodeCompletion;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.app.plugin.core.interpreter.InterpreterConnection;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources.PinInterpreterAction;
|
||||||
import ghidra.app.plugin.core.interpreter.InterpreterConsole;
|
import ghidra.app.plugin.core.interpreter.InterpreterConsole;
|
||||||
import ghidra.dbg.target.TargetConsole.Channel;
|
import ghidra.dbg.target.TargetConsole.Channel;
|
||||||
import ghidra.dbg.target.TargetConsole.TargetConsoleListener;
|
import ghidra.dbg.target.TargetConsole.TargetConsoleListener;
|
||||||
|
@ -35,7 +37,7 @@ import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetObject>
|
public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetObject>
|
||||||
implements InterpreterConnection {
|
implements DebuggerInterpreterConnection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We inherit console text output from interpreter listener, even though we may be listening to
|
* We inherit console text output from interpreter listener, even though we may be listening to
|
||||||
|
@ -75,8 +77,13 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||||
@Override
|
@Override
|
||||||
public void invalidated(TargetObject object, String reason) {
|
public void invalidated(TargetObject object, String reason) {
|
||||||
if (object == targetConsole) { // Redundant
|
if (object == targetConsole) { // Redundant
|
||||||
running.set(false);
|
if (pinned) {
|
||||||
plugin.disableConsole(targetConsole, guiConsole);
|
running.set(false);
|
||||||
|
plugin.disableConsole(targetConsole, guiConsole);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
plugin.destroyConsole(targetConsole, guiConsole);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +99,12 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||||
protected PrintWriter outWriter;
|
protected PrintWriter outWriter;
|
||||||
protected PrintWriter errWriter;
|
protected PrintWriter errWriter;
|
||||||
|
|
||||||
|
protected ToggleDockingAction actionPin;
|
||||||
|
protected boolean pinned = false;
|
||||||
|
|
||||||
|
// TODO: Fix InterpreterPanelService to take plugin name instead of just using title
|
||||||
|
protected boolean firstTimeAskedTitle = true;
|
||||||
|
|
||||||
public AbstractDebuggerWrappedConsoleConnection(DebuggerInterpreterPlugin plugin,
|
public AbstractDebuggerWrappedConsoleConnection(DebuggerInterpreterPlugin plugin,
|
||||||
T targetConsole) {
|
T targetConsole) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
@ -103,7 +116,11 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTitle() {
|
public String getTitle() {
|
||||||
return "Interpreter: " + targetConsole.getDisplay();
|
if (firstTimeAskedTitle) {
|
||||||
|
firstTimeAskedTitle = false;
|
||||||
|
return plugin.getName();
|
||||||
|
}
|
||||||
|
return targetConsole.getDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -114,14 +131,26 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||||
@Override
|
@Override
|
||||||
public List<CodeCompletion> getCompletions(String cmd) {
|
public List<CodeCompletion> getCompletions(String cmd) {
|
||||||
// TODO: If GDB or WinDBG ever provides an API for completion....
|
// TODO: If GDB or WinDBG ever provides an API for completion....
|
||||||
|
// TODO: Of course, that's another method on TargetInterpeter, too.
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConsole(InterpreterConsole guiConsole) {
|
public void setConsole(InterpreterConsole guiConsole) {
|
||||||
|
assert this.guiConsole == null;
|
||||||
this.guiConsole = guiConsole;
|
this.guiConsole = guiConsole;
|
||||||
setErrWriter(guiConsole.getErrWriter());
|
setErrWriter(guiConsole.getErrWriter());
|
||||||
setOutWriter(guiConsole.getOutWriter());
|
setOutWriter(guiConsole.getOutWriter());
|
||||||
setStdIn(guiConsole.getStdin());
|
setStdIn(guiConsole.getStdin());
|
||||||
|
|
||||||
|
createActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createActions() {
|
||||||
|
actionPin = PinInterpreterAction.builder(plugin)
|
||||||
|
.onAction(this::activatedPin)
|
||||||
|
.selected(pinned)
|
||||||
|
.build();
|
||||||
|
guiConsole.addAction(actionPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOutWriter(PrintWriter outWriter) {
|
public void setOutWriter(PrintWriter outWriter) {
|
||||||
|
@ -142,6 +171,10 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void activatedPin(ActionContext ignore) {
|
||||||
|
pinned = actionPin.isSelected();
|
||||||
|
}
|
||||||
|
|
||||||
protected void run() {
|
protected void run() {
|
||||||
try {
|
try {
|
||||||
while (running.get()) {
|
while (running.get()) {
|
||||||
|
@ -164,4 +197,25 @@ public abstract class AbstractDebuggerWrappedConsoleConnection<T extends TargetO
|
||||||
Msg.debug(this, "Lost console?");
|
Msg.debug(this, "Lost console?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InterpreterConsole getInterpreterConsole() {
|
||||||
|
return guiConsole;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TargetObject getTargetConsole() {
|
||||||
|
return targetConsole;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPinned() {
|
||||||
|
return pinned;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPinned(boolean pinned) {
|
||||||
|
this.pinned = pinned;
|
||||||
|
actionPin.setSelected(pinned);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/* ###
|
||||||
|
* 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.interpreters;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.interpreter.InterpreterConnection;
|
||||||
|
import ghidra.app.plugin.core.interpreter.InterpreterConsole;
|
||||||
|
import ghidra.dbg.target.TargetObject;
|
||||||
|
|
||||||
|
public interface DebuggerInterpreterConnection extends InterpreterConnection {
|
||||||
|
void setPinned(boolean pinned);
|
||||||
|
|
||||||
|
boolean isPinned();
|
||||||
|
|
||||||
|
TargetObject getTargetConsole();
|
||||||
|
|
||||||
|
InterpreterConsole getInterpreterConsole();
|
||||||
|
}
|
|
@ -51,66 +51,77 @@ public class DebuggerInterpreterPlugin extends AbstractDebuggerPlugin
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
protected InterpreterPanelService consoleService;
|
protected InterpreterPanelService consoleService;
|
||||||
|
|
||||||
protected final Map<TargetObject, InterpreterConsole> consoles = new HashMap<>();
|
protected final Map<TargetObject, DebuggerInterpreterConnection> connections = new HashMap<>();
|
||||||
|
|
||||||
public DebuggerInterpreterPlugin(PluginTool tool) {
|
public DebuggerInterpreterPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showConsole(TargetConsole<?> targetConsole) {
|
public DebuggerInterpreterConnection showConsole(TargetConsole<?> targetConsole) {
|
||||||
InterpreterConsole console;
|
DebuggerInterpreterConnection conn;
|
||||||
synchronized (consoles) {
|
synchronized (connections) {
|
||||||
console = consoles.computeIfAbsent(targetConsole, c -> createConsole(targetConsole));
|
conn = connections.computeIfAbsent(targetConsole, c -> createConnection(targetConsole));
|
||||||
}
|
}
|
||||||
console.show();
|
conn.getInterpreterConsole().show();
|
||||||
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showConsole(TargetInterpreter<?> targetInterpreter) {
|
public DebuggerInterpreterConnection showConsole(TargetInterpreter<?> targetInterpreter) {
|
||||||
InterpreterConsole console;
|
DebuggerInterpreterConnection conn;
|
||||||
synchronized (consoles) {
|
synchronized (connections) {
|
||||||
console =
|
conn = connections.computeIfAbsent(targetInterpreter,
|
||||||
consoles.computeIfAbsent(targetInterpreter, c -> createConsole(targetInterpreter));
|
c -> createConnection(targetInterpreter));
|
||||||
}
|
}
|
||||||
console.show();
|
conn.getInterpreterConsole().show();
|
||||||
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void disableConsole(TargetObject targetConsole, InterpreterConsole guiConsole) {
|
protected void disableConsole(TargetObject targetConsole, InterpreterConsole guiConsole) {
|
||||||
InterpreterConsole old = consoles.get(targetConsole);
|
DebuggerInterpreterConnection old;
|
||||||
assert old == guiConsole;
|
synchronized (connections) {
|
||||||
|
old = connections.remove(targetConsole);
|
||||||
|
}
|
||||||
|
assert old.getInterpreterConsole() == guiConsole;
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
if (guiConsole.isInputPermitted()) {
|
if (guiConsole.isInputPermitted()) {
|
||||||
guiConsole.setInputPermitted(false);
|
guiConsole.setInputPermitted(false);
|
||||||
guiConsole.setTransient();
|
guiConsole.setTransient();
|
||||||
guiConsole.setPrompt(">>INVALID<<");
|
guiConsole.setPrompt(">>INVALID<<");
|
||||||
/**
|
|
||||||
* TODO: Should invisible ones just be removed?
|
|
||||||
*
|
|
||||||
* TODO: Would like setTransient to work like other providers, but
|
|
||||||
* InterpreterComponentProvider overrides it.... For now, I leave invisible disabled
|
|
||||||
* ones in the tool. User must show and click custom "remove interpreter" action.
|
|
||||||
*/
|
|
||||||
/*if (!console.isVisible()) {
|
|
||||||
console.dispose();
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InterpreterConsole createConsole(
|
protected void createConsole(AbstractDebuggerWrappedConsoleConnection<?> connection) {
|
||||||
AbstractDebuggerWrappedConsoleConnection<?> connection) {
|
|
||||||
InterpreterConsole console = consoleService.createInterpreterPanel(connection, true);
|
InterpreterConsole console = consoleService.createInterpreterPanel(connection, true);
|
||||||
connection.setConsole(console);
|
connection.setConsole(console);
|
||||||
connection.runInBackground();
|
connection.runInBackground();
|
||||||
return console;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InterpreterConsole createConsole(TargetConsole<?> targetConsole) {
|
protected DebuggerInterpreterConnection createConnection(TargetConsole<?> targetConsole) {
|
||||||
return createConsole(new DebuggerWrappedConsoleConnection(this, targetConsole));
|
DebuggerWrappedConsoleConnection conn =
|
||||||
|
new DebuggerWrappedConsoleConnection(this, targetConsole);
|
||||||
|
createConsole(conn);
|
||||||
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InterpreterConsole createConsole(TargetInterpreter<?> targetInterpreter) {
|
protected DebuggerInterpreterConnection createConnection(
|
||||||
return createConsole(new DebuggerWrappedInterpreterConnection(this, targetInterpreter));
|
TargetInterpreter<?> targetInterpreter) {
|
||||||
|
DebuggerWrappedInterpreterConnection conn =
|
||||||
|
new DebuggerWrappedInterpreterConnection(this, targetInterpreter);
|
||||||
|
createConsole(conn);
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroyConsole(TargetObject targetConsole, InterpreterConsole guiConsole) {
|
||||||
|
DebuggerInterpreterConnection old;
|
||||||
|
synchronized (connections) {
|
||||||
|
old = connections.remove(targetConsole);
|
||||||
|
}
|
||||||
|
assert old.getInterpreterConsole() == guiConsole;
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
guiConsole.dispose();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -301,32 +301,37 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeAllTraces() {
|
public void closeAllTraces() {
|
||||||
for (Trace trace : getOpenTraces()) {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
closeTrace(trace);
|
for (Trace trace : getOpenTraces()) {
|
||||||
}
|
closeTrace(trace);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeOtherTraces(Trace keep) {
|
public void closeOtherTraces(Trace keep) {
|
||||||
for (Trace trace : getOpenTraces()) {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
if (trace != keep) {
|
for (Trace trace : getOpenTraces()) {
|
||||||
closeTrace(trace);
|
if (trace != keep) {
|
||||||
|
closeTrace(trace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeDeadTraces() {
|
public void closeDeadTraces() {
|
||||||
if (modelService == null) {
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
return;
|
if (modelService == null) {
|
||||||
}
|
return;
|
||||||
for (Trace trace : getOpenTraces()) {
|
|
||||||
TraceRecorder recorder = modelService.getRecorder(trace);
|
|
||||||
if (recorder == null) {
|
|
||||||
closeTrace(trace);
|
|
||||||
}
|
}
|
||||||
}
|
for (Trace trace : getOpenTraces()) {
|
||||||
|
TraceRecorder recorder = modelService.getRecorder(trace);
|
||||||
|
if (recorder == null) {
|
||||||
|
closeTrace(trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
@ -837,10 +842,16 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeTrace(Trace trace) {
|
public void closeTrace(Trace trace) {
|
||||||
if (trace.getConsumerList().contains(this)) {
|
/**
|
||||||
firePluginEvent(new TraceClosedPluginEvent(getName(), trace));
|
* A provider may be reading the trace, likely via the Swing thread, so schedule this on the
|
||||||
doTraceClosed(trace);
|
* same thread to avoid a ClosedException.
|
||||||
}
|
*/
|
||||||
|
Swing.runIfSwingOrRunLater(() -> {
|
||||||
|
if (trace.getConsumerList().contains(this)) {
|
||||||
|
firePluginEvent(new TraceClosedPluginEvent(getName(), trace));
|
||||||
|
doTraceClosed(trace);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.services;
|
package ghidra.app.services;
|
||||||
|
|
||||||
|
import ghidra.app.plugin.core.debug.gui.interpreters.DebuggerInterpreterConnection;
|
||||||
import ghidra.app.plugin.core.debug.gui.interpreters.DebuggerInterpreterPlugin;
|
import ghidra.app.plugin.core.debug.gui.interpreters.DebuggerInterpreterPlugin;
|
||||||
import ghidra.dbg.target.TargetConsole;
|
import ghidra.dbg.target.TargetConsole;
|
||||||
import ghidra.dbg.target.TargetInterpreter;
|
import ghidra.dbg.target.TargetInterpreter;
|
||||||
|
@ -25,7 +26,7 @@ import ghidra.framework.plugintool.ServiceInfo;
|
||||||
description = "Service for managing debugger interpreter panels" //
|
description = "Service for managing debugger interpreter panels" //
|
||||||
)
|
)
|
||||||
public interface DebuggerInterpreterService {
|
public interface DebuggerInterpreterService {
|
||||||
void showConsole(TargetConsole<?> console);
|
DebuggerInterpreterConnection showConsole(TargetConsole<?> console);
|
||||||
|
|
||||||
void showConsole(TargetInterpreter<?> interpreter);
|
DebuggerInterpreterConnection showConsole(TargetInterpreter<?> interpreter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,8 @@ import java.awt.event.KeyEvent;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||||
import ghidra.app.plugin.core.interpreter.InterpreterComponentProvider;
|
import ghidra.app.plugin.core.interpreter.InterpreterComponentProvider;
|
||||||
|
@ -112,7 +113,7 @@ public class DebuggerInterpreterPluginTest extends AbstractGhidraHeadedDebuggerG
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidateInterpreterDisablesConsole() throws Exception {
|
public void testInvalidateInterpreterDestroysConsole() throws Exception {
|
||||||
createTestModel();
|
createTestModel();
|
||||||
interpreterPlugin.showConsole(mb.testModel.session.interpreter);
|
interpreterPlugin.showConsole(mb.testModel.session.interpreter);
|
||||||
InterpreterComponentProvider interpreter =
|
InterpreterComponentProvider interpreter =
|
||||||
|
@ -123,44 +124,24 @@ public class DebuggerInterpreterPluginTest extends AbstractGhidraHeadedDebuggerG
|
||||||
), Map.of(), "Invalidate interpreter");
|
), Map.of(), "Invalidate interpreter");
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
|
assertFalse(interpreter.isVisible());
|
||||||
|
assertFalse(interpreter.isInTool());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidatePinnedInterpreterDisablesConsole() throws Exception {
|
||||||
|
createTestModel();
|
||||||
|
DebuggerInterpreterConnection conn =
|
||||||
|
interpreterPlugin.showConsole(mb.testModel.session.interpreter);
|
||||||
|
InterpreterComponentProvider interpreter =
|
||||||
|
waitForComponentProvider(InterpreterComponentProvider.class);
|
||||||
|
conn.setPinned(true);
|
||||||
|
|
||||||
|
mb.testModel.session.changeAttributes(List.of(
|
||||||
|
"Interpreter" //
|
||||||
|
), Map.of(), "Invalidate interpreter");
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
assertFalse(interpreter.isInputPermitted());
|
assertFalse(interpreter.isInputPermitted());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore("Haven't decided on proper behavior")
|
|
||||||
public void testInvalidateClosedDestroysConsole() throws Exception {
|
|
||||||
createTestModel();
|
|
||||||
interpreterPlugin.showConsole(mb.testModel.session.interpreter);
|
|
||||||
InterpreterComponentProvider interpreter =
|
|
||||||
waitForComponentProvider(InterpreterComponentProvider.class);
|
|
||||||
|
|
||||||
interpreter.setVisible(false);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
mb.testModel.session.changeAttributes(List.of(
|
|
||||||
"Interpreter" //
|
|
||||||
), Map.of(), "Invalidate interpreter");
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertFalse(interpreter.isInTool());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore("Haven't decided on proper behavior")
|
|
||||||
public void testCloseInvalidatedDestroysConsole() throws Exception {
|
|
||||||
createTestModel();
|
|
||||||
interpreterPlugin.showConsole(mb.testModel.session.interpreter);
|
|
||||||
InterpreterComponentProvider interpreter =
|
|
||||||
waitForComponentProvider(InterpreterComponentProvider.class);
|
|
||||||
|
|
||||||
mb.testModel.session.changeAttributes(List.of(
|
|
||||||
"Interpreter" //
|
|
||||||
), Map.of(), "Invalidate interpreter");
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
interpreter.setVisible(false);
|
|
||||||
waitForSwing();
|
|
||||||
|
|
||||||
assertFalse(interpreter.isInTool());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue