mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-4399: Display progress for memory reads in Debug Console
This commit is contained in:
parent
fa0cb8d881
commit
5ae08742c3
6 changed files with 115 additions and 16 deletions
|
@ -17,6 +17,7 @@ package ghidra.app.services;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
|
@ -117,6 +118,7 @@ public interface ProgressService {
|
|||
default CompletableFuture<Void> execute(Task task) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try (CloseableTaskMonitor monitor = publishTask()) {
|
||||
monitor.setCancelEnabled(task.canCancel());
|
||||
try {
|
||||
task.run(monitor);
|
||||
}
|
||||
|
@ -131,4 +133,27 @@ public interface ProgressService {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #execute(Task)}, but for asynchronous methods
|
||||
*
|
||||
* @param <T> the type of future result
|
||||
* @param canCancel true if the task can be cancelled
|
||||
* @param hasProgress true if the task displays progress
|
||||
* @param isModal true if the task is modal (ignored)
|
||||
* @param futureSupplier the task which returns a future, given the task monitor
|
||||
* @return the future returned by the supplier
|
||||
*/
|
||||
default <T> CompletableFuture<T> execute(boolean canCancel, boolean hasProgress,
|
||||
boolean isModal, Function<TaskMonitor, CompletableFuture<T>> futureSupplier) {
|
||||
CloseableTaskMonitor monitor = publishTask();
|
||||
monitor.setCancelEnabled(canCancel);
|
||||
CompletableFuture<T> future = futureSupplier.apply(monitor);
|
||||
future.handle((t, ex) -> {
|
||||
monitor.close();
|
||||
return null;
|
||||
});
|
||||
monitor.addCancelledListener(() -> future.cancel(true));
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,13 @@ package ghidra.app.plugin.core.debug.gui.action;
|
|||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
|
||||
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.options.SaveState;
|
||||
|
@ -31,6 +33,7 @@ import ghidra.framework.plugintool.PluginTool;
|
|||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* An interface for specifying how to automatically read target memory.
|
||||
|
@ -93,6 +96,12 @@ public interface AutoReadMemorySpec extends ExtensionPoint {
|
|||
* follow-up purposes only, and should always complete normally. It should complete with true if
|
||||
* any memory was actually loaded. Otherwise, it should complete with false.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> This returns the future, rather than being synchronous, because not all specs
|
||||
* will actually need to create a background task. If this were synchronous, the caller would
|
||||
* have to invoke it from a background thread, requiring it to create that thread whether or not
|
||||
* this method actually does anything.
|
||||
*
|
||||
* @param tool the tool containing the provider
|
||||
* @param coordinates the provider's current coordinates
|
||||
* @param visible the provider's visible addresses
|
||||
|
@ -100,4 +109,18 @@ public interface AutoReadMemorySpec extends ExtensionPoint {
|
|||
*/
|
||||
CompletableFuture<Boolean> readMemory(PluginTool tool, DebuggerCoordinates coordinates,
|
||||
AddressSetView visible);
|
||||
|
||||
/**
|
||||
* A convenience for performing target memory reads with progress displayed
|
||||
*
|
||||
* @param tool the tool for displaying progress
|
||||
* @param reader the method to perform the read, asynchronously
|
||||
* @return a future which returns true if the read completes
|
||||
*/
|
||||
default CompletableFuture<Boolean> doRead(PluginTool tool,
|
||||
Function<TaskMonitor, CompletableFuture<Void>> reader) {
|
||||
return TargetActionTask
|
||||
.executeTask(tool, getMenuName(), true, true, false, m -> reader.apply(m))
|
||||
.thenApply(__ -> true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.action;
|
|||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
|
@ -28,22 +29,25 @@ import docking.widgets.EventTrigger;
|
|||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractRefreshSelectedMemoryAction;
|
||||
import ghidra.app.plugin.core.debug.gui.action.AutoReadMemorySpec.AutoReadMemorySpecConfigFieldCodec;
|
||||
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
|
||||
import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.cmd.BackgroundCommand;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.model.DomainObjectChangeRecord;
|
||||
import ghidra.framework.model.DomainObjectEvent;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.TraceDomainObjectListener;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.trace.model.program.TraceProgramView;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.trace.util.TraceEvents;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public abstract class DebuggerReadsMemoryTrait {
|
||||
|
@ -69,22 +73,21 @@ public abstract class DebuggerReadsMemoryTrait {
|
|||
selection = visible;
|
||||
}
|
||||
final AddressSetView sel = selection;
|
||||
Trace trace = current.getTrace();
|
||||
Target target = current.getTarget();
|
||||
tool.executeBackgroundCommand(new BackgroundCommand(NAME, true, true, false) {
|
||||
|
||||
TargetActionTask.executeTask(tool, new Task(NAME, true, true, false) {
|
||||
@Override
|
||||
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
target.invalidateMemoryCaches();
|
||||
try {
|
||||
target.readMemory(sel, monitor);
|
||||
memoryWasRead(sel);
|
||||
target.readMemoryAsync(sel, monitor).get();
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
return false;
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException("Failed to read memory", e);
|
||||
}
|
||||
return true;
|
||||
memoryWasRead(sel);
|
||||
}
|
||||
}, trace);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -27,7 +27,6 @@ import ghidra.program.model.address.AddressSet;
|
|||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.trace.model.memory.TraceMemoryManager;
|
||||
import ghidra.trace.model.memory.TraceMemoryState;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec {
|
||||
public static final String CONFIG_NAME = "1_READ_VISIBLE";
|
||||
|
@ -68,6 +67,6 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec {
|
|||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
return target.readMemoryAsync(toRead, TaskMonitor.DUMMY).thenApply(__ -> true);
|
||||
return doRead(tool, monitor -> target.readMemoryAsync(toRead, monitor));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import ghidra.program.model.address.*;
|
|||
import ghidra.trace.model.Lifespan;
|
||||
import ghidra.trace.model.TraceAddressSnapRange;
|
||||
import ghidra.trace.model.memory.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec {
|
||||
public static final String CONFIG_NAME = "1_READ_VIS_RO_ONCE";
|
||||
|
@ -92,6 +91,6 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec {
|
|||
return CompletableFuture.completedFuture(false);
|
||||
}
|
||||
|
||||
return target.readMemoryAsync(toRead, TaskMonitor.DUMMY).thenApply(__ -> true);
|
||||
return doRead(tool, monitor -> target.readMemoryAsync(toRead, monitor));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
package ghidra.app.plugin.core.debug.gui.control;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.services.DebuggerConsoleService;
|
||||
import ghidra.app.services.ProgressService;
|
||||
import ghidra.async.AsyncUtils;
|
||||
import ghidra.debug.api.target.Target.ActionEntry;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -61,6 +64,29 @@ public class TargetActionTask extends Task {
|
|||
}
|
||||
}
|
||||
|
||||
static class FutureAsTask<T> extends Task {
|
||||
final CompletableFuture<T> future = new CompletableFuture<>();
|
||||
private final Function<TaskMonitor, CompletableFuture<T>> futureSupplier;
|
||||
|
||||
public FutureAsTask(String title, boolean canCancel, boolean hasProgress, boolean isModal,
|
||||
Function<TaskMonitor, CompletableFuture<T>> futureSupplier) {
|
||||
super(title, canCancel, hasProgress, isModal);
|
||||
this.futureSupplier = futureSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
CompletableFuture<T> future = futureSupplier.apply(monitor);
|
||||
future.handle(AsyncUtils.copyTo(this.future));
|
||||
try {
|
||||
future.get();
|
||||
}
|
||||
catch (InterruptedException | ExecutionException e) {
|
||||
// Client should get it via the copyTo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a task
|
||||
*
|
||||
|
@ -83,6 +109,30 @@ public class TargetActionTask extends Task {
|
|||
return wrapper.future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an asynchronous task
|
||||
*
|
||||
* @param tool the tool in which to execute
|
||||
* @param title the title of the task
|
||||
* @param canCancel if the task can be cancelled
|
||||
* @param hasProgress if the task displays progress
|
||||
* @param isModal if the task is modal
|
||||
* @param futureSupplier the task, a function of the monitor returning the future
|
||||
* @return a future which completes in the same way as the one returned by the supplier
|
||||
*/
|
||||
public static <T> CompletableFuture<T> executeTask(PluginTool tool, String title,
|
||||
boolean canCancel, boolean hasProgress, boolean isModal,
|
||||
Function<TaskMonitor, CompletableFuture<T>> futureSupplier) {
|
||||
ProgressService progressService = tool.getService(ProgressService.class);
|
||||
if (progressService != null) {
|
||||
return progressService.execute(canCancel, hasProgress, isModal, futureSupplier);
|
||||
}
|
||||
FutureAsTask<T> task =
|
||||
new FutureAsTask<>(title, canCancel, hasProgress, isModal, futureSupplier);
|
||||
tool.execute(task);
|
||||
return task.future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an {@link ActionEntry}
|
||||
*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue