GP-2426: Refactor emulator to use trace access shims. Implement register mapping conventions.

This commit is contained in:
Dan 2022-09-13 16:02:02 -04:00
parent 975db1919c
commit e4f18ad824
202 changed files with 8221 additions and 4199 deletions

View file

@ -22,8 +22,7 @@ import java.util.concurrent.CompletableFuture;
import agent.gdb.manager.GdbStackFrame;
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
import ghidra.dbg.agent.DefaultTargetObject;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.target.*;
import ghidra.dbg.target.schema.*;
import ghidra.dbg.util.PathUtils;
import ghidra.lifecycle.Internal;
@ -36,7 +35,7 @@ import ghidra.program.model.address.Address;
attributes = {
@TargetAttributeType(type = Void.class) })
public class GdbModelTargetStackFrame extends DefaultTargetObject<TargetObject, GdbModelTargetStack>
implements TargetStackFrame, GdbModelSelectableObject {
implements TargetStackFrame, TargetAggregate, GdbModelSelectableObject {
public static final String FUNC_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "function";
public static final String FROM_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "from"; // TODO

View file

@ -42,7 +42,8 @@ import ghidra.util.Msg;
@TargetAttributeType(type = Void.class) })
public class GdbModelTargetThread
extends DefaultTargetObject<TargetObject, GdbModelTargetThreadContainer> implements
TargetThread, TargetExecutionStateful, TargetSteppable, GdbModelSelectableObject {
TargetThread, TargetExecutionStateful, TargetSteppable, TargetAggregate,
GdbModelSelectableObject {
protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( //
TargetStepKind.ADVANCE, //
TargetStepKind.FINISH, //

View file

@ -29,6 +29,7 @@ import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator;
import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript;
import ghidra.app.services.DebuggerTraceManagerService;
@ -46,6 +47,7 @@ import ghidra.program.model.listing.InstructionIterator;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager;
@ -135,7 +137,9 @@ public class DebuggerEmuExampleScript extends GhidraScript {
* library. This emulator will still know how to integrate with the UI, reading through to
* open programs and writing state back into the trace.
*/
BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(tool, trace, 0, null) {
TracePlatform host = trace.getPlatformManager().getHostPlatform();
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool, null, host, 0);
BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(access) {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this);
@ -169,7 +173,7 @@ public class DebuggerEmuExampleScript extends GhidraScript {
thread.stepInstruction();
snapshot =
time.createSnapshot("Stepped to " + thread.getCounter());
emulator.writeDown(trace, snapshot.getKey(), 0);
emulator.writeDown(host, snapshot.getKey(), 0);
}
printerr("We should not have completed 10 steps!");
}

View file

@ -23,6 +23,7 @@ import docking.action.DockingAction;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.GoToAction;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.async.AsyncUtils;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.*;
@ -93,12 +94,13 @@ public abstract class DebuggerGoToTrait {
}
public CompletableFuture<Boolean> goToSleigh(AddressSpace space, PcodeExpression expression) {
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(current);
CompletableFuture<byte[]> result = expression.evaluate(executor);
return result.thenApply(offset -> {
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool, current);
CompletableFuture<byte[]> result =
CompletableFuture.supplyAsync(() -> expression.evaluate(executor));
return result.thenApplyAsync(offset -> {
Address address = space.getAddress(
Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian()));
return goToAddress(address);
});
}, AsyncUtils.SWING_EXECUTOR);
}
}

View file

@ -434,6 +434,10 @@ public class DebuggerStackProvider extends ComponentProviderAdapter {
return;
}
Register pc = curTrace.getBaseLanguage().getProgramCounter();
if (pc == null) {
contextChanged();
return;
}
RegisterValue value = regs.getViewValue(current.getViewSnap(), pc);
if (value == null) {
contextChanged();

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.watch;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.tuple.Pair;
@ -30,9 +31,9 @@ import ghidra.app.services.DebuggerStateEditingService.StateEditor;
import ghidra.docking.settings.Settings;
import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.pcode.exec.trace.*;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
@ -45,7 +46,7 @@ import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.symbol.TraceLabelSymbol;
import ghidra.trace.model.thread.TraceThread;
@ -63,7 +64,7 @@ public class WatchRow {
private SleighLanguage language;
private PcodeExecutor<Pair<byte[], TraceMemoryState>> executorWithState;
private ReadDepsPcodeExecutor executorWithAddress;
private AsyncPcodeExecutor<byte[]> asyncExecutor;
private PcodeExecutor<byte[]> asyncExecutor; // name is reminder to use asynchronously
private String expression;
private String typePath;
@ -117,7 +118,9 @@ public class WatchRow {
protected void doTargetReads() {
if (compiled != null && asyncExecutor != null) {
compiled.evaluate(asyncExecutor).exceptionally(ex -> {
CompletableFuture<byte[]> asyncEvaluation =
CompletableFuture.supplyAsync(() -> compiled.evaluate(asyncExecutor));
asyncEvaluation.exceptionally(ex -> {
error = ex;
Swing.runIfSwingOrRunLater(() -> {
provider.watchTableModel.notifyUpdated(this);
@ -174,9 +177,10 @@ public class WatchRow {
extends DirectBytesTracePcodeExecutorStatePiece {
private AddressSet reads = new AddressSet();
public ReadDepsTraceBytesPcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) {
super(trace, snap, thread, frame);
public ReadDepsTraceBytesPcodeExecutorStatePiece(TracePlatform platform, long snap,
TraceThread thread, int frame) {
super(DirectBytesTracePcodeExecutorState.getDefaultThreadAccess(platform, snap, thread,
frame));
}
@Override
@ -197,7 +201,7 @@ public class WatchRow {
}
@Override
protected void setInSpace(TraceMemorySpace space, long offset, int size, byte[] val) {
protected void setInSpace(AddressSpace space, long offset, int size, byte[] val) {
throw new UnsupportedOperationException("Expression cannot write to trace");
}
@ -233,18 +237,31 @@ public class WatchRow {
}
}
protected static ReadDepsPcodeExecutor buildAddressDepsExecutor(
/**
* Build an executor that can compute three things simultaneously
*
* <p>
* This computes the concrete value, its address, and the set of physical addresses involved in
* the computation. The resulting pair gives the value and its address. To get the addresses
* involved, invoke {@link ReadDepsPcodeExecutor#getReads()} after evaluation.
*
* @param tool the plugin tool
* @param coordinates the coordinates providing context for the evaluation
* @return an executor for evaluating the watch
*/
protected static ReadDepsPcodeExecutor buildAddressDepsExecutor(PluginTool tool,
DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace();
TracePlatform platform = DebuggerPcodeUtils.getCurrentPlatform(tool, coordinates);
ReadDepsTraceBytesPcodeExecutorStatePiece piece =
new ReadDepsTraceBytesPcodeExecutorStatePiece(trace, coordinates.getViewSnap(),
new ReadDepsTraceBytesPcodeExecutorStatePiece(platform, coordinates.getViewSnap(),
coordinates.getThread(), coordinates.getFrame());
Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Watch expressions require a SLEIGH language");
}
PcodeExecutorState<Pair<byte[], Address>> paired = new DefaultPcodeExecutorState<>(piece)
.paired(new AddressOfPcodeExecutorStatePiece(language.isBigEndian()));
.paired(new AddressOfPcodeExecutorStatePiece(language));
PairedPcodeArithmetic<byte[], Address> arithmetic = new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), AddressOfPcodeArithmetic.INSTANCE);
return new ReadDepsPcodeExecutor(piece, (SleighLanguage) language, arithmetic, paired);
@ -270,11 +287,12 @@ public class WatchRow {
recompile();
}
if (coordinates.isAliveAndReadsPresent()) {
asyncExecutor = DebuggerPcodeUtils.executorForCoordinates(coordinates);
asyncExecutor =
DebuggerPcodeUtils.executorForCoordinates(provider.getTool(), coordinates);
}
executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace,
coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame());
executorWithAddress = buildAddressDepsExecutor(coordinates);
executorWithAddress = buildAddressDepsExecutor(provider.getTool(), coordinates);
}
public void setExpression(String expression) {

View file

@ -38,6 +38,9 @@ public class DefaultDebuggerMemoryMapper implements DebuggerMemoryMapper {
}
protected static Address toSameNamedSpace(Address addr, AddressFactory factory) {
if (addr.isRegisterAddress()) {
throw new IllegalArgumentException("Memory mapper cannot handle register addresses");
}
return factory.getAddressSpace(addr.getAddressSpace().getName())
.getAddress(addr.getOffset());
}

View file

@ -23,6 +23,7 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.*;
import ghidra.trace.model.target.TraceObject;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction;
public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMapper {
@ -98,5 +99,14 @@ public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMappe
catch (AddressOverflowException e) {
throw new AssertionError(e);
}
try {
platform.addMappedRegisterRange();
}
catch (AddressOverflowException e) {
Msg.showError(this, null, "Map Registers",
"The host language cannot accomodate register storage for the" +
" guest platform (language: " + platform.getLanguage() + ")");
}
}
}

View file

@ -0,0 +1,107 @@
/* ###
* 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.emulation;
import java.util.concurrent.*;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.memory.TraceMemoryState;
/**
* An abstract executor state piece that knows to read live state if applicable
*
* <p>
* This requires a memory-access shim for the debugger. It will check if the shim is associated with
* a live session. If so, it will direct the recorder to capture the desired state, if they're not
* already {@link TraceMemoryState#KNOWN}. When such a target comments is required, the state will
* wait up to 1 second for it to complete (see
* {@link AbstractRWTargetCachedSpace#waitTimeout(CompletableFuture)}).
*/
public abstract class AbstractRWTargetPcodeExecutorStatePiece
extends BytesTracePcodeExecutorStatePiece {
abstract class AbstractRWTargetCachedSpace extends CachedSpace {
public AbstractRWTargetCachedSpace(Language language, AddressSpace space,
PcodeDebuggerDataAccess backing) {
super(language, space, backing);
}
protected abstract void fillUninitialized(AddressSet uninitialized);
@Override
public byte[] read(long offset, int size) {
if (backing != null) {
AddressSet uninitialized =
addrSet(bytes.getUninitialized(offset, offset + size - 1));
if (uninitialized.isEmpty()) {
return super.read(offset, size);
}
fillUninitialized(uninitialized);
AddressSetView unknown = backing.intersectUnknown(
addrSet(bytes.getUninitialized(offset, offset + size - 1)));
if (!unknown.isEmpty()) {
warnUnknown(unknown);
}
}
// TODO: What to flush when bytes in the trace change?
return super.read(offset, size);
}
protected <T> T waitTimeout(CompletableFuture<T> future) {
try {
return future.get(1, TimeUnit.SECONDS);
}
catch (TimeoutException e) {
throw new AccessPcodeExecutionException("Timed out reading or writing target", e);
}
catch (InterruptedException | ExecutionException e) {
throw new AccessPcodeExecutionException("Error reading or writing target", e);
}
}
}
protected final PcodeDebuggerDataAccess data;
/**
* Construct a piece
*
* @param data the trace-data access shim
*/
public AbstractRWTargetPcodeExecutorStatePiece(PcodeDebuggerDataAccess data) {
super(data);
this.data = data;
}
/**
* A partially implemented space map which retrieves "backing" objects from the trace's memory
* and register spaces.
*/
protected abstract class TargetBackedSpaceMap
extends CacheingSpaceMap<PcodeDebuggerDataAccess, CachedSpace> {
@Override
protected PcodeDebuggerDataAccess getBacking(AddressSpace space) {
return data;
}
}
}

View file

@ -1,141 +0,0 @@
/* ###
* 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.emulation;
import java.util.concurrent.*;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction;
/**
* An executor state piece that knows to read live state if applicable
*
* <p>
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
* the recorder's snap. If so, it will direct the recorder to capture the desired state, if they're
* not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to 1
* second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
*/
public abstract class AbstractReadsTargetPcodeExecutorStatePiece
extends BytesTracePcodeExecutorStatePiece {
abstract class AbstractReadsTargetCachedSpace extends CachedSpace {
public AbstractReadsTargetCachedSpace(Language language, AddressSpace space,
TraceMemorySpace backing, long snap) {
super(language, space, backing, snap);
}
protected abstract void fillUninitialized(AddressSet uninitialized);
protected boolean isLive() {
return recorder != null && recorder.isRecording() && recorder.getSnap() == snap;
}
protected AddressSet computeUnknown(AddressSet uninitialized) {
return uninitialized.subtract(backing.getAddressesWithState(snap, uninitialized,
s -> s != null && s != TraceMemoryState.UNKNOWN));
}
@Override
public byte[] read(long offset, int size) {
if (backing != null) {
AddressSet uninitialized =
addrSet(bytes.getUninitialized(offset, offset + size - 1));
if (uninitialized.isEmpty()) {
return super.read(offset, size);
}
fillUninitialized(uninitialized);
AddressSet unknown =
computeUnknown(addrSet(bytes.getUninitialized(offset, offset + size - 1)));
if (!unknown.isEmpty()) {
warnUnknown(unknown);
}
}
// TODO: What to flush when bytes in the trace change?
return super.read(offset, size);
}
protected <T> T waitTimeout(CompletableFuture<T> future) {
try {
return future.get(1, TimeUnit.SECONDS);
}
catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new AccessPcodeExecutionException("Timed out reading target", e);
}
}
}
protected final TraceRecorder recorder;
protected final PluginTool tool;
public AbstractReadsTargetPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(trace, snap, thread, frame);
this.tool = tool;
this.recorder = recorder;
}
/**
* Get the tool that manages this state's emulator.
*
* <p>
* This is necessary to obtain the static mapping service, in case memory should be filled from
* static images.
*
* @return the tool
*/
public PluginTool getTool() {
return tool;
}
/**
* Get the recorder associated with the trace
*
* @return this is used to check for and perform live reads
*/
public TraceRecorder getRecorder() {
return recorder;
}
/**
* A partially implemented space map which retrieves "backing" objects from the trace's memory
* and register spaces.
*/
protected abstract class TargetBackedSpaceMap
extends CacheingSpaceMap<TraceMemorySpace, CachedSpace> {
@Override
protected TraceMemorySpace getBacking(AddressSpace space) {
try (UndoableTransaction tid =
UndoableTransaction.start(trace, "Create space")) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true);
}
}
}
}

View file

@ -15,13 +15,11 @@
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.*;
import ghidra.app.plugin.core.debug.service.emulation.data.*;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.trace.BytesTracePcodeEmulator;
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
/**
* A trace emulator that knows how to read target memory when necessary
@ -39,51 +37,27 @@ import ghidra.trace.model.thread.TraceThread;
*/
public class BytesDebuggerPcodeEmulator extends BytesTracePcodeEmulator
implements DebuggerPcodeMachine<byte[]> {
protected final PluginTool tool;
protected final TraceRecorder recorder;
protected final PcodeDebuggerAccess access;
/**
* Create the emulator
*
* @param tool the tool creating the emulator
* @param trace the trace from which the emulator loads state
* @param snap the snap from which the emulator loads state
* @param recorder if applicable, the recorder for the trace's live target
* @param access the trace-and-debugger access shim
*/
public BytesDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder) {
super(trace, snap);
this.tool = tool;
this.recorder = recorder;
}
@Override
public PluginTool getTool() {
return tool;
}
@Override
public TraceRecorder getRecorder() {
return recorder;
}
@Override
protected BytesPcodeThread createThread(String name) {
BytesPcodeThread thread = super.createThread(name);
initializeThreadContext(thread);
return thread;
public BytesDebuggerPcodeEmulator(PcodeDebuggerAccess access) {
super(access);
this.access = access;
}
@Override
public TracePcodeExecutorState<byte[]> createSharedState() {
return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0, recorder);
return new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RO);
}
@Override
public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
TraceThread traceThread =
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName());
return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0,
recorder);
return new RWTargetRegistersPcodeExecutorState(access.getDataForLocalState(emuThread, 0),
Mode.RO);
}
}

View file

@ -15,9 +15,7 @@
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess;
/**
* The Debugger's default emulator factory
@ -32,8 +30,7 @@ public class BytesDebuggerPcodeEmulatorFactory implements DebuggerPcodeEmulatorF
}
@Override
public DebuggerPcodeMachine<?> create(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder) {
return new BytesDebuggerPcodeEmulator(tool, trace, snap, recorder);
public DebuggerPcodeMachine<?> create(PcodeDebuggerAccess access) {
return new BytesDebuggerPcodeEmulator(access);
}
}

View file

@ -43,10 +43,12 @@ import ghidra.async.AsyncLazyMap;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.pcode.exec.DebuggerPcodeUtils;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot;
@ -183,6 +185,8 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
@AutoServiceConsumed
private DebuggerModelService modelService;
@AutoServiceConsumed
private DebuggerPlatformService platformService;
@AutoServiceConsumed
private DebuggerStaticMappingService staticMappings;
@SuppressWarnings("unused")
private AutoService.Wiring autoServiceWiring;
@ -456,7 +460,14 @@ 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?
*/
TracePlatform platform = DebuggerPcodeUtils.getCurrentPlatform(platformService,
traceManager.getCurrentFor(trace).trace(trace));
TraceSchedule time = key.time;
CachedEmulator ce;
DebuggerPcodeMachine<?> emu;
Map.Entry<CacheKey, CachedEmulator> ancestor = findNearestPrefix(key);
@ -473,19 +484,23 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
ce = ancestor.getValue();
emu = ce.emulator;
monitor.initialize(time.totalTickCount() - prevKey.time.totalTickCount());
createRegisterSpaces(trace, time, monitor);
monitor.setMessage("Emulating");
time.finish(trace, prevKey.time, emu, monitor);
}
else {
emu = emulatorFactory.create(tool, trace, time.getSnap(),
emu = emulatorFactory.create(tool, platform, time.getSnap(),
modelService == null ? null : modelService.getRecorder(trace));
ce = new CachedEmulator(emu);
monitor.initialize(time.totalTickCount());
createRegisterSpaces(trace, time, monitor);
monitor.setMessage("Emulating");
time.execute(trace, emu, monitor);
}
TraceSnapshot destSnap;
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) {
destSnap = findScratch(trace, time);
emu.writeDown(trace, destSnap.getKey(), time.getSnap());
emu.writeDown(platform, destSnap.getKey(), time.getSnap());
}
synchronized (cache) {
@ -502,6 +517,19 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm
return destSnap.getKey();
}
protected void createRegisterSpaces(Trace trace, TraceSchedule time, TaskMonitor monitor) {
if (trace.getObjectManager().getRootObject() == null) {
return;
}
// Cause object-register support to copy values into new register spaces
monitor.setMessage("Creating register spaces");
try (UndoableTransaction tid = UndoableTransaction.start(trace, "Prepare emulation")) {
for (TraceThread thread : time.getThreads(trace)) {
trace.getMemoryManager().getMemoryRegisterSpace(thread, 0, true);
}
}
}
@Override
public long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor)
throws CancelledException {

View file

@ -15,9 +15,11 @@
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.util.classfinder.ExtensionPoint;
/**
@ -41,11 +43,21 @@ public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint {
* Create the emulator
*
* @param tool the tool creating the emulator
* @param trace the user's current trace from which the emulator should load state
* @param platform the user's current trace platform from which the emulator should load state
* @param snap the user's current snap from which the emulator should load state
* @param recorder if applicable, the recorder for the trace's live target
* @return the emulator
*/
DebuggerPcodeMachine<?> create(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder);
default DebuggerPcodeMachine<?> create(PluginTool tool, TracePlatform platform, long snap,
TraceRecorder recorder) {
return create(new DefaultPcodeDebuggerAccess(tool, recorder, platform, snap));
}
/**
* Create the emulator
*
* @param access the trace-and-debugger access shim
* @return the emulator
*/
DebuggerPcodeMachine<?> create(PcodeDebuggerAccess access);
}

View file

@ -15,9 +15,6 @@
*/
package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory;
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator;
import ghidra.pcode.exec.trace.TracePcodeMachine;
@ -26,26 +23,10 @@ import ghidra.pcode.exec.trace.TracePcodeMachine;
* A Debugger-integrated emulator (or p-code machine)
*
* <p>
* This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator
* developers should use this interface, but emulator clients should not. Clients should use
* {@link PcodeMachine} instead. A common implementation is an emulator with concrete plus some
* auxiliary state. To realize such a machine, please see {@link AuxDebuggerPcodeEmulator} and
* {@link AuxDebuggerEmulatorPartsFactory}.
* A common implementation is an emulator with concrete plus some auxiliary state. To realize such a
* machine, please see {@link AuxDebuggerPcodeEmulator} and {@link AuxDebuggerEmulatorPartsFactory}.
*
* @param <T> the type of values in the machine's memory and registers
*/
public interface DebuggerPcodeMachine<T> extends TracePcodeMachine<T> {
/**
* Get the tool where this emulator is integrated
*
* @return the tool
*/
PluginTool getTool();
/**
* Get the trace's recorder for its live target, if applicable
*
* @return the recorder, or null
*/
TraceRecorder getRecorder();
}

View file

@ -0,0 +1,47 @@
/* ###
* 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.emulation;
/**
* A write flag for target-associated emulator states
*/
public enum Mode {
/**
* The state can write the target directly
*/
RW {
@Override
public boolean isWriteTarget() {
return true;
}
},
/**
* The state will never write the target
*/
RO {
@Override
public boolean isWriteTarget() {
return false;
}
};
/**
* Check if the mode permits writing the target
*
* @return true to allow, false to prohibit
*/
public abstract boolean isWriteTarget();
}

View file

