mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
Merge remote-tracking branch
'origin/GP-1540_Dan_emulateHarvard--SQUASHED' into Ghidra_10.1
This commit is contained in:
commit
0f0b63f6ad
27 changed files with 439 additions and 56 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue