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.GdbStackFrame;
import agent.gdb.manager.impl.cmd.GdbStateChangeRecord; import agent.gdb.manager.impl.cmd.GdbStateChangeRecord;
import ghidra.dbg.agent.DefaultTargetObject; import ghidra.dbg.agent.DefaultTargetObject;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetStackFrame;
import ghidra.dbg.target.schema.*; import ghidra.dbg.target.schema.*;
import ghidra.dbg.util.PathUtils; import ghidra.dbg.util.PathUtils;
import ghidra.lifecycle.Internal; import ghidra.lifecycle.Internal;
@ -36,7 +35,7 @@ import ghidra.program.model.address.Address;
attributes = { attributes = {
@TargetAttributeType(type = Void.class) }) @TargetAttributeType(type = Void.class) })
public class GdbModelTargetStackFrame extends DefaultTargetObject<TargetObject, GdbModelTargetStack> 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 FUNC_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "function";
public static final String FROM_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "from"; // TODO 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) }) @TargetAttributeType(type = Void.class) })
public class GdbModelTargetThread public class GdbModelTargetThread
extends DefaultTargetObject<TargetObject, GdbModelTargetThreadContainer> implements extends DefaultTargetObject<TargetObject, GdbModelTargetThreadContainer> implements
TargetThread, TargetExecutionStateful, TargetSteppable, GdbModelSelectableObject { TargetThread, TargetExecutionStateful, TargetSteppable, TargetAggregate,
GdbModelSelectableObject {
protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( // protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( //
TargetStepKind.ADVANCE, // TargetStepKind.ADVANCE, //
TargetStepKind.FINISH, // TargetStepKind.FINISH, //

View file

@ -29,6 +29,7 @@ import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator; 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.ProgramEmulationUtils;
import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.services.DebuggerTraceManagerService; 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.listing.Program;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceSnapshot;
import ghidra.trace.model.time.TraceTimeManager; 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 * library. This emulator will still know how to integrate with the UI, reading through to
* open programs and writing state back into the trace. * 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 @Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() { protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this); return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this);
@ -169,7 +173,7 @@ public class DebuggerEmuExampleScript extends GhidraScript {
thread.stepInstruction(); thread.stepInstruction();
snapshot = snapshot =
time.createSnapshot("Stepped to " + thread.getCounter()); 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!"); 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.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.GoToAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.GoToAction;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.async.AsyncUtils;
import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.*; import ghidra.pcode.exec.*;
@ -93,12 +94,13 @@ public abstract class DebuggerGoToTrait {
} }
public CompletableFuture<Boolean> goToSleigh(AddressSpace space, PcodeExpression expression) { public CompletableFuture<Boolean> goToSleigh(AddressSpace space, PcodeExpression expression) {
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(current); PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool, current);
CompletableFuture<byte[]> result = expression.evaluate(executor); CompletableFuture<byte[]> result =
return result.thenApply(offset -> { CompletableFuture.supplyAsync(() -> expression.evaluate(executor));
return result.thenApplyAsync(offset -> {
Address address = space.getAddress( Address address = space.getAddress(
Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian())); Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian()));
return goToAddress(address); return goToAddress(address);
}); }, AsyncUtils.SWING_EXECUTOR);
} }
} }

