mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-31 04:09:11 +08:00
GP-2426: Refactor emulator to use trace access shims. Implement register mapping conventions.
This commit is contained in:
+2
-3
@@ -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
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -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!");
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -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();
|
||||||
|
|||||||
+32
-14
@@ -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) {
|
||||||
|
|||||||
+3
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -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() + ")");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+107
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-141
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+12
-38
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-6
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-2
@@ -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 {
|
||||||
|
|||||||
+16
-4
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-21
@@ -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();
|
|
||||||
}
|
}
|
||||||
|
|||||||
+47
@@ -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();
|
||||||
|
}
|
||||||
+16
-6
@@ -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())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
+127
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
+111
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
-43
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-173
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-43
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-108
@@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+50
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+166
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+80
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+119
@@ -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.
|
||||||
|
}
|
||||||
+28
@@ -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();
|
||||||
|
}
|
||||||
+37
@@ -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);
|
||||||
|
}
|
||||||
+38
@@ -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();
|
||||||
|
}
|
||||||
+77
@@ -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);
|
||||||
|
}
|
||||||
+57
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-118
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-101
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-40
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-154
@@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+7
-6
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-28
@@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -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()) {
|
||||||
|
|||||||
+8
-5
@@ -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()));
|
||||||
|
|||||||
+22
-15
@@ -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
|
||||||
|
|||||||
+66
-38
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-10
@@ -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
Reference in New Issue
Block a user