mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-24 08:26:05 +08:00
GP-7005: Adjust how re-launch works and improve launch menu.
This commit is contained in:
+24
-31
@@ -13,19 +13,17 @@
|
||||
<BODY lang="EN-US">
|
||||
<H1><A name="plugin"></A>Debugger: Launchers</H1>
|
||||
|
||||
<P>The Debugger has an updated and simplified launcher system. A nice collection of basic
|
||||
launchers for our supported platforms are provided out of the box. For Linux, we provide a
|
||||
suite of GDB-based launchers. For macOS, we provide a suite of LLDB-based launchers (though,
|
||||
these work on Linux, too). For Windows, we provide a launcher based on the Windows Debugger
|
||||
(<TT>dbgeng.dll</TT> and <TT>dbgmodel.dll</TT>). Help is available for each in its respective
|
||||
sub-topic.</P>
|
||||
<P>A nice collection of basic launchers for our supported platforms are provided out of the
|
||||
box. For Linux, we provide a suite of GDB-based launchers. For macOS, we provide a suite of
|
||||
LLDB-based launchers (though, these work on Linux, too). For Windows, we provide a launcher
|
||||
based on the Windows Debugger (<TT>dbgeng.dll</TT> and <TT>dbgmodel.dll</TT>). Help is
|
||||
available for each in its respective sub-topic.</P>
|
||||
|
||||
<P>Each launcher automates the creation of a Trace RMI <A href=
|
||||
"help/topics/TraceRmiConnectionManagerPlugin/TraceRmiConnectionManagerPlugin.html#connect_accept">acceptor</A>,
|
||||
executes the back-end shell script in a Terminal, then waits for the resulting target trace. In
|
||||
contrast to the previous system, the Terminal is the first and most basic interface presented.
|
||||
Even if just about everything else goes wrong, the terminal should still be faithfully
|
||||
operational:</P>
|
||||
executes the back-end shell script in a Terminal, then waits for the resulting target trace.
|
||||
The Terminal is the first and most basic interface presented. Even if just about everything
|
||||
else goes wrong, the terminal should still operate faithfully:</P>
|
||||
|
||||
<DIV class="image">
|
||||
<IMG alt="" src="images/GdbTerminal.png">
|
||||
@@ -35,28 +33,23 @@
|
||||
the rest of Ghidra's windows. It provides fairly robust VT-100 emulation. Thus, the user
|
||||
experience from the Terminal is nearly identical to using the same debugger outside of Ghidra.
|
||||
This terminal-first approach also ensures that you interact with the target application's
|
||||
standard I/O. This was not possible in the previous system, as we re-implemented the CLI using
|
||||
the back end's <TT>execute</TT> method. The debugger's (and so also the target's) actual I/O
|
||||
streams were hidden away within a GDB/MI wrapper.</P>
|
||||
standard I/O.</P>
|
||||
|
||||
<P>Each launcher script sets up a — usually Python — environment, launches the
|
||||
actual debugger, and provides a sequence of commands for it to load the Trace RMI plugin,
|
||||
connect back to Ghidra, launch the actual target process, and start the target trace. At this
|
||||
point, the plugin generally takes over, reacting to user and target events, accepting front-end
|
||||
requests, and generally keeping Ghidra and the back end synchronized.</P>
|
||||
<P>Each launcher script sets up an environment, launches the actual debugger, and provides a
|
||||
sequence of commands for it to load the Trace RMI plugin, connect back to Ghidra, launch the
|
||||
actual target process, and start the target trace. At this point, the plugin generally takes
|
||||
over, reacting to user and target events, accepting front-end requests, and generally keeping
|
||||
Ghidra and the back end synchronized.</P>
|
||||
|
||||
<P><A name="launch_tracermi"></A>The list of launchers can be accessed in either of two places:
|
||||
1) In the <B>Debugger → Configure and Launch ...</B> menu or more conveniently from the
|
||||
<B>Launch</B> button in the main toolbar. This is the blue bug <IMG alt="" src="icon.debugger">
|
||||
button near the top center. The <B>Configure and Launch ...</B> menu lists all available
|
||||
launchers. Selecting one will prompt for its options then launch. To re-launch quickly, use the
|
||||
<B>Launch</B> button. Clicking it will re-launch using the most recent launcher and
|
||||
configuration for the current program. If this is the first launch of the given program, the
|
||||
button will instead activate its drop-down menu. The drop-down is also accessible by clicking
|
||||
the down arrow next to the <B>Launch</B> button. The drop-down lists all launchers that have
|
||||
been previously configured for the current program. Clicking one will immediately launch the
|
||||
program without prompting. The <B>Configure and Launch ...</B> sub-menu of the drop-down
|
||||
functions exactly like in the <B>Debugger</B> menu.</P>
|
||||
<P><A name="launch_tracermi"></A>The list of launchers can be accessed conveniently from the
|
||||
<B>Launch</B> button's drop-down menu in the main toolbar. This is the blue bug <IMG alt=""
|
||||
src="icon.debugger"> button near the top center. The <B>Launch [program] ...</B> submenus list
|
||||
all available launchers for each open program. There is also an <B>Empty session ...</B>
|
||||
submenu which lists all launchers that can operate without a program. Selecting one will prompt
|
||||
for its options then launch. To quickly re-launch using the most recent configuration, click
|
||||
the <B>Launch</B> button — not its drop-down. The drop-down menu also lists the most
|
||||
recent launch configuration. Clicking it will immediately launch without prompting, unless you
|
||||
hold <B>SHIFT</B>.</P>
|
||||
|
||||
<P>The Terminal provides some fairly standard actions. Other keyboard control sequences,
|
||||
notably <B>CTRL-C</B>, are interpreted by the terminal, rather than Ghidra's action system, to
|
||||
@@ -134,7 +127,7 @@
|
||||
<UL style="list-style-type: none">
|
||||
<LI>
|
||||
<PRE>
|
||||
python3 -m pip install protobuf>=3.20.3
|
||||
python3 -m pip install protobuf>=3.20.3
|
||||
</PRE>
|
||||
</LI>
|
||||
</UL>
|
||||
|
||||
+5
-4
@@ -181,7 +181,7 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
};
|
||||
mappingService.addChangeListener(result.listener);
|
||||
result.check();
|
||||
result.exceptionally(ex -> {
|
||||
result.exceptionally(_ -> {
|
||||
mappingService.removeChangeListener(result.listener);
|
||||
return null;
|
||||
});
|
||||
@@ -229,11 +229,12 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||
}
|
||||
|
||||
protected void saveState(SaveState state) {
|
||||
plugin.writeToolLaunchConfig(getConfigName(), state);
|
||||
if (program == null) {
|
||||
return;
|
||||
plugin.writeToolLaunchConfig(getConfigName(), state);
|
||||
}
|
||||
else {
|
||||
plugin.writeProgramLaunchConfig(program, getConfigName(), state);
|
||||
}
|
||||
plugin.writeProgramLaunchConfig(program, getConfigName(), state);
|
||||
}
|
||||
|
||||
protected void saveLauncherArgs(Map<String, ValStr<?>> args,
|
||||
|
||||
+2
-7
@@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@@ -38,11 +38,6 @@ public abstract class AbstractTraceRmiLaunchOpinion implements TraceRmiLaunchOpi
|
||||
() -> new ScriptPathsPropertyEditor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresRefresh(String optionName) {
|
||||
return TraceRmiLauncherServicePlugin.OPTION_NAME_SCRIPT_PATHS.equals(optionName);
|
||||
}
|
||||
|
||||
protected Stream<ResourceFile> getModuleScriptPaths() {
|
||||
return Application.findModuleSubDirectories("data/debugger-launchers").stream();
|
||||
}
|
||||
|
||||
+202
-52
@@ -25,10 +25,10 @@ import javax.swing.*;
|
||||
import docking.ActionContext;
|
||||
import docking.PopupMenuHandler;
|
||||
import docking.action.*;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.menu.*;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.tracermi.launcher.TraceRmiLauncherServicePlugin.ConfigLast;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.HelpLocation;
|
||||
@@ -50,49 +50,192 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
setHelpLocation(new HelpLocation(plugin.getName(), HELP_ANCHOR));
|
||||
}
|
||||
|
||||
protected String[] prependConfigAndLaunch(List<String> menuPath) {
|
||||
Program program = plugin.currentProgram;
|
||||
String title = program == null
|
||||
? "Configure and Launch ..."
|
||||
: "Configure and Launch %s using...".formatted(getProgramName(program));
|
||||
return Stream.concat(Stream.of(title), menuPath.stream()).toArray(String[]::new);
|
||||
protected static String[] prependMenuPath(String pre, List<String> menuPath) {
|
||||
return Stream.concat(Stream.of(pre), menuPath.stream()).toArray(String[]::new);
|
||||
}
|
||||
|
||||
static abstract class AbstractLaunchOfferAction extends DockingAction {
|
||||
final TraceRmiLauncherServicePlugin plugin;
|
||||
final TraceRmiLaunchOffer offer;
|
||||
|
||||
public AbstractLaunchOfferAction(TraceRmiLauncherServicePlugin plugin,
|
||||
TraceRmiLaunchOffer offer) {
|
||||
this.plugin = plugin;
|
||||
this.offer = offer;
|
||||
super(offer.getConfigName(), plugin.getName());
|
||||
setHelpLocation(offer.getHelpLocation());
|
||||
setPopupMenuData(computeMenuData());
|
||||
}
|
||||
|
||||
abstract MenuData computeMenuData();
|
||||
|
||||
String getTopGroup(Program currentProgram) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String getTopOrder(Program currentProgram) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
plugin.configureAndLaunch(offer);
|
||||
}
|
||||
}
|
||||
|
||||
static class ProgramLaunchOfferAction extends AbstractLaunchOfferAction {
|
||||
final Program program;
|
||||
|
||||
public ProgramLaunchOfferAction(TraceRmiLauncherServicePlugin plugin,
|
||||
TraceRmiLaunchOffer offer, Program program) {
|
||||
this.program = program;
|
||||
super(plugin, offer);
|
||||
}
|
||||
|
||||
@Override
|
||||
MenuData computeMenuData() {
|
||||
return new MenuData(
|
||||
prependMenuPath("Launch %s ...".formatted(getProgramName(program)),
|
||||
offer.getMenuPath()),
|
||||
offer.getIcon(), offer.getMenuGroup(), 0, offer.getMenuOrder());
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTopGroup(Program currentProgram) {
|
||||
return "2";
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTopOrder(Program currentProgram) {
|
||||
return program == currentProgram ? "1" : "2";
|
||||
}
|
||||
}
|
||||
|
||||
static class EmptyLaunchOfferAction extends AbstractLaunchOfferAction {
|
||||
public EmptyLaunchOfferAction(TraceRmiLauncherServicePlugin plugin,
|
||||
TraceRmiLaunchOffer offer) {
|
||||
super(plugin, offer);
|
||||
}
|
||||
|
||||
@Override
|
||||
MenuData computeMenuData() {
|
||||
return new MenuData(
|
||||
prependMenuPath("Empty session ...", offer.getMenuPath()),
|
||||
offer.getIcon(), offer.getMenuGroup(), 0, offer.getMenuOrder());
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTopGroup(Program currentProgram) {
|
||||
return "2";
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTopOrder(Program currentProgram) {
|
||||
return "3";
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class AbstractReLaunchOfferAction extends AbstractLaunchOfferAction {
|
||||
public AbstractReLaunchOfferAction(TraceRmiLauncherServicePlugin plugin,
|
||||
TraceRmiLaunchOffer offer) {
|
||||
super(plugin, offer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
plugin.relaunchOrConfigure(context, offer);
|
||||
}
|
||||
}
|
||||
|
||||
static class TopReLaunchOfferAction extends AbstractReLaunchOfferAction {
|
||||
final Program program;
|
||||
|
||||
public TopReLaunchOfferAction(TraceRmiLauncherServicePlugin plugin,
|
||||
TraceRmiLaunchOffer offer, Program program) {
|
||||
this.program = program;
|
||||
super(plugin, offer);
|
||||
}
|
||||
|
||||
@Override
|
||||
MenuData computeMenuData() {
|
||||
String title = program == null
|
||||
? "Empty %s session".formatted(offer.getTitle())
|
||||
: "Re-launch %s in %s".formatted(getProgramName(program), offer.getTitle());
|
||||
return new MenuData(new String[] { title }, offer.getIcon(), "0: top");
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTopGroup(Program currentProgram) {
|
||||
return "0";
|
||||
}
|
||||
}
|
||||
|
||||
static class ProgramReLaunchOfferAction extends AbstractReLaunchOfferAction {
|
||||
final Program program;
|
||||
|
||||
public ProgramReLaunchOfferAction(TraceRmiLauncherServicePlugin plugin,
|
||||
TraceRmiLaunchOffer offer, Program program) {
|
||||
this.program = program;
|
||||
super(plugin, offer);
|
||||
}
|
||||
|
||||
@Override
|
||||
MenuData computeMenuData() {
|
||||
return new MenuData(
|
||||
prependMenuPath("Re-launch %s ...".formatted(getProgramName(program)),
|
||||
offer.getMenuPath()),
|
||||
offer.getIcon(), offer.getMenuGroup(), 0, offer.getMenuOrder());
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTopGroup(Program currentProgram) {
|
||||
return "1";
|
||||
}
|
||||
|
||||
@Override
|
||||
String getTopOrder(Program currentProgram) {
|
||||
return program == currentProgram ? "1" : "2";
|
||||
}
|
||||
}
|
||||
|
||||
public void collectActionsForProgram(List<DockingActionIf> actions, Program program) {
|
||||
Collection<TraceRmiLaunchOffer> offers = plugin.getOffers(program);
|
||||
Map<String, Long> saved = plugin.loadSavedConfigs(program);
|
||||
for (TraceRmiLaunchOffer offer : offers) {
|
||||
if (program != null) {
|
||||
actions.add(new ProgramLaunchOfferAction(plugin, offer, program));
|
||||
}
|
||||
else if (!offer.requiresImage()) {
|
||||
actions.add(new EmptyLaunchOfferAction(plugin, offer));
|
||||
}
|
||||
Long last = saved.get(offer.getConfigName());
|
||||
if (last == null) {
|
||||
continue;
|
||||
}
|
||||
if (program != null) {
|
||||
actions.add(new ProgramReLaunchOfferAction(plugin, offer, program));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingActionIf> getActionList(ActionContext context) {
|
||||
Program program = plugin.currentProgram;
|
||||
Collection<TraceRmiLaunchOffer> offers = plugin.getOffers(program);
|
||||
|
||||
ProgramManager programManager = plugin.getTool().getService(ProgramManager.class);
|
||||
List<Program> allPrograms = List.of(programManager.getAllOpenPrograms());
|
||||
List<DockingActionIf> actions = new ArrayList<>();
|
||||
|
||||
Map<String, Long> saved = plugin.loadSavedConfigs(program);
|
||||
|
||||
for (TraceRmiLaunchOffer offer : offers) {
|
||||
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
|
||||
.popupMenuPath(prependConfigAndLaunch(offer.getMenuPath()))
|
||||
.popupMenuGroup(offer.getMenuGroup(), offer.getMenuOrder())
|
||||
.popupMenuIcon(offer.getIcon())
|
||||
.helpLocation(offer.getHelpLocation())
|
||||
.enabledWhen(ctx -> !offer.requiresImage() || program != null)
|
||||
.onAction(ctx -> plugin.configureAndLaunch(offer))
|
||||
.build());
|
||||
Long last = saved.get(offer.getConfigName());
|
||||
if (last == null) {
|
||||
// NB. If program == null, this will always happen.
|
||||
// Thus, no worries about getProgramName(program) below.
|
||||
continue;
|
||||
}
|
||||
String title = program == null
|
||||
? "Re-launch " + offer.getTitle()
|
||||
: "Re-launch %s using %s".formatted(getProgramName(program), offer.getTitle());
|
||||
actions.add(new ActionBuilder(offer.getConfigName(), plugin.getName())
|
||||
.popupMenuPath(title)
|
||||
.popupMenuGroup("0", "%016x".formatted(Long.MAX_VALUE - last))
|
||||
.popupMenuIcon(offer.getIcon())
|
||||
.helpLocation(offer.getHelpLocation())
|
||||
.enabledWhen(ctx -> true)
|
||||
.onAction(ctx -> plugin.relaunch(ctx, offer))
|
||||
.build());
|
||||
for (Program program : allPrograms) {
|
||||
collectActionsForProgram(actions, program);
|
||||
}
|
||||
collectActionsForProgram(actions, null);
|
||||
ConfigLast last = plugin.findMostRecentConfig();
|
||||
TraceRmiLaunchOffer offer = plugin.findOffer(last);
|
||||
if (offer != null) {
|
||||
actions.add(new TopReLaunchOfferAction(plugin, offer, last.program()));
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
@@ -104,13 +247,24 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
|
||||
@Override
|
||||
protected JPopupMenu doCreateMenu() {
|
||||
ProgramManager programManager = plugin.getTool().getService(ProgramManager.class);
|
||||
Program currentProgram =
|
||||
programManager == null ? null : programManager.getCurrentProgram();
|
||||
ActionContext context = getActionContext();
|
||||
List<DockingActionIf> actionList = getActionList(context);
|
||||
MenuHandler handler =
|
||||
new PopupMenuHandler(plugin.getTool().getWindowManager(), context);
|
||||
MenuGroupMap groupMap = new MenuGroupMap();
|
||||
MenuManager manager =
|
||||
new MenuManager("Launch", (char) 0, GROUP, true, handler, null);
|
||||
new MenuManager("Launch", (char) 0, GROUP, true, handler, groupMap);
|
||||
for (DockingActionIf action : actionList) {
|
||||
if (action instanceof AbstractLaunchOfferAction loa) {
|
||||
String[] path = action.getPopupMenuData().getMenuPath();
|
||||
String[] topPath = Arrays.copyOf(path, 1);
|
||||
groupMap.setMenuGroup(topPath,
|
||||
loa.getTopGroup(currentProgram),
|
||||
loa.getTopOrder(currentProgram));
|
||||
}
|
||||
action.setEnabled(action.isEnabledForContext(context));
|
||||
manager.addAction(action);
|
||||
}
|
||||
@@ -136,35 +290,31 @@ public class LaunchAction extends MultiActionDockingAction {
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
return !plugin.getOffers(plugin.currentProgram).isEmpty();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
// See comment on super method about use of runLater
|
||||
ConfigLast last = plugin.findMostRecentConfig(plugin.currentProgram);
|
||||
ConfigLast last = plugin.findMostRecentConfig();
|
||||
TraceRmiLaunchOffer offer = plugin.findOffer(last);
|
||||
if (offer == null) {
|
||||
Swing.runLater(() -> button.showPopup());
|
||||
return;
|
||||
}
|
||||
plugin.relaunch(context, offer);
|
||||
plugin.relaunchOrConfigure(context, offer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
Program program = plugin.currentProgram;
|
||||
ConfigLast last = plugin.findMostRecentConfig(program);
|
||||
ConfigLast last = plugin.findMostRecentConfig();
|
||||
TraceRmiLaunchOffer offer = plugin.findOffer(last);
|
||||
if (offer == null && program == null) {
|
||||
return "Configure and launch";
|
||||
}
|
||||
if (offer == null) {
|
||||
return "Configure and launch " + getProgramName(program);
|
||||
return "Launch ...";
|
||||
}
|
||||
if (program == null) {
|
||||
return "Re-launch " + offer.getTitle();
|
||||
if (last.program() == null) {
|
||||
return "Empty %s session".formatted(offer.getTitle());
|
||||
}
|
||||
return "Re-launch %s using %s".formatted(getProgramName(program), offer.getTitle());
|
||||
return "Re-launch %s in %s".formatted(getProgramName(last.program()), offer.getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
+12
-81
@@ -27,13 +27,10 @@ import org.jdom2.JDOMException;
|
||||
|
||||
import db.Transaction;
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||
import ghidra.app.events.ProgramClosedPluginEvent;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DebugProgramAction;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
|
||||
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.LaunchConfigurator;
|
||||
@@ -41,7 +38,8 @@ import ghidra.debug.api.tracermi.TraceRmiLaunchOffer.PromptMode;
|
||||
import ghidra.debug.spi.tracermi.TraceRmiLaunchOpinion;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
@@ -52,7 +50,6 @@ import ghidra.program.model.lang.ProcessorNotFoundException;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.scalar.Scalar;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
@@ -78,8 +75,7 @@ import ghidra.util.xml.XmlUtilities;
|
||||
servicesProvided = {
|
||||
TraceRmiLauncherService.class,
|
||||
})
|
||||
public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
implements TraceRmiLauncherService, OptionsChangeListener {
|
||||
public class TraceRmiLauncherServicePlugin extends Plugin implements TraceRmiLauncherService {
|
||||
protected static final String KEY_DBGLAUNCH = "DBGLAUNCH";
|
||||
protected static final String PREFIX_DBGLAUNCH = "DBGLAUNCH_";
|
||||
protected static final String KEY_LAST = "last";
|
||||
@@ -280,16 +276,13 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
|
||||
protected final ToolOptions options;
|
||||
|
||||
protected Program currentProgram;
|
||||
protected LaunchAction launchAction;
|
||||
protected List<DockingActionIf> currentLaunchers = new ArrayList<>();
|
||||
|
||||
protected SaveState toolLaunchConfigs = new SaveState();
|
||||
|
||||
public TraceRmiLauncherServicePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
this.options = tool.getOptions(DebuggerPluginPackage.NAME);
|
||||
this.options.addOptionsChangeListener(this);
|
||||
createActions();
|
||||
}
|
||||
|
||||
@@ -307,18 +300,6 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
tool.addAction(launchAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) throws OptionsVetoException {
|
||||
for (TraceRmiLaunchOpinion opinion : ClassSearcher
|
||||
.getInstances(TraceRmiLaunchOpinion.class)) {
|
||||
if (opinion.requiresRefresh(optionName)) {
|
||||
updateLauncherMenu();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<TraceRmiLaunchOffer> getOffers(Program program) {
|
||||
return ClassSearcher.getInstances(TraceRmiLaunchOpinion.class)
|
||||
@@ -350,7 +331,7 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
executeTask(new ReLaunchTask(offer));
|
||||
}
|
||||
|
||||
protected void relaunch(ActionContext ctx, TraceRmiLaunchOffer offer) {
|
||||
protected void relaunchOrConfigure(ActionContext ctx, TraceRmiLaunchOffer offer) {
|
||||
int mods = ctx == null ? 0 : ctx.getEventClickModifiers();
|
||||
if ((mods & ActionEvent.SHIFT_MASK) != 0) {
|
||||
configureAndLaunch(offer);
|
||||
@@ -372,59 +353,6 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
return program.getName();
|
||||
}
|
||||
|
||||
protected String[] constructLaunchMenuPrefix() {
|
||||
return new String[] {
|
||||
DebuggerPluginPackage.NAME,
|
||||
"Configure and Launch " + getProgramName(currentProgram) + " using..." };
|
||||
}
|
||||
|
||||
protected String[] prependConfigAndLaunch(List<String> menuPath) {
|
||||
return Stream.concat(
|
||||
Stream.of(constructLaunchMenuPrefix()),
|
||||
menuPath.stream()).toArray(String[]::new);
|
||||
}
|
||||
|
||||
private void updateLauncherMenu() {
|
||||
Collection<TraceRmiLaunchOffer> offers = currentProgram == null
|
||||
? List.of()
|
||||
: getOffers(currentProgram);
|
||||
synchronized (currentLaunchers) {
|
||||
for (DockingActionIf launcher : currentLaunchers) {
|
||||
tool.removeAction(launcher);
|
||||
}
|
||||
currentLaunchers.clear();
|
||||
|
||||
if (!offers.isEmpty()) {
|
||||
tool.setMenuGroup(constructLaunchMenuPrefix(), DebugProgramAction.GROUP, "zz");
|
||||
}
|
||||
for (TraceRmiLaunchOffer offer : offers) {
|
||||
currentLaunchers.add(new ActionBuilder(offer.getConfigName(), getName())
|
||||
.menuPath(prependConfigAndLaunch(offer.getMenuPath()))
|
||||
.menuGroup(offer.getMenuGroup(), offer.getMenuOrder())
|
||||
.menuIcon(offer.getIcon())
|
||||
.helpLocation(offer.getHelpLocation())
|
||||
.enabledWhen(ctx -> true)
|
||||
.onAction(ctx -> configureAndLaunch(offer))
|
||||
.buildAndInstall(tool));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processEvent(PluginEvent event) {
|
||||
super.processEvent(event);
|
||||
if (event instanceof ProgramActivatedPluginEvent evt) {
|
||||
currentProgram = evt.getActiveProgram();
|
||||
updateLauncherMenu();
|
||||
}
|
||||
if (event instanceof ProgramClosedPluginEvent evt) {
|
||||
if (currentProgram == evt.getProgram()) {
|
||||
currentProgram = null;
|
||||
updateLauncherMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readConfigState(SaveState saveState) {
|
||||
super.readConfigState(saveState);
|
||||
@@ -479,7 +407,7 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
protected void writeProgramLaunchConfig(Program program, String name, SaveState state) {
|
||||
ProgramUserData userData = program.getProgramUserData();
|
||||
state.putLong(KEY_LAST, System.currentTimeMillis());
|
||||
try (Transaction tx = userData.openTransaction()) {
|
||||
try (Transaction _ = userData.openTransaction()) {
|
||||
Element element = state.saveToXml();
|
||||
userData.setStringProperty(PREFIX_DBGLAUNCH + name, XmlUtilities.toString(element));
|
||||
}
|
||||
@@ -490,8 +418,7 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
toolLaunchConfigs.putSaveState(name, state);
|
||||
}
|
||||
|
||||
protected record ConfigLast(String configName, long last, Program program) {
|
||||
}
|
||||
protected record ConfigLast(String configName, long last, Program program) {}
|
||||
|
||||
protected ConfigLast checkSavedConfig(Program program, ProgramUserData userData,
|
||||
String propName) {
|
||||
@@ -532,8 +459,12 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||
.filter(c -> c != null);
|
||||
}
|
||||
|
||||
protected ConfigLast findMostRecentConfig(Program program) {
|
||||
return streamSavedConfigs(program).max(Comparator.comparing(c -> c.last)).orElse(null);
|
||||
protected ConfigLast findMostRecentConfig() {
|
||||
return Stream.concat(Stream.of(tool.getService(ProgramManager.class).getAllOpenPrograms())
|
||||
.flatMap(this::streamSavedConfigs),
|
||||
this.streamSavedConfigs(null))
|
||||
.max(Comparator.comparing(c -> c.last))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
protected TraceRmiLaunchOffer findOffer(ConfigLast last) {
|
||||
|
||||
-10
@@ -41,16 +41,6 @@ public interface TraceRmiLaunchOpinion extends ExtensionPoint {
|
||||
default void registerOptions(Options options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a change in the given option requires a refresh of offers
|
||||
*
|
||||
* @param optionName the name of the option that changed
|
||||
* @return true to refresh, false otherwise
|
||||
*/
|
||||
default boolean requiresRefresh(String optionName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate or retrieve a collection of offers based on the current program.
|
||||
*
|
||||
|
||||
@@ -27,7 +27,7 @@ import docking.action.MenuData;
|
||||
* Class to manage a hierarchy of menus.
|
||||
*/
|
||||
public class MenuManager implements ManagedMenuItem {
|
||||
private static String NULL_GROUP_NAME = "<null group>";
|
||||
private static final String NULL_GROUP_NAME = "<null group>";
|
||||
|
||||
private Set<ManagedMenuItem> managedMenuItems = new HashSet<>();
|
||||
private Map<String, MenuManager> subMenus = new HashMap<>();
|
||||
|
||||
@@ -129,9 +129,6 @@ icon in my Tool Chest</a></li>
|
||||
<li><a href="#there-is-no-debug-launch-icon-in-the-global-toolbar"
|
||||
id="toc-there-is-no-debug-launch-icon-in-the-global-toolbar">There is no
|
||||
Debug / Launch icon in the global toolbar</a></li>
|
||||
<li><a href="#there-is-no-gdb-option-in-the-launch-drop-down"
|
||||
id="toc-there-is-no-gdb-option-in-the-launch-drop-down">There is no
|
||||
<strong>gdb</strong> option in the launch drop-down</a></li>
|
||||
<li><a
|
||||
href="#the-launch-hangs-for-several-seconds-and-then-i-get-prompted-with-a-wall-of-text"
|
||||
id="toc-the-launch-hangs-for-several-seconds-and-then-i-get-prompted-with-a-wall-of-text">The
|
||||
@@ -234,8 +231,7 @@ open</figcaption>
|
||||
</figure></li>
|
||||
<li><p>In the Debugger tool, click the dropdown ▾ for the debug <img
|
||||
src="images/debugger.png" alt="debug button" /> icon in the global tool
|
||||
bar, and select <strong>Configure and Launch termmines using… →
|
||||
gdb</strong>.</p>
|
||||
bar, and select <strong>Launch termmines … → gdb</strong>.</p>
|
||||
<figure>
|
||||
<img src="images/GettingStarted_LaunchGDBDialog.png"
|
||||
alt="Launch GDB Dialog" />
|
||||
@@ -263,7 +259,7 @@ specimen. This is the engine that backs WinDbg. You may choose an
|
||||
alternative Minesweeper, since terminal applications are less
|
||||
representative of Windows executables. Follow the same process as for
|
||||
Linux, except import <code>termmines.exe</code> and select
|
||||
<strong>Configure and Launch termmines.exe using… → dbgeng</strong>.</p>
|
||||
<strong>Launch termmines.exe … → dbgeng</strong>.</p>
|
||||
</section>
|
||||
<section id="launching-on-macos" class="level2">
|
||||
<h2>Launching on macOS</h2>
|
||||
@@ -300,19 +296,6 @@ tool. If it is still not there, then you may need to re-import the
|
||||
default Debugger tool as under the previous heading. If it is still not
|
||||
there, your installation may be corrupt.</p>
|
||||
</section>
|
||||
<section id="there-is-no-gdb-option-in-the-launch-drop-down"
|
||||
class="level3">
|
||||
<h3>There is no <strong>gdb</strong> option in the launch drop-down</h3>
|
||||
<p>You may have an older Debugger tool still configured for
|
||||
Recorder-based targets. We are transitioning to TraceRmi-based targets.
|
||||
Delete your Debugger tool and re-import the default one using the
|
||||
instructions above. If it is still not there, it’s possible your
|
||||
installation is corrupt. Search for a file called
|
||||
<code>local-gdb.sh</code> in your installation. Unlike the previous
|
||||
system, Trace RMI will not probe your system for dependencies nor hide
|
||||
incompatible launchers. All installed launchers should be present in the
|
||||
menus, even though some may not work on your configuration.</p>
|
||||
</section>
|
||||
<section
|
||||
id="the-launch-hangs-for-several-seconds-and-then-i-get-prompted-with-a-wall-of-text"
|
||||
class="level3">
|
||||
@@ -329,9 +312,11 @@ you are missing <code>gdb</code>, or you need to tell Ghidra where to
|
||||
find it.</p>
|
||||
<p>If it is just missing, then install it and try again. If you need to
|
||||
tell Ghidra where it is, then in the launcher drop-down, select
|
||||
<strong>Configure and Launch termmines using… → gdb</strong>. DO NOT
|
||||
select <strong>Re-launch termmines using gdb</strong>, since this will
|
||||
not allow you to correct the configuration.</p>
|
||||
<strong>Launch termmines … → gdb</strong>. Alternatively, hold
|
||||
<strong><code>SHIFT</code></strong> and select <strong>Re-launch
|
||||
termmines in gdb</strong>. If you forget to hold
|
||||
<strong><code>SHIFT</code></strong>, it will not prompt you before
|
||||
launching.</p>
|
||||
<p>If it looks like there’s an error about importing python packages,
|
||||
e.g., “google protobuf,” then you need to install some dependencies.
|
||||
These are listed in the launcher’s description. For your convenience,
|
||||
@@ -365,12 +350,13 @@ class="level4">
|
||||
specimen has a <code>main</code> symbol. <strong>NOTE</strong>: It is
|
||||
not sufficient to place a <code>main</code> label in Ghidra. The
|
||||
original file must have a <code>main</code> symbol.</p>
|
||||
<p>Alternatively, in the menus try <strong>Debugger → Configure and
|
||||
Launch termmines using → gdb</strong>, and select “starti” for
|
||||
<strong>Run Command</strong>. This will break at the system entry point.
|
||||
If you have labeled <code>main</code> in Ghidra, then you can place a
|
||||
breakpoint there and continue — these features are covered later in the
|
||||
course.</p>
|
||||
<p>Alternatively, from the launcher drop-down, hold
|
||||
<strong><code>SHIFT</code></strong> and click <strong>Re-launch
|
||||
termmines in gdb</strong>. Try selecting “starti” for <strong>Run
|
||||
Command</strong>, then launch. This will break at the system entry
|
||||
point. If you have labeled <code>main</code> in Ghidra, then you can
|
||||
place a breakpoint there and continue — these features are covered later
|
||||
in the course.</p>
|
||||
<p>Alternatively, try debugging the target in GDB from a separate
|
||||
terminal completely outside of Ghidra to see if things work as
|
||||
expected.</p>
|
||||
@@ -426,16 +412,16 @@ exercise. Disconnect before proceeding to the next exercise.</p>
|
||||
<h2>Customized Launching</h2>
|
||||
<p>For this specimen, you may occasionally need to provide custom
|
||||
command-line parameters. By default, Ghidra attempts to launch the
|
||||
target without any parameters. In the <strong>Debugger</strong> menu, or
|
||||
the <strong>Launch</strong> button’s drop-down menu, use
|
||||
<strong>Configure and Launch termmmines → gdb</strong> to adjust your
|
||||
configuration. This is where you can specify the image path and
|
||||
command-line parameters of your target. Ghidra will remember this
|
||||
configuration the next time you launch using the drop-down button from
|
||||
the toolbar. Launchers with memorized configurations are presented as
|
||||
<strong>Re-launch termmines using…</strong> options. Using one of those
|
||||
entries will re-launch with the saved configuration rather than
|
||||
prompting.</p>
|
||||
target without any parameters. In the <strong>Launch</strong> button’s
|
||||
drop-down menu, select <strong>Launch termmmines … → gdb</strong> to
|
||||
adjust your configuration. This is where you can specify the image path
|
||||
and command-line parameters of your target. Ghidra will save this
|
||||
configuration when you launch. Launchers with saved configurations are
|
||||
presented as entries in the <strong>Re-launch [program] …</strong>
|
||||
submenu. The most-recently saved entry is also presented at the top of
|
||||
the launch menu. Selecting one of those entries will re-launch that
|
||||
configuration. To adjust a saved configuration, hold
|
||||
<strong><code>SHIFT</code></strong> while selecting its entry.</p>
|
||||
</section>
|
||||
<section id="exercise-launch-with-command-line-help" class="level2">
|
||||
<h2>Exercise: Launch with Command-line Help</h2>
|
||||
@@ -449,18 +435,14 @@ its usage, and as a result, the rest of the UI will be mostly empty.</p>
|
||||
<p>Attaching is slightly more advanced, but can be useful if the target
|
||||
is part of a larger system, and it needs to be running <em>in situ</em>.
|
||||
For this section, we will just run <code>termmines</code> in a separate
|
||||
terminal and then attach to it from Ghidra. This used to be required,
|
||||
because the older Recorder-based system did not provide target I/O, but
|
||||
this limitation is overcome by the new <strong>Terminal</strong> window
|
||||
when using Trace RMI. Note this technique is only possible because the
|
||||
target waits for input.</p>
|
||||
terminal and then attach to it from Ghidra. Note this technique is only
|
||||
possible because the target waits for input.</p>
|
||||
<ol type="1">
|
||||
<li>Run <code>termmines</code> in a terminal outside of Ghidra with the
|
||||
desired command-line parameters.</li>
|
||||
<li>In the Ghidra Debugger, use the <strong>Launch</strong> button
|
||||
drop-down and select <strong>Configure and Launch termmines using… →
|
||||
gdb</strong>.</li>
|
||||
<li>Clear the <strong>Image</strong> field to configure a GDB session
|
||||
drop-down and select <strong>Empty session … → gdb</strong>. The
|
||||
<strong>Image</strong> field should be blank to configure a GDB session
|
||||
without a target.</li>
|
||||
<li>Ghidra needs to know the location of gdb and the architecture of the
|
||||
intended target. The defaults are correct for 64-bit x86 targets using
|
||||
|
||||
@@ -58,7 +58,7 @@ There are many ways to do this, but for the sake of simplicity, import and launc
|
||||
|
||||

|
||||
|
||||
1. In the Debugger tool, click the dropdown ▾ for the debug  icon in the global tool bar, and select **Configure and Launch termmines using... → gdb**.
|
||||
1. In the Debugger tool, click the dropdown ▾ for the debug  icon in the global tool bar, and select **Launch termmines ... → gdb**.
|
||||
|
||||

|
||||
|
||||
@@ -74,7 +74,7 @@ There are many ways to do this, but for the sake of simplicity, import and launc
|
||||
On Windows, we will use the Windows Debugger dbgeng.dll to debug the specimen.
|
||||
This is the engine that backs WinDbg.
|
||||
You may choose an alternative Minesweeper, since terminal applications are less representative of Windows executables.
|
||||
Follow the same process as for Linux, except import `termmines.exe` and select **Configure and Launch termmines.exe using... → dbgeng**.
|
||||
Follow the same process as for Linux, except import `termmines.exe` and select **Launch termmines.exe ... → dbgeng**.
|
||||
|
||||
## Launching on macOS
|
||||
|
||||
@@ -106,16 +106,6 @@ Double-check that you are in the Debugger tool, not the CodeBrowser tool.
|
||||
If it is still not there, then you may need to re-import the default Debugger tool as under the previous heading.
|
||||
If it is still not there, your installation may be corrupt.
|
||||
|
||||
### There is no **gdb** option in the launch drop-down
|
||||
|
||||
You may have an older Debugger tool still configured for Recorder-based targets.
|
||||
We are transitioning to TraceRmi-based targets.
|
||||
Delete your Debugger tool and re-import the default one using the instructions above.
|
||||
If it is still not there, it's possible your installation is corrupt.
|
||||
Search for a file called `local-gdb.sh` in your installation.
|
||||
Unlike the previous system, Trace RMI will not probe your system for dependencies nor hide incompatible launchers.
|
||||
All installed launchers should be present in the menus, even though some may not work on your configuration.
|
||||
|
||||
### The launch hangs for several seconds and then I get prompted with a wall of text
|
||||
|
||||
Read the wall of text.
|
||||
@@ -127,8 +117,9 @@ Once you have found the Terminal, check its output *starting at the top* for dia
|
||||
If you have something like `bash: gdb: command not found`, it is because you are missing `gdb`, or you need to tell Ghidra where to find it.
|
||||
|
||||
If it is just missing, then install it and try again.
|
||||
If you need to tell Ghidra where it is, then in the launcher drop-down, select **Configure and Launch termmines using... → gdb**.
|
||||
DO NOT select **Re-launch termmines using gdb**, since this will not allow you to correct the configuration.
|
||||
If you need to tell Ghidra where it is, then in the launcher drop-down, select **Launch termmines ... → gdb**.
|
||||
Alternatively, hold **`SHIFT`** and select **Re-launch termmines in gdb**.
|
||||
If you forget to hold **`SHIFT`**, it will not prompt you before launching.
|
||||
|
||||
If it looks like there's an error about importing python packages, e.g., "google protobuf," then you need to install some dependencies.
|
||||
These are listed in the launcher's description.
|
||||
@@ -157,7 +148,8 @@ Check that the specimen has a `main` symbol.
|
||||
**NOTE**: It is not sufficient to place a `main` label in Ghidra.
|
||||
The original file must have a `main` symbol.
|
||||
|
||||
Alternatively, in the menus try **Debugger → Configure and Launch termmines using → gdb**, and select "starti" for **Run Command**.
|
||||
Alternatively, from the launcher drop-down, hold **`SHIFT`** and click **Re-launch termmines in gdb**.
|
||||
Try selecting "starti" for **Run Command**, then launch.
|
||||
This will break at the system entry point.
|
||||
If you have labeled `main` in Ghidra, then you can place a breakpoint there and continue — these features are covered later in the course.
|
||||
|
||||
@@ -198,11 +190,13 @@ Disconnect before proceeding to the next exercise.
|
||||
|
||||
For this specimen, you may occasionally need to provide custom command-line parameters.
|
||||
By default, Ghidra attempts to launch the target without any parameters.
|
||||
In the **Debugger** menu, or the **Launch** button's drop-down menu, use **Configure and Launch termmmines → gdb** to adjust your configuration.
|
||||
In the **Launch** button's drop-down menu, select **Launch termmmines ... → gdb** to adjust your configuration.
|
||||
This is where you can specify the image path and command-line parameters of your target.
|
||||
Ghidra will remember this configuration the next time you launch using the drop-down button from the toolbar.
|
||||
Launchers with memorized configurations are presented as **Re-launch termmines using...** options.
|
||||
Using one of those entries will re-launch with the saved configuration rather than prompting.
|
||||
Ghidra will save this configuration when you launch.
|
||||
Launchers with saved configurations are presented as entries in the **Re-launch [program] ...** submenu.
|
||||
The most-recently saved entry is also presented at the top of the launch menu.
|
||||
Selecting one of those entries will re-launch that configuration.
|
||||
To adjust a saved configuration, hold **`SHIFT`** while selecting its entry.
|
||||
|
||||
## Exercise: Launch with Command-line Help
|
||||
|
||||
@@ -214,13 +208,11 @@ When successful, you will see the usage info in the Debugger's **Terminal** wind
|
||||
|
||||
Attaching is slightly more advanced, but can be useful if the target is part of a larger system, and it needs to be running *in situ*.
|
||||
For this section, we will just run `termmines` in a separate terminal and then attach to it from Ghidra.
|
||||
This used to be required, because the older Recorder-based system did not provide target I/O, but this limitation is overcome by the new **Terminal** window
|
||||
when using Trace RMI.
|
||||
Note this technique is only possible because the target waits for input.
|
||||
|
||||
1. Run `termmines` in a terminal outside of Ghidra with the desired command-line parameters.
|
||||
1. In the Ghidra Debugger, use the **Launch** button drop-down and select **Configure and Launch termmines using... → gdb**.
|
||||
1. Clear the **Image** field to configure a GDB session without a target.
|
||||
1. In the Ghidra Debugger, use the **Launch** button drop-down and select **Empty session ... → gdb**.
|
||||
The **Image** field should be blank to configure a GDB session without a target.
|
||||
1. Ghidra needs to know the location of gdb and the architecture of the intended target.
|
||||
The defaults are correct for 64-bit x86 targets using the system's copy of GDB.
|
||||
1. Click **Launch**.
|
||||
|
||||
Reference in New Issue
Block a user