@ -216,8 +216,8 @@ public enum ProgramEmulationUtils {
*/
public static void initializeRegisters(Trace trace, long snap, TraceThread thread,
Program program, Address tracePc, Address programPc, TraceMemoryRegion stack) {
TraceMemorySpace space =
trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
TraceMemoryManager memory = trace.getMemoryManager();
TraceMemorySpace regSpace = memory.getMemoryRegisterSpace(thread, true);
if (program != null) {
ProgramContext ctx = program.getProgramContext();
for (Register reg : Stream.of(ctx.getRegistersWithValues())
@ -227,20 +227,30 @@ public enum ProgramEmulationUtils {
if (rv == null || !rv.hasAnyValue()) {
continue;
}
TraceMemoryOperations space =
reg.getAddressSpace().isRegisterSpace() ? regSpace : memory;
// Set all the mask bits
space.setValue(snap, new RegisterValue(reg, BigInteger.ZERO).combineValues(rv));
}
}
space.setValue(snap, new RegisterValue(trace.getBaseLanguage().getProgramCounter(),
Register regPC = trace.getBaseLanguage().getProgramCounter();
TraceMemoryOperations spacePC =
regPC.getAddressSpace().isRegisterSpace() ? regSpace : memory;
spacePC.setValue(snap, new RegisterValue(regPC,
NumericUtilities.unsignedLongToBigInteger(tracePc.getAddressableWordOffset())));
if (stack != null) {
CompilerSpec cSpec = trace.getBaseCompilerSpec();
Address sp = cSpec.stackGrowsNegative()
? stack.getMaxAddress()
: stack.getMinAddress();
space.setValue(snap,
new RegisterValue(cSpec.getStackPointer(),
NumericUtilities.unsignedLongToBigInteger(sp.getAddressableWordOffset())));
Register regSP = cSpec.getStackPointer();
if (regSP != null) {
TraceMemoryOperations spaceSP =
regSP.getAddressSpace().isRegisterSpace() ? regSpace : memory;
spaceSP.setValue(snap,
new RegisterValue(regSP,
NumericUtilities.unsignedLongToBigInteger(sp.getAddressableWordOffset())));
}
}
}

View file

@ -0,0 +1,34 @@
/* ###
* 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.emulation;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerMemoryAccess;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
/**
* A state composing a single {@link RWTargetMemoryPcodeExecutorStatePiece}
*/
public class RWTargetMemoryPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param data the trace-memory access shim
* @param mode whether to ever write the target
*/
public RWTargetMemoryPcodeExecutorState(PcodeDebuggerMemoryAccess data, Mode mode) {
super(new RWTargetMemoryPcodeExecutorStatePiece(data, mode));
}
}

View file

@ -0,0 +1,127 @@
/* ###
* 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.emulation;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerMemoryAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.memory.TraceMemoryState;
/**
* An executor state piece that knows to read live memory if applicable
*
* <p>
* This requires a trace-memory access shim for the debugger. It will check if the shim is
* associated with a live session. If so, it will direct the recorder to capture the block(s)
* containing the read, if they're not already {@link TraceMemoryState#KNOWN}. When such a target
* comments is required, the state will wait up to 1 second for it to complete (see
* {@link AbstractRWTargetCachedSpace#waitTimeout(CompletableFuture)}).
*
* <p>
* This state will also attempt to fill unknown bytes with values from mapped static images. The
* order to retrieve state is:
* <ol>
* <li>The cache, i.e., this state object</li>
* <li>The trace</li>
* <li>The live target, if applicable</li>
* <li>Mapped static images, if available</li>
* </ol>
*
* <p>
* If all those defer, the state is read as if filled with 0s.
*/
public class RWTargetMemoryPcodeExecutorStatePiece
extends AbstractRWTargetPcodeExecutorStatePiece {
/**
* A space, corresponding to a memory space, of this state
*
* <p>
* All of the actual read logic is contained here. We override the space map factory so that it
* creates these spaces.
*/
protected class RWTargetMemoryCachedSpace extends AbstractRWTargetCachedSpace {
protected final PcodeDebuggerMemoryAccess backing;
public RWTargetMemoryCachedSpace(Language language, AddressSpace space,
PcodeDebuggerMemoryAccess backing) {
super(language, space, backing);
this.backing = backing;
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
if (space.isUniqueSpace()) {
return;
}
AddressSetView unknown;
unknown = backing.intersectUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
if (waitTimeout(backing.readFromTargetMemory(unknown))) {
unknown = backing.intersectUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
if (backing.readFromStaticImages(bytes, unknown)) {
unknown = backing.intersectUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
}
@Override
public void write(long offset, byte[] val, int srcOffset, int length) {
if (mode.isWriteTarget() && !space.isUniqueSpace() &&
waitTimeout(backing.writeTargetMemory(space.getAddress(offset), val))) {
// Change should already be recorded, if successful
return;
}
super.write(offset, val, srcOffset, length);
}
}
private final Mode mode;
/**
* Construct a piece
*
* @param data the trace-memory access shim
* @param mode whether to ever write the target
*/
public RWTargetMemoryPcodeExecutorStatePiece(PcodeDebuggerMemoryAccess data, Mode mode) {
super(data);
this.mode = mode;
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) {
return new RWTargetMemoryCachedSpace(language, space,
(PcodeDebuggerMemoryAccess) data);
}
};
}
}

View file

@ -0,0 +1,34 @@
/* ###
* 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.emulation;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerRegistersAccess;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
/**
* A state composing a single {@link RWTargetRegistersPcodeExecutorStatePiece}
*/
public class RWTargetRegistersPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param data the trace-registers access shim
* @param mode whether to ever write the target
*/
public RWTargetRegistersPcodeExecutorState(PcodeDebuggerRegistersAccess data, Mode mode) {
super(new RWTargetRegistersPcodeExecutorStatePiece(data, mode));
}
}

View file

@ -0,0 +1,111 @@
/* ###
* 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.emulation;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerDataAccess;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerRegistersAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.memory.TraceMemoryState;
/**
* An executor state piece that knows to read live registers if applicable
*
* <p>
* This requires a trace-register access shim for the debugger. It will check if the shim is
* associated with a live session. If so, it will direct the recorder to capture the register(s) to
* be read, if they're not already {@link TraceMemoryState#KNOWN}. When such a target comments is
* required, the state will wait up to 1 second for it to complete (see
* {@link AbstractRWTargetCachedSpace#waitTimeout(CompletableFuture)}).
*
* <ol>
* <li>The cache, i.e., this state object</li>
* <li>The trace</li>
* <li>The live target, if applicable</li>
* </ol>
*
* <p>
* If all those defer, the state is read as if filled with 0s.
*/
public class RWTargetRegistersPcodeExecutorStatePiece
extends AbstractRWTargetPcodeExecutorStatePiece {
/**
* A space, corresponding to a register space (really a thread) of this state
*
* <p>
* All of the actual read logic is contained here. We override the space map factory so that it
* creates these spaces.
*/
protected class RWTargetRegistersCachedSpace extends AbstractRWTargetCachedSpace {
protected final PcodeDebuggerRegistersAccess backing;
public RWTargetRegistersCachedSpace(Language language, AddressSpace space,
PcodeDebuggerRegistersAccess backing) {
super(language, space, backing);
this.backing = backing;
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
if (space.isUniqueSpace()) {
return;
}
if (!backing.isLive()) {
return;
}
AddressSetView unknown = backing.intersectUnknown(uninitialized);
waitTimeout(backing.readFromTargetRegisters(unknown));
}
@Override
public void write(long offset, byte[] val, int srcOffset, int length) {
if (mode.isWriteTarget() && !space.isUniqueSpace() &&
waitTimeout(backing.writeTargetRegister(space.getAddress(offset), val))) {
// Change should already be recorded, if successful
return;
}
super.write(offset, val, srcOffset, length);
}
}
private final Mode mode;
/**
* Construct a piece
*
* @param data the trace-register access shim
* @param mode whether to ever write the target
*/
public RWTargetRegistersPcodeExecutorStatePiece(PcodeDebuggerRegistersAccess data, Mode mode) {
super(data);
this.mode = mode;
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) {
return new RWTargetRegistersCachedSpace(language, space,
(PcodeDebuggerRegistersAccess) data);
}
};
}
}

View file

@ -1,43 +0,0 @@
/* ###
* 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.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
/**
* A state composing a single {@link ReadsTargetMemoryPcodeExecutorStatePiece}
*/
public class ReadsTargetMemoryPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param tool the tool of the emulator
* @param trace the trace of the emulator
* @param snap the snap of the emulator
* @param thread probably null, since this the shared part
* @param frame probably 0, because frame only matters for non-null thread
* @param recorder the recorder of the emulator
*/
public ReadsTargetMemoryPcodeExecutorState(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
recorder));
}
}

View file

@ -1,173 +0,0 @@
/* ###
* 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.emulation;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* An executor state piece that knows to read live memory if applicable
*
* <p>
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
* the recorder's snap. If so, it will direct the recorder to capture the block(s) containing the
* read, if they're not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state
* will wait up to 1 second (see
* {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
*
* <p>
* This state will also attempt to fill unknown bytes with values from mapped static images. The
* order to retrieve state is:
* <ol>
* <li>The cache, i.e., this state object</li>
* <li>The trace</li>
* <li>The live target, if applicable</li>
* <li>Mapped static images, if available</li>
* </ol>
*
* <p>
* If all those defer, the state is read as if filled with 0s.
*/
public class ReadsTargetMemoryPcodeExecutorStatePiece
extends AbstractReadsTargetPcodeExecutorStatePiece {
/**
* A space, corresponding to a memory space, of this state
*
* <p>
* All of the actual read logic is contained here. We override the space map factory so that it
* creates these spaces.
*/
protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space,
TraceMemorySpace backing, long snap) {
super(language, space, backing, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
AddressSet unknown;
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
if (fillUnknownWithRecorder(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
if (fillUnknownWithStaticImages(unknown)) {
unknown = computeUnknown(uninitialized);
if (unknown.isEmpty()) {
return;
}
}
}
protected boolean fillUnknownWithRecorder(AddressSet unknown) {
if (!isLive()) {
return false;
}
waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false));
return true;
}
private boolean fillUnknownWithStaticImages(AddressSet unknown) {
boolean result = false;
// TODO: Expand to block? DON'T OVERWRITE KNOWN!
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
byte[] data = new byte[4096];
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
.getOpenMappedViews(trace, unknown, snap)
.entrySet()) {
Program program = ent.getKey();
Memory memory = program.getMemory();
AddressSetView initialized = memory.getLoadedAndInitializedAddressSet();
Collection<MappedAddressRange> mappedSet = ent.getValue();
for (MappedAddressRange mappedRng : mappedSet) {
AddressRange drng = mappedRng.getDestinationAddressRange();
long shift = mappedRng.getShift();
for (AddressRange subdrng : initialized.intersectRange(drng.getMinAddress(),
drng.getMaxAddress())) {
Msg.debug(this,
"Filling in unknown trace memory in emulator using mapped image: " +
program + ": " + subdrng);
long lower = subdrng.getMinAddress().getOffset();
long fullLen = subdrng.getLength();
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(data.length, fullLen);
try {
int read =
memory.getBytes(space.getAddress(lower), data, 0, len);
if (read < len) {
Msg.warn(this,
" Partial read of " + subdrng + ". Got " + read +
" bytes");
}
// write(lower - shift, data, 0 ,read);
bytes.putData(lower - shift, data, 0, read);
}
catch (MemoryAccessException | AddressOutOfBoundsException e) {
throw new AssertionError(e);
}
lower += len;
fullLen -= len;
}
result = true;
}
}
}
return result;
}
}
public ReadsTargetMemoryPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new ReadsTargetMemoryCachedSpace(language, space, backing, snap);
}
};
}
}

View file

@ -1,43 +0,0 @@
/* ###
* 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.emulation;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
/**
* A state composing a single {@link ReadsTargetRegistersPcodeExecutorStatePiece}
*/
public class ReadsTargetRegistersPcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param tool the tool of the emulator
* @param trace the trace of the emulator
* @param snap the snap of the emulator
* @param thread the thread to which the state is assigned
* @param frame the frame to which the state is assigned, probably 0
* @param recorder the recorder of the emulator
*/
public ReadsTargetRegistersPcodeExecutorState(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap, thread, frame,
recorder));
}
}

View file

@ -1,108 +0,0 @@
/* ###
* 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.emulation;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.Msg;
/**
* An executor state piece that knows to read live memory if applicable
*
* <p>
* This takes a handle to the trace's recorder, if applicable, and will check if the source snap is
* the recorder's snap. If so, it will direct the recorder to capture the register to be read, if
* it's not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to
* 1 second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}).
*
* <ol>
* <li>The cache, i.e., this state object</li>
* <li>The trace</li>
* <li>The live target, if applicable</li>
* </ol>
*
* <p>
* If all those defer, the state is read as if filled with 0s.
*/
public class ReadsTargetRegistersPcodeExecutorStatePiece
extends AbstractReadsTargetPcodeExecutorStatePiece {
/**
* A space, corresponding to a register space (really a thread) of this state
*
* <p>
* All of the actual read logic is contained here. We override the space map factory so that it
* creates these spaces.
*/
protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace {
public ReadsTargetRegistersCachedSpace(Language language, AddressSpace space,
TraceMemorySpace source, long snap) {
super(language, space, source, snap);
}
@Override
protected void fillUninitialized(AddressSet uninitialized) {
if (!isLive()) {
return;
}
AddressSet unknown = computeUnknown(uninitialized);
Set<Register> toRead = new HashSet<>();
for (AddressRange rng : unknown) {
Register register =
language.getRegister(rng.getMinAddress(), (int) rng.getLength());
if (register == null) {
Msg.error(this, "Could not figure register for " + rng);
}
else if (!recorder.getRegisterMapper(thread)
.getRegistersOnTarget()
.contains(register)) {
Msg.warn(this, "Register not recognized by target: " + register);
}
else {
toRead.add(register);
}
}
waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead));
}
}
public ReadsTargetRegistersPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap,
TraceThread thread, int frame, TraceRecorder recorder) {
super(tool, trace, snap, thread, frame, recorder);
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TargetBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new ReadsTargetRegistersCachedSpace(language, space, backing, snap);
}
};
}
}

View file

@ -0,0 +1,50 @@
/* ###
* 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.emulation.data;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.trace.data.AbstractPcodeTraceAccess;
import ghidra.trace.model.guest.TracePlatform;
/**
* An abstract implementation of {@link PcodeDebuggerAccess}
*
* @param <S> the type of shared data-access shims provided
* @param <L> the type of thread-local data-access shims provided
*/
public abstract class AbstractPcodeDebuggerAccess<S extends PcodeDebuggerMemoryAccess, L extends PcodeDebuggerRegistersAccess>
extends AbstractPcodeTraceAccess<S, L>
implements PcodeDebuggerAccess {
protected final PluginTool tool;
protected final TraceRecorder recorder;
/**
* Construct a shim
*
* @param tool the tool controlling the session
* @param recorder the target's recorder
* @param platform the associated platform, having the same trace as the recorder
* @param snap the associated snap
*/
public AbstractPcodeDebuggerAccess(PluginTool tool, TraceRecorder recorder,
TracePlatform platform, long snap) {
super(platform, snap);
this.tool = tool;
this.recorder = recorder;
}
}

View file

@ -0,0 +1,54 @@
/* ###
* 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.emulation.data;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
/**
* The default debugger-and-trace access shim for a session
*/
public class DefaultPcodeDebuggerAccess extends
AbstractPcodeDebuggerAccess //
<DefaultPcodeDebuggerMemoryAccess, DefaultPcodeDebuggerRegistersAccess> {
/**
* Construct a shim
*
* @param tool the tool controlling the session
* @param recorder the target's recorder
* @param platform the associated platform, having the same trace as the recorder
* @param snap the associated snap
*/
public DefaultPcodeDebuggerAccess(PluginTool tool, TraceRecorder recorder,
TracePlatform platform, long snap) {
super(tool, recorder, platform, snap);
}
@Override
protected DefaultPcodeDebuggerMemoryAccess newDataForSharedState() {
return new DefaultPcodeDebuggerMemoryAccess(tool, recorder, platform, snap, viewport);
}
@Override
protected DefaultPcodeDebuggerRegistersAccess newDataForLocalState(TraceThread thread,
int frame) {
return new DefaultPcodeDebuggerRegistersAccess(tool, recorder, platform, snap, thread,
frame, viewport);
}
}

View file

@ -0,0 +1,166 @@
/* ###
* 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.emulation.data;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceMemoryAccess;
import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
/**
* The default data-and-debugger-access shim for session memory
*/
public class DefaultPcodeDebuggerMemoryAccess extends DefaultPcodeTraceMemoryAccess
implements PcodeDebuggerMemoryAccess, InternalPcodeDebuggerDataAccess {
protected final PluginTool tool;
protected final TraceRecorder recorder;
/**
* Construct a shim
*
* @param tool the tool controlling the session
* @param recorder the target's recorder
* @param platform the associated platform, having the same trace as the recorder
* @param snap the associated snap
* @param viewport the viewport, set to the same snapshot
*/
protected DefaultPcodeDebuggerMemoryAccess(PluginTool tool, TraceRecorder recorder,
TracePlatform platform, long snap, TraceTimeViewport viewport) {
super(platform, snap, viewport);
this.tool = Objects.requireNonNull(tool);
this.recorder = recorder;
}
@Override
public boolean isLive() {
return recorder != null && recorder.isRecording() && recorder.getSnap() == snap;
}
@Override
public PluginTool getTool() {
return tool;
}
@Override
public TraceRecorder getRecorder() {
return recorder;
}
@Override
public CompletableFuture<Boolean> readFromTargetMemory(AddressSetView guestView) {
if (!isLive()) {
return CompletableFuture.completedFuture(false);
}
AddressSetView hostView = platform.mapGuestToHost(guestView);
return recorder.readMemoryBlocks(hostView, TaskMonitor.DUMMY, false)
.thenCompose(__ -> recorder.getTarget().getModel().flushEvents())
.thenCompose(__ -> recorder.flushTransactions())
.thenAccept(__ -> platform.getTrace().flushEvents())
.thenApply(__ -> true);
}
@Override
public CompletableFuture<Boolean> writeTargetMemory(Address address, byte[] data) {
if (!isLive()) {
return CompletableFuture.completedFuture(false);
}
return recorder.writeMemory(address, data)
.thenCompose(__ -> recorder.getTarget().getModel().flushEvents())
.thenCompose(__ -> recorder.flushTransactions())
.thenAccept(__ -> platform.getTrace().flushEvents())
.thenApply(__ -> true);
}
@Override
public boolean readFromStaticImages(SemisparseByteArray bytes, AddressSetView guestView) {
boolean result = false;
// TODO: Expand to block? DON'T OVERWRITE KNOWN!
DebuggerStaticMappingService mappingService =
tool.getService(DebuggerStaticMappingService.class);
byte[] data = new byte[4096];
Trace trace = platform.getTrace();
AddressSetView hostView = platform.mapGuestToHost(guestView);
for (Entry<Program, Collection<MappedAddressRange>> ent : mappingService
.getOpenMappedViews(trace, hostView, snap)
.entrySet()) {
Program program = ent.getKey();
Memory memory = program.getMemory();
AddressSetView initialized = memory.getLoadedAndInitializedAddressSet();
Collection<MappedAddressRange> mappedSet = ent.getValue();
for (MappedAddressRange mappedRng : mappedSet) {
AddressRange progRng = mappedRng.getDestinationAddressRange();
AddressSpace progSpace = progRng.getAddressSpace();
for (AddressRange subProgRng : initialized.intersectRange(progRng.getMinAddress(),
progRng.getMaxAddress())) {
Msg.debug(this,
"Filling in unknown trace memory in emulator using mapped image: " +
program + ": " + subProgRng);
long lower = subProgRng.getMinAddress().getOffset();
long fullLen = subProgRng.getLength();
while (fullLen > 0) {
int len = MathUtilities.unsignedMin(data.length, fullLen);
try {
Address progAddr = progSpace.getAddress(lower);
int read = memory.getBytes(progAddr, data, 0, len);
if (read < len) {
Msg.warn(this,
" Partial read of " + subProgRng + ". Got " + read +
" bytes");
}
Address hostAddr = mappedRng.mapDestinationToSource(progAddr);
Address guestAddr = platform.mapHostToGuest(hostAddr);
// write(lower - shift, data, 0 ,read);
bytes.putData(guestAddr.getOffset(), data, 0, read);
}
catch (MemoryAccessException | AddressOutOfBoundsException e) {
throw new AssertionError(e);
}
lower += len;
fullLen -= len;
}
result = true;
}
}
}
return result;
}
@Override
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
return new DefaultPcodeDebuggerPropertyAccess<>(this, name, type);
}
}

View file

@ -0,0 +1,80 @@
/* ###
* 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.emulation.data;
import com.google.common.collect.Range;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.pcode.exec.trace.data.DefaultPcodeTracePropertyAccess;
import ghidra.program.model.address.Address;
import ghidra.program.model.util.PropertyMap;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.DefaultTraceLocation;
/**
* The default trace-and-debugger-property access shim
*
* <p>
* This implementation defers to the same property of mapped static images when the property is not
* set in the trace.
*
* @param <T> the type of the property's values
*/
public class DefaultPcodeDebuggerPropertyAccess<T>
extends DefaultPcodeTracePropertyAccess<T> {
protected final InternalPcodeDebuggerDataAccess data;
/**
* Construct the shim
*
* @param data the trace-and-debugger-data access shim providing this property access shim
* @param name the name of the property
* @param type the type of the property
*/
protected DefaultPcodeDebuggerPropertyAccess(InternalPcodeDebuggerDataAccess data,
String name, Class<T> type) {
super(data, name, type);
this.data = data;
}
@Override
protected T whenNull(Address hostAddress) {
DebuggerStaticMappingService mappingService =
data.getTool().getService(DebuggerStaticMappingService.class);
if (mappingService == null) {
return super.whenNull(hostAddress);
}
ProgramLocation progLoc = mappingService.getOpenMappedLocation(new DefaultTraceLocation(
data.getPlatform().getTrace(), null, Range.singleton(data.getSnap()), hostAddress));
if (progLoc == null) {
return super.whenNull(hostAddress);
}
// NB. This is stored in the program, not the user data, despite what the name implies
PropertyMap map =
progLoc.getProgram().getUsrPropertyManager().getPropertyMap(name);
if (map == null) {
return super.whenNull(hostAddress);
}
Object object = map.getObject(progLoc.getByteAddress());
if (!type.isInstance(object)) {
// TODO: Warn?
return super.whenNull(hostAddress);
}
return type.cast(object);
}
}

View file

@ -0,0 +1,119 @@
/* ###
* 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.emulation.data;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceRegistersAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.TraceTimeViewport;
import ghidra.util.Msg;
/**
* The default data-and-debugger access shim for session registers
*/
public class DefaultPcodeDebuggerRegistersAccess extends DefaultPcodeTraceRegistersAccess
implements PcodeDebuggerRegistersAccess, InternalPcodeDebuggerDataAccess {
protected final PluginTool tool;
protected final TraceRecorder recorder;
/**
* Construct a shim
*
* @param tool the tool controlling the session
* @param recorder the target's recorder
* @param platform the associated platform, having the same trace as the recorder
* @param snap the associated snap
* @param thread the associated thread whose registers to access
* @param frame the associated frame, or 0 if not applicable
* @param viewport the viewport, set to the same snapshot
*/
protected DefaultPcodeDebuggerRegistersAccess(PluginTool tool, TraceRecorder recorder,
TracePlatform platform, long snap, TraceThread thread, int frame,
TraceTimeViewport viewport) {
super(platform, snap, thread, frame, viewport);
this.tool = tool;
this.recorder = recorder;
}
@Override
public boolean isLive() {
return recorder != null && recorder.isRecording() && recorder.getSnap() == snap;
}
@Override
public PluginTool getTool() {
return tool;
}
@Override
public TraceRecorder getRecorder() {
return recorder;
}
@Override
public CompletableFuture<Boolean> readFromTargetRegisters(AddressSetView guestView) {
if (!isLive()) {
return CompletableFuture.completedFuture(false);
}
Set<Register> toRead = new HashSet<>();
Language language = platform.getLanguage();
for (AddressRange guestRng : guestView) {
Register register =
language.getRegister(guestRng.getMinAddress().getPhysicalAddress(),
(int) guestRng.getLength());
if (register == null) {
Msg.error(this, "Could not figure register for " + guestRng);
}
else if (!recorder.getRegisterMapper(thread)
.getRegistersOnTarget()
.contains(register)) {
Msg.warn(this, "Register not recognized by target: " + register);
}
else {
toRead.add(register);
}
}
return recorder.captureThreadRegisters(thread, 0, toRead)
.thenCompose(__ -> recorder.getTarget().getModel().flushEvents())
.thenCompose(__ -> recorder.flushTransactions())
.thenAccept(__ -> platform.getTrace().flushEvents())
.thenApply(__ -> true);
}
@Override
public CompletableFuture<Boolean> writeTargetRegister(Address address, byte[] data) {
if (!isLive()) {
return CompletableFuture.completedFuture(false);
}
return recorder.writeRegister(thread, frame, address.getPhysicalAddress(), data)
.thenCompose(__ -> recorder.getTarget().getModel().flushEvents())
.thenCompose(__ -> recorder.flushTransactions())
.thenAccept(__ -> platform.getTrace().flushEvents())
.thenApply(__ -> true);
}
// No need to override getPropertyAccess. Registers are not static mapped.
}

View file

@ -0,0 +1,28 @@
/* ###
* 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.emulation.data;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.lifecycle.Internal;
import ghidra.pcode.exec.trace.data.InternalPcodeTraceDataAccess;
@Internal
public interface InternalPcodeDebuggerDataAccess extends InternalPcodeTraceDataAccess {
PluginTool getTool();
TraceRecorder getRecorder();
}

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.app.plugin.core.debug.service.emulation.data;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
/**
* A trace-and-debugger access shim
*
* <p>
* In addition to the trace "coordinates" encapsulated by {@link PcodeTraceAccess}, this
* encapsulates the tool controlling a session and the session's trace recorder. This permits p-code
* executor/emulator states to access target data and to access session data, e.g., data from mapped
* static images. It supports the same method chain pattern as {@link PcodeTraceAccess}, but
* starting with {@link DefaultPcodeDebuggerAccess}.
*/
public interface PcodeDebuggerAccess extends PcodeTraceAccess {
@Override
PcodeDebuggerMemoryAccess getDataForSharedState();
@Override
PcodeDebuggerRegistersAccess getDataForLocalState(PcodeThread<?> thread, int frame);
}

View file

@ -0,0 +1,38 @@
/* ###
* 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.emulation.data;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/**
* A data-access shim for a trace and the debugger
*
* <p>
* This shim, in addition to the trace, can also access its associated target, as well as session
* information maintained by the Debugger tool.
*/
public interface PcodeDebuggerDataAccess extends PcodeTraceDataAccess {
/**
* Check if the associated trace represents a live session
*
* <p>
* The session is live if it's trace has a recorder and the source snapshot matches the
* recorder's destination snapshot.
*
* @return true if live, false otherwise
*/
boolean isLive();
}

View file

@ -0,0 +1,77 @@
/* ###
* 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.emulation.data;
import java.util.concurrent.CompletableFuture;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.trace.data.PcodeTraceMemoryAccess;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
/**
* A data-access shim for a trace's memory and the debugger
*/
public interface PcodeDebuggerMemoryAccess
extends PcodeTraceMemoryAccess, PcodeDebuggerDataAccess {
/**
* Instruct the associated recorder to read memory from the target
*
* <p>
* The recorder may quantize the given address set to pages. It will include all the requested
* addresses, though. If this shim is not associated with a live session, the returned future
* completes immediately with false.
*
* @param unknown the address set to read
* @return a future which completes when the read is complete and its results recorded to the
* trace. It completes with true when any part of target memory was successfully read.
* It completes with false if there is no target, or if the target was not read.
*/
CompletableFuture<Boolean> readFromTargetMemory(AddressSetView unknown);
/**
* Use the Debugger's static mapping service to read bytes from relocated program images
*
* <p>
* To be read, the program database for the static image must be open in the same tool as the
* trace being emulated. Depending on the use case, this may only be approximately correct. In
* particular, if the trace was from a live session that has since been terminated, and the
* image was relocated with fixups, reads at those fixups which fall through to static images
* will be incorrect, and may lead to undefined behavior in the emulated program.
*
* @param bytes the destination byte store
* @param unknown the address set to read
* @return true if any bytes were read, false if there was no effect
*/
boolean readFromStaticImages(SemisparseByteArray bytes, AddressSetView unknown);
/**
* Instruct the associated recorder to write target memory
*
* <p>
* In normal operation, this will also cause the recorder, upon a successful write, to record
* the same bytes into the destination trace. If this shim is not associated with a live
* session, the returned future completes immediately with false.
*
* @param address the address of the first byte to write
* @param data the bytes to write
* @return a future which completes when the write is complete and its results recorded to the
* trace. It completes with true when the target was written. It completes with false if
* there is no target, or if the target is not effected.
*/
CompletableFuture<Boolean> writeTargetMemory(Address address, byte[] data);
}

