GP-475: Completed the Debugger "Watches" plugin

This commit is contained in:
Dan
2020-12-10 11:35:28 -05:00
parent 29fe88b811
commit cd32ab60be
21 changed files with 1225 additions and 157 deletions
@@ -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,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);
}