diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterImpl.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterImpl.java index 5a1a16b772..bcc94dcb22 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterImpl.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/main/java/agent/dbgeng/model/impl/DbgModelTargetRegisterImpl.java @@ -65,7 +65,7 @@ public class DbgModelTargetRegisterImpl extends DbgModelTargetObjectImpl changeAttributes(List.of(), List.of(), Map.of( // CONTAINER_ATTRIBUTE_NAME, registers, // - LENGTH_ATTRIBUTE_NAME, bitLength, // + BIT_LENGTH_ATTRIBUTE_NAME, bitLength, // DISPLAY_ATTRIBUTE_NAME, "[" + register.getName() + "]" // ), "Initialized"); } diff --git a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetRegisterImpl.java b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetRegisterImpl.java index e810c517b3..64ce344a01 100644 --- a/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetRegisterImpl.java +++ b/Ghidra/Debug/Debugger-agent-frida/src/main/java/agent/frida/model/impl/FridaModelTargetRegisterImpl.java @@ -52,13 +52,14 @@ public class FridaModelTargetRegisterImpl changeAttributes(List.of(), Map.of( // CONTAINER_ATTRIBUTE_NAME, registers, // - LENGTH_ATTRIBUTE_NAME, getBitLength(), // + BIT_LENGTH_ATTRIBUTE_NAME, getBitLength(), // DISPLAY_ATTRIBUTE_NAME, getDescription(0), // VALUE_ATTRIBUTE_NAME, value == null ? "0" : value, // MODIFIED_ATTRIBUTE_NAME, false // ), "Initialized"); } + @Override public String getDescription(int level) { return getName() + " : " + getValue(); } @@ -85,6 +86,7 @@ public class FridaModelTargetRegisterImpl return (FridaValue) getModelObject(); } + @Override public byte[] getBytes() { String oldValue = value; value = getValue(); @@ -113,6 +115,7 @@ public class FridaModelTargetRegisterImpl return bytes; } + @Override public String getDisplay() { return getValue() == null ? getName() : getName() + " : " + getValue(); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetRegister.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetRegister.java index f445d20bac..058a98252a 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetRegister.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetRegister.java @@ -60,7 +60,7 @@ public class GdbModelTargetRegister changeAttributes(List.of(), Map.of( // CONTAINER_ATTRIBUTE_NAME, registers, // - LENGTH_ATTRIBUTE_NAME, bitLength, // + BIT_LENGTH_ATTRIBUTE_NAME, bitLength, // DISPLAY_ATTRIBUTE_NAME, getName() // ), "Initialized"); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrameRegister.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrameRegister.java index 33add4b14a..55403c9381 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrameRegister.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrameRegister.java @@ -64,7 +64,7 @@ public class GdbModelTargetStackFrameRegister changeAttributes(List.of(), Map.of( // CONTAINER_ATTRIBUTE_NAME, registers, // - LENGTH_ATTRIBUTE_NAME, bitLength, // + BIT_LENGTH_ATTRIBUTE_NAME, bitLength, // DISPLAY_ATTRIBUTE_NAME, getName(), // MODIFIED_ATTRIBUTE_NAME, false // ), "Initialized"); diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetStackFrameRegisterImpl.java b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetStackFrameRegisterImpl.java index 8319004a3d..5514c6c6a6 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetStackFrameRegisterImpl.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/main/java/agent/lldb/model/impl/LldbModelTargetStackFrameRegisterImpl.java @@ -53,7 +53,7 @@ public class LldbModelTargetStackFrameRegisterImpl changeAttributes(List.of(), Map.of( // CONTAINER_ATTRIBUTE_NAME, bank.getContainer(), // - LENGTH_ATTRIBUTE_NAME, getBitLength(), // + BIT_LENGTH_ATTRIBUTE_NAME, getBitLength(), // DISPLAY_ATTRIBUTE_NAME, getDescription(0), // VALUE_ATTRIBUTE_NAME, value == null ? "0" : value, // MODIFIED_ATTRIBUTE_NAME, false // diff --git a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRegister.java b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRegister.java index 850b9911c4..bf4a40c84b 100644 --- a/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRegister.java +++ b/Ghidra/Debug/Debugger-jpda/src/main/java/ghidra/dbg/jdi/model/JdiModelTargetRegister.java @@ -50,7 +50,7 @@ public class JdiModelTargetRegister extends JdiModelTargetObjectImpl implements changeAttributes(List.of(), List.of(), Map.of( // DISPLAY_ATTRIBUTE_NAME, getDisplay(), // CONTAINER_ATTRIBUTE_NAME, parent, // - LENGTH_ATTRIBUTE_NAME, Long.SIZE // + BIT_LENGTH_ATTRIBUTE_NAME, Long.SIZE // ), "Initialized"); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java index 2b7ac76404..b1b5b2b46c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/DebuggerCoordinates.java @@ -98,6 +98,7 @@ public class DebuggerCoordinates { private Long viewSnap; private DefaultTraceTimeViewport viewport; + private TraceObject registerContainer; DebuggerCoordinates(Trace trace, TracePlatform platform, TraceRecorder recorder, TraceThread thread, TraceProgramView view, TraceSchedule time, Integer frame, @@ -483,7 +484,7 @@ public class DebuggerCoordinates { } newTrace = trace; } - TracePlatform newPlatform = resolvePlatform(newTrace); + TracePlatform newPlatform = platform != null ? platform : resolvePlatform(newTrace); TraceThread newThread = resolveThread(newObject); Integer newFrame = resolveFrame(newObject); @@ -553,6 +554,13 @@ public class DebuggerCoordinates { return object; } + public TraceObject getRegisterContainer() { + if (registerContainer != null) { + return registerContainer; + } + return registerContainer = object.queryRegisterContainer(getFrame()); + } + public synchronized long getViewSnap() { if (viewSnap != null) { return viewSnap; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java index 238913a213..1ec2893408 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerReadsMemoryTrait.java @@ -86,7 +86,7 @@ public abstract class DebuggerReadsMemoryTrait { .map(TargetObject::invalidateCaches) .toArray(CompletableFuture[]::new); return CompletableFuture.allOf(requests).thenCompose(_r -> { - return recorder.readMemoryBlocks(sel, monitor, false); + return recorder.readMemoryBlocks(sel, monitor); }); }); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java index 53fd1e3bfa..141ce1dfdd 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java @@ -68,6 +68,6 @@ public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec { return AsyncUtils.NIL; } - return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY, false); + return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java index 3888765a7b..2b2016e9bb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java @@ -93,6 +93,6 @@ public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec { return AsyncUtils.NIL; } - return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY, false); + return recorder.readMemoryBlocks(toRead, TaskMonitor.DUMMY); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java index ea870a230d..fe8ce5806f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java @@ -812,8 +812,8 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider { throws Exception { synchronized (this) { monitor.checkCanceled(); - CompletableFuture> recCapture = - recorder.readMemoryBlocks(new AddressSet(range), monitor, false); + CompletableFuture recCapture = + recorder.readMemoryBlocks(new AddressSet(range), monitor); this.captureTask = recCapture.thenCompose(__ -> { return recorder.getTarget().getModel().flushEvents(); }).thenCompose(__ -> { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java index 5aeb57b458..45b97a82aa 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java @@ -980,7 +980,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { doLoadPcodeFrameFromEmulator(emu); return; } - emulationService.backgroundEmulate(trace, time).thenAcceptAsync(__ -> { + emulationService.backgroundEmulate(current.getPlatform(), time).thenAcceptAsync(__ -> { clear(); if (current != this.current) { return; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java index ab47a81d38..57b1f908a8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java @@ -70,10 +70,11 @@ import ghidra.program.model.listing.Data; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.trace.model.*; import ghidra.trace.model.Trace.*; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.listing.*; -import ghidra.trace.model.memory.TraceMemorySpace; -import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.memory.*; import ghidra.trace.model.program.TraceProgramView; +import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.*; import ghidra.util.*; @@ -223,8 +224,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter } protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { - if (!Objects.equals(a.getTrace(), b.getTrace())) { - return false; + if (!Objects.equals(a.getPlatform(), b.getPlatform())) { + return false; // subsumes trace } if (!Objects.equals(a.getRecorder(), b.getRecorder())) { return false; // For live read/writes @@ -254,11 +255,23 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter listenFor(TraceThreadChangeType.LIFESPAN_CHANGED, this::threadDestroyed); } + private boolean isVisibleObjectsMode(AddressSpace space) { + TraceObject container = current.getRegisterContainer(); + return container != null && + container.getCanonicalPath().toString().equals(space.getName()); + } + private boolean isVisible(TraceAddressSpace space) { TraceThread curThread = current.getThread(); if (curThread == null) { return false; } + if (space.getAddressSpace().isOverlaySpace()) { + return isVisibleObjectsMode(space.getAddressSpace()); + } + if (!space.getAddressSpace().isRegisterSpace()) { + return true; // Memory-mapped, visible no matter the active thread + } if (space.getThread() != curThread) { return false; } @@ -272,6 +285,12 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter if (!isVisible(space)) { return false; } + if (space.getAddressSpace().isMemorySpace()) { + return current.getPlatform() + .getLanguage() + .getRegisterAddresses() + .intersects(range.getX1(), range.getX2()); + } TraceProgramView view = current.getView(); if (view == null || !view.getViewport().containsAnyUpper(range.getLifespan())) { return false; @@ -439,7 +458,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter private AsyncLazyValue readTheseCoords = new AsyncLazyValue<>(this::readRegistersIfLiveAndAccessible); /* "read" past tense */ private Trace currentTrace; // Copy for transition - private TraceRecorder currentRecorder; // Copy of transition + private TraceRecorder currentRecorder; // Copy for transition @AutoServiceConsumed private DebuggerModelService modelService; @@ -498,7 +517,6 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter DebuggerRegisterActionContext myActionContext; AddressSetView viewKnown; - AddressSetView catalog; protected DebuggerRegistersProvider(final DebuggerRegistersPlugin plugin, Map> selectionByCSpec, @@ -691,14 +709,14 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter } private void selectRegistersActivated() { - TraceThread curThread = current.getThread(); - if (curThread == null) { + TracePlatform curPlatform = current.getPlatform(); + if (current.getThread() == null) { return; } - availableRegsDialog.setLanguage(curThread.getTrace().getBaseLanguage()); - Set viewKnown = computeDefaultRegisterSelection(curThread); + availableRegsDialog.setLanguage(curPlatform.getLanguage()); + Set viewKnown = computeDefaultRegisterSelection(curPlatform); availableRegsDialog.setKnown(viewKnown); - Set selection = getSelectionFor(curThread); + Set selection = getSelectionFor(curPlatform); // NOTE: Modifies selection in place availableRegsDialog.setSelection(selection); tool.showDialog(availableRegsDialog); @@ -777,20 +795,6 @@ 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() { @@ -830,6 +834,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter doSetRecorder(current.getRecorder()); updateSubTitle(); + prepareRegisterSpace(); recomputeViewKnown(); loadRegistersAndValues(); contextChanged(); @@ -861,7 +866,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter if (regs == null) { return BigInteger.ZERO; } - return regs.getViewValue(current.getViewSnap(), register).getUnsignedValue(); + return regs.getViewValue(current.getPlatform(), current.getViewSnap(), register) + .getUnsignedValue(); } void writeRegisterValue(Register register, BigInteger value) { @@ -902,8 +908,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter private RegisterValue combineWithTraceBaseRegisterValue(RegisterValue rv) { TraceMemorySpace regs = getRegisterMemorySpace(false); + TracePlatform platform = current.getPlatform(); long snap = current.getViewSnap(); - return TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, snap, regs, true); + return TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, platform, snap, regs, true); } /** @@ -916,9 +923,11 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter UndoableTransaction.start(current.getTrace(), "Edit Register Type")) { TraceCodeSpace space = getRegisterMemorySpace(true).getCodeSpace(true); long snap = current.getViewSnap(); - space.definedUnits().clear(Range.closed(snap, snap), register, TaskMonitor.DUMMY); + TracePlatform platform = current.getPlatform(); + space.definedUnits() + .clear(platform, Range.closed(snap, snap), register, TaskMonitor.DUMMY); if (dataType != null) { - space.definedData().create(Range.atLeast(snap), register, dataType); + space.definedData().create(platform, Range.atLeast(snap), register, dataType); } } catch (CodeUnitInsertionException | CancelledException e) { @@ -931,8 +940,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter if (space == null) { return null; } + TracePlatform platform = current.getPlatform(); long snap = current.getViewSnap(); - return space.definedData().getForRegister(snap, register); + return space.definedData().getForRegister(platform, snap, register); } DataType getRegisterDataType(Register register) { @@ -980,27 +990,64 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter return TraceRegisterUtils.getValueRepresentationHackPointer(data); } + /** + * Ensure the register space exists and has been populated from register object values. + * + *

+ * TODO: I wish this were not necessary. Maybe I should create the space when register object + * values are populated. + */ + void prepareRegisterSpace() { + if (current.getThread() != null && + current.getTrace().getObjectManager().getRootSchema() != null) { + try (UndoableTransaction tid = + UndoableTransaction.start(current.getTrace(), "Create/initialize register space")) { + getRegisterMemorySpace(true); + } + } + } + void recomputeViewKnown() { - if (catalog == null) { + TracePlatform platform = current.getPlatform(); + if (platform == null) { viewKnown = null; return; } - TraceMemorySpace regs = getRegisterMemorySpace(false); TraceProgramView view = current.getView(); - if (regs == null || view == null) { + if (view == null) { viewKnown = null; return; } - viewKnown = new AddressSet(view.getViewport() - .unionedAddresses(snap -> regs.getAddressesWithState(snap, catalog, - state -> state == TraceMemoryState.KNOWN))); + TraceMemoryManager mem = current.getTrace().getMemoryManager(); + AddressSetView viewKnownMem = view.getViewport() + .unionedAddresses(snap -> mem.getAddressesWithState(snap, + platform.mapGuestToHost(platform.getLanguage().getRegisterAddresses()), + state -> state == TraceMemoryState.KNOWN)); + TraceMemorySpace regs = getRegisterMemorySpace(false); + if (regs == null) { + viewKnown = new AddressSet(viewKnownMem); + return; + } + AddressSetView hostRegs = + platform.mapGuestToHost(platform.getLanguage().getRegisterAddresses()); + AddressSetView overlayRegs = + TraceRegisterUtils.getOverlaySet(regs.getAddressSpace(), hostRegs); + AddressSetView viewKnownRegs = view.getViewport() + .unionedAddresses(snap -> regs.getAddressesWithState(snap, overlayRegs, + state -> state == TraceMemoryState.KNOWN)); + viewKnown = viewKnownRegs.union(viewKnownMem); } boolean isRegisterKnown(Register register) { if (viewKnown == null) { return false; } - AddressRange range = TraceRegisterUtils.rangeForRegister(register); + TraceMemorySpace regs = getRegisterMemorySpace(false); + if (regs == null && register.getAddressSpace().isRegisterSpace()) { + return false; + } + AddressRange range = + current.getPlatform().getConventionalRegisterRange(regs.getAddressSpace(), register); return viewKnown.contains(range.getMinAddress(), range.getMaxAddress()); } @@ -1008,7 +1055,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter if (previous.getThread() == null || current.getThread() == null) { return false; } - if (previous.getTrace().getBaseLanguage() != current.getTrace().getBaseLanguage()) { + if (previous.getPlatform().getLanguage() != current.getPlatform().getLanguage()) { return false; } if (!isRegisterKnown(register)) { @@ -1019,8 +1066,10 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter if (prevSpace == null) { return false; } - RegisterValue curRegVal = curSpace.getViewValue(current.getViewSnap(), register); - RegisterValue prevRegVal = prevSpace.getViewValue(previous.getViewSnap(), register); + RegisterValue curRegVal = + curSpace.getViewValue(current.getPlatform(), current.getViewSnap(), register); + RegisterValue prevRegVal = + prevSpace.getViewValue(current.getPlatform(), previous.getViewSnap(), register); return !Objects.equals(curRegVal, prevRegVal); } @@ -1031,8 +1080,10 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter /** * Gather general registers, the program counter, and the stack pointer * + *

* This excludes the context register * + *

* TODO: Several pspec files need adjustment to clean up "common registers" * * @param cSpec the compiler spec @@ -1061,49 +1112,17 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter return result; } - public LinkedHashSet computeDefaultRegisterSelection(TraceThread thread) { - return collectCommonRegisters(thread.getTrace().getBaseCompilerSpec()); + public LinkedHashSet computeDefaultRegisterSelection(TracePlatform platform) { + return collectCommonRegisters(platform.getCompilerSpec()); } - public LinkedHashSet computeDefaultRegisterFavorites(TraceThread thread) { + public LinkedHashSet computeDefaultRegisterFavorites(TracePlatform platform) { LinkedHashSet favorites = new LinkedHashSet<>(); - CompilerSpec cSpec = thread.getTrace().getBaseCompilerSpec(); - favorites.add(cSpec.getLanguage().getProgramCounter()); - favorites.add(cSpec.getStackPointer()); + favorites.add(platform.getLanguage().getProgramCounter()); + favorites.add(platform.getCompilerSpec().getStackPointer()); return favorites; } - public LinkedHashSet computeDefaultRegistersOld(TraceThread thread) { - LinkedHashSet viewKnown = new LinkedHashSet<>(); - /** - * NOTE: It is rare that this includes registers outside of those common to the view and - * target, but in case the user has manually populated such registers, this will ensure they - * are visible in the UI. - * - * Also, in case the current thread is not live, we want the DB values to appear. - */ - viewKnown.addAll(collectBaseRegistersWithKnownValues(thread)); - Trace trace = thread.getTrace(); - TraceRecorder recorder = modelService.getRecorder(trace); - if (recorder == null) { - viewKnown.addAll(collectCommonRegisters(trace.getBaseCompilerSpec())); - return viewKnown; - } - TargetThread targetThread = recorder.getTargetThread(thread); - if (targetThread == null || !recorder.isRegisterBankAccessible(thread, 0)) { - return viewKnown; - } - DebuggerRegisterMapper regMapper = recorder.getRegisterMapper(thread); - if (regMapper == null) { - return viewKnown; - } - for (Register onTarget : regMapper.getRegistersOnTarget()) { - viewKnown.add(onTarget); - viewKnown.addAll(onTarget.getChildRegisters()); - } - return viewKnown; - } - protected static TraceMemorySpace getRegisterMemorySpace(DebuggerCoordinates coords, boolean createIfAbsent) { TraceThread thread = coords.getThread(); @@ -1163,33 +1182,29 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter return result; } - protected LanguageCompilerSpecPair getLangCSpecPair(Trace trace) { - return new LanguageCompilerSpecPair(trace.getBaseLanguage().getLanguageID(), - trace.getBaseCompilerSpec().getCompilerSpecID()); + protected static LanguageCompilerSpecPair getLangCSpecPair(TracePlatform platform) { + return new LanguageCompilerSpecPair(platform.getLanguage().getLanguageID(), + platform.getCompilerSpec().getCompilerSpecID()); } - protected LanguageCompilerSpecPair getLangCSpecPair(TraceThread thread) { - return getLangCSpecPair(thread.getTrace()); - } - - protected Set getSelectionFor(TraceThread thread) { + protected Set getSelectionFor(TracePlatform platform) { synchronized (selectionByCSpec) { - LanguageCompilerSpecPair lcsp = getLangCSpecPair(thread); + LanguageCompilerSpecPair lcsp = getLangCSpecPair(platform); return selectionByCSpec.computeIfAbsent(lcsp, - __ -> computeDefaultRegisterSelection(thread)); + __ -> computeDefaultRegisterSelection(platform)); } } - protected Set getFavoritesFor(TraceThread thread) { + protected Set getFavoritesFor(TracePlatform platform) { synchronized (favoritesByCSpec) { - LanguageCompilerSpecPair lcsp = getLangCSpecPair(thread); + LanguageCompilerSpecPair lcsp = getLangCSpecPair(platform); return favoritesByCSpec.computeIfAbsent(lcsp, - __ -> computeDefaultRegisterFavorites(thread)); + __ -> computeDefaultRegisterFavorites(platform)); } } protected void setFavorite(Register register, boolean favorite) { - Set favorites = getFavoritesFor(current.getThread()); + Set favorites = getFavoritesFor(current.getPlatform()); if (favorite) { favorites.add(register); } @@ -1199,13 +1214,13 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter } public boolean isFavorite(Register register) { - Set favorites = getFavoritesFor(current.getThread()); + Set favorites = getFavoritesFor(current.getPlatform()); return favorites.contains(register); } public CompletableFuture setSelectedRegistersAndLoad( Collection selectedRegisters) { - Set selection = getSelectionFor(current.getThread()); + Set selection = getSelectionFor(current.getPlatform()); selection.clear(); selection.addAll(new TreeSet<>(selectedRegisters)); return loadRegistersAndValues(); @@ -1226,7 +1241,7 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter } protected void displaySelectedRegisters(Set selected) { - List regs = currentTrace.getBaseLanguage().getRegisters(); + List regs = current.getPlatform().getLanguage().getRegisters(); for (Iterator> it = regMap.entrySet().iterator(); it .hasNext();) { Map.Entry ent = it.next(); @@ -1246,13 +1261,12 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter } protected CompletableFuture loadRegistersAndValues() { - TraceThread curThread = current.getThread(); - if (curThread == null) { + if (current.getThread() == null) { regsTableModel.clear(); regMap.clear(); return AsyncUtils.NIL; } - Set selected = getSelectionFor(curThread); + Set selected = getSelectionFor(current.getPlatform()); displaySelectedRegisters(selected); return loadValues(); } @@ -1272,6 +1286,30 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter return regs.stream().filter(Register::isBaseRegister).collect(Collectors.toSet()); } + protected CompletableFuture readRegistersLegacy(TraceRecorder recorder, + TraceThread traceThread, Set toRead) { + DebuggerRegisterMapper regMapper = recorder.getRegisterMapper(traceThread); + if (regMapper == null) { + Msg.error(this, "Target is live, but we haven't got a register mapper, yet"); + return AsyncUtils.NIL; + } + toRead.retainAll(regMapper.getRegistersOnTarget()); + TargetRegisterBank bank = recorder.getTargetRegisterBank(traceThread, current.getFrame()); + if (bank == null || !bank.isValid()) { + Msg.error(this, "Current frame's bank does not exist"); + return AsyncUtils.NIL; + } + // TODO: Should probably always be the host platform. I suspect it's ignored anyway. + return recorder.captureThreadRegisters(current.getPlatform(), traceThread, + current.getFrame(), toRead); + } + + protected CompletableFuture readRegistersObjectMode(TraceRecorder recorder, + TraceThread traceThread, Set toRead) { + return recorder.captureThreadRegisters(current.getPlatform(), traceThread, + current.getFrame(), toRead); + } + protected CompletableFuture readRegistersIfLiveAndAccessible() { TraceRecorder recorder = current.getRecorder(); if (recorder == null) { @@ -1289,20 +1327,16 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter if (targetThread == null) { return AsyncUtils.NIL; } - Set toRead = new HashSet<>(baseRegisters(getSelectionFor(traceThread))); - DebuggerRegisterMapper regMapper = recorder.getRegisterMapper(traceThread); - if (regMapper == null) { - Msg.error(this, "Target is live, but we haven't got a register mapper, yet"); - return AsyncUtils.NIL; + + Set toRead = new HashSet<>(baseRegisters(getSelectionFor(current.getPlatform()))); + + CompletableFuture future; + if (current.getTrace().getObjectManager().getRootSchema() == null) { + future = readRegistersLegacy(recorder, traceThread, toRead); } - toRead.retainAll(regMapper.getRegistersOnTarget()); - TargetRegisterBank bank = recorder.getTargetRegisterBank(traceThread, current.getFrame()); - if (bank == null || !bank.isValid()) { - Msg.error(this, "Current frame's bank does not exist"); - return AsyncUtils.NIL; + else { + future = readRegistersObjectMode(recorder, traceThread, toRead); } - CompletableFuture future = - recorder.captureThreadRegisters(traceThread, current.getFrame(), toRead); return future.exceptionally(ex -> { ex = AsyncUtils.unwrapThrowable(ex); if (ex instanceof DebuggerModelAccessException) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java index f3d5355aeb..39b59b1519 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java @@ -95,8 +95,7 @@ public class DbgengX64DisassemblyInject implements DisassemblyInject { try { // This is on its own task thread, so whatever. // Just don't hang it indefinitely. - recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, false) - .get(1000, TimeUnit.MILLISECONDS); + recorder.readMemoryBlocks(set, TaskMonitor.DUMMY).get(1000, TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Msg.error("Could not read module header from target", e); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java index 16ce1b4573..dff8ea1ee6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServicePlugin.java @@ -30,11 +30,14 @@ import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; +import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; import ghidra.program.model.mem.*; import ghidra.trace.model.Trace; import ghidra.trace.model.Trace.TraceProgramViewListener; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryOperations; +import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.program.*; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.schedule.PatchStep; @@ -146,30 +149,41 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin return CompletableFuture .failedFuture(new MemoryAccessException("View is not the present")); } - return recorder.writeVariable(coordinates.getThread(), coordinates.getFrame(), address, - data); + return recorder.writeVariable(coordinates.getPlatform(), coordinates.getThread(), + coordinates.getFrame(), address, data); } protected CompletableFuture writeTraceVariable(DebuggerCoordinates coordinates, - Address address, byte[] data) { + Address guestAddress, byte[] data) { Trace trace = coordinates.getTrace(); + TracePlatform platform = coordinates.getPlatform(); long snap = coordinates.getViewSnap(); + Address hostAddress = platform.mapGuestToHost(guestAddress); + if (hostAddress == null) { + throw new IllegalArgumentException( + "Guest address " + guestAddress + " is not mapped"); + } TraceMemoryOperations memOrRegs; + Address overlayAddress; try (UndoableTransaction txid = UndoableTransaction.start(trace, "Edit Variable")) { - if (address.isRegisterAddress()) { + if (hostAddress.isRegisterAddress()) { TraceThread thread = coordinates.getThread(); if (thread == null) { throw new IllegalArgumentException("Register edits require a thread."); } - memOrRegs = trace.getMemoryManager() + TraceMemorySpace regs = trace.getMemoryManager() .getMemoryRegisterSpace(thread, coordinates.getFrame(), true); + memOrRegs = regs; + overlayAddress = regs.getAddressSpace().getOverlayAddress(hostAddress); } else { memOrRegs = trace.getMemoryManager(); + overlayAddress = hostAddress; } - if (memOrRegs.putBytes(snap, address, ByteBuffer.wrap(data)) != data.length) { + if (memOrRegs.putBytes(snap, overlayAddress, + ByteBuffer.wrap(data)) != data.length) { return CompletableFuture.failedFuture(new MemoryAccessException()); } } @@ -186,9 +200,9 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin // TODO: Well, technically, only for register edits throw new IllegalArgumentException("Emulator edits require a thread."); } + Language language = coordinates.getPlatform().getLanguage(); TraceSchedule time = coordinates.getTime() - .patched(thread, PatchStep.generateSleigh( - coordinates.getTrace().getBaseLanguage(), address, data)); + .patched(thread, language, PatchStep.generateSleigh(language, address, data)); DebuggerCoordinates withTime = coordinates.time(time); Long found = traceManager.findSnapshot(withTime); @@ -198,7 +212,7 @@ public class DebuggerStateEditingServicePlugin extends AbstractDebuggerPlugin // TODO: Could still do it async on another thread, no? // Not sure it buys anything, since program view will call .get on swing thread try { - emulationSerivce.emulate(coordinates.getTrace(), time, TaskMonitor.DUMMY); + emulationSerivce.emulate(coordinates.getPlatform(), time, TaskMonitor.DUMMY); } catch (CancelledException e) { throw new AssertionError(e); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java index c0a70c0b71..335a1ad4c7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java @@ -82,12 +82,15 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm protected static final int MAX_CACHE_SIZE = 5; protected static class CacheKey implements Comparable { + // TODO: Should key on platform, not trace protected final Trace trace; + protected final TracePlatform platform; protected final TraceSchedule time; private final int hashCode; - public CacheKey(Trace trace, TraceSchedule time) { - this.trace = Objects.requireNonNull(trace); + public CacheKey(TracePlatform platform, TraceSchedule time) { + this.platform = Objects.requireNonNull(platform); + this.trace = platform.getTrace(); this.time = Objects.requireNonNull(time); this.hashCode = Objects.hash(trace, time); } @@ -425,7 +428,8 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } @Override - public CompletableFuture backgroundEmulate(Trace trace, TraceSchedule time) { + public CompletableFuture backgroundEmulate(TracePlatform platform, TraceSchedule time) { + Trace trace = platform.getTrace(); if (!traceManager.getOpenTraces().contains(trace)) { throw new IllegalArgumentException( "Cannot emulate a trace unless it's opened in the tool."); @@ -433,7 +437,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm if (time.isSnapOnly()) { return CompletableFuture.completedFuture(time.getSnap()); } - return requests.get(new CacheKey(trace, time)); + return requests.get(new CacheKey(platform, time)); } protected TraceSnapshot findScratch(Trace trace, TraceSchedule time) { @@ -459,12 +463,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm protected long doEmulate(CacheKey key, TaskMonitor monitor) throws CancelledException { Trace trace = key.trace; - /** - * TODO: object and/or platform should somehow be incorporated into the key, the schedule? - * something? - */ - DebuggerCoordinates current = traceManager.resolveTrace(trace); - TracePlatform platform = current.getPlatform(); + TracePlatform platform = key.platform; TraceSchedule time = key.time; CachedEmulator ce; @@ -521,6 +520,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm return; } // Cause object-register support to copy values into new register spaces + // TODO: I wish this were not necessary monitor.setMessage("Creating register spaces"); try (UndoableTransaction tid = UndoableTransaction.start(trace, "Prepare emulation")) { for (TraceThread thread : time.getThreads(trace)) { @@ -530,8 +530,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } @Override - public long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor) + public long emulate(TracePlatform platform, TraceSchedule time, TaskMonitor monitor) throws CancelledException { + Trace trace = platform.getTrace(); if (!traceManager.getOpenTraces().contains(trace)) { throw new IllegalArgumentException( "Cannot emulate a trace unless it's opened in the tool."); @@ -539,12 +540,13 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm if (time.isSnapOnly()) { return time.getSnap(); } - return doEmulate(new CacheKey(trace, time), monitor); + return doEmulate(new CacheKey(platform, time), monitor); } @Override public DebuggerPcodeMachine getCachedEmulator(Trace trace, TraceSchedule time) { - CachedEmulator ce = cache.get(new CacheKey(trace, time)); + CachedEmulator ce = + cache.get(new CacheKey(trace.getPlatformManager().getHostPlatform(), time)); return ce == null ? null : ce.emulator; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java index b09a483617..1ef3989032 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java @@ -241,7 +241,7 @@ public enum ProgramEmulationUtils { if (stack != null) { CompilerSpec cSpec = trace.getBaseCompilerSpec(); Address sp = cSpec.stackGrowsNegative() - ? stack.getMaxAddress() + ? stack.getMaxAddress().addWrap(1) : stack.getMinAddress(); Register regSP = cSpec.getStackPointer(); if (regSP != null) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java index 083d27aea8..8b1a944c19 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java @@ -84,7 +84,7 @@ public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAcc return CompletableFuture.completedFuture(false); } AddressSetView hostView = platform.mapGuestToHost(guestView); - return recorder.readMemoryBlocks(hostView, TaskMonitor.DUMMY, false) + return recorder.readMemoryBlocks(hostView, TaskMonitor.DUMMY) .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) .thenCompose(__ -> recorder.flushTransactions()) .thenAccept(__ -> platform.getTrace().flushEvents()) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java index edc9b921fc..a2d6854ab1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java @@ -96,7 +96,7 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist toRead.add(register); } } - return recorder.captureThreadRegisters(thread, 0, toRead) + return recorder.captureThreadRegisters(platform, thread, 0, toRead) .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) .thenCompose(__ -> recorder.flushTransactions()) .thenAccept(__ -> platform.getTrace().flushEvents()) @@ -108,7 +108,7 @@ public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegist if (!isLive()) { return CompletableFuture.completedFuture(false); } - return recorder.writeRegister(thread, frame, address.getPhysicalAddress(), data) + return recorder.writeRegister(platform, thread, frame, address.getPhysicalAddress(), data) .thenCompose(__ -> recorder.getTarget().getModel().flushEvents()) .thenCompose(__ -> recorder.flushTransactions()) .thenAccept(__ -> platform.getTrace().flushEvents()) diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultMemoryRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultMemoryRecorder.java index 3d1adc29b4..eed28a59e3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultMemoryRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultMemoryRecorder.java @@ -46,9 +46,9 @@ public class DefaultMemoryRecorder implements ManagedMemoryRecorder { this.memoryManager = trace.getMemoryManager(); } - public CompletableFuture> captureProcessMemory(AddressSetView set, - TaskMonitor monitor, boolean toMap) { - return RecorderUtils.INSTANCE.readMemoryBlocks(recorder, BLOCK_BITS, set, monitor, toMap); + public CompletableFuture captureProcessMemory(AddressSetView set, + TaskMonitor monitor) { + return RecorderUtils.INSTANCE.readMemoryBlocks(recorder, BLOCK_BITS, set, monitor); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java index ce47761f75..c4ffc0e812 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorder.java @@ -42,6 +42,7 @@ import ghidra.program.model.lang.RegisterValue; import ghidra.trace.model.Trace; import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.modules.TraceSection; @@ -268,12 +269,11 @@ public class DefaultTraceRecorder implements TraceRecorder { /*---------------- CAPTURE METHODS -------------------*/ @Override - public CompletableFuture> readMemoryBlocks(AddressSetView set, - TaskMonitor monitor, boolean toMap) { + public CompletableFuture readMemoryBlocks(AddressSetView set, TaskMonitor monitor) { if (set.isEmpty()) { - return CompletableFuture.completedFuture(new TreeMap<>()); + return AsyncUtils.NIL; } - return memoryRecorder.captureProcessMemory(set, monitor, toMap); + return memoryRecorder.captureProcessMemory(set, monitor); } @Override @@ -315,11 +315,10 @@ public class DefaultTraceRecorder implements TraceRecorder { } @Override - public CompletableFuture> captureThreadRegisters( - TraceThread thread, int frameLevel, - Set registers) { + public CompletableFuture captureThreadRegisters( + TracePlatform platform, TraceThread thread, int frameLevel, Set registers) { DefaultThreadRecorder rec = getThreadRecorder(thread); - return rec.captureThreadRegisters(thread, frameLevel, registers); + return rec.captureThreadRegisters(thread, frameLevel, registers).thenApply(__ -> null); } /*---------------- SNAPSHOT METHODS -------------------*/ @@ -537,8 +536,8 @@ public class DefaultTraceRecorder implements TraceRecorder { } @Override - public CompletableFuture writeThreadRegisters(TraceThread thread, int frameLevel, - Map values) { + public CompletableFuture writeThreadRegisters(TracePlatform platform, TraceThread thread, + int frameLevel, Map values) { DefaultThreadRecorder rec = getThreadRecorder(thread); return (rec == null) ? null : rec.writeThreadRegisters(frameLevel, values); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java index 4ea30c6071..d034341aca 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/TraceObjectManager.java @@ -521,10 +521,14 @@ public class TraceObjectManager { } if (added.containsKey(TargetObject.VALUE_ATTRIBUTE_NAME)) { TargetRegister register = (TargetRegister) parent; - String valstr = (String) added.get(TargetObject.VALUE_ATTRIBUTE_NAME); - byte[] value = new BigInteger(valstr, 16).toByteArray(); + Object val = added.get(TargetObject.VALUE_ATTRIBUTE_NAME); ManagedThreadRecorder rec = recorder.getThreadRecorderForSuccessor(register); - rec.recordRegisterValue(register, value); + if (val instanceof String valstr) { + rec.recordRegisterValue(register, new BigInteger(valstr, 16).toByteArray()); + } + else if (val instanceof byte[] valarr) { + rec.recordRegisterValue(register, valarr); + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java index f21ce99aee..15a5beef8f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorder.java @@ -16,6 +16,7 @@ package ghidra.app.plugin.core.debug.service.model.record; import java.lang.invoke.MethodHandles; +import java.math.BigInteger; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -27,6 +28,7 @@ import ghidra.app.plugin.core.debug.service.model.DebuggerModelServicePlugin; import ghidra.app.plugin.core.debug.service.model.PermanentTransactionExecutor; import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorderListener; +import ghidra.async.AsyncFence; import ghidra.async.AsyncUtils; import ghidra.dbg.AnnotatedDebuggerAttributeListener; import ghidra.dbg.error.DebuggerMemoryAccessException; @@ -34,13 +36,16 @@ import ghidra.dbg.error.DebuggerModelAccessException; import ghidra.dbg.target.*; import ghidra.dbg.target.TargetEventScope.TargetEventType; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; +import ghidra.dbg.util.PathMatcher; import ghidra.dbg.util.PathUtils; +import ghidra.pcode.utils.Utils; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; import ghidra.trace.database.module.TraceObjectSection; import ghidra.trace.model.Trace; import ghidra.trace.model.breakpoint.*; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceObjectMemoryRegion; import ghidra.trace.model.modules.*; @@ -471,13 +476,13 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { @Override public boolean isRegisterBankAccessible(TargetRegisterBank bank) { - // TODO: This seems a little aggressive, but the accessbility thing is already out of hand + // TODO: This seems a little aggressive, but the accessibility thing is already out of hand return true; } @Override public boolean isRegisterBankAccessible(TraceThread thread, int frameLevel) { - // TODO: This seems a little aggressive, but the accessbility thing is already out of hand + // TODO: This seems a little aggressive, but the accessibility thing is already out of hand return true; } @@ -486,16 +491,88 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { return memoryRecorder.getAccessible(); } - @Override - public CompletableFuture> captureThreadRegisters( - TraceThread thread, int frameLevel, Set registers) { - return CompletableFuture.completedFuture(Map.of()); + protected TargetRegisterContainer getTargetRegisterContainer(TraceThread thread, + int frameLevel) { + if (!(thread instanceof TraceObjectThread tot)) { + throw new AssertionError(); + } + TraceObject objThread = tot.getObject(); + TraceObject regContainer = objThread.queryRegisterContainer(frameLevel); + if (regContainer == null) { + Msg.error(this, + "No register container for " + thread + " and frame " + frameLevel + " in trace"); + return null; + } + TargetObject result = + target.getModel().getModelObject(regContainer.getCanonicalPath().getKeyList()); + if (result == null) { + Msg.error(this, + "No register container for " + thread + " and frame " + frameLevel + " on target"); + return null; + } + return (TargetRegisterContainer) result; } @Override - public CompletableFuture writeThreadRegisters(TraceThread thread, int frameLevel, - Map values) { - throw new UnsupportedOperationException(); + public CompletableFuture captureThreadRegisters( + TracePlatform platform, TraceThread thread, int frameLevel, Set registers) { + TargetRegisterContainer regContainer = getTargetRegisterContainer(thread, frameLevel); + /** + * TODO: Seems I should be able to single out specific registers.... Is this convention + * universal, or do some models allow refreshing on a register-by-register basis? If so, + * what communicates that convention? + */ + if (regContainer == null) { + return AsyncUtils.NIL; + } + return regContainer.resync(); + } + + protected static byte[] encodeValue(int byteLength, BigInteger value) { + return Utils.bigIntegerToBytes(value, byteLength, true); + } + + @Override + public CompletableFuture writeThreadRegisters(TracePlatform platform, TraceThread thread, + int frameLevel, Map values) { + TargetRegisterContainer regContainer = getTargetRegisterContainer(thread, frameLevel); + if (regContainer == null) { + return AsyncUtils.NIL; + } + Map> writesByBank = new HashMap<>(); + for (RegisterValue rv : values.values()) { + Register register = rv.getRegister(); + PathMatcher matcher = + platform.getConventionalRegisterPath(regContainer.getSchema(), List.of(), register); + Collection regs = matcher.getCachedSuccessors(regContainer).values(); + if (regs.isEmpty()) { + Msg.warn(this, "No register object for " + register); + } + for (TargetObject objRegUntyped : regs) { + TargetRegister objReg = (TargetRegister) objRegUntyped; + List pathBank = objReg.getModel() + .getRootSchema() + .searchForAncestor(TargetRegisterBank.class, objReg.getPath()); + if (pathBank == null) { + Msg.warn(this, "No register bank for " + register); + continue; + } + TargetRegisterBank objBank = + (TargetRegisterBank) objReg.getModel().getModelObject(pathBank); + if (objBank == null) { + Msg.warn(this, "No register bank for " + register); + continue; + } + writesByBank.computeIfAbsent(objBank, __ -> new HashMap<>()) + .put(objReg, encodeValue(objReg.getByteLength(), rv.getUnsignedValue())); + } + } + AsyncFence fence = new AsyncFence(); + for (Map.Entry> ent : writesByBank + .entrySet()) { + fence.include(ent.getKey().writeRegisters(ent.getValue())); + } + return fence.ready(); } @Override @@ -509,10 +586,8 @@ public class ObjectBasedTraceRecorder implements TraceRecorder { } @Override - public CompletableFuture> readMemoryBlocks( - AddressSetView set, TaskMonitor monitor, boolean returnResult) { - return RecorderUtils.INSTANCE.readMemoryBlocks(this, BLOCK_BITS, set, monitor, - returnResult); + public CompletableFuture readMemoryBlocks(AddressSetView set, TaskMonitor monitor) { + return RecorderUtils.INSTANCE.readMemoryBlocks(this, BLOCK_BITS, set, monitor); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/RecorderUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/RecorderUtils.java index 7064328e8d..3be64da08b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/RecorderUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/record/RecorderUtils.java @@ -15,8 +15,6 @@ */ package ghidra.app.plugin.core.debug.service.model.record; -import java.util.NavigableMap; -import java.util.TreeMap; import java.util.concurrent.CompletableFuture; import ghidra.app.services.TraceRecorder; @@ -45,9 +43,8 @@ public enum RecorderUtils { return result; } - public CompletableFuture> readMemoryBlocks( - TraceRecorder recorder, int blockBits, AddressSetView set, TaskMonitor monitor, - boolean returnResult) { + public CompletableFuture readMemoryBlocks( + TraceRecorder recorder, int blockBits, AddressSetView set, TaskMonitor monitor) { // NOTE: I don't intend to warn about the number of requests. // They're delivered in serial, and there's a cancel button that works @@ -61,7 +58,6 @@ public enum RecorderUtils { monitor.initialize(total); monitor.setMessage("Reading memory"); // TODO: Read blocks in parallel? Probably NO. Tends to overload the connector. - NavigableMap result = returnResult ? new TreeMap<>() : null; return AsyncUtils.each(TypeSpec.VOID, expSet.iterator(), (r, loop) -> { AddressRangeChunker blocks = new AddressRangeChunker(r, blockSize); AsyncUtils.each(TypeSpec.VOID, blocks.iterator(), (blk, inner) -> { @@ -69,15 +65,11 @@ public enum RecorderUtils { monitor.incrementProgress(1); CompletableFuture future = recorder.readMemory(blk.getMinAddress(), (int) blk.getLength()); - future.thenAccept(data -> { - if (returnResult) { - result.put(blk.getMinAddress(), data); - } - }).exceptionally(e -> { + future.exceptionally(e -> { Msg.error(this, "Could not read " + blk + ": " + e); return null; // Continue looping on errors }).thenApply(__ -> !monitor.isCancelled()).handle(inner::repeatWhile); }).thenApply(v -> !monitor.isCancelled()).handle(loop::repeatWhile); - }).thenApply(__ -> result); + }); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index 5b876ae519..a01840db2a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -651,7 +651,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin "Cannot navigate to coordinates with execution schedules, " + "because the emulation service is not available."); } - return emulationService.backgroundEmulate(coordinates.getTrace(), coordinates.getTime()); + return emulationService.backgroundEmulate(coordinates.getPlatform(), coordinates.getTime()); } protected CompletableFuture prepareViewAndFireEvent(DebuggerCoordinates coordinates) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java index ccf79d252b..bdb7b2c785 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java @@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture; import ghidra.app.plugin.core.debug.service.emulation.*; import ghidra.framework.plugintool.ServiceInfo; import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -83,13 +84,29 @@ public interface DebuggerEmulationService { * costs. On the other hand, the service should be careful to invalidate cached results when the * recorded machine state in a trace changes. * - * @param trace the trace containing the initial state + * @param platform the trace platform containing the initial state * @param time the time coordinates, including initial snap, steps, and p-code steps * @param monitor a monitor for cancellation and progress reporting * @return the snap in the trace's scratch space where the realized state is stored * @throws CancelledException if the emulation is cancelled */ - long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor) throws CancelledException; + long emulate(TracePlatform platform, TraceSchedule time, TaskMonitor monitor) + throws CancelledException; + + /** + * Emulate using the trace's "host" platform + * + * @see #emulate(TracePlatform, TraceSchedule, TaskMonitor) + * @param trace + * @param time + * @param monitor + * @return + * @throws CancelledException + */ + default long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor) + throws CancelledException { + return emulate(trace.getPlatformManager().getHostPlatform(), time, monitor); + } /** * Invoke {@link #emulate(Trace, TraceSchedule, TaskMonitor)} in the background @@ -97,15 +114,15 @@ public interface DebuggerEmulationService { *

* This is the preferred means of performing emulation. Because the underlying emulator may * request a blocking read from a target, it is important that - * {@link #emulate(Trace, TraceSchedule, TaskMonitor)} is never called by the Swing - * thread. + * {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor)} is never called by the + * Swing thread. * - * @param trace the trace containing the initial state + * @param platform the trace platform containing the initial state * @param time the time coordinates, including initial snap, steps, and p-code steps * @return a future which completes with the result of - * {@link #emulate(Trace, TraceSchedule, TaskMonitor)} + * {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor)} */ - CompletableFuture backgroundEmulate(Trace trace, TraceSchedule time); + CompletableFuture backgroundEmulate(TracePlatform platform, TraceSchedule time); /** * The the cached emulator for the given trace and time @@ -116,6 +133,9 @@ public interface DebuggerEmulationService { *

* WARNING: This emulator belongs to this service. Stepping it, or otherwise manipulating * it without the service's knowledge can lead to unintended consequences. + *

+ * TODO: Should cache by (Platform, Time) instead, but need a way to distinguish platform in the + * trace's time table. * * @param trace the trace containing the initial state * @param time the time coordinates, including initial snap, steps, and p-code steps diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java index 8eec698903..aec0106e2f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerStateEditingService.java @@ -91,9 +91,8 @@ public interface DebuggerStateEditingService { default CompletableFuture setRegister(RegisterValue value) { Register register = value.getRegister(); - boolean isBigEndian = getCoordinates().getTrace().getBaseLanguage().isBigEndian(); byte[] bytes = Utils.bigIntegerToBytes(value.getUnsignedValue(), register.getNumBytes(), - isBigEndian); + register.isBigEndian()); return setVariable(register.getAddress(), bytes); } } @@ -115,6 +114,12 @@ public interface DebuggerStateEditingService { StateEditor createStateEditor(DebuggerCoordinates coordinates); + /** + * Create a state editor whose coordinates follow the trace manager for the given trace + * + * @param trace the trace to follow + * @return the editor + */ StateEditor createStateEditor(Trace trace); StateEditingMemoryHandler createStateEditor(TraceProgramView view); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java index 0a1b05d5d6..beedd916e9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java @@ -31,6 +31,7 @@ import ghidra.program.model.lang.*; import ghidra.trace.model.Trace; import ghidra.trace.model.breakpoint.TraceBreakpoint; import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.modules.TraceModule; @@ -263,15 +264,16 @@ public interface TraceRecorder { * Nevertheless, this method can force the retrieval of a given set of registers from the * target. * + * @param platform the platform whose language defines the registers * @param thread the trace thread associated with the desired target thread * @param frameLevel the number of stack frames to "unwind", likely 0 * @param registers the base registers, as viewed by the trace - * @return a future which completes with the captured values + * @return a future which completes when the commands succeed * @throws IllegalArgumentException if no {@link TargetRegisterBank} is known for the given * thread */ - CompletableFuture> captureThreadRegisters(TraceThread thread, - int frameLevel, Set registers); + CompletableFuture captureThreadRegisters(TracePlatform platform, + TraceThread thread, int frameLevel, Set registers); /** * Write a target thread's registers. @@ -280,6 +282,7 @@ public interface TraceRecorder { * Note that the model and recorder should cause values successfully written on the target to be * updated in the trace. The caller should not update the trace out of band. * + * @param platform the platform whose language defines the registers * @param thread the trace thread associated with the desired target thread * @param frameLevel the number of stack frames to "unwind", likely 0 * @param values the values to write @@ -287,8 +290,8 @@ public interface TraceRecorder { * @throws IllegalArgumentException if no {@link TargetRegisterBank} is known for the given * thread */ - CompletableFuture writeThreadRegisters(TraceThread thread, int frameLevel, - Map values); + CompletableFuture writeThreadRegisters(TracePlatform platform, TraceThread thread, + int frameLevel, Map values); /** * Read (and capture) a range of target memory @@ -320,20 +323,14 @@ public interface TraceRecorder { * *

* This task is relatively error tolerant. If a block or region cannot be captured -- a common - * occurrence -- the error is logged, but the task may still complete "successfully." For large - * captures, it is recommended to set {@code returnResult} to false. The recorder will capture - * the bytes into the trace where they can be retrieved later. For small captures, and where - * bypassing the database may offer some advantage, set {@code returnResult} to true, and the - * captured bytes will be returned in an interval map. Connected intervals may or may not be - * joined. + * occurrence -- the error is logged, but the task may still complete "successfully." * * @param set the addresses to capture, as viewed in the trace * @param monitor a monitor for displaying task steps * @param returnResult true to complete with results, false to complete with null * @return a future which completes when the task finishes */ - CompletableFuture> readMemoryBlocks(AddressSetView set, - TaskMonitor monitor, boolean returnResult); + CompletableFuture readMemoryBlocks(AddressSetView set, TaskMonitor monitor); /** * Write a variable (memory or register) of the given thread or the process @@ -350,13 +347,13 @@ public interface TraceRecorder { * @param data the value to write * @return a future which completes when the write is complete */ - default CompletableFuture writeVariable(TraceThread thread, int frameLevel, - Address address, byte[] data) { + default CompletableFuture writeVariable(TracePlatform platform, TraceThread thread, + int frameLevel, Address address, byte[] data) { if (address.isMemoryAddress()) { return writeMemory(address, data); } if (address.isRegisterAddress()) { - return writeRegister(thread, frameLevel, address, data); + return writeRegister(platform, thread, frameLevel, address, data); } throw new IllegalArgumentException("Address is not in a recognized space: " + address); } @@ -370,21 +367,21 @@ public interface TraceRecorder { * @param data the value to write * @return a future which completes when the write is complete */ - default CompletableFuture writeRegister(TraceThread thread, int frameLevel, - Address address, byte[] data) { - Language lang = getTrace().getBaseLanguage(); - Register register = lang.getRegister(address, data.length); + default CompletableFuture writeRegister(TracePlatform platform, TraceThread thread, + int frameLevel, Address address, byte[] data) { + Register register = platform.getLanguage().getRegister(address, data.length); if (register == null) { throw new IllegalArgumentException( "Cannot identify the (single) register to write: " + address); } RegisterValue rv = new RegisterValue(register, - Utils.bytesToBigInteger(data, data.length, lang.isBigEndian(), false)); + Utils.bytesToBigInteger(data, data.length, register.isBigEndian(), false)); TraceMemorySpace regs = getTrace().getMemoryManager().getMemoryRegisterSpace(thread, frameLevel, false); - rv = TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, getSnap(), regs, true); - return writeThreadRegisters(thread, frameLevel, Map.of(rv.getRegister(), rv)); + rv = TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, platform, getSnap(), regs, + true); + return writeThreadRegisters(platform, thread, frameLevel, Map.of(rv.getRegister(), rv)); } /** diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java index e128f596ed..c6ffd181c6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/debug/flatapi/FlatDebuggerAPI.java @@ -47,6 +47,7 @@ import ghidra.program.util.ProgramLocation; import ghidra.trace.model.Trace; import ghidra.trace.model.TraceLocation; import ghidra.trace.model.breakpoint.TraceBreakpointKind.TraceBreakpointKindSet; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryOperations; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.program.TraceProgramView; @@ -158,7 +159,7 @@ public interface FlatDebuggerAPI { * Get the current trace * * @see #getCurrentDebuggerCoordinates() - * @return the trace + * @return the trace, or null */ default Trace getCurrentTrace() { return getTraceManager().getCurrentTrace(); @@ -192,6 +193,45 @@ public interface FlatDebuggerAPI { return trace; } + /** + * Get the current trace platform + * + * @return the trace platform, or null + */ + default TracePlatform getCurrentPlatform() { + return getTraceManager().getCurrentPlatform(); + } + + /** + * Get the current trace platform, throwing an exception if there isn't one + * + * @return the trace platform + * @throws IllegalStateException if there is no current trace platform + */ + default TracePlatform requireCurrentPlatform() { + TracePlatform platform = getCurrentPlatform(); + if (platform == null) { + // NB: Yes I've left off "platform" + // It's less confusing, and if there's a trace, there's always a platform + throw new IllegalStateException("There is no current trace"); + } + return platform; + } + + /** + * Require that the given platform is not null + * + * @param platform the platform + * @return the platform + * @throws IllegalStateException if the platform is null + */ + default TracePlatform requirePlatform(TracePlatform platform) { + if (platform == null) { + throw new IllegalStateException("There is no platform"); + } + return platform; + } + /** * Get the current thread * @@ -594,6 +634,25 @@ public interface FlatDebuggerAPI { return emulateLaunch(requireCurrentProgram(), address); } + /** + * Emulate the given trace platform as specified in the given schedule and display the result in + * the UI + * + * @param platform the trace platform + * @param time the schedule of steps + * @param monitor a monitor for the emulation + * @return true if successful + * @throws CancelledException if the user cancelled via the given monitor + */ + default boolean emulate(TracePlatform platform, TraceSchedule time, TaskMonitor monitor) + throws CancelledException { + // Use the script's thread to perform the actual emulation + getEmulationService().emulate(platform, time, monitor); + // This should just display the cached state + getTraceManager().activateTime(time); + return true; + } + /** * Emulate the given trace as specified in the given schedule and display the result in the UI * @@ -605,11 +664,7 @@ public interface FlatDebuggerAPI { */ default boolean emulate(Trace trace, TraceSchedule time, TaskMonitor monitor) throws CancelledException { - // Use the script's thread to perform the actual emulation - getEmulationService().emulate(trace, time, monitor); - // This should just display the cached state - getTraceManager().activateTime(time); - return true; + return emulate(trace.getPlatformManager().getHostPlatform(), time, monitor); } /** @@ -622,7 +677,7 @@ public interface FlatDebuggerAPI { * @throws IllegalStateException if there is no current trace */ default boolean emulate(TraceSchedule time, TaskMonitor monitor) throws CancelledException { - return emulate(requireCurrentTrace(), time, monitor); + return emulate(requireCurrentPlatform(), time, monitor); } /** @@ -636,13 +691,13 @@ public interface FlatDebuggerAPI { */ default boolean stepEmuInstruction(long count, TaskMonitor monitor) throws CancelledException { DebuggerCoordinates current = getCurrentDebuggerCoordinates(); - Trace trace = requireCurrentTrace(); + TracePlatform platform = requireCurrentPlatform(); TraceThread thread = current.getThread(); TraceSchedule time = current.getTime(); TraceSchedule stepped = count <= 0 - ? time.steppedBackward(trace, -count) + ? time.steppedBackward(platform.getTrace(), -count) : time.steppedForward(requireThread(thread), count); - return emulate(trace, stepped, monitor); + return emulate(platform, stepped, monitor); } /** @@ -655,13 +710,13 @@ public interface FlatDebuggerAPI { */ default boolean stepEmuPcodeOp(int count, TaskMonitor monitor) throws CancelledException { DebuggerCoordinates current = getCurrentDebuggerCoordinates(); - Trace trace = requireCurrentTrace(); + TracePlatform platform = requireCurrentPlatform(); TraceThread thread = current.getThread(); TraceSchedule time = current.getTime(); TraceSchedule stepped = count <= 0 ? time.steppedPcodeBackward(-count) : time.steppedPcodeForward(requireThread(thread), count); - return emulate(trace, stepped, monitor); + return emulate(platform, stepped, monitor); } /** @@ -678,13 +733,13 @@ public interface FlatDebuggerAPI { */ default boolean skipEmuInstruction(long count, TaskMonitor monitor) throws CancelledException { DebuggerCoordinates current = getCurrentDebuggerCoordinates(); - Trace trace = requireCurrentTrace(); + TracePlatform platform = requireCurrentPlatform(); TraceThread thread = current.getThread(); TraceSchedule time = current.getTime(); TraceSchedule stepped = count <= 0 - ? time.steppedBackward(trace, -count) + ? time.steppedBackward(platform.getTrace(), -count) : time.skippedForward(requireThread(thread), count); - return emulate(trace, stepped, monitor); + return emulate(platform, stepped, monitor); } /** @@ -701,13 +756,13 @@ public interface FlatDebuggerAPI { */ default boolean skipEmuPcodeOp(int count, TaskMonitor monitor) throws CancelledException { DebuggerCoordinates current = getCurrentDebuggerCoordinates(); - Trace trace = requireCurrentTrace(); + TracePlatform platform = requireCurrentPlatform(); TraceThread thread = current.getThread(); TraceSchedule time = current.getTime(); TraceSchedule stepped = count <= 0 ? time.steppedPcodeBackward(-count) : time.skippedPcodeForward(requireThread(thread), count); - return emulate(trace, stepped, monitor); + return emulate(platform, stepped, monitor); } /** @@ -720,11 +775,11 @@ public interface FlatDebuggerAPI { */ default boolean patchEmu(String sleigh, TaskMonitor monitor) throws CancelledException { DebuggerCoordinates current = getCurrentDebuggerCoordinates(); - Trace trace = requireCurrentTrace(); + TracePlatform platform = requireCurrentPlatform(); TraceThread thread = current.getThread(); TraceSchedule time = current.getTime(); - TraceSchedule patched = time.patched(requireThread(thread), sleigh); - return emulate(trace, patched, monitor); + TraceSchedule patched = time.patched(requireThread(thread), platform.getLanguage(), sleigh); + return emulate(platform, patched, monitor); } /** @@ -771,7 +826,7 @@ public interface FlatDebuggerAPI { if (recorder.getSnap() != snap) { return; } - waitOn(recorder.readMemoryBlocks(new AddressSet(safeRange(start, length)), monitor, false)); + waitOn(recorder.readMemoryBlocks(new AddressSet(safeRange(start, length)), monitor)); waitOn(recorder.getTarget().getModel().flushEvents()); waitOn(recorder.flushTransactions()); trace.flushEvents(); @@ -903,6 +958,7 @@ public interface FlatDebuggerAPI { /** * Copy registers from target to trace, if applicable and not already cached * + * @param platform the platform whose language defines the registers * @param thread the trace thread to update * @param frame the frame level, 0 being the innermost * @param snap the snap, to determine whether target values are applicable @@ -911,8 +967,8 @@ public interface FlatDebuggerAPI { * @throws ExecutionException if an error occurs * @throws TimeoutException if the operation times out */ - default void refreshRegistersIfLive(TraceThread thread, int frame, long snap, - Collection registers) + default void refreshRegistersIfLive(TracePlatform platform, TraceThread thread, int frame, + long snap, Collection registers) throws InterruptedException, ExecutionException, TimeoutException { Trace trace = thread.getTrace(); TraceRecorder recorder = getModelService().getRecorder(trace); @@ -924,7 +980,7 @@ public interface FlatDebuggerAPI { } Set asSet = registers instanceof Set ? (Set) registers : Set.copyOf(registers); - waitOn(recorder.captureThreadRegisters(thread, frame, asSet)); + waitOn(recorder.captureThreadRegisters(platform, thread, frame, asSet)); waitOn(recorder.getTarget().getModel().flushEvents()); waitOn(recorder.flushTransactions()); trace.flushEvents(); @@ -933,16 +989,17 @@ public interface FlatDebuggerAPI { /** * Read several registers from the given context, refreshing from target if needed * + * @param platform the platform whose language defines the registers * @param thread the trace thread * @param frame the source frame level, 0 being the innermost * @param snap the source snap * @param registers the source registers * @return the list of register values, or null on error */ - default List readRegisters(TraceThread thread, int frame, long snap, - Collection registers) { + default List readRegisters(TracePlatform platform, TraceThread thread, int frame, + long snap, Collection registers) { try { - refreshRegistersIfLive(thread, frame, snap, registers); + refreshRegistersIfLive(platform, thread, frame, snap, registers); } catch (InterruptedException | ExecutionException | TimeoutException e) { return null; @@ -961,9 +1018,9 @@ public interface FlatDebuggerAPI { * @see #readRegisters(TraceThread, int, long, Collection) * @return the register's value, or null on error */ - default RegisterValue readRegister(TraceThread thread, int frame, long snap, - Register register) { - List result = readRegisters(thread, frame, snap, Set.of(register)); + default RegisterValue readRegister(TracePlatform platform, TraceThread thread, int frame, + long snap, Register register) { + List result = readRegisters(platform, thread, frame, snap, Set.of(register)); return result == null ? null : result.get(0); } @@ -974,8 +1031,8 @@ public interface FlatDebuggerAPI { */ default List readRegisters(Collection registers) { DebuggerCoordinates current = getCurrentDebuggerCoordinates(); - return readRegisters(requireThread(current.getThread()), current.getFrame(), - current.getSnap(), registers); + return readRegisters(requireCurrentPlatform(), requireThread(current.getThread()), + current.getFrame(), current.getSnap(), registers); } /** @@ -1030,6 +1087,27 @@ public interface FlatDebuggerAPI { return readRegisters(validateRegisterNames(requireCurrentTrace().getBaseLanguage(), names)); } + /** + * Read a register from the current context, refreshing from the target if needed + * + * @param platform the platform whose language defines the register + * @param register the register + * @return the value, or null on error + */ + default RegisterValue readRegister(TracePlatform platform, Register register) { + DebuggerCoordinates current = getCurrentDebuggerCoordinates(); + if (platform.getTrace() != current.getTrace()) { + throw new IllegalArgumentException("Given platform is not from the current trace"); + } + Language language = platform.getLanguage(); + if (!register.equals(language.getRegister(register.getName()))) { + throw new IllegalArgumentException( + "Register " + register + " is not in language " + language); + } + return readRegister(platform, requireThread(current.getThread()), current.getFrame(), + current.getSnap(), register); + } + /** * Read a register from the current context, refreshing from the target if needed * @@ -1037,9 +1115,7 @@ public interface FlatDebuggerAPI { * @return the value, or null on error */ default RegisterValue readRegister(Register register) { - DebuggerCoordinates current = getCurrentDebuggerCoordinates(); - return readRegister(requireThread(current.getThread()), current.getFrame(), - current.getSnap(), register); + return readRegister(requireCurrentPlatform(), register); } /** @@ -1049,7 +1125,9 @@ public interface FlatDebuggerAPI { * @throws IllegalArgumentException if the name is invalid */ default RegisterValue readRegister(String name) { - return readRegister(validateRegisterName(requireCurrentTrace().getBaseLanguage(), name)); + TracePlatform platform = requireCurrentPlatform(); + Register register = validateRegisterName(platform.getLanguage(), name); + return readRegister(platform, register); } /** @@ -1059,10 +1137,10 @@ public interface FlatDebuggerAPI { * @return the program counter, or null if not known */ default Address getProgramCounter(DebuggerCoordinates coordinates) { - Language language = requireTrace(coordinates.getTrace()).getBaseLanguage(); - RegisterValue value = - readRegister(requireThread(coordinates.getThread()), coordinates.getFrame(), - coordinates.getSnap(), language.getProgramCounter()); + TracePlatform platform = requirePlatform(coordinates.getPlatform()); + Language language = platform.getLanguage(); + RegisterValue value = readRegister(platform, requireThread(coordinates.getThread()), + coordinates.getFrame(), coordinates.getSnap(), language.getProgramCounter()); if (!value.hasValue()) { return null; } @@ -1085,10 +1163,10 @@ public interface FlatDebuggerAPI { * @return the stack pointer, or null if not known */ default Address getStackPointer(DebuggerCoordinates coordinates) { - CompilerSpec cSpec = requireTrace(coordinates.getTrace()).getBaseCompilerSpec(); - RegisterValue value = - readRegister(requireThread(coordinates.getThread()), coordinates.getFrame(), - coordinates.getSnap(), cSpec.getStackPointer()); + TracePlatform platform = requirePlatform(coordinates.getPlatform()); + CompilerSpec cSpec = platform.getCompilerSpec(); + RegisterValue value = readRegister(platform, requireThread(coordinates.getThread()), + coordinates.getFrame(), coordinates.getSnap(), cSpec.getStackPointer()); if (!value.hasValue()) { return null; } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java index 77e454ff84..a3b1acea2c 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java @@ -605,18 +605,27 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest modelService.addModel(mb.testModel); } - protected TraceRecorder recordAndWaitSync() throws Throwable { - createTestModel(); + protected void populateTestModel() throws Throwable { mb.createTestProcessesAndThreads(); - mb.createTestThreadRegisterBanks(); // NOTE: Test mapper uses TOYBE64 mb.testProcess1.regs.addRegistersFromLanguage(getToyBE64Language(), Register::isBaseRegister); + mb.createTestThreadRegisterBanks(); mb.testProcess1.addRegion(".text", mb.rng(0x00400000, 0x00401000), "rx"); mb.testProcess1.addRegion(".data", mb.rng(0x00600000, 0x00601000), "rw"); + } - TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); + protected TargetObject chooseTarget() { + return mb.testProcess1; + } + + protected TraceRecorder recordAndWaitSync() throws Throwable { + createTestModel(); + populateTestModel(); + + TargetObject target = chooseTarget(); + TraceRecorder recorder = modelService.recordTarget(target, + createTargetTraceMapper(target), ActionSource.AUTOMATIC); waitRecorder(recorder); return recorder; diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index fdd32837fc..f148cf05eb 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -1272,7 +1272,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI .createRegion(".text", 0, tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); thread1 = tb.getOrAddThread("Thread1", 0); - tb.exec(0, 0, thread1, "RIP = 0x00400000;"); + tb.exec(0, thread1, 0, "RIP = 0x00400000;"); } TraceThread thread2; @@ -1282,7 +1282,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI .createRegion(".text", 0, tb2.range(0x200, 0x3ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); thread2 = tb2.getOrAddThread("Thread2", 0); - tb2.exec(0, 0, thread2, "PC = 0x100;"); + tb2.exec(0, thread2, 0, "PC = 0x100;"); } traceManager.openTrace(tb.trace); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderGuestTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderGuestTest.java new file mode 100644 index 0000000000..dc3387875a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderGuestTest.java @@ -0,0 +1,167 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.register; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.Before; +import org.junit.experimental.categories.Category; + +import com.google.common.collect.Range; + +import generic.test.category.NightlyCategory; +import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; +import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper; +import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper; +import ghidra.app.plugin.core.debug.service.editing.DebuggerStateEditingServicePlugin; +import ghidra.app.plugin.core.debug.service.model.DebuggerModelServicePlugin; +import ghidra.app.services.TraceRecorder; +import ghidra.dbg.target.TargetObject; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.listing.TraceCodeSpace; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.database.UndoableTransaction; + +@Category(NightlyCategory.class) // this may actually be an @PortSensitive test +public class DebuggerRegistersProviderGuestTest extends DebuggerRegistersProviderTest { + + protected TraceGuestPlatform toy; + + @Override + protected void createTrace() throws IOException { + createTrace("DATA:BE:64:default"); + } + + public void createToyPlatform() throws Exception { + try (UndoableTransaction tid = tb.startTransaction()) { + toy = tb.trace.getPlatformManager() + .addGuestPlatform(getToyBE64Language().getDefaultCompilerSpec()); + toy.addMappedRange(tb.addr(0), tb.addr(toy, 0), -1); + toy.addMappedRegisterRange(); + } + } + + @Before + @Override + public void setUpRegistersProviderTest() throws Exception { + registersPlugin = addPlugin(tool, DebuggerRegistersPlugin.class); + registersProvider = waitForComponentProvider(DebuggerRegistersProvider.class); + listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); + editingService = addPlugin(tool, DebuggerStateEditingServicePlugin.class); + + createTrace(); + createToyPlatform(); + + r0 = tb.reg(toy, "r0"); + pc = toy.getLanguage().getProgramCounter(); + sp = toy.getCompilerSpec().getStackPointer(); + contextreg = toy.getLanguage().getContextBaseRegister(); + + pch = tb.reg(toy, "pch"); + pcl = tb.reg(toy, "pcl"); + + r0h = tb.reg(toy, "r0h"); + r0l = tb.reg(toy, "r0l"); + + r0Struct = new StructureDataType("r0_struct", 0); + r0Struct.add(SignedDWordDataType.dataType, "hi", ""); + r0Struct.add(DWordDataType.dataType, "lo", ""); + + baseRegs = toy.getLanguage() + .getRegisters() + .stream() + .filter(Register::isBaseRegister) + .collect(Collectors.toSet()); + } + + @Override + protected TargetObject chooseTarget() { + return mb.testModel.session; + } + + @Override + protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target) + throws Exception { + return new ObjectBasedDebuggerTargetTraceMapper(target, + new LanguageID("DATA:BE:64:default"), new CompilerSpecID("pointer64"), Set.of()) { + @Override + public TraceRecorder startRecording(DebuggerModelServicePlugin service, + Trace trace) { + useTrace(trace); + return super.startRecording(service, trace); + } + }; + } + + @Override + protected TraceRecorder recordAndWaitSync() throws Throwable { + TraceRecorder recorder = super.recordAndWaitSync(); + createToyPlatform(); + return recorder; + } + + @Override + protected TracePlatform getPlatform() { + return toy; + } + + @Override + protected void activateThread(TraceThread thread) { + traceManager.activate(traceManager.resolveThread(thread).platform(toy)); + } + + @Override + protected void addRegisterValues(TraceThread thread, UndoableTransaction tid) { + TraceMemorySpace regVals = + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + regVals.putBytes(toy, 0, pc, tb.buf(0, 0, 0, 0, 0, 0x40, 0, 0)); + regVals.putBytes(toy, 0, sp, tb.buf(0x1f, 0, 0, 0, 0, 0, 0, 0)); + regVals.putBytes(toy, 0, r0, tb.buf(1, 2, 3, 4, 5, 6, 7, 8)); + } + + @Override + protected void addRegisterTypes(TraceThread thread, UndoableTransaction tid) + throws CodeUnitInsertionException { + TraceCodeSpace regCode = + tb.trace.getCodeManager().getCodeRegisterSpace(thread, true); + regCode.definedData().create(toy, Range.atLeast(0L), pc, PointerDataType.dataType); + // TODO: Pointer needs to be to ram, not register space + regCode.definedData().create(toy, Range.atLeast(0L), r0, r0Struct); + } + + @Override + public void testDefaultSelection() throws Exception { + traceManager.openTrace(tb.trace); + + TraceThread thread = addThread(); + addRegisterValues(thread); + traceManager.activate(traceManager.resolveThread(thread).platform(toy)); + waitForSwing(); + + assertEquals(DebuggerRegistersProvider.collectCommonRegisters(toy.getCompilerSpec()), + registersProvider.getSelectionFor(toy)); + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java index a479520dfe..269a48e68d 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java @@ -47,6 +47,7 @@ import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.listing.DBTraceCodeSpace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.listing.*; import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.memory.TraceMemorySpace; @@ -115,6 +116,14 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG } } + protected TracePlatform getPlatform() { + return tb.host; + } + + protected void activateThread(TraceThread thread) { + traceManager.activateThread(thread); + } + protected void addRegisterValues(TraceThread thread) { try (UndoableTransaction tid = tb.startTransaction()) { addRegisterValues(thread, tid); @@ -252,7 +261,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); assertPCRowValueEmpty(); @@ -269,12 +278,12 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG // TODO: Use another language to test effect of recorded non-common registers TraceThread thread = addThread(); addRegisterValues(thread); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); assertEquals( DebuggerRegistersProvider.collectCommonRegisters(tb.trace.getBaseCompilerSpec()), - registersProvider.getSelectionFor(thread)); + registersProvider.getSelectionFor(tb.host)); } @Test @@ -283,7 +292,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG TraceThread thread = addThread(); addRegisterValues(thread); - traceManager.activateThread(thread); + activateThread(thread); waitForDomainObject(tb.trace); assertPCRowValuePopulated(); @@ -299,7 +308,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG mb.testBank1.writeRegister("pc", new byte[] { 0x00, 0x40, 0x00, 0x00 }); waitForSwing(); - traceManager.activateThread(recorder.getTraceThread(mb.testThread1)); + activateThread(recorder.getTraceThread(mb.testThread1)); waitForSwing(); assertPCRowValuePopulated(); @@ -309,7 +318,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG public void testLiveActivateThenAddValuesPopulatesPanel() throws Throwable { TraceRecorder recorder = recordAndWaitSync(); traceManager.openTrace(recorder.getTrace()); - traceManager.activateThread(recorder.getTraceThread(mb.testThread1)); + activateThread(recorder.getTraceThread(mb.testThread1)); waitForSwing(); mb.testBank1.writeRegister("pc", new byte[] { 0x00, 0x40, 0x00, 0x00 }); @@ -326,7 +335,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); addRegisterValues(thread); @@ -343,7 +352,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG TraceThread thread = addThread(); addRegisterValues(thread); addRegisterTypes(thread); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); assertPCRowTypePopulated(); @@ -355,7 +364,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); addRegisterValues(thread); addRegisterTypes(thread); @@ -373,7 +382,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR); @@ -395,7 +404,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG long viewSnap = traceManager.getCurrent().getViewSnap(); assertTrue(DBTraceUtils.isScratch(viewSnap)); assertEquals(BigInteger.valueOf(0x1234), - regVals.getValue(viewSnap, r0).getUnsignedValue()); + regVals.getValue(getPlatform(), viewSnap, r0).getUnsignedValue()); assertEquals(BigInteger.valueOf(0x1234), row.getValue()); }); } @@ -411,7 +420,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR); @@ -437,7 +446,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG long viewSnap = traceManager.getCurrent().getViewSnap(); assertTrue(DBTraceUtils.isScratch(viewSnap)); assertEquals(BigInteger.valueOf(encodeDouble(1234)), - regVals.getValue(viewSnap, r0).getUnsignedValue()); + regVals.getValue(getPlatform(), viewSnap, r0).getUnsignedValue()); assertEquals(BigInteger.valueOf(encodeDouble(1234)), row.getValue()); }); } @@ -449,7 +458,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); RegisterRow row = findRegisterRow(pc); @@ -459,7 +468,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG DBTraceCodeSpace regCode = tb.trace.getCodeManager().getCodeRegisterSpace(thread, false); assertNotNull(regCode); - TraceData data = regCode.data().getForRegister(0L, pc); + TraceData data = regCode.data().getForRegister(getPlatform(), 0L, pc); assertTypeEquals(PointerDataType.dataType, data.getDataType()); } @@ -469,9 +478,9 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG TraceThread thread = addThread(); try (UndoableTransaction tid = tb.startTransaction()) { - tb.exec(0, 0, thread, "pc = 100;"); + tb.exec(getPlatform(), 0, thread, 0, "pc = 100;"); } - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); RegisterRow row = findRegisterRow(pc); @@ -481,7 +490,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG DBTraceCodeSpace regCode = tb.trace.getCodeManager().getCodeRegisterSpace(thread, false); assertNotNull(regCode); - TraceData data = regCode.data().getForRegister(0L, pc); + TraceData data = regCode.data().getForRegister(getPlatform(), 0L, pc); assertTypeEquals(LongLongDataType.dataType, data.getDataType()); assertEquals("64h", row.getRepresentation()); @@ -505,7 +514,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); RegisterRow rowL = findRegisterRow(r0l); @@ -518,10 +527,11 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG tb.trace.getCodeManager().getCodeRegisterSpace(thread, false); assertNotNull(regCode); // It's two units, not a struct with two components - assertNull(regCode.data().getForRegister(0L, r0)); - TraceData dataL = regCode.data().getForRegister(0L, r0l); + TracePlatform platform = getPlatform(); + assertNull(regCode.data().getForRegister(platform, 0L, r0)); + TraceData dataL = regCode.data().getForRegister(platform, 0L, r0l); assertTypeEquals(PointerDataType.dataType, dataL.getDataType()); - TraceData dataH = regCode.data().getForRegister(0L, r0h); + TraceData dataH = regCode.data().getForRegister(platform, 0L, r0h); assertTypeEquals(PointerDataType.dataType, dataH.getDataType()); } @@ -530,7 +540,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); // Group adds into a single transaction try (UndoableTransaction tid = tb.startTransaction()) { addRegisterValues(thread, tid); @@ -564,7 +574,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); try (UndoableTransaction tid = tb.startTransaction()) { addRegisterValues(thread, tid); @@ -595,7 +605,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG assertFalse(registersProvider.actionEnableEdits.isEnabled()); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); assertTrue(registersProvider.actionEnableEdits.isEnabled()); @@ -613,7 +623,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG TraceThread thread = addThread(); addRegisterValues(thread); - traceManager.activateThread(thread); + activateThread(thread); waitForDomainObject(tb.trace); assertEquals(0, traceManager.getCurrentSnap()); @@ -625,8 +635,9 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); TraceCodeSpace regCode = tb.trace.getCodeManager().getCodeRegisterSpace(thread, true); - regVals.putBytes(1, r0, tb.buf(1, 1, 2, 2, 3, 3, 4, 4)); - regCode.definedData().create(Range.atLeast(1L), r0, r0Struct); + TracePlatform platform = getPlatform(); + regVals.putBytes(platform, 1, r0, tb.buf(1, 1, 2, 2, 3, 3, 4, 4)); + regCode.definedData().create(platform, Range.atLeast(1L), r0, r0Struct); } waitForDomainObject(tb.trace); @@ -641,7 +652,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG TraceThread thread = addThread(); addRegisterValues(thread); addRegisterTypes(thread); - traceManager.activateThread(thread); + activateThread(thread); traceManager.activateSnap(1); waitForDomainObject(tb.trace); @@ -651,7 +662,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG try (UndoableTransaction tid = tb.startTransaction()) { TraceCodeSpace regCode = tb.trace.getCodeManager().getCodeRegisterSpace(thread, true); - TraceCodeUnit code = regCode.codeUnits().getContaining(1, r0); + TraceCodeUnit code = regCode.codeUnits().getContaining(getPlatform(), 1, r0); code.setEndSnap(0); } waitForDomainObject(tb.trace); @@ -673,7 +684,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG TraceThread thread = addThread(); addRegisterValues(thread); addRegisterTypes(thread); - traceManager.activateThread(thread); + activateThread(thread); waitForDomainObject(tb.trace); assertR0RowTypePopulated(); @@ -681,7 +692,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG try (UndoableTransaction tid = tb.startTransaction()) { TraceCodeSpace regCode = tb.trace.getCodeManager().getCodeRegisterSpace(thread, true); - TraceCodeUnit code = regCode.codeUnits().getContaining(1, r0); + TraceCodeUnit code = regCode.codeUnits().getContaining(getPlatform(), 1, r0); code.delete(); } waitForDomainObject(tb.trace); @@ -702,7 +713,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG assertFalse(registersProvider.actionCreateSnapshot.isEnabled()); - traceManager.activateThread(thread1); + activateThread(thread1); waitForSwing(); assertTrue(registersProvider.actionCreateSnapshot.isEnabled()); @@ -716,7 +727,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG assertEquals("[Registers]", cloned.getTitle()); assertEquals("Thread1", cloned.getSubTitle()); - traceManager.activateThread(thread2); + activateThread(thread2); waitForSwing(); assertEquals(thread2, registersProvider.current.getThread()); @@ -757,7 +768,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG // Ensure cause is goto PC, not register tracking listingPlugin.setTrackingSpec( LocationTrackingSpec.fromConfigName(NoneLocationTrackingSpec.CONFIG_NAME)); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); assertPCRowTypePopulated(); @@ -784,7 +795,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG assertFalse(registersProvider.actionSelectRegisters.isEnabled()); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); assertTrue(registersProvider.regsTableModel.getRowIndex(findRegisterRow(pc)) >= 0); @@ -823,21 +834,21 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG TraceThread thread1 = addThread(); TraceThread thread2 = addThread("Thread2"); addRegisterValues(thread1); - traceManager.activateThread(thread2); + activateThread(thread2); waitForSwing(); assertEquals(thread2, registersProvider.current.getThread()); assertPCRowValueEmpty(); // Should have no effect - traceManager.activateThread(thread2); + activateThread(thread2); waitForSwing(); assertPCRowValueEmpty(); assertEquals(thread2, registersProvider.current.getThread()); // Should have effect - traceManager.activateThread(thread1); + activateThread(thread1); waitForSwing(); assertEquals(thread1, registersProvider.current.getThread()); @@ -867,7 +878,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG assertPCRowValueEmpty(); // Should just work - traceManager.activateThread(thread1); + activateThread(thread1); waitForSwing(); assertEquals(thread1, registersProvider.current.getThread()); @@ -880,7 +891,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); addRegisterValues(thread); @@ -895,9 +906,9 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); TraceCodeSpace regCode = tb.trace.getCodeManager().getCodeRegisterSpace(thread, true); - regVals.putBytes(10, r0, tb.buf(0, 0, 0, 0, 0, 0, 0, 0)); + regVals.putBytes(getPlatform(), 10, r0, tb.buf(0, 0, 0, 0, 0, 0, 0, 0)); // NB. the manager should have split the data unit at the value change - TraceCodeUnit cu = regCode.codeUnits().getContaining(10, r0); + TraceCodeUnit cu = regCode.codeUnits().getContaining(getPlatform(), 10, r0); assertNotNull(cu); assertEquals(10, cu.getStartSnap()); cu.delete(); @@ -922,7 +933,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG traceManager.openTrace(tb.trace); TraceThread thread = addThread(); - traceManager.activateThread(thread); + activateThread(thread); waitForSwing(); addRegisterValues(thread); @@ -935,11 +946,12 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG try (UndoableTransaction tid = tb.startTransaction()) { TraceMemorySpace regVals = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, 1, true); - regVals.putBytes(0, pc, tb.buf(0, 0, 0, 0, 0, 0x50, 0, 0)); + regVals.putBytes(getPlatform(), 0, pc, tb.buf(0, 0, 0, 0, 0, 0x50, 0, 0)); TraceCodeSpace regCode = tb.trace.getCodeManager().getCodeRegisterSpace(thread, 1, true); - regCode.definedData().create(Range.atLeast(0L), pc, QWordDataType.dataType); + regCode.definedData() + .create(getPlatform(), Range.atLeast(0L), pc, QWordDataType.dataType); } waitForDomainObject(tb.trace); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/LargestSubDebuggerRegisterMapperTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/LargestSubDebuggerRegisterMapperTest.java index b1931f4d56..a537b41a00 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/LargestSubDebuggerRegisterMapperTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/LargestSubDebuggerRegisterMapperTest.java @@ -26,7 +26,7 @@ import org.junit.Test; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.services.ActionSource; import ghidra.app.services.TraceRecorder; -import ghidra.dbg.model.TestTargetRegister; +import ghidra.dbg.model.*; import ghidra.dbg.target.*; import ghidra.dbg.util.CollectionUtils.Delta; import ghidra.program.model.lang.*; @@ -54,6 +54,18 @@ public class LargestSubDebuggerRegisterMapperTest extends AbstractGhidraHeadedDe } } + protected static void assertSameRegister(TargetRegister expected, TargetObject actual) { + if (actual instanceof TestTargetRegister tr) { + assertEquals(expected, tr); + } + else if (actual instanceof TestTargetRegisterValue rv) { + assertEquals(expected, rv.desc); + } + else { + fail(); + } + } + @Before public void setUpMapperTest() throws Throwable { createTestModel(); @@ -98,8 +110,8 @@ public class LargestSubDebuggerRegisterMapperTest extends AbstractGhidraHeadedDe TestTargetRegister tEAX = Objects.requireNonNull(mb.testProcess1.regs.getCachedElements().get("EAX")); - assertEquals(tRAX, waitForValue(() -> rm.getTargetRegister("rax"))); - assertEquals(tEAX, waitForValue(() -> rm.getTargetRegister("eax"))); // Seems reasonable + assertSameRegister(tRAX, waitForValue(() -> rm.getTargetRegister("rax"))); + assertSameRegister(tEAX, waitForValue(() -> rm.getTargetRegister("eax"))); // Seems reasonable } @Test @@ -130,7 +142,7 @@ public class LargestSubDebuggerRegisterMapperTest extends AbstractGhidraHeadedDe Register lRAX = Objects.requireNonNull(getSLEIGH_X86_64_LANGUAGE().getRegister("RAX")); TargetRegister tReg = waitForValue(() -> rm.traceToTarget(lRAX)); - assertEquals(tRAX, tReg); + assertSameRegister(tRAX, tReg); } @Test @@ -175,7 +187,7 @@ public class LargestSubDebuggerRegisterMapperTest extends AbstractGhidraHeadedDe TestTargetRegister tEAX = Objects.requireNonNull(mb.testProcess1.regs.getCachedElements().get("EAX")); - assertEquals(tEAX, waitForValue(() -> rm.getTargetRegister("eax"))); + assertSameRegister(tEAX, waitForValue(() -> rm.getTargetRegister("eax"))); assertNull(rm.getTargetRegister("rax")); } @@ -211,7 +223,7 @@ public class LargestSubDebuggerRegisterMapperTest extends AbstractGhidraHeadedDe Register lRAX = Objects.requireNonNull(getSLEIGH_X86_64_LANGUAGE().getRegister("RAX")); TargetRegister tReg = waitForValue(() -> rm.traceToTarget(lRAX)); - assertEquals(tEAX, tReg); + assertSameRegister(tEAX, tReg); } @Test @@ -256,7 +268,7 @@ public class LargestSubDebuggerRegisterMapperTest extends AbstractGhidraHeadedDe // NOTE: This is not allowed, but still generates a courtesy warning assertNull(rm.targetToTrace("eax", genBytes4())); - Delta delta = mb.testProcess1.regs.changeElements(List.of("RAX"), List.of(), "WoW64"); + Delta delta = mb.testProcess1.regs.removeRegister(lRAX, "WoW64"); assertFalse(delta.removed.isEmpty()); waitForPass(() -> assertNull(rm.getTargetRegister("rax"))); @@ -287,7 +299,7 @@ public class LargestSubDebuggerRegisterMapperTest extends AbstractGhidraHeadedDe assertEquals("RAX", ent.getKey()); assertArrayEquals(genBytes8(), ent.getValue()); - Delta delta = mb.testProcess1.regs.changeElements(List.of("RAX"), List.of(), "WoW64"); + Delta delta = mb.testProcess1.regs.removeRegister(lRAX, "WoW64"); assertFalse(delta.removed.isEmpty()); waitForPass(() -> assertNull(rm.getTargetRegister("rax"))); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceGuestTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceGuestTest.java new file mode 100644 index 0000000000..0eebe1efc8 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceGuestTest.java @@ -0,0 +1,89 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.service.editing; + +import java.io.IOException; +import java.util.Set; + +import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper; +import ghidra.app.plugin.core.debug.mapping.ObjectBasedDebuggerTargetTraceMapper; +import ghidra.app.plugin.core.debug.service.model.DebuggerModelServicePlugin; +import ghidra.app.services.TraceRecorder; +import ghidra.dbg.target.TargetObject; +import ghidra.program.model.lang.CompilerSpecID; +import ghidra.program.model.lang.LanguageID; +import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerStateEditingServiceGuestTest extends DebuggerStateEditingServiceTest { + protected TraceGuestPlatform platform; + + public void createToyPlatform() { + try (UndoableTransaction tid = tb.startTransaction()) { + platform = tb.trace.getPlatformManager() + .addGuestPlatform(getToyBE64Language().getDefaultCompilerSpec()); + platform.addMappedRegisterRange(); + platform.addMappedRange(tb.addr(0), tb.addr(platform, 0), -1); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + @Override + protected void createAndOpenTrace() throws IOException { + createAndOpenTrace("DATA:BE:64:default"); + createToyPlatform(); + } + + @Override + protected void activateTrace() { + traceManager.activatePlatform(platform); + } + + @Override + protected TracePlatform getPlatform() { + return platform; + } + + @Override + protected TargetObject chooseTarget() { + return mb.testModel.session; + } + + @Override + protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target) + throws Exception { + return new ObjectBasedDebuggerTargetTraceMapper(target, + new LanguageID("DATA:BE:64:default"), new CompilerSpecID("pointer64"), Set.of()) { + @Override + public TraceRecorder startRecording(DebuggerModelServicePlugin service, + Trace trace) { + useTrace(trace); + return super.startRecording(service, trace); + } + }; + } + + @Override + protected TraceRecorder recordAndWaitSync() throws Throwable { + TraceRecorder recorder = super.recordAndWaitSync(); + createToyPlatform(); + return recorder; + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java index e86e5c4aa4..5ae60bfb78 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java @@ -23,8 +23,7 @@ import java.nio.ByteBuffer; import org.junit.Before; import org.junit.Test; -import ghidra.app.plugin.assembler.Assembler; -import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.assembler.*; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.services.DebuggerStateEditingService; @@ -32,24 +31,35 @@ import ghidra.app.services.DebuggerStateEditingService.StateEditingMode; import ghidra.app.services.DebuggerStateEditingService.StateEditor; import ghidra.app.services.TraceRecorder; import ghidra.dbg.target.TargetRegisterBank; -import ghidra.pcode.exec.DebuggerPcodeUtils; -import ghidra.pcode.exec.PcodeExecutor; import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemoryAccessException; import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.database.UndoableTransaction; public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebuggerGUITest { - private DebuggerStateEditingService editingService; + protected DebuggerStateEditingService editingService; - private Register r0; - private Register r0h; - private RegisterValue rv1234; - private RegisterValue rv5678; - private RegisterValue rvHigh1234; + protected Register r0; + protected Register r0h; + protected RegisterValue rv1234; + protected RegisterValue rv5678; + protected RegisterValue rvHigh1234; + + protected StateEditor createStateEditor() { + return editingService.createStateEditor(tb.trace); + } + + protected void activateTrace() { + traceManager.activateTrace(tb.trace); + } + + protected TracePlatform getPlatform() { + return tb.trace.getPlatformManager().getHostPlatform(); + } @Before public void setUpEditorTest() throws Exception { @@ -71,9 +81,11 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge * only work if they don't refer to any register. */ createAndOpenTrace(); + activateTrace(); + editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4))); } @@ -82,7 +94,10 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge createAndOpenTrace(); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR); - StateEditor editor = editingService.createStateEditor(tb.trace); + activateTrace(); + waitForSwing(); + + StateEditor editor = createStateEditor(); waitOn(editor.setRegister(rv1234)); } @@ -95,9 +110,10 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge // NB. TraceManager should automatically activate the first thread tb.getOrAddThread("Threads[0]", 0); } + activateTrace(); waitForSwing(); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4))); waitForSwing(); @@ -120,9 +136,10 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge // NB. TraceManager should automatically activate the first thread thread = tb.getOrAddThread("Threads[0]", 0); } + activateTrace(); waitForSwing(); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setRegister(rv1234)); waitForSwing(); @@ -131,7 +148,9 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge assertTrue(DBTraceUtils.isScratch(snap)); RegisterValue value = - tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0); + tb.trace.getMemoryManager() + .getMemoryRegisterSpace(thread, false) + .getValue(getPlatform(), snap, r0); assertEquals(rv1234, value); } @@ -143,14 +162,14 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge try (UndoableTransaction tid = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread TraceThread thread = tb.getOrAddThread("Threads[0]", 0); - PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates( - env.getTool(), DebuggerCoordinates.NOWHERE.thread(thread)); - - Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); - asm.assemble(tb.addr(0x00400000), "imm r0,#123"); - executor.executeSleigh("pc = 0x00400000;"); + Assembler asm = Assemblers.getAssembler(getPlatform().getLanguage()); + AssemblyBuffer buf = new AssemblyBuffer(asm, tb.addr(getPlatform(), 0x00400000)); + buf.assemble("imm r0,#123"); + tb.trace.getMemoryManager() + .putBytes(0, tb.addr(0x00400000), ByteBuffer.wrap(buf.getBytes())); + tb.exec(getPlatform(), 0, thread, 0, "pc = 0x00400000;"); } - traceManager.activateTrace(tb.trace); + activateTrace(); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR); waitForSwing(); @@ -158,7 +177,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge traceManager.activateTime(step1); waitForPass(() -> assertEquals(step1, traceManager.getCurrent().getTime())); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setVariable(tb.addr(0x00600000), tb.arr(1, 2, 3, 4))); waitForSwing(); @@ -181,14 +200,14 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge try (UndoableTransaction tid = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread thread = tb.getOrAddThread("Threads[0]", 0); - PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates( - env.getTool(), DebuggerCoordinates.NOWHERE.thread(thread)); - - Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); - asm.assemble(tb.addr(0x00400000), "imm r0,#123"); - executor.executeSleigh("pc = 0x00400000;"); + Assembler asm = Assemblers.getAssembler(getPlatform().getLanguage()); + AssemblyBuffer buf = new AssemblyBuffer(asm, tb.addr(getPlatform(), 0x00400000)); + buf.assemble("imm r0,#123"); + tb.trace.getMemoryManager() + .putBytes(0, tb.addr(0x00400000), ByteBuffer.wrap(buf.getBytes())); + tb.exec(getPlatform(), 0, thread, 0, "pc = 0x00400000;"); } - traceManager.activateTrace(tb.trace); + activateTrace(); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_EMULATOR); waitForSwing(); @@ -196,7 +215,7 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge traceManager.activateTime(step1); waitForPass(() -> assertEquals(step1, traceManager.getCurrent().getTime())); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setRegister(rv1234)); waitForSwing(); @@ -205,8 +224,9 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge long snap = current.getViewSnap(); assertTrue(DBTraceUtils.isScratch(snap)); - RegisterValue value = - tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0); + RegisterValue value = tb.trace.getMemoryManager() + .getMemoryRegisterSpace(thread, false) + .getValue(getPlatform(), snap, r0); assertEquals(rv1234, value); } @@ -219,9 +239,10 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge // NB. TraceManager should automatically activate the first thread tb.getOrAddThread("Threads[0]", 0); } + activateTrace(); waitForSwing(); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4))); waitOn(editor.setVariable(tb.addr(0x00400002), tb.arr(5, 6, 7, 8))); waitForSwing(); @@ -246,9 +267,10 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge // NB. TraceManager should automatically activate the first thread thread = tb.getOrAddThread("Threads[0]", 0); } + activateTrace(); waitForSwing(); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setRegister(rv1234)); waitOn(editor.setRegister(rv5678)); waitForSwing(); @@ -258,8 +280,9 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge assertTrue(DBTraceUtils.isScratch(snap)); assertEquals(1, current.getTime().patchCount()); // Check coalesced - RegisterValue value = - tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0); + RegisterValue value = tb.trace.getMemoryManager() + .getMemoryRegisterSpace(thread, false) + .getValue(getPlatform(), snap, r0); assertEquals(rv5678, value); } @@ -268,8 +291,10 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge // NB. Definitely no thread required createAndOpenTrace(); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + activateTrace(); + waitForSwing(); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); // NB. Editor creates its own transaction waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4))); waitForSwing(); @@ -288,8 +313,10 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge // NB. Definitely no thread required createAndOpenTrace(); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TRACE); + activateTrace(); + waitForSwing(); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); // NB. Editor creates its own transaction waitOn(editor.setRegister(rv1234)); } @@ -305,9 +332,10 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge // NB. TraceManager should automatically activate the first thread thread = tb.getOrAddThread("Threads[0]", 0); } + activateTrace(); waitForSwing(); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); // NB. Editor creates its own transaction waitOn(editor.setRegister(rv1234)); waitForSwing(); @@ -316,20 +344,22 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge long snap = current.getViewSnap(); assertEquals(0, snap); - RegisterValue value = - tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false).getValue(snap, r0); + RegisterValue value = tb.trace.getMemoryManager() + .getMemoryRegisterSpace(thread, false) + .getValue(getPlatform(), snap, r0); assertEquals(rv1234, value); } @Test public void testWriteTargetMemory() throws Throwable { TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(recorder.getTrace()); + traceManager.openTrace(tb.trace); + activateTrace(); traceManager.activateThread(recorder.getTraceThread(mb.testThread1)); waitForSwing(); editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4))); assertArrayEquals(mb.arr(1, 2, 3, 4), @@ -341,12 +371,14 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge TraceRecorder recorder = recordAndWaitSync(); TargetRegisterBank bank = (TargetRegisterBank) mb.testThread1.getCachedAttribute("RegisterBank"); - traceManager.openTrace(recorder.getTrace()); + traceManager.openTrace(tb.trace); + activateTrace(); + waitForSwing(); traceManager.activateThread(recorder.getTraceThread(mb.testThread1)); waitForSwing(); editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setRegister(rv1234)); assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 4, 0xd2), waitOn(bank.readRegister("r0"))); @@ -357,19 +389,20 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge TraceRecorder recorder = recordAndWaitSync(); TargetRegisterBank bank = (TargetRegisterBank) mb.testThread1.getCachedAttribute("RegisterBank"); - traceManager.openTrace(recorder.getTrace()); + traceManager.openTrace(tb.trace); + activateTrace(); TraceThread thread = recorder.getTraceThread(mb.testThread1); traceManager.activateThread(thread); waitForSwing(); editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setRegister(rv1234)); waitForPass(() -> { TraceMemorySpace regs = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false); assertNotNull(regs); - RegisterValue value = regs.getValue(traceManager.getCurrentSnap(), r0); + RegisterValue value = regs.getValue(getPlatform(), traceManager.getCurrentSnap(), r0); assertEquals(rv1234, value); }); waitOn(editor.setRegister(rvHigh1234)); @@ -380,64 +413,70 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge @Test(expected = MemoryAccessException.class) public void testWriteTargetMemoryNotPresentErr() throws Throwable { TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(recorder.getTrace()); + traceManager.openTrace(tb.trace); + activateTrace(); traceManager.activateThread(recorder.getTraceThread(mb.testThread1)); waitForSwing(); editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET); traceManager.activateSnap(traceManager.getCurrentSnap() - 1); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4))); } @Test(expected = MemoryAccessException.class) public void testWriteTargetRegisterNotPresentErr() throws Throwable { TraceRecorder recorder = recordAndWaitSync(); - traceManager.openTrace(recorder.getTrace()); + traceManager.openTrace(tb.trace); + activateTrace(); traceManager.activateThread(recorder.getTraceThread(mb.testThread1)); waitForSwing(); editingService.setCurrentMode(recorder.getTrace(), StateEditingMode.WRITE_TARGET); traceManager.activateSnap(traceManager.getCurrentSnap() - 1); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setRegister(rv1234)); } @Test(expected = MemoryAccessException.class) public void testWriteTargetMemoryNotAliveErr() throws Throwable { createAndOpenTrace(); + activateTrace(); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4))); } @Test(expected = MemoryAccessException.class) public void testWriteTargetRegisterNotAliveErr() throws Throwable { createAndOpenTrace(); + activateTrace(); editingService.setCurrentMode(tb.trace, StateEditingMode.WRITE_TARGET); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setRegister(rv1234)); } @Test(expected = MemoryAccessException.class) public void testWriteReadOnlyMemoryErr() throws Throwable { createAndOpenTrace(); + activateTrace(); editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setVariable(tb.addr(0x00400000), tb.arr(1, 2, 3, 4))); } @Test(expected = MemoryAccessException.class) public void testWriteReadOnlyRegisterErr() throws Throwable { createAndOpenTrace(); + activateTrace(); editingService.setCurrentMode(tb.trace, StateEditingMode.READ_ONLY); - StateEditor editor = editingService.createStateEditor(tb.trace); + StateEditor editor = createStateEditor(); waitOn(editor.setRegister(rv1234)); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java index 080e5d8887..4b1d3b6ebb 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java @@ -328,7 +328,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU } long scratch = - emulationPlugin.emulate(tb.trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY); + emulationPlugin.emulate(platform, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY); TraceMemorySpace regs = mem.getMemoryRegisterSpace(thread, false); assertEquals("deadbeefcafebabe", regs.getViewValue(platform, scratch, tb.reg(platform, "RAX")) diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java index d8c1bba217..0f1195c605 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/record/ObjectBasedTraceRecorderTest.java @@ -15,10 +15,10 @@ */ package ghidra.app.plugin.core.debug.service.model.record; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.oneOf; import static org.junit.Assert.*; -import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.*; @@ -39,6 +39,7 @@ import ghidra.dbg.target.TargetEventScope.TargetEventType; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRangeImpl; +import ghidra.program.model.lang.Language; import ghidra.trace.model.ImmutableTraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.breakpoint.*; @@ -334,9 +335,9 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU is(oneOf(null, TraceMemoryState.UNKNOWN))); byte[] data = new byte[10]; - assertNull(waitOn(recorder.readMemoryBlocks( + waitOn(recorder.readMemoryBlocks( tb.set(tb.range(0x00400123, 0x00400123), tb.range(0x00600ffe, 0x00601000)), - TaskMonitor.DUMMY, false))); + TaskMonitor.DUMMY)); flushAndWait(); assertEquals(Set.of( stateEntry(0x00400000, 0x00400fff, TraceMemoryState.KNOWN), @@ -493,15 +494,22 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU public void testRecordRegisters() throws Throwable { startRecording(); mb.createTestProcessesAndThreads(); - // TODO: Adjust schema to reflect merging of container and bank // TODO: Other bank placements. Will need different schemas, though :/ mb.createTestThreadRegisterBanks(); - TestTargetRegisterValue targetPC = new TestTargetRegisterValue(mb.testBank1, "pc", true, - BigInteger.valueOf(0x00400123), 8); + Language toy = getToyBE64Language(); + mb.testProcess1.regs.addRegister(toy.getRegister("pc")); + mb.testProcess1.regs.addRegister(toy.getRegister("r0")); + TestTargetRegisterValue targetPC = + (TestTargetRegisterValue) mb.testBank1.getCachedAttribute("pc"); + targetPC.changeAttributes(List.of(), + Map.of(TargetRegister.VALUE_ATTRIBUTE_NAME, tb.arr(0, 0, 0, 0, 0, 0x40, 0x01, 0x23)), + "Write PC=0x00400123"); TestTargetRegisterValue targetR0 = - new TestTargetRegisterValue(mb.testBank1, "r0", false, BigInteger.ZERO, 8); - mb.testBank1.setElements(Set.of(targetPC, targetR0), "Test registers"); + (TestTargetRegisterValue) mb.testBank1.getCachedAttribute("r0"); + targetR0.changeAttributes(List.of(), + Map.of(TargetRegister.VALUE_ATTRIBUTE_NAME, tb.arr(0, 0, 0, 0, 0, 0, 0, 0)), + "Write R0=0x00000000"); flushAndWait(); TraceObjectThread thread = (TraceObjectThread) recorder.getTraceThread(mb.testThread1); @@ -517,18 +525,16 @@ public class ObjectBasedTraceRecorderTest extends AbstractGhidraHeadedDebuggerGU .findAny() .orElseThrow(); - TraceObject pc = traceBank.getElement(recorder.getSnap(), "pc").getChild(); - assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0x40, 0x01, 0x023), + TraceObject pc = traceBank.getAttribute(recorder.getSnap(), "pc").getChild(); + assertNotNull(pc); + assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0x40, 0x01, 0x23), (byte[]) pc.getAttribute(recorder.getSnap(), TargetObject.VALUE_ATTRIBUTE_NAME) .getValue()); - TraceObject r0 = traceBank.getElement(recorder.getSnap(), "r0").getChild(); + TraceObject r0 = traceBank.getAttribute(recorder.getSnap(), "r0").getChild(); + assertNotNull(r0); assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0, 0), (byte[]) r0.getAttribute(recorder.getSnap(), TargetObject.VALUE_ATTRIBUTE_NAME) .getValue()); - // TODO: Test interpretation, once mapping scheme is worked out - // TODO: How to annotate values with types, etc? - // TODO: Perhaps byte-array values are allocated in memory-like byte store? - // TODO: Brings endianness into the picture :/ } @Test diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetRegister.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetRegister.java index ea02024bc5..2dd69e9635 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetRegister.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/TargetRegister.java @@ -31,7 +31,7 @@ import ghidra.dbg.util.PathUtils; public interface TargetRegister extends TargetObject { String CONTAINER_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "container"; - String LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "length"; + String BIT_LENGTH_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "length"; /** * Get the container of this register. @@ -60,12 +60,25 @@ public interface TargetRegister extends TargetObject { * @return the length of the register */ @TargetAttributeType( - name = LENGTH_ATTRIBUTE_NAME, + name = BIT_LENGTH_ATTRIBUTE_NAME, required = true, fixed = true, hidden = true) default int getBitLength() { - return getTypedAttributeNowByName(LENGTH_ATTRIBUTE_NAME, Integer.class, 0); + return getTypedAttributeNowByName(BIT_LENGTH_ATTRIBUTE_NAME, Integer.class, 0); + } + + /** + * Get the length, in bytes, of the register + * + *

+ * For registers whose bit lengths are not a multiple of 8, this should be the minimum number of + * bytes required to bit all the bits, i.e., it should divide by 8 rounding up. + * + * @return the length of the register + */ + default int getByteLength() { + return (getBitLength() + 7) / 8; } /** diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java index 9b78f6a297..730f239130 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java @@ -317,12 +317,8 @@ public interface TargetObjectSchema { * @return the named schema */ default SchemaName getElementSchema(String index) { - for (Entry ent : getElementSchemas().entrySet()) { - if (ent.getKey().equals(index)) { - return ent.getValue(); - } - } - return getDefaultElementSchema(); + SchemaName schemaName = getElementSchemas().get(index); + return schemaName == null ? getDefaultElementSchema() : schemaName; } /** @@ -365,12 +361,8 @@ public interface TargetObjectSchema { * @return the attribute schema */ default AttributeSchema getAttributeSchema(String name) { - for (Entry ent : getAttributeSchemas().entrySet()) { - if (ent.getKey().equals(name)) { - return ent.getValue(); - } - } - return getDefaultAttributeSchema(); + AttributeSchema attributeSchema = getAttributeSchemas().get(name); + return attributeSchema == null ? getDefaultAttributeSchema() : attributeSchema; } /** @@ -935,46 +927,46 @@ public interface TargetObjectSchema { * This places some conventional restrictions / expectations on models where registers are given * on a frame-by-frame basis. The schema should present the {@link TargetRegisterContainer} as * the same object or a successor to {@link TargetStackFrame}, which must in turn be a successor - * to {@link TargetThread}. The frame level (usually an index) must be in the path from thread - * to stack frame. There can be no wild cards between the frame and the register container. For - * example, the container for {@code Threads[1]} may be {@code Threads[1].Stack[n].Registers}, - * where {@code n} is the frame level. {@code Threads[1]} would have the {@link TargetThread} + * to {@link TargetStack}. The frame level (an index) must be in the path from stack to frame. + * There can be no wild cards between the frame and the register container. For example, the + * container for {@code Threads[1]} may be {@code Threads[1].Stack[n].Registers}, where + * {@code n} is the frame level. {@code Threads[1].Stack} would have the {@link TargetStack} * interface, {@code Threads[1].Stack[0]} would have the {@link TargetStackFrame} interface, and * {@code Threads[1].Stack[0].Registers} would have the {@link TargetRegisterContainer} * interface. Note it is not sufficient for {@link TargetRegisterContainer} to be a successor of - * {@link TargetThread} with a single index between. There must be an intervening + * {@link TargetStack} with a single index between. There must be an intervening * {@link TargetStackFrame}, and the frame level (index) must precede it. * - * @param frameLevel the frameLevel, must be 0 if not applicable + * @param frameLevel the frame level. May be ignored if not applicable * @path the path of the seed object relative to the root * @return the predicates where the register container should be found, possibly empty */ default PathPredicates searchForRegisterContainer(int frameLevel, List path) { List simple = searchForSuitable(TargetRegisterContainer.class, path); if (simple != null) { - return frameLevel == 0 ? PathPredicates.pattern(simple) : PathPredicates.EMPTY; + return PathPredicates.pattern(simple); } - List threadPath = searchForAncestor(TargetThread.class, path); - if (threadPath == null) { + List stackPath = searchForSuitable(TargetStack.class, path); + if (stackPath == null) { return PathPredicates.EMPTY; } - PathPattern framePatternRelThread = - getSuccessorSchema(threadPath).searchFor(TargetStackFrame.class, false) + PathPattern framePatternRelStack = + getSuccessorSchema(stackPath).searchFor(TargetStackFrame.class, false) .getSingletonPattern(); - if (framePatternRelThread == null) { + if (framePatternRelStack == null) { return PathPredicates.EMPTY; } - if (framePatternRelThread.countWildcards() != 1) { + if (framePatternRelStack.countWildcards() != 1) { return null; } PathMatcher result = new PathMatcher(); for (String index : List.of(Integer.toString(frameLevel), "0x" + Integer.toHexString(frameLevel))) { - List framePathRelThread = - framePatternRelThread.applyKeys(index).getSingletonPath(); - List framePath = PathUtils.extend(threadPath, framePathRelThread); + List framePathRelStack = + framePatternRelStack.applyKeys(index).getSingletonPath(); + List framePath = PathUtils.extend(stackPath, framePathRelStack); List regsPath = searchForSuitable(TargetRegisterContainer.class, framePath); if (regsPath != null) { @@ -983,4 +975,36 @@ public interface TargetObjectSchema { } return result; } + + /** + * Compute the frame level of the object at the given path relative to this schema + * + *

+ * If there is no {@link TargetStackFrame} in the path, this will return 0 since it is not + * applicable to the object. If there is a stack frame in the path, this will examine its + * ancestry, up to and excluding the {@link TargetStack} for an index. If there isn't a stack in + * the path, it is assumed to be an ancestor of this schema, meaning the examination will + * exhaust the ancestry provided in the path. If no index is found, an exception is thrown, + * because the frame level is applicable, but couldn't be computed from the path given. In that + * case, the client should include more ancestry in the path. Ideally, this is invoked relative + * to the root schema. + * + * @param path the path + * @return the frame level, or 0 if not applicable + * @throws IllegalArgumentException if frame level is applicable but not given in the path + */ + default int computeFrameLevel(List path) { + List framePath = searchForAncestor(TargetStackFrame.class, path); + if (framePath == null) { + return 0; + } + List stackPath = searchForAncestor(TargetStack.class, framePath); + for (int i = stackPath == null ? 0 : stackPath.size(); i < framePath.size(); i++) { + String key = framePath.get(i); + if (PathUtils.isIndex(key)) { + return Integer.decode(PathUtils.parseIndex(key)); + } + } + throw new IllegalArgumentException("No index between stack and frame"); + } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/AbstractTestTargetRegisterBank.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/AbstractTestTargetRegisterBank.java index 316ee6c313..aecdb28261 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/AbstractTestTargetRegisterBank.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/AbstractTestTargetRegisterBank.java @@ -15,27 +15,35 @@ */ package ghidra.dbg.model; +import java.math.BigInteger; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import ghidra.dbg.error.DebuggerRegisterAccessException; import ghidra.dbg.target.TargetRegisterBank; +import ghidra.dbg.target.TargetRegisterContainer; +import ghidra.dbg.util.PathUtils; +import ghidra.pcode.utils.Utils; import ghidra.program.model.address.Address; public abstract class AbstractTestTargetRegisterBank

- extends DefaultTestTargetObject implements TargetRegisterBank { + extends DefaultTestTargetObject + implements TargetRegisterBank, TargetRegisterContainer { - protected final TestTargetRegisterContainer regs; + // TODO: Remove the separate descriptors idea + protected final TestTargetRegisterContainer descs; public final Map regVals = new HashMap<>(); public AbstractTestTargetRegisterBank(P parent, String name, String typeHint, TestTargetRegisterContainer regs) { super(parent, name, typeHint); - this.regs = regs; + this.descs = regs; + this.descs.addBank(this); changeAttributes(List.of(), Map.of( - DESCRIPTIONS_ATTRIBUTE_NAME, regs // - ), "Initialized"); + DESCRIPTIONS_ATTRIBUTE_NAME, this), + "Initialized"); + initializeValues(); } public abstract TestTargetThread getThread(); @@ -43,18 +51,19 @@ public abstract class AbstractTestTargetRegisterBank

@Override public CompletableFuture> readRegistersNamed( Collection names) { - if (!regs.getDescs().keySet().containsAll(names)) { + if (!descs.getDescs().keySet().containsAll(names)) { throw new DebuggerRegisterAccessException("No such register"); } Map result = new LinkedHashMap<>(); for (String n : names) { byte[] v = regVals.get(n); if (v == null) { - v = regs.getDescs().get(n).defaultValue(); + v = descs.getDescs().get(n).defaultValue(); } result.put(n, v); } - return model.gateFuture(regs.getModel().future(result).thenApply(__ -> { + populateObjectValues(result, "Read registers"); + return model.gateFuture(descs.getModel().future(result).thenApply(__ -> { listeners.fire.registersUpdated(this, result); return result; })); @@ -62,14 +71,14 @@ public abstract class AbstractTestTargetRegisterBank

protected CompletableFuture writeRegs(Map values, Consumer

setPC) { - if (!regs.getDescs().keySet().containsAll(values.keySet())) { + if (!descs.getDescs().keySet().containsAll(values.keySet())) { throw new DebuggerRegisterAccessException("No such register"); } Map updates = new LinkedHashMap<>(); - CompletableFuture future = regs.getModel().future(null); + CompletableFuture future = descs.getModel().future(null); for (Map.Entry ent : values.entrySet()) { String n = ent.getKey(); - TestTargetRegister desc = regs.getDescs().get(n); + TestTargetRegister desc = descs.getDescs().get(n); byte[] v = desc.normalizeValue(ent.getValue()); regVals.put(n, v); updates.put(n, v); @@ -79,12 +88,53 @@ public abstract class AbstractTestTargetRegisterBank

}); } } + populateObjectValues(updates, "Write registers"); future.thenAccept(__ -> { listeners.fire.registersUpdated(this, updates); }); return model.gateFuture(future); } + protected void addObjectValues(Collection descs, String reason) { + Set objVals = new HashSet<>(); + for (TestTargetRegister rd : descs) { + if (attributes.containsKey(rd.getName())) { + continue; + } + TestTargetRegisterValue tv = new TestTargetRegisterValue(this, rd, (BigInteger) null); + objVals.add(tv); + } + changeAttributes(List.of(), objVals, Map.of(), reason); + } + + protected void removeObjectValues(Collection descs, String reason) { + List toRemove = new ArrayList<>(); + for (TestTargetRegister rd : descs) { + toRemove.add(PathUtils.parseIndex(rd.getName())); + } + changeAttributes(toRemove, Map.of(), reason); + } + + protected void populateObjectValues(Map values, String reason) { + Set objVals = new HashSet<>(); + for (Map.Entry ent : values.entrySet()) { + TestTargetRegister rd = descs.getDescs().get(ent.getKey()); + byte[] value = ent.getValue(); + TestTargetRegisterValue tv = new TestTargetRegisterValue(this, rd, + value == null ? null : Utils.bytesToBigInteger(value, rd.byteLength, true, false)); + objVals.add(tv); + } + changeAttributes(List.of(), objVals, Map.of(), reason); + } + + protected void initializeValues() { + Map values = new HashMap<>(); + for (TestTargetRegister desc : descs.getDescs().values()) { + values.put(desc.getIndex(), null); + } + populateObjectValues(values, "Populate"); + } + public void setFromBank(AbstractTestTargetRegisterBank bank) { //Map updates = new HashMap<>(); //updates.putAll(bank.regVals); @@ -98,4 +148,12 @@ public abstract class AbstractTestTargetRegisterBank

kit.remove(); } } + + public void addRegisterDescs(Collection added, String reason) { + addObjectValues(added, reason); + } + + public void removeRegisterDescs(Collection removed, String reason) { + removeObjectValues(removed, reason); + } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegister.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegister.java index 4b761a23bb..847d1b8799 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegister.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegister.java @@ -44,8 +44,8 @@ public class TestTargetRegister changeAttributes(List.of(), Map.of( CONTAINER_ATTRIBUTE_NAME, parent, - LENGTH_ATTRIBUTE_NAME, byteLength // - ), "Initialized"); + BIT_LENGTH_ATTRIBUTE_NAME, byteLength * 8), + "Initialized"); } public byte[] normalizeValue(byte[] value) { diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegisterContainer.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegisterContainer.java index d0a1b28431..cc241b071b 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegisterContainer.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegisterContainer.java @@ -19,6 +19,7 @@ import java.util.*; import java.util.function.Predicate; import ghidra.dbg.target.TargetRegisterContainer; +import ghidra.dbg.util.CollectionUtils.Delta; import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; @@ -26,6 +27,8 @@ public class TestTargetRegisterContainer extends DefaultTestTargetObject implements TargetRegisterContainer { + private final Set> banks = new HashSet<>(); + public TestTargetRegisterContainer(TestTargetProcess parent) { super(parent, "Registers", "RegisterContainer"); } @@ -43,13 +46,50 @@ public class TestTargetRegisterContainer } add.add(getModel().newTestTargetRegister(this, register)); } - changeElements(List.of(), add, "Added registers from Ghidra language: " + language); + String reason = "Added registers from Ghidra language: " + language; + changeElements(List.of(), add, reason); + List> banks; + synchronized (this.banks) { + banks = List.copyOf(this.banks); + } + for (AbstractTestTargetRegisterBank bank : banks) { + bank.addRegisterDescs(add, reason); + } return add; } public TestTargetRegister addRegister(Register register) { - TestTargetRegister tr = getModel().newTestTargetRegister(this, register); - changeElements(List.of(), List.of(tr), "Added " + register + " from Ghidra language"); + TestTargetRegister tr = + getModel().newTestTargetRegister(this, Objects.requireNonNull(register)); + String reason = "Added " + register + " from Ghidra language"; + changeElements(List.of(), List.of(tr), reason); + List> banks; + synchronized (this.banks) { + banks = List.copyOf(this.banks); + } + for (AbstractTestTargetRegisterBank bank : banks) { + bank.addRegisterDescs(List.of(tr), reason); + } return tr; } + + public Delta removeRegister(Register register, + String reason) { + Delta result = + changeElements(List.of(register.getName()), List.of(), reason); + List> banks; + synchronized (this.banks) { + banks = List.copyOf(this.banks); + } + for (AbstractTestTargetRegisterBank bank : banks) { + bank.removeRegisterDescs(result.removed.values(), reason); + } + return result; + } + + public void addBank(AbstractTestTargetRegisterBank bank) { + synchronized (this.banks) { + this.banks.add(bank); + } + } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegisterValue.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegisterValue.java index dd2b1d03a9..7d95925ed7 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegisterValue.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetRegisterValue.java @@ -22,44 +22,42 @@ import java.util.Map; import ghidra.dbg.target.TargetRegister; import ghidra.dbg.util.PathUtils; import ghidra.pcode.utils.Utils; -import ghidra.program.model.lang.Register; -import ghidra.program.model.lang.RegisterValue; public class TestTargetRegisterValue extends DefaultTestTargetObject> implements TargetRegister { - public static TestTargetRegisterValue fromRegisterValue( - AbstractTestTargetRegisterBank parent, RegisterValue rv) { - Register register = rv.getRegister(); - return new TestTargetRegisterValue(parent, PathUtils.makeKey(register.getName()), - register.isProgramCounter(), rv.getUnsignedValue(), register.getBitLength() + 7 / 8); + public final TestTargetRegister desc; + + public TestTargetRegisterValue(AbstractTestTargetRegisterBank parent, + TestTargetRegister desc, BigInteger value) { + this(parent, desc, + value == null ? null : Utils.bigIntegerToBytes(value, desc.byteLength, true)); } - protected final int byteLength; - protected final boolean isPC; + public TestTargetRegisterValue(AbstractTestTargetRegisterBank parent, + TestTargetRegister desc, byte[] value) { + super(parent, PathUtils.parseIndex(desc.getName()), "Register"); + this.desc = desc; - public TestTargetRegisterValue(AbstractTestTargetRegisterBank parent, String name, - boolean isPC, BigInteger value, int byteLength) { - this(parent, name, isPC, Utils.bigIntegerToBytes(value, byteLength, true)); - } - - public TestTargetRegisterValue(AbstractTestTargetRegisterBank parent, String name, - boolean isPC, byte[] value) { - super(parent, name, "Register"); - this.byteLength = value.length; - this.isPC = isPC; - - changeAttributes(List.of(), Map.of( - CONTAINER_ATTRIBUTE_NAME, parent, - LENGTH_ATTRIBUTE_NAME, byteLength, - VALUE_ATTRIBUTE_NAME, value // - ), "Initialized"); + if (value == null) { + changeAttributes(List.of(), Map.of( + CONTAINER_ATTRIBUTE_NAME, parent, + BIT_LENGTH_ATTRIBUTE_NAME, desc.byteLength * 8), + "Populated"); + } + else { + changeAttributes(List.of(), Map.of( + CONTAINER_ATTRIBUTE_NAME, parent, + BIT_LENGTH_ATTRIBUTE_NAME, desc.byteLength * 8, + VALUE_ATTRIBUTE_NAME, value), + "Initialized"); + } } public void setValue(BigInteger value) { changeAttributes(List.of(), Map.of( - VALUE_ATTRIBUTE_NAME, Utils.bigIntegerToBytes(value, byteLength, true) // - ), "Set value"); + VALUE_ATTRIBUTE_NAME, Utils.bigIntegerToBytes(value, desc.byteLength, true)), + "Set value"); } } diff --git a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetThread.java b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetThread.java index d6465c4fb9..9cd22cbc99 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetThread.java +++ b/Ghidra/Debug/Framework-Debugging/src/test/java/ghidra/dbg/model/TestTargetThread.java @@ -25,8 +25,8 @@ import ghidra.dbg.util.PathUtils; public class TestTargetThread extends DefaultTestTargetObject - implements TargetThread, TargetExecutionStateful, TargetSteppable, TargetResumable, - TargetInterruptible, TargetKillable { + implements TargetThread, TargetAggregate, TargetExecutionStateful, TargetSteppable, + TargetResumable, TargetInterruptible, TargetKillable { public static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of(TargetStepKind.values()); diff --git a/Ghidra/Debug/Framework-Debugging/src/test/resources/ghidra/dbg/model/test_schema.xml b/Ghidra/Debug/Framework-Debugging/src/test/resources/ghidra/dbg/model/test_schema.xml index 9f586e8c5e..8109d9390e 100644 --- a/Ghidra/Debug/Framework-Debugging/src/test/resources/ghidra/dbg/model/test_schema.xml +++ b/Ghidra/Debug/Framework-Debugging/src/test/resources/ghidra/dbg/model/test_schema.xml @@ -94,7 +94,7 @@ - + @@ -171,6 +171,7 @@ + @@ -269,7 +270,7 @@ - + @@ -280,12 +281,13 @@ - + + - + + + - + + + + + \ No newline at end of file diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceRegistersAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceRegistersAccess.java index 0d25c4d43b..4d92b4d8ea 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceRegistersAccess.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceRegistersAccess.java @@ -25,6 +25,7 @@ import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.property.TracePropertyMap; import ghidra.trace.model.property.TracePropertyMapSpace; import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceRegisterUtils; import ghidra.trace.util.TraceTimeViewport; /** @@ -128,9 +129,7 @@ public class DefaultPcodeTraceRegistersAccess extends AbstractPcodeTraceDataAcce return null; // client should bail anyway } AddressSpace space = ops.getAddressSpace(); - return new AddressRangeImpl( - space.getOverlayAddress(range.getMinAddress()), - space.getOverlayAddress(range.getMaxAddress())); + return TraceRegisterUtils.getOverlayRange(space, range); } @Override @@ -140,12 +139,6 @@ public class DefaultPcodeTraceRegistersAccess extends AbstractPcodeTraceDataAcce return null; // client should bail anyway } AddressSpace space = ops.getAddressSpace(); - AddressSet result = new AddressSet(); - for (AddressRange rng : set) { - result.add( - space.getOverlayAddress(rng.getMinAddress()), - space.getOverlayAddress(rng.getMaxAddress())); - } - return result; + return TraceRegisterUtils.getOverlaySet(space, set); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java index 30a4d9f81b..0645e04413 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java @@ -227,6 +227,20 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager { } } + protected AddressSpace doCreateOverlaySpace(String name, AddressSpace base) { + TraceAddressFactory factory = trace.getInternalAddressFactory(); + OverlayAddressSpace space = + factory.addOverlayAddressSpace(name, true, base, base.getMinAddress().getOffset(), + base.getMaxAddress().getOffset()); + // Only if it succeeds do we store the record + DBTraceOverlaySpaceEntry ent = overlayStore.create(); + ent.set(space.getName(), base.getName()); + trace.updateViewsAddSpaceBlock(space); + trace.setChanged(new TraceChangeRecord<>(TraceOverlaySpaceChangeType.ADDED, null, + trace, null, space)); + return space; + } + public AddressSpace createOverlayAddressSpace(String name, AddressSpace base) throws DuplicateNameException { // TODO: Exclusive lock? @@ -235,17 +249,19 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager { if (factory.getAddressSpace(name) != null) { throw new DuplicateNameException("Address space " + name + " already exists."); } + return doCreateOverlaySpace(name, base); + } + } - OverlayAddressSpace space = - factory.addOverlayAddressSpace(name, true, base, base.getMinAddress().getOffset(), - base.getMaxAddress().getOffset()); - // Only if it succeeds do we store the record - DBTraceOverlaySpaceEntry ent = overlayStore.create(); - ent.set(space.getName(), base.getName()); - trace.updateViewsAddSpaceBlock(space); - trace.setChanged(new TraceChangeRecord<>(TraceOverlaySpaceChangeType.ADDED, null, - trace, null, space)); - return space; + public AddressSpace getOrCreateOverlayAddressSpace(String name, AddressSpace base) { + // TODO: Exclusive lock? + try (LockHold hold = LockHold.lock(lock.writeLock())) { + TraceAddressFactory factory = trace.getInternalAddressFactory(); + AddressSpace space = factory.getAddressSpace(name); + if (space != null) { + return space.getPhysicalSpace() == base ? space : null; + } + return doCreateOverlaySpace(name, base); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java index 7fd7207356..6cf1b1d258 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java @@ -66,7 +66,7 @@ public enum DBTraceObjectRegisterSupport { static class LazyValues { private final TraceObjectValue registerValue; private BigInteger value; - private int length = -1; + private int bitLength = -1; private byte[] be; private byte[] le; @@ -85,7 +85,11 @@ public enum DBTraceObjectRegisterSupport { "Invalid register value " + s + ". Must be hex digits only."); } } - if (val instanceof Byte b) { + else if (val instanceof byte[] arr) { + // NOTE: Reg object values are always big endian + return new BigInteger(1, arr); + } + else if (val instanceof Byte b) { return BigInteger.valueOf(b); } else if (val instanceof Short s) { @@ -106,16 +110,16 @@ public enum DBTraceObjectRegisterSupport { "'"); } - int getRegisterValueLength() throws RegisterValueException { - Object objLength = registerValue.getParent() - .getValue(registerValue.getMinSnap(), TargetRegister.LENGTH_ATTRIBUTE_NAME) + int getRegisterValueBitLength() throws RegisterValueException { + Object objBitLength = registerValue.getParent() + .getValue(registerValue.getMinSnap(), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME) .getValue(); - if (!(objLength instanceof Number)) { + if (!(objBitLength instanceof Number)) { throw new RegisterValueException( - "Register length is not numeric: (" + objLength.getClass() + ") '" + objLength + - "'"); + "Register length is not numeric: (" + objBitLength.getClass() + ") '" + + objBitLength + "'"); } - return ((Number) objLength).intValue(); + return ((Number) objBitLength).intValue(); } BigInteger getValue() throws RegisterValueException { @@ -125,25 +129,29 @@ public enum DBTraceObjectRegisterSupport { return value = convertRegisterValueToBigInteger(); } - int getLength() throws RegisterValueException { - if (length != -1) { - return length; + int getBitLength() throws RegisterValueException { + if (bitLength != -1) { + return bitLength; } - return length = getRegisterValueLength(); + return bitLength = getRegisterValueBitLength(); + } + + int getByteLength() throws RegisterValueException { + return (getBitLength() + 7) / 8; } byte[] getBytesBigEndian() throws RegisterValueException { if (be != null) { return be; } - return be = Utils.bigIntegerToBytes(getValue(), getLength(), true); + return be = Utils.bigIntegerToBytes(getValue(), getByteLength(), true); } byte[] getBytesLittleEndian() throws RegisterValueException { if (le != null) { return le; } - return le = Utils.bigIntegerToBytes(getValue(), getLength(), false); + return le = Utils.bigIntegerToBytes(getValue(), getByteLength(), false); } public byte[] getBytes(boolean isBigEndian) throws RegisterValueException { @@ -160,14 +168,10 @@ public enum DBTraceObjectRegisterSupport { return null; } String pathStr = container.getCanonicalPath().toString(); - AddressSpace space = object.getTrace().getBaseAddressFactory().getAddressSpace(pathStr); - if (space == null) { - return null; - } - if (!space.isRegisterSpace()) { - return null; - } - return space; + Trace trace = object.getTrace(); + return trace.getMemoryManager() + .getOrCreateOverlayAddressSpace(pathStr, + trace.getBaseAddressFactory().getRegisterSpace()); } protected AddressSpace findRegisterOverlay(TraceObjectValue objectValue) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java index 5d85c1194e..b5d9c740d7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java @@ -18,6 +18,7 @@ package ghidra.trace.database.guest; import java.util.Collection; import java.util.List; +import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetRegister; import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.util.PathMatcher; @@ -30,6 +31,7 @@ import ghidra.program.model.symbol.SourceType; import ghidra.trace.database.guest.DBTraceGuestPlatform.DBTraceGuestLanguage; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.symbol.*; +import ghidra.trace.model.target.TraceObject; import ghidra.trace.util.TraceRegisterUtils; import ghidra.util.LockHold; import ghidra.util.exception.DuplicateNameException; @@ -78,6 +80,48 @@ public interface InternalTracePlatform extends TracePlatform { return result; } + @Override + default String getConventionalRegisterObjectName(Register register) { + Address pmin = mapGuestToHost(register.getAddress()); + if (pmin == null) { + return register.getName(); + } + TraceSymbolManager symbolManager = getTrace().getSymbolManager(); + TraceNamespaceSymbol nsRegMap = symbolManager.namespaces().getGlobalNamed(regMap(register)); + Collection labels = symbolManager.labels() + .getAt(0, null, pmin, false) + .stream() + .filter(s -> s.getParentNamespace() == nsRegMap) + .toList(); + if (labels.isEmpty()) { + return register.getName(); + } + // primary is listed first, so take it + return labels.iterator().next().getName(); + } + + @Override + default PathMatcher getConventionalRegisterPath(TargetObjectSchema schema, List path, + Register register) { + PathMatcher matcher = schema.searchFor(TargetRegister.class, path, true); + if (matcher.isEmpty()) { + return matcher; + } + String name = getConventionalRegisterObjectName(register); + return matcher.applyKeys(Align.RIGHT, List.of(name)); + } + + @Override + default PathMatcher getConventionalRegisterPath(TraceObject container, Register register) { + return getConventionalRegisterPath(container.getTargetSchema(), + container.getCanonicalPath().getKeyList(), register); + } + + @Override + default PathMatcher getConventionalRegisterPath(TargetObject container, Register register) { + return getConventionalRegisterPath(container.getSchema(), container.getPath(), register); + } + @Override default PathMatcher getConventionalRegisterPath(AddressSpace space, Register register) { List path = PathUtils.parse(space.getName()); @@ -87,20 +131,7 @@ public interface InternalTracePlatform extends TracePlatform { } TargetObjectSchema schema = rootSchema .getSuccessorSchema(path); - PathMatcher matcher = schema.searchFor(TargetRegister.class, path, true); - if (matcher.isEmpty()) { - return matcher; - } - Address pmin = mapGuestToHost(register.getAddress()); - TraceSymbolManager symbolManager = getTrace().getSymbolManager(); - TraceNamespaceSymbol nsRegMap = symbolManager.namespaces().getGlobalNamed(regMap(register)); - Collection labels = symbolManager.labels() - .getAt(0, null, pmin, false) - .stream() - .filter(s -> s.getParentNamespace() == nsRegMap) - .toList(); - String name = labels.isEmpty() ? register.getName() : labels.iterator().next().getName(); - return matcher.applyKeys(Align.RIGHT, List.of(name)); + return getConventionalRegisterPath(schema, path, register); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/AbstractBaseDBTraceCodeUnitsMemoryView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/AbstractBaseDBTraceCodeUnitsMemoryView.java index c7244738ad..9a0de9d0a1 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/AbstractBaseDBTraceCodeUnitsMemoryView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/AbstractBaseDBTraceCodeUnitsMemoryView.java @@ -55,6 +55,10 @@ public abstract class AbstractBaseDBTraceCodeUnitsMemoryView - implements TraceCodeUnitsView { + implements TraceCodeUnitsView, InternalBaseCodeUnitsView { /** * Construct the view diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitsView.java index aad7f84eed..345719ed06 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitsView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitsView.java @@ -20,15 +20,14 @@ import java.util.List; import com.google.common.collect.Range; import ghidra.program.model.address.AddressRange; -import ghidra.trace.model.listing.TraceCodeSpace; -import ghidra.trace.model.listing.TraceCodeUnitsView; +import ghidra.trace.model.listing.*; /** * The implementation of {@link TraceCodeSpace#codeUnits()} */ public class DBTraceCodeUnitsView extends AbstractComposedDBTraceCodeUnitsView> - implements TraceCodeUnitsView { + implements TraceCodeUnitsView, InternalBaseCodeUnitsView { /** * Construct the view diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataMemoryView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataMemoryView.java index 7235d427a1..bc3a42e95d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataMemoryView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataMemoryView.java @@ -15,15 +15,14 @@ */ package ghidra.trace.database.listing; -import ghidra.trace.model.listing.TraceCodeManager; -import ghidra.trace.model.listing.TraceDataView; +import ghidra.trace.model.listing.*; /** * The implementation of {@link TraceCodeManager#data()} */ public class DBTraceDataMemoryView extends AbstractWithUndefinedDBTraceCodeUnitsMemoryView - implements TraceDataView { + implements TraceDataView, InternalBaseCodeUnitsView { /** * Construct the view diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataView.java index 3072368f5d..ecda32167e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataView.java @@ -20,15 +20,14 @@ import java.util.List; import com.google.common.collect.Range; import ghidra.program.model.address.AddressRange; -import ghidra.trace.model.listing.TraceCodeSpace; -import ghidra.trace.model.listing.TraceDataView; +import ghidra.trace.model.listing.*; /** * The implementation of {@link TraceCodeSpace#data()} */ public class DBTraceDataView extends AbstractComposedDBTraceCodeUnitsView> - implements TraceDataView { + implements TraceDataView, InternalBaseCodeUnitsView { /** * Construct the view diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedDataMemoryView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedDataMemoryView.java index 2dc2f89913..fc225d3fff 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedDataMemoryView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedDataMemoryView.java @@ -17,12 +17,10 @@ package ghidra.trace.database.listing; import com.google.common.collect.Range; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressRange; +import ghidra.program.model.address.*; import ghidra.program.model.data.DataType; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.trace.model.listing.TraceCodeManager; -import ghidra.trace.model.listing.TraceDefinedDataView; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -31,7 +29,7 @@ import ghidra.util.task.TaskMonitor; */ public class DBTraceDefinedDataMemoryView extends AbstractBaseDBTraceCodeUnitsMemoryView - implements TraceDefinedDataView { + implements InternalTraceDefinedDataView { /** * Construct the view diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedDataView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedDataView.java index fc6bec01f0..fd5c731f45 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedDataView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedDataView.java @@ -28,7 +28,6 @@ import ghidra.trace.model.Trace.TraceCodeChangeType; import ghidra.trace.model.Trace.TraceCompositeDataChangeType; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.listing.TraceCodeSpace; -import ghidra.trace.model.listing.TraceDefinedDataView; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; @@ -36,7 +35,7 @@ import ghidra.util.LockHold; * The implementation of {@link TraceCodeSpace#definedData()} */ public class DBTraceDefinedDataView extends AbstractBaseDBTraceDefinedUnitsView - implements TraceDefinedDataView { + implements InternalTraceDefinedDataView { /** * Construct the view * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedUnitsMemoryView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedUnitsMemoryView.java index a2cc41b2f2..139d58c70a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedUnitsMemoryView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedUnitsMemoryView.java @@ -18,8 +18,7 @@ package ghidra.trace.database.listing; import com.google.common.collect.Range; import ghidra.program.model.address.AddressRange; -import ghidra.trace.model.listing.TraceCodeManager; -import ghidra.trace.model.listing.TraceDefinedUnitsView; +import ghidra.trace.model.listing.*; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -28,7 +27,7 @@ import ghidra.util.task.TaskMonitor; */ public class DBTraceDefinedUnitsMemoryView extends AbstractBaseDBTraceCodeUnitsMemoryView, DBTraceDefinedUnitsView> - implements TraceDefinedUnitsView { + implements TraceDefinedUnitsView, InternalTraceBaseDefinedUnitsView { /** * Construct the view diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedUnitsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedUnitsView.java index 015c911e40..15b6d20807 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedUnitsView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDefinedUnitsView.java @@ -22,8 +22,7 @@ import com.google.common.collect.Range; import ghidra.program.model.address.AddressRange; import ghidra.trace.model.ImmutableTraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange; -import ghidra.trace.model.listing.TraceCodeSpace; -import ghidra.trace.model.listing.TraceDefinedUnitsView; +import ghidra.trace.model.listing.*; import ghidra.util.LockHold; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -33,7 +32,7 @@ import ghidra.util.task.TaskMonitor; */ public class DBTraceDefinedUnitsView extends AbstractComposedDBTraceCodeUnitsView, AbstractBaseDBTraceDefinedUnitsView>> - implements TraceDefinedUnitsView { + implements TraceDefinedUnitsView, InternalTraceBaseDefinedUnitsView { /** * Construct the view diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsMemoryView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsMemoryView.java index 05f54a5670..cb3cbfb267 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsMemoryView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsMemoryView.java @@ -25,8 +25,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.trace.model.guest.TracePlatform; -import ghidra.trace.model.listing.TraceCodeManager; -import ghidra.trace.model.listing.TraceInstructionsView; +import ghidra.trace.model.listing.*; import ghidra.util.LockHold; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -36,7 +35,7 @@ import ghidra.util.task.TaskMonitor; */ public class DBTraceInstructionsMemoryView extends AbstractBaseDBTraceCodeUnitsMemoryView - implements TraceInstructionsView { + implements TraceInstructionsView, InternalTraceBaseDefinedUnitsView { /** * Construct the view diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java index 762dd12492..971d0dd887 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java @@ -46,7 +46,7 @@ import ghidra.util.task.TaskMonitor; * The implementation of {@link TraceCodeSpace#instructions()} */ public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView - implements TraceInstructionsView { + implements TraceInstructionsView, InternalTraceBaseDefinedUnitsView { protected static T replaceIfNotNull(T cur, T rep) { return rep != null ? rep : cur; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceUndefinedDataMemoryView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceUndefinedDataMemoryView.java index a6a9aca93b..4580792021 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceUndefinedDataMemoryView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceUndefinedDataMemoryView.java @@ -15,15 +15,14 @@ */ package ghidra.trace.database.listing; -import ghidra.trace.model.listing.TraceCodeManager; -import ghidra.trace.model.listing.TraceUndefinedDataView; +import ghidra.trace.model.listing.*; /** * The implementation of {@link TraceCodeManager#undefinedData()} */ public class DBTraceUndefinedDataMemoryView extends AbstractWithUndefinedDBTraceCodeUnitsMemoryView - implements TraceUndefinedDataView { + implements TraceUndefinedDataView, InternalBaseCodeUnitsView { /** * Construct the view diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceUndefinedDataView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceUndefinedDataView.java index fc2b2dde99..b21fb3cb59 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceUndefinedDataView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceUndefinedDataView.java @@ -26,15 +26,15 @@ import com.google.common.collect.Range; import ghidra.program.model.address.*; import ghidra.trace.database.DBTraceUtils; import ghidra.trace.model.TraceAddressSnapRange; -import ghidra.trace.model.listing.TraceCodeSpace; -import ghidra.trace.model.listing.TraceUndefinedDataView; +import ghidra.trace.model.listing.*; import ghidra.util.*; /** * The implementation of {@link TraceCodeSpace#undefinedData()} */ public class DBTraceUndefinedDataView extends - AbstractSingleDBTraceCodeUnitsView implements TraceUndefinedDataView { + AbstractSingleDBTraceCodeUnitsView + implements TraceUndefinedDataView, InternalBaseCodeUnitsView { protected final static int CACHE_MAX_SNAPS = 5; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/InternalBaseCodeUnitsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/InternalBaseCodeUnitsView.java new file mode 100644 index 0000000000..64a7d65d74 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/InternalBaseCodeUnitsView.java @@ -0,0 +1,67 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.listing; + +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Register; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.listing.*; +import ghidra.trace.util.TraceRegisterUtils; + +public interface InternalBaseCodeUnitsView + extends TraceBaseCodeUnitsView { + AddressSpace getSpace(); + + @Override + @SuppressWarnings("unchecked") + default T getForRegister(TracePlatform platform, long snap, Register register) { + // Find a code unit which contains the register completely + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + T candidate = getContaining(snap, range.getMinAddress()); + if (candidate == null) { + return null; + } + int cmpMax = range.getMaxAddress().compareTo(candidate.getMaxAddress()); + if (cmpMax > 0) { + return null; + } + if (cmpMax == 0 && candidate.getMinAddress().equals(range.getMinAddress())) { + return candidate; + } + if (!(candidate instanceof TraceData)) { + return null; + } + TraceData data = (TraceData) candidate; + // Cast because if candidate is TraceData, T is, too + // NOTE: It may not be a primitive + return (T) TraceRegisterUtils.seekComponent(data, range); + } + + @Override + default T getContaining(TracePlatform platform, long snap, Register register) { + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + T candidate = getContaining(snap, range.getMinAddress()); + if (candidate == null) { + return null; + } + int cmpMax = range.getMaxAddress().compareTo(candidate.getMaxAddress()); + if (cmpMax > 0) { + return null; + } + return candidate; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/InternalTraceBaseDefinedUnitsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/InternalTraceBaseDefinedUnitsView.java new file mode 100644 index 0000000000..02c81955d9 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/InternalTraceBaseDefinedUnitsView.java @@ -0,0 +1,37 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.listing; + +import com.google.common.collect.Range; + +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.lang.Register; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.listing.TraceBaseDefinedUnitsView; +import ghidra.trace.model.listing.TraceCodeUnit; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public interface InternalTraceBaseDefinedUnitsView + extends TraceBaseDefinedUnitsView, InternalBaseCodeUnitsView { + + @Override + default void clear(TracePlatform platform, Range span, Register register, + TaskMonitor monitor) throws CancelledException { + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + clear(span, range, true, monitor); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/InternalTraceDefinedDataView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/InternalTraceDefinedDataView.java new file mode 100644 index 0000000000..cfd28fa867 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/InternalTraceDefinedDataView.java @@ -0,0 +1,39 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.listing; + +import com.google.common.collect.Range; + +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.data.DataType; +import ghidra.program.model.lang.Register; +import ghidra.program.model.util.CodeUnitInsertionException; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.listing.TraceData; +import ghidra.trace.model.listing.TraceDefinedDataView; +import ghidra.trace.util.TraceRegisterUtils; + +public interface InternalTraceDefinedDataView + extends TraceDefinedDataView, InternalTraceBaseDefinedUnitsView { + + @Override + default TraceData create(TracePlatform platform, Range lifespan, Register register, + DataType dataType) throws CodeUnitInsertionException { + TraceRegisterUtils.requireByteBound(register); + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + return create(lifespan, range.getMinAddress(), dataType, (int) range.getLength()); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java index 3b55763460..75f20234ca 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryManager.java @@ -77,6 +77,11 @@ public class DBTraceMemoryManager extends AbstractDBTraceSpaceBasedManager lifespan, byte[] value) { - int length = getLength(); + int length = getByteLength(); if (length != 0 && value.length != length) { throw new IllegalArgumentException("Length must match the register"); } @@ -88,7 +88,7 @@ public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObject if (val instanceof String) { // Always base 16. Model API says byte array for register value is big endian. BigInteger bigVal = new BigInteger((String) val, 16); - return Utils.bigIntegerToBytes(bigVal, getLength(), true); + return Utils.bigIntegerToBytes(bigVal, getByteLength(), true); } throw new ClassCastException("Cannot convert " + val + " to byte array for register value"); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java index 8b0a192614..3842673e28 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java @@ -26,8 +26,6 @@ import db.DBHandle; import db.DBRecord; import generic.CatenatedCollection; import ghidra.dbg.target.TargetRegisterContainer; -import ghidra.dbg.util.PathPattern; -import ghidra.dbg.util.PathPredicates; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.trace.database.*; @@ -35,7 +33,6 @@ import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.stack.TraceObjectStackFrame; import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.target.TraceObject; -import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceAddressSpace; @@ -247,22 +244,6 @@ public abstract class AbstractDBTraceSpaceBasedManager o.queryInterface(ifClass)); } + // TODO: Post filter until GP-1301 + private boolean isActuallyInterface(TraceObjectValPath path, + Class targetIf) { + TraceObjectValue lastEntry = path.getLastEntry(); + if (lastEntry == null) { + // TODO: This assumes the client will call getDestination(this) + return this.getTargetSchema().getInterfaces().contains(targetIf); + } + if (!lastEntry.isObject()) { + return false; + } + return lastEntry.getChild().getTargetSchema().getInterfaces().contains(targetIf); + } + @Override public Stream querySuccessorsTargetInterface(Range span, Class targetIf) { PathMatcher matcher = getTargetSchema().searchFor(targetIf, true); - // TODO: Post filter until GP-1301 - return getSuccessors(span, matcher).filter( - p -> p.getDestination(this).getTargetSchema().getInterfaces().contains(targetIf)); + return getSuccessors(span, matcher).filter(p -> isActuallyInterface(p, targetIf)); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java index 8b302bdc5d..4ec5161426 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java @@ -15,13 +15,18 @@ */ package ghidra.trace.model.guest; +import java.util.List; + +import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetRegister; +import ghidra.dbg.target.schema.TargetObjectSchema; import ghidra.dbg.util.PathMatcher; import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemBuffer; import ghidra.trace.model.Trace; import ghidra.trace.model.symbol.TraceLabelSymbol; +import ghidra.trace.model.target.TraceObject; /** * A platform within a trace @@ -119,7 +124,7 @@ public interface TracePlatform { * Translate a set from host to guest * *

- * Only those ranges (or parts of ranges) that map are included. + * Only those ranges (or parts of ranges) that mapped are included. * * @param hostSet the host set * @return the guest set @@ -149,7 +154,7 @@ public interface TracePlatform { * Translate a set from guest to host * *

- * Only those ranges (or parts of ranges) that map are included. + * Only those ranges (or parts of ranges) that mapped are included. * * @param guestSet the guest set * @return the host set @@ -165,13 +170,57 @@ public interface TracePlatform { */ AddressRange getConventionalRegisterRange(AddressSpace overlay, Register register); + /** + * Get the name or index of the register object for the given platform register + * + *

+ * This will check for a label in the host physical space, allowing a mapper to specify an + * alternative register object name. See {@link #addRegisterMapOverride(Register, String)}. + * + * @param register the platform register + * @return the mapped name + */ + String getConventionalRegisterObjectName(Register register); + /** * Get the expected path where an object defining the register value would be * *

* This will check for a label in the host physical space, allowing a mapper to specify an - * alternative register name. + * alternative register object name. See {@link #addRegisterMapOverride(Register, String)}. * + * @param schema the schema of the register container + * @param path the path to the register container + * @param register the platform register + * @return the path matcher, possibly empty + */ + PathMatcher getConventionalRegisterPath(TargetObjectSchema schema, List path, + Register register); + + /** + * Get the expected path where an object defining the register value would be + * + * @see #getConventionalRegisterPath(TargetObjectSchema, List, Register) + * @param container the register container + * @param register the platform register + * @return that path matcher, possibly empty, or null if the trace has no root schema + */ + PathMatcher getConventionalRegisterPath(TraceObject container, Register register); + + /** + * Get the expected path where an object defining the register value would be + * + * @see #getConventionalRegisterPath(TargetObjectSchema, List, Register) + * @param container the target register container + * @param register the platform register + * @return the path matcher, possibly empty + */ + PathMatcher getConventionalRegisterPath(TargetObject container, Register register); + + /** + * Get the expected path where an object defining the register value would be + * + * @see #getConventionalRegisterPath(TargetObjectSchema, List, Register) * @param overlay the overlay space allocated for a thread or frame * @param register the platform register * @return the path matcher, or null if there is no root schema diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseCodeUnitsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseCodeUnitsView.java index 8b0c69d4ea..8c4068c4c9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseCodeUnitsView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseCodeUnitsView.java @@ -15,15 +15,13 @@ */ package ghidra.trace.model.listing; -import java.util.HashSet; -import java.util.Set; - import com.google.common.collect.Range; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.trace.model.Trace; import ghidra.trace.model.TraceAddressSnapRange; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.util.TraceRegisterUtils; import ghidra.util.IntersectionAddressSetView; import ghidra.util.UnionAddressSetView; @@ -251,15 +249,6 @@ public interface TraceBaseCodeUnitsView { */ boolean intersectsRange(TraceAddressSnapRange range); - /** - * Get the set of registers for the trace's base language - * - * @return the register set - */ - default Set getRegisters() { - return new HashSet<>(getTrace().getBaseLanguage().getRegisters()); - } - /** * Get the unit (or component of a structure) which spans exactly the addresses of the given * register @@ -267,28 +256,32 @@ public interface TraceBaseCodeUnitsView { * @param register the register * @return the unit or {@code null} */ - @SuppressWarnings("unchecked") default T getForRegister(long snap, Register register) { - // Find a code unit which contains the register completely - T candidate = getContaining(snap, register.getAddress()); - if (candidate == null) { - return null; - } - AddressRange range = TraceRegisterUtils.rangeForRegister(register); - int cmpMax = range.getMaxAddress().compareTo(candidate.getMaxAddress()); - if (cmpMax > 0) { - return null; - } - if (cmpMax == 0 && candidate.getMinAddress().equals(register.getAddress())) { - return candidate; - } - if (!(candidate instanceof TraceData)) { - return null; - } - TraceData data = (TraceData) candidate; - // Cast because if candidate is TraceData, T is, too - // NOTE: It may not be a primitive - return (T) TraceRegisterUtils.seekComponent(data, range); + return getForRegister(getTrace().getPlatformManager().getHostPlatform(), snap, register); + } + + /** + * Get the unit (or component of a structure) which spans exactly the addresses of the given + * platform register + * + * @param platform the platform whose language defines the register + * @param register the register + * @return the unit or {@code null} + */ + T getForRegister(TracePlatform platform, long snap, Register register); + + /** + * Get the unit which completely contains the given register + * + *

+ * This does not descend into structures. + * + * @param snap the snap during which the unit must be alive + * @param register the register + * @return the unit or {@code unit} + */ + default T getContaining(long snap, Register register) { + return getContaining(getTrace().getPlatformManager().getHostPlatform(), snap, register); } /** @@ -297,21 +290,12 @@ public interface TraceBaseCodeUnitsView { *

* This does not descend into structures. * + * @platform the platform whose language defines the register + * @param snap the snap during which the unit must be alive * @param register the register * @return the unit or {@code unit} */ - default T getContaining(long snap, Register register) { - T candidate = getContaining(snap, register.getAddress()); - if (candidate == null) { - return null; - } - AddressRange range = TraceRegisterUtils.rangeForRegister(register); - int cmpMax = range.getMaxAddress().compareTo(candidate.getMaxAddress()); - if (cmpMax > 0) { - return null; - } - return candidate; - } + T getContaining(TracePlatform platform, long snap, Register register); /** * Get the live units whose start addresses are within the given register diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseDefinedUnitsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseDefinedUnitsView.java index 6f541165f7..d6dac9d60c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseDefinedUnitsView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseDefinedUnitsView.java @@ -19,6 +19,7 @@ import com.google.common.collect.Range; import ghidra.program.model.address.AddressRange; import ghidra.program.model.lang.Register; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.util.TraceRegisterUtils; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -64,4 +65,19 @@ public interface TraceBaseDefinedUnitsView throws CancelledException { clear(span, TraceRegisterUtils.rangeForRegister(register), true, monitor); } + + /** + * Clear the units contained within the given span and platform register + * + *

+ * Any units alive before the given span are truncated instead of deleted. + * + * @param platform the platform whose language defines the register + * @param span the span to clear + * @param register the register + * @param monitor a monitor for progress and cancellation + * @throws CancelledException if the clear is cancelled + */ + void clear(TracePlatform platform, Range span, Register register, TaskMonitor monitor) + throws CancelledException; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceDefinedDataView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceDefinedDataView.java index 9f43229b88..c3d561fbd4 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceDefinedDataView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceDefinedDataView.java @@ -21,7 +21,7 @@ import ghidra.program.model.address.Address; 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.model.guest.TracePlatform; import ghidra.trace.util.TraceRegisterUtils; /** @@ -75,17 +75,24 @@ public interface TraceDefinedDataView extends TraceBaseDefinedUnitsView lifespan, Register register, DataType dataType) throws CodeUnitInsertionException { - // TODO: A better way to handle memory-mapped registers? - Trace trace = 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()); } + + /** + * Create a data unit on the given platform register + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. In those + * cases, the assignment affects all threads. + * + * @param platform the platform whose language defines the register + * @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 + */ + TraceData create(TracePlatform platform, Range lifespan, Register register, + DataType dataType) throws CodeUnitInsertionException; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryManager.java index f8132afdc9..d8c80944fe 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryManager.java @@ -49,6 +49,12 @@ public interface TraceMemoryManager extends TraceMemoryOperations { * space named after the path of each memory being recorded. Of course, the mapping still needs * to occur between the trace and parts of the display and during emulation. * + *

+ * NOTE: We are also moving away from (space, thread, frame) triples to uniquely identify + * register storage. Instead, that will be encoded into the address space itself. Register + * overlays will overlay register space as be named after the register container object, which + * subsumes thread and frame when applicable. + * * @param name the name of the new address space * @param base the space after which this is modeled * @return the create space @@ -57,6 +63,21 @@ public interface TraceMemoryManager extends TraceMemoryOperations { AddressSpace createOverlayAddressSpace(String name, AddressSpace base) throws DuplicateNameException; + /** + * Get or create an overlay address space + * + *

+ * If the space already exists, and it overlays the given base, the existing space is returned. + * If it overlays a different space, null is returned. If the space does not exist, it is + * created with the given base space. + * + * @see #createOverlayAddressSpace(String, AddressSpace) + * @param name the name of the address space + * @param base the expected base space + * @return the space, or null + */ + AddressSpace getOrCreateOverlayAddressSpace(String name, AddressSpace base); + /** * Delete an overlay address space * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java index eccfa873fa..28e0ccf969 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java @@ -27,7 +27,7 @@ import ghidra.trace.model.thread.TraceObjectThread; targetIf = TargetRegister.class, shortName = "register", fixedKeys = { - TargetRegister.LENGTH_ATTRIBUTE_NAME + TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME }) public interface TraceObjectRegister extends TraceObjectInterface { String KEY_STATE = "_state"; @@ -36,7 +36,11 @@ public interface TraceObjectRegister extends TraceObjectInterface { String getName(); - int getLength(); + int getBitLength(); + + default int getByteLength() { + return (getBitLength() + 7) / 8; + } void setValue(Range lifespan, byte[] value); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java index 900220509c..d1227d6e9f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java @@ -25,6 +25,7 @@ import com.google.common.collect.RangeSet; import ghidra.dbg.target.TargetMethod; import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.dbg.util.PathPattern; import ghidra.dbg.util.PathPredicates; import ghidra.trace.model.Trace; import ghidra.trace.model.TraceUniqueObject; @@ -548,4 +549,25 @@ public interface TraceObject extends TraceUniqueObject { } return getTrace().getObjectManager().getObjectByCanonicalPath(TraceObjectKeyPath.of(path)); } + + /** + * Search for a suitable register container + * + * @see TargetObjectSchema#searchForRegisterContainer(int, List) + * @param frameLevel the frame level. Must be 0 if not applicable + * @return the register container, or null + */ + default TraceObject queryRegisterContainer(int frameLevel) { + PathPredicates regsMatcher = getRoot().getTargetSchema() + .searchForRegisterContainer(frameLevel, getCanonicalPath().getKeyList()); + for (PathPattern regsPattern : regsMatcher.getPatterns()) { + TraceObject regsObj = getTrace().getObjectManager() + .getObjectByCanonicalPath( + TraceObjectKeyPath.of(regsPattern.getSingletonPath())); + if (regsObj != null) { + return regsObj; + } + } + return null; + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java index 638b77429c..8fa8b8ff37 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java @@ -18,6 +18,7 @@ package ghidra.trace.model.time.schedule; import java.util.*; import ghidra.pcode.emu.PcodeMachine; +import ghidra.program.model.lang.Language; import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; @@ -523,16 +524,16 @@ public class TraceSchedule implements Comparable { * @param sleigh a single line of sleigh, excluding the terminating semicolon. * @return the resulting schedule */ - public TraceSchedule patched(TraceThread thread, String sleigh) { + public TraceSchedule patched(TraceThread thread, Language language, String sleigh) { if (!this.pSteps.isNop()) { Sequence pTicks = this.pSteps.clone(); pTicks.advance(new PatchStep(thread.getKey(), sleigh)); - pTicks.coalescePatches(thread.getTrace().getBaseLanguage()); + pTicks.coalescePatches(language); return new TraceSchedule(snap, steps.clone(), pTicks); } Sequence ticks = this.steps.clone(); ticks.advance(new PatchStep(keyOf(thread), sleigh)); - ticks.coalescePatches(thread.getTrace().getBaseLanguage()); + ticks.coalescePatches(language); return new TraceSchedule(snap, ticks, new Sequence()); } @@ -543,20 +544,20 @@ public class TraceSchedule implements Comparable { * @param sleigh the lines of sleigh, excluding the terminating semicolons. * @return the resulting schedule */ - public TraceSchedule patched(TraceThread thread, List sleigh) { + public TraceSchedule patched(TraceThread thread, Language language, List sleigh) { if (!this.pSteps.isNop()) { Sequence pTicks = this.pSteps.clone(); for (String line : sleigh) { pTicks.advance(new PatchStep(thread.getKey(), line)); } - pTicks.coalescePatches(thread.getTrace().getBaseLanguage()); + pTicks.coalescePatches(language); return new TraceSchedule(snap, steps.clone(), pTicks); } Sequence ticks = this.steps.clone(); for (String line : sleigh) { ticks.advance(new PatchStep(thread.getKey(), line)); } - ticks.coalescePatches(thread.getTrace().getBaseLanguage()); + ticks.coalescePatches(language); return new TraceSchedule(snap, ticks, new Sequence()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java index e68e8e70a3..8f291b4b1d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java @@ -27,6 +27,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.data.*; import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.listing.TraceData; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemoryState; @@ -39,6 +40,27 @@ public enum TraceRegisterUtils { return new AddressRangeImpl(address, address.add(register.getNumBytes() - 1)); } + public static AddressRange getOverlayRange(AddressSpace space, AddressRange range) { + AddressSpace physical = space.getPhysicalSpace(); + if (physical == space || physical != range.getAddressSpace()) { + return range; + } + return new AddressRangeImpl( + space.getAddress(range.getMinAddress().getOffset()), + space.getAddress(range.getMaxAddress().getOffset())); + } + + public static AddressSetView getOverlaySet(AddressSpace space, AddressSetView set) { + if (!space.isOverlaySpace()) { + return set; + } + AddressSet result = new AddressSet(); + for (AddressRange rng : set) { + result.add(getOverlayRange(space, rng)); + } + return result; + } + public static byte[] padOrTruncate(byte[] arr, int length) { if (arr.length == length) { return arr; @@ -141,8 +163,8 @@ public enum TraceRegisterUtils { return new RegisterValue(register, addr.getOffsetAsBigInteger()); } - public static RegisterValue combineWithTraceBaseRegisterValue(RegisterValue rv, long snap, - TraceMemorySpace regs, boolean requireKnown) { + public static RegisterValue combineWithTraceBaseRegisterValue(RegisterValue rv, + TracePlatform platform, long snap, TraceMemorySpace regs, boolean requireKnown) { Register reg = rv.getRegister(); if (reg.isBaseRegister()) { return rv; @@ -154,11 +176,11 @@ public enum TraceRegisterUtils { return rv.getBaseRegisterValue(); } if (requireKnown) { - if (TraceMemoryState.KNOWN != regs.getState(snap, reg.getBaseRegister())) { + if (TraceMemoryState.KNOWN != regs.getState(platform, snap, reg.getBaseRegister())) { throw new IllegalStateException("Must fetch base register before setting a child"); } } - return regs.getValue(snap, reg.getBaseRegister()).combineValues(rv); + return regs.getValue(platform, snap, reg.getBaseRegister()).combineValues(rv); } public static ByteBuffer prepareBuffer(Register register) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java index 4db2e4ba3b..ee017edfb3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java @@ -128,11 +128,11 @@ public class ToyDBTraceBuilder implements AutoCloseable { * Manipulate the trace's memory and registers using Sleigh * * @param snap the snap to modify - * @param frame the frame to modify * @param thread the thread to modify, can be {@code null} if only memory is used + * @param frame the frame to modify * @param sleigh the Sleigh source */ - public void exec(long snap, int frame, TraceThread thread, String sleigh) { + public void exec(long snap, TraceThread thread, int frame, String sleigh) { PcodeProgram program = SleighProgramCompiler.compileProgram((SleighLanguage) language, "builder", sleigh, PcodeUseropLibrary.nil()); TraceSleighUtils.buildByteExecutor(trace, snap, thread, frame) @@ -144,8 +144,8 @@ public class ToyDBTraceBuilder implements AutoCloseable { * * @param platform the platform whose language to use * @param snap the snap to modify - * @param frame the frame to modify * @param thread the thread to modify, can be {@code null} if only memory is used + * @param frame the frame to modify * @param sleigh the lines of Sleigh, including semicolons. */ public void exec(TracePlatform platform, long snap, TraceThread thread, int frame, diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java index e71048915d..8b3b328400 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java @@ -76,7 +76,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte .createOverlayAddressSpace("Targets[0].Threads[0].Registers", b.trace.getBaseAddressFactory().getRegisterSpace()); - regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regR0.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 64); regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); } @@ -109,7 +109,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte getSLEIGH_X86_64_LANGUAGE().getCompilerSpecByID(new CompilerSpecID("gcc"))); amd64.addMappedRegisterRange(); - regRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regRAX.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 64); regRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); } @@ -145,7 +145,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte RAX = amd64.getLanguage().getRegister("RAX"); amd64.addRegisterMapOverride(RAX, "orig_rax"); - regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 64); regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); } @@ -177,7 +177,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte avr8.addMappedRange(b.addr(0), avr8.getLanguage().getDefaultDataSpace().getAddress(0), 0x1000); - regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 1); + regR0.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 8); regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x12); } @@ -215,7 +215,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte avr8.addMappedRange(b.addr(overlay, 0), avr8.getLanguage().getDefaultDataSpace().getAddress(0), 0x1000); - regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 1); + regR0.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 8); regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x12); } @@ -251,7 +251,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte R0 = avr8.getLanguage().getRegister("R0"); avr8.addRegisterMapOverride(R0, "orig_r0"); - regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 1); + regR0.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 8); regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x12); } @@ -285,7 +285,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte getSLEIGH_X86_64_LANGUAGE().getCompilerSpecByID(new CompilerSpecID("gcc"))); amd64.addMappedRegisterRange(); - regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 64); regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); RAX = amd64.getLanguage().getRegister("RAX"); @@ -321,7 +321,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte avr8.addMappedRange(b.addr(0), avr8.getLanguage().getDefaultDataSpace().getAddress(0), 0x1000); - regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 1); + regR0.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 8); regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x12); R0 = avr8.getLanguage().getRegister("R0"); @@ -352,7 +352,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte .createOverlayAddressSpace("Targets[0].Threads[0].Registers", b.trace.getBaseAddressFactory().getRegisterSpace()); - regRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regRAX.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 64); regRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); amd64 = b.trace.getPlatformManager() @@ -382,7 +382,7 @@ public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessInte TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[RAX]")); regRAX.insert(Range.atLeast(0L), ConflictResolution.DENY); - regRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regRAX.setValue(Range.atLeast(0L), TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, 64); regRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); amd64 = b.trace.getPlatformManager() diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java index ee2613fb07..a50d7f52f0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java @@ -445,15 +445,16 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { thread = tb.trace.getThreadManager().createThread("Threads[0]", 0); } TraceSchedule time = TraceSchedule.parse("0"); - time = time.patched(thread, "r0l=1"); + time = time.patched(thread, tb.language, "r0l=1"); assertEquals("0:t0-{r0l=0x1}", time.toString()); - time = time.patched(thread, "r0h=2"); + time = time.patched(thread, tb.language, "r0h=2"); assertEquals("0:t0-{r0=0x200000001}", time.toString()); - time = time.patched(thread, "r1l=3").patched(thread, "*[ram]:4 0xcafe:8=0xdeadbeef"); + time = time.patched(thread, tb.language, "r1l=3") + .patched(thread, tb.language, "*[ram]:4 0xcafe:8=0xdeadbeef"); assertEquals("0:t0-{*:4 0xcafe:8=0xdeadbeef};t0-{r0=0x200000001};t0-{r1l=0x3}", time.toString()); - time = time.patched(thread, "*:8 0xcb00:8 = 0x1122334455667788"); + time = time.patched(thread, tb.language, "*:8 0xcb00:8 = 0x1122334455667788"); assertEquals("0:t0-{*:8 0xcafe:8=0xdead112233445566};t0-{*:2 0xcb06:8=0x7788};" + "t0-{r0=0x200000001};t0-{r1l=0x3}", time.toString()); } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/address/CachedAddressSetView.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/address/CachedAddressSetView.java index e33eaa64aa..1293440693 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/address/CachedAddressSetView.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/address/CachedAddressSetView.java @@ -86,6 +86,16 @@ public class CachedAddressSetView implements AddressSetView { maxAddress = delegate.getMaxAddress(); } + protected static void addMixed(AddressSet set, Address min, Address max) { + if (min.getAddressSpace() == max.getAddressSpace()) { + set.add(min, max); + } + else { + set.add(min, min.getAddressSpace().getMaxAddress()); + set.add(max.getAddressSpace().getMinAddress(), max); + } + } + protected void ensureKnown(Address min, Address max) { if (minAddress == null) { return; @@ -101,21 +111,21 @@ public class CachedAddressSetView implements AddressSetView { if (rangesBackward.hasNext()) { AddressRange prev = rangesBackward.next(); cache.add(prev); - known.add(prev.getMinAddress(), min); + addMixed(known, prev.getMinAddress(), min); } else { - known.add(minAddress, min); + addMixed(known, minAddress, min); } AddressRangeIterator rangesForward = delegate.getAddressRanges(min, true); while (true) { if (!rangesForward.hasNext()) { - known.add(min, maxAddress); + addMixed(known, min, maxAddress); break; } AddressRange next = rangesForward.next(); cache.add(next); if (next.getMaxAddress().compareTo(max) >= 0) { - known.add(min, next.getMaxAddress()); + addMixed(known, min, next.getMaxAddress()); break; } } diff --git a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java index 7383bbba20..7b271cfb71 100644 --- a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java +++ b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java @@ -104,7 +104,7 @@ public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x55550000)), new ProgramLocation(program, tb.addr(0x00400000)), 0x1000, false); thread = tb.getOrAddThread("Threads[0]", 0); - tb.exec(0, 0, thread, "RIP = 0x55550000;"); + tb.exec(0, thread, 0, "RIP = 0x55550000;"); } waitForDomainObject(tb.trace); waitForPass(() -> assertEquals(new ProgramLocation(program, tb.addr(0x00400000)),