mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-01-10 06:27:59 -05:00
Merge remote-tracking branch 'origin/patch'
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -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>
|
||||
|
||||
|
||||
@@ -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 |
@@ -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";
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user