GP-1930: Make the 'refresh memory' button better

This commit is contained in:
Dan 2022-05-05 12:32:39 -04:00
parent 6e70f97143
commit 81d5b3f24d
8 changed files with 71 additions and 48 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -144,8 +144,7 @@ public interface DebuggerResources {
ImageIcon ICON_AUTOREAD = ResourceManager.loadImage("images/autoread.png");
// TODO: Draw a real icon.
ImageIcon ICON_READ_MEMORY = ICON_REGIONS;
//ResourceManager.loadImage("images/read-memory.png");
ImageIcon ICON_REFRESH_MEMORY = ICON_REFRESH;
ImageIcon ICON_RENAME_SNAPSHOT = ICON_TIME;
@ -784,12 +783,12 @@ public interface DebuggerResources {
}
}
abstract class AbstractReadSelectedMemoryAction extends DockingAction {
abstract class AbstractRefreshSelectedMemoryAction extends DockingAction {
public static final String NAME = "Read Selected Memory";
public static final Icon ICON = ICON_READ_MEMORY;
public static final Icon ICON = ICON_REFRESH_MEMORY;
public static final String HELP_ANCHOR = "read_memory";
public AbstractReadSelectedMemoryAction(Plugin owner) {
public AbstractRefreshSelectedMemoryAction(Plugin owner) {
super(NAME, owner.getName());
setDescription(
"(Re-)read and record memory for the selected addresses into the trace database");

View file

@ -16,7 +16,9 @@
package ghidra.app.plugin.core.debug.gui.action;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import docking.ActionContext;
import docking.ComponentProvider;
@ -27,12 +29,16 @@ import docking.menu.MultiStateDockingAction;
import docking.widgets.EventTrigger;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractReadSelectedMemoryAction;
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.utils.BackgroundUtils;
import ghidra.app.services.TraceRecorder;
import ghidra.app.services.TraceRecorderListener;
import ghidra.app.util.viewer.listingpanel.AddressSetDisplayListener;
import ghidra.dbg.DebuggerObjectModel;
import ghidra.dbg.target.TargetMemory;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.PathMatcher;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
@ -49,10 +55,10 @@ public abstract class DebuggerReadsMemoryTrait {
protected static final AutoConfigState.ClassHandler<DebuggerReadsMemoryTrait> CONFIG_STATE_HANDLER =
AutoConfigState.wireHandler(DebuggerReadsMemoryTrait.class, MethodHandles.lookup());
protected class ReadSelectedMemoryAction extends AbstractReadSelectedMemoryAction {
protected class RefreshSelectedMemoryAction extends AbstractRefreshSelectedMemoryAction {
public static final String GROUP = DebuggerResources.GROUP_GENERAL;
public ReadSelectedMemoryAction() {
public RefreshSelectedMemoryAction() {
super(plugin);
setToolBarData(new ToolBarData(ICON, GROUP));
setEnabled(false);
@ -60,22 +66,40 @@ public abstract class DebuggerReadsMemoryTrait {
@Override
public void actionPerformed(ActionContext context) {
AddressSetView selection = getSelection();
if (selection == null || selection.isEmpty() || !current.isAliveAndReadsPresent()) {
if (!current.isAliveAndReadsPresent()) {
return;
}
AddressSetView selection = getSelection();
if (selection == null || selection.isEmpty()) {
selection = visible;
}
final AddressSetView sel = selection;
Trace trace = current.getTrace();
TraceRecorder recorder = current.getRecorder();
BackgroundUtils.async(tool, trace, NAME, true, true, false,
(__, monitor) -> recorder.captureProcessMemory(selection, monitor, false));
BackgroundUtils.async(tool, trace, NAME, true, true, false, (_t, monitor) -> {
TargetObject target = recorder.getTarget();
DebuggerObjectModel model = target.getModel();
model.invalidateAllLocalCaches();
PathMatcher memMatcher = target.getSchema().searchFor(TargetMemory.class, true);
Collection<TargetObject> memories = memMatcher.getCachedSuccessors(target).values();
CompletableFuture<?>[] requests = memories.stream()
.map(TargetObject::invalidateCaches)
.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(requests).thenCompose(_r -> {
return recorder.captureProcessMemory(sel, monitor, false);
});
});
}
@Override
public boolean isEnabledForContext(ActionContext context) {
AddressSetView selection = getSelection();
if (selection == null || selection.isEmpty() || !current.isAliveAndReadsPresent()) {
if (!current.isAliveAndReadsPresent()) {
return false;
}
AddressSetView selection = getSelection();
if (selection == null || selection.isEmpty()) {
selection = visible;
}
TraceRecorder recorder = current.getRecorder();
// TODO: Either allow partial, or provide action to intersect with accessible
if (!recorder.getAccessibleProcessMemory().contains(selection)) {
@ -96,7 +120,7 @@ public abstract class DebuggerReadsMemoryTrait {
}
private void snapshotAdded(TraceSnapshot snapshot) {
actionReadSelected.updateEnabled(null);
actionRefreshSelected.updateEnabled(null);
}
private void memStateChanged(TraceAddressSnapRange range, TraceMemoryState oldIsNull,
@ -120,7 +144,7 @@ public abstract class DebuggerReadsMemoryTrait {
@Override
public void processMemoryAccessibilityChanged(TraceRecorder recorder) {
Swing.runIfSwingOrRunLater(() -> {
actionReadSelected.updateEnabled(null);
actionRefreshSelected.updateEnabled(null);
});
}
}
@ -137,7 +161,7 @@ public abstract class DebuggerReadsMemoryTrait {
}
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoRead;
protected ReadSelectedMemoryAction actionReadSelected;
protected RefreshSelectedMemoryAction actionRefreshSelected;
private final AutoReadMemorySpec defaultAutoSpec =
AutoReadMemorySpec.fromConfigName(VisibleROOnceAutoReadMemorySpec.CONFIG_NAME);
@ -257,10 +281,10 @@ public abstract class DebuggerReadsMemoryTrait {
}
}
public DockingAction installReadSelectedAction() {
actionReadSelected = new ReadSelectedMemoryAction();
provider.addLocalAction(actionReadSelected);
return actionReadSelected;
public DockingAction installRefreshSelectedAction() {
actionRefreshSelected = new RefreshSelectedMemoryAction();
provider.addLocalAction(actionRefreshSelected);
return actionRefreshSelected;
}
public AddressSetDisplayListener getDisplayListener() {

View file

@ -244,7 +244,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
protected SyncToStaticListingAction actionSyncToStaticListing;
protected FollowsCurrentThreadAction actionFollowsCurrentThread;
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
protected DockingAction actionReadSelectedMemory;
protected DockingAction actionRefreshSelectedMemory;
protected DockingAction actionOpenProgram;
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
@ -643,7 +643,7 @@ public class DebuggerListingProvider extends CodeViewerProvider {
actionGoTo = goToTrait.installAction();
actionTrackLocation = trackingTrait.installAction();
actionAutoReadMemory = readsMemTrait.installAutoReadAction();
actionReadSelectedMemory = readsMemTrait.installReadSelectedAction();
actionRefreshSelectedMemory = readsMemTrait.installRefreshSelectedAction();
actionOpenProgram = OpenProgramAction.builder(plugin)
.withContext(DebuggerOpenProgramActionContext.class)

View file

@ -149,7 +149,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
protected DockingAction actionGoTo;
protected FollowsCurrentThreadAction actionFollowsCurrentThread;
protected MultiStateDockingAction<AutoReadMemorySpec> actionAutoReadMemory;
protected DockingAction actionReadSelectedMemory;
protected DockingAction actionRefreshSelectedMemory;
protected MultiStateDockingAction<LocationTrackingSpec> actionTrackLocation;
protected ForMemoryBytesGoToTrait goToTrait;
@ -263,7 +263,7 @@ public class DebuggerMemoryBytesProvider extends ProgramByteViewerComponentProvi
actionGoTo = goToTrait.installAction();
actionTrackLocation = trackingTrait.installAction();
actionAutoReadMemory = readsMemTrait.installAutoReadAction();
actionReadSelectedMemory = readsMemTrait.installReadSelectedAction();
actionRefreshSelectedMemory = readsMemTrait.installRefreshSelectedAction();
}
@Override

View file

@ -80,7 +80,7 @@ public class DebuggerListingPluginScreenShots extends GhidraScreenShotGenerator
"clone", global, SourceType.USER_DEFINED);
TraceSymbol childLabel = symbolManager
.labels()
.create(snap, null, tb.addr(0x00400034),
.create(snap, null, tb.addr(0x00400032),
"child", global, SourceType.USER_DEFINED);
@SuppressWarnings("unused")
TraceSymbol exitLabel = symbolManager

View file

@ -933,7 +933,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
byte[] data = incBlock();
byte[] zero = new byte[data.length];
ByteBuffer buf = ByteBuffer.allocate(data.length);
assertFalse(listingProvider.actionReadSelectedMemory.isEnabled());
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
listingProvider.setAutoReadMemorySpec(readNone);
// To verify enabled requires live target
@ -948,12 +948,12 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
traceManager.activateTrace(tb.trace);
waitForSwing();
// Still
assertFalse(listingProvider.actionReadSelectedMemory.isEnabled());
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
listingProvider.setSelection(sel);
waitForSwing();
// Still
assertFalse(listingProvider.actionReadSelectedMemory.isEnabled());
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
// Now, simulate the sequence that typically enables the action
createTestModel();
@ -970,12 +970,12 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
// NOTE: recordTargetContainerAndOpenTrace has already activated the trace
// Action is still disabled, because it requires a selection
assertFalse(listingProvider.actionReadSelectedMemory.isEnabled());
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
listingProvider.setSelection(sel);
waitForSwing();
// Now, it should be enabled
assertTrue(listingProvider.actionReadSelectedMemory.isEnabled());
assertTrue(listingProvider.actionRefreshSelectedMemory.isEnabled());
// First check nothing captured yet
buf.clear();
@ -984,7 +984,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertArrayEquals(zero, buf.array());
// Verify that the action performs the expected task
performAction(listingProvider.actionReadSelectedMemory);
performAction(listingProvider.actionRefreshSelectedMemory);
waitForBusyTool(tool);
waitForDomainObject(trace);
@ -999,28 +999,28 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
// Verify that setting the memory inaccessible disables the action
mb.testProcess1.memory.setAccessible(false);
waitForPass(() -> assertFalse(listingProvider.actionReadSelectedMemory.isEnabled()));
waitForPass(() -> assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled()));
// Verify that setting it accessible re-enables it (assuming we still have selection)
mb.testProcess1.memory.setAccessible(true);
waitForPass(() -> assertTrue(listingProvider.actionReadSelectedMemory.isEnabled()));
waitForPass(() -> assertTrue(listingProvider.actionRefreshSelectedMemory.isEnabled()));
// Verify that moving into the past disables the action
TraceSnapshot forced = recorder.forceSnapshot();
waitForSwing(); // UI Wants to sync with new snap. Wait....
traceManager.activateSnap(forced.getKey() - 1);
waitForSwing();
assertFalse(listingProvider.actionReadSelectedMemory.isEnabled());
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
// Verify that advancing to the present enables the action (assuming a selection)
traceManager.activateSnap(forced.getKey());
waitForSwing();
assertTrue(listingProvider.actionReadSelectedMemory.isEnabled());
assertTrue(listingProvider.actionRefreshSelectedMemory.isEnabled());
// Verify that stopping the recording disables the action
recorder.stopRecording();
waitForSwing();
assertFalse(listingProvider.actionReadSelectedMemory.isEnabled());
assertFalse(listingProvider.actionRefreshSelectedMemory.isEnabled());
// TODO: When resume recording is implemented, verify action is enabled with selection
}

View file

@ -735,7 +735,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
byte[] data = incBlock();
byte[] zero = new byte[data.length];
ByteBuffer buf = ByteBuffer.allocate(data.length);
assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled());
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
runSwing(() -> memBytesProvider.setAutoReadMemorySpec(readNone));
// To verify enabled requires live target
@ -750,12 +750,12 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
traceManager.activateTrace(tb.trace);
waitForSwing();
// Still
assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled());
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
memBytesProvider.setSelection(sel);
waitForSwing();
// Still
assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled());
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// Now, simulate the sequence that typically enables the action
createTestModel();
@ -772,12 +772,12 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
// NOTE: recordTargetContainerAndOpenTrace has already activated the trace
// Action is still disabled, because it requires a selection
assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled());
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
memBytesProvider.setSelection(sel);
waitForSwing();
// Now, it should be enabled
assertTrue(memBytesProvider.actionReadSelectedMemory.isEnabled());
assertTrue(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// First check nothing recorded yet
buf.clear();
@ -786,7 +786,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
assertArrayEquals(zero, buf.array());
// Verify that the action performs the expected task
performAction(memBytesProvider.actionReadSelectedMemory);
performAction(memBytesProvider.actionRefreshSelectedMemory);
waitForBusyTool(tool);
waitForDomainObject(trace);
@ -801,28 +801,28 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
// Verify that setting the memory inaccessible disables the action
mb.testProcess1.memory.setAccessible(false);
waitForPass(() -> assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled()));
waitForPass(() -> assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled()));
// Verify that setting it accessible re-enables it (assuming we still have selection)
mb.testProcess1.memory.setAccessible(true);
waitForPass(() -> assertTrue(memBytesProvider.actionReadSelectedMemory.isEnabled()));
waitForPass(() -> assertTrue(memBytesProvider.actionRefreshSelectedMemory.isEnabled()));
// Verify that moving into the past disables the action
TraceSnapshot forced = recorder.forceSnapshot();
waitForSwing(); // UI Wants to sync with new snap. Wait....
traceManager.activateSnap(forced.getKey() - 1);
waitForSwing();
assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled());
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// Verify that advancing to the present enables the action (assuming a selection)
traceManager.activateSnap(forced.getKey());
waitForSwing();
assertTrue(memBytesProvider.actionReadSelectedMemory.isEnabled());
assertTrue(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// Verify that stopping the recording disables the action
recorder.stopRecording();
waitForSwing();
assertFalse(memBytesProvider.actionReadSelectedMemory.isEnabled());
assertFalse(memBytesProvider.actionRefreshSelectedMemory.isEnabled());
// TODO: When resume recording is implemented, verify action is enabled with selection
}