mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-1560: Add 'Watch' memory and register context actions
This commit is contained in:
parent
4830d035b3
commit
adeefc58c8
14 changed files with 473 additions and 89 deletions
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue