GP-475: Completed the Debugger "Watches" plugin

This commit is contained in:
Dan 2020-12-10 11:35:28 -05:00
parent 29fe88b811
commit cd32ab60be
21 changed files with 1225 additions and 157 deletions

View file

@ -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|

View file

@ -128,6 +128,10 @@
target="help/topics/DebuggerStaticMappingPlugin/DebuggerStaticMappingPlugin.html" />
</tocdef>
<tocdef id="DebuggerWatchesPlugin" text="Watches"
sortgroup="n"
target="help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html" />
<tocdef id="DebuggerBots" text="Bots: Workflow Automation"
sortgroup="o"
target="help/topics/DebuggerBots/DebuggerBots.html" />

View file

@ -0,0 +1,135 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Debugger: Watches</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
</HEAD>
<BODY lang="EN-US">
<H1><A name="plugin"></A>Debugger: Watches</H1>
<TABLE width="100%">
<TBODY>
<TR>
<TD align="center" width="100%"><IMG alt="" border="1" src=
"images/DebuggerWatchesPlugin.png"></TD>
</TR>
</TBODY>
</TABLE>
<P>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.</P>
<H2>Examples</H2>
<P>For those less familar with Sleigh, here are some example expressions:</P>
<UL>
<LI><CODE>*:4 (RSP+8)</CODE>: Display 4 bytes of [ram] starting 8 bytes after the offset
given by register RSP.</LI>
<LI><CODE>*:4 0x7fff0004:8</CODE>: Display 4 bytes starting at ram:7fff0004. The extraneous,
but required, size specifier on constant derefs is a known issue. Just use the target's
pointer size in bytes.</LI>
<LI><CODE>*:8 RSP</CODE>: Display 8 bytes of [ram] starting at the offset given by register
RSP.</LI>
<LI><CODE>RSP</CODE>: Display the value of register RSP.</LI>
</UL>
<H2>Table Columns</H2>
<P>The table displays and allows modification of each watch. It has the following columns:</P>
<UL>
<LI>Expression - the user-modifiable Sleigh expression defining this watch.</LI>
<LI>Address - when evaluation succeeds, the address of the watch's value. This field is
really only meaningful when the outermost operator of the expression is a memory dereference.
Double-clicking a row will navigate the primary dynamic listing to this address, if
possible.</LI>
<LI>Value - the raw bytes of the watched buffer. If the expression is a register, then this
is its hexadecimal value. If the value has changed since the last navigation event, this cell
is rendered in <FONT color="red">red</FONT>.</LI>
<LI>Type - the user-modifiable type of the watch. Note the type is not marked up in the
trace. Clicking the Apply Data Type action will apply it to the current trace, if
possible.</LI>
<LI>Representation - the value of the watch as interpreted the selected data type.</LI>
<LI>Error - if an error occurs during compilation or evaluation of the expression, that error
is rendered here. Double-clicking the row will display the stack trace. Note that errors
during evaluation can be a very common occurrence, especially as contexts change, and should
not cause alarm. An expression devised for one context may not have meaning under another,
even if it evaluates without error. E.g., <CODE>RIP</CODE> will disappear when switching to a
32-bit trace, or <CODE>*:8 (*:8 (RSP+8))</CODE> may cause an invalid dereference if an x86
<CODE>PUSH</CODE> causes <CODE>*:8 (RSP+8)</CODE> to become 0.</LI>
</UL>
<H2>Actions</H2>
<P>The watches window provides the following actions:</P>
<H3><A name="apply_data_type"></A>Apply Data to Listing</H3>
<P>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.</P>
<H3><A name="select_addresses"></A>Select Range</H3>
<P>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 <CODE>*:8 RSP</CODE> would cause 8 bytes of memory, starting at the offset given by
RSP, to be selected in the dynamic listing.</P>
<H3><A name="select_reads"></A>Select Reads</H3>
<P>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 <CODE>*:8 RSP</CODE> 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 <CODE>*:4 (*:8 RSP)</CODE> would cause two ranges to be selected: 8 bytes starting
at RSP and 4 bytes starting at the offset given by <CODE>*:8 RSP</CODE>.</P>
<H3><A name="add"></A>Add</H3>
<P>This action is always available. It adds a blank watch to the table.</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>
<H2><A name="colors"></A>Tool Options: Colors</H2>
<P>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.</P>
</BODY>
</HTML>

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -356,4 +356,8 @@ public class DebuggerCoordinates {
public boolean isPresent() {
return recorder.getSnap() == snap;
}
public boolean isAliveAndPresent() {
return isAlive() && isPresent();
}
}

View file

@ -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() {

View file

@ -104,10 +104,10 @@ public interface DebuggerListingAutoReadMemoryAction extends AutoReadMemoryActio
@Override
public CompletableFuture<Void> 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<Void> 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();

View file

@ -301,7 +301,6 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
class RegisterValueCellRenderer extends HexBigIntegerTableCellRenderer {
@Override
public final Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);

View file

@ -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);
}
}

View file

@ -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<WatchTableColumns, WatchRow> {
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<WatchRow, ?> getter;
private final BiConsumer<WatchRow, ?> setter;
private final BiConsumer<WatchRow, Object> setter;
private final Class<?> cls;
@SuppressWarnings("unchecked")
<T> WatchTableColumns(String header, Class<T> cls, Function<WatchRow, T> getter,
BiConsumer<WatchRow, T> setter) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.setter = setter;
this.setter = (BiConsumer<WatchRow, Object>) setter;
}
<T> WatchTableColumns(String header, Class<T> cls, Function<WatchRow, T> 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<String> {
@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<Void> 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<WatchRow> 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<String> 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<WatchRow> 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<WatchRow> 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);
}
}

View file

@ -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<Pair<byte[], TraceMemoryState>> executorWithState;
private ReadDepsPcodeExecutor executorWithAddress;
private AsyncPcodeExecutor<byte[]> 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<byte[], TraceMemoryState> valueWithState = compiled.evaluate(executorWithState);
Pair<byte[], Address> 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);
}
}

View file

@ -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());

View file