View file

@ -0,0 +1,57 @@
/* ###
* 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.emulation.data;
import java.util.concurrent.CompletableFuture;
import ghidra.pcode.exec.trace.data.PcodeTraceRegistersAccess;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
/**
* A data-access shim for a trace's registers and the debugger
*/
public interface PcodeDebuggerRegistersAccess
extends PcodeTraceRegistersAccess, PcodeDebuggerDataAccess {
/**
* Instruct the associated recorder to read registers from the target
*
* @param unknown the address set (in the platform's {@code register} space) of registers to
* read
* @return a future which completes when the read is complete and its results recorded to the
* trace. It completes with true when any part of target state was successfully read. It
* completes with false if there is no target, or if the target was not read.
*/
CompletableFuture<Boolean> readFromTargetRegisters(AddressSetView unknown);
/**
* Instruct the associated recorder to write target registers
*
* <p>
* In normal operation, this will also cause the recorder, upon a successful write, to record
* the same values into the destination trace. If this shim is not associated with a live
* session, the returned future completes immediately with false.
*
* @param address the address of the first byte to write (in the platform's {@code register}
* space)
* @param data the bytes to write
* @return a future which completes when the write is complete and its results recorded to the
* trace. It completes with true when the target was written. It completes with false if
* there is no target, or if the target is not effected.
*/
CompletableFuture<Boolean> writeTargetRegister(Address address, byte[] data);
}

View file

@ -16,7 +16,6 @@
package ghidra.app.services;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;

View file

@ -356,23 +356,37 @@ public interface TraceRecorder {
return writeMemory(address, data);
}
if (address.isRegisterAddress()) {
Language lang = getTrace().getBaseLanguage();
Register register = lang.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));
TraceMemorySpace regs =
getTrace().getMemoryManager().getMemoryRegisterSpace(thread, frameLevel, false);
rv = TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, getSnap(), regs, true);
return writeThreadRegisters(thread, frameLevel, Map.of(rv.getRegister(), rv));
return writeRegister(thread, frameLevel, address, data);
}
throw new IllegalArgumentException("Address is not in a recognized space: " + address);
}
/**
* Write a register (by address) of the given thread
*
* @param thread the thread
* @param frameLevel the frame, usually 0.
* @param address the address of the register
* @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);
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));
TraceMemorySpace regs =
getTrace().getMemoryManager().getMemoryRegisterSpace(thread, frameLevel, false);
rv = TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, getSnap(), regs, true);
return writeThreadRegisters(thread, frameLevel, Map.of(rv.getRegister(), rv));
}
/**
* Check if the given register exists on target (is mappable) for the given thread
*

View file

@ -1,109 +0,0 @@
/* ###
* 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.pcode.exec;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.async.AsyncUtils;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
/**
* An executor which can perform (some of) its work asynchronously
*
* <p>
* Note that a future returned from, e.g., {@link #executeAsync(SleighProgram, PcodeUseropLibrary)}
* may complete before the computation has actually been performed. They complete when all of the
* operations have been scheduled, and the last future has been written into the state. (This
* typically happens when any branch conditions have completed). Instead, a caller should read from
* the async state, which will return a future. The state will ensure that future does not complete
* until the computation has been performed -- assuming the requested variable actually depends on
* that computation.
*
* <p>
* TODO: Deprecate this? It's clever, but it'd probably be easier to just use a synchronous executor
* on a separate thread. The necessity of {@link #stepAsync(PcodeFrame, PcodeUseropLibrary)}, etc.,
* indicates a failure of the interface to encapsulate this use case. We can adjust the interface,
* which would probably not end well, or we can continue to allow the CompletableFuture-specific
* steppers to leak out, or we can just torch this and use another thread.
*
* @param <T> the type of values in the state
*/
public class AsyncPcodeExecutor<T> extends PcodeExecutor<CompletableFuture<T>> {
public AsyncPcodeExecutor(SleighLanguage language,
PcodeArithmetic<CompletableFuture<T>> arithmetic,
PcodeExecutorState<CompletableFuture<T>> state) {
super(language, arithmetic, state);
}
public CompletableFuture<Void> stepOpAsync(PcodeOp op, PcodeFrame frame,
PcodeUseropLibrary<CompletableFuture<T>> library) {
if (op.getOpcode() == PcodeOp.CBRANCH) {
return executeConditionalBranchAsync(op, frame);
}
stepOp(op, frame, library);
return AsyncUtils.NIL;
}
public CompletableFuture<Void> stepAsync(PcodeFrame frame,
PcodeUseropLibrary<CompletableFuture<T>> library) {
try {
return stepOpAsync(frame.nextOp(), frame, library);
}
catch (PcodeExecutionException e) {
e.frame = frame;
return CompletableFuture.failedFuture(e);
}
catch (Exception e) {
return CompletableFuture.failedFuture(
new PcodeExecutionException("Exception during pcode execution", frame, e));
}
}
public CompletableFuture<Void> executeConditionalBranchAsync(PcodeOp op, PcodeFrame frame) {
Varnode condVar = op.getInput(1);
CompletableFuture<T> cond = state.getVar(condVar);
return cond.thenAccept(c -> {
if (arithmetic.isTrue(cond, Purpose.CONDITION)) {
executeBranch(op, frame);
}
});
}
public CompletableFuture<Void> executeAsync(PcodeProgram program,
PcodeUseropLibrary<CompletableFuture<T>> library) {
return executeAsync(program.code, program.useropNames, library);
}
protected CompletableFuture<Void> executeAsyncLoop(PcodeFrame frame,
PcodeUseropLibrary<CompletableFuture<T>> library) {
if (frame.isFinished()) {
return AsyncUtils.NIL;
}
return stepAsync(frame, library)
.thenComposeAsync(__ -> executeAsyncLoop(frame, library));
}
public CompletableFuture<Void> executeAsync(List<PcodeOp> code,
Map<Integer, String> useropNames, PcodeUseropLibrary<CompletableFuture<T>> library) {
PcodeFrame frame = new PcodeFrame(language, code, useropNames);
return executeAsyncLoop(frame, library);
}
}

View file

@ -1,118 +0,0 @@
/* ###
* 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.pcode.exec;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import ghidra.program.model.lang.Endian;
import ghidra.program.model.lang.Language;
/**
* An arithmetic which can operate on futures of a wrapped type
*
* @see AsyncPcodeExecutor for comment regarding potential deprecation or immediate removal
* @param <T> the type of values wrapped
*/
public class AsyncWrappedPcodeArithmetic<T> implements PcodeArithmetic<CompletableFuture<T>> {
public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_BE =
new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.BIG_ENDIAN);
public static final AsyncWrappedPcodeArithmetic<byte[]> BYTES_LE =
new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.LITTLE_ENDIAN);
public static AsyncWrappedPcodeArithmetic<byte[]> forEndian(boolean isBigEndian) {
return isBigEndian ? BYTES_BE : BYTES_LE;
}
public static AsyncWrappedPcodeArithmetic<byte[]> forLanguage(Language language) {
return forEndian(language.isBigEndian());
}
private final PcodeArithmetic<T> arithmetic;
public AsyncWrappedPcodeArithmetic(PcodeArithmetic<T> arithmetic) {
this.arithmetic = arithmetic;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
AsyncWrappedPcodeArithmetic<?> that = (AsyncWrappedPcodeArithmetic<?>) obj;
return Objects.equals(this.arithmetic, that.arithmetic);
}
@Override
public Endian getEndian() {
return arithmetic.getEndian();
}
@Override
public CompletableFuture<T> unaryOp(int opcode, int sizeout, int sizein1,
CompletableFuture<T> in1) {
return in1.thenApply(t1 -> arithmetic.unaryOp(opcode, sizeout, sizein1, t1));
}
@Override
public CompletableFuture<T> binaryOp(int opcode, int sizeout, int sizein1,
CompletableFuture<T> in1, int sizein2, CompletableFuture<T> in2) {
return in1.thenCombine(in2,
(t1, t2) -> arithmetic.binaryOp(opcode, sizeout, sizein1, t1, sizein2, t2));
}
@Override
public CompletableFuture<T> modBeforeStore(int sizeout, int sizeinAddress,
CompletableFuture<T> inAddress, int sizeinValue, CompletableFuture<T> inValue) {
return inValue;
}
@Override
public CompletableFuture<T> modAfterLoad(int sizeout, int sizeinAddress,
CompletableFuture<T> inAddress, int sizeinValue, CompletableFuture<T> inValue) {
return inValue;
}
@Override
public CompletableFuture<T> fromConst(byte[] value) {
return CompletableFuture.completedFuture(arithmetic.fromConst(value));
}
@Override
public byte[] toConcrete(CompletableFuture<T> value, Purpose purpose) {
if (!value.isDone()) {
throw new ConcretionError("You need a better 8-ball", purpose);
}
return arithmetic.toConcrete(value.getNow(null), purpose);
}
@Override
public long sizeOf(CompletableFuture<T> value) {
if (!value.isDone()) {
// TODO: Make a class which has future and expected size?
throw new RuntimeException("You need a better 8-ball");
}
return arithmetic.sizeOf(value.getNow(null));
}
@Override
public CompletableFuture<T> sizeOfAbstract(CompletableFuture<T> value) {
return value.thenApply(v -> arithmetic.sizeOfAbstract(v));
}
}

View file

@ -1,101 +0,0 @@
/* ###
* 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.pcode.exec;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import ghidra.async.AsyncUtils;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.mem.MemBuffer;
/**
* An executor state piece which can operate on futures of a wrapped type
*
* @see AsyncPcodeExecutor for comment regarding potential deprecation or immediate removal
* @param <T> the type of values wrapped
*/
public class AsyncWrappedPcodeExecutorStatePiece<A, T>
implements PcodeExecutorStatePiece<CompletableFuture<A>, CompletableFuture<T>> {
protected final PcodeExecutorStatePiece<A, T> state;
protected final AsyncWrappedPcodeArithmetic<A> addressArithmetic;
protected final AsyncWrappedPcodeArithmetic<T> arithmetic;
private CompletableFuture<?> lastWrite = AsyncUtils.NIL;
public AsyncWrappedPcodeExecutorStatePiece(PcodeExecutorStatePiece<A, T> state) {
this.state = state;
this.addressArithmetic = new AsyncWrappedPcodeArithmetic<>(state.getAddressArithmetic());
this.arithmetic = new AsyncWrappedPcodeArithmetic<>(state.getArithmetic());
}
@Override
public AsyncWrappedPcodeArithmetic<A> getAddressArithmetic() {
return addressArithmetic;
}
@Override
public AsyncWrappedPcodeArithmetic<T> getArithmetic() {
return arithmetic;
}
protected boolean isWriteDone() {
return lastWrite.isDone();
}
protected <U> CompletableFuture<U> nextRead(Supplier<CompletableFuture<U>> next) {
return lastWrite.thenCompose(__ -> next.get()).exceptionally(ex -> null);
}
protected <U> void nextWrite(Supplier<CompletableFuture<U>> next) {
lastWrite = nextRead(next);
}
protected CompletableFuture<?> doSetVar(AddressSpace space, CompletableFuture<A> offset,
int size, boolean quantize, CompletableFuture<T> val) {
return offset.thenCompose(off -> val.thenAccept(v -> {
state.setVar(space, off, size, quantize, v);
}));
}
@Override
public void setVar(AddressSpace space, CompletableFuture<A> offset, int size,
boolean quantize, CompletableFuture<T> val) {
nextWrite(() -> doSetVar(space, offset, size, quantize, val));
}
protected CompletableFuture<T> doGetVar(AddressSpace space, CompletableFuture<A> offset,
int size, boolean quantize) {
return offset.thenApply(off -> {
return state.getVar(space, off, size, quantize);
});
}
@Override
public CompletableFuture<T> getVar(AddressSpace space, CompletableFuture<A> offset, int size,
boolean quantize) {
return nextRead(() -> doGetVar(space, offset, size, quantize));
}
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
if (!isWriteDone()) {
throw new AssertionError("An async write is still pending");
}
return state.getConcreteBuffer(address, purpose);
}
}

View file

@ -15,48 +15,118 @@
*/
package ghidra.pcode.exec;
import java.util.concurrent.CompletableFuture;
import java.util.Objects;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper;
import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece;
import ghidra.app.services.DebuggerPlatformService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.ThreadPcodeExecutorState;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
/**
* Utilities for evaluating or executing Sleigh/p-code in the Debugger
*/
public enum DebuggerPcodeUtils {
;
/**
* Get an executor which can be used to evaluate Sleigh expressions at the given coordinates,
* asynchronously.
* Get the current platform
*
* <p>
* TODO: Change this to be synchronous and have clients evaluate expressions in another thread?
* TODO: This should be part of {@link DebuggerTraceManagerService}.
*
* @param platformService the platform service
* @param coordinates the coordinates
* @return the executor
* @return the "current platform" for the coordinates
*/
public static AsyncPcodeExecutor<byte[]> executorForCoordinates(
public static TracePlatform getCurrentPlatform(DebuggerPlatformService platformService,
DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace();
if (platformService == null) {
return trace.getPlatformManager().getHostPlatform();
}
DebuggerPlatformMapper mapper = platformService.getCurrentMapperFor(trace);
if (mapper == null) {
return trace.getPlatformManager().getHostPlatform();
}
return Objects.requireNonNull(trace.getPlatformManager()
.getPlatform(mapper.getCompilerSpec(coordinates.getObject())));
}
/**
* Get the current platform
*
* <p>
* TODO: This should be part of {@link DebuggerTraceManagerService}.
*
* @param tool the plugin tool
* @param coordinates the coordinates
* @return the "current platform" for the coordinates
*/
public static TracePlatform getCurrentPlatform(PluginTool tool,
DebuggerCoordinates coordinates) {
return getCurrentPlatform(tool.getService(DebuggerPlatformService.class), coordinates);
}
/**
* Get a p-code executor state for the given coordinates
*
* <p>
* If a thread is included, the executor state will have access to both the memory and registers
* in the context of that thread. Otherwise, only memory access is permitted.
*
* @param tool the plugin tool. TODO: This shouldn't be required
* @param coordinates the coordinates
* @return the state
*/
public static PcodeExecutorState<byte[]> executorStateForCoordinates(PluginTool tool,
DebuggerCoordinates coordinates) {
// TODO: Make platform part of coordinates
Trace trace = coordinates.getTrace();
if (trace == null) {
throw new IllegalArgumentException("Coordinates have no trace");
}
Language language = trace.getBaseLanguage();
TracePlatform platform = getCurrentPlatform(tool, coordinates);
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Given trace does not use a Sleigh language");
throw new IllegalArgumentException(
"Given trace or platform does not use a Sleigh language");
}
SleighLanguage slang = (SleighLanguage) language;
PcodeExecutorState<CompletableFuture<byte[]>> state;
if (coordinates.getRecorder() == null) {
state = new AsyncWrappedPcodeExecutorState<>(
new DirectBytesTracePcodeExecutorStatePiece(trace, coordinates.getViewSnap(),
coordinates.getThread(), coordinates.getFrame()));
DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool,
coordinates.getRecorder(), platform, coordinates.getSnap());
PcodeExecutorState<byte[]> shared =
new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RW);
if (coordinates.getThread() == null) {
return shared;
}
else {
state = new TraceRecorderAsyncPcodeExecutorState(coordinates.getRecorder(),
coordinates.getSnap(), coordinates.getThread(), coordinates.getFrame());
}
return new AsyncPcodeExecutor<>(slang, AsyncWrappedPcodeArithmetic.forLanguage(slang),
state);
PcodeExecutorState<byte[]> local = new RWTargetRegistersPcodeExecutorState(
access.getDataForLocalState(coordinates.getThread(), coordinates.getFrame()), Mode.RW);
return new ThreadPcodeExecutorState<>(shared, local);
}
/**
* Get an executor which can be used to evaluate Sleigh expressions at the given coordinates
*
* <p>
* If a thread is included, the executor will have access to both the memory and registers in
* the context of that thread. Otherwise, only memory access is permitted.
*
* @param tool the plugin tool. TODO: This shouldn't be required
* @param coordinates the coordinates
* @return the executor
*/
public static PcodeExecutor<byte[]> executorForCoordinates(PluginTool tool,
DebuggerCoordinates coordinates) {
PcodeExecutorState<byte[]> state = executorStateForCoordinates(tool, coordinates);
SleighLanguage slang = (SleighLanguage) state.getLanguage();
return new PcodeExecutor<>(slang, BytesPcodeArithmetic.forLanguage(slang), state);
}
}

View file

@ -1,40 +0,0 @@
/* ###
* 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.pcode.exec;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.trace.model.thread.TraceThread;
/**
* A state composing a single {@link TraceRecorderAsyncPcodeExecutorStatePiece}
*/
public class TraceRecorderAsyncPcodeExecutorState
extends DefaultPcodeExecutorState<CompletableFuture<byte[]>> {
/**
* Create the state
*
* @param recorder the recorder for the trace's live target
* @param snap the user's current snap
* @param thread the user's current thread
* @param frame the user's current frame
*/
public TraceRecorderAsyncPcodeExecutorState(TraceRecorder recorder, long snap,
TraceThread thread, int frame) {
super(new TraceRecorderAsyncPcodeExecutorStatePiece(recorder, snap, thread, frame));
}
}

View file

@ -1,154 +0,0 @@
/* ###
* 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.pcode.exec;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import ghidra.app.services.TraceRecorder;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.TraceMemoryStatePcodeExecutorStatePiece;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.task.TaskMonitor;
/**
* An executor state which can asynchronously read and write a live target, if applicable
*
* <p>
* This is used for executing Sleigh code to manipulate trace history or a live target.
*
* <p>
* TODO: It might be easier to re-factor this to operate synchronously, executing Sleigh programs in
* a separate thread.
*/
public class TraceRecorderAsyncPcodeExecutorStatePiece
extends AsyncWrappedPcodeExecutorStatePiece<byte[], byte[]> {
private final TraceRecorder recorder;
private final DirectBytesTracePcodeExecutorStatePiece traceState;
private final TraceMemoryStatePcodeExecutorStatePiece traceMemState;
public TraceRecorderAsyncPcodeExecutorStatePiece(TraceRecorder recorder, long snap,
TraceThread thread, int frame) {
super(
new DirectBytesTracePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame));
this.recorder = recorder;
this.traceState = (DirectBytesTracePcodeExecutorStatePiece) state;
this.traceMemState =
new TraceMemoryStatePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame);
}
protected CompletableFuture<?> doSetTargetVar(AddressSpace space, long offset, int size,
boolean quantize, byte[] val) {
return recorder.writeVariable(traceState.getThread(), 0, space.getAddress(offset), val);
}
protected byte[] knitFromResults(NavigableMap<Address, byte[]> map, Address addr, int size) {
Address floor = map.floorKey(addr);
NavigableMap<Address, byte[]> tail;
if (floor == null) {
tail = map;
}
else {
tail = map.tailMap(floor, true);
}
byte[] result = new byte[size];
for (Map.Entry<Address, byte[]> ent : tail.entrySet()) {
long off = ent.getKey().subtract(addr);
if (off >= size || off < 0) {
break;
}
int subSize = Math.min(size - (int) off, ent.getValue().length);
System.arraycopy(ent.getValue(), 0, result, (int) off, subSize);
}
return result;
}
protected CompletableFuture<byte[]> doGetTargetVar(AddressSpace space, long offset,
int size, boolean quantize) {
if (space.isMemorySpace()) {
Address addr = space.getAddress(quantizeOffset(space, offset));
AddressSet set = new AddressSet(addr, space.getAddress(offset + size - 1));
CompletableFuture<NavigableMap<Address, byte[]>> future =
recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, true);
return future.thenApply(map -> {
return knitFromResults(map, addr, size);
});
}
assert space.isRegisterSpace();
Language lang = recorder.getTrace().getBaseLanguage();
Register register = lang.getRegister(space, offset, size);
if (register == null) {
// TODO: Is this too restrictive?
throw new IllegalArgumentException(
"read from register space must be from one register");
}
Register baseRegister = register.getBaseRegister();
CompletableFuture<Map<Register, RegisterValue>> future =
recorder.captureThreadRegisters(traceState.getThread(), traceState.getFrame(),
Set.of(baseRegister));
return future.thenApply(map -> {
RegisterValue baseVal = map.get(baseRegister);
if (baseVal == null) {
return state.getVar(space, offset, size, quantize);
}
BigInteger val = baseVal.getRegisterValue(register).getUnsignedValue();
return Utils.bigIntegerToBytes(val, size,
recorder.getTrace().getBaseLanguage().isBigEndian());
});
}
protected boolean isTargetSpace(AddressSpace space) {
return traceState.getSnap() == recorder.getSnap() && !space.isConstantSpace() &&
!space.isUniqueSpace();
}
@Override
protected CompletableFuture<?> doSetVar(AddressSpace space,
CompletableFuture<byte[]> offset, int size, boolean quantize,
CompletableFuture<byte[]> val) {
if (!isTargetSpace(space)) {
return super.doSetVar(space, offset, size, quantize, val);
}
return offset.thenCompose(off -> val.thenCompose(v -> {
long lOff = traceState.getAddressArithmetic().toLong(off, Purpose.STORE);
return doSetTargetVar(space, lOff, size, quantize, v);
}));
}
@Override
protected CompletableFuture<byte[]> doGetVar(AddressSpace space,
CompletableFuture<byte[]> offset, int size, boolean quantize) {
if (!isTargetSpace(space)) {
return super.doGetVar(space, offset, size, quantize);
}
return offset.thenCompose(off -> {
TraceMemoryState ms = traceMemState.getVar(space, off, size, quantize);
if (ms == TraceMemoryState.KNOWN) {
return super.doGetVar(space, offset, size, quantize);
}
long lOff = traceState.getAddressArithmetic().toLong(off, Purpose.LOAD);
return doGetTargetVar(space, lOff, size, quantize);
});
}
}

View file

@ -21,8 +21,7 @@ import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator;
import ghidra.pcode.exec.trace.PairedTracePcodeExecutorStatePiece;
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.pcode.exec.trace.*;
import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory;
import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator;
@ -70,7 +69,9 @@ public interface AuxDebuggerEmulatorPartsFactory<U> extends AuxTraceEmulatorPart
* given concrete piece is already capable of doing that for concrete values. The auxiliary
* piece can, at its discretion, delegate to the concrete piece in order to derive its values.
* It should be able to independently load its state from the trace and mapped static program,
* since this is one way a user expects to initialize the auxiliary values.
* since this is one way a user expects to initialize the auxiliary values. It ought to use the
* same data-access shim as the given concrete state. See
* {@link TracePcodeExecutorStatePiece#getData()}.
*
* @param emulator the emulator
* @param concrete the concrete piece
@ -78,14 +79,14 @@ public interface AuxDebuggerEmulatorPartsFactory<U> extends AuxTraceEmulatorPart
*/
TracePcodeExecutorState<Pair<byte[], U>> createDebuggerSharedState(
AuxDebuggerPcodeEmulator<U> emulator,
ReadsTargetMemoryPcodeExecutorStatePiece concrete);
RWTargetMemoryPcodeExecutorStatePiece concrete);
/**
* Create the local (register) state of a new Debugger-integrated thread
*
* <p>
* Like
* {@link #createDebuggerSharedState(AuxDebuggerPcodeEmulator, ReadsTargetMemoryPcodeExecutorStatePiece)}
* {@link #createDebuggerSharedState(AuxDebuggerPcodeEmulator, RWTargetMemoryPcodeExecutorStatePiece)}
* this state must also be capable of lazily loading state from a trace and from a live target.
* Static programs can't be mapped into register space, so they do not apply here.
*
@ -96,5 +97,5 @@ public interface AuxDebuggerEmulatorPartsFactory<U> extends AuxTraceEmulatorPart
*/
TracePcodeExecutorState<Pair<byte[], U>> createDebuggerLocalState(
AuxDebuggerPcodeEmulator<U> emulator, PcodeThread<Pair<byte[], U>> thread,
ReadsTargetRegistersPcodeExecutorStatePiece concrete);
RWTargetRegistersPcodeExecutorStatePiece concrete);
}

View file

@ -18,14 +18,12 @@ package ghidra.pcode.exec.debug.auxiliary;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory;
import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator;
import ghidra.trace.model.Trace;
/**
* An Debugger-integrated emulator whose parts are manufactured by a
@ -43,49 +41,33 @@ import ghidra.trace.model.Trace;
*/
public abstract class AuxDebuggerPcodeEmulator<U> extends AuxTracePcodeEmulator<U>
implements DebuggerPcodeMachine<Pair<byte[], U>> {
protected final PluginTool tool;
protected final TraceRecorder recorder;
protected final PcodeDebuggerAccess access;
/**
* Create a new emulator
*
* @param tool the user's tool where the emulator is integrated
* @param trace the user's current trace from which the emulator loads state
* @param snap the user's current snapshot from which the emulator loads state
* @param recorder if applicable, the trace's recorder for its live target
* @param access the trace-and-debugger access shim
*/
public AuxDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap,
TraceRecorder recorder) {
super(trace, snap);
this.tool = tool;
this.recorder = recorder;
public AuxDebuggerPcodeEmulator(PcodeDebuggerAccess access) {
super(access);
this.access = access;
}
@Override
protected abstract AuxDebuggerEmulatorPartsFactory<U> getPartsFactory();
@Override
public PluginTool getTool() {
return tool;
}
@Override
public TraceRecorder getRecorder() {
return recorder;
}
@Override
public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() {
return getPartsFactory().createDebuggerSharedState(this,
new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, null, 0, recorder));
new RWTargetMemoryPcodeExecutorStatePiece(access.getDataForSharedState(), Mode.RO));
}
@Override
public TracePcodeExecutorState<Pair<byte[], U>> createLocalState(
PcodeThread<Pair<byte[], U>> thread) {
return getPartsFactory().createDebuggerLocalState(this, thread,
new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap,
getTraceThread(thread), 0,
recorder));
new RWTargetRegistersPcodeExecutorStatePiece(access.getDataForLocalState(thread, 0),
Mode.RO));
}
}

View file

@ -284,7 +284,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
Register contextReg = tb.language.getContextBaseRegister();
Register longMode = tb.language.getRegister("longMode");
RegisterValue rv = tb.trace.getRegisterContextManager()
.getValueWithDefault(tb.language, contextReg, 0, tb.addr(0x55550000));
.getValueWithDefault(tb.host, contextReg, 0, tb.addr(0x55550000));
rv = rv.assign(longMode, BigInteger.ZERO);
Instruction checkCtx;
try (UndoableTransaction tid = tb.startTransaction()) {

View file

@ -835,17 +835,20 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertTrue(listingProvider.actionGoTo.isEnabled());
performAction(listingProvider.actionGoTo, false);
DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class);
dialog1.setExpression("r0");
runSwing(() -> dialog1.okCallback());
runSwing(() -> {
dialog1.setExpression("r0");
dialog1.okCallback();
});
waitForPass(
() -> assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress()));
performAction(listingProvider.actionGoTo, false);
DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class);
dialog2.setExpression("*:4 r0");
runSwing(() -> dialog2.okCallback());
runSwing(() -> {
dialog2.setExpression("*:4 r0");
dialog2.okCallback();
});
waitForPass(
() -> assertEquals(tb.addr(0x00404321), listingProvider.getLocation().getAddress()));

View file

@ -627,18 +627,21 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
assertTrue(memBytesProvider.actionGoTo.isEnabled());
performAction(memBytesProvider.actionGoTo, false);
DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class);
dialog.setExpression("r0");
dialog.okCallback();
DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class);
runSwing(() -> {
dialog1.setExpression("r0");
dialog1.okCallback();
});
waitForPass(
() -> assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()));
performAction(memBytesProvider.actionGoTo, false);
dialog = waitForDialogComponent(DebuggerGoToDialog.class);
dialog.setExpression("*:4 r0");
dialog.okCallback();
DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class);
runSwing(() -> {
dialog2.setExpression("*:4 r0");
dialog2.okCallback();
});
waitForPass(
() -> assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress()));
@ -1093,16 +1096,20 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge
mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx");
waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty());
goToDyn(addr(trace, 0x55550800));
performAction(actionEdit);
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
performAction(actionEdit);
waitForSwing();
waitRecorder(recorder);
waitForPass(noExc(() -> {
traceManager.activateTrace(trace);
goToDyn(addr(trace, 0x55550800));
triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42");
waitForSwing();
waitRecorder(recorder);
byte[] data = new byte[4];
mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data);
assertArrayEquals(mb.arr(0x42, 0, 0, 0), data);
byte[] data = new byte[4];
mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data);
assertArrayEquals(mb.arr(0x42, 0, 0, 0), data);
}));
performAction(actionEdit);
}
@Test

View file

@ -18,10 +18,15 @@ package ghidra.app.plugin.core.debug.gui.stack;
import java.io.IOException;
import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObjectKeyPath;
import ghidra.trace.model.target.TraceObject.ConflictResolution;
import ghidra.trace.model.thread.TraceObjectThread;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.exception.DuplicateNameException;
public class DebuggerStackProviderObjectTest extends DebuggerStackProviderTest {
@ -51,46 +56,69 @@ public class DebuggerStackProviderObjectTest extends DebuggerStackProviderTest {
public void activateObjectsMode() throws Exception {
// NOTE the use of index='1' allowing object-based managers to ID unique path
ctx = XmlSchemaContext.deserialize("" + //
"<context>" + //
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + //
" <attribute name='Processes' schema='ProcessContainer' />" + //
" </schema>" + //
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element index='1' schema='Process' />" + // <---- NOTE HERE
" </schema>" + //
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + //
" <attribute name='Threads' schema='ThreadContainer' />" + //
" <attribute name='Memory' schema='RegionContainer' />" + //
" </schema>" + //
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Thread' />" + //
" </schema>" + //
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='Thread' />" + //
" <attribute name='Stack' schema='Stack' />" + //
" </schema>" + //
" <schema name='Stack' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <interface name='Stack' />" + //
" <element schema='Frame' />" + //
" </schema>" + //
" <schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='StackFrame' />" + //
" </schema>" + //
" <schema name='RegionContainer' canonical='yes' elementResync='NEVER' " + //
" attributeResync='ONCE'>" + //
" <element schema='Region' />" + //
" </schema>" + //
" <schema name='Region' elementResync='NEVER' attributeResync='NEVER'>" + //
" <interface name='MemoryRegion' />" + //
" </schema>" + //
"</context>");
ctx = XmlSchemaContext.deserialize("""
<context>
<schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
<attribute name='Processes' schema='ProcessContainer' />
</schema>
<schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element index='1' schema='Process' />
</schema>
<schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
<attribute name='Threads' schema='ThreadContainer' />
<attribute name='Memory' schema='RegionContainer' />
</schema>
<schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Thread' />
</schema>
<schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
<interface name='Thread' />
<interface name='Aggregate' />
<attribute name='Stack' schema='Stack' />
<attribute name='Registers' schema='RegisterContainer' />
</schema>
<schema name='Stack' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<interface name='Stack' />
<element schema='Frame' />
</schema>
<schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>
<interface name='StackFrame' />
</schema>
<schema name='RegisterContainer' canonical='yes' elementResync='NEVER'
attributeResync='NEVER'>
<interface name='RegisterContainer' />
<element schema='Register' />
</schema>
<schema name='Register' elementResync='NEVER' attributeResync='NEVER'>
<interface name='Register' />
</schema>
<schema name='RegionContainer' canonical='yes' elementResync='NEVER'
attributeResync='ONCE'>
<element schema='Region' />
</schema>
<schema name='Region' elementResync='NEVER' attributeResync='NEVER'>
<interface name='MemoryRegion' />
</schema>
</context>
""");
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session")));
}
}
@Override
protected TraceThread addThread(String n) throws DuplicateNameException {
try (UndoableTransaction tid = tb.startTransaction()) {
TraceObjectThread thread = (TraceObjectThread) super.addThread(n);
TraceObjectKeyPath regsPath = thread.getObject().getCanonicalPath().extend("Registers");
tb.trace.getObjectManager()
.createObject(regsPath)
.insert(thread.getLifespan(), ConflictResolution.DENY);
return thread;
}
}
}

View file

@ -405,7 +405,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceMemorySpace regVals =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
row.setRawValueString("0x1234");
runSwing(() -> row.setRawValueString("0x1234"));
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -414,7 +414,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals("0x1234", row.getRawValueString());
});
row.setRawValueString("1234"); // Decimal this time
runSwing(() -> row.setRawValueString("1234")); // Decimal this time
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -436,7 +436,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceMemorySpace regVals =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
row.setValueString("1234");
runSwing(() -> row.setValueString("1234"));
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -456,7 +456,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
// Wait for row to settle. TODO: Why is this necessary?
waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress()));
row.setRawValueString("0x1234");
runSwing(() -> row.setRawValueString("0x1234"));
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -468,7 +468,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
// Wait for row to settle. TODO: Why is this necessary?
waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress()));
row.setRawValueString("{ 12 34 56 78 9a bc de f0 }");
runSwing(() -> row.setRawValueString("{ 12 34 56 78 9a bc de f0 }"));
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -491,7 +491,9 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceMemoryOperations mem = tb.trace.getMemoryManager();
ByteBuffer buf = ByteBuffer.allocate(8);
row.setValueString("1234");
// Wait for row to settle. TODO: Why is this necessary?
waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress()));
runSwing(() -> row.setValueString("1234"));
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -516,7 +518,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceMemoryOperations mem = tb.trace.getMemoryManager();
ByteBuffer buf = ByteBuffer.allocate(14);
row.setValueString("\"Hello, World!\"");
runSwing(() -> row.setValueString("\"Hello, World!\""));
waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -561,7 +563,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
public void testEditRegisterTarget() throws Throwable {
WatchRow row = prepareTestEditTarget("r0");
row.setRawValueString("0x1234");
runSwing(() -> row.setRawValueString("0x1234"));
retryVoid(() -> {
assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0"));
}, List.of(AssertionError.class));
@ -573,7 +575,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
// Wait for the async reads to settle.
waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress()));
row.setRawValueString("0x1234");
runSwing(() -> row.setRawValueString("0x1234"));
retryVoid(() -> {
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34),
waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 8)));
@ -588,7 +590,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertFalse(recorder.isRegisterOnTarget(thread, r1));
assertFalse(row.isRawValueEditable());
row.setRawValueString("0x1234");
runSwingWithException(() -> row.setRawValueString("0x1234"));
}
protected void setupUnmappedDataSection() throws Throwable {

View file

@ -32,8 +32,8 @@ 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.AsyncPcodeExecutor;
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;
@ -143,8 +143,8 @@ 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);
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils
.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
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");
@ -181,8 +181,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread
thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils
.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread));
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");

View file

@ -18,24 +18,29 @@ package ghidra.app.plugin.core.debug.service.emulation;
import static org.junit.Assert.*;
import java.math.BigInteger;
import java.util.concurrent.CompletableFuture;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import com.google.common.collect.Range;
import generic.Unique;
import generic.test.category.NightlyCategory;
import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.Trace;
import ghidra.trace.model.*;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule;
@ -202,22 +207,75 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads());
TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
TraceMemoryManager mem = trace.getMemoryManager();
assertEquals(new BigInteger("000100", 16),
regs.getViewValue(0, regPC).getUnsignedValue());
assertEquals(new BigInteger("0000", 16), regs.getViewValue(0, regW0).getUnsignedValue());
mem.getViewValue(0, regPC).getUnsignedValue());
assertEquals(new BigInteger("0000", 16),
mem.getViewValue(0, regW0).getUnsignedValue());
assertEquals(new BigInteger("0800", 16),
regs.getViewValue(0, regW1).getUnsignedValue());
mem.getViewValue(0, regW1).getUnsignedValue());
long scratch =
emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY);
assertEquals(new BigInteger("000102", 16),
regs.getViewValue(scratch, regPC).getUnsignedValue());
mem.getViewValue(scratch, regPC).getUnsignedValue());
assertEquals(new BigInteger("1234", 16),
regs.getViewValue(scratch, regW0).getUnsignedValue());
mem.getViewValue(scratch, regW0).getUnsignedValue());
assertEquals(new BigInteger("0800", 16),
regs.getViewValue(scratch, regW1).getUnsignedValue());
mem.getViewValue(scratch, regW1).getUnsignedValue());
}
@Test
public void testPureEmulationRelocated() throws Throwable {
createAndOpenTrace("x86:LE:64:default");
createProgramFromTrace();
intoProject(program);
intoProject(tb.trace);
Assembler asm = Assemblers.getAssembler(program);
Memory memory = program.getMemory();
Address addrText = addr(program, 0x00400000);
Address addrData = addr(program, 0x00600000);
try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) {
MemoryBlock blockText = memory.createInitializedBlock("text", addrText, 0x1000,
(byte) 0, TaskMonitor.DUMMY, false);
blockText.setExecute(true);
memory.createInitializedBlock(".data", addrData, 0x1000, (byte) 0, TaskMonitor.DUMMY,
false);
// NOTE: qword ptr [0x00600800] is RIP-relative
asm.assemble(addrText, "MOV RAX, qword ptr [0x00600800]");
memory.setLong(addr(program, 0x00600800), 0xdeadbeefcafebabeL);
}
programManager.openProgram(program);
waitForSwing();
DebuggerStaticMappingService mappings =
tool.getService(DebuggerStaticMappingService.class);
CompletableFuture<Void> settled;
TraceThread thread;
TraceMemorySpace regs;
try (UndoableTransaction tid = tb.startTransaction()) {
thread = tb.getOrAddThread("Threads[0]", 0);
regs = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true);
regs.setValue(0, new RegisterValue(program.getLanguage().getProgramCounter(),
BigInteger.valueOf(0x55550000)));
settled = mappings.changesSettled();
mappings.addMapping(new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L),
tb.addr(0x55550000)), new ProgramLocation(program, addrText), 0x1000, false);
mappings.addMapping(new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L),
tb.addr(0x55750000)), new ProgramLocation(program, addrData), 0x1000, false);
}
waitForSwing();
waitOn(settled);
long scratch =
emulationPlugin.emulate(tb.trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY);
assertEquals("deadbeefcafebabe",
regs.getViewValue(scratch, tb.reg("RAX")).getUnsignedValue().toString(16));
}
}

View file

@ -448,6 +448,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
waitRecorder(recorder);
Trace trace = recorder.getTrace();
traceManager.openTrace(trace);
@ -464,6 +465,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
waitOn(mb.testModel.session.requestFocus(mb.testThread1));
TraceThread thread1 = recorder.getTraceThread(mb.testThread1);
assertNotNull(thread1);
waitForPass(() -> assertEquals(thread1, traceManager.getCurrentThread()));
TestTargetStack stack = mb.testThread1.addStack();

View file

@ -22,18 +22,25 @@ import java.util.Map;
import org.junit.Test;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper;
import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.ActionSource;
import ghidra.app.services.TraceRecorder;
import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorState;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebuggerGUITest {
/**
* Test the {@link DirectBytesTracePcodeExecutorState} in combination with
* {@link PcodeDebuggerAccess} to ensure it read and writes the target when appropriate.
*/
public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerGUITest {
@Test
public void testExecutorEval() throws Throwable {
@ -70,11 +77,11 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
assertTrue(rm.getRegistersOnTarget().contains(r1));
});
AsyncPcodeExecutor<byte[]> executor =
new AsyncPcodeExecutor<>(language, AsyncWrappedPcodeArithmetic.forLanguage(language),
new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0));
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool,
DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread));
byte[] result = waitOn(expr.evaluate(executor));
// In practice, this should be backgrounded, but we're in a test thread
byte[] result = expr.evaluate(executor);
assertEquals(11, Utils.bytesToLong(result, result.length, language.isBigEndian()));
}
@ -92,6 +99,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
waitRecorder(recorder);
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
Trace trace = recorder.getTrace();
@ -113,13 +121,12 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
assertTrue(rm.getRegistersOnTarget().contains(r1));
});
TraceRecorderAsyncPcodeExecutorState asyncState =
new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0);
AsyncPcodeExecutor<byte[]> executor = new AsyncPcodeExecutor<>(
language, AsyncWrappedPcodeArithmetic.forLanguage(language), asyncState);
PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool,
DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread));
waitOn(executor.executeAsync(prog, PcodeUseropLibrary.nil()));
waitOn(asyncState.getVar(language.getRegister("r2")));
executor.execute(prog, PcodeUseropLibrary.nil());
// Ignore return value. We'll assert that it got written to the trace
executor.state.getVar(language.getRegister("r2"));
assertEquals(BigInteger.valueOf(11), new BigInteger(1, regs.regVals.get("r2")));
}

View file

@ -21,8 +21,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import ghidra.dbg.agent.DefaultTargetObject;
import ghidra.dbg.target.TargetAggregate;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.*;
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
import ghidra.dbg.util.*;
import ghidra.dbg.util.CollectionUtils.Delta;
@ -906,4 +905,82 @@ public interface TargetObjectSchema {
schema.validateTypeAndInterfaces(element, parentPath, ent.getKey(), strict);
}
}
/**
* Search for a suitable register container
*
* <p>
* This will try with and without considerations for frames. If the schema indicates that
* register containers are not contained within frames, then frameLevel must be 0, otherwise
* this will return empty. If dependent on frameLevel, this will return two singleton paths: one
* for a decimal index and another for a hexadecimal index. If not, this will return a singleton
* path. If it fails to find a unique container, this will return empty.
*
* <p>
* <b>NOTE:</b> This must be used at the top of the search scope, probably the root schema. For
* example, to search the entire model for a register container related to {@code myObject}:
*
* <pre>
* for (PathPattern regPattern : myObject.getModel()
* .getSchema()
* .searchForRegisterContainer(0, myObject.getPath())) {
* TargetObject objRegs = myObject.getModel().getModelObject(regPattern.getSingletonPath());
* if (objRegs != null) {
* // found it
* }
* }
* </pre>
*
* <p>
* 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}
* 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 TargetStackFrame}, and the frame level (index) must precede it.
*
* @param frameLevel the frameLevel, must be 0 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;
}
List<String> threadPath = searchForAncestor(TargetThread.class, path);
if (threadPath == null) {
return PathPredicates.EMPTY;
}
PathPattern framePatternRelThread =
getSuccessorSchema(threadPath).searchFor(TargetStackFrame.class, false)
.getSingletonPattern();
if (framePatternRelThread == null) {
return PathPredicates.EMPTY;
}
if (framePatternRelThread.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> regsPath =
searchForSuitable(TargetRegisterContainer.class, framePath);
if (regsPath != null) {
result.addPattern(regsPath);
}
}
return result;
}
}

View file

@ -118,6 +118,11 @@ public class PathMatcher implements PathPredicates {
return patterns.iterator().next();
}
@Override
public Collection<PathPattern> getPatterns() {
return patterns;
}
protected void coalesceWilds(Set<String> result) {
if (result.contains("")) {
result.removeIf(PathUtils::isName);
@ -179,10 +184,10 @@ public class PathMatcher implements PathPredicates {
}
@Override
public PathMatcher applyKeys(List<String> indices) {
public PathMatcher applyKeys(Align align, List<String> indices) {
PathMatcher result = new PathMatcher();
for (PathPattern pat : patterns) {
result.addPattern(pat.applyKeys(indices));
result.addPattern(pat.applyKeys(align, indices));
}
return result;
}

View file

@ -199,6 +199,11 @@ public class PathPattern implements PathPredicates {
return this;
}
@Override
public Collection<PathPattern> getPatterns() {
return List.of(this);
}
@Override
public Set<String> getNextKeys(List<String> path) {
if (path.size() >= pattern.size()) {
@ -257,22 +262,26 @@ public class PathPattern implements PathPredicates {
}
@Override
public PathPattern applyKeys(List<String> indices) {
List<String> result = new ArrayList<>(pattern.size());
Iterator<String> it = indices.iterator();
for (String pat : pattern) {
if (it.hasNext() && isWildcard(pat)) {
String index = it.next();
public PathPattern applyKeys(Align align, List<String> indices) {
List<String> result = Arrays.asList(new String[pattern.size()]);
ListIterator<String> iit = align.iterator(indices);
ListIterator<String> pit = align.iterator(pattern);
while (pit.hasNext()) {
int i = pit.nextIndex();
String pat = pit.next();
if (iit.hasNext() && isWildcard(pat)) {
String index = iit.next();
if (PathUtils.isIndex(pat)) {
result.add(PathUtils.makeKey(index));
result.set(i, PathUtils.makeKey(index));
}
else {
// NB. Rare for attribute wildcards, but just in case
result.add(index);
result.set(i, index);
}
}
else {
result.add(pat);
result.set(i, pat);
}
}
return new PathPattern(result);

View file

@ -17,13 +17,108 @@ package ghidra.dbg.util;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import ghidra.async.AsyncFence;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.PathUtils.PathComparator;
import ghidra.util.ReversedListIterator;
public interface PathPredicates {
PathPredicates EMPTY = new PathPredicates() {
@Override
public PathPredicates or(PathPredicates that) {
return that;
}
@Override
public boolean matches(List<String> path) {
return false;
}
@Override
public boolean successorCouldMatch(List<String> path, boolean strict) {
return false;
}
@Override
public boolean ancestorMatches(List<String> path, boolean strict) {
return false;
}
@Override
public boolean ancestorCouldMatchRight(List<String> path, boolean strict) {
return false;
}
@Override
public Set<String> getNextKeys(List<String> path) {
return Set.of();
}
@Override
public Set<String> getNextNames(List<String> path) {
return Set.of();
}
@Override
public Set<String> getNextIndices(List<String> path) {
return Set.of();
}
@Override
public Set<String> getPrevKeys(List<String> path) {
return Set.of();
}
@Override
public List<String> getSingletonPath() {
return null;
}
@Override
public PathPattern getSingletonPattern() {
return null;
}
@Override
public Collection<PathPattern> getPatterns() {
return List.of();
}
@Override
public PathPredicates removeRight(int count) {
return this;
}
@Override
public PathPredicates applyKeys(Align align, List<String> keys) {
return this;
}
@Override
public boolean isEmpty() {
return true;
}
};
enum Align {
LEFT {
@Override
<T> ListIterator<T> iterator(List<T> list) {
return list.listIterator();
}
},
RIGHT {
@Override
<T> ListIterator<T> iterator(List<T> list) {
return new ReversedListIterator<>(list.listIterator(list.size()));
}
};
abstract <T> ListIterator<T> iterator(List<T> list);
}
static boolean keyMatches(String pat, String key) {
if (key.equals(pat)) {
@ -163,6 +258,13 @@ public interface PathPredicates {
*/
PathPattern getSingletonPattern();
/**
* Get the patterns of this predicate
*
* @return the patterns
*/
Collection<PathPattern> getPatterns();
/**
* Remove count elements from the right
*
@ -313,33 +415,44 @@ public interface PathPredicates {
* Substitute wildcards from left to right for the given list of keys
*
* <p>
* Takes each pattern and substitutes its wildcards for the given indices, starting from the
* left and working right. This object is unmodified, and the result is returned.
* Takes each pattern and substitutes its wildcards for the given indices, according to the
* given alignment. This object is unmodified, and the result is returned.
*
* <p>
* If there are fewer wildcards in a pattern than given, only the left-most keys are taken. If
* there are fewer keys than wildcards in a pattern, then the right-most wildcards are left in
* the resulting pattern.
* If there are fewer wildcards in a pattern than given, only the first keys are taken. If there
* are fewer keys than wildcards in a pattern, then the remaining wildcards are left in the
* resulting pattern. In this manner, the left-most wildcards are substituted for the left-most
* indices, or the right-most wildcards are substituted for the right-most indices, depending on
* the alignment.
*
* @param align the end to align
* @param keys the keys to substitute
* @return the pattern or matcher with the applied substitutions
*/
PathPredicates applyKeys(List<String> keys);
PathPredicates applyKeys(Align align, List<String> keys);
default PathPredicates applyKeys(Stream<String> keys) {
return applyKeys(keys.collect(Collectors.toList()));
default PathPredicates applyKeys(Align align, String... keys) {
return applyKeys(align, List.of(keys));
}
default PathPredicates applyKeys(String... keys) {
return applyKeys(List.of(keys));
return applyKeys(Align.LEFT, keys);
}
default PathPredicates applyIntKeys(int radix, List<Integer> keys) {
return applyKeys(keys.stream().map(k -> Integer.toString(k, radix)));
default PathPredicates applyIntKeys(int radix, Align align, List<Integer> keys) {
return applyKeys(align,
keys.stream().map(k -> Integer.toString(k, radix)).collect(Collectors.toList()));
}
default PathPredicates applyIntKeys(int radix, Align align, int... keys) {
return applyKeys(align,
IntStream.of(keys)
.mapToObj(k -> Integer.toString(k, radix))
.collect(Collectors.toList()));
}
default PathPredicates applyIntKeys(int... keys) {
return applyKeys(IntStream.of(keys).mapToObj(k -> Integer.toString(k)));
return applyIntKeys(10, Align.LEFT, keys);
}
/**

View file

@ -18,11 +18,9 @@ package ghidra.pcode.exec.trace;
import com.google.common.collect.RangeSet;
import com.google.common.primitives.UnsignedLong;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
/**
* A state piece which can check for uninitialized reads
@ -30,16 +28,16 @@ import ghidra.trace.model.thread.TraceThread;
* <p>
* Depending on the use case, it may be desirable to ensure all reads through the course of
* emulation are from initialized parts of memory. For traces, there's an additional consideration
* as to whether the values are present, but state. Again, depending on the use case, that may be
* as to whether the values are present, but stale. Again, depending on the use case, that may be
* acceptable. See the extensions of this class for "stock" implementations.
*/
public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece
extends BytesTracePcodeExecutorStatePiece {
protected class CheckedCachedSpace extends CachedSpace {
public CheckedCachedSpace(Language language, AddressSpace space, TraceMemorySpace source,
long snap) {
super(language, space, source, snap);
public CheckedCachedSpace(Language language, AddressSpace space,
PcodeTraceDataAccess backing) {
super(language, space, backing);
}
@Override
@ -54,31 +52,35 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiec
}
}
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
TraceThread thread, int frame) {
super(trace, snap, thread, frame);
/**
* Construct a piece
*
* @param data the trace-data access shim
*/
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) {
super(data);
}
@Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TraceBackedSpaceMap() {
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new CheckedCachedSpace(language, space, backing, snap);
protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) {
return new CheckedCachedSpace(language, space, backing);
}
};
}
/**
* Decide what to do, give that a portion of a read is uninitialized
* Decide what to do, given that a portion of a read is uninitialized
*
* @param backing the object backing the address space that was read
* @param backing the shim backing the address space that was read
* @param start the starting address of the requested read
* @param size the size of the requested read
* @param uninitialized the portion of the read that is uninitialized
* @return the adjusted size of the read
* @throws Exception to interrupt the emulator
*/
protected abstract int checkUninitialized(TraceMemorySpace backing, Address start, int size,
AddressSet uninitialized);
protected abstract int checkUninitialized(PcodeTraceDataAccess backing, Address start,
int size, AddressSet uninitialized);
}

