GP-1560: Add 'Watch' memory and register context actions

This commit is contained in:
Dan 2022-01-26 11:55:44 -05:00
parent 4830d035b3
commit adeefc58c8
14 changed files with 473 additions and 89 deletions

View file

@ -119,6 +119,18 @@
<P>This action is always available. It adds a blank watch to the table. Modify the expression
to make the entry useful.</P>
<H3><A name="watch"></A>Watch</H3>
<P>This action is available in context menus for addresses, selections, or registers. It adds
the selected item(s) to the watch table. If the context is derived from a static view, it first
attempts to map it to the current trace. For selections, it adds a typeless watch for each
range. For single-address locations derived from a listing, it adds a typed watch on the
current code unit. For other single-address locations, it adds a 1-byte typeless watch on the
address. For registers, it adds a watch on that register. Note that <CODE>contextreg</CODE> and
its children cannot be watched, due to a limitation in Sleigh.</P>
<P>If the context</P>
<H3><A name="remove"></A>Remove</H3>
<P>This action is available when at least one watch is selected. It removes those watches.</P>

View file

@ -374,6 +374,7 @@ public interface DebuggerResources {
String GROUP_TRACE_CLOSE = "Dbg7.b. Trace Close";
String GROUP_MAINTENANCE = "Dbg8. Maintenance";
String GROUP_MAPPING = "Dbg9. Map Modules/Sections";
String GROUP_WATCHES = "DbgA. Watches";
String GROUP_DIFF_NAV = "DiffNavigate";
static void tableRowActivationAction(GTable table, Runnable runnable) {
@ -1552,16 +1553,16 @@ public interface DebuggerResources {
}
}
interface NewMemoryAction{
interface NewMemoryAction {
String NAME = "New Memory View";
String DESCRIPTION = "Open a new memory bytes view";
String GROUP = GROUP_TRANSIENT_VIEWS;
Icon ICON = ICON_MEMORY_BYTES;
String HELP_ANCHOR = "new_memory";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME,ownerName)
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.menuGroup(GROUP)
.menuIcon(ICON)
@ -1569,7 +1570,7 @@ public interface DebuggerResources {
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
abstract class AbstractStepSnapForwardAction extends DockingAction {
public static final String NAME = "Step Trace Snap Forward";
public static final Icon ICON = ICON_SNAP_FORWARD;
@ -1932,6 +1933,24 @@ public interface DebuggerResources {
}
}
interface WatchAction {
String NAME = "Watch";
String DESCRIPTION = "Watch the selected item";
String GROUP = GROUP_WATCHES;
Icon ICON = ICON_PROVIDER_WATCHES;
String HELP_ANCHOR = "watch";
static ActionBuilder builder(Plugin owner) {
String ownerName = owner.getName();
return new ActionBuilder(NAME, ownerName)
.description(DESCRIPTION)
.popupMenuPath(NAME)
.popupMenuGroup(GROUP)
.popupMenuIcon(ICON)
.helpLocation(new HelpLocation(ownerName, HELP_ANCHOR));
}
}
interface HideScratchSnapshotsAction {
String NAME = "Hide Scratch";
String DESCRIPTION = "Hide negative snaps, typically used as emulation scratch space";
@ -2042,6 +2061,7 @@ public interface DebuggerResources {
public String getToolTip() {
return "A connected debugger client";
}
}
static <T> Function<Throwable, T> showError(Component parent, String message) {

View file

@ -1109,6 +1109,14 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
return loadRegistersAndValues();
}
public RegisterRow getRegisterRow(Register register) {
return regMap.get(register);
}
public void setSelectedRow(RegisterRow row) {
regsFilterPanel.setSelectedItem(row);
}
public DebuggerRegistersProvider cloneAsDisconnected() {
DebuggerRegistersProvider clone = plugin.createNewDisconnectedProvider();
clone.coordinatesActivated(current); // This should also enact the same selection

View file

@ -18,10 +18,12 @@ package ghidra.app.plugin.core.debug.gui.watch;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.*;
import java.util.List;
import java.util.Objects;
import java.util.function.*;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.swing.*;
import javax.swing.table.TableColumn;
@ -33,12 +35,16 @@ import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.context.ListingActionContext;
import ghidra.app.context.ProgramLocationActionContext;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.*;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.app.plugin.core.debug.gui.register.DebuggerRegisterActionContext;
import ghidra.app.plugin.core.debug.gui.register.RegisterRow;
import ghidra.app.services.*;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.async.AsyncDebouncer;
import ghidra.async.AsyncTimer;
import ghidra.base.widgets.table.DataTypeTableCellEditor;
@ -51,16 +57,19 @@ import ghidra.framework.options.annotation.HelpInfo;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeConflictException;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Listing;
import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.*;
import ghidra.trace.model.Trace.TraceMemoryBytesChangeType;
import ghidra.trace.model.Trace.TraceMemoryStateChangeType;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.time.schedule.TraceSchedule;
import ghidra.trace.util.TraceAddressSpace;
import ghidra.util.Msg;
@ -243,6 +252,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
// TODO: Allow address marking
@AutoServiceConsumed
private DebuggerTraceManagerService traceManager; // For goto time (emu mods)
@AutoServiceConsumed
private DebuggerStaticMappingService mappingService; // For listing action
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@ -285,6 +296,9 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
DockingAction actionAdd;
DockingAction actionRemove;
DockingAction actionAddFromLocation;
DockingAction actionAddFromRegister;
private DebuggerWatchActionContext myActionContext;
public DebuggerWatchesProvider(DebuggerWatchesPlugin plugin) {
@ -423,6 +437,18 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
.enabledWhen(ctx -> !ctx.getWatchRows().isEmpty())
.onAction(this::activatedRemove)
.buildAndInstallLocal(this);
// Pop-up context actions
actionAddFromLocation = WatchAction.builder(plugin)
.withContext(ProgramLocationActionContext.class)
.enabledWhen(this::hasDynamicLocation)
.onAction(this::activatedAddFromLocation)
.buildAndInstall(tool);
actionAddFromRegister = WatchAction.builder(plugin)
.withContext(DebuggerRegisterActionContext.class)
.enabledWhen(this::hasValidWatchRegister)
.onAction(this::activatedAddFromRegister)
.buildAndInstall(tool);
}
protected boolean selHasDataType(DebuggerWatchActionContext ctx) {
@ -549,10 +575,135 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
watchTableModel.deleteWith(context.getWatchRows()::contains);
}
private ProgramLocation getDynamicLocation(ProgramLocation someLoc) {
if (someLoc.getProgram() instanceof TraceProgramView) {
return someLoc;
}
return mappingService.getDynamicLocationFromStatic(current.getView(), someLoc);
}
private AddressSetView getDynamicAddresses(Program program, AddressSetView set) {
if (program instanceof TraceProgramView) {
return set;
}
if (set == null) {
return null;
}
AddressSet result = new AddressSet();
for (Entry<TraceSpan, Collection<MappedAddressRange>> ent : mappingService
.getOpenMappedViews(program, set)
.entrySet()) {
if (ent.getKey().getTrace() != current.getTrace()) {
continue;
}
if (!ent.getKey().getSpan().contains(current.getSnap())) {
continue;
}
for (MappedAddressRange rng : ent.getValue()) {
result.add(rng.getDestinationAddressRange());
}
}
return result;
}
private boolean hasDynamicLocation(ProgramLocationActionContext context) {
ProgramLocation dynLoc = getDynamicLocation(context.getLocation());
return dynLoc != null;
}
private boolean tryForSelection(ProgramLocationActionContext context) {
AddressSetView dynSel = getDynamicAddresses(context.getProgram(), context.getSelection());
if (dynSel == null || dynSel.isEmpty()) {
return false;
}
for (AddressRange rng : dynSel) {
addWatch(TraceSleighUtils
.generateExpressionForRange(current.getTrace().getBaseLanguage(), rng));
}
return true;
}
private boolean tryForDataInListing(ProgramLocationActionContext context) {
if (!(context instanceof ListingActionContext)) {
return false;
}
ListingActionContext lac = (ListingActionContext) context;
CodeUnit cu = lac.getCodeUnit();
if (cu == null) {
return false;
}
AddressSet cuAs = new AddressSet();
cuAs.add(cu.getMinAddress(), cu.getMaxAddress());
AddressSetView dynCuAs = getDynamicAddresses(context.getProgram(), cuAs);
// Verify mapping is complete and contiguous
if (dynCuAs.getNumAddressRanges() != 1) {
return false;
}
AddressRange dynCuRng = dynCuAs.getFirstRange();
if (dynCuRng.getLength() != cu.getLength()) {
return false;
}
WatchRow row = addWatch(TraceSleighUtils
.generateExpressionForRange(current.getTrace().getBaseLanguage(), dynCuRng));
if (cu instanceof Data) {
Data data = (Data) cu;
// TODO: Problems may arise if trace and program have different data organizations
row.setDataType(data.getDataType());
}
return true;
}
private boolean trySingleAddress(ProgramLocationActionContext context) {
ProgramLocation dynLoc = getDynamicLocation(context.getLocation());
if (dynLoc == null) {
return false;
}
addWatch(TraceSleighUtils.generateExpressionForRange(current.getTrace().getBaseLanguage(),
new AddressRangeImpl(dynLoc.getAddress(), dynLoc.getAddress())));
return true;
}
private void activatedAddFromLocation(ProgramLocationActionContext context) {
if (tryForSelection(context)) {
return;
}
if (tryForDataInListing(context)) {
return;
}
trySingleAddress(context);
}
private boolean hasValidWatchRegister(DebuggerRegisterActionContext context) {
RegisterRow row = context.getSelected();
if (row == null) {
return false;
}
if (row.getRegister().isProcessorContext()) {
return false;
}
return true;
}
private void activatedAddFromRegister(DebuggerRegisterActionContext context) {
RegisterRow regRow = context.getSelected();
if (regRow == null) {
return;
}
Register reg = regRow.getRegister();
if (reg.isProcessorContext()) {
return;
}
WatchRow watchRow = addWatch(reg.getName());
watchRow.setDataType(regRow.getDataType());
}
public WatchRow addWatch(String expression) {
WatchRow row = new WatchRow(this, expression);
WatchRow row = new WatchRow(this, "");
row.setCoordinates(current);
watchTableModel.add(row);
row.setExpression(expression);
return row;
}

View file

@ -135,8 +135,8 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
return staticRange.getMinAddress();
}
public TraceSnap getTraceSnap() {
return new DefaultTraceSnap(mapping.getTrace(), mapping.getStartSnap());
public TraceSpan getTraceSpan() {
return new DefaultTraceSpan(mapping.getTrace(), mapping.getLifespan());
}
public TraceAddressSnapRange getTraceAddressSnapRange() {
@ -499,7 +499,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
}
protected void collectOpenMappedViews(AddressRange rng,
Map<TraceSnap, Collection<MappedAddressRange>> result) {
Map<TraceSpan, Collection<MappedAddressRange>> result) {
for (Entry<MappingEntry, Address> inPreceeding : inbound.headMapByValue(
rng.getMaxAddress(), true).entrySet()) {
Address start = inPreceeding.getValue();
@ -513,14 +513,14 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
AddressRange srcRange = me.staticRange.intersect(rng);
AddressRange dstRange = me.mapProgramRangeToTrace(rng);
result.computeIfAbsent(me.getTraceSnap(), p -> new TreeSet<>())
result.computeIfAbsent(me.getTraceSpan(), p -> new TreeSet<>())
.add(new MappedAddressRange(srcRange, dstRange));
}
}
public Map<TraceSnap, Collection<MappedAddressRange>> getOpenMappedViews(
public Map<TraceSpan, Collection<MappedAddressRange>> getOpenMappedViews(
AddressSetView set) {
Map<TraceSnap, Collection<MappedAddressRange>> result = new HashMap<>();
Map<TraceSpan, Collection<MappedAddressRange>> result = new HashMap<>();
for (AddressRange rng : set) {
collectOpenMappedViews(rng, result);
}
@ -922,7 +922,7 @@ public class DebuggerStaticMappingServicePlugin extends Plugin
}
@Override
public Map<TraceSnap, Collection<MappedAddressRange>> getOpenMappedViews(Program program,
public Map<TraceSpan, Collection<MappedAddressRange>> getOpenMappedViews(Program program,
AddressSetView set) {
synchronized (lock) {
InfoPerProgram info = requireTrackedInfo(program);

View file

@ -355,7 +355,7 @@ public interface DebuggerStaticMappingService {
* @param set the destination address set, from which we are mapping back
* @return a map of source traces to corresponding computed source address ranges
*/
Map<TraceSnap, Collection<MappedAddressRange>> getOpenMappedViews(Program program,
Map<TraceSpan, Collection<MappedAddressRange>> getOpenMappedViews(Program program,
AddressSetView set);
/**

View file

@ -38,9 +38,13 @@ import org.junit.rules.TestName;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import docking.ActionContext;
import docking.action.ActionContextProvider;
import docking.action.DockingActionIf;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import generic.Unique;
import ghidra.app.nav.Navigatable;
import ghidra.app.plugin.core.debug.gui.action.*;
import ghidra.app.plugin.core.debug.mapping.*;
import ghidra.app.plugin.core.debug.service.model.*;
@ -58,8 +62,7 @@ import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.DefaultLanguageService;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.*;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.trace.database.ToyDBTraceBuilder;
@ -439,6 +442,23 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
});
}
protected static void assertDisabled(ActionContextProvider provider, DockingActionIf action) {
ActionContext context = provider.getActionContext(null);
assertFalse(action.isEnabledForContext(context));
}
protected static void assertEnabled(ActionContextProvider provider, DockingActionIf action) {
ActionContext context = provider.getActionContext(null);
assertTrue(action.isEnabledForContext(context));
}
protected static void performEnabledAction(ActionContextProvider provider,
DockingActionIf action, boolean wait) {
ActionContext context = provider.getActionContext(null);
waitForCondition(() -> action.isEnabledForContext(context));
performAction(action, context, wait);
}
protected static void goTo(ListingPanel listingPanel, ProgramLocation location) {
waitForPass(() -> {
runSwing(() -> listingPanel.goTo(location));
@ -448,6 +468,18 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest
});
}
protected void select(Navigatable nav, Address min, Address max) {
select(nav, new ProgramSelection(min, max));
}
protected void select(Navigatable nav, AddressSetView set) {
select(nav, new ProgramSelection(set));
}
protected void select(Navigatable nav, ProgramSelection sel) {
runSwing(() -> nav.setSelection(sel));
}
protected static LocationTrackingSpec getLocationTrackingSpec(String name) {
return LocationTrackingSpec.fromConfigName(name);
}

View file

@ -26,7 +26,6 @@ import org.junit.Test;
import com.google.common.collect.Range;
import docking.ActionContext;
import docking.action.DockingActionIf;
import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
@ -43,7 +42,6 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.test.ToyProgramBuilder;
import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.model.DefaultTraceLocation;
@ -68,29 +66,20 @@ public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerG
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
}
protected void select(Address min, Address max) {
select(new ProgramSelection(min, max));
}
protected void select(AddressSetView set) {
select(new ProgramSelection(set));
}
protected void select(ProgramSelection sel) {
runSwing(() -> {
listingProvider.setSelection(sel);
});
}
protected void assertDisabled(DockingActionIf action) {
ActionContext context = listingProvider.getActionContext(null);
assertFalse(action.isEnabledForContext(context));
assertDisabled(listingProvider, action);
}
protected void performEnabledAction(DockingActionIf action) {
ActionContext context = listingProvider.getActionContext(null);
waitForCondition(() -> action.isEnabledForContext(context));
performAction(action, context, false);
performEnabledAction(listingProvider, action, false);
}
protected void select(Address min, Address max) {
select(listingProvider, min, max);
}
protected void select(AddressSetView set) {
select(listingProvider, set);
}
@Test

View file

@ -23,9 +23,6 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import docking.ActionContext;
import docking.action.ActionContextProvider;
import docking.action.DockingActionIf;
import docking.widgets.dialogs.InputDialog;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
@ -92,23 +89,6 @@ public class DebuggerTimeProviderTest extends AbstractGhidraHeadedDebuggerGUITes
// Timestamp is left unchecked, since default is current time
}
protected static void assertDisabled(ActionContextProvider provider, DockingActionIf action) {
ActionContext context = provider.getActionContext(null);
assertFalse(action.isEnabledForContext(context));
}
protected static void assertEnabled(ActionContextProvider provider, DockingActionIf action) {
ActionContext context = provider.getActionContext(null);
assertTrue(action.isEnabledForContext(context));
}
protected static void performEnabledAction(ActionContextProvider provider,
DockingActionIf action, boolean wait) {
ActionContext context = provider.getActionContext(null);
waitForCondition(() -> action.isEnabledForContext(context));
performAction(action, context, wait);
}
@Test // TODO: Technically, this is a plugin action.... Different test case?
public void testActionRenameSnapshot() throws Exception {
// More often than not, this action will be used from the dynamic listing

View file

@ -19,30 +19,38 @@ import static org.junit.Assert.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.*;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.*;
import com.google.common.collect.Range;
import generic.Unique;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider;
import ghidra.app.plugin.core.debug.gui.register.*;
import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncTestUtils;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
import ghidra.program.model.data.LongDataType;
import ghidra.program.model.data.LongLongDataType;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.mem.Memory;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.DefaultTraceLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.memory.*;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceRegisterUtils;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.task.TaskMonitor;
public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUITest
implements AsyncTestUtils {
@ -57,6 +65,9 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
protected DebuggerWatchesPlugin watchesPlugin;
protected DebuggerWatchesProvider watchesProvider;
protected DebuggerListingPlugin listingPlugin;
protected DebuggerListingProvider listingProvider;
protected DebuggerStaticMappingServicePlugin mappingService;
protected CodeViewerProvider codeViewerProvider;
protected Register r0;
protected Register r1;
@ -67,9 +78,15 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
@Before
public void setUpWatchesProviderTest() throws Exception {
// Do this before listing, because DebuggerListing also implements CodeViewer
addPlugin(tool, CodeBrowserPlugin.class);
codeViewerProvider = waitForComponentProvider(CodeViewerProvider.class);
watchesPlugin = addPlugin(tool, DebuggerWatchesPlugin.class);
watchesProvider = waitForComponentProvider(DebuggerWatchesProvider.class);
listingPlugin = addPlugin(tool, DebuggerListingPlugin.class);
listingProvider = waitForComponentProvider(DebuggerListingProvider.class);
mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class);
createTrace();
r0 = tb.language.getRegister("r0");
@ -404,4 +421,149 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertFalse(bank.regVals.containsKey("r1"));
}
protected void setupUnmappedDataSection() throws Throwable {
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryOperations mem = tb.trace.getMemoryManager();
mem.createRegion("Memory[bin:.data]", 0, tb.range(0x00600000, 0x0060ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
}
waitForDomainObject(tb.trace);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
waitForSwing();
}
protected void setupMappedDataSection() throws Throwable {
createProgramFromTrace();
intoProject(tb.trace);
intoProject(program);
try (UndoableTransaction tid = tb.startTransaction()) {
TraceMemoryOperations mem = tb.trace.getMemoryManager();
mem.createRegion("Memory[bin:.data]", 0, tb.range(0x55750000, 0x5575ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
}
waitForDomainObject(tb.trace);
traceManager.openTrace(tb.trace);
traceManager.activateTrace(tb.trace);
programManager.openProgram(program);
AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace();
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add block", true)) {
Memory mem = program.getMemory();
mem.createInitializedBlock(".data", tb.addr(stSpace, 0x00600000), 0x10000,
(byte) 0, TaskMonitor.DUMMY, false);
}
DefaultTraceLocation tloc =
new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x55750000));
ProgramLocation ploc = new ProgramLocation(program, tb.addr(stSpace, 0x00600000));
try (UndoableTransaction tid = tb.startTransaction()) {
mappingService.addMapping(tloc, ploc, 0x10000, false);
}
waitForValue(() -> mappingService.getOpenMappedLocation(tloc));
}
@Test
public void testActionWatchViaListingDynamicSelection() throws Throwable {
setupUnmappedDataSection();
select(listingProvider,
tb.set(tb.range(0x00600000, 0x0060000f), tb.range(0x00600020, 0x0060002f)));
waitForSwing();
performEnabledAction(listingProvider, watchesProvider.actionAddFromLocation, true);
List<WatchRow> watches = new ArrayList<>(watchesProvider.watchTableModel.getModelData());
watches.sort(Comparator.comparing(WatchRow::getExpression));
assertEquals(2, watches.size());
assertEquals("*:16 0x00600000:8", watches.get(0).getExpression());
assertEquals("*:16 0x00600020:8", watches.get(1).getExpression());
}
@Test
public void testActionWatchViaListingStaticSelection() throws Throwable {
setupMappedDataSection();
select(codeViewerProvider,
tb.set(tb.range(0x00600000, 0x0060000f), tb.range(0x00600020, 0x0060002f)));
waitForSwing();
performEnabledAction(codeViewerProvider, watchesProvider.actionAddFromLocation, true);
List<WatchRow> watches = new ArrayList<>(watchesProvider.watchTableModel.getModelData());
watches.sort(Comparator.comparing(WatchRow::getExpression));
assertEquals(2, watches.size());
assertEquals("*:16 0x55750000:8", watches.get(0).getExpression());
assertEquals("*:16 0x55750020:8", watches.get(1).getExpression());
}
@Test
public void testActionWatchViaListingDynamicDataUnit() throws Throwable {
setupUnmappedDataSection();
Structure structDt = new StructureDataType("myStruct", 0);
structDt.add(DWordDataType.dataType, "field0", "");
structDt.add(DWordDataType.dataType, "field4", "");
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getCodeManager()
.definedData()
.create(Range.atLeast(0L), tb.addr(0x00600000), structDt);
}
// TODO: Test with expanded structure?
performEnabledAction(listingProvider, watchesProvider.actionAddFromLocation, true);
WatchRow watch = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
assertEquals("*:8 0x00600000:8", watch.getExpression());
assertTypeEquals(structDt, watch.getDataType());
}
@Test
public void testActionWatchViaListingStaticDataUnit() throws Throwable {
setupMappedDataSection();
AddressSpace stSpace = program.getAddressFactory().getDefaultAddressSpace();
Structure structDt = new StructureDataType("myStruct", 0);
structDt.add(DWordDataType.dataType, "field0", "");
structDt.add(DWordDataType.dataType, "field4", "");
try (UndoableTransaction tid = UndoableTransaction.start(program, "Add data", true)) {
program.getListing().createData(tb.addr(stSpace, 0x00600000), structDt);
}
// TODO: Test with expanded structure?
performEnabledAction(codeViewerProvider, watchesProvider.actionAddFromLocation, true);
WatchRow watch = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
assertEquals("*:8 0x55750000:8", watch.getExpression());
assertTypeEquals(structDt, watch.getDataType());
}
@Test
public void testActionWatchViaRegisters() throws Throwable {
addPlugin(tool, DebuggerRegistersPlugin.class);
DebuggerRegistersProvider registersProvider =
waitForComponentProvider(DebuggerRegistersProvider.class);
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
RegisterRow rowR0 = registersProvider.getRegisterRow(r0);
rowR0.setDataType(PointerDataType.dataType);
registersProvider.setSelectedRow(rowR0);
waitForSwing();
performEnabledAction(registersProvider, watchesProvider.actionAddFromRegister, true);
WatchRow watch = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
assertEquals("r0", watch.getExpression());
assertTypeEquals(PointerDataType.dataType, watch.getDataType());
}
}

View file

@ -384,7 +384,7 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
copyTrace();
add2ndMapping();
Map<TraceSnap, Collection<MappedAddressRange>> views =
Map<TraceSpan, Collection<MappedAddressRange>> views =
mappingService.getOpenMappedViews(program, new AddressSet());
assertTrue(views.isEmpty());
}
@ -407,12 +407,14 @@ public class DebuggerStaticMappingServiceTest extends AbstractGhidraHeadedDebugg
// After
set.add(stSpace.getAddress(0xbadbadbadL), stSpace.getAddress(0xbadbadbadL + 0xff));
Map<TraceSnap, Collection<MappedAddressRange>> views =
Map<TraceSpan, Collection<MappedAddressRange>> views =
mappingService.getOpenMappedViews(program, set);
Msg.info(this, views);
assertEquals(2, views.size());
Collection<MappedAddressRange> mappedSet1 = views.get(new DefaultTraceSnap(tb.trace, 0));
Collection<MappedAddressRange> mappedSet2 = views.get(new DefaultTraceSnap(copy, 0));
Collection<MappedAddressRange> mappedSet1 =
views.get(new DefaultTraceSpan(tb.trace, Range.atLeast(0L)));
Collection<MappedAddressRange> mappedSet2 =
views.get(new DefaultTraceSpan(copy, Range.atLeast(0L)));
assertEquals(Set.of(
new MappedAddressRange(tb.range(stSpace, 0x00200000, 0x002000ff),

View file

@ -24,6 +24,7 @@ import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
@ -155,4 +156,15 @@ public enum TraceSleighUtils {
SleighProgramCompiler.compileExpression((SleighLanguage) language, expr),
trace, snap, thread, frame);
}
public static String generateExpressionForRange(Language language, AddressRange range) {
AddressSpace space = range.getAddressSpace();
long length = range.getLength();
long offset = range.getMinAddress().getOffset();
int ptrSize = space.getPointerSize();
if (language != null && language.getDefaultSpace() == space) {
return String.format("*:%d 0x%08x:%d", length, offset, ptrSize);
}
return String.format("*[%s]:%d 0x%08x:%d", space.getName(), length, offset, ptrSize);
}
}

View file

@ -17,18 +17,26 @@ package ghidra.trace.model;
import java.util.Objects;
import com.google.common.collect.Range;
import ghidra.trace.database.DBTraceUtils;
/**
* NOTE: This is used to mark <trace,snap>; regardless of whether that snapshot is actually in the
* database.... Cannot just use TraceSnapshot here.
*/
public class DefaultTraceSnap implements TraceSnap {
public class DefaultTraceSpan implements TraceSpan {
private final Trace trace;
private final long snap;
private final Range<Long> span;
public DefaultTraceSnap(Trace trace, long snap) {
private final int hash;
public DefaultTraceSpan(Trace trace, Range<Long> span) {
this.trace = trace;
this.snap = snap;
this.span = span;
this.hash = Objects.hash(trace, span);
}
@Override
@ -37,13 +45,13 @@ public class DefaultTraceSnap implements TraceSnap {
}
@Override
public long getSnap() {
return snap;
public Range<Long> getSpan() {
return span;
}
@Override
public String toString() {
return "TraceSnap<" + trace + ": " + snap + ">";
return "TraceSnap<" + trace + ": " + span + ">";
}
@Override
@ -51,14 +59,14 @@ public class DefaultTraceSnap implements TraceSnap {
if (this == obj) {
return true;
}
if (!(obj instanceof DefaultTraceSnap)) {
if (!(obj instanceof DefaultTraceSpan)) {
return false;
}
DefaultTraceSnap that = (DefaultTraceSnap) obj;
DefaultTraceSpan that = (DefaultTraceSpan) obj;
if (this.trace != that.trace) {
return false;
}
if (this.snap != that.snap) {
if (!Objects.equals(this.span, that.span)) {
return false;
}
return true;
@ -66,11 +74,11 @@ public class DefaultTraceSnap implements TraceSnap {
@Override
public int hashCode() {
return Objects.hash(trace, snap);
return hash;
}
@Override
public int compareTo(TraceSnap that) {
public int compareTo(TraceSpan that) {
if (this == that) {
return 0;
}
@ -79,7 +87,13 @@ public class DefaultTraceSnap implements TraceSnap {
if (result != 0) {
return result;
}
result = Long.compareUnsigned(this.snap, that.getSnap());
result = Long.compare(DBTraceUtils.lowerEndpoint(this.span),
DBTraceUtils.lowerEndpoint(that.getSpan()));
if (result != 0) {
return result;
}
result = Long.compare(DBTraceUtils.upperEndpoint(this.span),
DBTraceUtils.upperEndpoint(that.getSpan()));
if (result != 0) {
return result;
}

View file

@ -15,8 +15,10 @@
*/
package ghidra.trace.model;
public interface TraceSnap extends Comparable<TraceSnap> {
import com.google.common.collect.Range;
public interface TraceSpan extends Comparable<TraceSpan> {
Trace getTrace();
long getSnap();
Range<Long> getSpan();
}