Merge remote-tracking branch 'origin/patch'

This commit is contained in:
Ryan Kurtz
2021-09-07 11:18:55 -04:00
25 changed files with 1168 additions and 618 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

@@ -941,10 +941,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

@@ -605,7 +605,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 -> {
})
@@ -759,12 +759,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"));
}
}

View File

@@ -26,7 +26,8 @@ public interface TraceDefinedDataRegisterView
extends TraceDefinedDataView, TraceBaseDefinedRegisterView<TraceData> {
default TraceData create(Range<Long> lifespan, Register register, DataType dataType)
throws CodeUnitInsertionException {
TraceRegisterUtils.requireByteBound(register);
return create(lifespan, register.getAddress(), dataType,
TraceRegisterUtils.byteLengthOf(register));
register.getNumBytes());
}
}

View File

@@ -54,13 +54,50 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
return getStates(snap, TraceRegisterUtils.rangeForRegister(register));
}
/**
* Set the value of a register at the given snap
*
* <p>
* <b>IMPORTANT:</b> The trace database cannot track the state ({@link TraceMemoryState#KNOWN},
* etc.) with per-bit accuracy. It only has byte precision. If the given value specifies, e.g.,
* only a single bit, then the entire byte will become marked {@link TraceMemoryState#KNOWN},
* even though the remaining 7 bits could technically be unknown.
*
* @param snap the snap
* @param value the register value
* @return the number of bytes written
*/
default int setValue(long snap, RegisterValue value) {
ByteBuffer buf = TraceRegisterUtils.bufferForValue(value);
return putBytes(snap, value.getRegister().getAddress(), buf);
if (!value.hasAnyValue()) {
return 0;
}
Register reg = value.getRegister();
if (!value.hasValue() || !TraceRegisterUtils.isByteBound(reg)) {
RegisterValue old = getValue(snap, reg.getBaseRegister());
// Do not use .getRegisterValue, as that will zero unmasked bits
// Instead, we'll pass the original register to bufferForValue
value = old.combineValues(value);
}
ByteBuffer buf = TraceRegisterUtils.bufferForValue(reg, value);
return putBytes(snap, reg.getAddress(), buf);
}
/**
* Write bytes at the given snap and register address
*
* <p>
* Note that bit-masked registers are not properly heeded. If the caller wishes to preserve
* non-masked bits, it must first retrieve the current value and combine it with the desired
* value. The caller must also account for any bit shift in the passed buffer. Alternatively,
* consider {@link #setValue(long, RegisterValue)}.
*
* @param snap the snap
* @param register the register to modify
* @param buf the buffer of bytes to write
* @return the number of bytes written
*/
default int putBytes(long snap, Register register, ByteBuffer buf) {
int byteLength = TraceRegisterUtils.byteLengthOf(register);
int byteLength = register.getNumBytes();
int limit = buf.limit();
buf.limit(Math.min(limit, buf.position() + byteLength));
int result = putBytes(snap, register.getAddress(), buf);
@@ -79,7 +116,7 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
}
default int getBytes(long snap, Register register, ByteBuffer buf) {
int byteLength = TraceRegisterUtils.byteLengthOf(register);
int byteLength = register.getNumBytes();
int limit = buf.limit();
buf.limit(Math.min(limit, buf.position() + byteLength));
int result = getBytes(snap, register.getAddress(), buf);
@@ -87,8 +124,21 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
return result;
}
/**
* Remove a value from the given time and register
*
* <p>
* <b>IMPORANT:</b> The trace database cannot track the state ({@link TraceMemoryState#KNOWN},
* etc.) with per-bit accuracy. It only has byte precision. If the given register specifies,
* e.g., only a single bit, then the entire byte will become marked
* {@link TraceMemoryState#UNKNOWN}, even though the remaining 7 bits could technically be
* known.
*
* @param snap the snap
* @param register the register
*/
default void removeValue(long snap, Register register) {
int byteLength = TraceRegisterUtils.byteLengthOf(register);
int byteLength = register.getNumBytes();
removeBytes(snap, register.getAddress(), byteLength);
}

View File

@@ -34,16 +34,7 @@ public enum TraceRegisterUtils {
public static AddressRange rangeForRegister(Register register) {
Address address = register.getAddress();
return new AddressRangeImpl(address, address.add(register.getMinimumByteSize() - 1));
}
public static int byteLengthOf(Register register) {
int bitLength = register.getBitLength();
if ((bitLength & 7) != 0) {
throw new IllegalArgumentException(
"Cannot work with sub-byte boundaries. Consider using the base register.");
}
return bitLength >> 3;
return new AddressRangeImpl(address, address.add(register.getNumBytes() - 1));
}
public static byte[] padOrTruncate(byte[] arr, int length) {
@@ -58,33 +49,22 @@ public enum TraceRegisterUtils {
return Arrays.copyOfRange(arr, arr.length - length, arr.length);
}
public static ByteBuffer bufferForValue(RegisterValue value) {
byte[] arr = value.getSignedValue().toByteArray();
int byteLength = byteLengthOf(value.getRegister());
arr = padOrTruncate(arr, byteLength);
if (!value.getRegister().isBigEndian()) {
ArrayUtils.reverse(arr);
public static ByteBuffer bufferForValue(Register reg, RegisterValue value) {
byte[] bytes = value.toBytes().clone();
int start = bytes.length / 2;
// NB: I guess contextreg is always big?
if (!reg.isBigEndian() && !reg.isProcessorContext()) {
ArrayUtils.reverse(bytes, start, bytes.length);
}
return ByteBuffer.wrap(arr);
}
public static int computeMaskOffset(byte[] arr) {
for (int i = 0; i < arr.length; i++) {
switch (arr[i]) {
case -1:
return i;
case 0:
continue;
default:
throw new IllegalArgumentException(
"Can only handle sub-registers on byte boundaries");
}
}
throw new IllegalArgumentException("No value");
int offset = TraceRegisterUtils.computeMaskOffset(reg);
return ByteBuffer.wrap(bytes, start + offset, reg.getNumBytes());
}
public static int computeMaskOffset(Register reg) {
return computeMaskOffset(reg.getBaseMask());
if (reg.isBaseRegister()) {
return 0;
}
return reg.getOffset() - reg.getBaseRegister().getOffset();
}
public static int computeMaskOffset(RegisterValue value) {
@@ -162,21 +142,38 @@ public enum TraceRegisterUtils {
return regs.getValue(snap, reg.getBaseRegister()).combineValues(rv);
}
public static RegisterValue getRegisterValue(Register register,
public static RegisterValue getRegisterValue(Register reg,
BiConsumer<Address, ByteBuffer> readAction) {
int byteLength = TraceRegisterUtils.byteLengthOf(register);
byte[] mask = register.getBaseMask();
/*
* The byte array for reg values spans the whole base register, but we'd like to avoid
* over-reading, so we'll zero in on the bytes actually included in the mask. We'll then
* have to handle endianness and such. The regval instance should then apply the actual mask
* for the sub-register, if applicable.
*/
int byteLength = reg.getNumBytes();
byte[] mask = reg.getBaseMask();
ByteBuffer buf = ByteBuffer.allocate(mask.length * 2);
buf.put(mask);
int maskOffset = TraceRegisterUtils.computeMaskOffset(mask);
int maskOffset = TraceRegisterUtils.computeMaskOffset(reg);
int startVal = buf.position() + maskOffset;
buf.position(startVal);
buf.limit(buf.position() + byteLength);
readAction.accept(register.getAddress(), buf);
readAction.accept(reg.getAddress(), buf);
byte[] arr = buf.array();
if (!register.isBigEndian()) {
ArrayUtils.reverse(arr, startVal, startVal + byteLength);
if (!reg.isBigEndian() && !reg.isProcessorContext()) {
ArrayUtils.reverse(arr, mask.length, buf.capacity());
}
return new RegisterValue(reg, arr);
}
public static boolean isByteBound(Register register) {
return register.getLeastSignificantBit() % 8 == 0 && register.getBitLength() % 8 == 0;
}
public static void requireByteBound(Register register) {
if (!isByteBound(register)) {
throw new IllegalArgumentException(
"Cannot work with sub-byte registers. Consider a parent, instead.");
}
return new RegisterValue(register, arr);
}
}

View File

@@ -49,6 +49,8 @@ import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.symbol.DBTraceReference;
import ghidra.trace.database.thread.DBTraceThread;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.language.TraceGuestLanguage;
import ghidra.util.Msg;
import ghidra.util.database.DBOpenMode;
@@ -94,6 +96,10 @@ public class ToyDBTraceBuilder implements AutoCloseable {
return new AddressRangeImpl(addr(start), addr(end));
}
public TraceAddressSnapRange srange(long snap, long start, long end) {
return new ImmutableTraceAddressSnapRange(addr(start), addr(end), snap, snap);
}
public AddressRange drng(long start, long end) {
return new AddressRangeImpl(data(start), data(end));
}

View File

@@ -15,11 +15,20 @@
*/
package ghidra.trace.database.memory;
import org.junit.Test;
import ghidra.program.model.lang.LanguageID;
import ghidra.trace.util.LanguageTestWatcher.TestLanguage;
public class DBTraceMemoryManagerBETest extends AbstractDBTraceMemoryManagerTest {
@Override
protected LanguageID getLanguageID() {
return new LanguageID("Toy:BE:64:default");
}
@TestLanguage("Toy:BE:32:builder")
@Test
public void testRegisterBits() throws Exception {
runTestRegisterBits();
}
}

View File

@@ -15,11 +15,20 @@
*/
package ghidra.trace.database.memory;
import org.junit.Test;
import ghidra.program.model.lang.LanguageID;
import ghidra.trace.util.LanguageTestWatcher.TestLanguage;
public class DBTraceMemoryManagerLETest extends AbstractDBTraceMemoryManagerTest {
@Override
protected LanguageID getLanguageID() {
return new LanguageID("Toy:LE:64:default");
}
@TestLanguage("Toy:LE:32:builder")
@Test
public void testRegisterBits() throws Exception {
runTestRegisterBits();
}
}

View File

@@ -19,12 +19,9 @@ import static org.junit.Assert.assertEquals;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.*;
import java.util.Set;
import org.junit.*;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import com.google.common.collect.Range;
@@ -45,6 +42,8 @@ import ghidra.trace.database.memory.DBTraceMemoryManager;
import ghidra.trace.database.memory.DBTraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceOverlappedRegionException;
import ghidra.trace.util.LanguageTestWatcher;
import ghidra.trace.util.LanguageTestWatcher.TestLanguage;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.*;
import ghidra.util.task.ConsoleTaskMonitor;
@@ -54,35 +53,8 @@ public class DBTraceDisassemblerIntegrationTest extends AbstractGhidraHeadlessIn
protected ToyDBTraceBuilder b;
protected DBTraceVariableSnapProgramView view;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestLanguage {
String value();
}
public static class LanguageWatcher extends TestWatcher {
String language = ProgramBuilder._TOY64_BE;
@Override
protected void starting(Description description) {
language = computeLanguage(description);
}
private String computeLanguage(Description description) {
TestLanguage annot = description.getAnnotation(TestLanguage.class);
if (annot == null) {
return ProgramBuilder._TOY64_BE;
}
return annot.value();
}
public String getLanguage() {
return language;
}
}
@Rule
public LanguageWatcher testLanguage = new LanguageWatcher();
public LanguageTestWatcher testLanguage = new LanguageTestWatcher();
@Before
public void setUp() throws IOException {

View File

@@ -0,0 +1,55 @@
/* ###
* 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.trace.util;
import java.lang.annotation.*;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import ghidra.program.database.ProgramBuilder;
public class LanguageTestWatcher extends TestWatcher {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestLanguage {
String value();
}
protected String language;
public LanguageTestWatcher() {
this(ProgramBuilder._TOY64_BE);
}
public LanguageTestWatcher(String defaultLanguage) {
this.language = defaultLanguage;
}
@Override
protected void starting(Description description) {
TestLanguage annot = description.getAnnotation(TestLanguage.class);
if (annot == null) {
return;
}
language = annot.value();
}
public String getLanguage() {
return language;
}
}

View File

@@ -38,20 +38,15 @@ abstract class AbstractDwarfEHDecoder implements DwarfEHDecoder {
protected static SignedLeb128DataType SLEB_DATA_TYPE = SignedLeb128DataType.dataType;
protected static UnsignedLeb128DataType ULEB_DATA_TYPE = UnsignedLeb128DataType.dataType;
protected DwarfEHDataApplicationMode appMode = DwarfEHDataApplicationMode.DW_EH_PE_absptr;
protected final DwarfEHDataApplicationMode appMode;
protected final boolean isIndirect;
protected boolean isIndirect = false;
@Override
public void setApplicationMode(DwarfEHDataApplicationMode mode) {
public AbstractDwarfEHDecoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
if (mode == null) {
mode = DwarfEHDataApplicationMode.DW_EH_PE_absptr;
}
appMode = mode;
}
@Override
public void setIndirect(boolean isIndirect) {
this.isIndirect = isIndirect;
}
@@ -91,8 +86,8 @@ abstract class AbstractDwarfEHDecoder implements DwarfEHDecoder {
return base;
default:
throw new AddressTranslationException("Don't know how to make a " +
addr.getPointerSize() + "-byte pointer");
throw new AddressTranslationException(
"Don't know how to make a " + addr.getPointerSize() + "-byte pointer");
}
}
@@ -113,8 +108,8 @@ abstract class AbstractDwarfEHDecoder implements DwarfEHDecoder {
case 8:
return readQWord(buf);
default:
throw new AddressTranslationException("Don't know how to make a " + ptrSize +
"-byte pointer");
throw new AddressTranslationException(
"Don't know how to make a " + ptrSize + "-byte pointer");
}
}

View File

@@ -15,8 +15,7 @@
*/
package ghidra.app.plugin.exceptionhandlers.gcc;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import ghidra.app.plugin.exceptionhandlers.gcc.datatype.SignedLeb128DataType;
import ghidra.app.plugin.exceptionhandlers.gcc.datatype.UnsignedLeb128DataType;
@@ -33,46 +32,8 @@ import ghidra.program.model.scalar.Scalar;
*/
public class DwarfDecoderFactory {
private static final Map<DwarfEHDataDecodeFormat, DwarfEHDecoder> decoderMap = new HashMap<>();
static {
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_absptr, new DW_EH_PE_absptr_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_uleb128, new DW_EH_PE_uleb128_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_udata2, new DW_EH_PE_udata2_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_udata4, new DW_EH_PE_udata4_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_udata8, new DW_EH_PE_udata8_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_signed, new DW_EH_PE_signed_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_sleb128, new DW_EH_PE_sleb128_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_sdata2, new DW_EH_PE_sdata2_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_sdata4, new DW_EH_PE_sdata4_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_sdata8, new DW_EH_PE_sdata8_Decoder());
decoderMap.put(DwarfEHDataDecodeFormat.DW_EH_PE_omit, new DW_EH_PE_omit_Decoder());
}
/**
* Get the appropriate decoder for the given decode format and application mode
* @param decodeFormat the exception handling data decoding format
* @param appFormat the desired application mode
* @param isIndirect flag indicating whether or not the data is an indirect reference
* @return the decoder or null
*/
public static DwarfEHDecoder getDecoder(DwarfEHDataDecodeFormat decodeFormat,
DwarfEHDataApplicationMode appFormat, boolean isIndirect) {
DwarfEHDecoder decoder = decoderMap.get(decodeFormat);
if (decoder == null) {
return null;
}
decoder.setApplicationMode(appFormat);
decoder.setIndirect(isIndirect);
return decoder;
}
private static final ConcurrentHashMap<Integer, DwarfEHDecoder> decoderMap =
new ConcurrentHashMap<>();
/**
* Get the appropriate decoder for the given 8-bit mode; mode is parsed into
@@ -83,24 +44,66 @@ public class DwarfDecoderFactory {
*/
public static DwarfEHDecoder getDecoder(int mode) {
DwarfEHDecoder dwarfEHDecoder = decoderMap.get(mode);
if (dwarfEHDecoder != null) {
return dwarfEHDecoder;
}
int format = mode & 0x0F;
int appl = mode & 0x70;
boolean isIndirect = (mode & 0x80) == 0x80;
if ((mode & 0xFF) == 0xFF) {
DwarfEHDecoder decoder = decoderMap.get(DwarfEHDataDecodeFormat.DW_EH_PE_omit);
decoder.setApplicationMode(DwarfEHDataApplicationMode.DW_EH_PE_omit);
return decoder;
}
DwarfEHDataDecodeFormat style = DwarfEHDataDecodeFormat.valueOf(format);
DwarfEHDataApplicationMode mod = DwarfEHDataApplicationMode.valueOf(appl);
return getDecoder(style, mod, isIndirect);
if ((mode & 0xFF) == 0xFF) {
dwarfEHDecoder =
new DW_EH_PE_omit_Decoder(DwarfEHDataApplicationMode.DW_EH_PE_omit, false);
}
else {
dwarfEHDecoder = createDecoder(style, mod, isIndirect);
}
decoderMap.put(mode, dwarfEHDecoder);
return dwarfEHDecoder;
}
private static DwarfEHDecoder createDecoder(DwarfEHDataDecodeFormat style,
DwarfEHDataApplicationMode mod, boolean isIndirect) {
switch (style) {
case DW_EH_PE_absptr:
return new DW_EH_PE_absptr_Decoder(mod, isIndirect);
case DW_EH_PE_uleb128:
return new DW_EH_PE_uleb128_Decoder(mod, isIndirect);
case DW_EH_PE_udata2:
return new DW_EH_PE_udata2_Decoder(mod, isIndirect);
case DW_EH_PE_udata4:
return new DW_EH_PE_udata4_Decoder(mod, isIndirect);
case DW_EH_PE_udata8:
return new DW_EH_PE_udata8_Decoder(mod, isIndirect);
case DW_EH_PE_signed:
return new DW_EH_PE_signed_Decoder(mod, isIndirect);
case DW_EH_PE_sleb128:
return new DW_EH_PE_sleb128_Decoder(mod, isIndirect);
case DW_EH_PE_sdata2:
return new DW_EH_PE_sdata2_Decoder(mod, isIndirect);
case DW_EH_PE_sdata4:
return new DW_EH_PE_sdata4_Decoder(mod, isIndirect);
case DW_EH_PE_sdata8:
return new DW_EH_PE_sdata8_Decoder(mod, isIndirect);
case DW_EH_PE_omit:
default:
return new DW_EH_PE_omit_Decoder(mod, isIndirect);
}
}
private static abstract class AbstractSignedDwarEHfDecoder extends AbstractDwarfEHDecoder {
public AbstractSignedDwarEHfDecoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public boolean isSigned() {
return true;
@@ -109,6 +112,10 @@ public class DwarfDecoderFactory {
private static abstract class AbstractUnsignedDwarfEHDecoder extends AbstractDwarfEHDecoder {
public AbstractUnsignedDwarfEHDecoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public boolean isSigned() {
return false;
@@ -117,6 +124,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_absptr_Decoder extends AbstractUnsignedDwarfEHDecoder {
public DW_EH_PE_absptr_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_absptr;
@@ -174,6 +185,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_omit_Decoder extends AbstractUnsignedDwarfEHDecoder {
public DW_EH_PE_omit_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_omit;
@@ -200,6 +215,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_uleb128_Decoder extends AbstractUnsignedDwarfEHDecoder {
public DW_EH_PE_uleb128_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_uleb128;
@@ -238,6 +257,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_udata2_Decoder extends AbstractUnsignedDwarfEHDecoder {
public DW_EH_PE_udata2_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_udata2;
@@ -268,6 +291,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_udata4_Decoder extends AbstractUnsignedDwarfEHDecoder {
public DW_EH_PE_udata4_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_udata4;
@@ -298,6 +325,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_udata8_Decoder extends AbstractUnsignedDwarfEHDecoder {
public DW_EH_PE_udata8_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_udata8;
@@ -328,6 +359,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_signed_Decoder extends AbstractSignedDwarEHfDecoder {
public DW_EH_PE_signed_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_signed;
@@ -352,6 +387,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_sleb128_Decoder extends AbstractSignedDwarEHfDecoder {
public DW_EH_PE_sleb128_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_sleb128;
@@ -389,6 +428,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_sdata2_Decoder extends AbstractSignedDwarEHfDecoder {
public DW_EH_PE_sdata2_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_sdata2;
@@ -419,6 +462,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_sdata4_Decoder extends AbstractSignedDwarEHfDecoder {
public DW_EH_PE_sdata4_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_sdata4;
@@ -449,6 +496,10 @@ public class DwarfDecoderFactory {
static final class DW_EH_PE_sdata8_Decoder extends AbstractSignedDwarEHfDecoder {
public DW_EH_PE_sdata8_Decoder(DwarfEHDataApplicationMode mode, boolean isIndirect) {
super(mode, isIndirect);
}
@Override
public DwarfEHDataDecodeFormat getDataFormat() {
return DwarfEHDataDecodeFormat.DW_EH_PE_sdata8;

View File

@@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -80,17 +79,4 @@ public interface DwarfEHDecoder {
* @return the data type.
*/
public DataType getDataType(Program program);
/**
* Sets the exception handling data application mode.
* @param mode the application mode
*/
public void setApplicationMode(DwarfEHDataApplicationMode mode);
/**
* Sets whether or not the data is an indirect reference.
* @param isIndirect true for an indirect reference.
*/
public void setIndirect(boolean isIndirect);
}

View File

@@ -67,19 +67,19 @@ public class FdeTable {
TaskMonitor monitor;
Program prog;
StructureDataType fdeTableEntry;
/**
* Constructor for an FDE table.
*
* @param monitor a status monitor for indicating progress or allowing a task to be cancelled.
* @param curProg the program containing the FDE table.
*/
public FdeTable( TaskMonitor monitor, Program curProg ) {
public FdeTable(TaskMonitor monitor, Program curProg) {
this.monitor = monitor;
this.prog = curProg;
this.fdeTableEntry = new StructureDataType("fde_table_entry", 0);
}
private void initFdeTableDataType(DwarfEHDecoder decoder) throws ExceptionHandlerFrameException {
DataType encodedDt = decoder.getDataType(prog);
@@ -101,11 +101,11 @@ public class FdeTable {
fdeTableEntry.add(encodedDt, "initial_loc", "Initial Location");
fdeTableEntry.add(encodedDt, "data_loc", "Data location");
DataTypeManager dtManager = prog.getDataTypeManager();
dtManager.addDataType(fdeTableEntry, DataTypeConflictHandler.REPLACE_HANDLER );
dtManager.addDataType(fdeTableEntry, DataTypeConflictHandler.REPLACE_HANDLER);
}
/**
* Creates an FDE Table at the specified Address.
*
@@ -119,13 +119,13 @@ public class FdeTable {
throws MemoryAccessException, ExceptionHandlerFrameException {
CreateStructureCmd dataCmd = null;
long curFdeTableCnt = 0;
if (addr == null || decoder == null || monitor.isCancelled()) {
if (addr == null || decoder == null) {
return;
}
initFdeTableDataType(decoder);
monitor.setMessage("Creating Frame Description Table Entries");
monitor.setShowProgressValue(true);
monitor.setIndeterminate(false);
@@ -134,13 +134,17 @@ public class FdeTable {
/* Create a new FDE structures beginning at startAddress */
MemoryBlock curMemBlock = prog.getMemory().getBlock(".eh_frame_hdr");
while( curMemBlock != null &&
(addr.compareTo( curMemBlock.getEnd()) < 0) &&
(curFdeTableCnt < fdeTableCnt) )
(addr.compareTo( curMemBlock.getEnd()) < 0) &&
(curFdeTableCnt < fdeTableCnt) )
{
if (monitor.isCancelled()) {
return;
}
/* Create a new FDE structure */
dataCmd = new CreateStructureCmd( fdeTableEntry, addr);
dataCmd = new CreateStructureCmd(fdeTableEntry, addr);
dataCmd.applyTo(prog);
/*
* -- Create references to the 'initial location' and 'data
* location' --
@@ -159,7 +163,7 @@ public class FdeTable {
// TODO: This should be a CODE flow, leaving as INDIRECTION until refactor
prog.getReferenceManager().addMemoryReference(locComponentAddr, locAddr,
RefType.INDIRECTION,
SourceType.ANALYSIS, 0);
SourceType.ANALYSIS, 0);
DataTypeComponent dataComponent = fdeStruct.getComponent(1);
Address dataComponentAddr = addr.add(dataComponent.getOffset());
@@ -168,7 +172,7 @@ public class FdeTable {
Address dataAddr = decoder.decodeAddress(dataDecodeContext);
prog.getReferenceManager().addMemoryReference(dataComponentAddr, dataAddr, RefType.DATA,
SourceType.ANALYSIS, 0);
SourceType.ANALYSIS, 0);
/* Increment curAddress by number of bytes in a FDE Table entry */
curFdeTableCnt++;

View File

@@ -21,11 +21,10 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
/**
* Class to represent a processor register. To sort of handle bit registers, a
* special addressing convention is used. First the upper bit is set. Second, the
* next 3 bits are used to specify what bit position within a byte that this register
* bit exists at. Finally, the rest of the address is the address of the byte where
* the register bit lives.
* Class to represent a processor register. To sort of handle bit registers, a special addressing
* convention is used. First the upper bit is set. Second, the next 3 bits are used to specify what
* bit position within a byte that this register bit exists at. Finally, the rest of the address is
* the address of the byte where the register bit lives.
*/
public class Register implements java.io.Serializable, Comparable<Register> {
@@ -61,7 +60,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
private Register baseRegister;
private String group;
/**Set of valid lane sizes**/
/** Set of valid lane sizes **/
private TreeSet<Integer> laneSizes;
/**
@@ -72,10 +71,10 @@ public class Register implements java.io.Serializable, Comparable<Register> {
* @param address the address in register space of this register
* @param numBytes the size (in bytes) of this register
* @param bigEndian true if the most significant bytes are associated with the lowest register
* addresses, and false if the least significant bytes are associated with the lowest register
* addresses.
* @param typeFlags the type(s) of this Register (TYPE_NONE, TYPE_FP, TYPE_SP,
* TYPE_PC, TYPE_CONTEXT, TYPE_ZERO);)
* addresses, and false if the least significant bytes are associated with the lowest
* register addresses.
* @param typeFlags the type(s) of this Register (TYPE_NONE, TYPE_FP, TYPE_SP, TYPE_PC,
* TYPE_CONTEXT, TYPE_ZERO);)
*/
public Register(String name, String description, Address address, int numBytes,
boolean bigEndian, int typeFlags) {
@@ -128,6 +127,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/**
* Add register alias
*
* @param aliasReg
*/
void addAlias(String alias) {
@@ -142,6 +142,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/**
* Remove register alias
*
* @param alias
*/
void removeAlias(String alias) {
@@ -151,9 +152,8 @@ public class Register implements java.io.Serializable, Comparable<Register> {
}
/**
* Return register aliases.
* NOTE: This is generally only supported for
* context register fields.
* Return register aliases. NOTE: This is generally only supported for context register fields.
*
* @return register aliases or null
*/
public Iterable<String> getAliases() {
@@ -201,6 +201,19 @@ public class Register implements java.io.Serializable, Comparable<Register> {
return (bitLength + 7) / 8;
}
/**
* Returns the number of bytes spanned by this Register.
*
* <p>
* Compare to {{@link #getMinimumByteSize()}: Suppose a 5-bit register spans 2 bytes: 1 bit in
* the first byte, and the remaining 4 in the following byte. Its value can still be stored in 1
* byte, which is what {@link #getMinimumByteSize()} returns; however, its storage still spans 2
* bytes of the base register, which is what this method returns.
*/
public int getNumBytes() {
return numBytes;
}
/**
* Returns the offset into the register space for this register
*/
@@ -210,6 +223,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/**
* Returns the bit offset from the register address for this register.
*
* @return the bit offset from the register address for this register.
*/
public int getLeastSignificantBit() {
@@ -224,8 +238,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
}
/**
* Returns true for a register whose context value should
* follow the disassembly flow.
* Returns true for a register whose context value should follow the disassembly flow.
*/
public boolean followsFlow() {
return (typeFlags & TYPE_DOES_NOT_FOLLOW_FLOW) == 0;
@@ -335,8 +348,8 @@ public class Register implements java.io.Serializable, Comparable<Register> {
}
/**
* Returns list of children registers sorted by
* lest-significant bit-offset within this register.
* Returns list of children registers sorted by lest-significant bit-offset within this
* register.
*/
public List<Register> getChildRegisters() {
return new ArrayList<>(childRegisters);
@@ -400,6 +413,7 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/**
* Returns the mask that indicates which bits in the base register apply to this register.
*
* @return the mask that indicates which bits in the base register apply to this register
*/
public byte[] getBaseMask() {
@@ -445,11 +459,11 @@ public class Register implements java.io.Serializable, Comparable<Register> {
}
/**
* Determines if reg is contained within this register.
* Method does not work for bit registers (e.g., context-bits)
* Determines if reg is contained within this register. Method does not work for bit registers
* (e.g., context-bits)
*
* @param reg another register
* @return true if reg equals this register or is contained
* within it.
* @return true if reg equals this register or is contained within it.
*/
public boolean contains(Register reg) {
if (equals(reg)) {
@@ -472,8 +486,9 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/**
* Returns true if this is a vector register
* @return true precisely when {@code this} is a full vector register (i.e., a register that can be
* used as input or output for a SIMD operation).
*
* @return true precisely when {@code this} is a full vector register (i.e., a register that can
* be used as input or output for a SIMD operation).
*/
public boolean isVectorRegister() {
return (typeFlags & TYPE_VECTOR) != 0;
@@ -481,8 +496,10 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/**
* Determines whether {@code laneSizeInBytes} is a valid lane size for this register.
*
* @param laneSizeInBytes lane size to check, measured in bytes
* @return true precisely when {@code this} is a vector register and {@code laneSizeInBytes} is a valid lane size.
* @return true precisely when {@code this} is a vector register and {@code laneSizeInBytes} is
* a valid lane size.
*/
public boolean isValidLaneSize(int laneSizeInBytes) {
if (!isVectorRegister()) {
@@ -496,7 +513,9 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/**
* Returns the sorted array of lane sizes for this register, measured in bytes.
* @return array of lane sizes, or {@code null} if {@code this} is not a vector register or no lane sizes have been set.
*
* @return array of lane sizes, or {@code null} if {@code this} is not a vector register or no
* lane sizes have been set.
*/
public int[] getLaneSizes() {
if (laneSizes == null) {
@@ -512,9 +531,10 @@ public class Register implements java.io.Serializable, Comparable<Register> {
/**
* Adds a lane size.
*
* @param laneSizeInBytes lane size to add
* @throws UnsupportedOperationException if register is unable to support the definition of
* lanes.
* @throws UnsupportedOperationException if register is unable to support the definition of
* lanes.
* @throws IllegalArgumentException if {@code laneSizeInBytes} is invalid
*/
void addLaneSize(int laneSizeInBytes) {