From dbe670bf85edd9c38608ade2817cac0ac4143d10 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 6 May 2022 15:08:09 -0400 Subject: [PATCH] GP-1881: Implement editable Repr column for Registers and Watches providers. --- .../DebuggerObjectsPlugin.html | 6 +- .../DebuggerRegistersPlugin.html | 3 +- .../DebuggerWatchesPlugin.html | 8 +- .../register/DebuggerRegistersProvider.java | 44 ++++++++-- .../core/debug/gui/register/RegisterRow.java | 8 +- .../gui/watch/DebuggerWatchesProvider.java | 6 +- .../plugin/core/debug/gui/watch/WatchRow.java | 41 ++++++++- .../DebuggerRegistersProviderTest.java | 48 ++++++++++ .../watch/DebuggerWatchesProviderTest.java | 87 ++++++++++++++++++- .../trace/model/time/schedule/PatchStep.java | 48 +++++++++- .../model/time/schedule/TraceSchedule.java | 24 +++++ .../ghidra/trace/util/TraceRegisterUtils.java | 19 ++++ 12 files changed, 314 insertions(+), 28 deletions(-) diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html index df1f77ae1c..e03a7277b2 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerObjectsPlugin/DebuggerObjectsPlugin.html @@ -325,9 +325,9 @@

Update While Running

-

By default, events are passed to the Objects Viewer even while the target is running. - The resulting changes in the GUI may be distracting for some. To disable updates to the - Objects Viewer, toggle "Updates While Running" off.

+

By default, events are passed to the Objects Viewer even while the target is running. The + resulting changes in the GUI may be distracting for some. To disable updates to the Objects + Viewer, toggle "Updates While Running" off.

Color Options

diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html index 0823dbf8e7..48f4d9ab59 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerRegistersPlugin/DebuggerRegistersPlugin.html @@ -58,7 +58,8 @@ time on."
  • 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.
  • + 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.

    Actions

    diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html index 6f87fc5f15..2e8a244983 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerWatchesPlugin/DebuggerWatchesPlugin.html @@ -78,10 +78,10 @@ trace. Clicking the Apply Data Type action will apply it to the current trace, if possible. -
  • Representation - the value of the watch as interpreted by the selected data type. This - field is not yet user modifiable, even if the Enable Edits toggle is on. If the value - is an address, i.e., Type is a pointer, then double-clicking this cell will navigate the - primary dynamic listing, if possible.
  • +
  • Representation - the value of the watch as interpreted by the selected data type. If the + value is an address, i.e., Type is a pointer, then double-clicking this cell will navigate + the primary dynamic listing, if possible. This field is user-modifiable whenever the Value + column is modifiable and the selected data type provides an encoder.
  • 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 diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java index 1f0a5500ea..4fda299f8d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java @@ -62,8 +62,7 @@ import ghidra.framework.plugintool.AutoService; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.program.model.address.*; -import ghidra.program.model.data.DataType; -import ghidra.program.model.data.DataTypeConflictException; +import ghidra.program.model.data.*; import ghidra.program.model.lang.*; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.trace.model.*; @@ -89,12 +88,16 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter protected enum RegisterTableColumns implements EnumeratedTableColumn { - 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), NAME("Name", String.class, RegisterRow::getName), - VALUE("Value", BigInteger.class, RegisterRow::getValue, RegisterRow::setValue, RegisterRow::isValueEditable, SortDirection.ASCENDING), - TYPE("Type", DataType.class, RegisterRow::getDataType, RegisterRow::setDataType, r -> true, SortDirection.ASCENDING), - REPR("Repr", String.class, RegisterRow::getRepresentation); + VALUE("Value", BigInteger.class, RegisterRow::getValue, RegisterRow::setValue, // + RegisterRow::isValueEditable, SortDirection.ASCENDING), + 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 Function getter; @@ -859,6 +862,35 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter 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) { TraceData data = getRegisterData(register); if (data == null) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java index f68db9bc9c..8844502dbb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java @@ -91,7 +91,13 @@ public class RegisterRow { 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() { return provider.getRegisterValueRepresentation(register); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java index b55001c87c..d59bf50ab3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java @@ -86,9 +86,11 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter { protected enum WatchTableColumns implements EnumeratedTableColumn { EXPRESSION("Expression", String.class, WatchRow::getExpression, WatchRow::setExpression), 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), - REPR("Repr", String.class, WatchRow::getValueString), + REPR("Repr", String.class, WatchRow::getValueString, WatchRow::setValueString, // + WatchRow::isValueEditable), ERROR("Error", String.class, WatchRow::getErrorMessage); private final String header; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java index e55da5b827..7aff305027 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java @@ -33,6 +33,7 @@ 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.data.DataTypeEncodeException; import ghidra.program.model.lang.Language; import ghidra.program.model.mem.ByteMemBufferImpl; import ghidra.program.model.mem.MemBuffer; @@ -143,6 +144,8 @@ public class WatchRow { return dataType.getRepresentation(buffer, SettingsImpl.NO_SETTINGS, value.length); } + // TODO: DataType settings + protected Object parseAsDataTypeObj() { if (dataType == null || value == null) { return null; @@ -373,7 +376,7 @@ public class WatchRow { return valueObj; } - public boolean isValueEditable() { + public boolean isRawValueEditable() { if (!provider.isEditsEnabled()) { return false; } @@ -423,8 +426,13 @@ public class WatchRow { 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"); + if (bytes.length > value.length) { + 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; 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() { return value == null ? 0 : value.length; } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java index 99cde57bda..8572ad4f41 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java @@ -19,6 +19,7 @@ import static ghidra.lifecycle.Unfinished.TODO; import static org.junit.Assert.*; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.util.*; import java.util.stream.Collectors; @@ -159,6 +160,11 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG row.setValue(new BigInteger(text, 16)); } + protected void setRowRepr(RegisterRow row, String repr) { + assertTrue(row.isRepresentationEditable()); + row.setRepresentation(repr); + } + protected void assertRowValueEmpty(RegisterRow row) { 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 @Test diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java index 0311c1b0cd..d15cb9f243 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java @@ -270,17 +270,17 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI WatchRow row = Unique.assertOne(watchesProvider.watchTableModel.getModelData()); row.setExpression(expression); - assertFalse(row.isValueEditable()); + assertFalse(row.isRawValueEditable()); traceManager.openTrace(tb.trace); traceManager.activateThread(thread); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR); waitForSwing(); assertNoErr(row); - assertFalse(row.isValueEditable()); + assertFalse(row.isRawValueEditable()); performAction(watchesProvider.actionEnableEdits); - assertEquals(expectWritable, row.isValueEditable()); + assertEquals(expectWritable, row.isRawValueEditable()); } @Test @@ -314,6 +314,12 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI return row; } + long encodeDouble(double value) { + ByteBuffer buf = ByteBuffer.allocate(Double.BYTES); + buf.putDouble(0, value); + return buf.getLong(0); + } + @Test public void testEditRegisterEmu() { 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 public void testEditMemoryEmu() { WatchRow row = prepareTestEditEmu("*:8 r0"); + TraceMemoryOperations mem = tb.trace.getMemoryManager(); 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 { createTestModel(); mb.createTestProcessesAndThreads(); @@ -422,7 +501,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI // Sanity check assertFalse(recorder.isRegisterOnTarget(thread, r1)); - assertFalse(row.isValueEditable()); + assertFalse(row.isRawValueEditable()); row.setRawValueString("0x1234"); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java index f69f3e542e..894b708c6e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java @@ -44,7 +44,21 @@ public class PatchStep implements Step { protected String sleigh; protected int hashCode; - public static String generateSleigh(Language language, Address address, byte[] data, + /** + * Generate a single line of Sleigh + * + *

    + * 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) { BigInteger value = Utils.bytesToBigInteger(data, length, language.isBigEndian(), false); if (address.isMemoryAddress()) { @@ -67,8 +81,34 @@ public class PatchStep implements Step { 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 generateSleigh(Language language, Address address, byte[] data) { + List result = new ArrayList<>(); + generateSleigh(result, language, address, data); + return result; + } + + protected static void generateSleigh(List 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 generateSleigh(Language language, @@ -102,7 +142,7 @@ public class PatchStep implements Step { Address min = chunk.getMinAddress(); int length = (int) chunk.getLength(); array.getData(min.getOffset(), data, 0, length); - result.add(generateSleigh(language, min, data, length)); + result.add(generateSleighLine(language, min, data, length)); } } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java index 80b3601112..53ae0ddf0d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java @@ -494,6 +494,7 @@ public class TraceSchedule implements Comparable { /** * 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. * @return the resulting schedule */ @@ -509,4 +510,27 @@ public class TraceSchedule implements Comparable { ticks.coalescePatches(thread.getTrace().getBaseLanguage()); 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 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()); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java index 7724c45cf7..10367c5d3b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java @@ -15,12 +15,14 @@ */ package ghidra.trace.util; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.function.BiConsumer; import org.apache.commons.lang3.ArrayUtils; +import ghidra.pcode.utils.Utils; import ghidra.program.model.address.*; import ghidra.program.model.data.*; import ghidra.program.model.lang.Register; @@ -122,6 +124,23 @@ public enum TraceRegisterUtils { 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, TraceMemoryRegisterSpace regs, boolean requireKnown) { Register reg = rv.getRegister();