GP-1540: Various emulator fixes: Harvard architectures, memory-mapped registers, word-level addressing.

This commit is contained in:
Dan 2021-11-30 15:04:03 -05:00
parent 16871e1eb6
commit e2b28ddb31
27 changed files with 439 additions and 56 deletions

View file

@ -76,7 +76,7 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec {
// TODO: Action to select the address space // TODO: Action to select the address space
// Could use code unit, but that can't specify space, yet, either.... // Could use code unit, but that can't specify space, yet, either....
return computeDefaultAddressSpace(coordinates) return computeDefaultAddressSpace(coordinates)
.getAddress(value.getUnsignedValue().longValue()); .getAddress(value.getUnsignedValue().longValue(), true);
} }
@Override @Override

View file

@ -435,6 +435,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
DebuggerRegisterActionContext myActionContext; DebuggerRegisterActionContext myActionContext;
AddressSetView viewKnown; AddressSetView viewKnown;
AddressSetView catalog;
protected DebuggerRegistersProvider(final DebuggerRegistersPlugin plugin, protected DebuggerRegistersProvider(final DebuggerRegistersPlugin plugin,
Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> selectionByCSpec, Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> selectionByCSpec,
@ -551,7 +552,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
} }
Address address; Address address;
try { try {
address = space.getAddress(lv); address = space.getAddress(lv, true);
} }
catch (AddressOutOfBoundsException e) { catch (AddressOutOfBoundsException e) {
continue; continue;
@ -689,6 +690,20 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
removeOldTraceListener(); removeOldTraceListener();
this.currentTrace = trace; this.currentTrace = trace;
addNewTraceListener(); addNewTraceListener();
catalogRegisterAddresses();
}
private void catalogRegisterAddresses() {
this.catalog = null;
if (currentTrace == null) {
return;
}
AddressSet catalog = new AddressSet();
for (Register reg : currentTrace.getBaseLanguage().getRegisters()) {
catalog.add(TraceRegisterUtils.rangeForRegister(reg));
}
this.catalog = catalog;
} }
private void removeOldRecorderListener() { private void removeOldRecorderListener() {
@ -870,6 +885,10 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
} }
void recomputeViewKnown() { void recomputeViewKnown() {
if (catalog == null) {
viewKnown = null;
return;
}
TraceMemoryRegisterSpace regs = getRegisterMemorySpace(false); TraceMemoryRegisterSpace regs = getRegisterMemorySpace(false);
TraceProgramView view = current.getView(); TraceProgramView view = current.getView();
if (regs == null || view == null) { if (regs == null || view == null) {
@ -877,7 +896,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
return; return;
} }
viewKnown = new AddressSet(view.getViewport() viewKnown = new AddressSet(view.getViewport()
.unionedAddresses(snap -> regs.getAddressesWithState(snap, .unionedAddresses(snap -> regs.getAddressesWithState(snap, catalog,
state -> state == TraceMemoryState.KNOWN))); state -> state == TraceMemoryState.KNOWN)));
} }

View file

@ -441,7 +441,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
} }
Address address = curTrace.getBaseLanguage() Address address = curTrace.getBaseLanguage()
.getDefaultSpace() .getDefaultSpace()
.getAddress(value.getUnsignedValue().longValue()); .getAddress(value.getUnsignedValue().longValue(), true);
stackTableModel.add(new StackFrameRow.Synthetic(this, address)); stackTableModel.add(new StackFrameRow.Synthetic(this, address));
} }

View file

@ -64,7 +64,9 @@ public class DebuggerTracePcodeEmulator extends TracePcodeEmulator {
if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(name, contextreg)) { if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(name, contextreg)) {
RegisterValue context = trace.getRegisterContextManager() RegisterValue context = trace.getRegisterContextManager()
.getValueWithDefault(language, contextreg, snap, thread.getCounter()); .getValueWithDefault(language, contextreg, snap, thread.getCounter());
thread.overrideContext(context); if (context != null) { // TODO: Why does this happen?
thread.overrideContext(context);
}
} }
return thread; return thread;
} }

View file

@ -17,8 +17,7 @@ package ghidra.app.plugin.core.debug.service.emulation;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.EnumSet; import java.util.*;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -41,8 +40,7 @@ import ghidra.trace.model.modules.TraceConflictedMappingException;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager; import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.ComparatorMath; import ghidra.util.*;
import ghidra.util.DifferenceAddressSetView;
import ghidra.util.database.UndoableTransaction; import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
@ -117,6 +115,18 @@ public enum ProgramEmulationUtils {
return result; return result;
} }
static class Extrema {
Address min = null;
Address max = null;
void consider(AddressRange range) {
min = min == null ? range.getMinAddress()
: ComparatorMath.cmin(min, range.getMinAddress());
max = max == null ? range.getMaxAddress()
: ComparatorMath.cmax(max, range.getMaxAddress());
}
}
/** /**
* Create regions for each block in a program, without relocation, and map the program in * Create regions for each block in a program, without relocation, and map the program in
* *
@ -134,8 +144,7 @@ public enum ProgramEmulationUtils {
*/ */
public static void loadExecutable(TraceSnapshot snapshot, Program program) { public static void loadExecutable(TraceSnapshot snapshot, Program program) {
Trace trace = snapshot.getTrace(); Trace trace = snapshot.getTrace();
Address min = null; Map<AddressSpace, Extrema> extremaBySpace = new HashMap<>();
Address max = null;
try { try {
for (MemoryBlock block : program.getMemory().getBlocks()) { for (MemoryBlock block : program.getMemory().getBlocks()) {
if (!block.isLoaded()) { if (!block.isLoaded()) {
@ -145,10 +154,8 @@ public enum ProgramEmulationUtils {
continue; continue;
} }
AddressRange range = new AddressRangeImpl(block.getStart(), block.getEnd()); AddressRange range = new AddressRangeImpl(block.getStart(), block.getEnd());
min = min == null ? range.getMinAddress() extremaBySpace.computeIfAbsent(range.getAddressSpace(), s -> new Extrema())
: ComparatorMath.cmin(min, range.getMinAddress()); .consider(range);
max = max == null ? range.getMaxAddress()
: ComparatorMath.cmax(max, range.getMaxAddress());
String modName = getModuleName(program); String modName = getModuleName(program);
// TODO: Do I populate modules, since the mapping will already be done? // TODO: Do I populate modules, since the mapping will already be done?
@ -157,10 +164,13 @@ public enum ProgramEmulationUtils {
trace.getMemoryManager() trace.getMemoryManager()
.createRegion(path, snapshot.getKey(), range, getRegionFlags(block)); .createRegion(path, snapshot.getKey(), range, getRegionFlags(block));
} }
DebuggerStaticMappingUtils.addMapping( for (Extrema extrema : extremaBySpace.values()) {
new DefaultTraceLocation(trace, null, Range.atLeast(snapshot.getKey()), min), DebuggerStaticMappingUtils.addMapping(
new ProgramLocation(program, min), new DefaultTraceLocation(trace, null, Range.atLeast(snapshot.getKey()),
max.subtract(min), false); extrema.min),
new ProgramLocation(program, extrema.min),
extrema.max.subtract(extrema.min), false);
}
} }
catch (TraceOverlappedRegionException | DuplicateNameException catch (TraceOverlappedRegionException | DuplicateNameException
| TraceConflictedMappingException e) { | TraceConflictedMappingException e) {
@ -214,7 +224,7 @@ public enum ProgramEmulationUtils {
.map(Register::getBaseRegister) .map(Register::getBaseRegister)
.collect(Collectors.toSet())) { .collect(Collectors.toSet())) {
RegisterValue rv = ctx.getRegisterValue(reg, programPc); RegisterValue rv = ctx.getRegisterValue(reg, programPc);
if (!rv.hasAnyValue()) { if (rv == null || !rv.hasAnyValue()) {
continue; continue;
} }
// Set all the mask bits // Set all the mask bits
@ -222,14 +232,15 @@ public enum ProgramEmulationUtils {
} }
} }
space.setValue(snap, new RegisterValue(trace.getBaseLanguage().getProgramCounter(), space.setValue(snap, new RegisterValue(trace.getBaseLanguage().getProgramCounter(),
tracePc.getOffsetAsBigInteger())); NumericUtilities.unsignedLongToBigInteger(tracePc.getAddressableWordOffset())));
if (stack != null) { if (stack != null) {
CompilerSpec cSpec = trace.getBaseCompilerSpec(); CompilerSpec cSpec = trace.getBaseCompilerSpec();
Address sp = cSpec.stackGrowsNegative() Address sp = cSpec.stackGrowsNegative()
? stack.getMaxAddress() ? stack.getMaxAddress()
: stack.getMinAddress(); : stack.getMinAddress();
space.setValue(snap, space.setValue(snap,
new RegisterValue(cSpec.getStackPointer(), sp.getOffsetAsBigInteger())); new RegisterValue(cSpec.getStackPointer(),
NumericUtilities.unsignedLongToBigInteger(sp.getAddressableWordOffset())));
} }
} }

View file

@ -19,6 +19,7 @@ import static org.junit.Assert.*;
import java.math.BigInteger; import java.math.BigInteger;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
@ -29,9 +30,11 @@ import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.*;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemoryRegisterSpace; import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
@ -42,15 +45,19 @@ import ghidra.util.task.TaskMonitor;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test @Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGUITest { public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGUITest {
protected DebuggerEmulationServicePlugin emulationPlugin; protected DebuggerEmulationServicePlugin emulationPlugin;
protected CodeBrowserPlugin codeBrowser;
@Before
public void setUpEmulationServiceTest() throws Exception {
emulationPlugin = addPlugin(tool, DebuggerEmulationServicePlugin.class);
// TODO: Action enablement doesn't work without CodeBrowser???
// Probably missing some contextChanged, but I have no provider!
// I need it for GoTo anyway
codeBrowser = addPlugin(tool, CodeBrowserPlugin.class);
}
@Test @Test
public void testPureEmulation() throws Exception { public void testPureEmulation() throws Exception {
emulationPlugin = addPlugin(tool, DebuggerEmulationServicePlugin.class);
// TODO: Action enablement doesn't work without CodeBrowser???
// Probably missing some contextChanged, but I have no provider!
addPlugin(tool, CodeBrowserPlugin.class);
createProgram(); createProgram();
intoProject(program); intoProject(program);
Assembler asm = Assemblers.getAssembler(program); Assembler asm = Assemblers.getAssembler(program);
@ -72,6 +79,8 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
programManager.openProgram(program); programManager.openProgram(program);
waitForSwing(); waitForSwing();
codeBrowser.goTo(new ProgramLocation(program, addrText));
waitForSwing();
assertTrue(emulationPlugin.actionEmulateProgram.isEnabled()); assertTrue(emulationPlugin.actionEmulateProgram.isEnabled());
performAction(emulationPlugin.actionEmulateProgram); performAction(emulationPlugin.actionEmulateProgram);
@ -97,4 +106,121 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
assertEquals(new BigInteger("1234", 16), assertEquals(new BigInteger("1234", 16),
regs.getViewValue(scratch, regR1).getUnsignedValue()); regs.getViewValue(scratch, regR1).getUnsignedValue());
} }
@Test
public void testPureEmulationHarvard() throws Exception {
Language toyHv = getLanguageService().getLanguage(new LanguageID("Toy:BE:64:harvard"));
createProgram(toyHv);
intoProject(program);
Assembler asm = Assemblers.getAssembler(program);
Memory memory = program.getMemory();
AddressSpace codeSpace = program.getLanguage().getDefaultSpace();
AddressSpace dataSpace = program.getLanguage().getDefaultDataSpace();
Address addrText = codeSpace.getAddress(0x00000400);
Address addrData = dataSpace.getAddress(0x00000400);
assertNotEquals(addrText, addrData);
Register regPC = program.getRegister("pc");
Register regR0 = program.getRegister("r0");
Register regR1 = program.getRegister("r1");
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
MemoryBlock blockData = memory.createInitializedBlock(".data", addrData, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
asm.assemble(addrText, "load r0, [r1]");
program.getProgramContext()
.setValue(regR1, addrText, addrText, new BigInteger("00000400", 16));
blockData.putBytes(addrData, new byte[] { 0, 0, 0, 0, 0, 0, 0x12, 0x34 });
}
programManager.openProgram(program);
waitForSwing();
codeBrowser.goTo(new ProgramLocation(program, addrText));
waitForSwing();
assertTrue(emulationPlugin.actionEmulateProgram.isEnabled());
performAction(emulationPlugin.actionEmulateProgram);
Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
TraceMemoryRegisterSpace regs =
trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
assertEquals(new BigInteger("00000400", 16),
regs.getViewValue(0, regPC).getUnsignedValue());
assertEquals(new BigInteger("0000", 16), regs.getViewValue(0, regR0).getUnsignedValue());
assertEquals(new BigInteger("00000400", 16),
regs.getViewValue(0, regR1).getUnsignedValue());
long scratch =
emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY);
assertEquals(new BigInteger("00000402", 16),
regs.getViewValue(scratch, regPC).getUnsignedValue());
assertEquals(new BigInteger("1234", 16),
regs.getViewValue(scratch, regR0).getUnsignedValue());
assertEquals(new BigInteger("00000400", 16),
regs.getViewValue(scratch, regR1).getUnsignedValue());
}
@Test
public void testPureEmulationMemoryMappedPC_NonByteAddressable() throws Exception {
Language pic33F =
getLanguageService().getLanguage(new LanguageID("dsPIC33F:LE:24:default"));
createProgram(pic33F);
intoProject(program);
Assembler asm = Assemblers.getAssembler(program);
Memory memory = program.getMemory();
AddressSpace codeSpace = program.getLanguage().getDefaultSpace();
AddressSpace dataSpace = program.getLanguage().getDefaultDataSpace();
Address addrText = codeSpace.getAddress(0x00000100, true);
Address addrData = dataSpace.getAddress(0x00000800, true);
assertNotEquals(addrText, addrData);
Register regPC = program.getRegister("PC");
Register regW0 = program.getRegister("W0");
Register regW1 = program.getRegister("W1");
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) {
MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
MemoryBlock blockData = memory.createInitializedBlock(".data", addrData, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
asm.assemble(addrText, "mov.w [W1], W0");
program.getProgramContext()
.setValue(regW1, addrText, addrText, new BigInteger("0800", 16));
blockData.putBytes(addrData, new byte[] { 0x34, 0x12 }); // LE
}
programManager.openProgram(program);
waitForSwing();
codeBrowser.goTo(new ProgramLocation(program, addrText));
waitForSwing();
assertTrue(emulationPlugin.actionEmulateProgram.isEnabled());
performAction(emulationPlugin.actionEmulateProgram);
Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
TraceMemoryRegisterSpace regs =
trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
assertEquals(new BigInteger("000100", 16),
regs.getViewValue(0, regPC).getUnsignedValue());
assertEquals(new BigInteger("0000", 16), regs.getViewValue(0, regW0).getUnsignedValue());
assertEquals(new BigInteger("0800", 16),
regs.getViewValue(0, regW1).getUnsignedValue());
long scratch =
emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY);
assertEquals(new BigInteger("000102", 16),
regs.getViewValue(scratch, regPC).getUnsignedValue());
assertEquals(new BigInteger("1234", 16),
regs.getViewValue(scratch, regW0).getUnsignedValue());
assertEquals(new BigInteger("0800", 16),
regs.getViewValue(scratch, regW1).getUnsignedValue());
}
} }

