mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GP-1881: Implement editable Repr column for Registers and Watches providers.
This commit is contained in:
parent
8e8b193ac1
commit
dbe670bf85
12 changed files with 314 additions and 28 deletions
|
@ -325,9 +325,9 @@
|
||||||
|
|
||||||
<H3><A name="toggle_update_while_running">Update While Running</A></H3>
|
<H3><A name="toggle_update_while_running">Update While Running</A></H3>
|
||||||
|
|
||||||
<P>By default, events are passed to the Objects Viewer even while the target is running.
|
<P>By default, events are passed to the Objects Viewer even while the target is running. The
|
||||||
The resulting changes in the GUI may be distracting for some. To disable updates to the
|
resulting changes in the GUI may be distracting for some. To disable updates to the Objects
|
||||||
Objects Viewer, toggle "Updates While Running" off.</P>
|
Viewer, toggle "Updates While Running" off.</P>
|
||||||
|
|
||||||
<H2><A name="color"></A>Color Options</H2>
|
<H2><A name="color"></A>Color Options</H2>
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,8 @@
|
||||||
time on."</LI>
|
time on."</LI>
|
||||||
|
|
||||||
<LI>Representation - the value of the register as interpreted by its data type. If the value
|
<LI>Representation - the value of the register as interpreted by its data type. If the value
|
||||||
is an address, double-clicking this field will navigate to it.</LI>
|
is an address, double-clicking this field will navigate to it. This field is user modifiable
|
||||||
|
whenever the Value column is modifiable and the selected data type provides an encoder.</LI>
|
||||||
</UL>
|
</UL>
|
||||||
|
|
||||||
<H2>Actions</H2>
|
<H2>Actions</H2>
|
||||||
|
|
|
@ -78,10 +78,10 @@
|
||||||
trace. Clicking the Apply Data Type action will apply it to the current trace, if
|
trace. Clicking the Apply Data Type action will apply it to the current trace, if
|
||||||
possible.</LI>
|
possible.</LI>
|
||||||
|
|
||||||
<LI>Representation - the value of the watch as interpreted by the selected data type. This
|
<LI>Representation - the value of the watch as interpreted by the selected data type. If the
|
||||||
field is not yet user modifiable, even if the <B>Enable Edits</B> toggle is on. If the value
|
value is an address, i.e., Type is a pointer, then double-clicking this cell will navigate
|
||||||
is an address, i.e., Type is a pointer, then double-clicking this cell will navigate the
|
the primary dynamic listing, if possible. This field is user-modifiable whenever the Value
|
||||||
primary dynamic listing, if possible.</LI>
|
column is modifiable and the selected data type provides an encoder.</LI>
|
||||||
|
|
||||||
<LI>Error - if an error occurs during compilation or evaluation of the expression, that error
|
<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
|
is rendered here. Double-clicking the row will display the stack trace. Note that errors
|
||||||
|
|
|
@ -62,8 +62,7 @@ import ghidra.framework.plugintool.AutoService;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.data.DataType;
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.program.model.data.DataTypeConflictException;
|
|
||||||
import ghidra.program.model.lang.*;
|
import ghidra.program.model.lang.*;
|
||||||
import ghidra.program.model.util.CodeUnitInsertionException;
|
import ghidra.program.model.util.CodeUnitInsertionException;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
|
@ -89,12 +88,16 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
|
|
||||||
protected enum RegisterTableColumns
|
protected enum RegisterTableColumns
|
||||||
implements EnumeratedTableColumn<RegisterTableColumns, RegisterRow> {
|
implements EnumeratedTableColumn<RegisterTableColumns, RegisterRow> {
|
||||||
FAV("Fav", Boolean.class, RegisterRow::isFavorite, RegisterRow::setFavorite, r -> true, SortDirection.DESCENDING),
|
FAV("Fav", Boolean.class, RegisterRow::isFavorite, RegisterRow::setFavorite, //
|
||||||
|
r -> true, SortDirection.DESCENDING),
|
||||||
NUMBER("#", Integer.class, RegisterRow::getNumber),
|
NUMBER("#", Integer.class, RegisterRow::getNumber),
|
||||||
NAME("Name", String.class, RegisterRow::getName),
|
NAME("Name", String.class, RegisterRow::getName),
|
||||||
VALUE("Value", BigInteger.class, RegisterRow::getValue, RegisterRow::setValue, RegisterRow::isValueEditable, SortDirection.ASCENDING),
|
VALUE("Value", BigInteger.class, RegisterRow::getValue, RegisterRow::setValue, //
|
||||||
TYPE("Type", DataType.class, RegisterRow::getDataType, RegisterRow::setDataType, r -> true, SortDirection.ASCENDING),
|
RegisterRow::isValueEditable, SortDirection.ASCENDING),
|
||||||
REPR("Repr", String.class, RegisterRow::getRepresentation);
|
TYPE("Type", DataType.class, RegisterRow::getDataType, RegisterRow::setDataType, //
|
||||||
|
r -> true, SortDirection.ASCENDING),
|
||||||
|
REPR("Repr", String.class, RegisterRow::getRepresentation, RegisterRow::setRepresentation, //
|
||||||
|
RegisterRow::isRepresentationEditable, SortDirection.ASCENDING);
|
||||||
|
|
||||||
private final String header;
|
private final String header;
|
||||||
private final Function<RegisterRow, ?> getter;
|
private final Function<RegisterRow, ?> getter;
|
||||||
|
@ -859,6 +862,35 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
||||||
return data.getDataType();
|
return data.getDataType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void writeRegisterValueRepresentation(Register register, String representation) {
|
||||||
|
TraceData data = getRegisterData(register);
|
||||||
|
if (data == null) {
|
||||||
|
// isEditable should have been false
|
||||||
|
tool.setStatusInfo("Register has no data type", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
RegisterValue rv = TraceRegisterUtils.encodeValueRepresentationHackPointer(
|
||||||
|
register, data, representation);
|
||||||
|
writeRegisterValue(rv);
|
||||||
|
}
|
||||||
|
catch (DataTypeEncodeException e) {
|
||||||
|
tool.setStatusInfo(e.getMessage(), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean canWriteRegisterRepresentation(Register register) {
|
||||||
|
if (!canWriteRegister(register)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TraceData data = getRegisterData(register);
|
||||||
|
if (data == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return data.getBaseDataType().isEncodable();
|
||||||
|
}
|
||||||
|
|
||||||
String getRegisterValueRepresentation(Register register) {
|
String getRegisterValueRepresentation(Register register) {
|
||||||
TraceData data = getRegisterData(register);
|
TraceData data = getRegisterData(register);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
|
|
@ -91,7 +91,13 @@ public class RegisterRow {
|
||||||
return provider.getRegisterDataType(register);
|
return provider.getRegisterDataType(register);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: setValueRepresentation. Requires support from data types.
|
public void setRepresentation(String representation) {
|
||||||
|
provider.writeRegisterValueRepresentation(register, representation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRepresentationEditable() {
|
||||||
|
return provider.canWriteRegisterRepresentation(register);
|
||||||
|
}
|
||||||
|
|
||||||
public String getRepresentation() {
|
public String getRepresentation() {
|
||||||
return provider.getRegisterValueRepresentation(register);
|
return provider.getRegisterValueRepresentation(register);
|
||||||
|
|
|
@ -86,9 +86,11 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter {
|
||||||
protected enum WatchTableColumns implements EnumeratedTableColumn<WatchTableColumns, WatchRow> {
|
protected enum WatchTableColumns implements EnumeratedTableColumn<WatchTableColumns, WatchRow> {
|
||||||
EXPRESSION("Expression", String.class, WatchRow::getExpression, WatchRow::setExpression),
|
EXPRESSION("Expression", String.class, WatchRow::getExpression, WatchRow::setExpression),
|
||||||
ADDRESS("Address", Address.class, WatchRow::getAddress),
|
ADDRESS("Address", Address.class, WatchRow::getAddress),
|
||||||
VALUE("Value", String.class, WatchRow::getRawValueString, WatchRow::setRawValueString, WatchRow::isValueEditable),
|
VALUE("Value", String.class, WatchRow::getRawValueString, WatchRow::setRawValueString, //
|
||||||
|
WatchRow::isRawValueEditable),
|
||||||
TYPE("Type", DataType.class, WatchRow::getDataType, WatchRow::setDataType),
|
TYPE("Type", DataType.class, WatchRow::getDataType, WatchRow::setDataType),
|
||||||
REPR("Repr", String.class, WatchRow::getValueString),
|
REPR("Repr", String.class, WatchRow::getValueString, WatchRow::setValueString, //
|
||||||
|
WatchRow::isValueEditable),
|
||||||
ERROR("Error", String.class, WatchRow::getErrorMessage);
|
ERROR("Error", String.class, WatchRow::getErrorMessage);
|
||||||
|
|
||||||
private final String header;
|
private final String header;
|
||||||
|
|
|
@ -33,6 +33,7 @@ import ghidra.pcode.exec.trace.TraceSleighUtils;
|
||||||
import ghidra.pcode.utils.Utils;
|
import ghidra.pcode.utils.Utils;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.data.DataType;
|
import ghidra.program.model.data.DataType;
|
||||||
|
import ghidra.program.model.data.DataTypeEncodeException;
|
||||||
import ghidra.program.model.lang.Language;
|
import ghidra.program.model.lang.Language;
|
||||||
import ghidra.program.model.mem.ByteMemBufferImpl;
|
import ghidra.program.model.mem.ByteMemBufferImpl;
|
||||||
import ghidra.program.model.mem.MemBuffer;
|
import ghidra.program.model.mem.MemBuffer;
|
||||||
|
@ -143,6 +144,8 @@ public class WatchRow {
|
||||||
return dataType.getRepresentation(buffer, SettingsImpl.NO_SETTINGS, value.length);
|
return dataType.getRepresentation(buffer, SettingsImpl.NO_SETTINGS, value.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: DataType settings
|
||||||
|
|
||||||
protected Object parseAsDataTypeObj() {
|
protected Object parseAsDataTypeObj() {
|
||||||
if (dataType == null || value == null) {
|
if (dataType == null || value == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -373,7 +376,7 @@ public class WatchRow {
|
||||||
return valueObj;
|
return valueObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isValueEditable() {
|
public boolean isRawValueEditable() {
|
||||||
if (!provider.isEditsEnabled()) {
|
if (!provider.isEditsEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -423,8 +426,13 @@ public class WatchRow {
|
||||||
if (address == null) {
|
if (address == null) {
|
||||||
throw new IllegalStateException("Cannot write to watch variable without an address");
|
throw new IllegalStateException("Cannot write to watch variable without an address");
|
||||||
}
|
}
|
||||||
if (bytes.length != value.length) {
|
if (bytes.length > value.length) {
|
||||||
throw new IllegalArgumentException("Byte array values must match length of variable");
|
throw new IllegalArgumentException("Byte arrays cannot exceed length of variable");
|
||||||
|
}
|
||||||
|
if (bytes.length < value.length) {
|
||||||
|
byte[] fillOld = Arrays.copyOf(value, value.length);
|
||||||
|
System.arraycopy(bytes, 0, fillOld, 0, bytes.length);
|
||||||
|
bytes = fillOld;
|
||||||
}
|
}
|
||||||
DebuggerStateEditingService editingService = provider.editingService;
|
DebuggerStateEditingService editingService = provider.editingService;
|
||||||
if (editingService == null) {
|
if (editingService == null) {
|
||||||
|
@ -438,6 +446,33 @@ public class WatchRow {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setValueString(String valueString) {
|
||||||
|
if (dataType == null || value == null) {
|
||||||
|
// isValueEditable should have been false
|
||||||
|
provider.getTool().setStatusInfo("Watch no value or no data type", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
byte[] encoded = dataType.encodeRepresentation(valueString,
|
||||||
|
new ByteMemBufferImpl(address, value, language.isBigEndian()),
|
||||||
|
SettingsImpl.NO_SETTINGS, value.length);
|
||||||
|
setRawValueBytes(encoded);
|
||||||
|
}
|
||||||
|
catch (DataTypeEncodeException e) {
|
||||||
|
provider.getTool().setStatusInfo(e.getMessage(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValueEditable() {
|
||||||
|
if (!isRawValueEditable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dataType == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return dataType.isEncodable();
|
||||||
|
}
|
||||||
|
|
||||||
public int getValueLength() {
|
public int getValueLength() {
|
||||||
return value == null ? 0 : value.length;
|
return value == null ? 0 : value.length;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import static ghidra.lifecycle.Unfinished.TODO;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -159,6 +160,11 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
||||||
row.setValue(new BigInteger(text, 16));
|
row.setValue(new BigInteger(text, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setRowRepr(RegisterRow row, String repr) {
|
||||||
|
assertTrue(row.isRepresentationEditable());
|
||||||
|
row.setRepresentation(repr);
|
||||||
|
}
|
||||||
|
|
||||||
protected void assertRowValueEmpty(RegisterRow row) {
|
protected void assertRowValueEmpty(RegisterRow row) {
|
||||||
assertEquals(BigInteger.ZERO, row.getValue());
|
assertEquals(BigInteger.ZERO, row.getValue());
|
||||||
}
|
}
|
||||||
|
@ -393,6 +399,48 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long encodeDouble(double value) {
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(Double.BYTES);
|
||||||
|
buf.putDouble(0, value);
|
||||||
|
return buf.getLong(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyRepresentationEmu() throws Exception {
|
||||||
|
traceManager.openTrace(tb.trace);
|
||||||
|
|
||||||
|
TraceThread thread = addThread();
|
||||||
|
traceManager.activateThread(thread);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||||
|
|
||||||
|
assertTrue(registersProvider.actionEnableEdits.isEnabled());
|
||||||
|
performAction(registersProvider.actionEnableEdits);
|
||||||
|
|
||||||
|
addRegisterValues(thread);
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
TraceMemoryRegisterSpace regVals =
|
||||||
|
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||||
|
|
||||||
|
RegisterRow row = findRegisterRow(r0);
|
||||||
|
assertFalse(row.isRepresentationEditable());
|
||||||
|
|
||||||
|
row.setDataType(DoubleDataType.dataType);
|
||||||
|
waitForDomainObject(tb.trace);
|
||||||
|
|
||||||
|
setRowRepr(row, "1234");
|
||||||
|
waitForSwing();
|
||||||
|
waitForPass(() -> {
|
||||||
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
|
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||||
|
assertEquals(BigInteger.valueOf(encodeDouble(1234)),
|
||||||
|
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
||||||
|
assertEquals(BigInteger.valueOf(encodeDouble(1234)), row.getValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Value modification only allowed on live target
|
// NOTE: Value modification only allowed on live target
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -270,17 +270,17 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
|
WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData());
|
||||||
row.setExpression(expression);
|
row.setExpression(expression);
|
||||||
|
|
||||||
assertFalse(row.isValueEditable());
|
assertFalse(row.isRawValueEditable());
|
||||||
traceManager.openTrace(tb.trace);
|
traceManager.openTrace(tb.trace);
|
||||||
traceManager.activateThread(thread);
|
traceManager.activateThread(thread);
|
||||||
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
assertNoErr(row);
|
assertNoErr(row);
|
||||||
assertFalse(row.isValueEditable());
|
assertFalse(row.isRawValueEditable());
|
||||||
|
|
||||||
performAction(watchesProvider.actionEnableEdits);
|
performAction(watchesProvider.actionEnableEdits);
|
||||||
assertEquals(expectWritable, row.isValueEditable());
|
assertEquals(expectWritable, row.isRawValueEditable());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -314,6 +314,12 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long encodeDouble(double value) {
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(Double.BYTES);
|
||||||
|
buf.putDouble(0, value);
|
||||||
|
return buf.getLong(0);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEditRegisterEmu() {
|
public void testEditRegisterEmu() {
|
||||||
WatchRow row = prepareTestEditEmu("r0");
|
WatchRow row = prepareTestEditEmu("r0");
|
||||||
|
@ -339,9 +345,33 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditRegisterRepresentationEmu() {
|
||||||
|
WatchRow row = prepareTestEditEmu("r0");
|
||||||
|
assertFalse(row.isValueEditable());
|
||||||
|
|
||||||
|
row.setDataType(DoubleDataType.dataType);
|
||||||
|
waitForSwing();
|
||||||
|
assertTrue(row.isValueEditable());
|
||||||
|
|
||||||
|
TraceMemoryRegisterSpace regVals =
|
||||||
|
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
|
||||||
|
|
||||||
|
row.setValueString("1234");
|
||||||
|
waitForPass(() -> {
|
||||||
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
|
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||||
|
assertEquals(BigInteger.valueOf(encodeDouble(1234)),
|
||||||
|
regVals.getValue(viewSnap, r0).getUnsignedValue());
|
||||||
|
assertEquals("0x4093480000000000", row.getRawValueString());
|
||||||
|
assertEquals("1234.0", row.getValueString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEditMemoryEmu() {
|
public void testEditMemoryEmu() {
|
||||||
WatchRow row = prepareTestEditEmu("*:8 r0");
|
WatchRow row = prepareTestEditEmu("*:8 r0");
|
||||||
|
|
||||||
TraceMemoryOperations mem = tb.trace.getMemoryManager();
|
TraceMemoryOperations mem = tb.trace.getMemoryManager();
|
||||||
ByteBuffer buf = ByteBuffer.allocate(8);
|
ByteBuffer buf = ByteBuffer.allocate(8);
|
||||||
|
|
||||||
|
@ -366,6 +396,55 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditMemoryRepresentationEmu() {
|
||||||
|
WatchRow row = prepareTestEditEmu("*:8 r0");
|
||||||
|
assertFalse(row.isValueEditable());
|
||||||
|
|
||||||
|
row.setDataType(DoubleDataType.dataType);
|
||||||
|
waitForSwing();
|
||||||
|
assertTrue(row.isValueEditable());
|
||||||
|
|
||||||
|
TraceMemoryOperations mem = tb.trace.getMemoryManager();
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(8);
|
||||||
|
|
||||||
|
row.setValueString("1234");
|
||||||
|
waitForPass(() -> {
|
||||||
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
|
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||||
|
buf.clear();
|
||||||
|
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
|
||||||
|
buf.flip();
|
||||||
|
assertEquals(encodeDouble(1234), buf.getLong());
|
||||||
|
assertEquals("1234.0", row.getValueString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEditMemoryStringEmu() {
|
||||||
|
// Variable size must exceed that of desired string's bytes
|
||||||
|
WatchRow row = prepareTestEditEmu("*:16 r0");
|
||||||
|
assertFalse(row.isValueEditable());
|
||||||
|
|
||||||
|
row.setDataType(TerminatedStringDataType.dataType);
|
||||||
|
waitForSwing();
|
||||||
|
assertTrue(row.isValueEditable());
|
||||||
|
|
||||||
|
TraceMemoryOperations mem = tb.trace.getMemoryManager();
|
||||||
|
ByteBuffer buf = ByteBuffer.allocate(14);
|
||||||
|
|
||||||
|
row.setValueString("\"Hello, World!\"");
|
||||||
|
waitForPass(() -> {
|
||||||
|
long viewSnap = traceManager.getCurrent().getViewSnap();
|
||||||
|
assertTrue(DBTraceUtils.isScratch(viewSnap));
|
||||||
|
buf.clear();
|
||||||
|
mem.getBytes(viewSnap, tb.addr(0x00400000), buf);
|
||||||
|
buf.flip();
|
||||||
|
assertArrayEquals("Hello, World!\0".getBytes(), buf.array());
|
||||||
|
assertEquals("\"Hello, World!\"", row.getValueString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected WatchRow prepareTestEditTarget(String expression) throws Exception {
|
protected WatchRow prepareTestEditTarget(String expression) throws Exception {
|
||||||
createTestModel();
|
createTestModel();
|
||||||
mb.createTestProcessesAndThreads();
|
mb.createTestProcessesAndThreads();
|
||||||
|
@ -422,7 +501,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
|
||||||
// Sanity check
|
// Sanity check
|
||||||
assertFalse(recorder.isRegisterOnTarget(thread, r1));
|
assertFalse(recorder.isRegisterOnTarget(thread, r1));
|
||||||
|
|
||||||
assertFalse(row.isValueEditable());
|
assertFalse(row.isRawValueEditable());
|
||||||
row.setRawValueString("0x1234");
|
row.setRawValueString("0x1234");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,21 @@ public class PatchStep implements Step {
|
||||||
protected String sleigh;
|
protected String sleigh;
|
||||||
protected int hashCode;
|
protected int hashCode;
|
||||||
|
|
||||||
public static String generateSleigh(Language language, Address address, byte[] data,
|
/**
|
||||||
|
* Generate a single line of Sleigh
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Note that when length is greater than 8, this will generate constants which are too large for
|
||||||
|
* the Java implementation of Sleigh. Use {@link #generateSleigh(Language, Address, byte[])}
|
||||||
|
* instead to write the variable in chunks.
|
||||||
|
*
|
||||||
|
* @param language the target language
|
||||||
|
* @param address the (start) address of the variable
|
||||||
|
* @param data the bytes to write to the variable
|
||||||
|
* @param length the length of the variable
|
||||||
|
* @return the Sleigh code
|
||||||
|
*/
|
||||||
|
public static String generateSleighLine(Language language, Address address, byte[] data,
|
||||||
int length) {
|
int length) {
|
||||||
BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false);
|
BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false);
|
||||||
if (address.isMemoryAddress()) {
|
if (address.isMemoryAddress()) {
|
||||||
|
@ -67,8 +81,34 @@ public class PatchStep implements Step {
|
||||||
return String.format("%s=0x%s", register, value.toString(16));
|
return String.format("%s=0x%s", register, value.toString(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String generateSleigh(Language language, Address address, byte[] data) {
|
/**
|
||||||
return generateSleigh(language, address, data, data.length);
|
* Generate a single line of Sleigh
|
||||||
|
*
|
||||||
|
* @see #generateSleighLine(Language, Address, byte[], int)
|
||||||
|
*/
|
||||||
|
public static String generateSleighLine(Language language, Address address, byte[] data) {
|
||||||
|
return generateSleighLine(language, address, data, data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate multiple lines of Sleigh, all to set a single variable
|
||||||
|
*
|
||||||
|
* @param language the target language
|
||||||
|
* @param address the (start) address of the variable
|
||||||
|
* @param data the bytes to write to the variable
|
||||||
|
* @return the lines of Sleigh code
|
||||||
|
*/
|
||||||
|
public static List<String> generateSleigh(Language language, Address address, byte[] data) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
generateSleigh(result, language, address, data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void generateSleigh(List<String> result, Language language, Address address,
|
||||||
|
byte[] data) {
|
||||||
|
SemisparseByteArray array = new SemisparseByteArray(); // TODO: Seems heavy-handed
|
||||||
|
array.putData(address.getOffset(), data);
|
||||||
|
generateSleigh(result, language, address.getAddressSpace(), array);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static List<String> generateSleigh(Language language,
|
protected static List<String> generateSleigh(Language language,
|
||||||
|
@ -102,7 +142,7 @@ public class PatchStep implements Step {
|
||||||
Address min = chunk.getMinAddress();
|
Address min = chunk.getMinAddress();
|
||||||
int length = (int) chunk.getLength();
|
int length = (int) chunk.getLength();
|
||||||
array.getData(min.getOffset(), data, 0, length);
|
array.getData(min.getOffset(), data, 0, length);
|
||||||
result.add(generateSleigh(language, min, data, length));
|
result.add(generateSleighLine(language, min, data, length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -494,6 +494,7 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
/**
|
/**
|
||||||
* Returns the equivalent of executing this schedule then performing a given patch
|
* Returns the equivalent of executing this schedule then performing a given patch
|
||||||
*
|
*
|
||||||
|
* @param thread the thread context for the patch; cannot be null
|
||||||
* @param sleigh a single line of sleigh, excluding the terminating semicolon.
|
* @param sleigh a single line of sleigh, excluding the terminating semicolon.
|
||||||
* @return the resulting schedule
|
* @return the resulting schedule
|
||||||
*/
|
*/
|
||||||
|
@ -509,4 +510,27 @@ public class TraceSchedule implements Comparable<TraceSchedule> {
|
||||||
ticks.coalescePatches(thread.getTrace().getBaseLanguage());
|
ticks.coalescePatches(thread.getTrace().getBaseLanguage());
|
||||||
return new TraceSchedule(snap, ticks, new Sequence());
|
return new TraceSchedule(snap, ticks, new Sequence());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the equivalent of executing this schedule then performing the given patches
|
||||||
|
*
|
||||||
|
* @param thread the thread context for the patch; cannot be null
|
||||||
|
* @param sleigh the lines of sleigh, excluding the terminating semicolons.
|
||||||
|
* @return the resulting schedule
|
||||||
|
*/
|
||||||
|
public TraceSchedule patched(TraceThread thread, List<String> sleigh) {
|
||||||
|
if (!this.pSteps.isNop()) {
|
||||||
|
Sequence pTicks = this.pSteps.clone();
|
||||||
|
for (String line : sleigh) {
|
||||||
|
pTicks.advance(new PatchStep(thread.getKey(), line));
|
||||||
|
}
|
||||||
|
pTicks.coalescePatches(thread.getTrace().getBaseLanguage());
|
||||||
|
return new TraceSchedule(snap, steps.clone(), pTicks);
|
||||||
|
}
|
||||||
|
Sequence ticks = this.steps.clone();
|
||||||
|
for (String line : sleigh) {
|
||||||
|
ticks.advance(new PatchStep(thread.getKey(), line));
|
||||||
|
}
|
||||||
|
return new TraceSchedule(snap, ticks, new Sequence());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,14 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.trace.util;
|
package ghidra.trace.util;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
||||||
|
import ghidra.pcode.utils.Utils;
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.data.*;
|
import ghidra.program.model.data.*;
|
||||||
import ghidra.program.model.lang.Register;
|
import ghidra.program.model.lang.Register;
|
||||||
|
@ -122,6 +124,23 @@ public enum TraceRegisterUtils {
|
||||||
return addr.toString();
|
return addr.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RegisterValue encodeValueRepresentationHackPointer(Register register,
|
||||||
|
TraceData data, String representation) throws DataTypeEncodeException {
|
||||||
|
DataType dataType = data.getBaseDataType();
|
||||||
|
if (data.getValueClass() != Address.class) {
|
||||||
|
byte[] bytes =
|
||||||
|
dataType.encodeRepresentation(representation, data, data, data.getLength());
|
||||||
|
BigInteger value = Utils.bytesToBigInteger(bytes, register.getMinimumByteSize(),
|
||||||
|
register.isBigEndian(), false);
|
||||||
|
return new RegisterValue(register, value);
|
||||||
|
}
|
||||||
|
Address addr = data.getTrace().getBaseAddressFactory().getAddress(representation);
|
||||||
|
if (addr == null) {
|
||||||
|
throw new DataTypeEncodeException("Invalid address", representation, dataType);
|
||||||
|
}
|
||||||
|
return new RegisterValue(register, addr.getOffsetAsBigInteger());
|
||||||
|
}
|
||||||
|
|
||||||
public static RegisterValue combineWithTraceBaseRegisterValue(RegisterValue rv, long snap,
|
public static RegisterValue combineWithTraceBaseRegisterValue(RegisterValue rv, long snap,
|
||||||
TraceMemoryRegisterSpace regs, boolean requireKnown) {
|
TraceMemoryRegisterSpace regs, boolean requireKnown) {
|
||||||
Register reg = rv.getRegister();
|
Register reg = rv.getRegister();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue