From 714c620426e5890574d31533ab976bc1f25e8acc Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 11 Sep 2026 19:49:43 +0000 Subject: [PATCH] GP-7005: Adjust how re-launch works and improve launch menu. --- .../TraceRmiLauncherServicePlugin.html | 55 ++-- .../launcher/AbstractTraceRmiLaunchOffer.java | 9 +- .../AbstractTraceRmiLaunchOpinion.java | 9 +- .../gui/tracermi/launcher/LaunchAction.java | 254 ++++++++++++++---- .../TraceRmiLauncherServicePlugin.java | 93 +------ .../spi/tracermi/TraceRmiLaunchOpinion.java | 10 - .../main/java/docking/menu/MenuManager.java | 2 +- .../Debugger/A1-GettingStarted.html | 74 ++--- .../GhidraClass/Debugger/A1-GettingStarted.md | 38 ++- 9 files changed, 289 insertions(+), 255 deletions(-) diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html b/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html index 878af066e8..42b742c3b4 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/help/help/topics/TraceRmiLauncherServicePlugin/TraceRmiLauncherServicePlugin.html @@ -13,19 +13,17 @@

Debugger: Launchers

-

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 - (dbgeng.dll and dbgmodel.dll). Help is available for each in its respective - sub-topic.

+

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 (dbgeng.dll and dbgmodel.dll). Help is + available for each in its respective sub-topic.

Each launcher automates the creation of a Trace RMI acceptor, - 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:

+ 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:

@@ -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 execute method. The debugger's (and so also the target's) actual I/O - streams were hidden away within a GDB/MI wrapper.

+ standard I/O.

-

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.

+

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.

-

The list of launchers can be accessed in either of two places: - 1) In the Debugger → Configure and Launch ... menu or more conveniently from the - Launch button in the main toolbar. This is the blue bug - button near the top center. The Configure and Launch ... menu lists all available - launchers. Selecting one will prompt for its options then launch. To re-launch quickly, use the - Launch 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 Launch 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 Configure and Launch ... sub-menu of the drop-down - functions exactly like in the Debugger menu.

+

The list of launchers can be accessed conveniently from the + Launch button's drop-down menu in the main toolbar. This is the blue bug button near the top center. The Launch [program] ... submenus list + all available launchers for each open program. There is also an Empty session ... + 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 Launch 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 SHIFT.

The Terminal provides some fairly standard actions. Other keyboard control sequences, notably CTRL-C, are interpreted by the terminal, rather than Ghidra's action system, to @@ -134,7 +127,7 @@

diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java index 4cb6b0da88..56aa1ada77 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOffer.java @@ -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> args, diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOpinion.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOpinion.java index e889ca5348..daa42cd9aa 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOpinion.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/AbstractTraceRmiLaunchOpinion.java @@ -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 getModuleScriptPaths() { return Application.findModuleSubDirectories("data/debugger-launchers").stream(); } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchAction.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchAction.java index 5a0a4af2bc..d57cfa44e0 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchAction.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/LaunchAction.java @@ -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 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 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 actions, Program program) { + Collection offers = plugin.getOffers(program); + Map 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 getActionList(ActionContext context) { - Program program = plugin.currentProgram; - Collection offers = plugin.getOffers(program); - + ProgramManager programManager = plugin.getTool().getService(ProgramManager.class); + List allPrograms = List.of(programManager.getAllOpenPrograms()); List actions = new ArrayList<>(); - - Map 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 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()); } } diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java index d5449138b3..8461c6cdbf 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/gui/tracermi/launcher/TraceRmiLauncherServicePlugin.java @@ -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 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 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 menuPath) { - return Stream.concat( - Stream.of(constructLaunchMenuPrefix()), - menuPath.stream()).toArray(String[]::new); - } - - private void updateLauncherMenu() { - Collection 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) { diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/debug/spi/tracermi/TraceRmiLaunchOpinion.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/debug/spi/tracermi/TraceRmiLaunchOpinion.java index cb9a308e5e..bf6cf2af80 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/debug/spi/tracermi/TraceRmiLaunchOpinion.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/debug/spi/tracermi/TraceRmiLaunchOpinion.java @@ -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. * diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java index 239015eb32..0df1949a88 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java @@ -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 = ""; + private static final String NULL_GROUP_NAME = ""; private Set managedMenuItems = new HashSet<>(); private Map subMenus = new HashMap<>(); diff --git a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html index bd758d9b6f..edcb88c3a9 100644 --- a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html +++ b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.html @@ -129,9 +129,6 @@ icon in my Tool Chest
  • There is no Debug / Launch icon in the global toolbar
  • -
  • There is no -gdb option in the launch drop-down
  • The @@ -234,8 +231,7 @@ open
  • In the Debugger tool, click the dropdown ▾ for the debug debug button icon in the global tool -bar, and select Configure and Launch termmines using… → -gdb.

    +bar, and select Launch termmines … → gdb.

    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 termmines.exe and select -Configure and Launch termmines.exe using… → dbgeng.

    +Launch termmines.exe … → dbgeng.

    Launching on macOS

    @@ -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.

    -
    -

    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.

    -
    @@ -329,9 +312,11 @@ 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.

    +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. For your convenience, @@ -365,12 +350,13 @@ class="level4"> 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. 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.

    +

    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.

    Alternatively, try debugging the target in GDB from a separate terminal completely outside of Ghidra to see if things work as expected.

    @@ -426,16 +412,16 @@ exercise. Disconnect before proceeding to the next exercise.

    Customized Launching

    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. 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.

    +target without any parameters. 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 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

    @@ -449,18 +435,14 @@ its usage, and as a result, the rest of the UI will be mostly empty.

    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.

    +terminal and then attach to it from Ghidra. 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.
    2. In the Ghidra Debugger, use the Launch button -drop-down and select Configure and Launch termmines using… → -gdb.
    3. -
    4. Clear the Image field to configure a GDB session +drop-down and select Empty session … → gdb. The +Image field should be blank to configure a GDB session without a target.
    5. 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 diff --git a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.md b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.md index 3d2968c1a1..650d97ef00 100644 --- a/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.md +++ b/GhidraDocs/GhidraClass/Debugger/A1-GettingStarted.md @@ -58,7 +58,7 @@ There are many ways to do this, but for the sake of simplicity, import and launc ![Debugger tool with termmines open](images/GettingStarted_ToolWSpecimen.png) -1. In the Debugger tool, click the dropdown ▾ for the debug ![debug button](images/debugger.png) 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 ![debug button](images/debugger.png) icon in the global tool bar, and select **Launch termmines ... → gdb**. ![Launch GDB Dialog](images/GettingStarted_LaunchGDBDialog.png) @@ -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**.