Merge remote-tracking branch

'origin/GP-1264_Dan_writeViaWatches--SQUASHED' into patch (Closes #2866)
This commit is contained in:
Ryan Kurtz 2021-09-07 11:10:10 -04:00
commit aa38095f77
11 changed files with 380 additions and 54 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -93,8 +93,8 @@
<P>This toggle is a write protector for live registers. To modify live register values, this
toggle must be enabled, and the trace must be live and "at the present." Note that editing
recorded historical values is not permitted via the UI, regardless of this toggle, but can be
accomplished via scripts.</P>
recorded historical values is not permitted, regardless of this toggle, but can be accomplished
via watches or scripts.</P>
<H3><A name="snapshot_window"></A>Snapshot Window</H3>

View File

@ -61,14 +61,17 @@
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>
is its hexadecimal value. This field is user modifiable when the <B>Enable Edits</B> toggle
is on. Changes are sent to the target if the trace is live and "at the present." 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 by the selected data type.</LI>
<LI>Representation - the value of the watch as interpreted by the selected data type. This
field is not yet user modifiable, even if the <B>Enable Edits</B> toggle is on.</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
@ -118,6 +121,14 @@
<P>This action is available when at least one watch is selected. It removes those watches.</P>
<H3><A name="enable_edits"></A>Enable Edits</H3>
<P>This toggle is a write protector for recorded and/or live values. To modify a watch's value,
this toggle must be enabled. Editing a value when the trace is live and "at the present" will
cause the value to be modified on the target. Editing historical and/or emulated values is
permitted, but it has no effect on the target. Note that only the raw "Value" column can be
edited directly. The "Repr" column cannot be edited, yet.</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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -895,10 +895,10 @@ public interface DebuggerResources {
}
}
interface EnableRegisterEditsAction {
interface EnableEditsAction {
String NAME = "Enable Edits";
String DESCRIPTION = "Enable editing of recorded register values";
String GROUP = "yyyy";
String DESCRIPTION = "Enable editing of recorded or live values";
String GROUP = "yyyy2";
Icon ICON = ResourceManager.loadImage("images/editbytes.gif");
String HELP_ANCHOR = "enable_edits";

View File

@ -604,7 +604,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
.onAction(c -> createSnapshotActivated())
.buildAndInstallLocal(this);
}
actionEnableEdits = DebuggerResources.EnableRegisterEditsAction.builder(plugin)
actionEnableEdits = DebuggerResources.EnableEditsAction.builder(plugin)
.enabledWhen(c -> current.getThread() != null)
.onAction(c -> {
})
@ -758,12 +758,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (!computeEditsEnabled()) {
return false;
}
Collection<Register> onTarget =
current.getRecorder().getRegisterMapper(current.getThread()).getRegistersOnTarget();
if (!onTarget.contains(register) && !onTarget.contains(register.getBaseRegister())) {
return false;
}
return true;
return current.getRecorder().isRegisterOnTarget(current.getThread(), register);
}
BigInteger getRegisterValue(Register register) {

View File

@ -21,8 +21,7 @@ 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 java.util.function.*;
import javax.swing.*;
import javax.swing.table.TableColumn;
@ -31,6 +30,7 @@ import javax.swing.table.TableColumnModel;
import docking.ActionContext;
import docking.WindowPosition;
import docking.action.DockingAction;
import docking.action.ToggleDockingAction;
import docking.widgets.table.*;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
@ -75,7 +75,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
protected enum WatchTableColumns implements EnumeratedTableColumn<WatchTableColumns, WatchRow> {
EXPRESSION("Expression", String.class, WatchRow::getExpression, WatchRow::setExpression),
ADDRESS("Address", Address.class, WatchRow::getAddress),
VALUE("Value", String.class, WatchRow::getRawValueString),
VALUE("Value", String.class, WatchRow::getRawValueString, WatchRow::setRawValueString, WatchRow::isValueEditable),
TYPE("Type", DataType.class, WatchRow::getDataType, WatchRow::setDataType),
REPR("Repr", String.class, WatchRow::getValueString),
ERROR("Error", String.class, WatchRow::getErrorMessage);
@ -83,19 +83,26 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
private final String header;
private final Function<WatchRow, ?> getter;
private final BiConsumer<WatchRow, Object> setter;
private final Predicate<WatchRow> editable;
private final Class<?> cls;
@SuppressWarnings("unchecked")
<T> WatchTableColumns(String header, Class<T> cls, Function<WatchRow, T> getter,
BiConsumer<WatchRow, T> setter) {
BiConsumer<WatchRow, T> setter, Predicate<WatchRow> editable) {
this.header = header;
this.cls = cls;
this.getter = getter;
this.setter = (BiConsumer<WatchRow, Object>) setter;
this.editable = editable;
}
<T> WatchTableColumns(String header, Class<T> cls, Function<WatchRow, T> getter,
BiConsumer<WatchRow, T> setter) {
this(header, cls, getter, setter, null);
}
<T> WatchTableColumns(String header, Class<T> cls, Function<WatchRow, T> getter) {
this(header, cls, getter, null);
this(header, cls, getter, null, null);
}
@Override
@ -120,7 +127,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
@Override
public boolean isEditable(WatchRow row) {
return setter != null;
return setter != null && (editable == null || editable.test(row));
}
}
@ -267,6 +274,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
protected GhidraTable watchTable;
protected GhidraTableFilterPanel<WatchRow> watchFilterPanel;
ToggleDockingAction actionEnableEdits;
DockingAction actionApplyDataType;
DockingAction actionSelectRange;
DockingAction actionSelectAllReads;
@ -360,6 +368,11 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
}
protected void createActions() {
actionEnableEdits = DebuggerResources.EnableEditsAction.builder(plugin)
.enabledWhen(c -> current.getTrace() != null)
.onAction(c -> {
})
.buildAndInstallLocal(this);
actionApplyDataType = ApplyDataTypeAction.builder(plugin)
.withContext(DebuggerWatchActionContext.class)
.enabledWhen(ctx -> current.getTrace() != null && selHasDataType(ctx))
@ -622,4 +635,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
}
watchTableModel.addAll(rows);
}
public boolean isEditsEnabled() {
return actionEnableEdits.isSelected();
}
}