View file

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

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.watch;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.tuple.Pair; 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.Settings;
import ghidra.docking.settings.SettingsImpl; import ghidra.docking.settings.SettingsImpl;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.exec.*; import ghidra.pcode.exec.*;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece; import ghidra.pcode.exec.trace.*;
import ghidra.pcode.exec.trace.TraceSleighUtils;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
@ -45,7 +46,7 @@ import ghidra.program.model.mem.MemBuffer;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*; 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.memory.TraceMemoryState;
import ghidra.trace.model.symbol.TraceLabelSymbol; import ghidra.trace.model.symbol.TraceLabelSymbol;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
@ -63,7 +64,7 @@ public class WatchRow {
private SleighLanguage language; private SleighLanguage language;
private PcodeExecutor<Pair<byte[], TraceMemoryState>> executorWithState; private PcodeExecutor<Pair<byte[], TraceMemoryState>> executorWithState;
private ReadDepsPcodeExecutor executorWithAddress; private ReadDepsPcodeExecutor executorWithAddress;
private AsyncPcodeExecutor<byte[]> asyncExecutor; private PcodeExecutor<byte[]> asyncExecutor; // name is reminder to use asynchronously
private String expression; private String expression;
private String typePath; private String typePath;
@ -117,7 +118,9 @@ public class WatchRow {
protected void doTargetReads() { protected void doTargetReads() {
if (compiled != null && asyncExecutor != null) { if (compiled != null && asyncExecutor != null) {
compiled.evaluate(asyncExecutor).exceptionally(ex -> { CompletableFuture<byte[]> asyncEvaluation =
CompletableFuture.supplyAsync(() -> compiled.evaluate(asyncExecutor));
asyncEvaluation.exceptionally(ex -> {
error = ex; error = ex;
Swing.runIfSwingOrRunLater(() -> { Swing.runIfSwingOrRunLater(() -> {
provider.watchTableModel.notifyUpdated(this); provider.watchTableModel.notifyUpdated(this);
@ -174,9 +177,10 @@ public class WatchRow {
extends DirectBytesTracePcodeExecutorStatePiece { extends DirectBytesTracePcodeExecutorStatePiece {
private AddressSet reads = new AddressSet(); private AddressSet reads = new AddressSet();
public ReadDepsTraceBytesPcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, public ReadDepsTraceBytesPcodeExecutorStatePiece(TracePlatform platform, long snap,
int frame) { TraceThread thread, int frame) {
super(trace, snap, thread, frame); super(DirectBytesTracePcodeExecutorState.getDefaultThreadAccess(platform, snap, thread,
frame));
} }
@Override @Override
@ -197,7 +201,7 @@ public class WatchRow {
} }
@Override @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"); 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) { DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace(); Trace trace = coordinates.getTrace();
TracePlatform platform = DebuggerPcodeUtils.getCurrentPlatform(tool, coordinates);
ReadDepsTraceBytesPcodeExecutorStatePiece piece = ReadDepsTraceBytesPcodeExecutorStatePiece piece =
new ReadDepsTraceBytesPcodeExecutorStatePiece(trace, coordinates.getViewSnap(), new ReadDepsTraceBytesPcodeExecutorStatePiece(platform, coordinates.getViewSnap(),
coordinates.getThread(), coordinates.getFrame()); coordinates.getThread(), coordinates.getFrame());
Language language = trace.getBaseLanguage(); Language language = trace.getBaseLanguage();
if (!(language instanceof SleighLanguage)) { if (!(language instanceof SleighLanguage)) {
throw new IllegalArgumentException("Watch expressions require a SLEIGH language"); throw new IllegalArgumentException("Watch expressions require a SLEIGH language");
} }
PcodeExecutorState<Pair<byte[], Address>> paired = new DefaultPcodeExecutorState<>(piece) PcodeExecutorState<Pair<byte[], Address>> paired = new DefaultPcodeExecutorState<>(piece)
.paired(new AddressOfPcodeExecutorStatePiece(language.isBigEndian())); .paired(new AddressOfPcodeExecutorStatePiece(language));
PairedPcodeArithmetic<byte[], Address> arithmetic = new PairedPcodeArithmetic<>( PairedPcodeArithmetic<byte[], Address> arithmetic = new PairedPcodeArithmetic<>(
BytesPcodeArithmetic.forLanguage(language), AddressOfPcodeArithmetic.INSTANCE); BytesPcodeArithmetic.forLanguage(language), AddressOfPcodeArithmetic.INSTANCE);
return new ReadDepsPcodeExecutor(piece, (SleighLanguage) language, arithmetic, paired); return new ReadDepsPcodeExecutor(piece, (SleighLanguage) language, arithmetic, paired);
@ -270,11 +287,12 @@ public class WatchRow {
recompile(); recompile();
} }
if (coordinates.isAliveAndReadsPresent()) { if (coordinates.isAliveAndReadsPresent()) {
asyncExecutor = DebuggerPcodeUtils.executorForCoordinates(coordinates); asyncExecutor =
DebuggerPcodeUtils.executorForCoordinates(provider.getTool(), coordinates);
} }
executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace, executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace,
coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame()); coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame());
executorWithAddress = buildAddressDepsExecutor(coordinates); executorWithAddress = buildAddressDepsExecutor(provider.getTool(), coordinates);
} }
public void setExpression(String expression) { public void setExpression(String expression) {

View file

@ -38,6 +38,9 @@ public class DefaultDebuggerMemoryMapper implements DebuggerMemoryMapper {
} }
protected static Address toSameNamedSpace(Address addr, AddressFactory factory) { 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()) return factory.getAddressSpace(addr.getAddressSpace().getName())
.getAddress(addr.getOffset()); .getAddress(addr.getOffset());
} }

View file

@ -23,6 +23,7 @@ import ghidra.trace.model.Trace;
import ghidra.trace.model.guest.*; import ghidra.trace.model.guest.*;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;
import ghidra.util.MathUtilities; import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.database.UndoableTransaction; import ghidra.util.database.UndoableTransaction;
public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMapper { public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMapper {
@ -98,5 +99,14 @@ public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMappe
catch (AddressOverflowException e) { catch (AddressOverflowException e) {
throw new AssertionError(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; package ghidra.app.plugin.core.debug.service.emulation;
import ghidra.app.services.TraceRecorder; import ghidra.app.plugin.core.debug.service.emulation.data.*;
import ghidra.framework.plugintool.PluginTool; import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.emu.*; import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.exec.trace.BytesTracePcodeEmulator; import ghidra.pcode.exec.trace.BytesTracePcodeEmulator;
import ghidra.pcode.exec.trace.TracePcodeExecutorState; 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 * 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 public class BytesDebuggerPcodeEmulator extends BytesTracePcodeEmulator
implements DebuggerPcodeMachine<byte[]> { implements DebuggerPcodeMachine<byte[]> {
protected final PluginTool tool;
protected final TraceRecorder recorder; protected final PcodeDebuggerAccess access;
/** /**
* Create the emulator * Create the emulator
* *
* @param tool the tool creating the emulator * @param access the trace-and-debugger access shim
* @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
*/ */
public BytesDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap, public BytesDebuggerPcodeEmulator(PcodeDebuggerAccess access) {
TraceRecorder recorder) { super(access);
super(trace, snap); this.access = access;
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;
} }
@Override @Override
public TracePcodeExecutorState<byte[]> createSharedState() { public TracePcodeExecutorState<byte[]> createSharedState() {
return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0, recorder); return new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RO);
} }
@Override @Override
public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) { public TracePcodeExecutorState<byte[]> createLocalState(PcodeThread<byte[]> emuThread) {
TraceThread traceThread = return new RWTargetRegistersPcodeExecutorState(access.getDataForLocalState(emuThread, 0),
trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName()); Mode.RO);
return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0,
recorder);
} }
} }

View file

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

View file

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

View file

@ -15,9 +15,11 @@
*/ */
package ghidra.app.plugin.core.debug.service.emulation; 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.app.services.TraceRecorder;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.trace.model.Trace; import ghidra.trace.model.guest.TracePlatform;
import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.classfinder.ExtensionPoint;
/** /**
@ -41,11 +43,21 @@ public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint {
* Create the emulator * Create the emulator
* *
* @param tool the tool creating 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 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 * @param recorder if applicable, the recorder for the trace's live target
* @return the emulator * @return the emulator
*/ */
DebuggerPcodeMachine<?> create(PluginTool tool, Trace trace, long snap, default DebuggerPcodeMachine<?> create(PluginTool tool, TracePlatform platform, long snap,
TraceRecorder recorder); 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; 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.AuxDebuggerEmulatorPartsFactory;
import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator;
import ghidra.pcode.exec.trace.TracePcodeMachine; import ghidra.pcode.exec.trace.TracePcodeMachine;
@ -26,26 +23,10 @@ import ghidra.pcode.exec.trace.TracePcodeMachine;
* A Debugger-integrated emulator (or p-code machine) * A Debugger-integrated emulator (or p-code machine)
* *
* <p> * <p>
* This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator * A common implementation is an emulator with concrete plus some auxiliary state. To realize such a
* developers should use this interface, but emulator clients should not. Clients should use * machine, please see {@link AuxDebuggerPcodeEmulator} and {@link AuxDebuggerEmulatorPartsFactory}.
* {@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}.
* *
* @param <T> the type of values in the machine's memory and registers * @param <T> the type of values in the machine's memory and registers
*/ */
public interface DebuggerPcodeMachine<T> extends TracePcodeMachine<T> { 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, public static void initializeRegisters(Trace trace, long snap, TraceThread thread,
Program program, Address tracePc, Address programPc, TraceMemoryRegion stack) { Program program, Address tracePc, Address programPc, TraceMemoryRegion stack) {
TraceMemorySpace space = TraceMemoryManager memory = trace.getMemoryManager();
trace.getMemoryManager().getMemoryRegisterSpace(thread, true); TraceMemorySpace regSpace = memory.getMemoryRegisterSpace(thread, true);
if (program != null) { if (program != null) {
ProgramContext ctx = program.getProgramContext(); ProgramContext ctx = program.getProgramContext();
for (Register reg : Stream.of(ctx.getRegistersWithValues()) for (Register reg : Stream.of(ctx.getRegistersWithValues())
@ -227,20 +227,30 @@ public enum ProgramEmulationUtils {
if (rv == null || !rv.hasAnyValue()) { if (rv == null || !rv.hasAnyValue()) {
continue; continue;
} }
TraceMemoryOperations space =
reg.getAddressSpace().isRegisterSpace() ? regSpace : memory;
// Set all the mask bits // Set all the mask bits
space.setValue(snap, new RegisterValue(reg, BigInteger.ZERO).combineValues(rv)); 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()))); NumericUtilities.unsignedLongToBigInteger(tracePc.getAddressableWordOffset())));
if (stack != null) { if (stack != null) {
CompilerSpec cSpec = trace.getBaseCompilerSpec(); CompilerSpec cSpec = trace.getBaseCompilerSpec();
Address sp = cSpec.stackGrowsNegative() Address sp = cSpec.stackGrowsNegative()
? stack.getMaxAddress() ? stack.getMaxAddress()
: stack.getMinAddress(); : stack.getMinAddress();
space.setValue(snap, Register regSP = cSpec.getStackPointer();
new RegisterValue(cSpec.getStackPointer(), if (regSP != null) {
NumericUtilities.unsignedLongToBigInteger(sp.getAddressableWordOffset()))); 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; package ghidra.app.services;
import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper; 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.Trace;
import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.target.TraceObject;

View file

@ -356,23 +356,37 @@ public interface TraceRecorder {
return writeMemory(address, data); return writeMemory(address, data);
} }
if (address.isRegisterAddress()) { if (address.isRegisterAddress()) {
Language lang = getTrace().getBaseLanguage(); return writeRegister(thread, frameLevel, address, data);
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));
} }
throw new IllegalArgumentException("Address is not in a recognized space: " + address); 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 * 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; 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.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.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.program.model.lang.Language;
import ghidra.trace.model.Trace; 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 { public enum DebuggerPcodeUtils {
; ;
/** /**
* Get an executor which can be used to evaluate Sleigh expressions at the given coordinates, * Get the current platform
* asynchronously.
* *
* <p> * <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 * @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) { DebuggerCoordinates coordinates) {
Trace trace = coordinates.getTrace(); 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) { if (trace == null) {
throw new IllegalArgumentException("Coordinates have no trace"); throw new IllegalArgumentException("Coordinates have no trace");
} }
Language language = trace.getBaseLanguage(); TracePlatform platform = getCurrentPlatform(tool, coordinates);
Language language = platform.getLanguage();
if (!(language instanceof SleighLanguage)) { 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; DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool,
PcodeExecutorState<CompletableFuture<byte[]>> state; coordinates.getRecorder(), platform, coordinates.getSnap());
if (coordinates.getRecorder() == null) { PcodeExecutorState<byte[]> shared =
state = new AsyncWrappedPcodeExecutorState<>( new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RW);
new DirectBytesTracePcodeExecutorStatePiece(trace, coordinates.getViewSnap(), if (coordinates.getThread() == null) {
coordinates.getThread(), coordinates.getFrame())); return shared;
} }
else { PcodeExecutorState<byte[]> local = new RWTargetRegistersPcodeExecutorState(
state = new TraceRecorderAsyncPcodeExecutorState(coordinates.getRecorder(), access.getDataForLocalState(coordinates.getThread(), coordinates.getFrame()), Mode.RW);
coordinates.getSnap(), coordinates.getThread(), coordinates.getFrame()); return new ThreadPcodeExecutorState<>(shared, local);
} }
return new AsyncPcodeExecutor<>(slang, AsyncWrappedPcodeArithmetic.forLanguage(slang),
state); /**
* 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.PcodeThread;
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator;
import ghidra.pcode.exec.trace.PairedTracePcodeExecutorStatePiece; import ghidra.pcode.exec.trace.*;
import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory;
import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; 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 * 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. * 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, * 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 emulator the emulator
* @param concrete the concrete piece * @param concrete the concrete piece
@ -78,14 +79,14 @@ public interface AuxDebuggerEmulatorPartsFactory<U> extends AuxTraceEmulatorPart
*/ */
TracePcodeExecutorState<Pair<byte[], U>> createDebuggerSharedState( TracePcodeExecutorState<Pair<byte[], U>> createDebuggerSharedState(
AuxDebuggerPcodeEmulator<U> emulator, AuxDebuggerPcodeEmulator<U> emulator,
ReadsTargetMemoryPcodeExecutorStatePiece concrete); RWTargetMemoryPcodeExecutorStatePiece concrete);
/** /**
* Create the local (register) state of a new Debugger-integrated thread * Create the local (register) state of a new Debugger-integrated thread
* *
* <p> * <p>
* Like * 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. * 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. * 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( TracePcodeExecutorState<Pair<byte[], U>> createDebuggerLocalState(
AuxDebuggerPcodeEmulator<U> emulator, PcodeThread<Pair<byte[], U>> thread, 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 org.apache.commons.lang3.tuple.Pair;
import ghidra.app.plugin.core.debug.service.emulation.*; import ghidra.app.plugin.core.debug.service.emulation.*;
import ghidra.app.services.TraceRecorder; import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess;
import ghidra.framework.plugintool.PluginTool;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory;
import ghidra.pcode.exec.trace.TracePcodeExecutorState; import ghidra.pcode.exec.trace.TracePcodeExecutorState;
import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory;
import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator;
import ghidra.trace.model.Trace;
/** /**
* An Debugger-integrated emulator whose parts are manufactured by a * 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> public abstract class AuxDebuggerPcodeEmulator<U> extends AuxTracePcodeEmulator<U>
implements DebuggerPcodeMachine<Pair<byte[], U>> { implements DebuggerPcodeMachine<Pair<byte[], U>> {
protected final PluginTool tool;
protected final TraceRecorder recorder; protected final PcodeDebuggerAccess access;
/** /**
* Create a new emulator * Create a new emulator
* *
* @param tool the user's tool where the emulator is integrated * @param access the trace-and-debugger access shim
* @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
*/ */
public AuxDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap, public AuxDebuggerPcodeEmulator(PcodeDebuggerAccess access) {
TraceRecorder recorder) { super(access);
super(trace, snap); this.access = access;
this.tool = tool;
this.recorder = recorder;
} }
@Override @Override
protected abstract AuxDebuggerEmulatorPartsFactory<U> getPartsFactory(); protected abstract AuxDebuggerEmulatorPartsFactory<U> getPartsFactory();
@Override
public PluginTool getTool() {
return tool;
}
@Override
public TraceRecorder getRecorder() {
return recorder;
}
@Override @Override
public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() { public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() {
return getPartsFactory().createDebuggerSharedState(this, return getPartsFactory().createDebuggerSharedState(this,
new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, null, 0, recorder)); new RWTargetMemoryPcodeExecutorStatePiece(access.getDataForSharedState(), Mode.RO));
} }
@Override @Override
public TracePcodeExecutorState<Pair<byte[], U>> createLocalState( public TracePcodeExecutorState<Pair<byte[], U>> createLocalState(
PcodeThread<Pair<byte[], U>> thread) { PcodeThread<Pair<byte[], U>> thread) {
return getPartsFactory().createDebuggerLocalState(this, thread, return getPartsFactory().createDebuggerLocalState(this, thread,
new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap, new RWTargetRegistersPcodeExecutorStatePiece(access.getDataForLocalState(thread, 0),
getTraceThread(thread), 0, Mode.RO));
recorder));
} }
} }

View file

@ -284,7 +284,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest {
Register contextReg = tb.language.getContextBaseRegister(); Register contextReg = tb.language.getContextBaseRegister();
Register longMode = tb.language.getRegister("longMode"); Register longMode = tb.language.getRegister("longMode");
RegisterValue rv = tb.trace.getRegisterContextManager() 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); rv = rv.assign(longMode, BigInteger.ZERO);
Instruction checkCtx; Instruction checkCtx;
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {

View file

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

View file

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

View file

@ -18,10 +18,15 @@ package ghidra.app.plugin.core.debug.gui.stack;
import java.io.IOException; import java.io.IOException;
import ghidra.dbg.target.schema.SchemaContext; import ghidra.dbg.target.schema.SchemaContext;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName;
import ghidra.dbg.target.schema.XmlSchemaContext;
import ghidra.trace.model.Trace; 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.database.UndoableTransaction;
import ghidra.util.exception.DuplicateNameException;
public class DebuggerStackProviderObjectTest extends DebuggerStackProviderTest { public class DebuggerStackProviderObjectTest extends DebuggerStackProviderTest {
@ -51,46 +56,69 @@ public class DebuggerStackProviderObjectTest extends DebuggerStackProviderTest {
public void activateObjectsMode() throws Exception { public void activateObjectsMode() throws Exception {
// NOTE the use of index='1' allowing object-based managers to ID unique path // NOTE the use of index='1' allowing object-based managers to ID unique path
ctx = XmlSchemaContext.deserialize("" + // ctx = XmlSchemaContext.deserialize("""
"<context>" + // <context>
" <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>" + // <schema name='Session' elementResync='NEVER' attributeResync='ONCE'>
" <attribute name='Processes' schema='ProcessContainer' />" + // <attribute name='Processes' schema='ProcessContainer' />
" </schema>" + // </schema>
" <schema name='ProcessContainer' canonical='yes' elementResync='NEVER' " + // <schema name='ProcessContainer' canonical='yes' elementResync='NEVER'
" attributeResync='ONCE'>" + // attributeResync='ONCE'>
" <element index='1' schema='Process' />" + // <---- NOTE HERE <element index='1' schema='Process' />
" </schema>" + // </schema>
" <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>" + // <schema name='Process' elementResync='NEVER' attributeResync='ONCE'>
" <attribute name='Threads' schema='ThreadContainer' />" + // <attribute name='Threads' schema='ThreadContainer' />
" <attribute name='Memory' schema='RegionContainer' />" + // <attribute name='Memory' schema='RegionContainer' />
" </schema>" + // </schema>
" <schema name='ThreadContainer' canonical='yes' elementResync='NEVER' " + // <schema name='ThreadContainer' canonical='yes' elementResync='NEVER'
" attributeResync='ONCE'>" + // attributeResync='ONCE'>
" <element schema='Thread' />" + // <element schema='Thread' />
" </schema>" + // </schema>
" <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>" + // <schema name='Thread' elementResync='NEVER' attributeResync='NEVER'>
" <interface name='Thread' />" + // <interface name='Thread' />
" <attribute name='Stack' schema='Stack' />" + // <interface name='Aggregate' />
" </schema>" + // <attribute name='Stack' schema='Stack' />
" <schema name='Stack' canonical='yes' elementResync='NEVER' " + // <attribute name='Registers' schema='RegisterContainer' />
" attributeResync='ONCE'>" + // </schema>
" <interface name='Stack' />" + // <schema name='Stack' canonical='yes' elementResync='NEVER'
" <element schema='Frame' />" + // attributeResync='ONCE'>
" </schema>" + // <interface name='Stack' />
" <schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>" + // <element schema='Frame' />
" <interface name='StackFrame' />" + // </schema>
" </schema>" + // <schema name='Frame' elementResync='NEVER' attributeResync='NEVER'>
" <schema name='RegionContainer' canonical='yes' elementResync='NEVER' " + // <interface name='StackFrame' />
" attributeResync='ONCE'>" + // </schema>
" <element schema='Region' />" + // <schema name='RegisterContainer' canonical='yes' elementResync='NEVER'
" </schema>" + // attributeResync='NEVER'>
" <schema name='Region' elementResync='NEVER' attributeResync='NEVER'>" + // <interface name='RegisterContainer' />
" <interface name='MemoryRegion' />" + // <element schema='Register' />
" </schema>" + // </schema>
"</context>"); <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()) { try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); 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 = TraceMemorySpace regVals =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false); tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
row.setRawValueString("0x1234"); runSwing(() -> row.setRawValueString("0x1234"));
waitForPass(() -> { waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap(); long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap)); assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -414,7 +414,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertEquals("0x1234", row.getRawValueString()); assertEquals("0x1234", row.getRawValueString());
}); });
row.setRawValueString("1234"); // Decimal this time runSwing(() -> row.setRawValueString("1234")); // Decimal this time
waitForPass(() -> { waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap(); long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap)); assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -436,7 +436,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceMemorySpace regVals = TraceMemorySpace regVals =
tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false); tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
row.setValueString("1234"); runSwing(() -> row.setValueString("1234"));
waitForPass(() -> { waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap(); long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap)); assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -456,7 +456,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
// Wait for row to settle. TODO: Why is this necessary? // Wait for row to settle. TODO: Why is this necessary?
waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress())); waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress()));
row.setRawValueString("0x1234"); runSwing(() -> row.setRawValueString("0x1234"));
waitForPass(() -> { waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap(); long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap)); assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -468,7 +468,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
// Wait for row to settle. TODO: Why is this necessary? // Wait for row to settle. TODO: Why is this necessary?
waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress())); 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(() -> { waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap(); long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap)); assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -491,7 +491,9 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceMemoryOperations mem = tb.trace.getMemoryManager(); TraceMemoryOperations mem = tb.trace.getMemoryManager();
ByteBuffer buf = ByteBuffer.allocate(8); 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(() -> { waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap(); long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap)); assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -516,7 +518,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
TraceMemoryOperations mem = tb.trace.getMemoryManager(); TraceMemoryOperations mem = tb.trace.getMemoryManager();
ByteBuffer buf = ByteBuffer.allocate(14); ByteBuffer buf = ByteBuffer.allocate(14);
row.setValueString("\"Hello, World!\""); runSwing(() -> row.setValueString("\"Hello, World!\""));
waitForPass(() -> { waitForPass(() -> {
long viewSnap = traceManager.getCurrent().getViewSnap(); long viewSnap = traceManager.getCurrent().getViewSnap();
assertTrue(DBTraceUtils.isScratch(viewSnap)); assertTrue(DBTraceUtils.isScratch(viewSnap));
@ -561,7 +563,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
public void testEditRegisterTarget() throws Throwable { public void testEditRegisterTarget() throws Throwable {
WatchRow row = prepareTestEditTarget("r0"); WatchRow row = prepareTestEditTarget("r0");
row.setRawValueString("0x1234"); runSwing(() -> row.setRawValueString("0x1234"));
retryVoid(() -> { retryVoid(() -> {
assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0")); assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0"));
}, List.of(AssertionError.class)); }, List.of(AssertionError.class));
@ -573,7 +575,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
// Wait for the async reads to settle. // Wait for the async reads to settle.
waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress())); waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress()));
row.setRawValueString("0x1234"); runSwing(() -> row.setRawValueString("0x1234"));
retryVoid(() -> { retryVoid(() -> {
assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34),
waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 8))); waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 8)));
@ -588,7 +590,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI
assertFalse(recorder.isRegisterOnTarget(thread, r1)); assertFalse(recorder.isRegisterOnTarget(thread, r1));
assertFalse(row.isRawValueEditable()); assertFalse(row.isRawValueEditable());
row.setRawValueString("0x1234"); runSwingWithException(() -> row.setRawValueString("0x1234"));
} }
protected void setupUnmappedDataSection() throws Throwable { 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.DebuggerStateEditingService.StateEditor;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.dbg.target.TargetRegisterBank; import ghidra.dbg.target.TargetRegisterBank;
import ghidra.pcode.exec.AsyncPcodeExecutor;
import ghidra.pcode.exec.DebuggerPcodeUtils; import ghidra.pcode.exec.DebuggerPcodeUtils;
import ghidra.pcode.exec.PcodeExecutor;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.mem.MemoryAccessException;
import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.DBTraceUtils;
@ -143,8 +143,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread // NB. TraceManager should automatically activate the first thread
TraceThread thread = tb.getOrAddThread("Threads[0]", 0); TraceThread thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(
.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); env.getTool(), DebuggerCoordinates.NOWHERE.thread(thread));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123"); asm.assemble(tb.addr(0x00400000), "imm r0,#123");
@ -181,8 +181,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge
try (UndoableTransaction tid = tb.startTransaction()) { try (UndoableTransaction tid = tb.startTransaction()) {
// NB. TraceManager should automatically activate the first thread // NB. TraceManager should automatically activate the first thread
thread = tb.getOrAddThread("Threads[0]", 0); thread = tb.getOrAddThread("Threads[0]", 0);
AsyncPcodeExecutor<byte[]> executor = DebuggerPcodeUtils PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(
.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); env.getTool(), DebuggerCoordinates.NOWHERE.thread(thread));
Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0));
asm.assemble(tb.addr(0x00400000), "imm r0,#123"); 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 static org.junit.Assert.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.concurrent.CompletableFuture;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
import com.google.common.collect.Range;
import generic.Unique; import generic.Unique;
import generic.test.category.NightlyCategory; import generic.test.category.NightlyCategory;
import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assembler;
import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.assembler.Assemblers;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.services.DebuggerStaticMappingService;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock; import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.ProgramLocation; import ghidra.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.memory.TraceMemorySpace;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.trace.model.time.schedule.TraceSchedule;
@ -202,22 +207,75 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU
Trace trace = traceManager.getCurrentTrace(); Trace trace = traceManager.getCurrentTrace();
assertNotNull(trace); assertNotNull(trace);
TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads()); TraceMemoryManager mem = trace.getMemoryManager();
TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false);
assertEquals(new BigInteger("000100", 16), assertEquals(new BigInteger("000100", 16),
regs.getViewValue(0, regPC).getUnsignedValue()); mem.getViewValue(0, regPC).getUnsignedValue());
assertEquals(new BigInteger("0000", 16), regs.getViewValue(0, regW0).getUnsignedValue()); assertEquals(new BigInteger("0000", 16),
mem.getViewValue(0, regW0).getUnsignedValue());
assertEquals(new BigInteger("0800", 16), assertEquals(new BigInteger("0800", 16),
regs.getViewValue(0, regW1).getUnsignedValue()); mem.getViewValue(0, regW1).getUnsignedValue());
long scratch = long scratch =
emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY); emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY);
assertEquals(new BigInteger("000102", 16), assertEquals(new BigInteger("000102", 16),
regs.getViewValue(scratch, regPC).getUnsignedValue()); mem.getViewValue(scratch, regPC).getUnsignedValue());
assertEquals(new BigInteger("1234", 16), assertEquals(new BigInteger("1234", 16),
regs.getViewValue(scratch, regW0).getUnsignedValue()); mem.getViewValue(scratch, regW0).getUnsignedValue());
assertEquals(new BigInteger("0800", 16), 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, TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
waitRecorder(recorder);
Trace trace = recorder.getTrace(); Trace trace = recorder.getTrace();
traceManager.openTrace(trace); traceManager.openTrace(trace);
@ -464,6 +465,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge
waitOn(mb.testModel.session.requestFocus(mb.testThread1)); waitOn(mb.testModel.session.requestFocus(mb.testThread1));
TraceThread thread1 = recorder.getTraceThread(mb.testThread1); TraceThread thread1 = recorder.getTraceThread(mb.testThread1);
assertNotNull(thread1);
waitForPass(() -> assertEquals(thread1, traceManager.getCurrentThread())); waitForPass(() -> assertEquals(thread1, traceManager.getCurrentThread()));
TestTargetStack stack = mb.testThread1.addStack(); TestTargetStack stack = mb.testThread1.addStack();

View file

@ -22,18 +22,25 @@ import java.util.Map;
import org.junit.Test; 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.gui.AbstractGhidraHeadedDebuggerGUITest;
import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper; 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.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.services.ActionSource; import ghidra.app.services.ActionSource;
import ghidra.app.services.TraceRecorder; import ghidra.app.services.TraceRecorder;
import ghidra.dbg.model.TestTargetRegisterBankInThread; import ghidra.dbg.model.TestTargetRegisterBankInThread;
import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorState;
import ghidra.pcode.utils.Utils; import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.thread.TraceThread; 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 @Test
public void testExecutorEval() throws Throwable { public void testExecutorEval() throws Throwable {
@ -70,11 +77,11 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
assertTrue(rm.getRegistersOnTarget().contains(r1)); assertTrue(rm.getRegistersOnTarget().contains(r1));
}); });
AsyncPcodeExecutor<byte[]> executor = PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool,
new AsyncPcodeExecutor<>(language, AsyncWrappedPcodeArithmetic.forLanguage(language), DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread));
new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0));
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())); assertEquals(11, Utils.bytesToLong(result, result.length, language.isBigEndian()));
} }
@ -92,6 +99,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, TraceRecorder recorder = modelService.recordTarget(mb.testProcess1,
createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC);
waitRecorder(recorder);
TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1));
Trace trace = recorder.getTrace(); Trace trace = recorder.getTrace();
@ -113,13 +121,12 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge
assertTrue(rm.getRegistersOnTarget().contains(r1)); assertTrue(rm.getRegistersOnTarget().contains(r1));
}); });
TraceRecorderAsyncPcodeExecutorState asyncState = PcodeExecutor<byte[]> executor = DebuggerPcodeUtils.executorForCoordinates(tool,
new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0); DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread));
AsyncPcodeExecutor<byte[]> executor = new AsyncPcodeExecutor<>(
language, AsyncWrappedPcodeArithmetic.forLanguage(language), asyncState);
waitOn(executor.executeAsync(prog, PcodeUseropLibrary.nil())); executor.execute(prog, PcodeUseropLibrary.nil());
waitOn(asyncState.getVar(language.getRegister("r2"))); // 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"))); 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 java.util.stream.Collectors;
import ghidra.dbg.agent.DefaultTargetObject; import ghidra.dbg.agent.DefaultTargetObject;
import ghidra.dbg.target.TargetAggregate; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetObject;
import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema; import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema;
import ghidra.dbg.util.*; import ghidra.dbg.util.*;
import ghidra.dbg.util.CollectionUtils.Delta; import ghidra.dbg.util.CollectionUtils.Delta;
@ -906,4 +905,82 @@ public interface TargetObjectSchema {
schema.validateTypeAndInterfaces(element, parentPath, ent.getKey(), strict); 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(); return patterns.iterator().next();
} }
@Override
public Collection<PathPattern> getPatterns() {
return patterns;
}
protected void coalesceWilds(Set<String> result) { protected void coalesceWilds(Set<String> result) {
if (result.contains("")) { if (result.contains("")) {
result.removeIf(PathUtils::isName); result.removeIf(PathUtils::isName);
@ -179,10 +184,10 @@ public class PathMatcher implements PathPredicates {
} }
@Override @Override
public PathMatcher applyKeys(List<String> indices) { public PathMatcher applyKeys(Align align, List<String> indices) {
PathMatcher result = new PathMatcher(); PathMatcher result = new PathMatcher();
for (PathPattern pat : patterns) { for (PathPattern pat : patterns) {
result.addPattern(pat.applyKeys(indices)); result.addPattern(pat.applyKeys(align, indices));
} }
return result; return result;
} }

View file

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

View file

@ -17,13 +17,108 @@ package ghidra.dbg.util;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; 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.async.AsyncFence;
import ghidra.dbg.target.TargetObject; import ghidra.dbg.target.TargetObject;
import ghidra.dbg.util.PathUtils.PathComparator; import ghidra.dbg.util.PathUtils.PathComparator;
import ghidra.util.ReversedListIterator;
public interface PathPredicates { 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) { static boolean keyMatches(String pat, String key) {
if (key.equals(pat)) { if (key.equals(pat)) {
@ -163,6 +258,13 @@ public interface PathPredicates {
*/ */
PathPattern getSingletonPattern(); PathPattern getSingletonPattern();
/**
* Get the patterns of this predicate
*
* @return the patterns
*/
Collection<PathPattern> getPatterns();
/** /**
* Remove count elements from the right * 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 * Substitute wildcards from left to right for the given list of keys
* *
* <p> * <p>
* Takes each pattern and substitutes its wildcards for the given indices, starting from the * Takes each pattern and substitutes its wildcards for the given indices, according to the
* left and working right. This object is unmodified, and the result is returned. * given alignment. This object is unmodified, and the result is returned.
* *
* <p> * <p>
* If there are fewer wildcards in a pattern than given, only the left-most keys are taken. If * If there are fewer wildcards in a pattern than given, only the first keys are taken. If there
* there are fewer keys than wildcards in a pattern, then the right-most wildcards are left in * are fewer keys than wildcards in a pattern, then the remaining wildcards are left in the
* the resulting pattern. * 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 * @param keys the keys to substitute
* @return the pattern or matcher with the applied substitutions * @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) { default PathPredicates applyKeys(Align align, String... keys) {
return applyKeys(keys.collect(Collectors.toList())); return applyKeys(align, List.of(keys));
} }
default PathPredicates applyKeys(String... keys) { default PathPredicates applyKeys(String... keys) {
return applyKeys(List.of(keys)); return applyKeys(Align.LEFT, keys);
} }
default PathPredicates applyIntKeys(int radix, List<Integer> keys) { default PathPredicates applyIntKeys(int radix, Align align, List<Integer> keys) {
return applyKeys(keys.stream().map(k -> Integer.toString(k, radix))); 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) { 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.collect.RangeSet;
import com.google.common.primitives.UnsignedLong; import com.google.common.primitives.UnsignedLong;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.lang.Language; 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 * A state piece which can check for uninitialized reads
@ -30,16 +28,16 @@ import ghidra.trace.model.thread.TraceThread;
* <p> * <p>
* Depending on the use case, it may be desirable to ensure all reads through the course of * 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 * 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. * acceptable. See the extensions of this class for "stock" implementations.
*/ */
public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece
extends BytesTracePcodeExecutorStatePiece { extends BytesTracePcodeExecutorStatePiece {
protected class CheckedCachedSpace extends CachedSpace { protected class CheckedCachedSpace extends CachedSpace {
public CheckedCachedSpace(Language language, AddressSpace space, TraceMemorySpace source, public CheckedCachedSpace(Language language, AddressSpace space,
long snap) { PcodeTraceDataAccess backing) {
super(language, space, source, snap); super(language, space, backing);
} }
@Override @Override
@ -54,31 +52,35 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiec
} }
} }
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap, /**
TraceThread thread, int frame) { * Construct a piece
super(trace, snap, thread, frame); *
* @param data the trace-data access shim
*/
public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) {
super(data);
} }
@Override @Override
protected AbstractSpaceMap<CachedSpace> newSpaceMap() { protected AbstractSpaceMap<CachedSpace> newSpaceMap() {
return new TraceBackedSpaceMap() { return new TraceBackedSpaceMap() {
@Override @Override
protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) {
return new CheckedCachedSpace(language, space, backing, snap); 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 start the starting address of the requested read
* @param size the size of the requested read * @param size the size of the requested read
* @param uninitialized the portion of the read that is uninitialized * @param uninitialized the portion of the read that is uninitialized
* @return the adjusted size of the read * @return the adjusted size of the read
* @throws Exception to interrupt the emulator * @throws Exception to interrupt the emulator
*/ */
protected abstract int checkUninitialized(TraceMemorySpace backing, Address start, int size, protected abstract int checkUninitialized(PcodeTraceDataAccess backing, Address start,
AddressSet uninitialized); int size, AddressSet uninitialized);
} }

View file

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

View file

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

View file

@ -16,8 +16,7 @@
package ghidra.pcode.exec.trace; package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.DefaultPcodeExecutorState; import ghidra.pcode.exec.DefaultPcodeExecutorState;
import ghidra.trace.model.Trace; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.trace.model.thread.TraceThread;
/** /**
* An adapter that implements {@link TracePcodeExecutorState} given a * An adapter that implements {@link TracePcodeExecutorState} given a
@ -41,7 +40,12 @@ public class DefaultTracePcodeExecutorState<T> extends DefaultPcodeExecutorState
} }
@Override @Override
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { public PcodeTraceDataAccess getData() {
piece.writeDown(trace, snap, thread, frame); 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 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.Trace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
/** /**
* A state composing a single {@link DirectBytesTracePcodeExecutorStatePiece} * 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 * @see TraceSleighUtils
*/ */
public class DirectBytesTracePcodeExecutorState extends DefaultPcodeExecutorState<byte[]> { public class DirectBytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState<byte[]> {
private final Trace trace;
private final long snap; /**
private final TraceThread thread; * Get a trace-data access shim suitable for evaluating Sleigh expressions with thread context
private final int frame; *
* <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 * 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 snap the snap the executor will access
* @param thread the thread for reading and writing registers * @param thread the thread for reading and writing registers
* @param frame the frame 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) { int frame) {
super(new DirectBytesTracePcodeExecutorStatePiece(trace, snap, thread, frame)); this(getDefaultThreadAccess(platform, snap, thread, frame));
this.trace = trace;
this.snap = snap;
this.thread = thread;
this.frame = frame;
} }
/** /**
@ -63,6 +81,6 @@ public class DirectBytesTracePcodeExecutorState extends DefaultPcodeExecutorStat
*/ */
public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() { public PcodeExecutorState<Pair<byte[], TraceMemoryState>> withMemoryState() {
return new PairedPcodeExecutorState<>(this, 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 java.nio.ByteBuffer;
import javax.help.UnsupportedOperationException;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.generic.util.datastruct.SemisparseByteArray;
import ghidra.pcode.exec.*; import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language;
import ghidra.program.model.mem.MemBuffer; 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.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 * An executor state piece that operates directly on trace memory and registers
@ -45,37 +43,37 @@ import ghidra.trace.util.DefaultTraceTimeViewport;
* @see TraceSleighUtils * @see TraceSleighUtils
*/ */
public class DirectBytesTracePcodeExecutorStatePiece 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(); protected final SemisparseByteArray unique = new SemisparseByteArray();
private final Trace trace;
private long snap;
private TraceThread thread;
private int frame;
private final DefaultTraceTimeViewport viewport; /**
* Construct a piece
protected DirectBytesTracePcodeExecutorStatePiece(Language language, *
PcodeArithmetic<byte[]> arithmetic, Trace trace, long snap, TraceThread thread, * @param arithmetic the arithmetic for byte arrays
int frame) { * @param data the trace-data access shim
super(language, arithmetic, arithmetic); */
this.trace = trace; protected DirectBytesTracePcodeExecutorStatePiece(PcodeArithmetic<byte[]> arithmetic,
this.snap = snap; PcodeTraceDataAccess data) {
this.thread = thread; super(data.getLanguage(), arithmetic, arithmetic);
this.frame = frame; this.data = data;
this.viewport = new DefaultTraceTimeViewport(trace);
this.viewport.setSnap(snap);
} }
protected DirectBytesTracePcodeExecutorStatePiece(Language language, Trace trace, long snap, /**
TraceThread thread, int frame) { * Construct a piece
this(language, BytesPcodeArithmetic.forLanguage(language), trace, snap, thread, frame); *
* @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, @Override
int frame) { public PcodeTraceDataAccess getData() {
this(trace.getBaseLanguage(), trace, snap, thread, frame); return data;
} }
/** /**
@ -91,74 +89,7 @@ public class DirectBytesTracePcodeExecutorStatePiece
*/ */
public PcodeExecutorStatePiece<byte[], Pair<byte[], TraceMemoryState>> withMemoryState() { public PcodeExecutorStatePiece<byte[], Pair<byte[], TraceMemoryState>> withMemoryState() {
return new PairedPcodeExecutorStatePiece<>(this, return new PairedPcodeExecutorStatePiece<>(this,
new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)); new TraceMemoryStatePcodeExecutorStatePiece(data));
}
/**
* 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;
} }
@Override @Override
@ -175,24 +106,23 @@ public class DirectBytesTracePcodeExecutorStatePiece
} }
@Override @Override
protected TraceMemorySpace getForSpace(AddressSpace space, boolean toWrite) { protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, toWrite); return space;
} }
@Override @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; assert size == val.length;
int wrote = int wrote = data.putBytes(space.getAddress(offset), ByteBuffer.wrap(val));
space.putBytes(snap, space.getAddressSpace().getAddress(offset), ByteBuffer.wrap(val));
if (wrote != size) { if (wrote != size) {
throw new RuntimeException("Could not write full value to trace"); throw new RuntimeException("Could not write full value to trace");
} }
} }
@Override @Override
protected byte[] getFromSpace(TraceMemorySpace space, long offset, int size) { protected byte[] getFromSpace(AddressSpace space, long offset, int size) {
ByteBuffer buf = ByteBuffer.allocate(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) { if (read != size) {
throw new RuntimeException("Could not read full value from trace"); throw new RuntimeException("Could not read full value from trace");
} }
@ -201,6 +131,11 @@ public class DirectBytesTracePcodeExecutorStatePiece
@Override @Override
public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { 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 org.apache.commons.lang3.tuple.Pair;
import ghidra.pcode.exec.PairedPcodeExecutorState; import ghidra.pcode.exec.PairedPcodeExecutorState;
import ghidra.trace.model.Trace; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.trace.model.thread.TraceThread;
/** /**
* A trace-bound state composed of another trace-bound state and a piece * 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 @Override
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { public PcodeTraceDataAccess getData() {
left.writeDown(trace, snap, thread, frame); return left.getData();
right.writeDown(trace, snap, thread, frame); }
@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.PairedPcodeExecutorStatePiece;
import ghidra.pcode.exec.PcodeArithmetic; import ghidra.pcode.exec.PcodeArithmetic;
import ghidra.trace.model.Trace; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.trace.model.thread.TraceThread;
/** /**
* A trace-bound state piece composed of two other trace-bound pieces sharing the same address type * 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 @Override
public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { public PcodeTraceDataAccess getData() {
left.writeDown(trace, snap, thread, frame); return left.getData();
right.writeDown(trace, snap, thread, frame); }
@Override
public void writeDown(PcodeTraceDataAccess into) {
left.writeDown(into);
right.writeDown(into);
} }
@Override @Override

View file

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

View file

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

View file

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

View file

@ -16,11 +16,9 @@
package ghidra.pcode.exec.trace; package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.AccessPcodeExecutionException; import ghidra.pcode.exec.AccessPcodeExecutionException;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace; 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. * 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 public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece
extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece { extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece {
public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap, public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) {
TraceThread thread, int frame) { super(data);
super(trace, snap, thread, frame);
} }
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) { protected AccessPcodeExecutionException excFor(AddressSetView unknown) {
@ -46,7 +48,7 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece
} }
@Override @Override
protected int checkUninitialized(TraceMemorySpace backing, Address start, int size, protected int checkUninitialized(PcodeTraceDataAccess backing, Address start, int size,
AddressSet uninitialized) { AddressSet uninitialized) {
if (backing == null) { if (backing == null) {
if (!uninitialized.contains(start)) { if (!uninitialized.contains(start)) {

View file

@ -22,13 +22,11 @@ import com.google.common.primitives.UnsignedLong;
import ghidra.pcode.exec.*; import ghidra.pcode.exec.*;
import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.model.Trace;
import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemorySpace;
import ghidra.trace.model.memory.TraceMemoryState; 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} * 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)}. * though it's also exemplified in {@link #getFromSpace(TraceMemorySpace, long, int)}.
*/ */
public class TraceMemoryStatePcodeExecutorStatePiece extends public class TraceMemoryStatePcodeExecutorStatePiece extends
AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, TraceMemorySpace> { AbstractLongOffsetPcodeExecutorStatePiece<byte[], TraceMemoryState, AddressSpace> {
private final RangeMap<UnsignedLong, TraceMemoryState> unique = TreeRangeMap.create(); protected final RangeMap<UnsignedLong, TraceMemoryState> unique = TreeRangeMap.create();
private final Trace trace; protected final PcodeTraceDataAccess data;
private long snap;
private TraceThread thread;
private int frame;
private final DefaultTraceTimeViewport viewport; /**
* Construct a piece
public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, *
int frame) { * @param data the trace-data access shim
super(trace.getBaseLanguage(), */
BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()), public TraceMemoryStatePcodeExecutorStatePiece(PcodeTraceDataAccess data) {
super(data.getLanguage(),
BytesPcodeArithmetic.forLanguage(data.getLanguage()),
TraceMemoryStatePcodeArithmetic.INSTANCE); TraceMemoryStatePcodeArithmetic.INSTANCE);
this.trace = trace; this.data = data;
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;
} }
protected Range<UnsignedLong> range(long offset, int size) { protected Range<UnsignedLong> range(long offset, int size) {
@ -136,24 +95,19 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends
} }
@Override @Override
protected TraceMemorySpace getForSpace(AddressSpace space, boolean toWrite) { protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) {
return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, toWrite); return space;
} }
@Override @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 // 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 @Override
protected TraceMemoryState getFromSpace(TraceMemorySpace space, long offset, int size) { protected TraceMemoryState getFromSpace(AddressSpace space, long offset, int size) {
AddressSet set = new AddressSet(range(space.getAddressSpace(), offset, size)); return data.getViewportState(range(space, offset, size));
for (long snap : viewport.getOrderedSnaps()) {
set.delete(
space.getAddressesWithState(snap, set, state -> state == TraceMemoryState.KNOWN));
}
return set.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN;
} }
@Override @Override

View file

@ -16,15 +16,14 @@
package ghidra.pcode.exec.trace; package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.PcodeExecutorState; import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.trace.model.Trace; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
import ghidra.trace.model.thread.TraceThread;
/** /**
* An interface for trace-bound states * An interface for trace-bound states
* *
* <p> * <p>
* In particular, because this derives from {@link TracePcodeExecutorStatePiece}, such states are * 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 * derives from {@link PcodeExecutorState} so that, as the name implies, they can be used where a
* state is required. * state is required.
* *

View file

@ -16,8 +16,8 @@
package ghidra.pcode.exec.trace; package ghidra.pcode.exec.trace;
import ghidra.pcode.exec.PcodeExecutorStatePiece; import ghidra.pcode.exec.PcodeExecutorStatePiece;
import ghidra.trace.model.Trace; import ghidra.pcode.exec.trace.data.PcodeTraceAccess;
import ghidra.trace.model.thread.TraceThread; import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess;
/** /**
* A state piece which knows how to write its values back into a trace * 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 * @param <T> the type of values
*/ */
public interface TracePcodeExecutorStatePiece<A, T> extends PcodeExecutorStatePiece<A, T> { 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 * 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 * <b>NOTE:</b> This method requires a transaction to have already been started on the
* destination trace. * destination trace.
* *
* @param trace the trace to modify * @param into the destination data-access shim
* @param snap the snap within the trace * @see TracePcodeMachine#writeDown(PcodeTraceAccess)
* @param thread the thread to take register writes
* @param frame the frame for register writes
* @see TracePcodeMachine#writeDown(Trace, long, long)
*/ */
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; package ghidra.pcode.exec.trace;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeMachine;
import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.lang.Register; import ghidra.pcode.exec.trace.data.*;
import ghidra.program.model.lang.RegisterValue; import ghidra.trace.model.guest.TracePlatform;
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;
/** /**
* A p-code machine which sources its state from a trace and can record back into it * 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 * @param <T> the type of values manipulated by the machine
*/ */
public interface TracePcodeMachine<T> extends PcodeMachine<T> { 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 * Create a shared state
@ -77,44 +43,7 @@ public interface TracePcodeMachine<T> extends PcodeMachine<T> {
TracePcodeExecutorState<T> createLocalState(PcodeThread<T> thread); TracePcodeExecutorState<T> createLocalState(PcodeThread<T> thread);
/** /**
* Check if a register has a {@link TraceMemoryState#KNOWN} value for the given thread * Write the accumulated emulator state via the given trace access shim
*
* @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
* *
* <p> * <p>
* <b>NOTE:</b> This method requires a transaction to have already been started on the * <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 * threadsSnap. When using scratch space, threadsSnap should be the source snap. If populating a
* new trace, threadsSnap should probably be the destination snap. * 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 destSnap the destination snap within the trace
* @param threadsSnap the snap at which to find corresponding threads, usually the same as * @param threadsSnap the snap at which to find corresponding threads, usually the same as
* {@link #getSnap()} * {@link #getSnap()}
*/ */
default void writeDown(Trace trace, long destSnap, long threadsSnap) { default void writeDown(TracePlatform platform, long destSnap, long threadsSnap) {
TracePcodeExecutorState<T> ss = (TracePcodeExecutorState<T>) getSharedState(); writeDown(new DefaultPcodeTraceAccess(platform, destSnap, threadsSnap));
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);
}
} }
} }

View file

@ -28,7 +28,7 @@ import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressSpace; import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Language;
import ghidra.trace.model.Trace; 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.memory.TraceMemoryState;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
@ -38,32 +38,6 @@ import ghidra.trace.model.thread.TraceThread;
public enum TraceSleighUtils { 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 * 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 manipulating or initializing variables using Sleigh code. It is generally not suitable
* for use in an emulator. For that, consider {@link BytesTracePcodeEmulator}. * 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 snap the snap
* @param thread the thread, required if register space is used * @param thread the thread, required if register space is used
* @param frame the frame, for when 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, public static PcodeExecutor<byte[]> buildByteExecutor(Trace trace, long snap,
TraceThread thread, int frame) { TraceThread thread, int frame) {
DirectBytesTracePcodeExecutorState state = return buildByteExecutor(trace.getPlatformManager().getHostPlatform(), snap, thread, frame);
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);
} }
/** /**
@ -98,7 +85,29 @@ public enum TraceSleighUtils {
* when the client would also like to know if all variables involved are * when the client would also like to know if all variables involved are
* {@link TraceMemoryState#KNOWN}. * {@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 snap the snap
* @param thread the thread, required if register space is used * @param thread the thread, required if register space is used
* @param frame the frame, for when 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( public static PcodeExecutor<Pair<byte[], TraceMemoryState>> buildByteWithStateExecutor(
Trace trace, long snap, TraceThread thread, int frame) { Trace trace, long snap, TraceThread thread, int frame) {
DirectBytesTracePcodeExecutorState state = return buildByteWithStateExecutor(trace.getPlatformManager().getHostPlatform(), snap,
new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame); 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);
} }
/** /**

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 * 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 * 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, * 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 emulator the emulator
* @param concrete the concrete piece * @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.AuxEmulatorPartsFactory;
import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator;
import ghidra.pcode.exec.trace.*; 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 * 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> public abstract class AuxTracePcodeEmulator<U> extends AuxPcodeEmulator<U>
implements TracePcodeMachine<Pair<byte[], U>> { implements TracePcodeMachine<Pair<byte[], U>> {
protected final Trace trace; protected final PcodeTraceAccess access;
protected final long snap;
/** /**
* Create a new emulator * Create a new emulator
* *
* @param trace the trace from which the emulator loads state * @param access the trace access shim
* @param snap the snap from which the emulator loads state
*/ */
public AuxTracePcodeEmulator(Trace trace, long snap) { public AuxTracePcodeEmulator(PcodeTraceAccess access) {
super(trace.getBaseLanguage()); super(access.getLanguage());
this.trace = trace; this.access = access;
this.snap = snap; }
/**
* 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 @Override
protected abstract AuxTraceEmulatorPartsFactory<U> getPartsFactory(); protected abstract AuxTraceEmulatorPartsFactory<U> getPartsFactory();
@Override
public Trace getTrace() {
return trace;
}
@Override
public long getSnap() {
return snap;
}
@Override @Override
protected PcodeThread<Pair<byte[], U>> createThread(String name) { protected PcodeThread<Pair<byte[], U>> createThread(String name) {
PcodeThread<Pair<byte[], U>> thread = super.createThread(name); PcodeThread<Pair<byte[], U>> thread = super.createThread(name);
initializeThreadContext(thread); access.getDataForLocalState(thread, 0).initializeThreadContext(thread);
return thread; return thread;
} }
@Override @Override
public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() { public TracePcodeExecutorState<Pair<byte[], U>> createSharedState() {
return getPartsFactory().createTraceSharedState(this, return getPartsFactory().createTraceSharedState(this,
new BytesTracePcodeExecutorStatePiece(trace, snap, null, 0)); new BytesTracePcodeExecutorStatePiece(access.getDataForSharedState()));
} }
@Override @Override
public TracePcodeExecutorState<Pair<byte[], U>> createLocalState( public TracePcodeExecutorState<Pair<byte[], U>> createLocalState(
PcodeThread<Pair<byte[], U>> thread) { PcodeThread<Pair<byte[], U>> thread) {
return getPartsFactory().createTraceLocalState(this, 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package ghidra.pcode.exec; package ghidra.pcode.exec.trace.data;
import java.util.concurrent.CompletableFuture;
/** /**
* The state for a {@link AsyncWrappedPcodeExecutorStatePiece} * A data-access shim for a trace's memory
*
* @param <T> the type of wrapped values
*/ */
public class AsyncWrappedPcodeExecutorState<T> public interface PcodeTraceMemoryAccess extends PcodeTraceDataAccess {
extends DefaultPcodeExecutorState<CompletableFuture<T>> { // Nothing to add
public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece<T, T> piece) {
super(new AsyncWrappedPcodeExecutorStatePiece<>(piece));
}
} }

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

View file

@ -26,6 +26,8 @@ import db.*;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTrace;
import ghidra.trace.database.DBTraceManager; import ghidra.trace.database.DBTraceManager;
import ghidra.trace.model.Trace.TraceOverlaySpaceChangeType;
import ghidra.trace.util.TraceChangeRecord;
import ghidra.util.LockHold; import ghidra.util.LockHold;
import ghidra.util.database.*; import ghidra.util.database.*;
import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec;
@ -241,6 +243,8 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager {
DBTraceOverlaySpaceEntry ent = overlayStore.create(); DBTraceOverlaySpaceEntry ent = overlayStore.create();
ent.set(space.getName(), base.getName()); ent.set(space.getName(), base.getName());
trace.updateViewsAddSpaceBlock(space); trace.updateViewsAddSpaceBlock(space);
trace.setChanged(new TraceChangeRecord<>(TraceOverlaySpaceChangeType.ADDED, null,
trace, null, space));
return space; return space;
} }
} }
@ -258,6 +262,8 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager {
assert space != null; assert space != null;
factory.removeOverlaySpace(name); factory.removeOverlaySpace(name);
trace.updateViewsDeleteSpaceBlock(space); 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); super(language, compilerSpec);
} }
@Override
protected boolean validateOriginalSpace(AddressSpace originalSpace) {
return (originalSpace.isMemorySpace() || originalSpace.isRegisterSpace()) &&
!originalSpace.isOverlaySpace();
}
@Override // for peer access @Override // for peer access
protected OverlayAddressSpace addOverlayAddressSpace(String name, boolean preserveName, protected OverlayAddressSpace addOverlayAddressSpace(String name, boolean preserveName,
AddressSpace originalSpace, long minOffset, long maxOffset) { 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.database.thread.DBTraceThreadManager;
import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.context.TraceRegisterContextManager; import ghidra.trace.model.context.TraceRegisterContextManager;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.*; import ghidra.util.database.*;
import ghidra.util.database.annot.*; import ghidra.util.database.annot.*;
@ -181,10 +182,15 @@ public class DBTraceRegisterContextManager
} }
@Override @Override
public RegisterValue getValueWithDefault(Language language, Register register, long snap, public RegisterValue getValueWithDefault(TracePlatform platform, Register register, long snap,
Address address) { Address address) {
return delegateReadOr(address.getAddressSpace(), Address hostAddress = platform.mapGuestToHost(address);
m -> m.getValueWithDefault(language, register, snap, 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)); () -> 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.ImmutableTraceAddressSnapRange;
import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange;
import ghidra.trace.model.context.TraceRegisterContextSpace; import ghidra.trace.model.context.TraceRegisterContextSpace;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThread;
import ghidra.util.LockHold; import ghidra.util.LockHold;
import ghidra.util.database.*; 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 @Override
public RegisterValue getValueWithDefault(Language language, Register register, long snap, public RegisterValue getValueWithDefault(TracePlatform platform, Register register, long snap,
Address address) { Address guestAddress) {
Language language = platform.getLanguage();
try (LockHold hold = LockHold.lock(lock.readLock())) { try (LockHold hold = LockHold.lock(lock.readLock())) {
Register base = register.getBaseRegister(); Address hostAddress = platform.mapGuestToHost(guestAddress);
RegisterValue baseValue = doGetBaseValue(language, base, snap, address); if (hostAddress == null) {
if (baseValue == null) { return getDefaultValue(language, register, guestAddress);
return getDefaultValue(language, register, address);
} }
RegisterValue defaultBaseValue = getDefaultValue(language, base, address); return getValueWithDefault(language, register, snap, hostAddress, guestAddress);
if (defaultBaseValue == null) {
return baseValue.getRegisterValue(register);
}
return defaultBaseValue.combineValues(baseValue).getRegisterValue(register);
} }
} }

View file

@ -19,6 +19,8 @@ import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import db.DBRecord; import db.DBRecord;
@ -35,6 +37,9 @@ import ghidra.trace.database.DBTraceUtils.LanguageIDDBFieldCodec;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
import ghidra.trace.model.Trace.TracePlatformChangeType; import ghidra.trace.model.Trace.TracePlatformChangeType;
import ghidra.trace.model.guest.TraceGuestPlatform; 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.trace.util.TraceChangeRecord;
import ghidra.util.LockHold; import ghidra.util.LockHold;
import ghidra.util.database.*; import ghidra.util.database.*;
@ -48,6 +53,33 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
implements TraceGuestPlatform, InternalTracePlatform { implements TraceGuestPlatform, InternalTracePlatform {
public static final String TABLE_NAME = "Platforms"; 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) @DBAnnotatedObjectInfo(version = 0)
public static class DBTraceGuestLanguage extends DBAnnotatedObject { public static class DBTraceGuestLanguage extends DBAnnotatedObject {
public static final String TABLE_NAME = "Languages"; public static final String TABLE_NAME = "Languages";
@ -239,6 +271,36 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject
return mappedRange; 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 @Override
public AddressSetView getHostAddressSet() { public AddressSetView getHostAddressSet() {
return new AddressSet(hostAddressSet); 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 @Override
public Address mapGuestToHost(Address guestAddress) { public Address mapGuestToHost(Address guestAddress) {
try (LockHold hold = LockHold.lock(manager.lock.readLock())) { 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 * 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 db.DBRecord;
import ghidra.program.model.address.*; 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.trace.model.guest.TraceGuestPlatformMappedRange;
import ghidra.util.database.*; import ghidra.util.database.*;
import ghidra.util.database.annot.*; import ghidra.util.database.annot.*;
@ -53,13 +51,13 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
static DBObjectColumn LENGTH_COLUMN; static DBObjectColumn LENGTH_COLUMN;
@DBAnnotatedField(column = HOST_SPACE_COLUMN_NAME) @DBAnnotatedField(column = HOST_SPACE_COLUMN_NAME)
private int hostSpace; private int hostSpaceID;
@DBAnnotatedField(column = HOST_OFFSET_COLUMN_NAME) @DBAnnotatedField(column = HOST_OFFSET_COLUMN_NAME)
private long hostOffset; private long hostOffset;
@DBAnnotatedField(column = GUEST_LANGUAGE_COLUMN_NAME) @DBAnnotatedField(column = GUEST_LANGUAGE_COLUMN_NAME)
int guestPlatformKey; int guestPlatformKey;
@DBAnnotatedField(column = GUEST_SPACE_COLUMN_NAME) @DBAnnotatedField(column = GUEST_SPACE_COLUMN_NAME)
private int guestSpace; private int guestSpaceID;
@DBAnnotatedField(column = GUEST_OFFSET_COLUMN_NAME) @DBAnnotatedField(column = GUEST_OFFSET_COLUMN_NAME)
private long guestOffset; private long guestOffset;
@DBAnnotatedField(column = LENGTH_COLUMN_NAME) @DBAnnotatedField(column = LENGTH_COLUMN_NAME)
@ -70,6 +68,9 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
private AddressRangeImpl hostRange; private AddressRangeImpl hostRange;
private DBTraceGuestPlatform platform; private DBTraceGuestPlatform platform;
private AddressRangeImpl guestRange; private AddressRangeImpl guestRange;
private AddressSpace hostSpace;
private AddressSpace guestSpace;
private long shiftHostToGuest;
public DBTraceGuestPlatformMappedRange(DBTracePlatformManager manager, DBCachedObjectStore<?> s, public DBTraceGuestPlatformMappedRange(DBTracePlatformManager manager, DBCachedObjectStore<?> s,
DBRecord r) { DBRecord r) {
@ -83,10 +84,8 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
if (created) { if (created) {
return; return;
} }
Address hostStart = this.hostSpace = manager.trace.getBaseAddressFactory().getAddressSpace(hostSpaceID);
manager.trace.getBaseLanguage() Address hostStart = hostSpace.getAddress(hostOffset);
.getAddressFactory()
.getAddress(hostSpace, hostOffset);
Address hostEnd = hostStart.addWrap(length - 1); Address hostEnd = hostStart.addWrap(length - 1);
this.hostRange = new AddressRangeImpl(hostStart, hostEnd); 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."); throw new IOException("Table is corrupt. Got host platform in guest mapping.");
} }
this.platform = (DBTraceGuestPlatform) platform; 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); Address guestEnd = guestStart.addWrap(length - 1);
this.guestRange = new AddressRangeImpl(guestStart, guestEnd); this.guestRange = new AddressRangeImpl(guestStart, guestEnd);
this.shiftHostToGuest = guestStart.getOffset() - hostStart.getOffset();
} }
void set(Address hostStart, DBTraceGuestPlatform platform, Address guestStart, long length) { 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.hostOffset = hostStart.getOffset();
this.guestPlatformKey = (int) platform.getKey(); this.guestPlatformKey = (int) platform.getKey();
this.guestSpace = guestStart.getAddressSpace().getSpaceID(); this.guestSpace = guestStart.getAddressSpace();
this.guestSpaceID = guestSpace.getSpaceID();
this.guestOffset = guestStart.getOffset(); this.guestOffset = guestStart.getOffset();
this.length = length; this.length = length;
update(HOST_SPACE_COLUMN, HOST_OFFSET_COLUMN, GUEST_LANGUAGE_COLUMN, GUEST_SPACE_COLUMN, 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.platform = platform;
this.guestRange = new AddressRangeImpl(guestStart, guestStart.addWrap(length - 1)); this.guestRange = new AddressRangeImpl(guestStart, guestStart.addWrap(length - 1));
this.shiftHostToGuest = guestStart.getOffset() - hostStart.getOffset();
} }
@Override @Override
public Language getHostLanguage() { public InternalTracePlatform getHostPlatform() {
return manager.trace.getBaseLanguage(); return manager.hostPlatform;
}
@Override
public CompilerSpec getHostCompilerSpec() {
return manager.trace.getBaseCompilerSpec();
} }
@Override @Override
@ -141,13 +141,31 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
return guestRange; 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 @Override
public Address mapHostToGuest(Address hostAddress) { public Address mapHostToGuest(Address hostAddress) {
if (!hostRange.contains(hostAddress)) { if (!hostRange.contains(hostAddress)) {
return null; return null;
} }
long offset = hostAddress.subtract(hostRange.getMinAddress()); return doMapTo(hostAddress, guestSpace, shiftHostToGuest);
return guestRange.getMinAddress().add(offset); }
@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 @Override
@ -155,8 +173,16 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject
if (!guestRange.contains(guestAddress)) { if (!guestRange.contains(guestAddress)) {
return null; return null;
} }
long offset = guestAddress.subtract(guestRange.getMinAddress()); return doMapTo(guestAddress, hostSpace, -shiftHostToGuest);
return hostRange.getMinAddress().add(offset); }
@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 @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 db.DBHandle;
import ghidra.lifecycle.Internal; import ghidra.lifecycle.Internal;
import ghidra.program.model.address.Address; import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemBuffer;
import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTrace;
@ -106,11 +105,31 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
return hostAddress; return hostAddress;
} }
@Override
public AddressRange mapHostToGuest(AddressRange hostRange) {
return hostRange;
}
@Override
public AddressSetView mapHostToGuest(AddressSetView hostSet) {
return hostSet;
}
@Override @Override
public Address mapGuestToHost(Address guestAddress) { public Address mapGuestToHost(Address guestAddress) {
return guestAddress; return guestAddress;
} }
@Override
public AddressRange mapGuestToHost(AddressRange guestRange) {
return guestRange;
}
@Override
public AddressSetView mapGuestToHost(AddressSetView guestSet) {
return guestSet;
}
@Override @Override
public MemBuffer getMappedMemBuffer(long snap, Address guestAddress) { public MemBuffer getMappedMemBuffer(long snap, Address guestAddress) {
return trace.getMemoryManager().getBufferAt(snap, guestAddress); return trace.getMemoryManager().getBufferAt(snap, guestAddress);
@ -342,4 +361,18 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana
} }
return dbPlatform; 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