@ -1,157 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<TOOL_CONFIG CONFIG_NAME="NO_LONGER_USED">
<SUPPORTED_DATA_TYPE CLASS_NAME="ghidra.program.model.listing.DataTypeArchive" />
<SUPPORTED_DATA_TYPE CLASS_NAME="ghidra.trace.model.Trace" />
<SUPPORTED_DATA_TYPE CLASS_NAME="ghidra.program.model.listing.Program" />
<SUPPORTED_DATA_TYPE CLASS_NAME="ghidra.trace.model.Trace" />
<SUPPORTED_DATA_TYPE CLASS_NAME="ghidra.program.model.listing.DataTypeArchive" />
<ICON LOCATION="debugger32.png" />
<TOOL TOOL_NAME="Debugger" INSTANCE_NAME="">
<OPTIONS />
<PACKAGE NAME="Debugger">
</PACKAGE>
<PACKAGE NAME="Ghidra Core">
<INCLUDE CLASS="ghidra.app.plugin.core.editor.TextEditorManagerPlugin" />
<INCLUDE CLASS="ghidra.app.plugin.core.interpreter.InterpreterPanelPlugin" />
</PACKAGE>
<PACKAGE NAME="Debugger">
<INCLUDE CLASS="ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin" />
<INCLUDE CLASS="ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin" />
</PACKAGE>
<ROOT_NODE X_POS="0" Y_POS="32" WIDTH="1920" HEIGHT="1017" EX_STATE="0" FOCUSED_OWNER="DebuggerTargetsPlugin" FOCUSED_NAME="Debugger Targets" FOCUSED_TITLE="Debugger Targets">
<ROOT_NODE X_POS="60" Y_POS="466" WIDTH="1920" HEIGHT="1017" EX_STATE="0" FOCUSED_OWNER="DebuggerTargetsPlugin" FOCUSED_NAME="Debugger Targets" FOCUSED_TITLE="Debugger Targets">
<SPLIT_NODE WIDTH="1918" HEIGHT="910" DIVIDER_LOCATION="767" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="100" HEIGHT="100" DIVIDER_LOCATION="0" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1918" HEIGHT="695" DIVIDER_LOCATION="156" ORIENTATION="HORIZONTAL">
<SPLIT_NODE WIDTH="299" HEIGHT="695" DIVIDER_LOCATION="692" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="299" HEIGHT="478" DIVIDER_LOCATION="427" ORIENTATION="VERTICAL">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Debugger Targets" OWNER="DebuggerTargetsPlugin" TITLE="Debugger Targets" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261614922873274" />
<COMPONENT_INFO NAME="Debugger Targets" OWNER="DebuggerTargetsPlugin" TITLE="Debugger Targets" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434796977987111" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Objects" OWNER="DebuggerObjectsPlugin" TITLE="Objects" ACTIVE="true" GROUP="Debugger.Core.Objects" INSTANCE_ID="3367261614922873273" />
<COMPONENT_INFO NAME="Objects" OWNER="DebuggerObjectsPlugin" TITLE="Objects" ACTIVE="true" GROUP="Debugger.Core.Objects" INSTANCE_ID="3373434796977987110" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="DataTypes Provider" OWNER="DataTypeManagerPlugin" TITLE="Data Type Manager" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261614541191594" />
<COMPONENT_INFO NAME="Program Tree" OWNER="ProgramTreePlugin" TITLE="Program Trees" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261611017976230" />
<COMPONENT_INFO NAME="Symbol Tree" OWNER="SymbolTreePlugin" TITLE="Symbol Tree" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261611017976234" />
<COMPONENT_INFO NAME="DataTypes Provider" OWNER="DataTypeManagerPlugin" TITLE="Data Type Manager" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434792393612850" />
<COMPONENT_INFO NAME="Program Tree" OWNER="ProgramTreePlugin" TITLE="Program Trees" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434785093426726" />
<COMPONENT_INFO NAME="Symbol Tree" OWNER="SymbolTreePlugin" TITLE="Symbol Tree" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434785093426730" />
</COMPONENT_NODE>
</SPLIT_NODE>
<SPLIT_NODE WIDTH="1293" HEIGHT="590" DIVIDER_LOCATION="785" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1386" HEIGHT="638" DIVIDER_LOCATION="705" ORIENTATION="VERTICAL">
<SPLIT_NODE WIDTH="1615" HEIGHT="695" DIVIDER_LOCATION="679" ORIENTATION="HORIZONTAL">
<SPLIT_NODE WIDTH="1094" HEIGHT="695" DIVIDER_LOCATION="506" ORIENTATION="VERTICAL">
<COMPONENT_NODE TOP_INFO="1">
<COMPONENT_INFO NAME="Listing" OWNER="DebuggerListingPlugin" TITLE="Dynamic" ACTIVE="true" GROUP="Core" INSTANCE_ID="3367261602111371697" />
<COMPONENT_INFO NAME="Listing" OWNER="DebuggerListingPlugin" TITLE="[Dynamic]" ACTIVE="true" GROUP="Core" INSTANCE_ID="3367261618683066788" />
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Listing" OWNER="DebuggerListingPlugin" TITLE="Dynamic" ACTIVE="true" GROUP="Core" INSTANCE_ID="3373434785093426731" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Listing" OWNER="CodeBrowserPlugin" TITLE="Listing: " ACTIVE="true" GROUP="Core" INSTANCE_ID="3367261602111371709" />
<COMPONENT_INFO NAME="Listing" OWNER="CodeBrowserPlugin" TITLE="Listing: " ACTIVE="true" GROUP="Core" INSTANCE_ID="3367240603901382758" />
</COMPONENT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="10">
<COMPONENT_INFO NAME="Decompiler" OWNER="DecompilePlugin" TITLE="Decompile" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261611017976224" />
<COMPONENT_INFO NAME="Bytes" OWNER="ByteViewerPlugin" TITLE="Bytes: No Program" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261602111371706" />
<COMPONENT_INFO NAME="Data Window" OWNER="DataWindowPlugin" TITLE="Defined Data" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261618683066785" />
<COMPONENT_INFO NAME="Defined Strings" OWNER="ViewStringsPlugin" TITLE="Defined Strings" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261614922873276" />
<COMPONENT_INFO NAME="Equates Table" OWNER="EquateTablePlugin" TITLE="Equates Table" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261611017976226" />
<COMPONENT_INFO NAME="External Programs" OWNER="ReferencesPlugin" TITLE="External Programs" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261611017976231" />
<COMPONENT_INFO NAME="Functions Window" OWNER="FunctionWindowPlugin" TITLE="Functions" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261614922873256" />
<COMPONENT_INFO NAME="Relocation Table" OWNER="RelocationTablePlugin" TITLE="Relocation Table" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261614922873275" />
<COMPONENT_INFO NAME="Modules" OWNER="DebuggerModulesPlugin" TITLE="Modules" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261614922873271" />
<COMPONENT_INFO NAME="Registers" OWNER="DebuggerRegistersPlugin" TITLE="Registers" ACTIVE="true" GROUP="Debugger.Core" INSTANCE_ID="3367261618683066787" />
<COMPONENT_INFO NAME="Breakpoints" OWNER="DebuggerBreakpointsPlugin" TITLE="Breakpoints" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261614922873279" />
<COMPONENT_NODE TOP_INFO="9">
<COMPONENT_INFO NAME="Decompiler" OWNER="DecompilePlugin" TITLE="Decompiler" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367240603901382761" />
<COMPONENT_INFO NAME="Bytes" OWNER="ByteViewerPlugin" TITLE="Bytes: No Program" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367240603901382755" />
<COMPONENT_INFO NAME="Data Window" OWNER="DataWindowPlugin" TITLE="Defined Data" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434796977987114" />
<COMPONENT_INFO NAME="Defined Strings" OWNER="ViewStringsPlugin" TITLE="Defined Strings" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434796977987104" />
<COMPONENT_INFO NAME="Equates Table" OWNER="EquateTablePlugin" TITLE="Equates Table" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434785093426722" />
<COMPONENT_INFO NAME="External Programs" OWNER="ReferencesPlugin" TITLE="External Programs" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434785093426727" />
<COMPONENT_INFO NAME="Functions Window" OWNER="FunctionWindowPlugin" TITLE="Functions" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434793108741680" />
<COMPONENT_INFO NAME="Relocation Table" OWNER="RelocationTablePlugin" TITLE="Relocation Table" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434793108741695" />
<COMPONENT_INFO NAME="Modules" OWNER="DebuggerModulesPlugin" TITLE="Modules" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434796977987108" />
<COMPONENT_INFO NAME="Registers" OWNER="DebuggerRegistersPlugin" TITLE="Registers" ACTIVE="true" GROUP="Debugger.Core" INSTANCE_ID="3373434796977987117" />
<COMPONENT_INFO NAME="Breakpoints" OWNER="DebuggerBreakpointsPlugin" TITLE="Breakpoints" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434796977987116" />
</COMPONENT_NODE>
</SPLIT_NODE>
<SPLIT_NODE WIDTH="1386" HEIGHT="189" DIVIDER_LOCATION="495" ORIENTATION="HORIZONTAL">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Data Type Preview" OWNER="DataTypePreviewPlugin" TITLE="Data Type Preview" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261614922873255" />
<COMPONENT_INFO NAME="Data Type Preview" OWNER="DataTypePreviewPlugin" TITLE="Data Type Preview" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434793108741679" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Virtual Disassembler - Current Instruction" OWNER="DisassembledViewPlugin" TITLE="Disassembled View" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261611017976225" />
<COMPONENT_INFO NAME="Virtual Disassembler - Current Instruction" OWNER="DisassembledViewPlugin" TITLE="Disassembled View" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434785093426721" />
</COMPONENT_NODE>
</SPLIT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Bookmarks" OWNER="BookmarkPlugin" TITLE="Bookmarks" ACTIVE="false" GROUP="Core.Bookmarks" INSTANCE_ID="3367261602111371705" />
<COMPONENT_INFO NAME="Bookmarks" OWNER="BookmarkPlugin" TITLE="Bookmarks" ACTIVE="false" GROUP="Core.Bookmarks" INSTANCE_ID="3373434773550702137" />
</COMPONENT_NODE>
</SPLIT_NODE>
</SPLIT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Function Call Trees" OWNER="CallTreePlugin" TITLE="Function Call Trees" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261602111371707" />
<COMPONENT_INFO NAME="Function Call Trees" OWNER="CallTreePlugin" TITLE="Function Call Trees" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434773550702139" />
</COMPONENT_NODE>
</SPLIT_NODE>
<SPLIT_NODE WIDTH="1918" HEIGHT="211" DIVIDER_LOCATION="348" ORIENTATION="HORIZONTAL">
<COMPONENT_NODE TOP_INFO="1">
<COMPONENT_INFO NAME="Regions" OWNER="DebuggerRegionsPlugin" TITLE="Regions" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261614922873270" />
<COMPONENT_INFO NAME="Stack" OWNER="DebuggerStackPlugin" TITLE="Stack" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261611017976235" />
<COMPONENT_INFO NAME="Console" OWNER="ConsolePlugin" TITLE="Console" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261602111371710" />
<COMPONENT_INFO NAME="Regions" OWNER="DebuggerRegionsPlugin" TITLE="Regions" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434796977987107" />
<COMPONENT_INFO NAME="Stack" OWNER="DebuggerStackPlugin" TITLE="Stack" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434793108741683" />
<COMPONENT_INFO NAME="Console" OWNER="ConsolePlugin" TITLE="Console" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434773550702142" />
<COMPONENT_INFO NAME="Interpreter" OWNER="InterpreterPanelPlugin" TITLE="Interpreter" ACTIVE="false" GROUP="Default" INSTANCE_ID="3372862709093332258" />
<COMPONENT_INFO NAME="Watches" OWNER="DebuggerWatchesPlugin" TITLE="Watches" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434796977987112" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Threads" OWNER="DebuggerThreadsPlugin" TITLE="Threads" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261614922873259" />
<COMPONENT_INFO NAME="Time" OWNER="DebuggerTimePlugin" TITLE="Time" ACTIVE="true" GROUP="Default" INSTANCE_ID="3367261614922873260" />
<COMPONENT_INFO NAME="Threads" OWNER="DebuggerThreadsPlugin" TITLE="Threads" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434793108741693" />
<COMPONENT_INFO NAME="Time" OWNER="DebuggerTimePlugin" TITLE="Time" ACTIVE="true" GROUP="Default" INSTANCE_ID="3373434793108741694" />
</COMPONENT_NODE>
</SPLIT_NODE>
</SPLIT_NODE>
<WINDOW_NODE X_POS="426" Y_POS="178" WIDTH="1033" HEIGHT="689">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Script Manager" OWNER="GhidraScriptMgrPlugin" TITLE="Script Manager" ACTIVE="false" GROUP="Script Group" INSTANCE_ID="3367261611017976232" />
<COMPONENT_INFO NAME="Script Manager" OWNER="GhidraScriptMgrPlugin" TITLE="Script Manager" ACTIVE="false" GROUP="Script Group" INSTANCE_ID="3373434785093426728" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="423" Y_POS="144" WIDTH="927" HEIGHT="370">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Memory Map" OWNER="MemoryMapPlugin" TITLE="Memory Map" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261611017976229" />
<COMPONENT_INFO NAME="Memory Map" OWNER="MemoryMapPlugin" TITLE="Memory Map" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434785093426725" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="383" Y_POS="7" WIDTH="1020" HEIGHT="1038">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Function Graph" OWNER="FunctionGraphPlugin" TITLE="Function Graph" ACTIVE="false" GROUP="Function Graph" INSTANCE_ID="3367261618683066786" />
<COMPONENT_INFO NAME="Function Graph" OWNER="FunctionGraphPlugin" TITLE="Function Graph" ACTIVE="false" GROUP="Function Graph" INSTANCE_ID="3373434796977987115" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="550" Y_POS="206" WIDTH="655" HEIGHT="509">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Register Manager" OWNER="RegisterPlugin" TITLE="Register Manager" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261614922873257" />
<COMPONENT_INFO NAME="Register Manager" OWNER="RegisterPlugin" TITLE="Register Manager" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434793108741681" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="287" Y_POS="186" WIDTH="1424" HEIGHT="666">
<SPLIT_NODE WIDTH="1408" HEIGHT="559" DIVIDER_LOCATION="573" ORIENTATION="HORIZONTAL">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Symbol Table" OWNER="SymbolTablePlugin" TITLE="Symbol Table" ACTIVE="false" GROUP="symbolTable" INSTANCE_ID="3367261614922873277" />
<COMPONENT_INFO NAME="Symbol Table" OWNER="SymbolTablePlugin" TITLE="Symbol Table" ACTIVE="false" GROUP="symbolTable" INSTANCE_ID="3373434796977987105" />
</COMPONENT_NODE>
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Symbol References" OWNER="SymbolTablePlugin" TITLE="Symbol References" ACTIVE="false" GROUP="symbolTable" INSTANCE_ID="3367261614922873278" />
<COMPONENT_INFO NAME="Symbol References" OWNER="SymbolTablePlugin" TITLE="Symbol References" ACTIVE="false" GROUP="symbolTable" INSTANCE_ID="3373434796977987106" />
</COMPONENT_NODE>
</SPLIT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="-1" Y_POS="-1" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Checksum Generator" OWNER="ComputeChecksumsPlugin" TITLE="Checksum Generator" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261602111371708" />
<COMPONENT_INFO NAME="Checksum Generator" OWNER="ComputeChecksumsPlugin" TITLE="Checksum Generator" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434773550702140" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="-1" Y_POS="-1" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Function Tags" OWNER="FunctionTagPlugin" TITLE="Function Tags" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261611017976228" />
<COMPONENT_INFO NAME="Function Tags" OWNER="FunctionTagPlugin" TITLE="Function Tags" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434785093426724" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="-1" Y_POS="-1" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Comment Window" OWNER="CommentWindowPlugin" TITLE="Comments" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261618683066784" />
<COMPONENT_INFO NAME="Comment Window" OWNER="CommentWindowPlugin" TITLE="Comments" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434796977987113" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="-1" Y_POS="-1" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Python" OWNER="InterpreterPanelPlugin" TITLE="Python" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261614922873258" />
<COMPONENT_INFO NAME="Python" OWNER="InterpreterPanelPlugin" TITLE="Python" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434793108741682" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="0" Y_POS="0" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Function Call Graph" OWNER="FunctionCallGraphPlugin" TITLE="Function Call Graph" ACTIVE="false" GROUP="Function Call Graph" INSTANCE_ID="3367261611017976236" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="658" Y_POS="1489" WIDTH="470" HEIGHT="540">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Static Mappings" OWNER="DebuggerStaticMappingPlugin" TITLE="Static Mappings" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261614922873272" />
<COMPONENT_INFO NAME="Function Call Graph" OWNER="FunctionCallGraphPlugin" TITLE="Function Call Graph" ACTIVE="false" GROUP="Function Call Graph" INSTANCE_ID="3373434785093426732" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="0" Y_POS="0" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="BundleManager" OWNER="GhidraScriptMgrPlugin" TITLE="Bundle Manager" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367261611017976233" />
<COMPONENT_INFO NAME="BundleManager" OWNER="GhidraScriptMgrPlugin" TITLE="Bundle Manager" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434785093426729" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="2105" Y_POS="966" WIDTH="1122" HEIGHT="546">
@ -159,9 +153,9 @@
<COMPONENT_INFO NAME="Objects" OWNER="DebuggerTimelinePlugin" TITLE="Objects" ACTIVE="false" GROUP="Default" INSTANCE_ID="3366353018521038486" />
</COMPONENT_NODE>
</WINDOW_NODE>
<WINDOW_NODE X_POS="592" Y_POS="198" WIDTH="739" HEIGHT="585">
<WINDOW_NODE X_POS="0" Y_POS="0" WIDTH="0" HEIGHT="0">
<COMPONENT_NODE TOP_INFO="0">
<COMPONENT_INFO NAME="Interpreter: GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-30.amzn2.0.3" OWNER="InterpreterPanelPlugin" TITLE="Interpreter: GNU gdb (GDB) Red Hat Enterprise Linux 8.0.1-30.amzn2.0.3" ACTIVE="false" GROUP="Default" INSTANCE_ID="3367240649541701752" />
<COMPONENT_INFO NAME="Static Mappings" OWNER="DebuggerStaticMappingPlugin" TITLE="Static Mappings" ACTIVE="false" GROUP="Default" INSTANCE_ID="3373434796977987109" />
</COMPONENT_NODE>
</WINDOW_NODE>
</ROOT_NODE>
@ -199,6 +193,20 @@
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="docking.widgets.table.DefaultEnumeratedColumnTableModel:Base Address:Max Address:Module Name:Lifespan:Length:">
<XML NAME="COLUMN_DATA">
<Table_State>
<COLUMN NAME="Base Address" WIDTH="102" VISIBLE="true" />
<COLUMN NAME="Max Address" WIDTH="102" VISIBLE="true" />
<COLUMN NAME="Module Name" WIDTH="102" VISIBLE="true" />
<COLUMN NAME="Lifespan" WIDTH="102" VISIBLE="true" />
<COLUMN NAME="Length" WIDTH="101" VISIBLE="true" />
<TABLE_SORT_STATE>
<COLUMN_SORT_STATE COLUMN_MODEL_INDEX="0" SORT_DIRECTION="ascending" SORT_ORDER="1" />
</TABLE_SORT_STATE>
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="docking.widgets.table.DefaultEnumeratedColumnTableModel:Name:Created:Destroyed:State:Comment:">
<XML NAME="COLUMN_DATA">
<Table_State>
@ -306,10 +314,10 @@
<PREFERENCE_STATE NAME="docking.ErrLogDialog$ErrEntryTableModel:#:Message:Details:">
<XML NAME="COLUMN_DATA">
<Table_State>
<COLUMN NAME="docking.ErrLogDialog$ErrEntryTableModel$IdColumn.#" WIDTH="362" VISIBLE="true" />
<COLUMN NAME="docking.ErrLogDialog$ErrEntryTableModel$MessageColumn.Message" WIDTH="363" VISIBLE="true" />
<COLUMN NAME="docking.ErrLogDialog$ErrEntryTableModel$IdColumn.#" WIDTH="15" VISIBLE="true" />
<COLUMN NAME="docking.ErrLogDialog$ErrEntryTableModel$MessageColumn.Message" WIDTH="15" VISIBLE="true" />
<COLUMN NAME="docking.ErrLogDialog$ErrEntryTableModel$DetailsColumn.Details" WIDTH="500" VISIBLE="false" />
<COLUMN NAME="docking.ErrLogDialog$ErrEntryTableModel$TimestampColumn.Time" WIDTH="362" VISIBLE="true" />
<COLUMN NAME="docking.ErrLogDialog$ErrEntryTableModel$TimestampColumn.Time" WIDTH="15" VISIBLE="true" />
<TABLE_SORT_STATE>
<COLUMN_SORT_STATE COLUMN_MODEL_INDEX="0" SORT_DIRECTION="ascending" SORT_ORDER="1" />
</TABLE_SORT_STATE>
@ -373,6 +381,24 @@
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="docking.widgets.table.DefaultEnumeratedColumnTableModel:Remove:Module:Section:Dynamic Base:Program:Block:Static Base:Size:Choose:">
<XML NAME="COLUMN_DATA">
<Table_State>
<COLUMN NAME="Remove" WIDTH="32" VISIBLE="true" />
<COLUMN NAME="Module" WIDTH="75" VISIBLE="true" />
<COLUMN NAME="Section" WIDTH="75" VISIBLE="true" />
<COLUMN NAME="Dynamic Base" WIDTH="75" VISIBLE="true" />
<COLUMN NAME="Program" WIDTH="75" VISIBLE="true" />
<COLUMN NAME="Block" WIDTH="75" VISIBLE="true" />
<COLUMN NAME="Static Base" WIDTH="75" VISIBLE="true" />
<COLUMN NAME="Size" WIDTH="74" VISIBLE="true" />
<COLUMN NAME="Choose" WIDTH="32" VISIBLE="true" />
<TABLE_SORT_STATE>
<COLUMN_SORT_STATE COLUMN_MODEL_INDEX="0" SORT_DIRECTION="ascending" SORT_ORDER="1" />
</TABLE_SORT_STATE>
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="ghidra.app.plugin.core.bookmark.BookmarkTableModel:Type:Category:Description:Location:Label:Preview:">
<XML NAME="COLUMN_DATA">
<Table_State>
@ -413,6 +439,19 @@
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="ghidra.app.plugin.core.debug.gui.listing.DebuggerModuleImportDialog$FileTableModel:Remove:Ignore:Path:Import:">
<XML NAME="COLUMN_DATA">
<Table_State>
<COLUMN NAME="Remove" WIDTH="32" VISIBLE="true" />
<COLUMN NAME="Ignore" WIDTH="26" VISIBLE="true" />
<COLUMN NAME="Path" WIDTH="368" VISIBLE="true" />
<COLUMN NAME="Import" WIDTH="32" VISIBLE="true" />
<TABLE_SORT_STATE>
<COLUMN_SORT_STATE COLUMN_MODEL_INDEX="0" SORT_DIRECTION="ascending" SORT_ORDER="1" />
</TABLE_SORT_STATE>
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="ghidra.app.plugin.core.register.RegisterValuesPanel$RegisterValuesTableModel:Start Address:End Address:Value:">
<XML NAME="COLUMN_DATA">
<Table_State>
@ -428,13 +467,13 @@
<PREFERENCE_STATE NAME="docking.widgets.table.DefaultEnumeratedColumnTableModel:Name:Lifespan:Start:End:Length:Read:Write:Execute:Volatile:">
<XML NAME="COLUMN_DATA">
<Table_State>
<COLUMN NAME="Name" WIDTH="127" VISIBLE="true" />
<COLUMN NAME="Name" WIDTH="126" VISIBLE="true" />
<COLUMN NAME="Lifespan" WIDTH="67" VISIBLE="true" />
<COLUMN NAME="Start" WIDTH="69" VISIBLE="true" />
<COLUMN NAME="End" WIDTH="92" VISIBLE="true" />
<COLUMN NAME="Start" WIDTH="70" VISIBLE="true" />
<COLUMN NAME="End" WIDTH="91" VISIBLE="true" />
<COLUMN NAME="Length" WIDTH="65" VISIBLE="true" />
<COLUMN NAME="Read" WIDTH="60" VISIBLE="true" />
<COLUMN NAME="Write" WIDTH="65" VISIBLE="true" />
<COLUMN NAME="Write" WIDTH="66" VISIBLE="true" />
<COLUMN NAME="Execute" WIDTH="58" VISIBLE="true" />
<COLUMN NAME="Volatile" WIDTH="57" VISIBLE="true" />
<TABLE_SORT_STATE>
@ -457,6 +496,21 @@
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="ghidra.app.plugin.core.debug.gui.watch.DebuggerWatchesProvider$WatchTableModel:Expression:Address:Value:Type:Repr:Error:">
<XML NAME="COLUMN_DATA">
<Table_State>
<COLUMN NAME="Expression" WIDTH="110" VISIBLE="true" />
<COLUMN NAME="Address" WIDTH="110" VISIBLE="true" />
<COLUMN NAME="Value" WIDTH="110" VISIBLE="true" />
<COLUMN NAME="Type" WIDTH="110" VISIBLE="true" />
<COLUMN NAME="Repr" WIDTH="110" VISIBLE="true" />
<COLUMN NAME="Error" WIDTH="110" VISIBLE="true" />
<TABLE_SORT_STATE>
<COLUMN_SORT_STATE COLUMN_MODEL_INDEX="0" SORT_DIRECTION="ascending" SORT_ORDER="1" />
</TABLE_SORT_STATE>
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="SymbolTablePlugin">
<STATE NAME="SELECTION_NAVIGATION_SELECTED_STATE" TYPE="boolean" VALUE="true" />
</PREFERENCE_STATE>
@ -525,6 +579,22 @@
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="docking.widgets.table.DefaultEnumeratedColumnTableModel:Remove:Module:Dynamic Base:Program:Static Base:Size:Choose:">
<XML NAME="COLUMN_DATA">
<Table_State>
<COLUMN NAME="Remove" WIDTH="32" VISIBLE="true" />
<COLUMN NAME="Module" WIDTH="105" VISIBLE="true" />
<COLUMN NAME="Dynamic Base" WIDTH="105" VISIBLE="true" />
<COLUMN NAME="Program" WIDTH="105" VISIBLE="true" />
<COLUMN NAME="Static Base" WIDTH="105" VISIBLE="true" />
<COLUMN NAME="Size" WIDTH="104" VISIBLE="true" />
<COLUMN NAME="Choose" WIDTH="32" VISIBLE="true" />
<TABLE_SORT_STATE>
<COLUMN_SORT_STATE COLUMN_MODEL_INDEX="0" SORT_DIRECTION="ascending" SORT_ORDER="1" />
</TABLE_SORT_STATE>
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="KNOWN_EXTENSIONS">
<ARRAY NAME="KNOWN_EXTENSIONS" TYPE="string" />
</PREFERENCE_STATE>
@ -556,6 +626,22 @@
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="ghidra.app.plugin.core.debug.gui.register.DebuggerRegistersProvider$RegistersTableModel:Fav:#:Name:Value:Type:Repr:">
<XML NAME="COLUMN_DATA">
<Table_State>
<COLUMN NAME="Fav" WIDTH="47" VISIBLE="true" />
<COLUMN NAME="#" WIDTH="46" VISIBLE="true" />
<COLUMN NAME="Name" WIDTH="73" VISIBLE="true" />
<COLUMN NAME="Value" WIDTH="131" VISIBLE="true" />
<COLUMN NAME="Type" WIDTH="83" VISIBLE="true" />
<COLUMN NAME="Repr" WIDTH="131" VISIBLE="true" />
<TABLE_SORT_STATE>
<COLUMN_SORT_STATE COLUMN_MODEL_INDEX="0" SORT_DIRECTION="descending" SORT_ORDER="1" />
<COLUMN_SORT_STATE COLUMN_MODEL_INDEX="1" SORT_DIRECTION="ascending" SORT_ORDER="2" />
</TABLE_SORT_STATE>
</Table_State>
</XML>
</PREFERENCE_STATE>
<PREFERENCE_STATE NAME="ghidra.app.plugin.core.strings.ViewStringsTableModel:Location:String:">
<XML NAME="COLUMN_DATA">
<Table_State>

View file

@ -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<byte[]> executor0 =
TraceSleighUtils.buildByteExecutor(tb.trace, snap0, thread, 0);
executor0.executeLine("RSP = 0x7ffefff8");
executor0.executeLine("*:4 (RSP+8) = 0x4030201");
PcodeExecutor<byte[]> 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);
}
}

View file

@ -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));

View file

@ -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);
}
}

View file

@ -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

View file

@ -105,6 +105,17 @@ public interface DBTraceDelegatingManager<M> {
}
}
default int delegateReadI(AddressSpace space, ToIntFunction<M> 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<M> func, boolean ifNull) {
checkIsInMemory(space);
try (LockHold hold = LockHold.lock(readLock())) {

View file

@ -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();

View file

@ -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);

View file

@ -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<T> {
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<T> library) {
execute(program.code, program.useropNames, library);
}