Merge remote-tracking branch 'origin/GP-2102_Dan_fixNPEAtLaunchNoPause--SQUASHED'

This commit is contained in:
Ryan Kurtz 2022-06-22 17:04:55 -04:00
commit 87ffbb6c0b
3 changed files with 77 additions and 15 deletions

View File

@ -21,6 +21,8 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.swing.JOptionPane;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jdom.Element;
import org.jdom.JDOMException;
@ -46,6 +48,7 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceLocation;
import ghidra.trace.model.modules.TraceModule;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.exception.CancelledException;
@ -53,6 +56,12 @@ import ghidra.util.task.TaskMonitor;
import ghidra.util.xml.XmlUtilities;
public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProgramLaunchOffer {
private static final String HTML = "<html><p style='width:300px;'>";
private static final String NO_PAUSE_DIAGNOSTIC_MESSAGE = "" +
"It's possible the target launched but never paused, and so Ghidra has not been " +
"able to inspect it. Try interrupting the target, then inspect the process list. " +
"Further intervention may be required to establish the module/address mappings.";
protected final Program program;
protected final PluginTool tool;
protected final DebuggerModelFactory factory;
@ -430,7 +439,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
return factory.build().thenApplyAsync(m -> {
service.addModel(m);
return m;
});
}, SwingExecutorService.LATER);
}
protected CompletableFuture<TargetLauncher> findLauncher(DebuggerObjectModel m) {
@ -453,9 +462,37 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
return launcher.launch(args);
}
protected void checkCancelled(TaskMonitor monitor) {
if (monitor.isCancelled()) {
throw new CancellationException("User cancelled");
}
}
protected TargetLauncher onTimedOutFindLauncher(TaskMonitor monitor) {
checkCancelled(monitor);
monitor.setMessage("Timed out finding the launcher. Aborting.");
JOptionPane.showMessageDialog(null, HTML + "Timed out finding the launcher. " +
"This indicates an error in the implementation of the connector and/or the launcher " +
"opinion. Try again, and/or report the bug.",
getMenuParentTitle(), JOptionPane.ERROR_MESSAGE);
throw new CancellationException("Timed out");
}
protected Void onTimedOutLaunch(TaskMonitor monitor) {
checkCancelled(monitor);
monitor.setMessage("Timed out waiting for launch. Aborting.");
JOptionPane.showMessageDialog(null, HTML +
"Timed out waiting for launch. " + NO_PAUSE_DIAGNOSTIC_MESSAGE,
getMenuParentTitle(), JOptionPane.ERROR_MESSAGE);
throw new CancellationException("Timed out");
}
protected TargetObject onTimedOutTarget(TaskMonitor monitor) {
checkCancelled(monitor);
monitor.setMessage("Timed out waiting for target. Aborting.");
Msg.showError(this, null, getButtonTitle(), "Timed out waiting for target.");
JOptionPane.showMessageDialog(null, HTML +
"Timed out waiting for target. " + NO_PAUSE_DIAGNOSTIC_MESSAGE,
getMenuParentTitle(), JOptionPane.ERROR_MESSAGE);
throw new CancellationException("Timed out");
}
@ -472,12 +509,27 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
protected TraceRecorder onTimedOutRecorder(TaskMonitor monitor, DebuggerModelService service,
TargetObject target) {
checkCancelled(monitor);
monitor.setMessage("Timed out waiting for recording. Invoking the recorder.");
return service.recordTargetPromptOffers(target);
TraceRecorder recorder = service.recordTargetPromptOffers(target);
if (recorder == null) {
throw new CancellationException("User cancelled at record dialog");
}
DebuggerTraceManagerService traceManager =
tool.getService(DebuggerTraceManagerService.class);
if (traceManager != null) {
Trace trace = recorder.getTrace();
Swing.runLater(() -> {
traceManager.openTrace(trace);
traceManager.activateTrace(trace);
});
}
return recorder;
}
protected Void onTimedOutMapping(TaskMonitor monitor,
DebuggerStaticMappingService mappingService, TraceRecorder recorder) {
checkCancelled(monitor);
monitor.setMessage("Timed out waiting for module map. Invoking the mapper.");
Collection<ModuleMapEntry> mapped;
try {
@ -503,41 +555,49 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
DebuggerModelService service = tool.getService(DebuggerModelService.class);
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
monitor.initialize(4);
monitor.initialize(6);
monitor.setMessage("Connecting");
var locals = new Object() {
CompletableFuture<TargetObject> futureTarget;
};
return connect(service, prompt).thenComposeAsync(m -> {
return connect(service, prompt).thenCompose(m -> {
checkCancelled(monitor);
monitor.incrementProgress(1);
monitor.setMessage("Finding Launcher");
return findLauncher(m);
}, SwingExecutorService.LATER).thenCompose(l -> {
return AsyncTimer.DEFAULT_TIMER.mark()
.timeOut(findLauncher(m), getTimeoutMillis(),
() -> onTimedOutFindLauncher(monitor));
}).thenCompose(l -> {
checkCancelled(monitor);
monitor.incrementProgress(1);
monitor.setMessage("Launching");
locals.futureTarget = listenForTarget(l.getModel());
return launch(l, prompt);
return AsyncTimer.DEFAULT_TIMER.mark()
.timeOut(launch(l, prompt), getTimeoutMillis(),
() -> onTimedOutLaunch(monitor));
}).thenCompose(__ -> {
checkCancelled(monitor);
monitor.incrementProgress(1);
monitor.setMessage("Waiting for target");
return AsyncTimer.DEFAULT_TIMER.mark()
.timeOut(locals.futureTarget, getTimeoutMillis(),
() -> onTimedOutTarget(monitor));
}).thenCompose(t -> {
checkCancelled(monitor);
monitor.incrementProgress(1);
monitor.setMessage("Waiting for recorder");
return AsyncTimer.DEFAULT_TIMER.mark()
.timeOut(waitRecorder(service, t), getTimeoutMillis(),
() -> onTimedOutRecorder(monitor, service, t));
}).thenCompose(r -> {
checkCancelled(monitor);
monitor.incrementProgress(1);
if (r == null) {
throw new CancellationException();
}
monitor.setMessage("Confirming program is mapped to target");
CompletableFuture<Void> futureMapped = listenForMapping(mappingService, r);
return AsyncTimer.DEFAULT_TIMER.mark()
.timeOut(futureMapped, getTimeoutMillis(),
.timeOut(listenForMapping(mappingService, r), getTimeoutMillis(),
() -> onTimedOutMapping(monitor, mappingService, r));
}).exceptionally(ex -> {
if (AsyncUtils.unwrapThrowable(ex) instanceof CancellationException) {

View File

@ -136,6 +136,7 @@ public enum BackgroundUtils {
boolean hasProgress, boolean canCancel,
Function<TaskMonitor, CompletableFuture<T>> futureProducer) {
var dialog = new TaskDialog(name, canCancel, true, hasProgress) {
CancelledListener cancelledListener = this::cancelled;
CompletableFuture<T> orig = futureProducer.apply(this);
CompletableFuture<T> future = orig.exceptionally(ex -> {
if (AsyncUtils.unwrapThrowable(ex) instanceof CancellationException) {
@ -148,11 +149,15 @@ public enum BackgroundUtils {
return v;
});
@Override
protected void cancelCallback() {
{
addCancelledListener(cancelledListener);
}
private void cancelled() {
future.cancel(true);
close();
}
};
if (!dialog.orig.isDone()) {
tool.showDialog(dialog);

View File

@ -139,9 +139,6 @@ public interface DebuggerModelService {
* as much. If the user cancels, the returned future completes with {@code null}. The invocation
* is assumed to come from a {@link ActionSource#MANUAL} source.
*
* <p>
* TODO: Should the prompt allow the user to force an opinion which gave no offers?
*
* @see DebuggerMappingOpinion#queryOpinions(TargetObject)
* @param target the target to record.
* @return a future which completes with the recorder, or completes exceptionally