mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-475: Completed the Debugger "Watches" plugin
This commit is contained in:
parent
29fe88b811
commit
cd32ab60be
21 changed files with 1225 additions and 157 deletions
|
@ -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|
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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 |
|
@ -356,4 +356,8 @@ public class DebuggerCoordinates {
|
|||
public boolean isPresent() {
|
||||
return recorder.getSnap() == snap;
|
||||
}
|
||||
|
||||
public boolean isAliveAndPresent() {
|
||||
return isAlive() && isPresent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -301,7 +301,6 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
class RegisterValueCellRenderer extends HexBigIntegerTableCellRenderer {
|
||||
|
||||
@Override
|
||||
public final Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue