mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Merge remote-tracking branch 'origin/GP-3997_Dan_lessObtrusiveCommandFailures--SQUASHED'
This commit is contained in:
commit
68d209347c
15 changed files with 337 additions and 120 deletions
|
@ -16,12 +16,15 @@
|
||||||
package ghidra.app.services;
|
package ghidra.app.services;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
|
||||||
import ghidra.debug.api.progress.*;
|
import ghidra.debug.api.progress.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.ServiceInfo;
|
import ghidra.framework.plugintool.ServiceInfo;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.Task;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,4 +102,33 @@ public interface ProgressService {
|
||||||
* @param listener the listener
|
* @param listener the listener
|
||||||
*/
|
*/
|
||||||
void removeProgressListener(ProgressListener 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.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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<Void> 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,17 @@ package ghidra.debug.api.progress;
|
||||||
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task monitor that can be used in a try-with-resources block.
|
||||||
|
*/
|
||||||
public interface CloseableTaskMonitor extends TaskMonitor, AutoCloseable {
|
public interface CloseableTaskMonitor extends TaskMonitor, AutoCloseable {
|
||||||
@Override
|
@Override
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report an error while working on this task
|
||||||
|
*
|
||||||
|
* @param error the error
|
||||||
|
*/
|
||||||
|
void reportError(Throwable error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.debug.api.progress;
|
package ghidra.debug.api.progress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener for events on the progress service, including updates to task progress
|
||||||
|
*/
|
||||||
public interface ProgressListener {
|
public interface ProgressListener {
|
||||||
|
/**
|
||||||
|
* Describes how or why a task monitor was disposed
|
||||||
|
*/
|
||||||
enum Disposal {
|
enum Disposal {
|
||||||
/**
|
/**
|
||||||
* The monitor was properly closed
|
* The monitor was properly closed
|
||||||
|
@ -27,12 +33,52 @@ public interface ProgressListener {
|
||||||
CLEANED;
|
CLEANED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A new task monitor has been created
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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);
|
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);
|
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);
|
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
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 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);
|
void progressUpdated(MonitorReceiver monitor, long progress);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +92,7 @@ public interface ProgressListener {
|
||||||
* <li>show progress value in percent string</li>
|
* <li>show progress value in percent string</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param monitor the monitor
|
* @param monitor the receiver whose monitor's attribute(s) changed
|
||||||
*/
|
*/
|
||||||
void attributeUpdated(MonitorReceiver monitor);
|
void attributeUpdated(MonitorReceiver monitor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -394,11 +394,11 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
* Obtain the launcher args
|
* Obtain the launcher args
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This should either call {@link #promptLauncherArgs(Map))} or
|
* This should either call {@link #promptLauncherArgs(LaunchConfigurator, Throwable)} or
|
||||||
* {@link #loadLastLauncherArgs(Map, boolean))}. Note if choosing the latter, the user will not
|
* {@link #loadLastLauncherArgs(boolean)}. Note if choosing the latter, the user will not be
|
||||||
* be prompted to confirm.
|
* 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 configurator the rules for configuring the launcher
|
||||||
* @param lastExc if retrying, the last exception to display as an error message
|
* @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
|
* @return the chosen arguments, or null if the user cancels at the prompt
|
||||||
|
@ -543,19 +543,24 @@ public abstract class AbstractTraceRmiLaunchOffer implements TraceRmiLaunchOffer
|
||||||
|
|
||||||
try {
|
try {
|
||||||
monitor.setMessage("Listening for connection");
|
monitor.setMessage("Listening for connection");
|
||||||
|
monitor.increment();
|
||||||
acceptor = service.acceptOne(new InetSocketAddress("127.0.0.1", 0));
|
acceptor = service.acceptOne(new InetSocketAddress("127.0.0.1", 0));
|
||||||
monitor.setMessage("Launching back-end");
|
monitor.setMessage("Launching back-end");
|
||||||
|
monitor.increment();
|
||||||
launchBackEnd(monitor, sessions, args, acceptor.getAddress());
|
launchBackEnd(monitor, sessions, args, acceptor.getAddress());
|
||||||
monitor.setMessage("Waiting for connection");
|
monitor.setMessage("Waiting for connection");
|
||||||
|
monitor.increment();
|
||||||
acceptor.setTimeout(getTimeoutMillis());
|
acceptor.setTimeout(getTimeoutMillis());
|
||||||
connection = acceptor.accept();
|
connection = acceptor.accept();
|
||||||
connection.registerTerminals(sessions.values());
|
connection.registerTerminals(sessions.values());
|
||||||
monitor.setMessage("Waiting for trace");
|
monitor.setMessage("Waiting for trace");
|
||||||
|
monitor.increment();
|
||||||
trace = connection.waitForTrace(getTimeoutMillis());
|
trace = connection.waitForTrace(getTimeoutMillis());
|
||||||
traceManager.openTrace(trace);
|
traceManager.openTrace(trace);
|
||||||
traceManager.activate(traceManager.resolveTrace(trace),
|
traceManager.activate(traceManager.resolveTrace(trace),
|
||||||
ActivationCause.START_RECORDING);
|
ActivationCause.START_RECORDING);
|
||||||
monitor.setMessage("Waiting for module mapping");
|
monitor.setMessage("Waiting for module mapping");
|
||||||
|
monitor.increment();
|
||||||
try {
|
try {
|
||||||
listenForMapping(mappingService, connection, trace).get(getTimeoutMillis(),
|
listenForMapping(mappingService, connection, trace).get(getTimeoutMillis(),
|
||||||
TimeUnit.MILLISECONDS);
|
TimeUnit.MILLISECONDS);
|
||||||
|
|
|
@ -183,12 +183,22 @@ public class TraceRmiLauncherServicePlugin extends Plugin
|
||||||
.toList();
|
.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) {
|
protected void relaunch(TraceRmiLaunchOffer offer) {
|
||||||
tool.execute(new ReLaunchTask(offer));
|
executeTask(new ReLaunchTask(offer));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void configureAndLaunch(TraceRmiLaunchOffer offer) {
|
protected void configureAndLaunch(TraceRmiLaunchOffer offer) {
|
||||||
tool.execute(new ConfigureAndLaunchTask(offer));
|
executeTask(new ConfigureAndLaunchTask(offer));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String[] constructLaunchMenuPrefix() {
|
protected String[] constructLaunchMenuPrefix() {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.AutoService.Wiring;
|
import ghidra.framework.plugintool.AutoService.Wiring;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
import ghidra.util.datastruct.ListenerSet;
|
import ghidra.util.datastruct.ListenerSet;
|
||||||
import ghidra.util.task.ConsoleTaskMonitor;
|
import ghidra.util.task.ConsoleTaskMonitor;
|
||||||
|
@ -63,6 +64,11 @@ public class TraceRmiPlugin extends Plugin implements InternalTraceRmiService {
|
||||||
public void close() {
|
public void close() {
|
||||||
// Nothing
|
// Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reportError(Throwable e) {
|
||||||
|
Msg.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
|
|
|
@ -187,6 +187,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This class is public for access by test cases only.
|
* This class is public for access by test cases only.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the message
|
||||||
*/
|
*/
|
||||||
public interface LogRow<T> {
|
public interface LogRow<T> {
|
||||||
Icon getIcon();
|
Icon getIcon();
|
||||||
|
@ -319,6 +321,11 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter
|
||||||
logTableModel.updateItem(logRow);
|
logTableModel.updateItem(logRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void errorReported(MonitorReceiver monitor, Throwable error) {
|
||||||
|
log(DebuggerResources.ICON_LOG_ERROR, error.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void progressUpdated(MonitorReceiver monitor, long progress) {
|
public void progressUpdated(MonitorReceiver monitor, long progress) {
|
||||||
LogRow<?> logRow = logTableModel.getMap().get(contextFor(monitor));
|
LogRow<?> logRow = logTableModel.getMap().get(contextFor(monitor));
|
||||||
|
|
|
@ -146,6 +146,8 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
|
||||||
private DebuggerControlService controlService;
|
private DebuggerControlService controlService;
|
||||||
// @AutoServiceConsumed // via method
|
// @AutoServiceConsumed // via method
|
||||||
private DebuggerEmulationService emulationService;
|
private DebuggerEmulationService emulationService;
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private ProgressService progressService;
|
||||||
|
|
||||||
public DebuggerControlPlugin(PluginTool tool) {
|
public DebuggerControlPlugin(PluginTool tool) {
|
||||||
super(tool);
|
super(tool);
|
||||||
|
@ -282,8 +284,17 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
|
||||||
updateActions();
|
updateActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void executeTask(Task task) {
|
||||||
|
if (progressService != null) {
|
||||||
|
progressService.execute(task);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tool.execute(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void runTask(String title, ActionEntry entry) {
|
protected void runTask(String title, ActionEntry entry) {
|
||||||
tool.execute(new TargetActionTask(title, entry));
|
executeTask(new TargetActionTask(tool, title, entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addTargetStepExtActions(Target target) {
|
protected void addTargetStepExtActions(Target target) {
|
||||||
|
@ -359,7 +370,7 @@ public class DebuggerControlPlugin extends AbstractDebuggerPlugin
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tool.execute(new Task("Disconnect", false, false, false) {
|
executeTask(new Task("Disconnect", false, false, false) {
|
||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor monitor) throws CancelledException {
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class DebuggerMethodActionsPlugin extends Plugin implements PopupActionPr
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
tool.execute(new TargetActionTask(entry.display(), entry));
|
tool.execute(new TargetActionTask(tool, entry.display(), entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,21 +15,42 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.debug.gui.control;
|
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.debug.api.target.Target.ActionEntry;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.task.Task;
|
import ghidra.util.task.Task;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
class TargetActionTask extends Task {
|
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);
|
super(title, false, false, false);
|
||||||
|
this.tool = tool;
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor monitor) throws CancelledException {
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ import ghidra.dbg.target.TargetThread;
|
||||||
import ghidra.debug.api.action.ActionSource;
|
import ghidra.debug.api.action.ActionSource;
|
||||||
import ghidra.debug.api.model.*;
|
import ghidra.debug.api.model.*;
|
||||||
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.PromptMode;
|
import ghidra.debug.api.model.DebuggerProgramLaunchOffer.PromptMode;
|
||||||
|
import ghidra.debug.api.progress.CloseableTaskMonitor;
|
||||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||||
import ghidra.framework.main.AppInfo;
|
import ghidra.framework.main.AppInfo;
|
||||||
import ghidra.framework.main.FrontEndTool;
|
import ghidra.framework.main.FrontEndTool;
|
||||||
|
@ -230,6 +231,8 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||||
private DebuggerTraceManagerService traceManager;
|
private DebuggerTraceManagerService traceManager;
|
||||||
@AutoServiceConsumed
|
@AutoServiceConsumed
|
||||||
private DebuggerTargetService targetService;
|
private DebuggerTargetService targetService;
|
||||||
|
@AutoServiceConsumed
|
||||||
|
private ProgressService progressService;
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final AutoService.Wiring autoServiceWiring;
|
private final AutoService.Wiring autoServiceWiring;
|
||||||
|
|
||||||
|
@ -371,27 +374,41 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
|
||||||
|
|
||||||
private void debugProgram(DebuggerProgramLaunchOffer offer, Program program,
|
private void debugProgram(DebuggerProgramLaunchOffer offer, Program program,
|
||||||
PromptMode prompt) {
|
PromptMode prompt) {
|
||||||
BackgroundUtils.asyncModal(tool, offer.getButtonTitle(), true, true, m -> {
|
|
||||||
List<String> recent = new ArrayList<>(readMostRecentLaunches(program));
|
List<String> recent = new ArrayList<>(readMostRecentLaunches(program));
|
||||||
recent.remove(offer.getConfigName());
|
recent.remove(offer.getConfigName());
|
||||||
recent.add(offer.getConfigName());
|
recent.add(offer.getConfigName());
|
||||||
writeMostRecentLaunches(program, recent);
|
writeMostRecentLaunches(program, recent);
|
||||||
CompletableFuture.runAsync(() -> {
|
updateActionDebugProgram();
|
||||||
updateActionDebugProgram();
|
|
||||||
}, AsyncUtils.SWING_EXECUTOR).exceptionally(ex -> {
|
if (progressService == null) {
|
||||||
Msg.error(this, "Trouble writing recent launches to program user data");
|
BackgroundUtils.asyncModal(tool, offer.getButtonTitle(), true, true, m -> {
|
||||||
return null;
|
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);
|
Throwable t = AsyncUtils.unwrapThrowable(ex);
|
||||||
if (t instanceof CancellationException || t instanceof CancelledException) {
|
if (t instanceof CancellationException || t instanceof CancelledException) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
monitor.reportError(t);
|
||||||
return ExceptionUtils.rethrow(ex);
|
return ExceptionUtils.rethrow(ex);
|
||||||
}).whenCompleteAsync((v, e) -> {
|
}).whenCompleteAsync((v, e) -> {
|
||||||
|
monitor.close();
|
||||||
updateActionDebugProgram();
|
updateActionDebugProgram();
|
||||||
}, AsyncUtils.SWING_EXECUTOR);
|
}, AsyncUtils.SWING_EXECUTOR);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void debugProgramButtonActivated(ActionContext ctx) {
|
private void debugProgramButtonActivated(ActionContext ctx) {
|
||||||
|
|
|
@ -105,6 +105,44 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
return 10000;
|
return 10000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static class TargetResult extends CompletableFuture<TargetObject>
|
||||||
|
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<String> removed,
|
||||||
|
Map<String, ?> added) {
|
||||||
|
if (!added.containsKey(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkObject(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen for the launched target in the model
|
* 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
|
* @return a future that completes with the target object
|
||||||
*/
|
*/
|
||||||
protected CompletableFuture<TargetObject> listenForTarget(DebuggerObjectModel model) {
|
protected CompletableFuture<TargetObject> listenForTarget(DebuggerObjectModel model) {
|
||||||
var result = new CompletableFuture<TargetObject>() {
|
return new TargetResult(model);
|
||||||
DebuggerModelListener listener = new DebuggerModelListener() {
|
}
|
||||||
protected void checkObject(TargetObject object) {
|
|
||||||
if (DebugModelConventions.liveProcessOrNull(object) == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
complete(object);
|
|
||||||
model.removeModelListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
protected static class RecorderResult extends CompletableFuture<TraceRecorder>
|
||||||
public void created(TargetObject object) {
|
implements CollectionChangeListener<TraceRecorder> {
|
||||||
checkObject(object);
|
private final DebuggerModelService service;
|
||||||
}
|
private final TargetObject target;
|
||||||
|
|
||||||
@Override
|
public RecorderResult(DebuggerModelService service, TargetObject target) {
|
||||||
public void attributesChanged(TargetObject object, Collection<String> removed,
|
this.service = service;
|
||||||
Map<String, ?> added) {
|
this.target = target;
|
||||||
if (!added.containsKey(TargetExecutionStateful.STATE_ATTRIBUTE_NAME)) {
|
exceptionally(this::onError);
|
||||||
return;
|
service.addTraceRecordersChangedListener(this);
|
||||||
}
|
}
|
||||||
checkObject(object);
|
|
||||||
}
|
protected TraceRecorder onError(Throwable ex) {
|
||||||
};
|
service.removeTraceRecordersChangedListener(this);
|
||||||
};
|
|
||||||
model.addModelListener(result.listener);
|
|
||||||
result.exceptionally(ex -> {
|
|
||||||
model.removeModelListener(result.listener);
|
|
||||||
return null;
|
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<TraceRecorder> listenForRecorder(DebuggerModelService service,
|
protected CompletableFuture<TraceRecorder> listenForRecorder(DebuggerModelService service,
|
||||||
TargetObject target) {
|
TargetObject target) {
|
||||||
var result = new CompletableFuture<TraceRecorder>() {
|
return new RecorderResult(service, target);
|
||||||
CollectionChangeListener<TraceRecorder> 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Address getMappingProbeAddress() {
|
protected static class MappingResult extends CompletableFuture<Void>
|
||||||
AddressIterator eepi = program.getSymbolTable().getExternalEntryPointIterator();
|
implements DebuggerStaticMappingChangeListener {
|
||||||
if (eepi.hasNext()) {
|
private final DebuggerStaticMappingService mappingService;
|
||||||
return eepi.next();
|
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()) {
|
protected Void onError(Throwable ex) {
|
||||||
return ii.next().getAddress();
|
mappingService.removeChangeListener(this);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
AddressSetView es = program.getMemory().getExecuteSet();
|
|
||||||
if (!es.isEmpty()) {
|
protected Address getMappingProbeAddress() {
|
||||||
return es.getMinAddress();
|
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<Trace> affectedTraces, Set<Program> 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<Void> listenForMapping(
|
protected CompletableFuture<Void> listenForMapping(
|
||||||
DebuggerStaticMappingService mappingService, TraceRecorder recorder) {
|
DebuggerStaticMappingService mappingService, TraceRecorder recorder) {
|
||||||
ProgramLocation probe = new ProgramLocation(program, getMappingProbeAddress());
|
return new MappingResult(mappingService, recorder, program);
|
||||||
Trace trace = recorder.getTrace();
|
|
||||||
var result = new CompletableFuture<Void>() {
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Collection<ModuleMapEntry> invokeMapper(TaskMonitor monitor,
|
protected Collection<ModuleMapEntry> invokeMapper(TaskMonitor monitor,
|
||||||
|
@ -396,15 +435,17 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
* Obtain the launcher args
|
* Obtain the launcher args
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* This should either call {@link #promptLauncherArgs(Map))} or
|
* This should either call {@link #promptLauncherArgs(TargetLauncher,LaunchConfigurator)} or
|
||||||
* {@link #loadLastLauncherArgs(Map, boolean))}. Note if choosing the latter, the user will not
|
* {@link #loadLastLauncherArgs(TargetLauncher, boolean)}. Note if choosing the latter, the user
|
||||||
* be prompted to confirm.
|
* 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
|
* @return the chosen arguments, or null if the user cancels at the prompt
|
||||||
*/
|
*/
|
||||||
public Map<String, ?> getLauncherArgs(TargetLauncher launcher,
|
public Map<String, ?> getLauncherArgs(TargetLauncher launcher, boolean prompt,
|
||||||
boolean prompt, LaunchConfigurator configurator) {
|
LaunchConfigurator configurator) {
|
||||||
return prompt
|
return prompt
|
||||||
? configurator.configureLauncher(launcher,
|
? configurator.configureLauncher(launcher,
|
||||||
promptLauncherArgs(launcher, configurator), RelPrompt.AFTER)
|
promptLauncherArgs(launcher, configurator), RelPrompt.AFTER)
|
||||||
|
@ -668,6 +709,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
|
||||||
}).thenApply(__ -> {
|
}).thenApply(__ -> {
|
||||||
if (locals.exception != null) {
|
if (locals.exception != null) {
|
||||||
monitor.setMessage("Launch error: " + locals.exception);
|
monitor.setMessage("Launch error: " + locals.exception);
|
||||||
|
Msg.error(this, "Launch error", locals.exception);
|
||||||
return locals.getResult();
|
return locals.getResult();
|
||||||
}
|
}
|
||||||
monitor.setMessage("Launch successful");
|
monitor.setMessage("Launch successful");
|
||||||
|
|
|
@ -69,6 +69,11 @@ public class DefaultCloseableTaskMonitor implements CloseableTaskMonitor {
|
||||||
receiver.setMessage(message);
|
receiver.setMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reportError(Throwable error) {
|
||||||
|
receiver.reportError(error);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return receiver.getMessage();
|
return receiver.getMessage();
|
||||||
|
|
|
@ -75,6 +75,10 @@ public class DefaultMonitorReceiver implements MonitorReceiver {
|
||||||
plugin.listeners.invoke().messageUpdated(this, message);
|
plugin.listeners.invoke().messageUpdated(this, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reportError(Throwable error) {
|
||||||
|
plugin.listeners.invoke().errorReported(this, error);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
|
@ -158,6 +162,7 @@ public class DefaultMonitorReceiver implements MonitorReceiver {
|
||||||
return cancelEnabled;
|
return cancelEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean isShowProgressValue() {
|
public boolean isShowProgressValue() {
|
||||||
return showProgressValue;
|
return showProgressValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ import ghidra.util.datastruct.ListenerSet;
|
||||||
""",
|
""",
|
||||||
servicesProvided = { ProgressService.class },
|
servicesProvided = { ProgressService.class },
|
||||||
packageName = DebuggerPluginPackage.NAME,
|
packageName = DebuggerPluginPackage.NAME,
|
||||||
status = PluginStatus.STABLE)
|
status = PluginStatus.RELEASED)
|
||||||
public class ProgressServicePlugin extends Plugin implements ProgressService {
|
public class ProgressServicePlugin extends Plugin implements ProgressService {
|
||||||
ListenerSet<ProgressListener> listeners = new ListenerSet<>(ProgressListener.class, true);
|
ListenerSet<ProgressListener> listeners = new ListenerSet<>(ProgressListener.class, true);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue