From cd32ab60be3c1511b65181936cf7d891dcbe1954 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Thu, 10 Dec 2020 11:35:28 -0500 Subject: [PATCH] GP-475: Completed the Debugger "Watches" plugin --- Ghidra/Debug/Debugger/certification.manifest | 2 + .../src/main/help/help/TOC_Source.xml | 4 + .../DebuggerWatchesPlugin.html | 135 +++++++ .../images/DebuggerWatchesPlugin.png | Bin 0 -> 19832 bytes .../core/debug/DebuggerCoordinates.java | 4 + .../core/debug/gui/DebuggerResources.java | 65 ++- .../DebuggerListingAutoReadMemoryAction.java | 8 +- .../register/DebuggerRegistersProvider.java | 1 - .../gui/watch/DebuggerWatchesPlugin.java | 14 +- .../gui/watch/DebuggerWatchesProvider.java | 373 ++++++++++++++++-- .../plugin/core/debug/gui/watch/WatchRow.java | 185 +++++++-- .../DebuggerTraceManagerServicePlugin.java | 9 +- .../main/resources/defaultTools/Debugger.tool | 212 +++++++--- .../DebuggerWatchesPluginScreenShots.java | 91 +++++ .../listing/DebuggerListingProviderTest.java | 6 +- .../watch/DebuggerWatchesProviderTest.java | 234 +++++++++++ .../database/memory/DBTraceMemoryManager.java | 8 +- .../space/DBTraceDelegatingManager.java | 11 + .../AbstractDBTraceMemoryManagerTest.java | 9 +- .../exec/AddressOfPcodeExecutorState.java | 4 +- .../java/ghidra/pcode/exec/PcodeExecutor.java | 7 + 21 files changed, 1225 insertions(+), 157 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html create mode 100644 Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png create mode 100644 Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPluginScreenShots.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java diff --git a/Ghidra/Debug/Debugger/certification.manifest b/Ghidra/Debug/Debugger/certification.manifest index 241b0602fb..21cfd14c2d 100644 --- a/Ghidra/Debug/Debugger/certification.manifest +++ b/Ghidra/Debug/Debugger/certification.manifest @@ -101,6 +101,8 @@ src/main/help/help/topics/DebuggerThreadsPlugin/images/stepinto.png||GHIDRA||||E src/main/help/help/topics/DebuggerTimePlugin/DebuggerTimePlugin.html||GHIDRA||||END| src/main/help/help/topics/DebuggerTimePlugin/images/DebuggerTimePlugin.png||GHIDRA||||END| src/main/help/help/topics/DebuggerTraceManagerServicePlugin/DebuggerTraceManagerServicePlugin.html||GHIDRA||||END| +src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html||GHIDRA||||END| +src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png||GHIDRA||||END| src/main/resources/defaultTools/Debugger.tool||GHIDRA||||END| src/main/resources/define_info_proc_mappings||GHIDRA||||END| src/main/resources/images/add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END| diff --git a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml index 6e53ece5b8..52045e9a48 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml +++ b/Ghidra/Debug/Debugger/src/main/help/help/TOC_Source.xml @@ -128,6 +128,10 @@ target="help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html" /> + + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html new file mode 100644 index 0000000000..1c2a00fc3a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html @@ -0,0 +1,135 @@ + + + + + + + Debugger: Watches + + + + + +

Debugger: Watches

+ + + + + + + +
+ +

Watches refer to expressions which are evaluated each pause in order to monitor the value of + variables in the target machine state. The watch variables are expressed in Sleigh and + evaluated in the current thread's and trace's context at the current point in time. If the + current trace is live and at the present, then the target state is read and recorded as + necessary. The watch can be assigned a data type so that the raw data is rendered in a + meaningful way. When applicable, that data type can optionally be applied to the trace + database. Some metadata about the watch is also given, e.g., the address of the value.

+ +

Examples

+ +

For those less familar with Sleigh, here are some example expressions:

+ + + +

Table Columns

+ +

The table displays and allows modification of each watch. It has the following columns:

+ + + +

Actions

+ +

The watches window provides the following actions:

+ +

Apply Data to Listing

+ +

This action is available when there's an active trace and at least one watch with an address + and data type is selected. If so, it applies that data type to the value in the listing. That + is, it attempts to apply the selected data type to the evaluated address, sizing it to the + value's size.

+ +

Select Range

+ +

This action is available when there's an active trace and at least one watch with memory + addresses is selected. It selects the memory range comprising the resulting value. This only + works when the outermost operator of the expression is a memory dereference. It selects the + range at the address of that dereference having the size of the dereference. For example, the + expression *:8 RSP would cause 8 bytes of memory, starting at the offset given by + RSP, to be selected in the dynamic listing.

+ +

Select Reads

+ +

This action is available when there's an active trace and at least one watch with memory + reads is selected. It selects all memory ranges dereferenced in the course of expression + evaluation. This can be useful when examining a watch whose value seems unusual. For example, + the expression *:8 RSP would cause 8 bytes of memory, starting at the offset given + by RSP, to be selected in the dynamic listing -- the same result as Select Range. However, the + expression *:4 (*:8 RSP) would cause two ranges to be selected: 8 bytes starting + at RSP and 4 bytes starting at the offset given by *:8 RSP.

+ +

Add

+ +

This action is always available. It adds a blank watch to the table.

+ +

Remove

+ +

This action is available when at least one watch is selected. It removes those watches.

+ +

Tool Options: Colors

+ +

The watch window uses colors to hint about changes in and freshness of displayed values. + They can be configured in the tool's options. By default, changed values are displayed in red, + and stale values are displayed in dark grey. A "stale" value is one which depends on any + register or memory whose contents are not known. The value displayed is that computed from the + last recorded contents, defaulting to 0 when never recorded. Simply, a "changed" watch is one + whose value has just changed. For example, if a value changes as result of stepping, then that + watch is changed. However, given the possibility of rewinding, changing thread focus, etc., + "changed" is actually subtly more flexible. The watch remembers the evaluation from the user's + last coordinates (time, thread, frame, etc.) as well as the current coordinates. So, "changed" + more precisely refers to a watch whose value differs between those two coordinates. This + permits the user to switch focus between different coordinates and quickly identify what is + different.

+ + diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/images/DebuggerWatchesPlugin.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b63a6770a1d1580ec7d35139507e150b67f360 GIT binary patch literal 19832 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 RdbA!S;pyt|;g=NkY3 literal 0 HcmV?d00001 diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java index d0bf327a4b..c9f042cc4f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java @@ -356,4 +356,8 @@ public class DebuggerCoordinates { public boolean isPresent() { return recorder.getSnap() == snap; } + + public boolean isAliveAndPresent() { + return isAlive() && isPresent(); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 9b80f11f6f..eeb6f969fc 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -121,7 +121,7 @@ public interface DebuggerResources { // TODO: Draw an icon ImageIcon ICON_SELECT_ADDRESSES = ResourceManager.loadImage("images/NextSelectionBlock16.gif"); // TODO: Draw an icon? - ImageIcon ICON_CAPTURE_TYPES = ResourceManager.loadImage("images/dataTypes.png"); + ImageIcon ICON_DATA_TYPES = ResourceManager.loadImage("images/dataTypes.png"); // TODO: Draw an icon? ImageIcon ICON_CAPTURE_SYMBOLS = ResourceManager.loadImage("images/closedFolderLabels.png"); @@ -222,6 +222,15 @@ public interface DebuggerResources { String OPTION_NAME_COLORS_REGISTER_CHANGED_SEL = "Colors.Changed Registers (selected)"; Color DEFAULT_COLOR_REGISTER_CHANGED_SEL = ColorUtils.blend(Color.RED, Color.WHITE, 0.5f); + String OPTION_NAME_COLORS_WATCH_STALE = "Colors.Stale Watches"; + Color DEFAULT_COLOR_WATCH_STALE = Color.GRAY; + String OPTION_NAME_COLORS_WATCH_STALE_SEL = "Colors.Stale Watches (selected)"; + Color DEFAULT_COLOR_WATCH_STALE_SEL = Color.LIGHT_GRAY; + String OPTION_NAME_COLORS_WATCH_CHANGED = "Colors.Changed Watches"; + Color DEFAULT_COLOR_WATCH_CHANGED = Color.RED; + String OPTION_NAME_COLORS_WATCH_CHANGED_SEL = "Colors.Changed Watches (selected)"; + Color DEFAULT_COLOR_WATCH_CHANGED_SEL = ColorUtils.blend(Color.RED, Color.WHITE, 0.5f); + String MARKER_NAME_BREAKPOINT_ENABLED = "Enabled Breakpoint"; String MARKER_NAME_BREAKPOINT_DISABLED = "Disabled Breakpoint"; String MARKER_NAME_BREAKPOINT_MIXED_ED = "Mixed Enabled-Disabled Breakpont"; @@ -1090,7 +1099,7 @@ public interface DebuggerResources { abstract class AbstractCaptureTypesAction extends DockingAction { public static final String NAME = "Capture Data Types"; - public static final Icon ICON = ICON_CAPTURE_TYPES; + public static final Icon ICON = ICON_DATA_TYPES; public static final String HELP_ANCHOR = "capture_types"; public AbstractCaptureTypesAction(Plugin owner) { @@ -1309,6 +1318,58 @@ public interface DebuggerResources { } } + interface ApplyDataTypeAction { + String NAME = "Apply Data to Listing "; + String DESCRIPTION = + "Apply the selected data type at the address of this value in the listing"; + String GROUP = GROUP_GENERAL; + Icon ICON = ICON_DATA_TYPES; + String HELP_ANCHOR = "apply_data_type"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarGroup(GROUP) + .toolBarIcon(ICON) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface SelectWatchRangeAction { + String NAME = "Select Range"; + String DESCRIPTION = "For memory watches, select the range comprising the value"; + String GROUP = GROUP_GENERAL; + Icon ICON = ICON_SELECT_ADDRESSES; + String HELP_ANCHOR = "select_addresses"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarGroup(GROUP) + .toolBarIcon(ICON) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface SelectWatchReadsAction { + String NAME = "Select Reads"; + String DESCRIPTION = "Select every memory range read evaluating this watch"; + String GROUP = GROUP_GENERAL; + Icon ICON = ICON_REGIONS; // TODO: Meh. Better icon. + String HELP_ANCHOR = "select_reads"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarGroup(GROUP) + .toolBarIcon(ICON) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + public abstract class AbstractDebuggerConnectionsNode extends GTreeNode { @Override public String getName() { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingAutoReadMemoryAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingAutoReadMemoryAction.java index b9f7afc25b..80c861576b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingAutoReadMemoryAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingAutoReadMemoryAction.java @@ -104,10 +104,10 @@ public interface DebuggerListingAutoReadMemoryAction extends AutoReadMemoryActio @Override public CompletableFuture readMemory(DebuggerCoordinates coordinates, AddressSetView visible) { - TraceRecorder recorder = coordinates.getRecorder(); - if (recorder == null || !coordinates.isPresent()) { + if (!coordinates.isAliveAndPresent()) { return AsyncUtils.NIL; } + TraceRecorder recorder = coordinates.getRecorder(); AddressSet visibleAccessible = recorder.getAccessibleProcessMemory().intersect(visible); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); @@ -135,10 +135,10 @@ public interface DebuggerListingAutoReadMemoryAction extends AutoReadMemoryActio @Override public CompletableFuture readMemory(DebuggerCoordinates coordinates, AddressSetView visible) { - TraceRecorder recorder = coordinates.getRecorder(); - if (recorder == null || !coordinates.isPresent()) { + if (!coordinates.isAliveAndPresent()) { return AsyncUtils.NIL; } + TraceRecorder recorder = coordinates.getRecorder(); AddressSet visibleAccessible = recorder.getAccessibleProcessMemory().intersect(visible); TraceMemoryManager mm = coordinates.getTrace().getMemoryManager(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java index 9595314f2d..140f0a5ec8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java @@ -301,7 +301,6 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter } class RegisterValueCellRenderer extends HexBigIntegerTableCellRenderer { - @Override public final Component getTableCellRendererComponent(GTableCellRenderingData data) { super.getTableCellRendererComponent(data); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPlugin.java index 3f2f1c5322..2a3f0fa884 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPlugin.java @@ -20,6 +20,7 @@ import ghidra.app.plugin.core.debug.AbstractDebuggerPlugin; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceActivatedPluginEvent; import ghidra.app.services.*; +import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @@ -28,7 +29,7 @@ import ghidra.framework.plugintool.util.PluginStatus; description = "GUI to watch values of expressions", // category = PluginCategoryNames.DEBUGGER, // packageName = DebuggerPluginPackage.NAME, // - status = PluginStatus.UNSTABLE, // + status = PluginStatus.RELEASED, // eventsConsumed = { TraceActivatedPluginEvent.class, // }, // @@ -39,7 +40,6 @@ import ghidra.framework.plugintool.util.PluginStatus; } // ) public class DebuggerWatchesPlugin extends AbstractDebuggerPlugin { - private static final String KEY_EXPRESSION_LIST = "expressionList"; private DebuggerWatchesProvider provider; @@ -66,4 +66,14 @@ public class DebuggerWatchesPlugin extends AbstractDebuggerPlugin { provider.coordinatesActivated(ev.getActiveCoordinates()); } } + + @Override + public void writeConfigState(SaveState saveState) { + provider.writeConfigState(saveState); + } + + @Override + public void readConfigState(SaveState saveState) { + provider.readConfigState(saveState); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java index 5ecce17f28..eab673f94c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java @@ -15,61 +15,83 @@ */ package ghidra.app.plugin.core.debug.gui.watch; -import java.awt.BorderLayout; +import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Function; import javax.swing.*; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import docking.ActionContext; import docking.WindowPosition; -import docking.widgets.table.DefaultEnumeratedColumnTableModel; +import docking.action.DockingAction; +import docking.widgets.table.*; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; 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.async.AsyncDebouncer; import ghidra.async.AsyncTimer; +import ghidra.base.widgets.table.DataTypeTableCellEditor; +import ghidra.docking.settings.Settings; import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObjectChangeRecord; +import ghidra.framework.options.SaveState; +import ghidra.framework.options.annotation.AutoOptionDefined; +import ghidra.framework.options.annotation.HelpInfo; import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; -import ghidra.lifecycle.Unfinished; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSet; +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.util.CodeUnitInsertionException; +import ghidra.program.util.ProgramSelection; import ghidra.trace.model.*; import ghidra.trace.model.Trace.TraceMemoryBytesChangeType; import ghidra.trace.model.Trace.TraceMemoryStateChangeType; import ghidra.trace.util.TraceAddressSpace; +import ghidra.util.Msg; import ghidra.util.Swing; +import ghidra.util.database.UndoableTransaction; import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTableFilterPanel; +import ghidra.util.table.column.AbstractGColumnRenderer; + +public class DebuggerWatchesProvider extends ComponentProviderAdapter { + private static final String KEY_EXPRESSION_LIST = "expressionList"; + private static final String KEY_TYPE_LIST = "typeList"; -public class DebuggerWatchesProvider extends ComponentProviderAdapter implements Unfinished { protected enum WatchTableColumns implements EnumeratedTableColumn { EXPRESSION("Expression", String.class, WatchRow::getExpression, WatchRow::setExpression), ADDRESS("Address", Address.class, WatchRow::getAddress), - DATA_TYPE("Data Type", DataType.class, WatchRow::getDataType, WatchRow::setDataType), - RAW("Raw", String.class, WatchRow::getRawValueString), - VALUE("Value", String.class, WatchRow::getValueString), - ERROR("Error", String.class, WatchRow::getError); + VALUE("Value", String.class, WatchRow::getRawValueString), + TYPE("Type", DataType.class, WatchRow::getDataType, WatchRow::setDataType), + REPR("Repr", String.class, WatchRow::getValueString), + ERROR("Error", String.class, WatchRow::getErrorMessage); private final String header; private final Function getter; - private final BiConsumer setter; + private final BiConsumer setter; private final Class cls; + @SuppressWarnings("unchecked") WatchTableColumns(String header, Class cls, Function getter, BiConsumer setter) { this.header = header; this.cls = cls; this.getter = getter; - this.setter = setter; + this.setter = (BiConsumer) setter; } WatchTableColumns(String header, Class cls, Function getter) { @@ -91,6 +113,11 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter implements return header; } + @Override + public void setValueOf(WatchRow row, Object value) { + setter.accept(row, value); + } + @Override public boolean isEditable(WatchRow row) { return setter != null; @@ -137,49 +164,122 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter implements } private void bytesChanged(TraceAddressSpace space, TraceAddressSnapRange range) { - if (space.getThread() == current.getThread()) { + if (space.getThread() == current.getThread() || space.getThread() == null) { changed.add(range.getRange()); changeDebouncer.contact(null); } } private void stateChanged(TraceAddressSpace space, TraceAddressSnapRange range) { - if (space.getThread() == current.getThread()) { + if (space.getThread() == current.getThread() || space.getThread() == null) { changed.add(range.getRange()); changeDebouncer.contact(null); } } } + class WatchDataTypeEditor extends DataTypeTableCellEditor { + public WatchDataTypeEditor() { + super(plugin.getTool()); + } + + @Override + protected DataType resolveSelection(DataType dataType) { + if (dataType == null) { + return null; + } + try (UndoableTransaction tid = + UndoableTransaction.start(currentTrace, "Resolve DataType", true)) { + return currentTrace.getDataTypeManager().resolve(dataType, null); + } + } + } + + class WatchValueCellRenderer extends AbstractGColumnRenderer { + @Override + public Component getTableCellRendererComponent(GTableCellRenderingData data) { + super.getTableCellRendererComponent(data); + WatchRow row = (WatchRow) data.getRowObject(); + if (!row.isKnown()) { + if (data.isSelected()) { + setForeground(watchStaleSelColor); + } + else { + setForeground(watchStaleColor); + } + } + else if (row.isChanged()) { + if (data.isSelected()) { + setForeground(watchChangesSelColor); + } + else { + setForeground(watchChangesColor); + } + } + return this; + } + + @Override + public String getFilterString(String t, Settings settings) { + return t; + } + } + + final DebuggerWatchesPlugin plugin; + DebuggerCoordinates current = DebuggerCoordinates.NOWHERE; private Trace currentTrace; // Copy for transition @AutoServiceConsumed - private DebuggerListingService listingService; // TODO: For goto + private DebuggerListingService listingService; // TODO: For goto and selection // TODO: Allow address marking @SuppressWarnings("unused") private final AutoService.Wiring autoServiceWiring; + @AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_WATCH_STALE, // + description = "Text color for watches whose value is not known", // + help = @HelpInfo(anchor = "colors")) + protected Color watchStaleColor = DebuggerResources.DEFAULT_COLOR_WATCH_STALE; + @AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_WATCH_STALE_SEL, // + description = "Selected text color for watches whose value is not known", // + help = @HelpInfo(anchor = "colors")) + protected Color watchStaleSelColor = DebuggerResources.DEFAULT_COLOR_WATCH_STALE_SEL; + @AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_WATCH_CHANGED, // + description = "Text color for watches whose value just changed", // + help = @HelpInfo(anchor = "colors")) + protected Color watchChangesColor = DebuggerResources.DEFAULT_COLOR_WATCH_CHANGED; + @AutoOptionDefined(name = DebuggerResources.OPTION_NAME_COLORS_WATCH_CHANGED_SEL, // + description = "Selected text color for watches whose value just changed", // + help = @HelpInfo(anchor = "colors")) + protected Color watchChangesSelColor = DebuggerResources.DEFAULT_COLOR_WATCH_CHANGED_SEL; + private final AddressSet changed = new AddressSet(); private final AsyncDebouncer changeDebouncer = new AsyncDebouncer<>(AsyncTimer.DEFAULT_TIMER, 100); private ForDepsListener forDepsListener = new ForDepsListener(); + private JPanel mainPanel = new JPanel(new BorderLayout()); + protected final WatchTableModel watchTableModel = new WatchTableModel(); protected GhidraTable watchTable; protected GhidraTableFilterPanel watchFilterPanel; - private JPanel mainPanel = new JPanel(new BorderLayout()); + DockingAction actionApplyDataType; + DockingAction actionSelectRange; + DockingAction actionSelectAllReads; + DockingAction actionAdd; + DockingAction actionRemove; private DebuggerWatchActionContext myActionContext; public DebuggerWatchesProvider(DebuggerWatchesPlugin plugin) { super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_WATCHES, plugin.getName()); + this.plugin = plugin; this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); setIcon(DebuggerResources.ICON_PROVIDER_WATCHES); - setHelpLocation(DebuggerResources.HELP_PROVIDER_STACK); + setHelpLocation(DebuggerResources.HELP_PROVIDER_WATCHES); setWindowMenuGroup(DebuggerPluginPackage.NAME); buildMainPanel(); @@ -193,6 +293,14 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter implements changeDebouncer.addListener(__ -> doCheckDepsAndReevaluate()); } + @Override + public ActionContext getActionContext(MouseEvent event) { + if (myActionContext != null) { + return myActionContext; + } + return super.getActionContext(event); + } + protected void buildMainPanel() { watchTable = new GhidraTable(watchTableModel); mainPanel.add(new JScrollPane(watchTable)); @@ -211,19 +319,34 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter implements if (e.getClickCount() != 2 || e.getButton() != MouseEvent.BUTTON1) { return; } - if (listingService == null) { - return; - } if (myActionContext == null) { return; } + WatchRow row = myActionContext.getWatchRow(); + if (row == null) { + return; + } + Throwable error = row.getError(); + if (error != null) { + Msg.showError(this, getComponent(), "Evaluation error", + "Could not evaluate watch", error); + return; + } Address address = myActionContext.getWatchRow().getAddress(); - if (address == null || !address.isMemoryAddress()) { + if (listingService == null || address == null || !address.isMemoryAddress()) { return; } listingService.goTo(address, true); } }); + + TableColumnModel columnModel = watchTable.getColumnModel(); + TableColumn addrCol = columnModel.getColumn(WatchTableColumns.ADDRESS.ordinal()); + addrCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT); + TableColumn valCol = columnModel.getColumn(WatchTableColumns.VALUE.ordinal()); + valCol.setCellRenderer(new WatchValueCellRenderer()); + TableColumn typeCol = columnModel.getColumn(WatchTableColumns.TYPE.ordinal()); + typeCol.setCellEditor(new WatchDataTypeEditor()); } @Override @@ -234,10 +357,162 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter implements } protected void createActions() { - // TODO: Apply data type to listing - // TODO: Select read addresses - // TODO: Add - // TODO: Remove + actionApplyDataType = ApplyDataTypeAction.builder(plugin) + .withContext(DebuggerWatchActionContext.class) + .enabledWhen(ctx -> current.getTrace() != null && selHasDataType(ctx)) + .onAction(this::activatedApplyDataType) + .buildAndInstallLocal(this); + actionSelectRange = SelectWatchRangeAction.builder(plugin) + .withContext(DebuggerWatchActionContext.class) + .enabledWhen(ctx -> current.getTrace() != null && listingService != null && + selHasMemoryRanges(ctx)) + .onAction(this::activatedSelectRange) + .buildAndInstallLocal(this); + actionSelectAllReads = SelectWatchReadsAction.builder(plugin) + .withContext(DebuggerWatchActionContext.class) + .enabledWhen(ctx -> current.getTrace() != null && listingService != null && + selHasMemoryReads(ctx)) + .onAction(this::activatedSelectReads) + .buildAndInstallLocal(this); + actionAdd = AddAction.builder(plugin) + .onAction(this::activatedAdd) + .buildAndInstallLocal(this); + actionRemove = RemoveAction.builder(plugin) + .withContext(DebuggerWatchActionContext.class) + .enabledWhen(ctx -> !ctx.getWatchRows().isEmpty()) + .onAction(this::activatedRemove) + .buildAndInstallLocal(this); + } + + protected boolean selHasDataType(DebuggerWatchActionContext ctx) { + for (WatchRow row : ctx.getWatchRows()) { + Address address = row.getAddress(); + if (row.getDataType() != null && address != null && address.isMemoryAddress() && + row.getValueLength() != 0) { + return true; + } + } + return false; + } + + protected boolean selHasMemoryRanges(DebuggerWatchActionContext ctx) { + for (WatchRow row : ctx.getWatchRows()) { + AddressRange rng = row.getRange(); + if (rng != null && rng.getAddressSpace().isMemorySpace()) { + return true; + } + } + return false; + } + + protected boolean selHasMemoryReads(DebuggerWatchActionContext ctx) { + for (WatchRow row : ctx.getWatchRows()) { + AddressSet set = row.getReads(); + if (set == null) { + continue; + } + for (AddressRange rng : set) { + if (rng.getAddressSpace().isMemorySpace()) { + return true; + } + } + } + return false; + } + + private void activatedApplyDataType(DebuggerWatchActionContext context) { + if (current.getTrace() == null) { + return; + } + List errs = new ArrayList<>(); + for (WatchRow row : context.getWatchRows()) { + DataType dataType = row.getDataType(); + if (dataType == null) { + continue; + } + Address address = row.getAddress(); + if (address == null) { + continue; + } + if (!address.isMemoryAddress()) { + continue; + } + int size = row.getValueLength(); + if (size == 0) { + continue; + } + + // Using the view will handle the "from-now-until-whenever" logic. + Listing listing = current.getView().getListing(); + // Avoid a transaction that just replaces it with an equivalent.... + Data existing = listing.getDefinedDataAt(address); + if (existing != null) { + if (existing.getDataType().isEquivalent(dataType)) { + return; + } + } + try (UndoableTransaction tid = + UndoableTransaction.start(current.getTrace(), "Apply Watch Data Type", true)) { + try { + listing.clearCodeUnits(row.getAddress(), row.getRange().getMaxAddress(), false); + listing.createData(address, dataType, size); + } + catch (CodeUnitInsertionException | DataTypeConflictException e) { + errs.add(address + " " + dataType + "(" + size + "): " + e.getMessage()); + } + } + } + if (!errs.isEmpty()) { + StringBuffer msg = new StringBuffer("One or more types could not be applied:"); + for (String line : errs) { + msg.append("\n "); + msg.append(line); + } + Msg.showError(this, getComponent(), "Apply Data Type", msg.toString()); + } + } + + private void activatedSelectRange(DebuggerWatchActionContext context) { + if (listingService == null) { + return; + } + AddressSet sel = new AddressSet(); + for (WatchRow row : context.getWatchRows()) { + AddressRange rng = row.getRange(); + if (rng != null) { + sel.add(rng); + } + } + listingService.setCurrentSelection(new ProgramSelection(sel)); + } + + private void activatedSelectReads(DebuggerWatchActionContext context) { + if (listingService == null) { + return; + } + AddressSet sel = new AddressSet(); + for (WatchRow row : context.getWatchRows()) { + AddressSet reads = row.getReads(); + if (reads != null) { + sel.add(reads); + } + } + listingService.setCurrentSelection(new ProgramSelection(sel)); + } + + private void activatedAdd(ActionContext ignored) { + addWatch(""); + } + + private void activatedRemove(DebuggerWatchActionContext context) { + watchTableModel.deleteWith(context.getWatchRows()::contains); + } + + public WatchRow addWatch(String expression) { + WatchRow row = new WatchRow(this, expression); + row.setCoordinates(current); + watchTableModel.add(row); + return row; } @Override @@ -277,28 +552,39 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter implements doSetTrace(current.getTrace()); - if (current.getRecorder() != null) { + setRowsContext(coordinates); + + if (current.isAliveAndPresent()) { readTarget(); } reevaluate(); + Swing.runIfSwingOrRunLater(() -> watchTableModel.fireTableDataChanged()); + } + + public synchronized void setRowsContext(DebuggerCoordinates coordinates) { + for (WatchRow row : watchTableModel.getModelData()) { + row.setCoordinates(coordinates); + } } public synchronized void readTarget() { for (WatchRow row : watchTableModel.getModelData()) { - if (row.getReads().intersects(changed)) { - row.doTargetReads(); - } + row.doTargetReads(); } } public synchronized void doCheckDepsAndReevaluate() { for (WatchRow row : watchTableModel.getModelData()) { - if (row.getReads().intersects(changed)) { + AddressSet reads = row.getReads(); + if (reads == null || reads.intersects(changed)) { row.reevaluate(); } } changed.clear(); - Swing.runIfSwingOrRunLater(() -> watchTableModel.fireTableDataChanged()); + Swing.runIfSwingOrRunLater(() -> { + watchTableModel.fireTableDataChanged(); + contextChanged(); + }); } public void reevaluate() { @@ -307,4 +593,29 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter implements } changed.clear(); } + + public void writeConfigState(SaveState saveState) { + List rows = List.copyOf(watchTableModel.getModelData()); + String[] expressions = rows.stream().map(WatchRow::getExpression).toArray(String[]::new); + String[] types = rows.stream().map(WatchRow::getTypePath).toArray(String[]::new); + saveState.putStrings(KEY_EXPRESSION_LIST, expressions); + saveState.putStrings(KEY_TYPE_LIST, types); + } + + public void readConfigState(SaveState saveState) { + String[] expressions = saveState.getStrings(KEY_EXPRESSION_LIST, new String[] {}); + String[] types = saveState.getStrings(KEY_TYPE_LIST, new String[] {}); + if (expressions.length != types.length) { + Msg.error(this, "Watch provider config error. Unequal number of expressions and types"); + return; + } + int len = expressions.length; + List rows = new ArrayList<>(); + for (int i = 0; i < len; i++) { + WatchRow r = new WatchRow(this, expressions[i]); + r.setTypePath(types[i]); + rows.add(r); + } + watchTableModel.addAll(rows); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java index eb97e81931..d1a14e0647 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java @@ -15,14 +15,20 @@ */ package ghidra.app.plugin.core.debug.gui.watch; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + import org.apache.commons.lang3.tuple.Pair; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.app.services.DataTypeManagerService; import ghidra.docking.settings.SettingsImpl; import ghidra.pcode.exec.*; import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState; import ghidra.pcode.exec.trace.TraceSleighUtils; +import ghidra.pcode.utils.Utils; import ghidra.program.model.address.*; import ghidra.program.model.data.DataType; import ghidra.program.model.lang.Language; @@ -37,12 +43,14 @@ import ghidra.util.Swing; public class WatchRow { private final DebuggerWatchesProvider provider; + private Trace trace; private SleighLanguage language; private PcodeExecutor> executorWithState; private ReadDepsPcodeExecutor executorWithAddress; private AsyncPcodeExecutor asyncExecutor; private String expression; + private String typePath; private DataType dataType; private SleighExpression compiled; @@ -50,8 +58,9 @@ public class WatchRow { private Address address; private AddressSet reads; private byte[] value; + private byte[] prevValue; // Value at previous coordinates private String valueString; - private String error = ""; + private Throwable error = null; public WatchRow(DebuggerWatchesProvider provider, String expression) { this.provider = provider; @@ -59,8 +68,6 @@ public class WatchRow { } protected void blank() { - error = null; - compiled = null; state = null; address = null; reads = null; @@ -69,20 +76,27 @@ public class WatchRow { } protected void recompile() { - this.error = null; + compiled = null; + error = null; + if (expression == null || expression.length() == 0) { + return; + } + if (language == null) { + return; + } try { - this.compiled = SleighProgramCompiler.compileExpression(language, expression); + compiled = SleighProgramCompiler.compileExpression(language, expression); } catch (Exception e) { - this.error = e.getMessage(); + error = e; return; } } protected void doTargetReads() { - if (asyncExecutor != null) { + if (compiled != null && asyncExecutor != null) { compiled.evaluate(asyncExecutor).exceptionally(ex -> { - error = ex.getMessage(); + error = ex; Swing.runIfSwingOrRunLater(() -> { provider.watchTableModel.notifyUpdated(this); }); @@ -94,11 +108,15 @@ public class WatchRow { protected void reevaluate() { blank(); + if (trace == null || compiled == null) { + return; + } try { Pair valueWithState = compiled.evaluate(executorWithState); Pair valueWithAddress = compiled.evaluate(executorWithAddress); value = valueWithState.getLeft(); + error = null; state = valueWithState.getRight(); address = valueWithAddress.getRight(); reads = executorWithAddress.getReads(); @@ -106,12 +124,12 @@ public class WatchRow { valueString = parseAsDataType(); } catch (Exception e) { - error = e.getMessage(); + error = e; } } protected String parseAsDataType() { - if (dataType == null) { + if (dataType == null || value == null) { return ""; } MemBuffer buffer = new ByteMemBufferImpl(address, value, language.isBigEndian()); @@ -128,14 +146,19 @@ public class WatchRow { } @Override - protected byte[] getFromSpace(TraceMemorySpace space, long offset, int size) { - byte[] data = super.getFromSpace(space, offset, size); - try { - reads.add( - new AddressRangeImpl(space.getAddressSpace().getAddress(offset), data.length)); + public byte[] getVar(AddressSpace space, long offset, int size, + boolean truncateAddressableUnit) { + byte[] data = super.getVar(space, offset, size, truncateAddressableUnit); + if (space.isMemorySpace()) { + offset = truncateOffset(space, offset); } - catch (AddressOverflowException | AddressOutOfBoundsException e) { - throw new AssertionError(e); + if (space.isMemorySpace() || space.isRegisterSpace()) { + try { + reads.add(new AddressRangeImpl(space.getAddress(offset), data.length)); + } + catch (AddressOverflowException | AddressOutOfBoundsException e) { + throw new AssertionError(e); + } } return data; } @@ -191,46 +214,85 @@ public class WatchRow { return new ReadDepsPcodeExecutor(state, language, arithmetic, paired); } - public void setContext(DebuggerCoordinates coordinates) { - Trace trace = coordinates.getTrace(); + public void setCoordinates(DebuggerCoordinates coordinates) { + // NB. Caller has already verified coordinates actually changed + prevValue = value; + trace = coordinates.getTrace(); + updateType(); if (trace == null) { blank(); - error = "No trace nor thread active"; return; } Language newLanguage = trace.getBaseLanguage(); - if (this.language != newLanguage) { + if (language != newLanguage) { if (!(newLanguage instanceof SleighLanguage)) { - error = "No a sleigh-based langauge"; + error = new RuntimeException("Not a sleigh-based langauge"); return; } - this.language = (SleighLanguage) newLanguage; + language = (SleighLanguage) newLanguage; recompile(); } - boolean live = coordinates.isAlive() && coordinates.isPresent(); - if (live) { - this.asyncExecutor = TracePcodeUtils.executorForCoordinates(coordinates); + if (coordinates.isAliveAndPresent()) { + asyncExecutor = TracePcodeUtils.executorForCoordinates(coordinates); } - this.executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace, + executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace, coordinates.getSnap(), coordinates.getThread(), coordinates.getFrame()); - this.executorWithAddress = buildAddressDepsExecutor(coordinates); - if (live) { - doTargetReads(); - } - reevaluate(); // NB. Target reads may not cause database changes + executorWithAddress = buildAddressDepsExecutor(coordinates); } public void setExpression(String expression) { + if (!Objects.equals(this.expression, expression)) { + prevValue = null; + // NB. Allow fall-through so user can re-evaluate via nop edit. + } this.expression = expression; + blank(); recompile(); + if (error != null) { + provider.contextChanged(); + return; + } + if (asyncExecutor != null) { + doTargetReads(); + } + reevaluate(); + provider.contextChanged(); } public String getExpression() { return expression; } + protected void updateType() { + dataType = null; + if (trace == null || typePath == null) { + return; + } + dataType = trace.getDataTypeManager().getDataType(typePath); + if (dataType != null) { + return; + } + DataTypeManagerService dtms = provider.getTool().getService(DataTypeManagerService.class); + if (dtms == null) { + return; + } + dataType = dtms.getBuiltInDataTypesManager().getDataType(typePath); + } + + public void setTypePath(String typePath) { + this.typePath = typePath; + updateType(); + } + + public String getTypePath() { + return typePath; + } + public void setDataType(DataType dataType) { + this.typePath = dataType == null ? null : dataType.getPathName(); this.dataType = dataType; + valueString = parseAsDataType(); + provider.contextChanged(); } public DataType getDataType() { @@ -241,11 +303,34 @@ public class WatchRow { return address; } - public String getRawValueString() { - if (value.length > 20) { - return NumericUtilities.convertBytesToString(value, 0, 20, " ") + "..."; + public AddressRange getRange() { + if (address == null || value == null) { + return null; } - return NumericUtilities.convertBytesToString(value, " "); + if (address.isConstantAddress()) { + return new AddressRangeImpl(address, address); + } + try { + return new AddressRangeImpl(address, value.length); + } + catch (AddressOverflowException e) { + throw new AssertionError(e); + } + } + + public String getRawValueString() { + if (value == null) { + return "??"; + } + if (address == null || !address.getAddressSpace().isMemorySpace()) { + BigInteger asBigInt = + Utils.bytesToBigInteger(value, value.length, language.isBigEndian(), false); + return "0x" + asBigInt.toString(16); + } + if (value.length > 20) { + return "{ " + NumericUtilities.convertBytesToString(value, 0, 20, " ") + " ... }"; + } + return "{ " + NumericUtilities.convertBytesToString(value, " ") + " }"; } public AddressSet getReads() { @@ -260,7 +345,33 @@ public class WatchRow { return valueString; } - public String getError() { + public int getValueLength() { + return value == null ? 0 : value.length; + } + + public String getErrorMessage() { + if (error == null) { + return ""; + } + String message = error.getMessage(); + if (message != null && message.trim().length() != 0) { + return message; + } + return error.getClass().getSimpleName(); + } + + public Throwable getError() { return error; } + + public boolean isKnown() { + return state == TraceMemoryState.KNOWN; + } + + public boolean isChanged() { + if (prevValue == null) { + return false; + } + return !Arrays.equals(value, prevValue); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 44783bc203..303ba304fa 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -890,13 +890,10 @@ public class DebuggerTraceManagerServicePlugin extends Plugin protected static TargetObjectRef translateToFocus(DebuggerCoordinates prev, DebuggerCoordinates resolved) { + if (!resolved.isAliveAndPresent()) { + return null; + } TraceRecorder recorder = resolved.getRecorder(); - if (recorder == null) { - return null; - } - if (!resolved.isPresent()) { - return null; - } if (!Objects.equals(prev.getFrame(), resolved.getFrame())) { TargetStackFrame frame = recorder.getTargetStackFrame(resolved.getThread(), resolved.getFrame()); diff --git a/Ghidra/Debug/Debugger/src/main/resources/defaultTools/Debugger.tool b/Ghidra/Debug/Debugger/src/main/resources/defaultTools/Debugger.tool index 38df329c0d..d24e0a8845 100644 --- a/Ghidra/Debug/Debugger/src/main/resources/defaultTools/Debugger.tool +++ b/Ghidra/Debug/Debugger/src/main/resources/defaultTools/Debugger.tool @@ -1,157 +1,151 @@ - - + + + + - - - - - + - + - + - - - + + + - - - + + - + - - - - - - - - - - - - + + + + + + + + + + + + - + - + - + - + - - - + + + + + - - + + - + - + - + - + - + - + - + - + - + - + - - - - - - + - + @@ -159,9 +153,9 @@ - + - + @@ -199,6 +193,20 @@ + + + + + + + + + + + + + + @@ -306,10 +314,10 @@ - - + + - + @@ -373,6 +381,24 @@ + + + + + + + + + + + + + + + + + + @@ -413,6 +439,19 @@ + + + + + + + + + + + + + @@ -428,13 +467,13 @@ - + - - + + - + @@ -457,6 +496,21 @@ + + + + + + + + + + + + + + + @@ -525,6 +579,22 @@ + + + + + + + + + + + + + + + + @@ -556,6 +626,22 @@ + + + + + + + + + + + + + + + + diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPluginScreenShots.java new file mode 100644 index 0000000000..0f6855f156 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesPluginScreenShots.java @@ -0,0 +1,91 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.watch; + +import org.junit.*; + +import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.pcode.exec.PcodeExecutor; +import ghidra.pcode.exec.trace.TraceSleighUtils; +import ghidra.program.model.data.FloatDataType; +import ghidra.program.model.data.LongDataType; +import ghidra.test.ToyProgramBuilder; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.database.UndoableTransaction; +import help.screenshot.GhidraScreenShotGenerator; + +public class DebuggerWatchesPluginScreenShots extends GhidraScreenShotGenerator { + + DebuggerTraceManagerService traceManager; + DebuggerWatchesPlugin watchesPlugin; + DebuggerWatchesProvider watchesProvider; + ToyDBTraceBuilder tb; + + @Before + public void setUpMin() throws Throwable { + traceManager = addPlugin(tool, DebuggerTraceManagerServicePlugin.class); + watchesPlugin = addPlugin(tool, DebuggerWatchesPlugin.class); + + watchesProvider = waitForComponentProvider(DebuggerWatchesProvider.class); + + tb = new ToyDBTraceBuilder("echo", ToyProgramBuilder._X64); + } + + @After + public void tearDownMine() { + tb.close(); + } + + @Test + public void testCaptureDebuggerWatchesPlugin() throws Throwable { + TraceThread thread; + long snap0, snap1; + try (UndoableTransaction tid = tb.startTransaction()) { + snap0 = tb.trace.getTimeManager().createSnapshot("First").getKey(); + snap1 = tb.trace.getTimeManager().createSnapshot("Second").getKey(); + + thread = tb.getOrAddThread("[1]", snap0); + + PcodeExecutor executor0 = + TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0); + executor0.executeLine("RSP = 0x7ffefff8"); + executor0.executeLine("*:4 (RSP+8) = 0x4030201"); + + PcodeExecutor executor1 = + TraceSleighUtils.buildByteExecutor(tb.trace, snap1, thread, 0); + executor1.executeLine("RSP = 0x7ffefff8"); + executor1.executeLine("*:4 (RSP+8) = 0x1020304"); + executor1.executeLine("*:4 0x7fff0004:8 = 0x4A9A70C8"); + } + + watchesProvider.addWatch("RSP"); + watchesProvider.addWatch("*:8 RSP"); + watchesProvider.addWatch("*:4 (RSP+8)").setDataType(LongDataType.dataType); + watchesProvider.addWatch("*:4 0x7fff0004:8").setDataType(FloatDataType.dataType); + + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + traceManager.activateSnap(snap0); + waitForSwing(); + traceManager.activateSnap(snap1); + waitForSwing(); + + captureIsolatedProvider(watchesProvider, 700, 400); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index 068cb999a9..05845f519e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -991,7 +991,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI // First check nothing captured yet buf.clear(); - trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf); + assertEquals(data.length, + trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); assertArrayEquals(zero, buf.array()); // Verify that the action performs the expected task @@ -1001,7 +1002,8 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI waitForPass(() -> { buf.clear(); - trace.getMemoryManager().getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf); + assertEquals(data.length, trace.getMemoryManager() + .getBytes(recorder.getSnap(), addr(trace, 0x55550000), buf)); // NOTE: The region is only 256 bytes long // TODO: This fails unpredictably, and I'm not sure why. assertArrayEquals(Arrays.copyOf(data, 256), Arrays.copyOf(buf.array(), 256)); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java new file mode 100644 index 0000000000..d3575a6e1a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java @@ -0,0 +1,234 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.watch; + +import static org.junit.Assert.*; + +import java.math.BigInteger; +import java.nio.ByteBuffer; + +import org.junit.*; + +import generic.Unique; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceTest; +import ghidra.app.services.TraceRecorder; +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.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemoryRegisterSpace; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceRegisterUtils; +import ghidra.util.Msg; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUITest { + static { + DebuggerModelServiceTest.addTestModelPathPatterns(); + } + + protected static void assertNoErr(WatchRow row) { + Throwable error = row.getError(); + if (error != null) { + throw new AssertionError(error); + } + } + + protected DebuggerWatchesPlugin watchesPlugin; + protected DebuggerWatchesProvider watchesProvider; + protected DebuggerListingPlugin listingPlugin; + + protected Register r0; + protected TraceThread thread; + + @Before + public void setUpWatchesProviderTest() throws Exception { + watchesPlugin = addPlugin(tool, DebuggerWatchesPlugin.class); + watchesProvider = waitForComponentProvider(DebuggerWatchesProvider.class); + listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); + + createTrace(); + r0 = tb.language.getRegister("r0"); + try (UndoableTransaction tid = tb.startTransaction()) { + thread = tb.getOrAddThread("Thread1", 0); + } + } + + @After + public void tearDownWatchesProviderTest() throws Exception { + for (WatchRow row : watchesProvider.watchTableModel.getModelData()) { + Throwable error = row.getError(); + if (error != null) { + Msg.info(this, "Error on watch row: ", error); + } + } + } + + private void setRegisterValues(TraceThread thread) { + try (UndoableTransaction tid = tb.startTransaction()) { + TraceMemoryRegisterSpace regVals = + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + regVals.setValue(0, new RegisterValue(r0, BigInteger.valueOf(0x00400000))); + } + } + + @Test + public void testAddValsAddWatchThenActivateThread() { + setRegisterValues(thread); + + performAction(watchesProvider.actionAdd); + WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData()); + row.setExpression("r0"); + + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + assertEquals("0x400000", row.getRawValueString()); + assertEquals("", row.getValueString()); // NB. No data type set + assertNoErr(row); + } + + @Test + public void testActivateThreadAddWatchThenAddVals() { + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + performAction(watchesProvider.actionAdd); + WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData()); + row.setExpression("r0"); + + setRegisterValues(thread); + + waitForPass(() -> assertEquals("0x400000", row.getRawValueString())); + assertNoErr(row); + } + + @Test + public void testWatchWithDataType() { + setRegisterValues(thread); + + performAction(watchesProvider.actionAdd); + WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData()); + row.setExpression("r0"); + row.setDataType(LongLongDataType.dataType); + + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + assertEquals("0x400000", row.getRawValueString()); + assertEquals("400000h", row.getValueString()); + assertNoErr(row); + + assertEquals(r0.getAddress(), row.getAddress()); + assertEquals(TraceRegisterUtils.rangeForRegister(r0), row.getRange()); + } + + @Test + public void testConstantWatch() { + setRegisterValues(thread); + + performAction(watchesProvider.actionAdd); + WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData()); + row.setExpression("0xdeadbeef:4"); + row.setDataType(LongDataType.dataType); + + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + assertEquals("{ de ad be ef }", row.getRawValueString()); + assertEquals("DEADBEEFh", row.getValueString()); + assertNoErr(row); + + Address constDeadbeef = tb.trace.getBaseAddressFactory().getConstantAddress(0xdeadbeefL); + assertEquals(constDeadbeef, row.getAddress()); + assertEquals(new AddressRangeImpl(constDeadbeef, constDeadbeef), row.getRange()); + } + + @Test + public void testUniqueWatch() { + setRegisterValues(thread); + + performAction(watchesProvider.actionAdd); + WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData()); + row.setExpression("r0 + 8"); + row.setDataType(LongLongDataType.dataType); + + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + waitForSwing(); + + assertEquals("{ 00 00 00 00 00 40 00 08 }", row.getRawValueString()); + assertEquals("400008h", row.getValueString()); + assertNoErr(row); + + assertNull(row.getAddress()); + assertNull(row.getRange()); + } + + @Test + public void testLiveCausesReads() throws Exception { + createTestModel(); + mb.createTestProcessesAndThreads(); + TestTargetRegisterBankInThread bank = mb.testThread1.addRegisterBank(); + + // Write before we record, and verify trace has not recorded it before setting watch + mb.testProcess1.regs.addRegistersFromLanguage(tb.language, Register::isBaseRegister); + bank.writeRegister("r0", tb.arr(0, 0, 0, 0, 0, 0x40, 0, 0)); + mb.testProcess1.addRegion(".header", mb.rng(0, 0x1000), "r"); // Keep the listing away + mb.testProcess1.addRegion(".text", mb.rng(0x00400000, 0x00401000), "rx"); + mb.testProcess1.memory.writeMemory(mb.addr(0x00400000), tb.arr(1, 2, 3, 4)); + + TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, + new TestDebuggerTargetTraceMapper(mb.testProcess1)); + Trace trace = recorder.getTrace(); + TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); + + traceManager.openTrace(trace); + traceManager.activateThread(thread); + waitForSwing(); + + // Verify no target read has occurred yet + TraceMemoryRegisterSpace regs = + trace.getMemoryManager().getMemoryRegisterSpace(thread, false); + if (regs != null) { + assertEquals(BigInteger.ZERO, regs.getValue(0, r0).getUnsignedValue()); + } + ByteBuffer buf = ByteBuffer.allocate(4); + assertEquals(4, trace.getMemoryManager().getBytes(0, tb.addr(0x00400000), buf)); + assertArrayEquals(tb.arr(0, 0, 0, 0), buf.array()); + + performAction(watchesProvider.actionAdd); + WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData()); + row.setExpression("*:4 r0"); + row.setDataType(LongDataType.dataType); + + waitForPass(() -> { + assertEquals("{ 01 02 03 04 }", row.getRawValueString()); + assertEquals("1020304h", row.getValueString()); + }); + assertNoErr(row); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java index a43427b96c..8bd2897c82 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java @@ -41,6 +41,7 @@ import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.memory.*; import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.thread.TraceThread; +import ghidra.util.MathUtilities; import ghidra.util.UnionAddressSetView; import ghidra.util.database.DBOpenMode; import ghidra.util.exception.DuplicateNameException; @@ -258,7 +259,12 @@ public class DBTraceMemoryManager @Override public int getBytes(long snap, Address start, ByteBuffer buf) { - return delegateReadI(start.getAddressSpace(), m -> m.getBytes(snap, start, buf), 0); + return delegateReadI(start.getAddressSpace(), m -> m.getBytes(snap, start, buf), () -> { + Address max = start.getAddressSpace().getMaxAddress(); + int len = MathUtilities.unsignedMin(buf.remaining(), max.subtract(start)); + buf.position(buf.position() + len); + return len; + }); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceDelegatingManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceDelegatingManager.java index b7b2435723..2ec35e7d77 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceDelegatingManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceDelegatingManager.java @@ -105,6 +105,17 @@ public interface DBTraceDelegatingManager { } } + default int delegateReadI(AddressSpace space, ToIntFunction func, IntSupplier ifNull) { + checkIsInMemory(space); + try (LockHold hold = LockHold.lock(readLock())) { + M m = getForSpace(space, false); + if (m == null) { + return ifNull.getAsInt(); + } + return func.applyAsInt(m); + } + } + default boolean delegateReadB(AddressSpace space, Predicate func, boolean ifNull) { checkIsInMemory(space); try (LockHold hold = LockHold.lock(readLock())) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java index c6c33bf550..ff2171e27c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java @@ -831,8 +831,7 @@ public abstract class AbstractDBTraceMemoryManagerTest assertEquals(expected, collectAsMap(memory.getStates(3, range(0x3000, 0x5000)))); ByteBuffer read = ByteBuffer.allocate(4); - // NOTE: 0 is returned because the space is no longer active.... - assertEquals(0, memory.getBytes(3, addr(0x4000), read)); + assertEquals(4, memory.getBytes(3, addr(0x4000), read)); assertArrayEquals(arr(0, 0, 0, 0), read.array()); } @@ -860,8 +859,7 @@ public abstract class AbstractDBTraceMemoryManagerTest assertEquals(expected, collectAsMap(memory.getStates(3, range(0x3000, 0x5000)))); ByteBuffer read = ByteBuffer.allocate(4); - // NOTE: 0 is returned because the space is no longer active.... - assertEquals(0, memory.getBytes(3, addr(0x4000), read)); + assertEquals(4, memory.getBytes(3, addr(0x4000), read)); assertArrayEquals(arr(0, 0, 0, 0), read.array()); } @@ -889,8 +887,7 @@ public abstract class AbstractDBTraceMemoryManagerTest assertEquals(expected, collectAsMap(memory.getStates(3, range(0x3000, 0x5000)))); ByteBuffer read = ByteBuffer.allocate(4); - // NOTE: 0 is returned because the space is no longer active.... - assertEquals(0, memory.getBytes(3, addr(0x4000), read)); + assertEquals(4, memory.getBytes(3, addr(0x4000), read)); assertArrayEquals(arr(0, 0, 0, 0), read.array()); trace.redo(); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java index 7928d89596..b373a2f137 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java @@ -39,7 +39,7 @@ public class AddressOfPcodeExecutorState @Override public void setVar(AddressSpace space, byte[] offset, int size, boolean truncateAddressableUnit, Address val) { - if (space != unique) { + if (!space.isUniqueSpace()) { return; } long off = Utils.bytesToLong(offset, offset.length, isBigEndian); @@ -50,7 +50,7 @@ public class AddressOfPcodeExecutorState public Address getVar(AddressSpace space, byte[] offset, int size, boolean truncateAddressableUnit) { long off = Utils.bytesToLong(offset, offset.length, isBigEndian); - if (space != unique) { + if (!space.isUniqueSpace()) { return space.getAddress(off); } return unique.get(off); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java index fe116bbb22..8174c4a810 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java @@ -18,6 +18,7 @@ package ghidra.pcode.exec; import java.util.List; import java.util.Map; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.error.LowlevelError; import ghidra.pcode.exec.SleighUseropLibrary.SleighUseropDefinition; import ghidra.pcode.opbehavior.*; @@ -45,6 +46,12 @@ public class PcodeExecutor { this.pointerSize = language.getDefaultSpace().getPointerSize(); } + public void executeLine(String line) { + SleighProgram program = SleighProgramCompiler.compileProgram((SleighLanguage) language, + "line", List.of(line + ";"), SleighUseropLibrary.NIL); + execute(program, SleighUseropLibrary.nil()); + } + public void execute(SleighProgram program, SleighUseropLibrary library) { execute(program.code, program.useropNames, library); }