View File

@ -16,6 +16,7 @@
package ghidra.app.plugin.core.debug.gui.watch;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
@ -38,12 +39,15 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.NumericUtilities;
import ghidra.util.Swing;
import ghidra.util.*;
import ghidra.util.database.UndoableTransaction;
public class WatchRow {
public static final int TRUNCATE_BYTES_LENGTH = 64;
private final DebuggerWatchesProvider provider;
private Trace trace;
private DebuggerCoordinates coordinates;
private SleighLanguage language;
private PcodeExecutor<Pair<byte[], TraceMemoryState>> executorWithState;
private ReadDepsPcodeExecutor executorWithAddress;
@ -218,6 +222,7 @@ public class WatchRow {
// NB. Caller has already verified coordinates actually changed
prevValue = value;
trace = coordinates.getTrace();
this.coordinates = coordinates;
updateType();
if (trace == null) {
blank();
@ -327,8 +332,12 @@ public class WatchRow {
Utils.bytesToBigInteger(value, value.length, language.isBigEndian(), false);
return "0x" + asBigInt.toString(16);
}
if (value.length > 20) {
return "{ " + NumericUtilities.convertBytesToString(value, 0, 20, " ") + " ... }";
if (value.length > TRUNCATE_BYTES_LENGTH) {
// TODO: I'd like this not to affect the actual value, just the display
// esp., since this will be the "value" when starting to edit.
return "{ " +
NumericUtilities.convertBytesToString(value, 0, TRUNCATE_BYTES_LENGTH, " ") +
" ... }";
}
return "{ " + NumericUtilities.convertBytesToString(value, " ") + " }";
}
@ -345,6 +354,79 @@ public class WatchRow {
return valueString;
}
public boolean isValueEditable() {
return address != null && provider.isEditsEnabled();
}
public void setRawValueString(String valueString) {
valueString = valueString.trim();
if (valueString.startsWith("{")) {
if (!valueString.endsWith("}")) {
throw new NumberFormatException("Byte array values must be hex enclosed in {}");
}
setRawValueBytesString(valueString.substring(1, valueString.length() - 1));
return;
}
setRawValueIntString(valueString);
}
public void setRawValueBytesString(String bytesString) {
setRawValueBytes(NumericUtilities.convertStringToBytes(bytesString));
}
public void setRawValueIntString(String intString) {
intString = intString.trim();
final BigInteger val;
if (intString.startsWith("0x")) {
val = new BigInteger(intString.substring(2), 16);
}
else {
val = new BigInteger(intString, 10);
}
setRawValueBytes(
Utils.bigIntegerToBytes(val, value.length, trace.getBaseLanguage().isBigEndian()));
}
public void setRawValueBytes(byte[] bytes) {
if (address == null) {
throw new IllegalStateException("Cannot write to watch variable without an address");
}
if (bytes.length != value.length) {
throw new IllegalArgumentException("Byte array values must match length of variable");
}
// Allow writes to unmappable registers to fall through to trace
// However, attempts to write "weird" register addresses is forbidden
if (coordinates.isAliveAndPresent() && coordinates.getRecorder()
.isVariableOnTarget(coordinates.getThread(), address, bytes.length)) {
coordinates.getRecorder()
.writeVariable(coordinates.getThread(), coordinates.getFrame(), address, bytes)
.exceptionally(ex -> {
Msg.showError(this, null, "Write Failed",
"Could not modify watch value (on target)", ex);
return null;
});
// NB: if successful, recorder will write to trace
return;
}
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Write watch at " + address, true)) {
final TraceMemorySpace space;
if (address.isRegisterAddress()) {
space = trace.getMemoryManager()
.getMemoryRegisterSpace(coordinates.getThread(), coordinates.getFrame(),
true);
}
else {
space = trace.getMemoryManager().getMemorySpace(address.getAddressSpace(), true);
}
space.putBytes(coordinates.getViewSnap(), address, ByteBuffer.wrap(bytes));
}
}
public int getValueLength() {
return value == null ? 0 : value.length;
}

View File

@ -26,20 +26,22 @@ import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetBreakpointSpec.TargetBreakpointKind;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.lifecycle.Internal;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.lang.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.breakpoint.TraceBreakpointKind;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.modules.TraceModule;
import ghidra.trace.model.modules.TraceSection;
import ghidra.trace.model.stack.TraceStackFrame;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
import ghidra.trace.util.TraceRegisterUtils;
import ghidra.util.task.TaskMonitor;
/**
@ -336,6 +338,86 @@ public interface TraceRecorder {
CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView selection,
TaskMonitor monitor, boolean toMap);
/**
* Write a variable (memory or register) of the given thread or the process
*
* <p>
* This is a convenience for writing target memory or registers, based on address. If the given
* address represents a register, this will attempt to map it to a register and write it in the
* given thread and frame. If the address is in memory, it will simply delegate to
* {@link #writeProcessMemory(Address, byte[])}.
*
* @param thread the thread. Ignored (may be null) if address is in memory
* @param frameLevel the frame, usually 0. Ignored if address is in memory
* @param address the starting address
* @param data the value to write
* @return a future which completes when the write is complete
*/
default CompletableFuture<Void> writeVariable(TraceThread thread, int frameLevel,
Address address, byte[] data) {
if (address.isMemoryAddress()) {
return writeProcessMemory(address, data);
}
if (address.isRegisterAddress()) {
Language lang = getTrace().getBaseLanguage();
Register register = lang.getRegister(address, data.length);
if (register == null) {
throw new IllegalArgumentException(
"Cannot identify the (single) register to write: " + address);
}
RegisterValue rv = new RegisterValue(register,
Utils.bytesToBigInteger(data, data.length, lang.isBigEndian(), false));
TraceMemoryRegisterSpace regs =
getTrace().getMemoryManager().getMemoryRegisterSpace(thread, frameLevel, false);
rv = TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, getSnap(), regs, true);
return writeThreadRegisters(thread, frameLevel, Map.of(rv.getRegister(), rv));
}
throw new IllegalArgumentException("Address is not in a recognized space: " + address);
}
/**
* Check if the given register exists on target (is mappable) for the given thread
*
* @param thread the thread whose registers to examine
* @param register the register to check
* @return true if the given register is known for the given thread on target
*/
default boolean isRegisterOnTarget(TraceThread thread, Register register) {
Collection<Register> onTarget = getRegisterMapper(thread).getRegistersOnTarget();
return onTarget.contains(register) || onTarget.contains(register.getBaseRegister());
}
/**
* Check if the given trace address exists in target memory
*
* @param address the address to check
* @return true if the given trace address can be mapped to the target's memory
*/
default boolean isMemoryOnTarget(Address address) {
return getMemoryMapper().traceToTarget(address) != null;
}
/**
* Check if a given variable (register or memory) exists on target
*
* @param thread if a register, the thread whose registers to examine
* @param address the address of the variable
* @param size the size of the variable. Ignored for memory
* @return true if the variable can be mapped to the target
*/
default boolean isVariableOnTarget(TraceThread thread, Address address, int size) {
if (address.isMemoryAddress()) {
return isMemoryOnTarget(address);
}
Register register = getTrace().getBaseLanguage().getRegister(address, size);
if (register == null) {
throw new IllegalArgumentException("Cannot identify the (single) register: " + address);
}
return isRegisterOnTarget(thread, register);
}
/**
* Capture the data types of a target's module.
*

View File

@ -25,10 +25,8 @@ import ghidra.pcode.exec.trace.TraceMemoryStatePcodeExecutorStatePiece;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceRegisterUtils;
import ghidra.util.task.TaskMonitor;
public class TraceRecorderAsyncPcodeExecutorState
@ -48,28 +46,7 @@ public class TraceRecorderAsyncPcodeExecutorState
protected CompletableFuture<?> doSetTargetVar(AddressSpace space, long offset, int size,
boolean truncateAddressableUnit, byte[] val) {
if (space.isMemorySpace()) {
return recorder.writeProcessMemory(space.getAddress(offset), val);
}
assert space.isRegisterSpace();
Language lang = recorder.getTrace().getBaseLanguage();
Register register = lang.getRegister(space, offset, size);
if (register == null) {
// TODO: Is this too restrictive? I can't imagine any code producing such nonsense
throw new IllegalArgumentException(
"write to register space must be to one register");
}
RegisterValue rv = new RegisterValue(register, Utils.bytesToBigInteger(
val, size, recorder.getTrace().getBaseLanguage().isBigEndian(), false));
TraceMemoryRegisterSpace regs = recorder.getTrace()
.getMemoryManager()
.getMemoryRegisterSpace(traceState.getThread(), false);
rv = TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, traceState.getSnap(),
regs, true);
return recorder.writeThreadRegisters(traceState.getThread(), traceState.getFrame(),
Map.of(rv.getRegister(), rv));
return recorder.writeVariable(traceState.getThread(), 0, space.getAddress(offset), val);
}
protected byte[] knitFromResults(NavigableMap<Address, byte[]> map, Address addr, int size) {

View File

@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.junit.*;
@ -27,6 +28,7 @@ import generic.Unique;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin;
import ghidra.app.services.TraceRecorder;
import ghidra.async.AsyncTestUtils;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRangeImpl;
@ -35,13 +37,15 @@ 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.TraceMemoryOperations;
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 {
public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUITest
implements AsyncTestUtils {
protected static void assertNoErr(WatchRow row) {
Throwable error = row.getError();
@ -55,8 +59,12 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
protected DebuggerListingPlugin listingPlugin;
protected Register r0;
protected Register r1;
protected TraceThread thread;
protected TestTargetRegisterBankInThread bank;
protected TraceRecorder recorder;
@Before
public void setUpWatchesProviderTest() throws Exception {
watchesPlugin = addPlugin(tool, DebuggerWatchesPlugin.class);
@ -65,6 +73,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
createTrace();
r0 = tb.language.getRegister("r0");
r1 = tb.language.getRegister("r1");
try (UndoableTransaction tid = tb.startTransaction()) {
thread = tb.getOrAddThread("Thread1", 0);
}
@ -189,7 +198,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
public void testLiveCausesReads() throws Exception {
createTestModel();
mb.createTestProcessesAndThreads();
TestTargetRegisterBankInThread bank = mb.testThread1.addRegisterBank();
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);
@ -198,7 +207,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
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,
recorder = modelService.recordTarget(mb.testProcess1,
new TestDebuggerTargetTraceMapper(mb.testProcess1));
Trace trace = recorder.getTrace();
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
@ -231,4 +240,157 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
});
assertNoErr(row);
}
protected void runTestDeadIsEditable(String expression, boolean expectWritable) {
setRegisterValues(thread);
performAction(watchesProvider.actionAdd);
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
row.setExpression(expression);
assertFalse(row.isValueEditable());
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
waitForSwing();
assertNoErr(row);
assertFalse(row.isValueEditable());
performAction(watchesProvider.actionEnableEdits);
assertEquals(expectWritable, row.isValueEditable());
}
@Test
public void testDeadIsRegisterEditable() {
runTestDeadIsEditable("r0", true);
}
@Test
public void testDeadIsUniqueEditable() {
runTestDeadIsEditable("r0 + 8", false);
}
@Test
public void testDeadIsMemoryEditable() {
runTestDeadIsEditable("*:8 r0", true);
}
protected WatchRow prepareTestDeadEdit(String expression) {
setRegisterValues(thread);
performAction(watchesProvider.actionAdd);
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
row.setExpression("r0");
traceManager.openTrace(tb.trace);
traceManager.activateThread(thread);
performAction(watchesProvider.actionEnableEdits);
return row;
}
@Test
public void testDeadEditRegister() {
WatchRow row = prepareTestDeadEdit("r0");
row.setRawValueString("0x1234");
waitForSwing();
TraceMemoryRegisterSpace regVals =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
assertEquals(BigInteger.valueOf(0x1234), regVals.getValue(0, r0).getUnsignedValue());
row.setRawValueString("1234");
waitForSwing();
assertEquals(BigInteger.valueOf(1234), regVals.getValue(0, r0).getUnsignedValue());
}
@Test
public void testDeadEditMemory() {
WatchRow row = prepareTestDeadEdit("*:8 r0");
row.setRawValueString("0x1234");
waitForSwing();
TraceMemoryOperations mem = tb.trace.getMemoryManager();
ByteBuffer buf = ByteBuffer.allocate(8);
mem.getBytes(0, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x1234, buf.getLong());
row.setRawValueString("{ 12 34 56 78 9a bc de f0 }");
waitForSwing();
buf.clear();
mem.getBytes(0, tb.addr(0x00400000), buf);
buf.flip();
assertEquals(0x123456789abcdef0L, buf.getLong());
}
protected WatchRow prepareTestLiveEdit(String expression) throws Exception {
createTestModel();
mb.createTestProcessesAndThreads();
bank = mb.testThread1.addRegisterBank();
mb.testProcess1.regs.addRegistersFromLanguage(tb.language,
r -> r != r1 && r.isBaseRegister());
bank.writeRegister("r0", tb.arr(0, 0, 0, 0, 0, 0x40, 0, 0));
mb.testProcess1.addRegion(".text", mb.rng(0x00400000, 0x00401000), "rx");
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();
performAction(watchesProvider.actionAdd);
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
row.setExpression(expression);
performAction(watchesProvider.actionEnableEdits);
return row;
}
@Test
public void testLiveEditRegister() throws Throwable {
WatchRow row = prepareTestLiveEdit("r0");
row.setRawValueString("0x1234");
retryVoid(() -> {
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0"));
}, List.of(AssertionError.class));
}
@Test
public void testLiveEditMemory() throws Throwable {
WatchRow row = prepareTestLiveEdit("*:8 r0");
row.setRawValueString("0x1234");
retryVoid(() -> {
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34),
waitOn(mb.testProcess1.memory.readMemory(tb.addr(0x00400000), 8)));
}, List.of(AssertionError.class));
}
@Test
public void testLiveEditNonMappableRegister() throws Throwable {
WatchRow row = prepareTestLiveEdit("r1");
TraceThread thread = recorder.getTraceThread(mb.testThread1);
// Sanity check
assertFalse(recorder.isRegisterOnTarget(thread, r1));
row.setRawValueString("0x1234");
waitForSwing();
TraceMemoryRegisterSpace regs =
recorder.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false);
assertEquals(BigInteger.valueOf(0x1234),
regs.getValue(recorder.getSnap(), r1).getUnsignedValue());
assertFalse(bank.regVals.containsKey("r1"));
}
}