mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +02:00
GP-1540: Various emulator fixes: Harvard architectures, memory-mapped registers, word-level addressing.
This commit is contained in:
parent
16871e1eb6
commit
e2b28ddb31
27 changed files with 439 additions and 56 deletions
|
@ -76,7 +76,7 @@ public interface RegisterLocationTrackingSpec extends LocationTrackingSpec {
|
|||
// TODO: Action to select the address space
|
||||
// Could use code unit, but that can't specify space, yet, either....
|
||||
return computeDefaultAddressSpace(coordinates)
|
||||
.getAddress(value.getUnsignedValue().longValue());
|
||||
.getAddress(value.getUnsignedValue().longValue(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -435,6 +435,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
|
||||
DebuggerRegisterActionContext myActionContext;
|
||||
AddressSetView viewKnown;
|
||||
AddressSetView catalog;
|
||||
|
||||
protected DebuggerRegistersProvider(final DebuggerRegistersPlugin plugin,
|
||||
Map<LanguageCompilerSpecPair, LinkedHashSet<Register>> selectionByCSpec,
|
||||
|
@ -551,7 +552,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
}
|
||||
Address address;
|
||||
try {
|
||||
address = space.getAddress(lv);
|
||||
address = space.getAddress(lv, true);
|
||||
}
|
||||
catch (AddressOutOfBoundsException e) {
|
||||
continue;
|
||||
|
@ -689,6 +690,20 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
removeOldTraceListener();
|
||||
this.currentTrace = trace;
|
||||
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() {
|
||||
|
@ -870,6 +885,10 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
void recomputeViewKnown() {
|
||||
if (catalog == null) {
|
||||
viewKnown = null;
|
||||
return;
|
||||
}
|
||||
TraceMemoryRegisterSpace regs = getRegisterMemorySpace(false);
|
||||
TraceProgramView view = current.getView();
|
||||
if (regs == null || view == null) {
|
||||
|
@ -877,7 +896,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
|
|||
return;
|
||||
}
|
||||
viewKnown = new AddressSet(view.getViewport()
|
||||
.unionedAddresses(snap -> regs.getAddressesWithState(snap,
|
||||
.unionedAddresses(snap -> regs.getAddressesWithState(snap, catalog,
|
||||
state -> state == TraceMemoryState.KNOWN)));
|
||||
}
|
||||
|
||||
|
|
|
@ -441,7 +441,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
Address address = curTrace.getBaseLanguage()
|
||||
.getDefaultSpace()
|
||||
.getAddress(value.getUnsignedValue().longValue());
|
||||
.getAddress(value.getUnsignedValue().longValue(), true);
|
||||
stackTableModel.add(new StackFrameRow.Synthetic(this, address));
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,9 @@ public class DebuggerTracePcodeEmulator extends TracePcodeEmulator {
|
|||
if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(name, contextreg)) {
|
||||
RegisterValue context = trace.getRegisterContextManager()
|
||||
.getValueWithDefault(language, contextreg, snap, thread.getCounter());
|
||||
thread.overrideContext(context);
|
||||
if (context != null) { // TODO: Why does this happen?
|
||||
thread.overrideContext(context);
|
||||
}
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@ package ghidra.app.plugin.core.debug.service.emulation;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
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.TraceThreadManager;
|
||||
import ghidra.trace.model.time.TraceSnapshot;
|
||||
import ghidra.util.ComparatorMath;
|
||||
import ghidra.util.DifferenceAddressSetView;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.database.UndoableTransaction;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
|
@ -117,6 +115,18 @@ public enum ProgramEmulationUtils {
|
|||
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
|
||||
*
|
||||
|
@ -134,8 +144,7 @@ public enum ProgramEmulationUtils {
|
|||
*/
|
||||
public static void loadExecutable(TraceSnapshot snapshot, Program program) {
|
||||
Trace trace = snapshot.getTrace();
|
||||
Address min = null;
|
||||
Address max = null;
|
||||
Map<AddressSpace, Extrema> extremaBySpace = new HashMap<>();
|
||||
try {
|
||||
for (MemoryBlock block : program.getMemory().getBlocks()) {
|
||||
if (!block.isLoaded()) {
|
||||
|
@ -145,10 +154,8 @@ public enum ProgramEmulationUtils {
|
|||
continue;
|
||||
}
|
||||
AddressRange range = new AddressRangeImpl(block.getStart(), block.getEnd());
|
||||
min = min == null ? range.getMinAddress()
|
||||
: ComparatorMath.cmin(min, range.getMinAddress());
|
||||
max = max == null ? range.getMaxAddress()
|
||||
: ComparatorMath.cmax(max, range.getMaxAddress());
|
||||
extremaBySpace.computeIfAbsent(range.getAddressSpace(), s -> new Extrema())
|
||||
.consider(range);
|
||||
String modName = getModuleName(program);
|
||||
|
||||
// TODO: Do I populate modules, since the mapping will already be done?
|
||||
|
@ -157,10 +164,13 @@ public enum ProgramEmulationUtils {
|
|||
trace.getMemoryManager()
|
||||
.createRegion(path, snapshot.getKey(), range, getRegionFlags(block));
|
||||
}
|
||||
DebuggerStaticMappingUtils.addMapping(
|
||||
new DefaultTraceLocation(trace, null, Range.atLeast(snapshot.getKey()), min),
|
||||
new ProgramLocation(program, min),
|
||||
max.subtract(min), false);
|
||||
for (Extrema extrema : extremaBySpace.values()) {
|
||||
DebuggerStaticMappingUtils.addMapping(
|
||||
new DefaultTraceLocation(trace, null, Range.atLeast(snapshot.getKey()),
|
||||
extrema.min),
|
||||
new ProgramLocation(program, extrema.min),
|
||||
extrema.max.subtract(extrema.min), false);
|
||||
}
|
||||
}
|
||||
catch (TraceOverlappedRegionException | DuplicateNameException
|
||||
| TraceConflictedMappingException e) {
|
||||
|
@ -214,7 +224,7 @@ public enum ProgramEmulationUtils {
|
|||
.map(Register::getBaseRegister)
|
||||
.collect(Collectors.toSet())) {
|
||||
RegisterValue rv = ctx.getRegisterValue(reg, programPc);
|
||||
if (!rv.hasAnyValue()) {
|
||||
if (rv == null || !rv.hasAnyValue()) {
|
||||
continue;
|
||||
}
|
||||
// Set all the mask bits
|
||||
|
@ -222,14 +232,15 @@ public enum ProgramEmulationUtils {
|
|||
}
|
||||
}
|
||||
space.setValue(snap, new RegisterValue(trace.getBaseLanguage().getProgramCounter(),
|
||||
tracePc.getOffsetAsBigInteger()));
|
||||
NumericUtilities.unsignedLongToBigInteger(tracePc.getAddressableWordOffset())));
|
||||
if (stack != null) {
|
||||
CompilerSpec cSpec = trace.getBaseCompilerSpec();
|
||||
Address sp = cSpec.stackGrowsNegative()
|
||||
? stack.getMaxAddress()
|
||||
: stack.getMinAddress();
|
||||
space.setValue(snap,
|
||||
new RegisterValue(cSpec.getStackPointer(), sp.getOffsetAsBigInteger()));
|
||||
new RegisterValue(cSpec.getStackPointer(),
|
||||
NumericUtilities.unsignedLongToBigInteger(sp.getAddressableWordOffset())));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import static org.junit.Assert.*;
|
|||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
|
||||
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.MemoryBlock;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.trace.model.Trace;
|
||||
import ghidra.trace.model.memory.TraceMemoryRegisterSpace;
|
||||
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
|
||||
public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGUITest {
|
||||
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
|
||||
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();
|
||||
intoProject(program);
|
||||
Assembler asm = Assemblers.getAssembler(program);
|
||||
|
@ -72,6 +79,8 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
|
|||
|
||||
programManager.openProgram(program);
|
||||
waitForSwing();
|
||||
codeBrowser.goTo(new ProgramLocation(program, addrText));
|
||||
waitForSwing();
|
||||
|
||||
assertTrue(emulationPlugin.actionEmulateProgram.isEnabled());
|
||||
performAction(emulationPlugin.actionEmulateProgram);
|
||||
|
@ -97,4 +106,121 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
|
|||
assertEquals(new BigInteger("1234", 16),
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
@ -983,4 +988,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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -171,7 +171,8 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
|
|||
@Override
|
||||
public void overrideCounter(Address counter) {
|
||||
setCounter(counter);
|
||||
state.setVar(pc, arithmetic.fromConst(counter.getOffset(), pc.getMinimumByteSize()));
|
||||
state.setVar(pc,
|
||||
arithmetic.fromConst(counter.getAddressableWordOffset(), pc.getMinimumByteSize()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -211,7 +212,7 @@ public class DefaultPcodeThread<T> implements PcodeThread<T> {
|
|||
@Override
|
||||
public void reInitialize() {
|
||||
long offset = arithmetic.toConcrete(state.getVar(pc)).longValue();
|
||||
setCounter(language.getDefaultSpace().getAddress(offset));
|
||||
setCounter(language.getDefaultSpace().getAddress(offset, true));
|
||||
|
||||
if (contextreg != Register.NO_CONTEXT) {
|
||||
try {
|
||||
|
|
|
@ -266,7 +266,7 @@ public class PcodeExecutor<T> {
|
|||
branchToOffset(offset, frame);
|
||||
|
||||
long concrete = arithmetic.toConcrete(offset).longValue();
|
||||
Address target = op.getSeqnum().getTarget().getNewAddress(concrete);
|
||||
Address target = op.getSeqnum().getTarget().getNewAddress(concrete, true);
|
||||
branchToAddress(target);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@define SIZE "8"
|
||||
|
||||
@define INSTR_PHASE "" # not used by basic toy language
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy.sinc"
|
||||
@include "toyInstructions.sinc"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@define SIZE "8"
|
||||
|
||||
@define INSTR_PHASE "" # not used by basic toy language
|
||||
@define DATA_SPACE "data"
|
||||
|
||||
@include "toy.sinc"
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@define SIZE "8"
|
||||
|
||||
@define INSTR_PHASE "" # not used by basic toy language
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy.sinc"
|
||||
@include "toyInstructions.sinc"
|
||||
|
|
|
@ -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; }
|
||||
Rel11: addr is simm0010 [ addr = inst_start + simm0010; ] { export *:$(SIZE) addr; }
|
||||
|
||||
RS: [rs] is rs { export *[ram]:$(SIZE) rs; }
|
||||
RT: [rt] is rt { export *[ram]:$(SIZE) rt; }
|
||||
RS: [rs] is rs { export *[$(DATA_SPACE)]:$(SIZE) rs; }
|
||||
RT: [rt] is rt { export *[$(DATA_SPACE)]:$(SIZE) rt; }
|
||||
|
||||
CC: "eq" is cc0002=0x0 { export Z; }
|
||||
CC: "ne" is cc0002=0x1 { tmp = !Z; export tmp; }
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@define SIZE "4"
|
||||
|
||||
@define INSTR_PHASE "" # not used by basic toy language
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy.sinc"
|
||||
@include "toyInstructions.sinc"
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
@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 DATA_SPACE "ram"
|
||||
|
||||
@include "toy.sinc"
|
||||
@include "toyInstructions.sinc"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@define ENDIAN "big"
|
||||
@define SIZE "4"
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy_builder.sinc"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@define ENDIAN "big"
|
||||
@define ALIGN "2"
|
||||
@define SIZE "4"
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy_builder.sinc"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@define ENDIAN "little"
|
||||
@define SIZE "4"
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy_builder.sinc"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@define ENDIAN "little"
|
||||
@define ALIGN "2"
|
||||
@define SIZE "4"
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy_builder.sinc"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@define SIZE "4"
|
||||
|
||||
@define INSTR_PHASE "" # not used by basic toy language
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy.sinc"
|
||||
@include "toyInstructions.sinc"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
@define WORDSIZE "2"
|
||||
|
||||
@define INSTR_PHASE "" # not used by basic toy language
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy.sinc"
|
||||
@include "toyInstructions.sinc"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
@define WORDSIZE "2"
|
||||
|
||||
@define INSTR_PHASE "" # not used by basic toy language
|
||||
@define DATA_SPACE "ram"
|
||||
|
||||
@include "toy.sinc"
|
||||
@include "toyInstructions.sinc"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue