From 75ba9afb2dccdb718c842e42b2eabf798a27d983 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:00:28 -0500 Subject: [PATCH] GP-3997: Move Debugger progress and errors to Debug Console rather than pop-ups. --- .../ghidra/app/services/ProgressService.java | 36 ++- .../api/progress/CloseableTaskMonitor.java | 10 + .../debug/api/progress/ProgressListener.java | 48 +++- .../launcher/AbstractTraceRmiLaunchOffer.java | 13 +- .../TraceRmiLauncherServicePlugin.java | 14 +- .../service/tracermi/TraceRmiPlugin.java | 6 + .../gui/console/DebuggerConsoleProvider.java | 7 + .../gui/control/DebuggerControlPlugin.java | 15 +- .../control/DebuggerMethodActionsPlugin.java | 2 +- .../debug/gui/control/TargetActionTask.java | 27 ++- .../DebuggerModelServiceProxyPlugin.java | 41 +++- .../AbstractDebuggerProgramLaunchOffer.java | 226 +++++++++++------- .../progress/DefaultCloseableTaskMonitor.java | 5 + .../progress/DefaultMonitorReceiver.java | 5 + .../progress/ProgressServicePlugin.java | 2 +- 15 files changed, 337 insertions(+), 120 deletions(-) diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/ProgressService.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/ProgressService.java index b99c776630..7a1b1ebafc 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/ProgressService.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/app/services/ProgressService.java @@ -16,12 +16,15 @@ package ghidra.app.services; import java.util.Collection; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; +import java.util.concurrent.*; + +import org.apache.commons.lang3.exception.ExceptionUtils; import ghidra.debug.api.progress.*; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.ServiceInfo; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.Task; import ghidra.util.task.TaskMonitor; /** @@ -99,4 +102,33 @@ public interface ProgressService { * @param listener the listener */ void removeProgressListener(ProgressListener listener); + + /** + * A drop-in replacement for {@link PluginTool#execute(Task)} that publishes progress via the + * service rather than displaying a dialog. + * + *

+ * In addition to changing how progress is displayed, this also returns a future so that task + * completion can be detected by the caller. + * + * @param task task to run in a new thread + * @return a future which completes when the task is finished + */ + default CompletableFuture execute(Task task) { + return CompletableFuture.supplyAsync(() -> { + try (CloseableTaskMonitor monitor = publishTask()) { + try { + task.run(monitor); + } + catch (CancelledException e) { + throw new CancellationException("User cancelled"); + } + catch (Throwable e) { + monitor.reportError(e); + return ExceptionUtils.rethrow(e); + } + return null; + } + }); + } } diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/progress/CloseableTaskMonitor.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/progress/CloseableTaskMonitor.java index 58ced490e6..5697e12fbb 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/progress/CloseableTaskMonitor.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/progress/CloseableTaskMonitor.java @@ -17,7 +17,17 @@ package ghidra.debug.api.progress; import ghidra.util.task.TaskMonitor; +/** + * A task monitor that can be used in a try-with-resources block. + */ public interface CloseableTaskMonitor extends TaskMonitor, AutoCloseable { @Override void close(); + + /** + * Report an error while working on this task + * + * @param error the error + */ + void reportError(Throwable error); } diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/progress/ProgressListener.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/progress/ProgressListener.java index 60749983f7..d8d506932b 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/progress/ProgressListener.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/progress/ProgressListener.java @@ -15,7 +15,13 @@ */ package ghidra.debug.api.progress; +/** + * A listener for events on the progress service, including updates to task progress + */ public interface ProgressListener { + /** + * Describes how or why a task monitor was disposed + */ enum Disposal { /** * The monitor was properly closed @@ -27,12 +33,52 @@ public interface ProgressListener { CLEANED; } + /** + * A new task monitor has been created + * + *

+ * The subscriber ought to display the monitor as soon as is reasonable. Optionally, a + * subscriber may apply a grace period, e.g., half a second, before displaying it, in case it is + * quickly disposed. + * + * @param monitor a means of retrieving messages and progress about the task + */ void monitorCreated(MonitorReceiver monitor); + /** + * A task monitor has been disposed + * + * @param monitor the receiver for the disposed monitor + * @param disposal why it was disposed + */ void monitorDisposed(MonitorReceiver monitor, Disposal disposal); + /** + * A task has updated a monitor's message + * + * @param monitor the receiver whose monitor's message changed + * @param message the new message + */ void messageUpdated(MonitorReceiver monitor, String message); + /** + * A task has reported an error + * + * @param monitor the receiver for the task reporting the error + * @param error the exception representing the error + */ + void errorReported(MonitorReceiver monitor, Throwable error); + + /** + * A task's progress has updated + * + *

+ * Note the subscriber may need to use {@link MonitorReceiver#getMaximum()} to properly update + * the display. + * + * @param monitor the receiver whose monitor's progress changed + * @param progress the new progress value + */ void progressUpdated(MonitorReceiver monitor, long progress); /** @@ -46,7 +92,7 @@ public interface ProgressListener { *

  • show progress value in percent string
  • * * - * @param monitor the monitor + * @param monitor the receiver whose monitor's attribute(s) changed */ void attributeUpdated(MonitorReceiver monitor); } 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 063cf34de0..2f5ffc5a69 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 @@ -394,11 +394,11 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer * Obtain the launcher args * *

    - * This should either call {@link #promptLauncherArgs(Map))} or - * {@link #loadLastLauncherArgs(Map, boolean))}. Note if choosing the latter, the user will not - * be prompted to confirm. + * This should either call {@link #promptLauncherArgs(LaunchConfigurator, Throwable)} or + * {@link #loadLastLauncherArgs(boolean)}. Note if choosing the latter, the user will not be + * prompted to confirm. * - * @param params the parameters of the model's launcher + * @param prompt true to prompt the user, false to use saved arguments * @param configurator the rules for configuring the launcher * @param lastExc if retrying, the last exception to display as an error message * @return the chosen arguments, or null if the user cancels at the prompt @@ -543,19 +543,24 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer try { monitor.setMessage("Listening for connection"); + monitor.increment(); acceptor = service.acceptOne(new InetSocketAddress("127.0.0.1", 0)); monitor.setMessage("Launching back-end"); + monitor.increment(); launchBackEnd(monitor, sessions, args, acceptor.getAddress()); monitor.setMessage("Waiting for connection"); + monitor.increment(); acceptor.setTimeout(getTimeoutMillis()); connection = acceptor.accept(); connection.registerTerminals(sessions.values()); monitor.setMessage("Waiting for trace"); + monitor.increment(); trace = connection.waitForTrace(getTimeoutMillis()); traceManager.openTrace(trace); traceManager.activate(traceManager.resolveTrace(trace), ActivationCause.START_RECORDING); monitor.setMessage("Waiting for module mapping"); + monitor.increment(); try { listenForMapping(mappingService, connection, trace).get(getTimeoutMillis(), TimeUnit.MILLISECONDS); 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 7756636506..4a9c0853c2 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 @@ -183,12 +183,22 @@ public class TraceRmiLauncherServicePlugin extends Plugin .toList(); } + protected void executeTask(Task task) { + ProgressService progressService = tool.getService(ProgressService.class); + if (progressService != null) { + progressService.execute(task); + } + else { + tool.execute(task); + } + } + protected void relaunch(TraceRmiLaunchOffer offer) { - tool.execute(new ReLaunchTask(offer)); + executeTask(new ReLaunchTask(offer)); } protected void configureAndLaunch(TraceRmiLaunchOffer offer) { - tool.execute(new ConfigureAndLaunchTask(offer)); + executeTask(new ConfigureAndLaunchTask(offer)); } protected String[] constructLaunchMenuPrefix() { diff --git a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiPlugin.java b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiPlugin.java index 7750e34924..e0734327f3 100644 --- a/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiPlugin.java +++ b/Ghidra/Debug/Debugger-rmi-trace/src/main/java/ghidra/app/plugin/core/debug/service/tracermi/TraceRmiPlugin.java @@ -31,6 +31,7 @@ import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.AutoService.Wiring; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.util.Msg; import ghidra.util.Swing; import ghidra.util.datastruct.ListenerSet; import ghidra.util.task.ConsoleTaskMonitor; @@ -63,6 +64,11 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService { public void close() { // Nothing } + + @Override + public void reportError(Throwable e) { + Msg.error(e.getMessage(), e); + } } @AutoServiceConsumed diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java index c6634967f1..6ddddd172f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java @@ -187,6 +187,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter * *

    * This class is public for access by test cases only. + * + * @param the type of the message */ public interface LogRow { Icon getIcon(); @@ -319,6 +321,11 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter logTableModel.updateItem(logRow); } + @Override + public void errorReported(MonitorReceiver monitor, Throwable error) { + log(DebuggerResources.ICON_LOG_ERROR, error.getMessage()); + } + @Override public void progressUpdated(MonitorReceiver monitor, long progress) { LogRow logRow = logTableModel.getMap().get(contextFor(monitor)); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java index 592599b71f..46d7308428 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerControlPlugin.java @@ -146,6 +146,8 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin private DebuggerControlService controlService; // @AutoServiceConsumed // via method private DebuggerEmulationService emulationService; + @AutoServiceConsumed + private ProgressService progressService; public DebuggerControlPlugin(PluginTool tool) { super(tool); @@ -282,8 +284,17 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin updateActions(); } + protected void executeTask(Task task) { + if (progressService != null) { + progressService.execute(task); + } + else { + tool.execute(task); + } + } + protected void runTask(String title, ActionEntry entry) { - tool.execute(new TargetActionTask(title, entry)); + executeTask(new TargetActionTask(tool, title, entry)); } protected void addTargetStepExtActions(Target target) { @@ -359,7 +370,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin if (target == null) { return; } - tool.execute(new Task("Disconnect", false, false, false) { + executeTask(new Task("Disconnect", false, false, false) { @Override public void run(TaskMonitor monitor) throws CancelledException { try { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java index 9ffcd44382..a02bcbc9ea 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPlugin.java @@ -64,7 +64,7 @@ public class DebuggerMethodActionsPlugin extends Plugin implements PopupActionPr @Override public void actionPerformed(ActionContext context) { - tool.execute(new TargetActionTask(entry.display(), entry)); + tool.execute(new TargetActionTask(tool, entry.display(), entry)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java index bcfcec2358..a3fd70154f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/control/TargetActionTask.java @@ -15,21 +15,42 @@ */ package ghidra.app.plugin.core.debug.gui.control; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.app.services.DebuggerConsoleService; import ghidra.debug.api.target.Target.ActionEntry; +import ghidra.framework.plugintool.PluginTool; +import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.Task; import ghidra.util.task.TaskMonitor; class TargetActionTask extends Task { - private ActionEntry entry; + private final PluginTool tool; + private final ActionEntry entry; - public TargetActionTask(String title, ActionEntry entry) { + public TargetActionTask(PluginTool tool, String title, ActionEntry entry) { super(title, false, false, false); + this.tool = tool; this.entry = entry; } @Override public void run(TaskMonitor monitor) throws CancelledException { - entry.run(false); + try { + entry.run(false); + } + catch (Throwable e) { + reportError(e); + } + } + + private void reportError(Throwable error) { + DebuggerConsoleService consoleService = tool.getService(DebuggerConsoleService.class); + if (consoleService != null) { + consoleService.log(DebuggerResources.ICON_LOG_ERROR, error.getMessage()); + } + else { + Msg.showError(this, null, "Control Error", error.getMessage(), error); + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java index aa0622d7a4..35ff07b6ac 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceProxyPlugin.java @@ -52,6 +52,7 @@ import ghidra.dbg.target.TargetThread; import ghidra.debug.api.action.ActionSource; import ghidra.debug.api.model.*; import ghidra.debug.api.model.DebuggerProgramLaunchOffer.PromptMode; +import ghidra.debug.api.progress.CloseableTaskMonitor; import ghidra.debug.api.tracemgr.DebuggerCoordinates; import ghidra.framework.main.AppInfo; import ghidra.framework.main.FrontEndTool; @@ -230,6 +231,8 @@ public class DebuggerModelServiceProxyPlugin extends Plugin private DebuggerTraceManagerService traceManager; @AutoServiceConsumed private DebuggerTargetService targetService; + @AutoServiceConsumed + private ProgressService progressService; @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; @@ -371,27 +374,41 @@ public class DebuggerModelServiceProxyPlugin extends Plugin private void debugProgram(DebuggerProgramLaunchOffer offer, Program program, PromptMode prompt) { - BackgroundUtils.asyncModal(tool, offer.getButtonTitle(), true, true, m -> { - List recent = new ArrayList<>(readMostRecentLaunches(program)); - recent.remove(offer.getConfigName()); - recent.add(offer.getConfigName()); - writeMostRecentLaunches(program, recent); - CompletableFuture.runAsync(() -> { - updateActionDebugProgram(); - }, AsyncUtils.SWING_EXECUTOR).exceptionally(ex -> { - Msg.error(this, "Trouble writing recent launches to program user data"); - return null; + + List recent = new ArrayList<>(readMostRecentLaunches(program)); + recent.remove(offer.getConfigName()); + recent.add(offer.getConfigName()); + writeMostRecentLaunches(program, recent); + updateActionDebugProgram(); + + if (progressService == null) { + BackgroundUtils.asyncModal(tool, offer.getButtonTitle(), true, true, m -> { + return offer.launchProgram(m, prompt).exceptionally(ex -> { + Throwable t = AsyncUtils.unwrapThrowable(ex); + if (t instanceof CancellationException || t instanceof CancelledException) { + return null; + } + return ExceptionUtils.rethrow(ex); + }).whenCompleteAsync((v, e) -> { + updateActionDebugProgram(); + }, AsyncUtils.SWING_EXECUTOR); }); - return offer.launchProgram(m, prompt).exceptionally(ex -> { + } + else { + @SuppressWarnings("resource") + CloseableTaskMonitor monitor = progressService.publishTask(); + offer.launchProgram(monitor, prompt).exceptionally(ex -> { Throwable t = AsyncUtils.unwrapThrowable(ex); if (t instanceof CancellationException || t instanceof CancelledException) { return null; } + monitor.reportError(t); return ExceptionUtils.rethrow(ex); }).whenCompleteAsync((v, e) -> { + monitor.close(); updateActionDebugProgram(); }, AsyncUtils.SWING_EXECUTOR); - }); + } } private void debugProgramButtonActivated(ActionContext ctx) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java index 5449811a0f..f1b1329a2c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/launch/AbstractDebuggerProgramLaunchOffer.java @@ -105,6 +105,44 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg return 10000; } + protected static class TargetResult extends CompletableFuture + implements DebuggerModelListener { + private final DebuggerObjectModel model; + + public TargetResult(DebuggerObjectModel model) { + this.model = model; + exceptionally(this::onError); + model.addModelListener(this); + } + + protected void checkObject(TargetObject object) { + if (DebugModelConventions.liveProcessOrNull(object) == null) { + return; + } + complete(object); + model.removeModelListener(this); + } + + protected TargetObject onError(Throwable ex) { + model.removeModelListener(this); + return null; + } + + @Override + public void created(TargetObject object) { + checkObject(object); + } + + @Override + public void attributesChanged(TargetObject object, Collection removed, + Map added) { + if (!added.containsKey(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)) { + return; + } + checkObject(object); + } + } + /** * Listen for the launched target in the model * @@ -118,37 +156,33 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg * @return a future that completes with the target object */ protected CompletableFuture listenForTarget(DebuggerObjectModel model) { - var result = new CompletableFuture() { - DebuggerModelListener listener = new DebuggerModelListener() { - protected void checkObject(TargetObject object) { - if (DebugModelConventions.liveProcessOrNull(object) == null) { - return; - } - complete(object); - model.removeModelListener(this); - } + return new TargetResult(model); + } - @Override - public void created(TargetObject object) { - checkObject(object); - } + protected static class RecorderResult extends CompletableFuture + implements CollectionChangeListener { + private final DebuggerModelService service; + private final TargetObject target; - @Override - public void attributesChanged(TargetObject object, Collection removed, - Map added) { - if (!added.containsKey(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)) { - return; - } - checkObject(object); - } - }; - }; - model.addModelListener(result.listener); - result.exceptionally(ex -> { - model.removeModelListener(result.listener); + public RecorderResult(DebuggerModelService service, TargetObject target) { + this.service = service; + this.target = target; + exceptionally(this::onError); + service.addTraceRecordersChangedListener(this); + } + + protected TraceRecorder onError(Throwable ex) { + service.removeTraceRecordersChangedListener(this); return null; - }); - return result; + } + + @Override + public void elementAdded(TraceRecorder element) { + if (element.getTarget() == target) { + complete(element); + service.removeTraceRecordersChangedListener(this); + } + } } /** @@ -160,74 +194,79 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg */ protected CompletableFuture listenForRecorder(DebuggerModelService service, TargetObject target) { - var result = new CompletableFuture() { - CollectionChangeListener listener = new CollectionChangeListener<>() { - @Override - public void elementAdded(TraceRecorder element) { - if (element.getTarget() == target) { - complete(element); - service.removeTraceRecordersChangedListener(this); - } - } - }; - }; - service.addTraceRecordersChangedListener(result.listener); - result.exceptionally(ex -> { - service.removeTraceRecordersChangedListener(result.listener); - return null; - }); - return result; + return new RecorderResult(service, target); } - protected Address getMappingProbeAddress() { - AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator(); - if (eepi.hasNext()) { - return eepi.next(); + protected static class MappingResult extends CompletableFuture + implements DebuggerStaticMappingChangeListener { + private final DebuggerStaticMappingService mappingService; + private final TraceRecorder recorder; + private final Program program; + + private final Trace trace; + private final ProgramLocation probe; + + public MappingResult(DebuggerStaticMappingService mappingService, TraceRecorder recorder, + Program program) { + this.mappingService = mappingService; + this.recorder = recorder; + this.program = program; + + this.probe = new ProgramLocation(program, getMappingProbeAddress()); + this.trace = recorder.getTrace(); + + exceptionally(this::onError); + mappingService.addChangeListener(this); + check(); } - InstructionIterator ii = program.getListing().getInstructions(true); - if (ii.hasNext()) { - return ii.next().getAddress(); + + protected Void onError(Throwable ex) { + mappingService.removeChangeListener(this); + return null; } - AddressSetView es = program.getMemory().getExecuteSet(); - if (!es.isEmpty()) { - return es.getMinAddress(); + + protected Address getMappingProbeAddress() { + AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator(); + if (eepi.hasNext()) { + return eepi.next(); + } + InstructionIterator ii = program.getListing().getInstructions(true); + if (ii.hasNext()) { + return ii.next().getAddress(); + } + AddressSetView es = program.getMemory().getExecuteSet(); + if (!es.isEmpty()) { + return es.getMinAddress(); + } + if (!program.getMemory().isEmpty()) { + return program.getMinAddress(); + } + return null; // There's no hope } - if (!program.getMemory().isEmpty()) { - return program.getMinAddress(); + + @Override + public void mappingsChanged(Set affectedTraces, Set affectedPrograms) { + if (!affectedPrograms.contains(program) && + !affectedTraces.contains(trace)) { + return; + } + check(); + } + + protected void check() { + TraceLocation result = + mappingService.getOpenMappedLocation(trace, probe, recorder.getSnap()); + if (result == null) { + return; + } + complete(null); + mappingService.removeChangeListener(this); } - return null; // There's no hope } protected CompletableFuture listenForMapping( DebuggerStaticMappingService mappingService, TraceRecorder recorder) { - ProgramLocation probe = new ProgramLocation(program, getMappingProbeAddress()); - Trace trace = recorder.getTrace(); - var result = new CompletableFuture() { - DebuggerStaticMappingChangeListener listener = (affectedTraces, affectedPrograms) -> { - if (!affectedPrograms.contains(program) && - !affectedTraces.contains(trace)) { - return; - } - check(); - }; - - protected void check() { - TraceLocation result = - mappingService.getOpenMappedLocation(trace, probe, recorder.getSnap()); - if (result == null) { - return; - } - complete(null); - mappingService.removeChangeListener(listener); - } - }; - mappingService.addChangeListener(result.listener); - result.check(); - result.exceptionally(ex -> { - mappingService.removeChangeListener(result.listener); - return null; - }); - return result; + return new MappingResult(mappingService, recorder, program); } protected Collection invokeMapper(TaskMonitor monitor, @@ -396,15 +435,17 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg * Obtain the launcher args * *

    - * This should either call {@link #promptLauncherArgs(Map))} or - * {@link #loadLastLauncherArgs(Map, boolean))}. Note if choosing the latter, the user will not - * be prompted to confirm. + * This should either call {@link #promptLauncherArgs(TargetLauncher,LaunchConfigurator)} or + * {@link #loadLastLauncherArgs(TargetLauncher, boolean)}. Note if choosing the latter, the user + * will not be prompted to confirm. * - * @param params the parameters of the model's launcher + * @param launcher the model's launcher + * @param prompt true to prompt the user, false to use saved arguments + * @param configurator a means of configuring the launcher * @return the chosen arguments, or null if the user cancels at the prompt */ - public Map getLauncherArgs(TargetLauncher launcher, - boolean prompt, LaunchConfigurator configurator) { + public Map getLauncherArgs(TargetLauncher launcher, boolean prompt, + LaunchConfigurator configurator) { return prompt ? configurator.configureLauncher(launcher, promptLauncherArgs(launcher, configurator), RelPrompt.AFTER) @@ -668,6 +709,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg }).thenApply(__ -> { if (locals.exception != null) { monitor.setMessage("Launch error: " + locals.exception); + Msg.error(this, "Launch error", locals.exception); return locals.getResult(); } monitor.setMessage("Launch successful"); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/DefaultCloseableTaskMonitor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/DefaultCloseableTaskMonitor.java index 6366d0f76e..ddc1592ca9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/DefaultCloseableTaskMonitor.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/DefaultCloseableTaskMonitor.java @@ -69,6 +69,11 @@ public class DefaultCloseableTaskMonitor implements CloseableTaskMonitor { receiver.setMessage(message); } + @Override + public void reportError(Throwable error) { + receiver.reportError(error); + } + @Override public String getMessage() { return receiver.getMessage(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/DefaultMonitorReceiver.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/DefaultMonitorReceiver.java index 8b42f7ed86..f5d57182ef 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/DefaultMonitorReceiver.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/DefaultMonitorReceiver.java @@ -75,6 +75,10 @@ public class DefaultMonitorReceiver implements MonitorReceiver { plugin.listeners.invoke().messageUpdated(this, message); } + void reportError(Throwable error) { + plugin.listeners.invoke().errorReported(this, error); + } + @Override public String getMessage() { synchronized (lock) { @@ -158,6 +162,7 @@ public class DefaultMonitorReceiver implements MonitorReceiver { return cancelEnabled; } + @Override public boolean isShowProgressValue() { return showProgressValue; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/ProgressServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/ProgressServicePlugin.java index 8a4e59988b..0a4001b04d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/ProgressServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/progress/ProgressServicePlugin.java @@ -36,7 +36,7 @@ import ghidra.util.datastruct.ListenerSet; """, servicesProvided = { ProgressService.class }, packageName = DebuggerPluginPackage.NAME, - status = PluginStatus.STABLE) + status = PluginStatus.RELEASED) public class ProgressServicePlugin extends Plugin implements ProgressService { ListenerSet listeners = new ListenerSet<>(ProgressListener.class, true);