View file

@ -452,9 +452,8 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace
while (!remains.isEmpty()) { while (!remains.isEmpty()) {
AddressRange range = remains.getFirstRange(); AddressRange range = remains.getFirstRange();
remains.delete(range); remains.delete(range);
for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : stateMapSpace.reduce( for (Entry<TraceAddressSnapRange, TraceMemoryState> entry : doGetStates(snap,
TraceAddressSnapRangeQuery.intersecting(range.getMinAddress(), range)) {
range.getMaxAddress(), snap, snap)).entries()) {
AddressRange foundRange = entry.getKey().getRange(); AddressRange foundRange = entry.getKey().getRange();
remains.delete(foundRange); remains.delete(foundRange);
if (predicate.test(entry.getValue())) { 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 @Override
public Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap, public Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap,
AddressRange range) { AddressRange range) {
return stateMapSpace.reduce(TraceAddressSnapRangeQuery.intersecting(range.getMinAddress(), assertInSpace(range);
range.getMaxAddress(), snap, snap)).entries(); return doGetStates(snap, range);
} }
@Override @Override

View file

@ -29,6 +29,7 @@ import ghidra.util.UnionAddressSetView;
* @param <T> the type of units in the view * @param <T> the type of units in the view
*/ */
public interface TraceBaseCodeUnitsView<T extends TraceCodeUnit> { public interface TraceBaseCodeUnitsView<T extends TraceCodeUnit> {
/** /**
* Get the total number of <em>defined</em> units in this view * 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. * 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 * 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 * 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 * 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.data.DataType;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.trace.model.Trace;
import ghidra.trace.util.TraceRegisterUtils; import ghidra.trace.util.TraceRegisterUtils;
public interface TraceDefinedDataRegisterView public interface TraceDefinedDataRegisterView
extends TraceDefinedDataView, TraceBaseDefinedRegisterView<TraceData> { 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) default TraceData create(Range<Long> lifespan, Register register, DataType dataType)
throws CodeUnitInsertionException { 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); TraceRegisterUtils.requireByteBound(register);
return create(lifespan, register.getAddress(), dataType, return create(lifespan, register.getAddress(), dataType, register.getNumBytes());
register.getNumBytes());
} }
} }

View file

@ -19,6 +19,7 @@ import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange;
@ -26,17 +27,54 @@ import ghidra.trace.model.listing.TraceCodeRegisterSpace;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceRegisterUtils; 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 { public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
/**
* Get the thread for this register space
*
* @return the thread
*/
TraceThread getThread(); 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() { default List<Register> getRegisters() {
return getThread().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) { default void setState(long snap, Register register, TraceMemoryState state) {
setState(snap, TraceRegisterUtils.rangeForRegister(register), 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) { default TraceMemoryState getState(long snap, Register register) {
Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> states = Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> states =
getStates(snap, register); getStates(snap, register);
@ -49,15 +87,33 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
return states.iterator().next().getValue(); 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, default Collection<Entry<TraceAddressSnapRange, TraceMemoryState>> getStates(long snap,
Register register) { 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 * Set the value of a register at the given snap
* *
* <p> * <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}, * <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., * 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}, * 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); value = old.combineValues(value);
} }
ByteBuffer buf = TraceRegisterUtils.bufferForValue(reg, 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); return putBytes(snap, reg.getAddress(), buf);
} }
@ -86,6 +146,10 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
* Write bytes at the given snap and register address * Write bytes at the given snap and register address
* *
* <p> * <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 * 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 * 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, * 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 byteLength = register.getNumBytes();
int limit = buf.limit(); int limit = buf.limit();
buf.limit(Math.min(limit, buf.position() + byteLength)); 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); buf.limit(limit);
return result; 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) { default RegisterValue getValue(long snap, Register register) {
return TraceRegisterUtils.getRegisterValue(register, return TraceRegisterUtils.getRegisterValue(register, (a, buf) -> {
(a, buf) -> getBytes(snap, 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) { default RegisterValue getViewValue(long snap, Register register) {
return TraceRegisterUtils.getRegisterValue(register, return TraceRegisterUtils.getRegisterValue(register, (a, buf) -> {
(a, buf) -> getViewBytes(snap, 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) { default int getBytes(long snap, Register register, ByteBuffer buf) {
int byteLength = register.getNumBytes(); int byteLength = register.getNumBytes();
int limit = buf.limit(); int limit = buf.limit();
buf.limit(Math.min(limit, buf.position() + byteLength)); 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); buf.limit(limit);
return result; return result;
} }
@ -128,6 +251,9 @@ public interface TraceMemoryRegisterSpace extends TraceMemorySpace {
* Remove a value from the given time and register * Remove a value from the given time and register
* *
* <p> * <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}, * <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, * 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 * 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) { default void removeValue(long snap, Register register) {
int byteLength = register.getNumBytes(); 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) { default Collection<RegisterValue> getAllValues(long snap) {
Set<RegisterValue> result = new LinkedHashSet<>(); Set<RegisterValue> result = new LinkedHashSet<>();
for (Register reg : getRegisters()) { 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.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.*; import ghidra.pcode.exec.*;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction; import ghidra.program.model.listing.Instruction;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
@ -46,6 +47,12 @@ import ghidra.util.database.UndoableTransaction;
public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTest { 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 * 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 * @return a new trace thread, whose register state is initialized as specified
* @throws Throwable if anything goes wrong * @throws Throwable if anything goes wrong
*/ */
public TraceThread initTrace(ToyDBTraceBuilder tb, List<String> stateInit, public TraceThread initTrace(ToyDBTraceBuilder tb, AddressRange text, AddressRange stack,
List<String> assembly) throws Throwable { List<String> stateInit, List<String> assembly) throws Throwable {
TraceMemoryManager mm = tb.trace.getMemoryManager(); TraceMemoryManager mm = tb.trace.getMemoryManager();
TraceThread thread; TraceThread thread;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
thread = tb.getOrAddThread("Thread1", 0); thread = tb.getOrAddThread("Thread1", 0);
mm.addRegion("Regions[bin:.text]", mm.addRegion("Regions[bin:.text]", Range.atLeast(0L), text,
Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE);
mm.addRegion("Regions[stack1]", mm.addRegion("Regions[stack1]", Range.atLeast(0L), stack,
Range.atLeast(0L), tb.range(0x00100000, 0x0010ffff),
TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); TraceMemoryFlag.READ, TraceMemoryFlag.WRITE);
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
Iterator<Instruction> block = assembly.isEmpty() ? Collections.emptyIterator() 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; Instruction last = null;
while (block.hasNext()) { while (block.hasNext()) {
last = block.next(); last = block.next();
@ -983,4 +988,39 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
emuThread.stepInstruction(); 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));
}
}
} }

View file

@ -171,7 +171,8 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override @Override
public void overrideCounter(Address counter) { public void overrideCounter(Address counter) {
setCounter(counter); setCounter(counter);
state.setVar(pc, arithmetic.fromConst(counter.getOffset(), pc.getMinimumByteSize())); state.setVar(pc,
arithmetic.fromConst(counter.getAddressableWordOffset(), pc.getMinimumByteSize()));
} }
@Override @Override
@ -211,7 +212,7 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
@Override @Override
public void reInitialize() { public void reInitialize() {
long offset = arithmetic.toConcrete(state.getVar(pc)).longValue(); long offset = arithmetic.toConcrete(state.getVar(pc)).longValue();
setCounter(language.getDefaultSpace().getAddress(offset)); setCounter(language.getDefaultSpace().getAddress(offset, true));
if (contextreg != Register.NO_CONTEXT) { if (contextreg != Register.NO_CONTEXT) {
try { try {

View file

@ -266,7 +266,7 @@ public class PcodeExecutor<T> {
branchToOffset(offset, frame); branchToOffset(offset, frame);
long concrete = arithmetic.toConcrete(offset).longValue(); long concrete = arithmetic.toConcrete(offset).longValue();
Address target = op.getSeqnum().getTarget().getNewAddress(concrete); Address target = op.getSeqnum().getTarget().getNewAddress(concrete, true);
branchToAddress(target); branchToAddress(target);
} }

View file

@ -2,6 +2,7 @@
@define SIZE "8" @define SIZE "8"
@define INSTR_PHASE "" # not used by basic toy language @define INSTR_PHASE "" # not used by basic toy language
@define DATA_SPACE "ram"
@include "toy.sinc" @include "toy.sinc"
@include "toyInstructions.sinc" @include "toyInstructions.sinc"

View file

@ -2,6 +2,7 @@
@define SIZE "8" @define SIZE "8"
@define INSTR_PHASE "" # not used by basic toy language @define INSTR_PHASE "" # not used by basic toy language
@define DATA_SPACE "data"
@include "toy.sinc" @include "toy.sinc"

View file

@ -2,6 +2,7 @@
@define SIZE "8" @define SIZE "8"
@define INSTR_PHASE "" # not used by basic toy language @define INSTR_PHASE "" # not used by basic toy language
@define DATA_SPACE "ram"
@include "toy.sinc" @include "toy.sinc"
@include "toyInstructions.sinc" @include "toyInstructions.sinc"

View file

@ -109,8 +109,8 @@ Rel8: addr is simm0007 [ addr = inst_start + simm0007; ] { export *:$(SIZE) add
Rel82: addr is simm0411 [ addr = inst_start + simm0411; ] { export *:$(SIZE) addr; } Rel82: addr is simm0411 [ addr = inst_start + simm0411; ] { export *:$(SIZE) addr; }
Rel11: addr is simm0010 [ addr = inst_start + simm0010; ] { export *:$(SIZE) addr; } Rel11: addr is simm0010 [ addr = inst_start + simm0010; ] { export *:$(SIZE) addr; }
RS: [rs] is rs { export *[ram]:$(SIZE) rs; } RS: [rs] is rs { export *[$(DATA_SPACE)]:$(SIZE) rs; }
RT: [rt] is rt { export *[ram]:$(SIZE) rt; } RT: [rt] is rt { export *[$(DATA_SPACE)]:$(SIZE) rt; }
CC: "eq" is cc0002=0x0 { export Z; } CC: "eq" is cc0002=0x0 { export Z; }
CC: "ne" is cc0002=0x1 { tmp = !Z; export tmp; } CC: "ne" is cc0002=0x1 { tmp = !Z; export tmp; }

View file

@ -2,6 +2,7 @@
@define SIZE "4" @define SIZE "4"
@define INSTR_PHASE "" # not used by basic toy language @define INSTR_PHASE "" # not used by basic toy language
@define DATA_SPACE "ram"
@include "toy.sinc" @include "toy.sinc"
@include "toyInstructions.sinc" @include "toyInstructions.sinc"

View file

@ -3,6 +3,7 @@
@define INSTR_PHASE "" # not used by basic toy language @define INSTR_PHASE "" # not used by basic toy language
@define POS_STACK "true" # enables switch in instructions for push/pop to work in positive direction @define POS_STACK "true" # enables switch in instructions for push/pop to work in positive direction
@define DATA_SPACE "ram"
@include "toy.sinc" @include "toy.sinc"
@include "toyInstructions.sinc" @include "toyInstructions.sinc"

View file

@ -1,4 +1,5 @@
@define ENDIAN "big" @define ENDIAN "big"
@define SIZE "4" @define SIZE "4"
@define DATA_SPACE "ram"
@include "toy_builder.sinc" @include "toy_builder.sinc"

View file

@ -1,5 +1,6 @@
@define ENDIAN "big" @define ENDIAN "big"
@define ALIGN "2" @define ALIGN "2"
@define SIZE "4" @define SIZE "4"
@define DATA_SPACE "ram"
@include "toy_builder.sinc" @include "toy_builder.sinc"

View file

@ -1,4 +1,5 @@
@define ENDIAN "little" @define ENDIAN "little"
@define SIZE "4" @define SIZE "4"
@define DATA_SPACE "ram"
@include "toy_builder.sinc" @include "toy_builder.sinc"

View file

@ -1,5 +1,6 @@
@define ENDIAN "little" @define ENDIAN "little"
@define ALIGN "2" @define ALIGN "2"
@define SIZE "4" @define SIZE "4"
@define DATA_SPACE "ram"
@include "toy_builder.sinc" @include "toy_builder.sinc"

View file

@ -2,6 +2,7 @@
@define SIZE "4" @define SIZE "4"
@define INSTR_PHASE "" # not used by basic toy language @define INSTR_PHASE "" # not used by basic toy language
@define DATA_SPACE "ram"
@include "toy.sinc" @include "toy.sinc"
@include "toyInstructions.sinc" @include "toyInstructions.sinc"

View file

@ -4,6 +4,7 @@
@define WORDSIZE "2" @define WORDSIZE "2"
@define INSTR_PHASE "" # not used by basic toy language @define INSTR_PHASE "" # not used by basic toy language
@define DATA_SPACE "ram"
@include "toy.sinc" @include "toy.sinc"
@include "toyInstructions.sinc" @include "toyInstructions.sinc"

View file

@ -4,6 +4,7 @@
@define WORDSIZE "2" @define WORDSIZE "2"
@define INSTR_PHASE "" # not used by basic toy language @define INSTR_PHASE "" # not used by basic toy language
@define DATA_SPACE "ram"
@include "toy.sinc" @include "toy.sinc"
@include "toyInstructions.sinc" @include "toyInstructions.sinc"