Merge remote-tracking branch 'origin/GP-2551_Dan_RegistersViewerNewConvention-REBASED-1'

This commit is contained in:
Ryan Kurtz 2022-09-20 13:30:38 -04:00
commit 45165ea167
86 changed files with 1743 additions and 708 deletions

View File

@ -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");
}

View File

@ -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();
}

View File

@ -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");
}

View File

@ -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");

View File

@ -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 //

View File

@ -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");
}

View File

@ -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;

View File

@ -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);
});
});
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -812,8 +812,8 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider {
throws Exception {
synchronized (this) {
monitor.checkCanceled();
CompletableFuture<NavigableMap<Address, byte[]>> recCapture =
recorder.readMemoryBlocks(new AddressSet(range), monitor, false);
CompletableFuture<Void> recCapture =
recorder.readMemoryBlocks(new AddressSet(range), monitor);
this.captureTask = recCapture.thenCompose(__ -> {
return recorder.getTarget().getModel().flushEvents();
}).thenCompose(__ -> {

View File

@ -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;

View File

@ -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<Void> 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<LanguageCompilerSpecPair, LinkedHashSet<Register>> 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<Register> viewKnown = computeDefaultRegisterSelection(curThread);
availableRegsDialog.setLanguage(curPlatform.getLanguage());
Set<Register> viewKnown = computeDefaultRegisterSelection(curPlatform);
availableRegsDialog.setKnown(viewKnown);
Set<Register> selection = getSelectionFor(curThread);
Set<Register> 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.
*
* <p>
* 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
*
* <p>
* This excludes the context register
*
* <p>
* 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<Register> computeDefaultRegisterSelection(TraceThread thread) {
return collectCommonRegisters(thread.getTrace().getBaseCompilerSpec());
public LinkedHashSet<Register> computeDefaultRegisterSelection(TracePlatform platform) {
return collectCommonRegisters(platform.getCompilerSpec());
}
public LinkedHashSet<Register> computeDefaultRegisterFavorites(TraceThread thread) {
public LinkedHashSet<Register> computeDefaultRegisterFavorites(TracePlatform platform) {
LinkedHashSet<Register> 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<Register> computeDefaultRegistersOld(TraceThread thread) {
LinkedHashSet<Register> 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<Register> getSelectionFor(TraceThread thread) {
protected Set<Register> getSelectionFor(TracePlatform platform) {
synchronized (selectionByCSpec) {
LanguageCompilerSpecPair lcsp = getLangCSpecPair(thread);
LanguageCompilerSpecPair lcsp = getLangCSpecPair(platform);
return selectionByCSpec.computeIfAbsent(lcsp,
__ -> computeDefaultRegisterSelection(thread));
__ -> computeDefaultRegisterSelection(platform));
}
}
protected Set<Register> getFavoritesFor(TraceThread thread) {
protected Set<Register> 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<Register> favorites = getFavoritesFor(current.getThread());
Set<Register> favorites = getFavoritesFor(current.getPlatform());
if (favorite) {
favorites.add(register);
}
@ -1199,13 +1214,13 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
public boolean isFavorite(Register register) {
Set<Register> favorites = getFavoritesFor(current.getThread());
Set<Register> favorites = getFavoritesFor(current.getPlatform());
return favorites.contains(register);
}
public CompletableFuture<Void> setSelectedRegistersAndLoad(
Collection<Register> selectedRegisters) {
Set<Register> selection = getSelectionFor(current.getThread());
Set<Register> 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<Register> selected) {
List<Register> regs = currentTrace.getBaseLanguage().getRegisters();
List<Register> regs = current.getPlatform().getLanguage().getRegisters();
for (Iterator<Entry<Register, RegisterRow>> it = regMap.entrySet().iterator(); it
.hasNext();) {
Map.Entry<Register, RegisterRow> ent = it.next();
@ -1246,13 +1261,12 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
}
protected CompletableFuture<Void> loadRegistersAndValues() {
TraceThread curThread = current.getThread();
if (curThread == null) {
if (current.getThread() == null) {
regsTableModel.clear();
regMap.clear();
return AsyncUtils.NIL;
}
Set<Register> selected = getSelectionFor(curThread);
Set<Register> 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<Register> 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<Register> toRead) {
return recorder.captureThreadRegisters(current.getPlatform(), traceThread,
current.getFrame(), toRead);
}
protected CompletableFuture<Void> readRegistersIfLiveAndAccessible() {
TraceRecorder recorder = current.getRecorder();
if (recorder == null) {
@ -1289,20 +1327,16 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (targetThread == null) {
return AsyncUtils.NIL;
}
Set<Register> 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<Register> 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) {

View File

@ -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);

View File

@ -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<Void> 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);

View File

@ -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<CacheKey> {
// 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<Long> backgroundEmulate(Trace trace, TraceSchedule time) {
public CompletableFuture<Long> 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;
}

View File

@ -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) {

View File

@ -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())

View File

@ -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())

View File

@ -46,9 +46,9 @@ public class DefaultMemoryRecorder implements ManagedMemoryRecorder {
this.memoryManager = trace.getMemoryManager();
}
public CompletableFuture<NavigableMap<Address, byte[]>> captureProcessMemory(AddressSetView set,
TaskMonitor monitor, boolean toMap) {
return RecorderUtils.INSTANCE.readMemoryBlocks(recorder, BLOCK_BITS, set, monitor, toMap);
public CompletableFuture<Void> captureProcessMemory(AddressSetView set,
TaskMonitor monitor) {
return RecorderUtils.INSTANCE.readMemoryBlocks(recorder, BLOCK_BITS, set, monitor);
}
@Override

View File

@ -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<NavigableMap<Address, byte[]>> readMemoryBlocks(AddressSetView set,
TaskMonitor monitor, boolean toMap) {
public CompletableFuture<Void> 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<Map<Register, RegisterValue>> captureThreadRegisters(
TraceThread thread, int frameLevel,
Set<Register> registers) {
public CompletableFuture<Void> captureThreadRegisters(
TracePlatform platform, TraceThread thread, int frameLevel, Set<Register> 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<Void> writeThreadRegisters(TraceThread thread, int frameLevel,
Map<Register, RegisterValue> values) {
public CompletableFuture<Void> writeThreadRegisters(TracePlatform platform, TraceThread thread,
int frameLevel, Map<Register, RegisterValue> values) {
DefaultThreadRecorder rec = getThreadRecorder(thread);
return (rec == null) ? null : rec.writeThreadRegisters(frameLevel, values);
}

View File

@ -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);
}
}
}

View File

@ -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<Map<Register, RegisterValue>> captureThreadRegisters(
TraceThread thread, int frameLevel, Set<Register> 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<Void> writeThreadRegisters(TraceThread thread, int frameLevel,
Map<Register, RegisterValue> values) {
throw new UnsupportedOperationException();
public CompletableFuture<Void> captureThreadRegisters(
TracePlatform platform, TraceThread thread, int frameLevel, Set<Register> 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<Void> writeThreadRegisters(TracePlatform platform, TraceThread thread,
int frameLevel, Map<Register, RegisterValue> values) {
TargetRegisterContainer regContainer = getTargetRegisterContainer(thread, frameLevel);
if (regContainer == null) {
return AsyncUtils.NIL;
}
Map<TargetRegisterBank, Map<TargetRegister, byte[]>> writesByBank = new HashMap<>();
for (RegisterValue rv : values.values()) {
Register register = rv.getRegister();
PathMatcher matcher =
platform.getConventionalRegisterPath(regContainer.getSchema(), List.of(), register);
Collection<TargetObject> 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<String> 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<TargetRegisterBank, Map<TargetRegister, byte[]>> 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<NavigableMap<Address, byte[]>> readMemoryBlocks(
AddressSetView set, TaskMonitor monitor, boolean returnResult) {
return RecorderUtils.INSTANCE.readMemoryBlocks(this, BLOCK_BITS, set, monitor,
returnResult);
public CompletableFuture<Void> readMemoryBlocks(AddressSetView set, TaskMonitor monitor) {
return RecorderUtils.INSTANCE.readMemoryBlocks(this, BLOCK_BITS, set, monitor);
}
@Override

View File

@ -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<NavigableMap<Address, byte[]>> readMemoryBlocks(
TraceRecorder recorder, int blockBits, AddressSetView set, TaskMonitor monitor,
boolean returnResult) {
public CompletableFuture<Void> 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<Address, byte[]> 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<byte[]> 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);
});
}
}

View File

@ -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<Void> prepareViewAndFireEvent(DebuggerCoordinates coordinates) {

View File

@ -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 {
* <p>
* This is the preferred means of performing emulation. Because the underlying emulator may
* request a <em>blocking</em> read from a target, it is important that
* {@link #emulate(Trace, TraceSchedule, TaskMonitor)} is <em>never</em> called by the Swing
* thread.
* {@link #emulate(TracePlatform, TraceSchedule, TaskMonitor)} is <em>never</em> 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<Long> backgroundEmulate(Trace trace, TraceSchedule time);
CompletableFuture<Long> backgroundEmulate(TracePlatform platform, TraceSchedule time);
/**
* The the cached emulator for the given trace and time
@ -116,6 +133,9 @@ public interface DebuggerEmulationService {
* <p>
* <b>WARNING:</b> This emulator belongs to this service. Stepping it, or otherwise manipulating
* it without the service's knowledge can lead to unintended consequences.
* <p>
* 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

View File

@ -91,9 +91,8 @@ public interface DebuggerStateEditingService {
default CompletableFuture<Void> 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);

View File

@ -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 <em>base</em> 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<Map<Register, RegisterValue>> captureThreadRegisters(TraceThread thread,
int frameLevel, Set<Register> registers);
CompletableFuture<Void> captureThreadRegisters(TracePlatform platform,
TraceThread thread, int frameLevel, Set<Register> 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<Void> writeThreadRegisters(TraceThread thread, int frameLevel,
Map<Register, RegisterValue> values);
CompletableFuture<Void> writeThreadRegisters(TracePlatform platform, TraceThread thread,
int frameLevel, Map<Register, RegisterValue> values);
/**
* Read (and capture) a range of target memory
@ -320,20 +323,14 @@ public interface TraceRecorder {
*
* <p>
* 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<NavigableMap<Address, byte[]>> readMemoryBlocks(AddressSetView set,
TaskMonitor monitor, boolean returnResult);
CompletableFuture<Void> 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<Void> writeVariable(TraceThread thread, int frameLevel,
Address address, byte[] data) {
default CompletableFuture<Void> 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<Void> writeRegister(TraceThread thread, int frameLevel,
Address address, byte[] data) {
Language lang = getTrace().getBaseLanguage();
Register register = lang.getRegister(address, data.length);
default CompletableFuture<Void> 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));
}
/**

View File

@ -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<Register> registers)
default void refreshRegistersIfLive(TracePlatform platform, TraceThread thread, int frame,
long snap, Collection<Register> registers)
throws InterruptedException, ExecutionException, TimeoutException {
Trace trace = thread.getTrace();
TraceRecorder recorder = getModelService().getRecorder(trace);
@ -924,7 +980,7 @@ public interface FlatDebuggerAPI {
}
Set<Register> asSet =
registers instanceof Set<?> ? (Set<Register>) 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<RegisterValue> readRegisters(TraceThread thread, int frame, long snap,
Collection<Register> registers) {
default List<RegisterValue> readRegisters(TracePlatform platform, TraceThread thread, int frame,
long snap, Collection<Register> 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<RegisterValue> result = readRegisters(thread, frame, snap, Set.of(register));
default RegisterValue readRegister(TracePlatform platform, TraceThread thread, int frame,
long snap, Register register) {
List<RegisterValue> 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<RegisterValue> readRegisters(Collection<Register> 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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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")));

View File

@ -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;
}
}

View File

@ -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<byte[]> 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<byte[]> 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));
}
}

View File

@ -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"))

View File

@ -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

View File

@ -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
*
* <p>
* 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;
}
/**

View File

@ -317,12 +317,8 @@ public interface TargetObjectSchema {
* @return the named schema
*/
default SchemaName getElementSchema(String index) {
for (Entry<String, SchemaName> 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<String, AttributeSchema> 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 <em>must</em> be an intervening
* {@link TargetStack} with a single index between. There <em>must</em> 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<String> path) {
List<String> simple = searchForSuitable(TargetRegisterContainer.class, path);
if (simple != null) {
return frameLevel == 0 ? PathPredicates.pattern(simple) : PathPredicates.EMPTY;
return PathPredicates.pattern(simple);
}
List<String> threadPath = searchForAncestor(TargetThread.class, path);
if (threadPath == null) {
List<String> 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<String> framePathRelThread =
framePatternRelThread.applyKeys(index).getSingletonPath();
List<String> framePath = PathUtils.extend(threadPath, framePathRelThread);
List<String> framePathRelStack =
framePatternRelStack.applyKeys(index).getSingletonPath();
List<String> framePath = PathUtils.extend(stackPath, framePathRelStack);
List<String> 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
*
* <p>
* 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<String> path) {
List<String> framePath = searchForAncestor(TargetStackFrame.class, path);
if (framePath == null) {
return 0;
}
List<String> 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");
}
}

View File

@ -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<P extends TestTargetObject>
extends DefaultTestTargetObject<TestTargetObject, P> implements TargetRegisterBank {
extends DefaultTestTargetObject<TestTargetObject, P>
implements TargetRegisterBank, TargetRegisterContainer {
protected final TestTargetRegisterContainer regs;
// TODO: Remove the separate descriptors idea
protected final TestTargetRegisterContainer descs;
public final Map<String, byte[]> 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<P extends TestTargetObject>
@Override
public CompletableFuture<? extends Map<String, byte[]>> readRegistersNamed(
Collection<String> names) {
if (!regs.getDescs().keySet().containsAll(names)) {
if (!descs.getDescs().keySet().containsAll(names)) {
throw new DebuggerRegisterAccessException("No such register");
}
Map<String, byte[]> 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<P extends TestTargetObject>
protected CompletableFuture<Void> writeRegs(Map<String, byte[]> values,
Consumer<Address> setPC) {
if (!regs.getDescs().keySet().containsAll(values.keySet())) {
if (!descs.getDescs().keySet().containsAll(values.keySet())) {
throw new DebuggerRegisterAccessException("No such register");
}
Map<String, byte[]> updates = new LinkedHashMap<>();
CompletableFuture<Void> future = regs.getModel().future(null);
CompletableFuture<Void> future = descs.getModel().future(null);
for (Map.Entry<String, byte[]> 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<P extends TestTargetObject>
});
}
}
populateObjectValues(updates, "Write registers");
future.thenAccept(__ -> {
listeners.fire.registersUpdated(this, updates);
});
return model.gateFuture(future);
}
protected void addObjectValues(Collection<TestTargetRegister> descs, String reason) {
Set<TestTargetRegisterValue> 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<TestTargetRegister> descs, String reason) {
List<String> toRemove = new ArrayList<>();
for (TestTargetRegister rd : descs) {
toRemove.add(PathUtils.parseIndex(rd.getName()));
}
changeAttributes(toRemove, Map.of(), reason);
}
protected void populateObjectValues(Map<String, byte[]> values, String reason) {
Set<TestTargetRegisterValue> objVals = new HashSet<>();
for (Map.Entry<String, byte[]> 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<String, byte[]> values = new HashMap<>();
for (TestTargetRegister desc : descs.getDescs().values()) {
values.put(desc.getIndex(), null);
}
populateObjectValues(values, "Populate");
}
public void setFromBank(AbstractTestTargetRegisterBank<?> bank) {
//Map<String, byte[]> updates = new HashMap<>();
//updates.putAll(bank.regVals);
@ -98,4 +148,12 @@ public abstract class AbstractTestTargetRegisterBank<P extends TestTargetObject>
kit.remove();
}
}
public void addRegisterDescs(Collection<TestTargetRegister> added, String reason) {
addObjectValues(added, reason);
}
public void removeRegisterDescs(Collection<TestTargetRegister> removed, String reason) {
removeObjectValues(removed, reason);
}
}

View File

@ -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) {

View File

@ -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<TestTargetRegister, TestTargetProcess>
implements TargetRegisterContainer {
private final Set<AbstractTestTargetRegisterBank<?>> 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<AbstractTestTargetRegisterBank<?>> 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<AbstractTestTargetRegisterBank<?>> banks;
synchronized (this.banks) {
banks = List.copyOf(this.banks);
}
for (AbstractTestTargetRegisterBank<?> bank : banks) {
bank.addRegisterDescs(List.of(tr), reason);
}
return tr;
}
public Delta<TestTargetRegister, TestTargetRegister> removeRegister(Register register,
String reason) {
Delta<TestTargetRegister, TestTargetRegister> result =
changeElements(List.of(register.getName()), List.of(), reason);
List<AbstractTestTargetRegisterBank<?>> 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);
}
}
}

View File

@ -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<TestTargetObject, AbstractTestTargetRegisterBank<?>>
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");
}
}

View File

@ -25,8 +25,8 @@ import ghidra.dbg.util.PathUtils;
public class TestTargetThread
extends DefaultTestTargetObject<TestTargetObject, TestTargetThreadContainer>
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());

View File

@ -94,7 +94,7 @@
<attribute name="Breakpoints" schema="BreakpointContainer" required="yes" fixed="yes" />
<attribute name="Memory" schema="Memory" required="yes" fixed="yes" />
<attribute name="Modules" schema="ModuleContainer" required="yes" fixed="yes" />
<attribute name="Registers" schema="RegisterContainer" required="yes" fixed="yes" />
<!-- <attribute name="Registers" schema="RegisterContainer" required="yes" fixed="yes" /> -->
<attribute name="Threads" schema="ThreadContainer" required="yes" fixed="yes" />
<attribute name="Devices" schema="OBJECT" />
<attribute name="Environment" schema="OBJECT" />
@ -171,6 +171,7 @@
</schema>
<schema name="Thread" elementResync="NEVER" attributeResync="NEVER">
<interface name="Thread" />
<interface name="Aggregate" />
<interface name="ExecutionStateful" />
<interface name="Steppable" />
<interface name="Resumable" />
@ -269,7 +270,7 @@
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="RegisterContainer" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<schema name="RegisterContainer" elementResync="NEVER" attributeResync="NEVER">
<interface name="RegisterContainer" />
<element schema="RegisterDescriptor" />
<attribute name="_descriptions" schema="RegisterContainer" />
@ -280,12 +281,13 @@
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="ANY" />
</schema>
<schema name="RegisterBank" elementResync="NEVER" attributeResync="NEVER">
<schema name="RegisterBank" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="RegisterBank" />
<interface name="RegisterContainer" />
<!-- NB: registers are attributes, not elements here -->
<element schema="RegisterDescriptor" />
<element schema="VOID" />
<attribute schema="RegisterValue" />
<attribute name="_descriptions" schema="RegisterContainer" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
@ -294,9 +296,9 @@
<attribute name="_value" schema="ANY" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="OBJECT" />
</schema>
<schema name="Stack" canonical="yes" elementResync="NEVER" attributeResync="NEVER">
<interface name="Stack" />
<element schema="StackFrame" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
@ -394,4 +396,18 @@
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
</context>
<schema name="RegisterValue" elementResync="NEVER" attributeResync="NEVER">
<interface name="Register" />
<element schema="VOID" />
<attribute name="_length" schema="INT" fixed="yes" hidden="yes" />
<attribute name="_container" schema="RegisterContainer" />
<attribute name="_modified" schema="BOOL" hidden="yes" />
<attribute name="_display" schema="STRING" hidden="yes" />
<attribute name="_kind" schema="STRING" fixed="yes" hidden="yes" />
<attribute name="_short_display" schema="STRING" hidden="yes" />
<attribute name="_value" schema="ANY" required="yes" hidden="yes" />
<attribute name="_type" schema="STRING" hidden="yes" />
<attribute name="_order" schema="INT" hidden="yes" />
<attribute schema="VOID" />
</schema>
</context>

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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<? extends TraceLabelSymbol> 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<String> 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<String> 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<? extends TraceLabelSymbol> 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

View File

@ -55,6 +55,10 @@ public abstract class AbstractBaseDBTraceCodeUnitsMemoryView<T extends DBTraceCo
Collections2.transform(manager.getActiveMemorySpaces(), this::getView);
}
public AddressSpace getSpace() {
return null;
}
/**
* @see TraceBaseCodeUnitsView#getTrace()
*/

View File

@ -47,6 +47,10 @@ public abstract class AbstractBaseDBTraceCodeUnitsView<T extends DBTraceCodeUnit
this.space = space;
}
public AddressSpace getSpace() {
return getAddressSpace();
}
/**
* Get the address space for this view
*

View File

@ -16,15 +16,14 @@
package ghidra.trace.database.listing;
import ghidra.program.model.address.*;
import ghidra.trace.model.listing.TraceCodeManager;
import ghidra.trace.model.listing.TraceCodeUnitsView;
import ghidra.trace.model.listing.*;
/**
* The implementation of {@link TraceCodeManager#codeUnits()}
*/
public class DBTraceCodeUnitsMemoryView extends
AbstractWithUndefinedDBTraceCodeUnitsMemoryView<DBTraceCodeUnitAdapter, DBTraceCodeUnitsView>
implements TraceCodeUnitsView {
implements TraceCodeUnitsView, InternalBaseCodeUnitsView<TraceCodeUnit> {
/**
* Construct the view

View File

@ -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<DBTraceCodeUnitAdapter, AbstractSingleDBTraceCodeUnitsView<? extends DBTraceCodeUnitAdapter>>
implements TraceCodeUnitsView {
implements TraceCodeUnitsView, InternalBaseCodeUnitsView<TraceCodeUnit> {
/**
* Construct the view

View File

@ -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<DBTraceDataAdapter, DBTraceDataView>
implements TraceDataView {
implements TraceDataView, InternalBaseCodeUnitsView<TraceData> {
/**
* Construct the view

View File

@ -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<DBTraceDataAdapter, AbstractSingleDBTraceCodeUnitsView<? extends DBTraceDataAdapter>>
implements TraceDataView {
implements TraceDataView, InternalBaseCodeUnitsView<TraceData> {
/**
* Construct the view

View File

@ -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<DBTraceData, DBTraceDefinedDataView>
implements TraceDefinedDataView {
implements InternalTraceDefinedDataView {
/**
* Construct the view

View File

@ -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<DBTraceData>
implements TraceDefinedDataView {
implements InternalTraceDefinedDataView {
/**
* Construct the view
*

View File

@ -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<AbstractDBTraceCodeUnit<?>, DBTraceDefinedUnitsView>
implements TraceDefinedUnitsView {
implements TraceDefinedUnitsView, InternalTraceBaseDefinedUnitsView<TraceCodeUnit> {
/**
* Construct the view

View File

@ -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<AbstractDBTraceCodeUnit<?>, AbstractBaseDBTraceDefinedUnitsView<? extends AbstractDBTraceCodeUnit<?>>>
implements TraceDefinedUnitsView {
implements TraceDefinedUnitsView, InternalTraceBaseDefinedUnitsView<TraceCodeUnit> {
/**
* Construct the view

View File

@ -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<DBTraceInstruction, DBTraceInstructionsView>
implements TraceInstructionsView {
implements TraceInstructionsView, InternalTraceBaseDefinedUnitsView<TraceInstruction> {
/**
* Construct the view

View File

@ -46,7 +46,7 @@ import ghidra.util.task.TaskMonitor;
* The implementation of {@link TraceCodeSpace#instructions()}
*/
public class DBTraceInstructionsView extends AbstractBaseDBTraceDefinedUnitsView<DBTraceInstruction>
implements TraceInstructionsView {
implements TraceInstructionsView, InternalTraceBaseDefinedUnitsView<TraceInstruction> {
protected static <T> T replaceIfNotNull(T cur, T rep) {
return rep != null ? rep : cur;

View File

@ -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<UndefinedDBTraceData, DBTraceUndefinedDataView>
implements TraceUndefinedDataView {
implements TraceUndefinedDataView, InternalBaseCodeUnitsView<TraceData> {
/**
* Construct the view

View File

@ -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<UndefinedDBTraceData> implements TraceUndefinedDataView {
AbstractSingleDBTraceCodeUnitsView<UndefinedDBTraceData>
implements TraceUndefinedDataView, InternalBaseCodeUnitsView<TraceData> {
protected final static int CACHE_MAX_SNAPS = 5;

View File

@ -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<T extends TraceCodeUnit>
extends TraceBaseCodeUnitsView<T> {
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;
}
}

View File

@ -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<T extends TraceCodeUnit>
extends TraceBaseDefinedUnitsView<T>, InternalBaseCodeUnitsView<T> {
@Override
default void clear(TracePlatform platform, Range<Long> span, Register register,
TaskMonitor monitor) throws CancelledException {
AddressRange range = platform.getConventionalRegisterRange(getSpace(), register);
clear(span, range, true, monitor);
}
}

View File

@ -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<TraceData> {
@Override
default TraceData create(TracePlatform platform, Range<Long> 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());
}
}

View File

@ -77,6 +77,11 @@ public class DBTraceMemoryManager extends AbstractDBTraceSpaceBasedManager<DBTra
return overlayAdapter.createOverlayAddressSpace(name, base);
}
@Override
public AddressSpace getOrCreateOverlayAddressSpace(String name, AddressSpace base) {
return overlayAdapter.getOrCreateOverlayAddressSpace(name, base);
}
@Override
public void deleteOverlayAddressSpace(String name) {
overlayAdapter.deleteOverlayAddressSpace(name);

View File

@ -60,14 +60,14 @@ public class DBTraceObjectRegister implements TraceObjectRegister, DBTraceObject
}
@Override
public int getLength() {
public int getBitLength() {
return TraceObjectInterfaceUtils.getValue(object, computeMinSnap(),
TargetRegister.LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
TargetRegister.BIT_LENGTH_ATTRIBUTE_NAME, Integer.class, 0);
}
@Override
public void setValue(Range<Long> 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");
}

View File

@ -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<M extends DBTraceSpaceBas
return getForRegisterSpace(frame.getStack().getThread(), frame.getLevel(), createIfAbsent);
}
private TraceObject searchForRegisterContainer(TraceObject object, int frameLevel) {
PathPredicates regsMatcher = object.getRoot()
.getTargetSchema()
.searchForRegisterContainer(frameLevel, object.getCanonicalPath().getKeyList());
for (PathPattern regsPattern : regsMatcher.getPatterns()) {
TraceObject regsObj = trace.getObjectManager()
.getObjectByCanonicalPath(
TraceObjectKeyPath.of(regsPattern.getSingletonPath()));
if (regsObj != null) {
return regsObj;
}
}
return null;
}
private M doGetForRegisterSpaceFoundContainer(TraceObject object, TraceObject objRegs,
boolean createIfAbsent) {
String name = objRegs.getCanonicalPath().toString();
@ -284,21 +265,15 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
}
}
try (LockHold hold = LockHold.lock(lock.writeLock())) {
AddressSpace as = trace.getBaseAddressFactory().getAddressSpace(name);
if (as == null) {
as = trace.getMemoryManager()
.createOverlayAddressSpace(name,
trace.getBaseAddressFactory().getRegisterSpace());
}
AddressSpace as = trace.getMemoryManager()
.getOrCreateOverlayAddressSpace(name,
trace.getBaseAddressFactory().getRegisterSpace());
M space = getForSpace(as, createIfAbsent);
synchronized (regSpacesByObject) {
regSpacesByObject.put(object, space);
}
return space;
}
catch (DuplicateNameException e) {
throw new AssertionError(e); // I checked for it first, with a lock
}
}
protected M getForRegisterSpaceObjectThread(TraceObjectThread thread, int frameLevel,
@ -319,7 +294,7 @@ public abstract class AbstractDBTraceSpaceBasedManager<M extends DBTraceSpaceBas
if (object.getTargetSchema().getInterfaces().contains(TargetRegisterContainer.class)) {
return doGetForRegisterSpaceFoundContainer(object, object, createIfAbsent);
}
TraceObject objRegs = searchForRegisterContainer(object, frameLevel);
TraceObject objRegs = object.queryRegisterContainer(frameLevel);
if (objRegs != null) {
return doGetForRegisterSpaceFoundContainer(object, objRegs, createIfAbsent);
}

View File

@ -36,10 +36,18 @@ public interface DBTraceSpaceBased extends DBTraceSpaceKey {
return false;
}
default String explainLanguages(AddressSpace space) {
if (space.getName().equals(getAddressSpace().getName())) {
return ". It's likely they come from different languages. Check the platform.";
}
return "";
}
default long assertInSpace(Address addr) {
if (!isMySpace(addr.getAddressSpace())) {
throw new IllegalArgumentException(
"Address '" + addr + "' is not in this space: '" + getAddressSpace() + "'");
"Address '" + addr + "' is not in this space: '" + getAddressSpace() + "'" +
explainLanguages(addr.getAddressSpace()));
}
return addr.getOffset();
}
@ -47,7 +55,8 @@ public interface DBTraceSpaceBased extends DBTraceSpaceKey {
default void assertInSpace(AddressRange range) {
if (!isMySpace(range.getAddressSpace())) {
throw new IllegalArgumentException(
"Address Range '" + range + "' is not in this space: '" + getAddressSpace() + "'");
"Address Range '" + range + "' is not in this space: '" + getAddressSpace() + "'" +
explainLanguages(range.getAddressSpace()));
}
}

View File

@ -859,13 +859,25 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject {
.map(o -> o.queryInterface(ifClass));
}
// TODO: Post filter until GP-1301
private boolean isActuallyInterface(TraceObjectValPath path,
Class<? extends TargetObject> 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<? extends TraceObjectValPath> querySuccessorsTargetInterface(Range<Long> span,
Class<? extends TargetObject> 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

View File

@ -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
*
* <p>
* 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
*
* <p>
* 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
*
* <p>
* 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
*
* <p>
* 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<String> 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

View File

@ -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<T extends TraceCodeUnit> {
*/
boolean intersectsRange(TraceAddressSnapRange range);
/**
* Get the set of registers for the trace's base language
*
* @return the register set
*/
default Set<Register> 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<T extends TraceCodeUnit> {
* @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
*
* <p>
* 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<T extends TraceCodeUnit> {
* <p>
* 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

View File

@ -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<T extends TraceCodeUnit>
throws CancelledException {
clear(span, TraceRegisterUtils.rangeForRegister(register), true, monitor);
}
/**
* Clear the units contained within the given span and platform register
*
* <p>
* 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<Long> span, Register register, TaskMonitor monitor)
throws CancelledException;
}

View File

@ -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<TraceDat
*/
default TraceData create(Range<Long> 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
*
* <p>
* 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<Long> lifespan, Register register,
DataType dataType) throws CodeUnitInsertionException;
}

View File

@ -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.
*
* <p>
* 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
*
* <p>
* 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
*

View File

@ -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<Long> lifespan, byte[] value);

View File

@ -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;
}
}

View File

@ -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<TraceSchedule> {
* @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<TraceSchedule> {
* @param sleigh the lines of sleigh, excluding the terminating semicolons.
* @return the resulting schedule
*/
public TraceSchedule patched(TraceThread thread, List<String> sleigh) {
public TraceSchedule patched(TraceThread thread, Language language, List<String> 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());
}

View File

@ -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) {

View File

@ -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,

View File

@ -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()

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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)),