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
@@ -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
@@ -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, //
@@ -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!");
} }
@@ -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);
} }
} }
@@ -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();
@@ -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) {
@@ -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());
} }
@@ -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() + ")");
}
} }
} }
@@ -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;
}
}
}
@@ -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);
}
}
}
}
@@ -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);
} }
} }
@@ -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);
} }
} }
@@ -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 {
@@ -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);
} }
@@ -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();
} }
@@ -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();
}
@@ -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())));
}
} }
} }
@@ -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));
}
}
@@ -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);
}
};
}
}
@@ -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));
}
}
@@ -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);
}
};
}
}
@@ -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));
}
}
@@ -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);
}
};
}
}
@@ -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));
}
}
@@ -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);
}
};
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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.
}
@@ -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();
}
@@ -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);
}
@@ -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();
}
@@ -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);
}
@@ -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);
}
@@ -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;
@@ -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
* *
@@ -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);
}
}
@@ -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));
}
}
@@ -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);
}
}
@@ -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);
} }
} }
@@ -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));
}
}
@@ -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);
});
}
}
@@ -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);
} }
@@ -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));
} }
} }
@@ -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()) {
@@ -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()));
@@ -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
@@ -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;
}
}
} }
@@ -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 {

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