GP-1256: Permit reading/writing of contextreg bit fields in traces.

This commit is contained in:
Dan 2021-09-07 11:02:30 -04:00
parent c1726c6e4f
commit ca3228b063
10 changed files with 661 additions and 473 deletions

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;
}
}