View file

@ -15,51 +15,54 @@
*/
package ghidra.pcode.exec.trace;
import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emu.PcodeThread;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.pcode.emu.*;
import ghidra.pcode.exec.trace.data.*;
import ghidra.trace.model.guest.TracePlatform;
/**
* An emulator that can read initial state from a trace and record its state back into it
*/
public class BytesTracePcodeEmulator extends PcodeEmulator implements TracePcodeMachine<byte[]> {
protected final Trace trace;
protected final long snap;
protected final PcodeTraceAccess access;
/**
* Create a trace-bound emulator
*
* @param trace the trace
* @param snap the snap from which it lazily reads its state
* @param access the trace access shim
*/
public BytesTracePcodeEmulator(Trace trace, long snap) {
super(trace.getBaseLanguage());
this.trace = trace;
this.snap = snap;
public BytesTracePcodeEmulator(PcodeTraceAccess access) {
super(access.getLanguage());
this.access = access;
}
/**
* Create a trace-bound emulator
*
* @param platform the platform to emulate
* @param snap the source snap
*/
public BytesTracePcodeEmulator(TracePlatform platform, long snap) {
this(new DefaultPcodeTraceAccess(platform, snap));
}
@Override
public Trace getTrace() {
return trace;
protected BytesPcodeThread createThread(String name) {
BytesPcodeThread thread = super.createThread(name);
access.getDataForLocalState(thread, 0).initializeThreadContext(thread);
return thread;
}
@Override
public long getSnap() {
return snap;
}
protected TracePcodeExecutorState<byte[]> newState(TraceThread thread) {
return new BytesTracePcodeExecutorState(trace, snap, thread, 0);
protected TracePcodeExecutorState<byte[]> newState(PcodeTraceDataAccess data) {
return new BytesTracePcodeExecutorState(data);
}
@Override
public TracePcodeExecutorState<byte[]> createSharedState() {
return newState(null);
return newState(access.getDataForSharedState());
}
@Override
public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> thread) {
return newState(getTraceThread(thread));
return newState(access.getDataForLocalState(thread, 0));
}
}

View file

@ -15,22 +15,18 @@
*/
package ghidra.pcode.exec.trace;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/**
* A state composing a single {@link BytesTracePcodeExecutorStatePiece}
*/
class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
public class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Create the state
*
* @param trace the trace from which bytes are loaded
* @param snap the snap from which bytes are loaded
* @param thread if applicable, the thread identifying the register space
* @param frame if applicable, the frame identifying the register space
* @param data the trace-data access shim
*/
public BytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) {
super(new BytesTracePcodeExecutorStatePiece(trace, snap, thread, frame));
public BytesTracePcodeExecutorState(PcodeTraceDataAccess data) {
super(new BytesTracePcodeExecutorStatePiece(data));
}
}

View file

@ -17,17 +17,16 @@ package ghidra.pcode.exec.trace;
import java.nio.ByteBuffer;
import com.google.common.collect.*;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import com.google.common.primitives.UnsignedLong;
import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece;
import ghidra.pcode.exec.BytesPcodeExecutorStateSpace;
import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.MathUtilities;
/**
@ -42,35 +41,20 @@ public class BytesTracePcodeExecutorStatePiece
extends AbstractBytesPcodeExecutorStatePiece<CachedSpace>
implements TracePcodeExecutorStatePiece<byte[], byte[]> {
protected final Trace trace;
protected final long snap;
protected final TraceThread thread;
protected final int frame;
public BytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) {
super(trace.getBaseLanguage());
this.trace = trace;
this.snap = snap;
this.thread = thread;
this.frame = frame;
}
protected static class CachedSpace extends BytesPcodeExecutorStateSpace<TraceMemorySpace> {
protected static class CachedSpace
extends BytesPcodeExecutorStateSpace<PcodeTraceDataAccess> {
protected final AddressSet written = new AddressSet();
protected final long snap;
public CachedSpace(Language language, AddressSpace space, TraceMemorySpace backing,
long snap) {
public CachedSpace(Language language, AddressSpace space, PcodeTraceDataAccess backing) {
// Backing could be null, so we need language parameter
super(language, space, backing);
this.snap = snap;
}
@Override
public void write(long offset, byte[] val, int srcOffset, int length) {
super.write(offset, val, srcOffset, length);
Address loc = space.getAddress(offset);
Address end = loc.addWrap(length);
Address end = loc.addWrap(length - 1);
if (loc.compareTo(end) <= 0) {
written.add(loc, end);
}
@ -91,7 +75,7 @@ public class BytesTracePcodeExecutorStatePiece
long lower = lower(toRead);
long upper = upper(toRead);
ByteBuffer buf = ByteBuffer.allocate((int) (upper - lower + 1));
backing.getBytes(snap, space.getAddress(lower), buf);
backing.getBytes(space.getAddress(lower), buf);
for (Range<UnsignedLong> rng : uninitialized.asRanges()) {
long l = lower(rng);
long u = upper(rng);
@ -100,19 +84,17 @@ public class BytesTracePcodeExecutorStatePiece
}
}
protected void warnUnknown(AddressSet unknown) {
protected void warnUnknown(AddressSetView unknown) {
warnAddressSet("Emulator state initialized from UNKNOWN", unknown);
}
// Must already have started a transaction
protected void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
protected void writeDown(PcodeTraceDataAccess into) {
if (space.isUniqueSpace()) {
return;
}
byte[] data = new byte[4096];
ByteBuffer buf = ByteBuffer.wrap(data);
TraceMemorySpace mem =
TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true);
for (AddressRange range : written) {
long lower = range.getMinAddress().getOffset();
long fullLen = range.getLength();
@ -121,7 +103,7 @@ public class BytesTracePcodeExecutorStatePiece
bytes.getData(lower, data, 0, len);
buf.position(0);
buf.limit(len);
mem.putBytes(snap, space.getAddress(lower), buf);
into.putBytes(space.getAddress(lower), buf);
lower += len;
fullLen -= len;
@ -130,64 +112,47 @@ public class BytesTracePcodeExecutorStatePiece
}
}
/**
* Get the state's source trace
*
* @return the trace
*/
public Trace getTrace() {
return trace;
}
protected final PcodeTraceDataAccess data;
/**
* Get the source snap
* Create a concrete state piece backed by a trace
*
* @return the snap
* @param data the trace-data access shim
*/
public long getSnap() {
return snap;
}
/**
* Get the source thread, if a local state
*
* @return the thread
*/
public TraceThread getThread() {
return thread;
}
/**
* Get the source frame, if a local state
*
* @return the frame, probably 0
*/
public int getFrame() {
return frame;
public BytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
super(data.getLanguage());
this.data = data;
}
@Override
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
if (trace.getBaseLanguage() != language) {
throw new IllegalArgumentException("Destination trace must be same language as source");
public PcodeTraceDataAccess getData() {
return data;
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
if (into.getLanguage() != language) {
throw new IllegalArgumentException(
"Destination platform must be same language as source");
}
for (CachedSpace cached : spaceMap.values()) {
cached.writeDown(trace, snap, thread, frame);
cached.writeDown(into);
}
}
/**
* A space map which binds spaces to corresponding spaces in the trace
*/
protected class TraceBackedSpaceMap extends CacheingSpaceMap<TraceMemorySpace, CachedSpace> {
protected class TraceBackedSpaceMap
extends CacheingSpaceMap<PcodeTraceDataAccess, CachedSpace> {
@Override
protected TraceMemorySpace getBacking(AddressSpace space) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false);
protected PcodeTraceDataAccess getBacking(AddressSpace space) {
return data;
}
@Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) {
return new CachedSpace(language, space, backing, snap);
protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) {
return new CachedSpace(language, space, backing);
}
}

View file

@ -16,8 +16,7 @@
package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.DefaultPcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/**
* An adapter that implements {@link TracePcodeExecutorState} given a
@ -41,7 +40,12 @@ public class DefaultTracePcodeExecutorState<T> extends DefaultPcodeExecutorState
}
@Override
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
piece.writeDown(trace, snap, thread, frame);
public PcodeTraceDataAccess getData() {
return piece.getData();
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
piece.writeDown(into);
}
}

View file

@ -17,42 +17,60 @@ package ghidra.pcode.exec.trace;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PairedPcodeExecutorState;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
/**
* A state composing a single {@link DirectBytesTracePcodeExecutorStatePiece}
*
* <p>
* Note this does not implement {@link DefaultTracePcodeExecutorState} because it treats the trace
* as if it were a stand-alone state. The interface expects implementations to lazily load into a
* cache and write it back down later. This does not do that.
*
* @see TraceSleighUtils
*/
public class DirectBytesTracePcodeExecutorState extends DefaultPcodeExecutorState<byte[]> {
private final Trace trace;
private final long snap;
private final TraceThread thread;
private final int frame;
public class DirectBytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
/**
* Get a trace-data access shim suitable for evaluating Sleigh expressions with thread context
*
* <p>
* Do not use the returned shim for emulation, but only for one-off p-code execution, e.g.,
* Sleigh expression evaluation.
*
* @param platform the platform whose language and address mappings to use
* @param snap the source snap
* @param thread the thread for register context
* @param frame the frame for register context, 0 if not applicable
* @return the trace-data access shim
*/
public static PcodeTraceDataAccess getDefaultThreadAccess(TracePlatform platform, long snap,
TraceThread thread, int frame) {
return new DefaultPcodeTraceAccess(platform, snap).getDataForThreadState(thread, frame);
}
/**
* Create the state
*
* @param trace the trace the executor will access
* @param data the trace-data access shim
*/
public DirectBytesTracePcodeExecutorState(PcodeTraceDataAccess data) {
super(new DirectBytesTracePcodeExecutorStatePiece(data));
}
/**
* Create the state
*
* @param platform the platform whose language and address mappings to use
* @param snap the snap the executor will access
* @param thread the thread for reading and writing registers
* @param frame the frame for reading and writing registers
*/
public DirectBytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread,
public DirectBytesTracePcodeExecutorState(TracePlatform platform, long snap, TraceThread thread,
int frame) {
super(new DirectBytesTracePcodeExecutorStatePiece(trace, snap, thread, frame));
this.trace = trace;
this.snap = snap;
this.thread = thread;
this.frame = frame;
this(getDefaultThreadAccess(platform, snap, thread, frame));
}
/**
@ -63,6 +81,6 @@ public class DirectBytesTracePcodeExecutorState extends DefaultPcodeExecutorStat
*/
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() {
return new PairedPcodeExecutorState<>(this,
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame));
new TraceMemoryStatePcodeExecutorStatePiece(getData()));
}
}

View file

@ -17,20 +17,18 @@ package ghidra.pcode.exec.trace;
import java.nio.ByteBuffer;
import javax.help.UnsupportedOperationException;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport;
/**
* An executor state piece that operates directly on trace memory and registers
@ -45,37 +43,37 @@ import ghidra.trace.util.DefaultTraceTimeViewport;
* @see TraceSleighUtils
*/
public class DirectBytesTracePcodeExecutorStatePiece
extends AbstractLongOffsetPcodeExecutorStatePiece<byte[], byte[], TraceMemorySpace> {
extends AbstractLongOffsetPcodeExecutorStatePiece<byte[], byte[], AddressSpace>
implements TracePcodeExecutorStatePiece<byte[], byte[]> {
protected final PcodeTraceDataAccess data;
protected final SemisparseByteArray unique = new SemisparseByteArray();
private final Trace trace;
private long snap;
private TraceThread thread;
private int frame;
private final DefaultTraceTimeViewport viewport;
protected DirectBytesTracePcodeExecutorStatePiece(Language language,
PcodeArithmetic<byte[]> arithmetic, Trace trace, long snap, TraceThread thread,
int frame) {
super(language, arithmetic, arithmetic);
this.trace = trace;
this.snap = snap;
this.thread = thread;
this.frame = frame;
this.viewport = new DefaultTraceTimeViewport(trace);
this.viewport.setSnap(snap);
/**
* Construct a piece
*
* @param arithmetic the arithmetic for byte arrays
* @param data the trace-data access shim
*/
protected DirectBytesTracePcodeExecutorStatePiece(PcodeArithmetic<byte[]> arithmetic,
PcodeTraceDataAccess data) {
super(data.getLanguage(), arithmetic, arithmetic);
this.data = data;
}
protected DirectBytesTracePcodeExecutorStatePiece(Language language, Trace trace, long snap,
TraceThread thread, int frame) {
this(language, BytesPcodeArithmetic.forLanguage(language), trace, snap, thread, frame);
/**
* Construct a piece
*
* @param data the trace-data access shim
*/
protected DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data);
}
public DirectBytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) {
this(trace.getBaseLanguage(), trace, snap, thread, frame);
@Override
public PcodeTraceDataAccess getData() {
return data;
}
/**
@ -91,74 +89,7 @@ public class DirectBytesTracePcodeExecutorStatePiece
*/
public PcodeExecutorStatePiece<byte[], Pair<byte[], TraceMemoryState>> withMemoryState() {
return new PairedPcodeExecutorStatePiece<>(this,
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame));
}
/**
* Get the trace
*
* @return the trace
*/
public Trace getTrace() {
return trace;
}
/**
* Re-bind this state to another snap
*
* @param snap the new snap
*/
public void setSnap(long snap) {
this.snap = snap;
this.viewport.setSnap(snap);
}
/**
* Get the current snap
*
* @return the snap
*/
public long getSnap() {
return snap;
}
/**
* Re-bind this state to another thread
*
* @param thread the new thread
*/
public void setThread(TraceThread thread) {
if (thread != null & thread.getTrace() != trace) {
throw new IllegalArgumentException("Thread, if given, must be part of the same trace");
}
this.thread = thread;
}
/**
* Get the current thread
*
* @return the thread
*/
public TraceThread getThread() {
return thread;
}
/**
* Re-bind this state to another frame
*
* @param frame the new frame
*/
public void setFrame(int frame) {
this.frame = frame;
}
/**
* Get the current frame
*
* @return the frame
*/
public int getFrame() {
return frame;
new TraceMemoryStatePcodeExecutorStatePiece(data));
}
@Override
@ -175,24 +106,23 @@ public class DirectBytesTracePcodeExecutorStatePiece
}
@Override
protected TraceMemorySpace getForSpace(AddressSpace space, boolean toWrite) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, toWrite);
protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) {
return space;
}
@Override
protected void setInSpace(TraceMemorySpace space, long offset, int size, byte[] val) {
protected void setInSpace(AddressSpace space, long offset, int size, byte[] val) {
assert size == val.length;
int wrote =
space.putBytes(snap, space.getAddressSpace().getAddress(offset), ByteBuffer.wrap(val));
int wrote = data.putBytes(space.getAddress(offset), ByteBuffer.wrap(val));
if (wrote != size) {
throw new RuntimeException("Could not write full value to trace");
}
}
@Override
protected byte[] getFromSpace(TraceMemorySpace space, long offset, int size) {
protected byte[] getFromSpace(AddressSpace space, long offset, int size) {
ByteBuffer buf = ByteBuffer.allocate(size);
int read = space.getViewBytes(snap, space.getAddressSpace().getAddress(offset), buf);
int read = data.getBytes(space.getAddress(offset), buf);
if (read != size) {
throw new RuntimeException("Could not read full value from trace");
}
@ -201,6 +131,11 @@ public class DirectBytesTracePcodeExecutorStatePiece
@Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) {
return trace.getMemoryManager().getBufferAt(snap, address);
throw new UnsupportedOperationException();
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
// Writes directly, so just ignore
}
}

View file

@ -18,8 +18,7 @@ package ghidra.pcode.exec.trace;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.exec.PairedPcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/**
* A trace-bound state composed of another trace-bound state and a piece
@ -48,8 +47,13 @@ public class PairedTracePcodeExecutorState<L, R> extends PairedPcodeExecutorStat
}
@Override
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
left.writeDown(trace, snap, thread, frame);
right.writeDown(trace, snap, thread, frame);
public PcodeTraceDataAccess getData() {
return left.getData();
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
left.writeDown(into);
right.writeDown(into);
}
}

View file

@ -19,8 +19,7 @@ import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.exec.PairedPcodeExecutorStatePiece;
import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/**
* A trace-bound state piece composed of two other trace-bound pieces sharing the same address type
@ -53,9 +52,14 @@ public class PairedTracePcodeExecutorStatePiece<A, L, R>
}
@Override
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) {
left.writeDown(trace, snap, thread, frame);
right.writeDown(trace, snap, thread, frame);
public PcodeTraceDataAccess getData() {
return left.getData();
}
@Override
public void writeDown(PcodeTraceDataAccess into) {
left.writeDown(into);
right.writeDown(into);
}
@Override

View file

@ -15,8 +15,7 @@
*/
package ghidra.pcode.exec.trace;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/**
* A state composing a single {@link RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece}
@ -27,14 +26,9 @@ public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState
/**
* Create the state
*
* @param trace the trace from which to load state
* @param snap the snap from which to load state
* @param thread if applicable, the thread identifying the register space
* @param frame if applicable, the frame identifying the register space
* @param data the trace-data access shim
*/
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
TraceThread thread, int frame) {
super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread,
frame));
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(PcodeTraceDataAccess data) {
super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data));
}
}

View file

@ -15,14 +15,10 @@
*/
package ghidra.pcode.exec.trace;
import com.google.common.collect.Range;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.AddressSetView;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
/**
* A relaxation of {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece} that permits
@ -35,15 +31,18 @@ import ghidra.trace.model.thread.TraceThread;
public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece
extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece {
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
TraceThread thread, int frame) {
super(trace, snap, thread, frame);
/**
* Construct a piece
*
* @param data the trace-data access shim
*/
public RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) {
super(data);
}
@Override
protected AddressSetView getKnown(TraceMemorySpace source) {
return source.getAddressesWithState(Range.closed(0L, snap),
s -> s == TraceMemoryState.KNOWN);
protected AddressSetView getKnown(PcodeTraceDataAccess backing) {
return backing.getKnownBefore();
}
@Override

View file

@ -15,8 +15,7 @@
*/
package ghidra.pcode.exec.trace;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/**
* A state composing a single {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorState}
@ -27,14 +26,9 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState
/**
* Create the state
*
* @param trace the trace from which to load state
* @param snap the snap from which to load state
* @param thread if applicable, the thread identifying the register space
* @param frame if applicable, the frame identifying the register space
* @param data the trace-data access shim
*/
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap,
TraceThread thread, int frame) {
super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread,
frame));
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(PcodeTraceDataAccess data) {
super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data));
}
}

View file

@ -16,11 +16,9 @@
package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
/**
* A space which requires reads to be completely {@link TraceMemorySpace#KNOWN} memory.
@ -32,13 +30,17 @@ import ghidra.trace.model.thread.TraceThread;
public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece
extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece {
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap,
TraceThread thread, int frame) {
super(trace, snap, thread, frame);
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) {
super(data);
}
protected AddressSetView getKnown(TraceMemorySpace source) {
return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN);
/**
* Construct a piece
*
* @param data the trace-data access shim
*/
protected AddressSetView getKnown(PcodeTraceDataAccess backing) {
return backing.getKnownNow();
}
protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
@ -46,7 +48,7 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece
}
@Override
protected int checkUninitialized(TraceMemorySpace backing, Address start, int size,
protected int checkUninitialized(PcodeTraceDataAccess backing, Address start, int size,
AddressSet uninitialized) {
if (backing == null) {
if (!uninitialized.contains(start)) {

View file

@ -22,13 +22,11 @@ import com.google.common.primitives.UnsignedLong;
import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport;
/**
* The p-code execute state piece for {@link TraceMemoryState}
@ -43,60 +41,21 @@ import ghidra.trace.util.DefaultTraceTimeViewport;
* though it's also exemplified in {@link #getFromSpace(TraceMemorySpace, long, int)}.
*/
public class TraceMemoryStatePcodeExecutorStatePiece extends
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, TraceMemorySpace> {
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, AddressSpace> {
private final RangeMap<UnsignedLong, TraceMemoryState> unique = TreeRangeMap.create();
private final Trace trace;
private long snap;
private TraceThread thread;
private int frame;
protected final RangeMap<UnsignedLong, TraceMemoryState> unique = TreeRangeMap.create();
protected final PcodeTraceDataAccess data;
private final DefaultTraceTimeViewport viewport;
public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread,
int frame) {
super(trace.getBaseLanguage(),
BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()),
/**
* Construct a piece
*
* @param data the trace-data access shim
*/
public TraceMemoryStatePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
super(data.getLanguage(),
BytesPcodeArithmetic.forLanguage(data.getLanguage()),
TraceMemoryStatePcodeArithmetic.INSTANCE);
this.trace = trace;
this.snap = snap;
this.thread = thread;
this.frame = frame;
this.viewport = new DefaultTraceTimeViewport(trace);
this.viewport.setSnap(snap);
}
public Trace getTrace() {
return trace;
}
public void setSnap(long snap) {
this.snap = snap;
this.viewport.setSnap(snap);
}
public long getSnap() {
return snap;
}
public void setThread(TraceThread thread) {
if (thread != null & thread.getTrace() != trace) {
throw new IllegalArgumentException("Thread, if given, must be part of the same trace");
}
this.thread = thread;
}
public TraceThread getThread() {
return thread;
}
public void setFrame(int frame) {
this.frame = frame;
}
public int getFrame() {
return frame;
this.data = data;
}
protected Range<UnsignedLong> range(long offset, int size) {
@ -136,24 +95,19 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
}
@Override
protected TraceMemorySpace getForSpace(AddressSpace space, boolean toWrite) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, toWrite);
protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) {
return space;
}
@Override
protected void setInSpace(TraceMemorySpace space, long offset, int size, TraceMemoryState val) {
protected void setInSpace(AddressSpace space, long offset, int size, TraceMemoryState val) {
// NB. Will ensure writes with unknown state are still marked unknown
space.setState(size, space.getAddressSpace().getAddress(offset), val);
data.setState(range(space, offset, size), val);
}
@Override
protected TraceMemoryState getFromSpace(TraceMemorySpace space, long offset, int size) {
AddressSet set = new AddressSet(range(space.getAddressSpace(), offset, size));
for (long snap : viewport.getOrderedSnaps()) {
set.delete(
space.getAddressesWithState(snap, set, state -> state == TraceMemoryState.KNOWN));
}
return set.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN;
protected TraceMemoryState getFromSpace(AddressSpace space, long offset, int size) {
return data.getViewportState(range(space, offset, size));
}
@Override

View file

@ -16,15 +16,14 @@
package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/**
* An interface for trace-bound states
*
* <p>
* In particular, because this derives from {@link TracePcodeExecutorStatePiece}, such states are
* required to implement {@link #writeDown(Trace, long, TraceThread, int)}. This interface also
* required to implement {@link #writeDown(PcodeTraceDataAccess)}. This interface also
* derives from {@link PcodeExecutorState} so that, as the name implies, they can be used where a
* state is required.
*

View file

@ -16,8 +16,8 @@
package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.PcodeExecutorStatePiece;
import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread;
import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/**
* A state piece which knows how to write its values back into a trace
@ -26,6 +26,18 @@ import ghidra.trace.model.thread.TraceThread;
* @param <T> the type of values
*/
public interface TracePcodeExecutorStatePiece<A, T> extends PcodeExecutorStatePiece<A, T> {
/**
* Get the state's trace-data access shim
*
* <p>
* This method is meant for auxiliary state pieces, so that it can access the same trace data as
* this piece.
*
* @return the trace-data access shim
*/
PcodeTraceDataAccess getData();
/**
* Write the accumulated values (cache) into the given trace
*
@ -33,11 +45,8 @@ public interface TracePcodeExecutorStatePiece<A, T> extends PcodeExecutorStatePi
* <b>NOTE:</b> This method requires a transaction to have already been started on the
* destination trace.
*
* @param trace the trace to modify
* @param snap the snap within the trace
* @param thread the thread to take register writes
* @param frame the frame for register writes
* @see TracePcodeMachine#writeDown(Trace, long, long)
* @param into the destination data-access shim
* @see TracePcodeMachine#writeDown(PcodeTraceAccess)
*/
void writeDown(Trace trace, long snap, TraceThread thread, int frame);
void writeDown(PcodeTraceDataAccess into);
}

View file

@ -15,51 +15,17 @@
*/
package ghidra.pcode.exec.trace;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.pcode.exec.trace.data.*;
import ghidra.trace.model.guest.TracePlatform;
/**
* A p-code machine which sources its state from a trace and can record back into it
*
* <p>
* This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator
* developers should use this interface, but emulator clients should not. Clients should use
* {@link PcodeMachine} instead.
*
* @param <T> the type of values manipulated by the machine
*/
public interface TracePcodeMachine<T> extends PcodeMachine<T> {
/**
* Get the trace from which this emulator reads its initial state
*
* @return the trace
*/
Trace getTrace();
/**
* Get the snapshot from which this emulator reads its initial state
*
* @return the snapshot key
*/
long getSnap();
/**
* Get the trace thread corresponding to the given p-code thread
*
* @param thread the p-code thread
* @return the trace thread
*/
default TraceThread getTraceThread(PcodeThread<T> thread) {
return getTrace().getThreadManager().getLiveThreadByPath(getSnap(), thread.getName());
}
/**
* Create a shared state
@ -77,44 +43,7 @@ public interface TracePcodeMachine<T> extends PcodeMachine<T> {
TracePcodeExecutorState<T> createLocalState(PcodeThread<T> thread);
/**
* Check if a register has a {@link TraceMemoryState#KNOWN} value for the given thread
*
* @param thread the thread
* @param register the register
* @return true if known
*/
default boolean isRegisterKnown(PcodeThread<T> thread, Register register) {
Trace trace = getTrace();
long snap = getSnap();
TraceThread traceThread =
trace.getThreadManager().getLiveThreadByPath(snap, thread.getName());
TraceMemorySpace space =
trace.getMemoryManager().getMemoryRegisterSpace(traceThread, false);
if (space == null) {
return false;
}
return space.getState(snap, register) == TraceMemoryState.KNOWN;
}
/**
* Initialize the given thread using context from the trace at its program counter
*
* @param thread the thread to initialize
*/
default void initializeThreadContext(PcodeThread<T> thread) {
SleighLanguage language = getLanguage();
Register contextreg = language.getContextBaseRegister();
if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(thread, contextreg)) {
RegisterValue context = getTrace().getRegisterContextManager()
.getValueWithDefault(language, contextreg, getSnap(), thread.getCounter());
if (context != null) { // TODO: Why does this happen?
thread.overrideContext(context);
}
}
}
/**
* Write the accumulated emulator state into the given trace at the given snap
* Write the accumulated emulator state via the given trace access shim
*
* <p>
* <b>NOTE:</b> This method requires a transaction to have already been started on the
@ -122,26 +51,32 @@ public interface TracePcodeMachine<T> extends PcodeMachine<T> {
* threadsSnap. When using scratch space, threadsSnap should be the source snap. If populating a
* new trace, threadsSnap should probably be the destination snap.
*
* @param trace the trace to modify
* @param into the destination trace-data access shim
*/
default void writeDown(PcodeTraceAccess into) {
TracePcodeExecutorState<T> sharedState = (TracePcodeExecutorState<T>) getSharedState();
sharedState.writeDown(into.getDataForSharedState());
for (PcodeThread<T> emuThread : getAllThreads()) {
PcodeTraceDataAccess localInto = into.getDataForLocalState(emuThread, 0);
if (localInto == null) {
throw new IllegalArgumentException(
"Given trace does not have thread with name/path '" + emuThread.getName() +
"' at source snap");
}
TracePcodeExecutorState<T> localState =
(TracePcodeExecutorState<T>) emuThread.getState().getLocalState();
localState.writeDown(localInto);
}
}
/**
* @see #writeDown(PcodeTraceAccess)
* @param platform the platform whose trace to modify
* @param destSnap the destination snap within the trace
* @param threadsSnap the snap at which to find corresponding threads, usually the same as
* {@link #getSnap()}
*/
default void writeDown(Trace trace, long destSnap, long threadsSnap) {
TracePcodeExecutorState<T> ss = (TracePcodeExecutorState<T>) getSharedState();
ss.writeDown(trace, destSnap, null, 0);
TraceThreadManager threadManager = trace.getThreadManager();
for (PcodeThread<T> emuThread : getAllThreads()) {
TracePcodeExecutorState<T> ls =
(TracePcodeExecutorState<T>) emuThread.getState().getLocalState();
TraceThread traceThread =
threadManager.getLiveThreadByPath(threadsSnap, emuThread.getName());
if (traceThread == null) {
throw new IllegalArgumentException(
"Given trace does not have thread with name/path '" + emuThread.getName() +
"' at snap " + destSnap);
}
ls.writeDown(trace, destSnap, traceThread, 0);
}
default void writeDown(TracePlatform platform, long destSnap, long threadsSnap) {
writeDown(new DefaultPcodeTraceAccess(platform, destSnap, threadsSnap));
}
}

View file

@ -28,7 +28,7 @@ import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread;
@ -38,32 +38,6 @@ import ghidra.trace.model.thread.TraceThread;
public enum TraceSleighUtils {
;
/**
* Get the trace memory space for the given "coordinates"
*
* <p>
* This is used to find "backing" objects for a p-code executor state bound to a trace, whether
* direct or cached.
*
* @param space the address space
* @param trace the trace
* @param thread the thread, if a register space
* @param frame the frame, if a register space
* @param toWrite true if the state intends to write to the space, i.e., the space must exist
* @return the space, or null if it doesn't exist
*/
public static TraceMemorySpace getSpaceForExecution(AddressSpace space, Trace trace,
TraceThread thread, int frame, boolean toWrite) {
if (space.isRegisterSpace()) {
if (thread == null) {
throw new IllegalArgumentException(
"Cannot access register unless a thread is given.");
}
return trace.getMemoryManager().getMemoryRegisterSpace(thread, frame, toWrite);
}
return trace.getMemoryManager().getMemorySpace(space, toWrite);
}
/**
* Build a p-code executor that operates directly on bytes of the given trace
*
@ -72,7 +46,27 @@ public enum TraceSleighUtils {
* for manipulating or initializing variables using Sleigh code. It is generally not suitable
* for use in an emulator. For that, consider {@link BytesTracePcodeEmulator}.
*
* @param trace the trace
* @param platform the platform
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the executor
*/
public static PcodeExecutor<byte[]> buildByteExecutor(TracePlatform platform, long snap,
TraceThread thread, int frame) {
DirectBytesTracePcodeExecutorState state =
new DirectBytesTracePcodeExecutorState(platform, snap, thread, frame);
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("TracePlatform must use a SLEIGH language");
}
return new PcodeExecutor<>((SleighLanguage) language,
BytesPcodeArithmetic.forLanguage(language), state);
}
/**
* @see #buildByteExecutor(TracePlatform, long, TraceThread, int)
* @param trace the trace whose host platform to use
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
@ -80,14 +74,7 @@ public enum TraceSleighUtils {
*/
public static PcodeExecutor<byte[]> buildByteExecutor(Trace trace, long snap,
TraceThread thread, int frame) {
DirectBytesTracePcodeExecutorState state =
new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Trace must use a SLEIGH language");
}
return new PcodeExecutor<>((SleighLanguage) language,
BytesPcodeArithmetic.forLanguage(language), state);
return buildByteExecutor(trace.getPlatformManager().getHostPlatform(), snap, thread, frame);
}
/**
@ -98,7 +85,29 @@ public enum TraceSleighUtils {
* when the client would also like to know if all variables involved are
* {@link TraceMemoryState#KNOWN}.
*
* @param trace the trace
* @param platform the platform
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
* @return the executor
*/
public static PcodeExecutor<Pair<byte[], TraceMemoryState>> buildByteWithStateExecutor(
TracePlatform platform, long snap, TraceThread thread, int frame) {
DirectBytesTracePcodeExecutorState state =
new DirectBytesTracePcodeExecutorState(platform, snap, thread, frame);
PcodeExecutorState<Pair<byte[], TraceMemoryState>> paired = state.withMemoryState();
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("TracePlatform must use a SLEIGH language");
}
return new PcodeExecutor<>((SleighLanguage) language, new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), TraceMemoryStatePcodeArithmetic.INSTANCE),
paired);
}
/**
* @see #buildByteWithStateExecutor(TracePlatform, long, TraceThread, int)
* @param trace the trace whose host platform to use
* @param snap the snap
* @param thread the thread, required if register space is used
* @param frame the frame, for when register space is used
@ -106,16 +115,8 @@ public enum TraceSleighUtils {
*/
public static PcodeExecutor<Pair<byte[], TraceMemoryState>> buildByteWithStateExecutor(
Trace trace, long snap, TraceThread thread, int frame) {
DirectBytesTracePcodeExecutorState state =
new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame);
PcodeExecutorState<Pair<byte[], TraceMemoryState>> paired = state.withMemoryState();
Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Trace must use a SLEIGH language");
}
return new PcodeExecutor<>((SleighLanguage) language, new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), TraceMemoryStatePcodeArithmetic.INSTANCE),
paired);
return buildByteWithStateExecutor(trace.getPlatformManager().getHostPlatform(), snap,
thread, frame);
}
/**

View file

@ -42,7 +42,9 @@ public interface AuxTraceEmulatorPartsFactory<U> extends AuxEmulatorPartsFactory
* capable of lazily loading state from a trace and later writing its cache back into the trace
* at another snapshot. The given concrete piece is already capable of doing that for concrete
* values. The auxiliary piece should be able to independently load its state from the trace,
* since this is one way a user expects to initialize the auxiliary values.
* since this is one way a user expects to initialize the auxiliary values. It ought to use the
* same data-access shim as the given concrete state. See
* {@link TracePcodeExecutorStatePiece#getData()}.
*
* @param emulator the emulator
* @param concrete the concrete piece

View file

@ -21,7 +21,8 @@ import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator;
import ghidra.pcode.exec.trace.*;
import ghidra.trace.model.Trace;
import ghidra.pcode.exec.trace.data.*;
import ghidra.trace.model.guest.TracePlatform;
/**
* An trace-integrated emulator whose parts are manufactured by a
@ -39,51 +40,48 @@ import ghidra.trace.model.Trace;
public abstract class AuxTracePcodeEmulator<U> extends AuxPcodeEmulator<U>
implements TracePcodeMachine<Pair<byte[], U>> {
protected final Trace trace;
protected final long snap;
protected final PcodeTraceAccess access;
/**
* Create a new emulator
*
* @param trace the trace from which the emulator loads state
* @param snap the snap from which the emulator loads state
* @param access the trace access shim
*/
public AuxTracePcodeEmulator(Trace trace, long snap) {
super(trace.getBaseLanguage());
this.trace = trace;
this.snap = snap;
public AuxTracePcodeEmulator(PcodeTraceAccess access) {
super(access.getLanguage());
this.access = access;
}
/**
* Create a new emulator
*
* @param platform the platform to emulate
* @param snap the source snap
*/
public AuxTracePcodeEmulator(TracePlatform platform, long snap) {
this(new DefaultPcodeTraceAccess(platform, snap));
}
@Override
protected abstract AuxTraceEmulatorPartsFactory<U> getPartsFactory();
@Override
public Trace getTrace() {
return trace;
}
@Override
public long getSnap() {
return snap;
}
@Override
protected PcodeThread<Pair<byte[], U>> createThread(String name) {
PcodeThread<Pair<byte[], U>> thread = super.createThread(name);
initializeThreadContext(thread);
access.getDataForLocalState(thread, 0).initializeThreadContext(thread);
return thread;
}
@Override
public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() {
return getPartsFactory().createTraceSharedState(this,
new BytesTracePcodeExecutorStatePiece(trace, snap, null, 0));
new BytesTracePcodeExecutorStatePiece(access.getDataForSharedState()));
}
@Override
public TracePcodeExecutorState<Pair<byte[], U>> createLocalState(
PcodeThread<Pair<byte[], U>> thread) {
return getPartsFactory().createTraceLocalState(this, thread,
new BytesTracePcodeExecutorStatePiece(trace, snap, getTraceThread(thread), 0));
new BytesTracePcodeExecutorStatePiece(access.getDataForLocalState(thread, 0)));
}
}

View file

@ -0,0 +1,146 @@
/* ###
* 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.pcode.exec.trace.data;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.util.DefaultTraceTimeViewport;
import ghidra.trace.util.TraceTimeViewport;
/**
* An abstract implementation of {@link PcodeTraceAccess}
*
* @param <S> the type of shared data-access shims provided
* @param <L> the type of thread-local data-access shims provided
*/
public abstract class AbstractPcodeTraceAccess<S extends PcodeTraceMemoryAccess, L extends PcodeTraceRegistersAccess>
implements PcodeTraceAccess {
protected final TracePlatform platform;
protected final long threadsSnap;
protected final long snap;
protected final TraceTimeViewport viewport;
protected S dataForSharedState;
protected final Map<Pair<TraceThread, Integer>, L> dataForLocalStateByThreadAndFrame =
new HashMap<>();
/**
* Construct a shim
*
* @param platform the associated platform
* @param snap the associated snap
* @param threadsSnap the snap to use when finding associated threads between trace and emulator
*/
public AbstractPcodeTraceAccess(TracePlatform platform, long snap, long threadsSnap) {
this.platform = platform;
this.snap = snap;
this.threadsSnap = threadsSnap;
DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(getTrace());
viewport.setSnap(snap);
this.viewport = viewport;
}
/**
* Construct a shim
*
* @param platform the associated platform
* @param snap the associated snap
*/
public AbstractPcodeTraceAccess(TracePlatform platform, long snap) {
this(platform, snap, snap);
}
/**
* Get the associated trace
*
* @return the trace
*/
protected Trace getTrace() {
return platform.getTrace();
}
/**
* Get the trace thread conventionally associated with the given p-code thread
*
* <p>
* A p-code thread is conventionally associated with the trace thread whose path matches the
* p-code thread's name.
*
* @param thread the p-code thread
* @return the trace thread
*/
protected TraceThread getTraceThread(PcodeThread<?> thread) {
return getTrace().getThreadManager().getLiveThreadByPath(threadsSnap, thread.getName());
}
@Override
public Language getLanguage() {
return platform.getLanguage();
}
/**
* Factory method for the shared data-access shim
*
* @return the new data-access shim
*/
protected abstract S newDataForSharedState();
@Override
public S getDataForSharedState() {
synchronized (dataForLocalStateByThreadAndFrame) {
if (dataForSharedState == null) {
dataForSharedState = newDataForSharedState();
}
return dataForSharedState;
}
}
/**
* Factory method for a thread's local data-access shim
*
* @param thread the associated trace thread
* @param frame the frame, usually 0
* @return the new data-access shim
*/
protected abstract L newDataForLocalState(TraceThread thread, int frame);
@Override
public L getDataForLocalState(TraceThread thread, int frame) {
if (thread == null) {
return null;
}
synchronized (dataForLocalStateByThreadAndFrame) {
return dataForLocalStateByThreadAndFrame.computeIfAbsent(Pair.of(thread, frame), p -> {
return newDataForLocalState(p.getLeft(), p.getRight());
});
}
}
@Override
public L getDataForLocalState(PcodeThread<?> thread, int frame) {
return getDataForLocalState(getTraceThread(thread), frame);
}
}

View file

@ -0,0 +1,193 @@
/* ###
* 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.pcode.exec.trace.data;
import java.nio.ByteBuffer;
import com.google.common.collect.Range;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.*;
import ghidra.trace.util.TraceTimeViewport;
/**
* An abstract data-access shim, for either memory or registers
*/
public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTraceDataAccess {
protected final TracePlatform platform;
protected final long snap;
protected final TraceTimeViewport viewport;
protected final TraceMemoryManager mm;
/**
* Construct a shim
*
* @param platform the associated platform
* @param snap the associated snap
* @param viewport the viewport, set to the same snapshot
*/
public AbstractPcodeTraceDataAccess(TracePlatform platform, long snap,
TraceTimeViewport viewport) {
this.platform = platform;
this.snap = snap;
this.viewport = viewport;
this.mm = platform.getTrace().getMemoryManager();
}
@Override
public Language getLanguage() {
return platform.getLanguage();
}
@Override
public TracePlatform getPlatform() {
return platform;
}
@Override
public long getSnap() {
return snap;
}
/**
* Get the interface for accessing trace memory or registers
*
* @param createIfAbsent in the case of registers, whether to create the missing space
* @return the operations, or null
*/
protected abstract TraceMemoryOperations getMemoryOps(boolean createIfAbsent);
/**
* If this shim is associated with a (register) overlay space, translate the given address into
* it
*
* @param address the physical (register) address
* @return the overlay address
*/
protected abstract Address toOverlay(Address address);
/**
* @see #toOverlay(Address)
* @param range the physical range
* @return the overlay range
*/
protected abstract AddressRange toOverlay(AddressRange range);
/**
* @see #toOverlay(Address)
* @param set
* @return
*/
protected abstract AddressSetView toOverlay(AddressSetView set);
@Override
public void setState(AddressRange guestRange, TraceMemoryState state) {
AddressRange hostRange = platform.mapGuestToHost(guestRange);
if (hostRange == null) {
return;
}
getMemoryOps(true).setState(snap, toOverlay(hostRange), state);
}
@Override
public TraceMemoryState getViewportState(AddressRange guestRange) {
TraceMemoryOperations ops = getMemoryOps(false);
if (ops == null) {
return TraceMemoryState.UNKNOWN;
}
AddressRange hostRange = platform.mapGuestToHost(guestRange);
if (hostRange == null) {
return TraceMemoryState.UNKNOWN;
}
AddressSet hostSet = new AddressSet(toOverlay(hostRange));
for (long snap : viewport.getOrderedSnaps()) {
hostSet.delete(
ops.getAddressesWithState(snap, hostSet, s -> s == TraceMemoryState.KNOWN));
}
return hostSet.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN;
}
protected AddressSetView doGetKnown(Range<Long> span) {
TraceMemoryOperations ops = getMemoryOps(false);
if (ops == null) {
return new AddressSet();
}
return platform.mapHostToGuest(ops.getAddressesWithState(span,
s -> s == TraceMemoryState.KNOWN));
}
@Override
public AddressSetView getKnownNow() {
return doGetKnown(Range.singleton(snap));
}
@Override
public AddressSetView getKnownBefore() {
return doGetKnown(Range.closed(0L, snap));
}
@Override
public AddressSetView intersectUnknown(AddressSetView guestView) {
TraceMemoryOperations ops = getMemoryOps(false);
if (ops == null) {
return guestView;
}
AddressSetView hostView = toOverlay(platform.mapGuestToHost(guestView));
AddressSetView hostKnown = ops.getAddressesWithState(snap, hostView,
s -> s != null && s != TraceMemoryState.UNKNOWN);
AddressSetView hostResult = hostView.subtract(hostKnown);
return platform.mapHostToGuest(hostResult);
}
@Override
public int putBytes(Address start, ByteBuffer buf) {
// TODO: Truncate or verify range?
Address hostStart = platform.mapGuestToHost(start);
if (hostStart == null) {
return 0;
}
return getMemoryOps(true).putBytes(snap, toOverlay(hostStart), buf);
}
@Override
public int getBytes(Address start, ByteBuffer buf) {
// TODO: Truncate or verify range?
Address hostStart = platform.mapGuestToHost(start);
if (hostStart == null) {
return 0;
}
TraceMemoryOperations ops = getMemoryOps(false);
if (ops == null) {
// TODO: Write 0s?
int length = buf.remaining();
buf.position(buf.position() + length);
return length;
}
return ops.getViewBytes(snap, toOverlay(hostStart), buf);
}
@Override
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
return new DefaultPcodeTracePropertyAccess<>(this, name, type);
}
}

View file

@ -0,0 +1,57 @@
/* ###
* 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.pcode.exec.trace.data;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
/**
* The default trace access shim for a session
*/
public class DefaultPcodeTraceAccess extends AbstractPcodeTraceAccess //
<DefaultPcodeTraceMemoryAccess, DefaultPcodeTraceRegistersAccess> {
/**
* Construct a shim
*
* @param platform the associated platform
* @param snap the associated snap
* @param threadsSnap the snap to use when finding associated threads between trace and emulator
*/
public DefaultPcodeTraceAccess(TracePlatform platform, long snap, long threadsSnap) {
super(platform, snap, threadsSnap);
}
/**
* Construct a shim
*
* @param platform the associated platform
* @param snap the associated snap
*/
public DefaultPcodeTraceAccess(TracePlatform platform, long snap) {
super(platform, snap);
}
@Override
public DefaultPcodeTraceMemoryAccess newDataForSharedState() {
return new DefaultPcodeTraceMemoryAccess(platform, snap, viewport);
}
@Override
public DefaultPcodeTraceRegistersAccess newDataForLocalState(TraceThread thread, int frame) {
return new DefaultPcodeTraceRegistersAccess(platform, snap, thread, frame, viewport);
}
}

View file

@ -0,0 +1,72 @@
/* ###
* 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.pcode.exec.trace.data;
import ghidra.program.model.address.*;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryOperations;
import ghidra.trace.model.property.TracePropertyMapOperations;
import ghidra.trace.util.TraceTimeViewport;
/**
* The default data-access shim for trace memory
*/
public class DefaultPcodeTraceMemoryAccess extends AbstractPcodeTraceDataAccess
implements PcodeTraceMemoryAccess {
/**
* Construct a shim
*
* @param platform the associated platform
* @param snap the associated snap
* @param viewport the viewport, set to the same snapshot
*/
protected DefaultPcodeTraceMemoryAccess(TracePlatform platform, long snap,
TraceTimeViewport viewport) {
super(platform, snap, viewport);
}
@Override
protected TraceMemoryOperations getMemoryOps(boolean createIfAbsent) {
return mm;
}
@Override
public <T> TracePropertyMapOperations<T> getPropertyOps(String name, Class<T> type,
boolean createIfAbsent) {
if (createIfAbsent) {
return platform.getTrace()
.getAddressPropertyManager()
.getOrCreatePropertyMap(name, type);
}
return platform.getTrace().getAddressPropertyManager().getPropertyMap(name, type);
}
@Override
protected Address toOverlay(Address address) {
return address;
}
@Override
protected AddressRange toOverlay(AddressRange range) {
return range;
}
@Override
protected AddressSetView toOverlay(AddressSetView set) {
return set;
}
}

View file

@ -0,0 +1,155 @@
/* ###
* 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.pcode.exec.trace.data;
import com.google.common.collect.Range;
import ghidra.program.model.address.*;
import ghidra.trace.database.DBTraceUtils;
import ghidra.trace.model.property.*;
/**
* The default trace-property access shim
*
* @param <T> the type of the property's values
*/
public class DefaultPcodeTracePropertyAccess<T>
implements PcodeTracePropertyAccess<T> {
protected final InternalPcodeTraceDataAccess data;
protected final String name;
protected final Class<T> type;
protected TracePropertyMapOperations<T> po;
/**
* Construct the shim
*
* @param data the trace-data access shim providing this property access shim
* @param name the name of the property
* @param type the type of the property
*/
protected DefaultPcodeTracePropertyAccess(InternalPcodeTraceDataAccess data, String name,
Class<T> type) {
this.data = data;
this.name = name;
this.type = type;
this.po = data.getPropertyOps(name, type, false);
}
/**
* Get the interface for accessing the trace property on memory or registers
*
* @param createIfAbsent whether to create the missing property (and space in the case of a
* register property)
* @return the operations, or null
*/
protected TracePropertyMapOperations<T> getPropertyOperations(boolean createIfAbsent) {
if (po == null) {
return po = data.getPropertyOps(name, type, createIfAbsent);
}
return po;
}
/**
* Extension point: Alternative logic when the trace property is null
*
* @param hostAddress the trace address (in the host platform)
* @return the alternative value, or null
*/
protected T whenNull(Address hostAddress) {
return null;
}
@Override
public T get(Address address) {
Address hostAddr = data.getPlatform().mapGuestToHost(address);
if (hostAddr == null) {
return null;
}
TracePropertyMapOperations<T> ops = getPropertyOperations(false);
if (ops == null) {
return whenNull(hostAddr);
}
Address overlayAddr = toOverlay(ops, hostAddr);
return ops.get(data.getSnap(), overlayAddr);
}
@Override
public void put(Address address, T value) {
Address hostAddr = data.getPlatform().mapGuestToHost(address);
if (hostAddr == null) {
// TODO: Warn?
return;
}
Range<Long> span = DBTraceUtils.atLeastMaybeScratch(data.getSnap());
TracePropertyMapOperations<T> ops = getPropertyOperations(true);
ops.set(span, toOverlay(ops, hostAddr), value);
}
@Override
public void clear(AddressRange range) {
AddressRange hostRange = data.getPlatform().mapGuestToHost(range);
if (hostRange == null) {
// TODO: Warn?
return;
}
Range<Long> span = DBTraceUtils.atLeastMaybeScratch(data.getSnap());
TracePropertyMapOperations<T> ops = getPropertyOperations(false);
if (ops == null) {
return;
}
ops.clear(span, toOverlay(ops, hostRange));
}
/**
* If this provides access to an overlay space, translate the physical address to it
*
* @param ops the property operations
* @param address the physical address
* @return the overlay address, or the same address
*/
protected Address toOverlay(TracePropertyMapOperations<T> ops, Address address) {
if (ops instanceof TracePropertyMap) {
return address;
}
if (ops instanceof TracePropertyMapSpace<T> mapSpace) {
return mapSpace.getAddressSpace().getOverlayAddress(address);
}
throw new AssertionError();
}
/**
* If this provides access to an overlay space, translate the physical range to it
*
* @param ops the property operations
* @param range the physical range
* @return the overlay range, or the same range
*/
protected AddressRange toOverlay(TracePropertyMapOperations<T> ops, AddressRange range) {
if (ops instanceof TracePropertyMap) {
return range;
}
if (ops instanceof TracePropertyMapSpace<T> mapSpace) {
AddressSpace space = mapSpace.getAddressSpace();
return new AddressRangeImpl(
space.getOverlayAddress(range.getMinAddress()),
space.getOverlayAddress(range.getMaxAddress()));
}
throw new AssertionError();
}
}

View file

@ -0,0 +1,151 @@
/* ###
* 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.pcode.exec.trace.data;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemorySpace;
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.TraceTimeViewport;
/**
* The default data-access shim for trace registers
*/
public class DefaultPcodeTraceRegistersAccess extends AbstractPcodeTraceDataAccess
implements PcodeTraceRegistersAccess {
protected final TraceThread thread;
protected final int frame;
protected TraceMemorySpace ms;
/**
* Construct a shim
*
* @param platform the associated platform
* @param snap the associated snap
* @param thread the associated thread whose registers to access
* @param frame the associated frame, or 0 if not applicable
* @param viewport the viewport, set to the same snapshot
*/
protected DefaultPcodeTraceRegistersAccess(TracePlatform platform, long snap,
TraceThread thread, int frame, TraceTimeViewport viewport) {
super(platform, snap, viewport);
this.thread = thread;
this.frame = frame;
this.ms = mm.getMemoryRegisterSpace(thread, frame, false);
}
@Override
protected TraceMemorySpace getMemoryOps(boolean createIfAbsent) {
if (ms == null) {
return ms = mm.getMemoryRegisterSpace(thread, frame, createIfAbsent);
}
return ms;
}
@Override
public <T> TracePropertyMapSpace<T> getPropertyOps(String name, Class<T> type,
boolean createIfAbsent) {
if (createIfAbsent) {
return platform.getTrace()
.getAddressPropertyManager()
.getOrCreatePropertyMap(name, type)
.getPropertyMapRegisterSpace(thread, frame, createIfAbsent);
}
TracePropertyMap<T> map = platform.getTrace()
.getAddressPropertyManager()
.getPropertyMap(name, type);
return map == null ? null : map.getPropertyMapRegisterSpace(thread, frame, createIfAbsent);
}
/**
* Check if a register has a {@link TraceMemoryState#KNOWN} value for the given thread
*
* @param thread the thread
* @param register the register
* @return true if known
*/
protected boolean isRegisterKnown(PcodeThread<?> thread, Register register) {
Trace trace = platform.getTrace();
TraceThread traceThread =
trace.getThreadManager().getLiveThreadByPath(snap, thread.getName());
TraceMemorySpace space =
trace.getMemoryManager().getMemoryRegisterSpace(traceThread, false);
if (space == null) {
return false;
}
return space.getState(platform, snap, register) == TraceMemoryState.KNOWN;
}
@Override
public void initializeThreadContext(PcodeThread<?> thread) {
Trace trace = platform.getTrace();
Language language = platform.getLanguage();
Register contextreg = language.getContextBaseRegister();
if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(thread, contextreg)) {
RegisterValue context = trace.getRegisterContextManager()
.getValueWithDefault(platform, contextreg, snap, thread.getCounter());
if (context != null) { // TODO: Why does this happen?
thread.overrideContext(context);
}
}
}
@Override
protected Address toOverlay(Address address) {
TraceMemorySpace ops = getMemoryOps(false);
if (ops == null) {
return null; // client should bail anyway
}
return ops.getAddressSpace().getOverlayAddress(address);
}
@Override
protected AddressRange toOverlay(AddressRange range) {
TraceMemorySpace ops = getMemoryOps(false);
if (ops == null) {
return null; // client should bail anyway
}
AddressSpace space = ops.getAddressSpace();
return new AddressRangeImpl(
space.getOverlayAddress(range.getMinAddress()),
space.getOverlayAddress(range.getMaxAddress()));
}
@Override
protected AddressSetView toOverlay(AddressSetView set) {
TraceMemorySpace ops = getMemoryOps(false);
if (ops == null) {
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;
}
}

View file

@ -0,0 +1,113 @@
/* ###
* 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.pcode.exec.trace.data;
import java.nio.ByteBuffer;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.memory.TraceMemoryState;
/**
* The default data-access shim, for both memory and registers
*
* <p>
* This is not designed for use with the emulator, but rather with stand-alone p-code executors,
* e.g., to evaluate a Sleigh expression. It multiplexes a given memory access shim and another
* register access shim into a single shim for use in one state piece.
*/
public class DefaultPcodeTraceThreadAccess
implements PcodeTraceMemoryAccess, PcodeTraceRegistersAccess {
protected final PcodeTraceMemoryAccess memory;
protected final PcodeTraceRegistersAccess registers;
/**
* Construct a shim
*
* @param memory the memory access shim
* @param registers the regsiter access shim
*/
protected DefaultPcodeTraceThreadAccess(PcodeTraceMemoryAccess memory,
PcodeTraceRegistersAccess registers) {
this.memory = memory;
this.registers = registers;
}
@Override
public Language getLanguage() {
return memory.getLanguage();
}
@Override
public void setState(AddressRange range, TraceMemoryState state) {
if (range.getAddressSpace().isRegisterSpace()) {
registers.setState(range, state);
return;
}
memory.setState(range, state);
}
@Override
public TraceMemoryState getViewportState(AddressRange range) {
if (range.getAddressSpace().isRegisterSpace()) {
return registers.getViewportState(range);
}
return memory.getViewportState(range);
}
@Override
public AddressSetView getKnownNow() {
return memory.getKnownNow().union(registers.getKnownNow());
}
@Override
public AddressSetView getKnownBefore() {
return memory.getKnownBefore().union(registers.getKnownBefore());
}
@Override
public AddressSetView intersectUnknown(AddressSetView view) {
return memory.intersectUnknown(view).union(registers.intersectUnknown(view));
}
@Override
public int putBytes(Address start, ByteBuffer buf) {
if (start.isRegisterAddress()) {
return registers.putBytes(start, buf);
}
return memory.putBytes(start, buf);
}
@Override
public int getBytes(Address start, ByteBuffer buf) {
if (start.isRegisterAddress()) {
return registers.getBytes(start, buf);
}
return memory.getBytes(start, buf);
}
@Override
public <T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type) {
throw new UnsupportedOperationException("This is meant for p-code executor use");
}
@Override
public void initializeThreadContext(PcodeThread<?> thread) {
registers.initializeThreadContext(thread);
}
}

View file

@ -0,0 +1,30 @@
/* ###
* 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.pcode.exec.trace.data;
import ghidra.lifecycle.Internal;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.property.TracePropertyMapOperations;
@Internal
public interface InternalPcodeTraceDataAccess extends PcodeTraceDataAccess {
TracePlatform getPlatform();
long getSnap();
<T> TracePropertyMapOperations<T> getPropertyOps(String name, Class<T> type,
boolean createIfAbsent);
}

View file

@ -0,0 +1,129 @@
/* ###
* 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.pcode.exec.trace.data;
import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.trace.TracePcodeMachine;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
/**
* A trace access shim
*
* <p>
* This encapsulates the source or destination "coordinates" of a trace to simplify access to that
* trace by p-code operations. This is also meant to encapsulate certain conventions, e.g., writes
* are effective from the destination snapshot into the indefinite future, and meant to protect
* p-code executor/emulator states from future re-factorings of the Trace API.
*
* <p>
* While, technically anything can be behind the shim, the default implementations are backed by a
* trace. The shim is associated with a chosen platform and snapshot. All methods are with respect
* to that platform. In particular the addresses must all be in spaces of the platform's language.
* Note that the platform may be the trace's host platform.
*
* <p>
* Typically, each component of an emulator and/or its state will accept a corresponding access
* shim. Thus, each method in the chain of obtaining the shim is invoked during that piece's
* construction or invoked and passed into the constructor by a factory method. A complete chain
* starts with {@link DefaultPcodeTraceAccess}. Each method is listed with notes about where it is
* typically invoked below:
*
* <ul>
* <li>Typically invoked by an overloaded constructor, which then passes it to {@code this(...)}.
* Clients can also construct the shim and pass it to the shim-accepting constructor manually.
* Similarly, {@link TracePcodeMachine#writeDown(TracePlatform, long, long)} will construct one and
* pass it to the overloaded method, which can instead be done by the client.
*
* <pre>
* PcodeTraceAccess access =
* new DefaultPcodeTraceAccess(trace.getPlatformManager().getHostPlatform(), 0, 0);
* </pre>
*
* <li>Typically invoked by a factory method for an emulator's shared executor state
*
* <pre>
* PcodeTraceMemoryAccess sharedData = access.getDataForSharedState();
* </pre>
*
* <li>Typically invoked by a factory method for an emulator thread's local executor state
*
* <pre>
* PcodeTraceRegisterAccess localData = access.getDataForLocalState(thread, 0);
* </pre>
*
* <li>Typically invoked by an auxiliary emulator state piece
*
* <pre>
* PcodeTracePropertyAccess<String> property = data.getPropertyAccess("MyProperty", String.class);
* </pre>
* </ul>
*/
public interface PcodeTraceAccess {
/**
* Get the language of the associated platform
*
* @return the langauge
*/
Language getLanguage();
/**
* Get the data-access shim for use in an emulator's shared state
*
* @return the shim
*/
PcodeTraceMemoryAccess getDataForSharedState();
/**
* Get the data-access shim for use in an emulator thread's local state
*
* @param thread the emulator's thread
* @param frame the frame, usually 0
* @return the shim
*/
PcodeTraceRegistersAccess getDataForLocalState(PcodeThread<?> thread, int frame);
/**
* Get the data-access shim for use in an emulator thread's local state
*
* @param thread the trace thread associated with the emulator's thread
* @param frame the frame, usually 0
* @return the shim
*/
PcodeTraceRegistersAccess getDataForLocalState(TraceThread thread, int frame);
/**
* Get the data-access shim for use in an executor having thread context
*
* <p>
* <b>NOTE:</b> Do not use this shim for an emulator thread's local state. Use
* {@link #getDataForLocalState(PcodeThread, int)} instead. This shim is meant for use in
* stand-alone executors, e.g., for evaluating Sleigh expressions. Most likely, the thread is
* the active thread in the UI.
*
* @param thread the trace thread for context, if applicable, or null
* @param frame the frame
* @return the shim
*/
default PcodeTraceDataAccess getDataForThreadState(TraceThread thread, int frame) {
if (thread == null) {
return getDataForSharedState();
}
return new DefaultPcodeTraceThreadAccess(getDataForSharedState(),
getDataForLocalState(thread, frame));
}
}

View file

@ -0,0 +1,135 @@
/* ###
* 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.pcode.exec.trace.data;
import java.nio.ByteBuffer;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.memory.TraceMemoryState;
/**
* A data-access shim for a trace
*
* @see PcodeTraceAccess
*/
public interface PcodeTraceDataAccess {
/**
* Get the language of the associated platform
*
* @return the language
*/
Language getLanguage();
/**
* Set the memory state of an address range
*
* <p>
* The state is set only for the destination snapshot. It is <em>not</em> effective for the
* indefinite future.
*
* @param range the range
* @param state the desired state
*/
void setState(AddressRange range, TraceMemoryState state);
/**
* Get the composite state of an address range, using the snapshot's viewport
*
* <p>
* Typically, the viewport is at most 2 snapshots deep. When reading from a captured snapshot,
* the viewport includes only the source snapshot. When reading from scratch snapshot (usually
* generated by emulation), the viewport includes that scratch snapshot and the original source
* snapshot. The {@link TraceMemoryState#KNOWN} address set is the union of known address sets
* among all snapshots in the viewport. If all addresses in the given range are
* {@link TraceMemoryState#KNOWN}, then the composite state is known. Otherwise, the composite
* state is {@link TraceMemoryState#UNKNOWN}.
*
* @param range the range to check
* @return the composite state of the range
*/
TraceMemoryState getViewportState(AddressRange range);
/**
* Get the address set of {@link TraceMemoryState#KNOWN} memory in the source snapshot
*
* <p>
* Note, this does not consider the snapshot's viewport.
*
* @implNote This can be an expensive operation when the platform is a guest, since what would
* ordinarily be a lazy address set must be computed and translated to the guest
* address spaces.
*
* @return the address set
*/
AddressSetView getKnownNow();
/**
* Get the address set of {@link TraceMemoryState#KNOWN} memory among all snapshots from 0 to
* the source snapshot
*
* <p>
* Note, this does not consider the snapshot's viewport.
*
* @implNote This can be an expensive operation when the platform is a guest, since what would
* ordinarily be a lazy address set must be computed and translated to the guest
* address spaces.
*
* @return the address set
*/
AddressSetView getKnownBefore();
/**
* Compute the intersection of the given address set and the set of
* {@link TraceMemoryState#UNKNOWN} memory
*
* @param view the address set
* @return the intersection
*/
AddressSetView intersectUnknown(AddressSetView view);
/**
* Write bytes into the trace
*
* <p>
* Each written byte is effective for future snapshots up to but excluding the next snapshot
* where another byte is written at the same address.
*
* @param start the address of the first byte to write
* @param buf a buffer of bytes to write
* @return the number of bytes written
*/
int putBytes(Address start, ByteBuffer buf);
/**
* Read bytes from the trace
*
* @param start the address of the first byte to read
* @param buf a buffer to receive the bytes
* @return the number of bytes read
*/
int getBytes(Address start, ByteBuffer buf);
/**
* Get a property-access shim for the named property
*
* @param <T> the type of the property's values
* @param name the name of the property
* @param type the class of the property's values
* @return the access shim
*/
<T> PcodeTracePropertyAccess<T> getPropertyAccess(String name, Class<T> type);
}

View file

@ -13,18 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.pcode.exec;
import java.util.concurrent.CompletableFuture;
package ghidra.pcode.exec.trace.data;
/**
* The state for a {@link AsyncWrappedPcodeExecutorStatePiece}
*
* @param <T> the type of wrapped values
* A data-access shim for a trace's memory
*/
public class AsyncWrappedPcodeExecutorState<T>
extends DefaultPcodeExecutorState<CompletableFuture<T>> {
public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece<T, T> piece) {
super(new AsyncWrappedPcodeExecutorStatePiece<>(piece));
}
public interface PcodeTraceMemoryAccess extends PcodeTraceDataAccess {
// Nothing to add
}

View file

@ -0,0 +1,60 @@
/* ###
* 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.pcode.exec.trace.data;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
/**
* A trace-property access shim for a specific property
*
* @see PcodeTraceAccess
* @see PcodeTraceDataAccess
*
* @param <T> the type of the property's values
*/
public interface PcodeTracePropertyAccess<T> {
/**
* Get the property's value at the given address
*
* <p>
* This may search for the same property from other related data sources, e.g., from mapped
* static images.
*
* @param address the address
* @return the value, or null if not set
*/
T get(Address address);
/**
* Set the property's value at the given address
*
* <p>
* The value is affective for future snapshots up to but excluding the next snapshot where
* another value is set at the same address.
*
* @param address the address
* @param value the value to set
*/
void put(Address address, T value);
/**
* Clear the property's value across a range
*
* @param range the range
*/
void clear(AddressRange range);
}

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.pcode.exec.trace.data;
import ghidra.pcode.emu.PcodeThread;
/**
* A data-access shim for a trace's registers
*/
public interface PcodeTraceRegistersAccess extends PcodeTraceDataAccess {
/**
* Initialize the given p-code thread's context register using register context from the trace
* at the thread's program counter
*
* <p>
* This is called during thread construction, after the program counter is initialized from the
* same trace thread. This will ensure that the instruction decoder starts in the same mode as
* the disassembler was for the trace.
*
* @param thread the thread to initialize
*/
void initializeThreadContext(PcodeThread<?> thread);
}

View file

@ -38,6 +38,7 @@ import ghidra.trace.database.breakpoint.DBTraceBreakpointManager;
import ghidra.trace.database.context.DBTraceRegisterContextManager;
import ghidra.trace.database.data.DBTraceDataSettingsAdapter;
import ghidra.trace.database.data.DBTraceDataTypeManager;
import ghidra.trace.database.guest.DBTraceObjectRegisterSupport;
import ghidra.trace.database.guest.DBTracePlatformManager;
import ghidra.trace.database.listing.DBTraceCodeManager;
import ghidra.trace.database.listing.DBTraceCommentAdapter;
@ -567,6 +568,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace
@Override
public void setChanged(TraceChangeRecord<?, ?> event) {
changed = true;
DBTraceObjectRegisterSupport.INSTANCE.processEvent(event);
fireEvent(event);
}

View file

@ -26,6 +26,8 @@ import db.*;
import ghidra.program.model.address.*;
import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceManager;
import ghidra.trace.model.Trace.TraceOverlaySpaceChangeType;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
@ -241,6 +243,8 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager {
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;
}
}
@ -258,6 +262,8 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager {
assert space != null;
factory.removeOverlaySpace(name);
trace.updateViewsDeleteSpaceBlock(space);
trace.setChanged(new TraceChangeRecord<>(TraceOverlaySpaceChangeType.DELETED, null,
trace, space, null));
}
}
}

View file

