Merge remote-tracking branch

'origin/GP-1540_Dan_emulateHarvard--SQUASHED' into
Ghidra_10.1
This commit is contained in:
ghidra1 2021-11-30 19:16:25 -05:00
commit 0f0b63f6ad
27 changed files with 439 additions and 56 deletions

View file

@ -452,9 +452,8 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
while (!remains.isEmpty()) {
AddressRange range = remains.getFirstRange();
remains.delete(range);
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : stateMapSpace.reduce(
TraceAddressSnapRangeQuery.intersecting(range.getMinAddress(),
range.getMaxAddress(), snap, snap)).entries()) {
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : doGetStates(snap,
range)) {
AddressRange foundRange = entry.getKey().getRange();
remains.delete(foundRange);
if (predicate.test(entry.getValue())) {
@ -466,11 +465,21 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
}
}
protected Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> doGetStates(long snap,
AddressRange range) {
// TODO: A better way to handle memory-mapped registers?
if (getAddressSpace().isRegisterSpace() && !range.getAddressSpace().isRegisterSpace()) {
return trace.getMemoryManager().getStates(snap, range);
}
return stateMapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range.getMinAddress(),
range.getMaxAddress(), snap, snap)).entries();
}
@Override
public Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap,
AddressRange range) {
return stateMapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range.getMinAddress(),
range.getMaxAddress(), snap, snap)).entries();
assertInSpace(range);
return doGetStates(snap, range);
}
@Override

View file

@ -29,6 +29,7 @@ import ghidra.util.UnionAddressSetView;
* @param <T> the type of units in the view
*/
public interface TraceBaseCodeUnitsView<T extends TraceCodeUnit> {
/**
* Get the total number of <em>defined</em> units in this view
*

View file

@ -32,6 +32,7 @@ public interface TraceBaseDefinedUnitsView<T extends TraceCodeUnit>
/**
* Clear the units contained within the given span and address range.
*
* <p>
* Any units alive before the given span are truncated instead of deleted. That is, their end
* snaps are reduced such that they no longer intersect the given span. Note that the same is
* not true of a unit's start snap. If the start snap is contained in the span, the unit is

View file

@ -20,14 +20,38 @@ import com.google.common.collect.Range;
import ghidra.program.model.data.DataType;
import ghidra.program.model.lang.Register;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.model.Trace;
import ghidra.trace.util.TraceRegisterUtils;
public interface TraceDefinedDataRegisterView
extends TraceDefinedDataView, TraceBaseDefinedRegisterView<TraceData> {
/**
* Create a data unit on the given register
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space. In those
* cases, the assignment affects all threads.
*
* @param lifespan the span for which the unit is effective
* @param register the register to assign a data type
* @param dataType the data type for the register
* @return the new data unit
* @throws CodeUnitInsertionException if there's a conflict
*/
default TraceData create(Range<Long> lifespan, Register register, DataType dataType)
throws CodeUnitInsertionException {
// TODO: A better way to handle memory-mapped registers?
Trace trace = getThread().getTrace();
if (register.getAddressSpace() != trace
.getBaseLanguage()
.getAddressFactory()
.getRegisterSpace()) {
return trace.getCodeManager()
.definedData()
.create(lifespan, register.getAddress(), dataType, register.getNumBytes());
}
TraceRegisterUtils.requireByteBound(register);
return create(lifespan, register.getAddress(), dataType,
register.getNumBytes());
return create(lifespan, register.getAddress(), dataType, register.getNumBytes());
}
}

View file

@ -19,6 +19,7 @@ import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.TraceAddressSnapRange;
@ -26,17 +27,54 @@ import ghidra.trace.model.listing.TraceCodeRegisterSpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceRegisterUtils;
/**
* A "memory" space for storing the raw bytes and values of registers for a thread.
*/
public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
/**
* Get the thread for this register space
*
* @return the thread
*/
TraceThread getThread();
/**
* Get the registers
*
* <p>
* This is essentially a convenience to {@code getTrace().getBaseLanguage().getRegisters()}
*
* @return the list of registers
*/
default List<Register> getRegisters() {
return getThread().getRegisters();
}
/**
* Set the state of a given register at a given time
*
* <p>
* Setting state to {@link TraceMemoryState#KNOWN} via this method is not recommended. Setting
* bytes will automatically update the state accordingly.
*
* @param snap the time
* @param register the register
* @param state the state
*/
default void setState(long snap, Register register, TraceMemoryState state) {
setState(snap, TraceRegisterUtils.rangeForRegister(register), state);
}
/**
* Assert that a register's range has a single state at the given snap and get that state
*
* @param snap the time
* @param register the register to examine
* @return the state
* @throws IllegalStateException if the register is mapped to more than one state. See
* {@link #getStates(long, Register)}
*/
default TraceMemoryState getState(long snap, Register register) {
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> states =
getStates(snap, register);
@ -49,15 +87,33 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
return states.iterator().next().getValue();
}
/**
* Break the register's range into smaller ranges each mapped to its state at the given snap
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
* @param snap the time
* @param register the register to examine
* @return the map of ranges to states
*/
default Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap,
Register register) {
return getStates(snap, TraceRegisterUtils.rangeForRegister(register));
AddressRange range = TraceRegisterUtils.rangeForRegister(register);
if (register.getAddressSpace() != getAddressSpace()) {
return getTrace().getMemoryManager().getStates(snap, range);
}
return getStates(snap, range);
}
/**
* Set the value of a register at the given snap
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space. In those
* cases, the assignment affects all threads.
*
* <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},
@ -79,6 +135,10 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
value = old.combineValues(value);
}
ByteBuffer buf = TraceRegisterUtils.bufferForValue(reg, value);
// TODO: A better way to deal with memory-mapped registers?
if (reg.getAddressSpace() != getAddressSpace()) {
return getTrace().getMemoryManager().putBytes(snap, reg.getAddress(), buf);
}
return putBytes(snap, reg.getAddress(), buf);
}
@ -86,6 +146,10 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
* Write bytes at the given snap and register address
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space. In those
* cases, the assignment affects all threads.
*
* <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,
@ -100,26 +164,85 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
int byteLength = register.getNumBytes();
int limit = buf.limit();
buf.limit(Math.min(limit, buf.position() + byteLength));
int result = putBytes(snap, register.getAddress(), buf);
// TODO: A better way to deal with memory-mapped registers?
int result;
if (register.getAddressSpace() != getAddressSpace()) {
result = getTrace().getMemoryManager().putBytes(snap, register.getAddress(), buf);
}
else {
result = putBytes(snap, register.getAddress(), buf);
}
buf.limit(limit);
return result;
}
/**
* Get the most-recent value of a given register at the given time
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
* @param snap the time
* @param register the register
* @return the value
*/
default RegisterValue getValue(long snap, Register register) {
return TraceRegisterUtils.getRegisterValue(register,
(a, buf) -> getBytes(snap, a, buf));
return TraceRegisterUtils.getRegisterValue(register, (a, buf) -> {
// TODO: A better way to deal with memory-mapped registers?
if (a.getAddressSpace() != getAddressSpace()) {
getTrace().getMemoryManager().getBytes(snap, a, buf);
}
else {
getBytes(snap, a, buf);
}
});
}
/**
* Get the most-recent value of a given register at the given time, following schedule forks
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
* @param snap the time
* @param register the register
* @return the value
*/
default RegisterValue getViewValue(long snap, Register register) {
return TraceRegisterUtils.getRegisterValue(register,
(a, buf) -> getViewBytes(snap, a, buf));
return TraceRegisterUtils.getRegisterValue(register, (a, buf) -> {
// TODO: A better way to deal with memory-mapped registers?
if (a.getAddressSpace() != getAddressSpace()) {
getTrace().getMemoryManager().getViewBytes(snap, a, buf);
}
else {
getViewBytes(snap, a, buf);
}
});
}
/**
* Get the most-recent bytes of a given register at the given time
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
* @param snap the time
* @param register the register
* @param buf the destination buffer
* @return the number of bytes read
*/
default int getBytes(long snap, Register register, ByteBuffer buf) {
int byteLength = register.getNumBytes();
int limit = buf.limit();
buf.limit(Math.min(limit, buf.position() + byteLength));
int result = getBytes(snap, register.getAddress(), buf);
// TODO: A better way to deal with memory-mapped registers?
int result;
if (register.getAddressSpace() != getAddressSpace()) {
result = getTrace().getMemoryManager().getBytes(snap, register.getAddress(), buf);
}
else {
result = getBytes(snap, register.getAddress(), buf);
}
buf.limit(limit);
return result;
}
@ -128,6 +251,9 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
* Remove a value from the given time and register
*
* <p>
* If the register is memory mapped, this will delegate to the appropriate space.
*
* <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
@ -139,9 +265,20 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
*/
default void removeValue(long snap, Register register) {
int byteLength = register.getNumBytes();
removeBytes(snap, register.getAddress(), byteLength);
if (register.getAddressSpace() != getAddressSpace()) {
getTrace().getMemoryManager().removeBytes(snap, register.getAddress(), byteLength);
}
else {
removeBytes(snap, register.getAddress(), byteLength);
}
}
/**
* Get the most recent values for all registers at the given time
*
* @param snap the time
* @return all register values
*/
default Collection<RegisterValue> getAllValues(long snap) {
Set<RegisterValue> result = new LinkedHashSet<>();
for (Register reg : getRegisters()) {

View file

@ -33,6 +33,7 @@ import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
@ -46,6 +47,12 @@ import ghidra.util.database.UndoableTransaction;
public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTest {
public TraceThread initTrace(ToyDBTraceBuilder tb, List<String> stateInit,
List<String> assembly) throws Throwable {
return initTrace(tb, tb.range(0x00400000, 0x0040ffff), tb.range(0x00100000, 0x0010ffff),
stateInit, assembly);
}
/**
* Build a trace with a program ready for emulation
*
@ -64,21 +71,19 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
* @return a new trace thread, whose register state is initialized as specified
* @throws Throwable if anything goes wrong
*/
public TraceThread initTrace(ToyDBTraceBuilder tb, List<String> stateInit,
List<String> assembly) throws Throwable {
public TraceThread initTrace(ToyDBTraceBuilder tb, AddressRange text, AddressRange stack,
List<String> stateInit, List<String> assembly) throws Throwable {
TraceMemoryManager mm = tb.trace.getMemoryManager();
TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) {
thread = tb.getOrAddThread("Thread1", 0);
mm.addRegion("Regions[bin:.text]",
Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff),
mm.addRegion("Regions[bin:.text]", Range.atLeast(0L), text,
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
mm.addRegion("Regions[stack1]",
Range.atLeast(0L), tb.range(0x00100000, 0x0010ffff),
mm.addRegion("Regions[stack1]", Range.atLeast(0L), stack,
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
Iterator<Instruction> block = assembly.isEmpty() ? Collections.emptyIterator()
: asm.assemble(tb.addr(0x00400000), assembly.toArray(String[]::new));
: asm.assemble(text.getMinAddress(), assembly.toArray(String[]::new));
Instruction last = null;
while (block.hasNext()) {
last = block.next();
@ -1006,4 +1011,39 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
emuThread.stepInstruction();
}
}
@Test
public void testMov_w_mW1_W0() throws Throwable {
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "dsPIC33F:LE:24:default")) {
Address textStart = tb.language.getDefaultSpace().getAddress(0x000100, true);
// TODO: Where is the stack typically on this arch?
Address stackStart = tb.language.getDefaultDataSpace().getAddress(0, true);
TraceThread thread = initTrace(tb,
new AddressRangeImpl(textStart, 0x200),
new AddressRangeImpl(stackStart, 1),
List.of(
"PC = 0x000100;",
"W1 = 0x0800;",
"*[ram]:2 0x000800:3 = 0x1234;"),
List.of(
"mov.w [W1], W0"));
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
//emuThread.overrideContextWithDefault(); // default context is null?
emuThread.stepInstruction();
try (UndoableTransaction tid = tb.startTransaction()) {
emu.writeDown(tb.trace, 1, 1, false);
}
assertEquals(BigInteger.valueOf(0x000102),
TraceSleighUtils.evaluate("PC", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x1234),
TraceSleighUtils.evaluate("W0", tb.trace, 1, thread, 0));
assertEquals(BigInteger.valueOf(0x0800),
TraceSleighUtils.evaluate("W1", tb.trace, 1, thread, 0));
}
}
}