@ -28,6 +28,12 @@ public class TraceAddressFactory extends ProgramAddressFactory {
super(language, compilerSpec);
}
@Override
protected boolean validateOriginalSpace(AddressSpace originalSpace) {
return (originalSpace.isMemorySpace() || originalSpace.isRegisterSpace()) &&
!originalSpace.isOverlaySpace();
}
@Override // for peer access
protected OverlayAddressSpace addOverlayAddressSpace(String name, boolean preserveName,
AddressSpace originalSpace, long minOffset, long maxOffset) {

View file

@ -39,6 +39,7 @@ import ghidra.trace.database.space.DBTraceDelegatingManager;
import ghidra.trace.database.thread.DBTraceThreadManager;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.context.TraceRegisterContextManager;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
@ -181,10 +182,15 @@ public class DBTraceRegisterContextManager
}
@Override
public RegisterValue getValueWithDefault(Language language, Register register, long snap,
public RegisterValue getValueWithDefault(TracePlatform platform, Register register, long snap,
Address address) {
return delegateReadOr(address.getAddressSpace(),
m -> m.getValueWithDefault(language, register, snap, address),
Address hostAddress = platform.mapGuestToHost(address);
Language language = platform.getLanguage();
if (hostAddress == null) {
return getDefaultValue(language, register, address);
}
return delegateReadOr(hostAddress.getAddressSpace(),
m -> m.getValueWithDefault(language, register, snap, hostAddress, address),
() -> getDefaultValue(language, register, address));
}

View file

@ -41,6 +41,7 @@ import ghidra.trace.database.space.DBTraceSpaceBased;
import ghidra.trace.model.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.context.TraceRegisterContextSpace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.LockHold;
import ghidra.util.database.*;
@ -369,20 +370,30 @@ public class DBTraceRegisterContextSpace implements TraceRegisterContextSpace, D
}
}
protected RegisterValue getValueWithDefault(Language language, Register register,
long snap, Address hostAddress, Address langAddress) {
Register base = register.getBaseRegister();
RegisterValue baseValue = doGetBaseValue(language, base, snap, hostAddress);
if (baseValue == null) {
return getDefaultValue(language, register, langAddress);
}
RegisterValue defaultBaseValue = getDefaultValue(language, base, langAddress);
if (defaultBaseValue == null) {
return baseValue.getRegisterValue(register);
}
return defaultBaseValue.combineValues(baseValue).getRegisterValue(register);
}
@Override
public RegisterValue getValueWithDefault(Language language, Register register, long snap,
Address address) {
public RegisterValue getValueWithDefault(TracePlatform platform, Register register, long snap,
Address guestAddress) {
Language language = platform.getLanguage();
try (LockHold hold = LockHold.lock(lock.readLock())) {
Register base = register.getBaseRegister();
RegisterValue baseValue = doGetBaseValue(language, base, snap, address);
if (baseValue == null) {
return getDefaultValue(language, register, address);
Address hostAddress = platform.mapGuestToHost(guestAddress);
if (hostAddress == null) {
return getDefaultValue(language, register, guestAddress);
}
RegisterValue defaultBaseValue = getDefaultValue(language, base, address);
if (defaultBaseValue == null) {
return baseValue.getRegisterValue(register);
}
return defaultBaseValue.combineValues(baseValue).getRegisterValue(register);
return getValueWithDefault(language, register, snap, hostAddress, guestAddress);
}
}

View file

@ -19,6 +19,8 @@ import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Range;
import db.DBRecord;
@ -35,6 +37,9 @@ import ghidra.trace.database.DBTraceUtils.LanguageIDDBFieldCodec;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TracePlatformChangeType;
import ghidra.trace.model.guest.TraceGuestPlatform;
import ghidra.trace.model.guest.TraceGuestPlatformMappedRange;
import ghidra.trace.util.OverlappingObjectIterator;
import ghidra.trace.util.OverlappingObjectIterator.Ranger;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold;
import ghidra.util.database.*;
@ -48,6 +53,33 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
implements TraceGuestPlatform, InternalTracePlatform {
public static final String TABLE_NAME = "Platforms";
private static enum MappedRangeRanger implements Ranger<DBTraceGuestPlatformMappedRange> {
HOST {
@Override
AddressRange getRange(DBTraceGuestPlatformMappedRange t) {
return t.getHostRange();
}
},
GUEST {
@Override
AddressRange getRange(DBTraceGuestPlatformMappedRange t) {
return t.getGuestRange();
}
};
abstract AddressRange getRange(DBTraceGuestPlatformMappedRange t);
@Override
public Address getMinAddress(DBTraceGuestPlatformMappedRange t) {
return getRange(t).getMinAddress();
}
@Override
public Address getMaxAddress(DBTraceGuestPlatformMappedRange t) {
return getRange(t).getMaxAddress();
}
}
@DBAnnotatedObjectInfo(version = 0)
public static class DBTraceGuestLanguage extends DBAnnotatedObject {
public static final String TABLE_NAME = "Languages";
@ -239,6 +271,36 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
return mappedRange;
}
protected Address computeNextRegisterMin() {
Address regMax = manager.hostPlatform.getLanguage()
.getAddressFactory()
.getRegisterSpace()
.getMaxAddress();
AddressRangeIterator rit = hostAddressSet.getAddressRanges(regMax, false);
if (!rit.hasNext()) {
return null;
}
AddressRange next = rit.next();
if (!next.getAddressSpace().isRegisterSpace()) {
return null;
}
return next.getMaxAddress().add(1);
}
@Override
public TraceGuestPlatformMappedRange addMappedRegisterRange()
throws AddressOverflowException {
try (LockHold hold = LockHold.lock(manager.lock.writeLock())) {
AddressRange guestRange = getRegistersRange();
if (guestRange == null) {
return null; // No registers, so we're mapped!
}
Address hostMin = manager.computeNextRegisterMin();
long size = guestRange.getLength();
return addMappedRange(hostMin, guestRange.getMinAddress(), size);
}
}
@Override
public AddressSetView getHostAddressSet() {
return new AddressSet(hostAddressSet);
@ -261,6 +323,34 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
}
}
@Override
public AddressRange mapHostToGuest(AddressRange hostRange) {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
Entry<Address, DBTraceGuestPlatformMappedRange> floorEntry =
rangesByHostAddress.floorEntry(hostRange.getMinAddress());
if (floorEntry == null) {
return null;
}
return floorEntry.getValue().mapHostToGuest(hostRange);
}
}
@Override
public AddressSetView mapHostToGuest(AddressSetView hostSet) {
Iterator<Pair<DBTraceGuestPlatformMappedRange, AddressRange>> it =
new OverlappingObjectIterator<DBTraceGuestPlatformMappedRange, AddressRange>(
rangesByHostAddress.values().iterator(), MappedRangeRanger.HOST,
hostSet.iterator(), OverlappingObjectIterator.ADDRESS_RANGE);
AddressSet result = new AddressSet();
while (it.hasNext()) {
Pair<DBTraceGuestPlatformMappedRange, AddressRange> next = it.next();
DBTraceGuestPlatformMappedRange entry = next.getLeft();
AddressRange hostRange = next.getRight();
result.add(entry.mapHostToGuest(hostRange));
}
return result;
}
@Override
public Address mapGuestToHost(Address guestAddress) {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
@ -273,6 +363,34 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
}
}
@Override
public AddressRange mapGuestToHost(AddressRange guestRange) {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) {
Entry<Address, DBTraceGuestPlatformMappedRange> floorEntry =
rangesByGuestAddress.floorEntry(guestRange.getMinAddress());
if (floorEntry == null) {
return null;
}
return floorEntry.getValue().mapGuestToHost(guestRange);
}
}
@Override
public AddressSetView mapGuestToHost(AddressSetView guestSet) {
Iterator<Pair<DBTraceGuestPlatformMappedRange, AddressRange>> it =
new OverlappingObjectIterator<DBTraceGuestPlatformMappedRange, AddressRange>(
rangesByGuestAddress.values().iterator(), MappedRangeRanger.GUEST,
guestSet.iterator(), OverlappingObjectIterator.ADDRESS_RANGE);
AddressSet result = new AddressSet();
while (it.hasNext()) {
Pair<DBTraceGuestPlatformMappedRange, AddressRange> next = it.next();
DBTraceGuestPlatformMappedRange entry = next.getLeft();
AddressRange hostRange = next.getRight();
result.add(entry.mapGuestToHost(hostRange));
}
return result;
}
/**
* Map the an address only if the entire range is contained in a single mapped range
*

View file

@ -19,8 +19,6 @@ import java.io.IOException;
import db.DBRecord;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.lang.Language;
import ghidra.trace.model.guest.TraceGuestPlatformMappedRange;
import ghidra.util.database.*;
import ghidra.util.database.annot.*;
@ -53,13 +51,13 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
static DBObjectColumn LENGTH_COLUMN;
@DBAnnotatedField(column = HOST_SPACE_COLUMN_NAME)
private int hostSpace;
private int hostSpaceID;
@DBAnnotatedField(column = HOST_OFFSET_COLUMN_NAME)
private long hostOffset;
@DBAnnotatedField(column = GUEST_LANGUAGE_COLUMN_NAME)
int guestPlatformKey;
@DBAnnotatedField(column = GUEST_SPACE_COLUMN_NAME)
private int guestSpace;
private int guestSpaceID;
@DBAnnotatedField(column = GUEST_OFFSET_COLUMN_NAME)
private long guestOffset;
@DBAnnotatedField(column = LENGTH_COLUMN_NAME)
@ -70,6 +68,9 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
private AddressRangeImpl hostRange;
private DBTraceGuestPlatform platform;
private AddressRangeImpl guestRange;
private AddressSpace hostSpace;
private AddressSpace guestSpace;
private long shiftHostToGuest;
public DBTraceGuestPlatformMappedRange(DBTracePlatformManager manager, DBCachedObjectStore<?> s,
DBRecord r) {
@ -83,10 +84,8 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
if (created) {
return;
}
Address hostStart =
manager.trace.getBaseLanguage()
.getAddressFactory()
.getAddress(hostSpace, hostOffset);
this.hostSpace = manager.trace.getBaseAddressFactory().getAddressSpace(hostSpaceID);
Address hostStart = hostSpace.getAddress(hostOffset);
Address hostEnd = hostStart.addWrap(length - 1);
this.hostRange = new AddressRangeImpl(hostStart, hostEnd);
@ -95,16 +94,21 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
throw new IOException("Table is corrupt. Got host platform in guest mapping.");
}
this.platform = (DBTraceGuestPlatform) platform;
Address guestStart = platform.getAddressFactory().getAddress(guestSpace, guestOffset);
this.guestSpace = platform.getAddressFactory().getAddressSpace(guestSpaceID);
Address guestStart = guestSpace.getAddress(guestOffset);
Address guestEnd = guestStart.addWrap(length - 1);
this.guestRange = new AddressRangeImpl(guestStart, guestEnd);
this.shiftHostToGuest = guestStart.getOffset() - hostStart.getOffset();
}
void set(Address hostStart, DBTraceGuestPlatform platform, Address guestStart, long length) {
this.hostSpace = hostStart.getAddressSpace().getSpaceID();
this.hostSpace = hostStart.getAddressSpace();
this.hostSpaceID = hostSpace.getSpaceID();
this.hostOffset = hostStart.getOffset();
this.guestPlatformKey = (int) platform.getKey();
this.guestSpace = guestStart.getAddressSpace().getSpaceID();
this.guestSpace = guestStart.getAddressSpace();
this.guestSpaceID = guestSpace.getSpaceID();
this.guestOffset = guestStart.getOffset();
this.length = length;
update(HOST_SPACE_COLUMN, HOST_OFFSET_COLUMN, GUEST_LANGUAGE_COLUMN, GUEST_SPACE_COLUMN,
@ -114,16 +118,12 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
this.platform = platform;
this.guestRange = new AddressRangeImpl(guestStart, guestStart.addWrap(length - 1));
this.shiftHostToGuest = guestStart.getOffset() - hostStart.getOffset();
}
@Override
public Language getHostLanguage() {
return manager.trace.getBaseLanguage();
}
@Override
public CompilerSpec getHostCompilerSpec() {
return manager.trace.getBaseCompilerSpec();
public InternalTracePlatform getHostPlatform() {
return manager.hostPlatform;
}
@Override
@ -141,13 +141,31 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
return guestRange;
}
protected static Address doMapTo(Address address, AddressSpace space, long shift) {
return space.getAddress(address.getOffset() + shift);
}
protected static AddressRange doMapTo(AddressRange range, AddressSpace space, long shift) {
return new AddressRangeImpl(
doMapTo(range.getMinAddress(), space, shift),
doMapTo(range.getMaxAddress(), space, shift));
}
@Override
public Address mapHostToGuest(Address hostAddress) {
if (!hostRange.contains(hostAddress)) {
return null;
}
long offset = hostAddress.subtract(hostRange.getMinAddress());
return guestRange.getMinAddress().add(offset);
return doMapTo(hostAddress, guestSpace, shiftHostToGuest);
}
@Override
public AddressRange mapHostToGuest(AddressRange hostRange) {
if (!this.hostRange.contains(hostRange.getMinAddress()) ||
!this.hostRange.contains(hostRange.getMaxAddress())) {
return null;
}
return doMapTo(hostRange, guestSpace, shiftHostToGuest);
}
@Override
@ -155,8 +173,16 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
if (!guestRange.contains(guestAddress)) {
return null;
}
long offset = guestAddress.subtract(guestRange.getMinAddress());
return hostRange.getMinAddress().add(offset);
return doMapTo(guestAddress, hostSpace, -shiftHostToGuest);
}
@Override
public AddressRange mapGuestToHost(AddressRange guestRange) {
if (!this.guestRange.contains(guestRange.getMinAddress()) ||
!this.guestRange.contains(guestRange.getMaxAddress())) {
return null;
}
return doMapTo(guestRange, hostSpace, -shiftHostToGuest);
}
@Override

View file

@ -0,0 +1,492 @@
/* ###
* 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.guest;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.stream.Stream;
import com.google.common.collect.Range;
import ghidra.dbg.target.TargetRegister;
import ghidra.dbg.target.TargetRegisterContainer;
import ghidra.dbg.target.schema.TargetObjectSchema;
import ghidra.dbg.util.PathMatcher;
import ghidra.dbg.util.PathPredicates.Align;
import ghidra.dbg.util.PathUtils;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Register;
import ghidra.program.model.lang.RegisterValue;
import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.*;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.guest.*;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.symbol.*;
import ghidra.trace.model.target.*;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.Msg;
public enum DBTraceObjectRegisterSupport {
// TODO: Could/should this be done by an analyzer, instead of being internal trace logic?
INSTANCE;
private static final TraceDomainObjectListener HANDLER = new TraceDomainObjectListener() {
{
listenFor(TraceObjectChangeType.VALUE_CREATED, INSTANCE::objectValueCreated);
listenFor(TraceSymbolChangeType.ADDED, INSTANCE::symbolAdded);
listenFor(TraceOverlaySpaceChangeType.ADDED, INSTANCE::spaceAdded);
listenFor(TracePlatformChangeType.MAPPING_ADDED, INSTANCE::guestMappingAdded);
}
};
static class RegisterValueException extends Exception {
public RegisterValueException(String message) {
super(message);
}
}
static class LazyValues {
private final TraceObjectValue registerValue;
private BigInteger value;
private int length = -1;
private byte[] be;
private byte[] le;
public LazyValues(TraceObjectValue registerValue) {
this.registerValue = registerValue;
}
BigInteger convertRegisterValueToBigInteger() throws RegisterValueException {
Object val = registerValue.getValue();
if (val instanceof String s) {
try {
return new BigInteger(s, 16);
}
catch (NumberFormatException e) {
throw new RegisterValueException(
"Invalid register value " + s + ". Must be hex digits only.");
}
}
if (val instanceof Byte b) {
return BigInteger.valueOf(b);
}
else if (val instanceof Short s) {
return BigInteger.valueOf(s);
}
else if (val instanceof Integer i) {
return BigInteger.valueOf(i);
}
else if (val instanceof Long l) {
return BigInteger.valueOf(l);
}
else if (val instanceof Address a) {
return a.getOffsetAsBigInteger();
}
throw new RegisterValueException(
"Cannot convert register value: (" + registerValue.getValue().getClass() + ") '" +
registerValue.getValue() +
"'");
}
int getRegisterValueLength() throws RegisterValueException {
Object objLength = registerValue.getParent()
.getValue(registerValue.getMinSnap(), TargetRegister.LENGTH_ATTRIBUTE_NAME)
.getValue();
if (!(objLength instanceof Number)) {
throw new RegisterValueException(
"Register length is not numeric: (" + objLength.getClass() + ") '" + objLength +
"'");
}
return ((Number) objLength).intValue();
}
BigInteger getValue() throws RegisterValueException {
if (value != null) {
return value;
}
return value = convertRegisterValueToBigInteger();
}
int getLength() throws RegisterValueException {
if (length != -1) {
return length;
}
return length = getRegisterValueLength();
}
byte[] getBytesBigEndian() throws RegisterValueException {
if (be != null) {
return be;
}
return be = Utils.bigIntegerToBytes(getValue(), getLength(), true);
}
byte[] getBytesLittleEndian() throws RegisterValueException {
if (le != null) {
return le;
}
return le = Utils.bigIntegerToBytes(getValue(), getLength(), false);
}
public byte[] getBytes(boolean isBigEndian) throws RegisterValueException {
return isBigEndian ? getBytesBigEndian() : getBytesLittleEndian();
}
}
protected AddressSpace findRegisterOverlay(TraceObject object) {
TraceObject container = object
.queryCanonicalAncestorsTargetInterface(TargetRegisterContainer.class)
.findFirst()
.orElse(null);
if (container == null) {
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;
}
protected AddressSpace findRegisterOverlay(TraceObjectValue objectValue) {
return findRegisterOverlay(objectValue.getParent());
}
protected void onValueCreatedTransferToPlatformRegister(TraceObjectValue registerValue,
TracePlatform platform, String name, LazyValues lazy) throws RegisterValueException {
Register register = platform.getLanguage().getRegister(name);
if (register == null) {
return;
}
Address hostAddr = platform.mapGuestToHost(register.getAddress());
if (hostAddr == null) {
return;
}
AddressSpace hostSpace = hostAddr.getAddressSpace();
TraceMemoryManager mem = registerValue.getTrace().getMemoryManager();
long minSnap = registerValue.getMinSnap();
if (hostSpace.isMemorySpace()) {
mem.getMemorySpace(hostSpace, true)
.setValue(platform, minSnap, new RegisterValue(register, lazy.getValue()));
}
else if (hostSpace.isRegisterSpace()) {
AddressSpace overlay = findRegisterOverlay(registerValue);
if (overlay == null) {
return;
}
mem.getMemorySpace(overlay, true)
.setValue(platform, minSnap, new RegisterValue(register, lazy.getValue()));
}
else {
throw new AssertionError();
}
}
protected void transferValueToPlatformRegister(TraceObjectValue registerValue,
TracePlatform platform, TraceMemorySpace mem, Register register) {
LazyValues lazy = new LazyValues(registerValue);
try {
mem.setValue(platform, registerValue.getMinSnap(),
new RegisterValue(register, lazy.getValue()));
}
catch (RegisterValueException e) {
Msg.error(this, e.getMessage());
}
}
protected String getRegisterName(TraceObject registerObject) {
String name = registerObject.getCanonicalPath().key();
if (PathUtils.isIndex(name)) {
return PathUtils.parseIndex(name);
}
return name;
}
protected void onSpaceAddedCheckTransferObjectToPlatformRegister(TraceObject registerObject,
TracePlatform platform, TraceMemorySpace mem) {
String name = getRegisterName(registerObject);
Register register = platform.getLanguage().getRegister(name);
if (register == null || !register.getAddressSpace().isRegisterSpace()) {
return;
}
for (TraceObjectValue registerValue : it(registerObject.getOrderedValues(Range.all(),
TargetRegister.VALUE_ATTRIBUTE_NAME, true))) {
transferValueToPlatformRegister(registerValue, platform, mem, register);
}
}
protected void onSpaceAddedCheckTransferToPlatformRegisters(TracePlatform platform,
TraceObject regContainer, TraceMemorySpace mem) {
for (TraceObjectValPath path : it(
regContainer.querySuccessorsTargetInterface(Range.all(), TargetRegister.class))) {
TraceObject registerObject =
path.getDestination(platform.getTrace().getObjectManager().getRootObject());
onSpaceAddedCheckTransferObjectToPlatformRegister(registerObject, platform, mem);
}
}
protected TraceMemorySpace getMemorySpace(TraceObject object, TraceLabelSymbol label) {
Address hostAddr = label.getAddress();
AddressSpace hostSpace = hostAddr.getAddressSpace();
TraceMemoryManager mem = label.getTrace().getMemoryManager();
if (hostSpace.isMemorySpace()) {
return mem.getMemorySpace(hostSpace, true);
}
else if (hostSpace.isRegisterSpace()) {
AddressSpace overlay = findRegisterOverlay(object);
return mem.getMemorySpace(overlay, true);
}
else {
throw new AssertionError();
}
}
protected void transferRegisterValueToLabel(TraceObjectValue registerValue,
TraceLabelSymbol label, byte[] value) {
TraceMemorySpace mem = getMemorySpace(registerValue.getParent(), label);
Address hostAddr = label.getAddress();
long minSnap = registerValue.getMinSnap();
Address address = mem.getAddressSpace().getOverlayAddress(hostAddr);
mem.putBytes(minSnap, address, ByteBuffer.wrap(value));
}
protected static <T> Iterable<T> it(Stream<T> stream) {
return () -> stream.iterator();
}
protected void transferRegisterObjectToLabel(TraceObject registerObject, TraceLabelSymbol label,
boolean isBigEndian) {
TraceMemorySpace mem = getMemorySpace(registerObject, label);
Address address = mem.getAddressSpace().getOverlayAddress(label.getAddress());
for (TraceObjectValue registerValue : it(registerObject.getOrderedValues(
label.getLifespan(), TargetRegister.VALUE_ATTRIBUTE_NAME, true))) {
LazyValues lazy = new LazyValues(registerValue);
try {
long minSnap = registerValue.getMinSnap();
mem.putBytes(minSnap, address, ByteBuffer.wrap(lazy.getBytes(isBigEndian)));
}
catch (RegisterValueException e) {
Msg.error(this, e.getMessage());
}
}
}
public void onValueCreatedTransfer(TraceObjectValue registerValue)
throws RegisterValueException {
TraceObject registerObject = registerValue.getParent();
Trace trace = registerValue.getTrace();
LazyValues lazy = new LazyValues(registerValue);
String name = getRegisterName(registerObject);
TracePlatformManager platformManager = trace.getPlatformManager();
onValueCreatedTransferToPlatformRegister(registerValue, platformManager.getHostPlatform(),
name, lazy);
for (TracePlatform platform : platformManager.getGuestPlatforms()) {
onValueCreatedTransferToPlatformRegister(registerValue, platform, name, lazy);
}
TraceNamespaceSymbolView namespaces = trace.getSymbolManager().namespaces();
TraceNamespaceSymbol nsRegMapBE =
namespaces.getGlobalNamed(InternalTracePlatform.REG_MAP_BE);
if (nsRegMapBE != null) {
for (TraceLabelSymbol label : trace.getSymbolManager()
.labels()
.getChildrenNamed(name, nsRegMapBE)) {
transferRegisterValueToLabel(registerValue, label, lazy.getBytesBigEndian());
}
}
TraceNamespaceSymbol nsRegMapLE =
namespaces.getGlobalNamed(InternalTracePlatform.REG_MAP_LE);
if (nsRegMapLE != null) {
for (TraceLabelSymbol label : trace.getSymbolManager()
.labels()
.getChildrenNamed(name, nsRegMapLE)) {
transferRegisterValueToLabel(registerValue, label, lazy.getBytesLittleEndian());
}
}
}
protected boolean isRegisterValue(TraceObjectValue objectValue) {
TraceObject parent = objectValue.getParent();
return parent != null &&
parent.getTargetSchema().getInterfaces().contains(TargetRegister.class) &&
TargetRegister.VALUE_ATTRIBUTE_NAME.equals(objectValue.getEntryKey());
}
public void onValueCreatedCheckTransfer(TraceObjectValue objectValue) {
if (isRegisterValue(objectValue)) {
try {
onValueCreatedTransfer(objectValue);
}
catch (RegisterValueException e) {
Msg.error(this, e.getMessage());
}
}
}
public void onSymbolAddedCheckTransferToLabel(TraceLabelSymbol label, boolean isBigEndian) {
TraceObjectManager objectManager = label.getTrace().getObjectManager();
TargetObjectSchema schema = objectManager.getRootSchema();
if (schema == null) {
return;
}
PathMatcher matcher = schema.searchFor(TargetRegister.class, true);
matcher = matcher.applyKeys(Align.RIGHT, List.of(label.getName()));
for (TraceObjectValPath path : it(
objectManager.getValuePaths(label.getLifespan(), matcher))) {
Object regRaw = path.getDestinationValue(objectManager.getRootObject());
if (regRaw instanceof TraceObject regObj) {
transferRegisterObjectToLabel(regObj, label, isBigEndian);
}
}
}
public void onSymbolAddedCheckTransfer(TraceSymbol symbol) {
TraceObject root = symbol.getTrace().getObjectManager().getRootObject();
if (root == null) {
return;
}
if (symbol instanceof TraceLabelSymbol label) {
TraceNamespaceSymbolView namespaces = label.getTrace().getSymbolManager().namespaces();
TraceNamespaceSymbol regMapBE =
namespaces.getGlobalNamed(InternalTracePlatform.REG_MAP_BE);
TraceNamespaceSymbol regMapLE =
namespaces.getGlobalNamed(InternalTracePlatform.REG_MAP_LE);
if (label.getParentNamespace() == regMapBE) {
onSymbolAddedCheckTransferToLabel(label, true);
}
else if (label.getParentNamespace() == regMapLE) {
onSymbolAddedCheckTransferToLabel(label, false);
}
}
}
public void onSpaceAddedCheckTransfer(Trace trace, AddressSpace space) {
TraceObject root = trace.getObjectManager().getRootObject();
if (root == null) {
return;
}
assert space.isOverlaySpace();
if (!space.isRegisterSpace()) {
return;
}
TraceMemorySpace mem = trace.getMemoryManager().getMemorySpace(space, true);
TraceObject regContainer = trace.getObjectManager()
.getObjectByCanonicalPath(
TraceObjectKeyPath.parse(mem.getAddressSpace().getName()));
if (regContainer == null || !regContainer.getTargetSchema()
.getInterfaces()
.contains(TargetRegisterContainer.class)) {
return;
}
TracePlatformManager platformManager = trace.getPlatformManager();
onSpaceAddedCheckTransferToPlatformRegisters(platformManager.getHostPlatform(),
regContainer, mem);
for (TraceGuestPlatform platform : platformManager.getGuestPlatforms()) {
onSpaceAddedCheckTransferToPlatformRegisters(platform, regContainer, mem);
}
}
protected void onMappingAddedCheckTransferRegisterObjectMemoryMapped(TraceObject registerObject,
TraceGuestPlatformMappedRange mapped) {
String name = getRegisterName(registerObject);
TraceGuestPlatform guest = mapped.getGuestPlatform();
Register register = guest.getLanguage().getRegister(name);
// TODO: Permit overlay spaces?
if (register == null || mapped.getGuestRange().contains(register.getAddress())) {
return;
}
Address hostAddr = mapped.mapGuestToHost(register.getAddress());
if (hostAddr == null) {
return;
}
TraceMemorySpace mem = registerObject.getTrace()
.getMemoryManager()
.getMemorySpace(hostAddr.getAddressSpace(), true);
for (TraceObjectValue registerValue : it(registerObject.getOrderedValues(Range.all(),
TargetRegister.VALUE_ATTRIBUTE_NAME, true))) {
transferValueToPlatformRegister(registerValue, guest, mem, register);
}
}
public void onMappingAddedCheckTransferMemoryMapped(TraceObject root,
TraceGuestPlatformMappedRange mapped) {
for (TraceObjectValPath path : it(
root.querySuccessorsTargetInterface(Range.all(), TargetRegister.class))) {
TraceObject registerObject = path.getDestination(root);
onMappingAddedCheckTransferRegisterObjectMemoryMapped(registerObject, mapped);
}
}
public void onMappingAddedCheckTransfer(TraceGuestPlatformMappedRange mapped) {
Trace trace = mapped.getHostPlatform().getTrace();
TraceObject root = trace.getObjectManager().getRootObject();
if (root == null) {
return;
}
AddressSpace guestSpace = mapped.getGuestRange().getAddressSpace();
if (guestSpace.isRegisterSpace()) {
// TODO: Optimize: create/use TraceAddressFactory.getOverlaySpaces()
for (AddressSpace space : trace.getBaseAddressFactory().getAllAddressSpaces()) {
if (!space.isOverlaySpace()) {
continue;
}
onSpaceAddedCheckTransfer(trace, space);
}
}
else if (guestSpace.isMemorySpace()) {
if (guestSpace.isOverlaySpace()) {
return;
}
onMappingAddedCheckTransferMemoryMapped(root, mapped);
}
else {
throw new AssertionError();
}
}
public void processEvent(TraceChangeRecord<?, ?> event) {
HANDLER.handleTraceChangeRecord(event);
}
private void objectValueCreated(TraceObjectValue objectValue) {
onValueCreatedCheckTransfer(objectValue);
}
private void symbolAdded(TraceSymbol symbol) {
onSymbolAddedCheckTransfer(symbol);
}
private void spaceAdded(Trace trace, AddressSpace isNull, AddressSpace space) {
onSpaceAddedCheckTransfer(trace, space);
}
private void guestMappingAdded(TraceGuestPlatform guest, TraceGuestPlatformMappedRange isNull,
TraceGuestPlatformMappedRange mapped) {
onMappingAddedCheckTransfer(mapped);
}
}

View file

@ -21,8 +21,7 @@ import java.util.concurrent.locks.ReadWriteLock;
import db.DBHandle;
import ghidra.lifecycle.Internal;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.address.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.database.DBTrace;
@ -106,11 +105,31 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
return hostAddress;
}
@Override
public AddressRange mapHostToGuest(AddressRange hostRange) {
return hostRange;
}
@Override
public AddressSetView mapHostToGuest(AddressSetView hostSet) {
return hostSet;
}
@Override
public Address mapGuestToHost(Address guestAddress) {
return guestAddress;
}
@Override
public AddressRange mapGuestToHost(AddressRange guestRange) {
return guestRange;
}
@Override
public AddressSetView mapGuestToHost(AddressSetView guestSet) {
return guestSet;
}
@Override
public MemBuffer getMappedMemBuffer(long snap, Address guestAddress) {
return trace.getMemoryManager().getBufferAt(snap, guestAddress);
@ -342,4 +361,18 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
}
return dbPlatform;
}
protected Address computeNextRegisterMin() {
AddressRange hostRegsRange = hostPlatform.getRegistersRange();
Address next = hostRegsRange == null
? hostPlatform.getAddressFactory().getRegisterSpace().getAddress(0)
: hostRegsRange.getMaxAddress().add(1);
for (DBTraceGuestPlatform guest : platformStore.asMap().values()) {
Address candidateNext = guest.computeNextRegisterMin();
if (candidateNext != null && next.compareTo(candidateNext) < 0) {
next = candidateNext;
}
}
return next;
}
}

Some files were not shown because too many files have changed in this diff Show more